Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
218 lines
4.4 KiB
TypeScript
218 lines
4.4 KiB
TypeScript
"use client"
|
|
|
|
import type { KeyboardEvent, ReactNode } from "react"
|
|
import { ChevronLeft, ChevronRight, Minus, Plus } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export function handleStepAdjustKeyDown(
|
|
e: KeyboardEvent,
|
|
onDecrease: () => void,
|
|
onIncrease: () => void,
|
|
) {
|
|
if (e.key === "ArrowUp" || e.key === "+" || (e.key === "=" && !e.shiftKey)) {
|
|
e.preventDefault()
|
|
onIncrease()
|
|
} else if (e.key === "ArrowDown" || e.key === "-") {
|
|
e.preventDefault()
|
|
onDecrease()
|
|
}
|
|
}
|
|
|
|
export function StepAdjustDecreaseButton({
|
|
onClick,
|
|
disabled,
|
|
label,
|
|
className,
|
|
}: {
|
|
onClick: () => void
|
|
disabled?: boolean
|
|
label: string
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
tabIndex={-1}
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn("size-7 rounded-full", className)}
|
|
aria-label={label}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
>
|
|
<Minus className="size-4" />
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
export function StepAdjustIncreaseButton({
|
|
onClick,
|
|
disabled,
|
|
label,
|
|
className,
|
|
}: {
|
|
onClick: () => void
|
|
disabled?: boolean
|
|
label: string
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
tabIndex={-1}
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn("size-7 rounded-full", className)}
|
|
aria-label={label}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
>
|
|
<Plus className="size-4" />
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
/** Reculer l'heure — chevron gauche (réservé aux champs horaires). */
|
|
export function StepAdjustTimeDecreaseButton({
|
|
onClick,
|
|
disabled,
|
|
label,
|
|
className,
|
|
}: {
|
|
onClick: () => void
|
|
disabled?: boolean
|
|
label: string
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
tabIndex={-1}
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn("size-7 rounded-full", className)}
|
|
aria-label={label}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
>
|
|
<ChevronLeft className="size-4" />
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
/** Avancer l'heure — chevron droit (réservé aux champs horaires). */
|
|
export function StepAdjustTimeIncreaseButton({
|
|
onClick,
|
|
disabled,
|
|
label,
|
|
className,
|
|
}: {
|
|
onClick: () => void
|
|
disabled?: boolean
|
|
label: string
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
tabIndex={-1}
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn("size-7 rounded-full", className)}
|
|
aria-label={label}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
>
|
|
<ChevronRight className="size-4" />
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
export function StepAdjustGroup({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"inline-flex items-center gap-1 rounded-full border border-border/70 bg-muted/40 px-1 py-0.5",
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function FocusableStepValue({
|
|
tabIndex,
|
|
value,
|
|
ariaLabel,
|
|
onDecrease,
|
|
onIncrease,
|
|
decreaseDisabled,
|
|
increaseDisabled,
|
|
decreaseLabel,
|
|
increaseLabel,
|
|
className,
|
|
buttonClassName,
|
|
valueWrapperClassName,
|
|
valueClassName,
|
|
}: {
|
|
tabIndex?: number
|
|
value: string
|
|
ariaLabel: string
|
|
onDecrease: () => void
|
|
onIncrease: () => void
|
|
decreaseDisabled?: boolean
|
|
increaseDisabled?: boolean
|
|
decreaseLabel: string
|
|
increaseLabel: string
|
|
className?: string
|
|
buttonClassName?: string
|
|
valueWrapperClassName?: string
|
|
valueClassName?: string
|
|
}) {
|
|
const valueNode = (
|
|
<span
|
|
tabIndex={tabIndex}
|
|
role="spinbutton"
|
|
aria-label={ariaLabel}
|
|
aria-valuetext={value}
|
|
onKeyDown={(e) => handleStepAdjustKeyDown(e, onDecrease, onIncrease)}
|
|
className={cn(
|
|
"cursor-default outline-none focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-ring",
|
|
valueClassName,
|
|
)}
|
|
>
|
|
{value}
|
|
</span>
|
|
)
|
|
|
|
return (
|
|
<StepAdjustGroup className={className}>
|
|
<StepAdjustDecreaseButton
|
|
onClick={onDecrease}
|
|
disabled={decreaseDisabled}
|
|
label={decreaseLabel}
|
|
className={buttonClassName}
|
|
/>
|
|
{valueWrapperClassName ? (
|
|
<div className={valueWrapperClassName}>{valueNode}</div>
|
|
) : (
|
|
valueNode
|
|
)}
|
|
<StepAdjustIncreaseButton
|
|
onClick={onIncrease}
|
|
disabled={increaseDisabled}
|
|
label={increaseLabel}
|
|
className={buttonClassName}
|
|
/>
|
|
</StepAdjustGroup>
|
|
)
|
|
}
|