UNPKG

laif-ds

Version:

Design System di Laif con componenti React basati su principi di Atomic Design

307 lines (242 loc) 12.8 kB
# AppTimePicker ## Overview Standalone time picker component for selecting hours, minutes, and optionally seconds. Displays a trigger with a clock icon and opens a popover with scrollable columns for each time unit. Supports controlled/uncontrolled usage, UTC mode, clearable selection, min/max time constraints, multiple sizes, and visual variants. --- ## Props | Prop | Type | Default | Description | | ---------------- | ----------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------ | | `value` | `Date` | `undefined` | Controlled selected time as a Date object. | | `onChange` | `(date: Date \| undefined) => void` | `undefined` | Callback fired when the selected time changes. | | `onClear` | `() => void` | `undefined` | Callback fired when the value is cleared. | | `label` | `string \| React.ReactNode` | `undefined` | Label displayed above the trigger. | | `placeholder` | `string` | `"Seleziona orario"` | Placeholder text shown when no time is selected. | | `disabled` | `boolean` | `false` | Disables the time picker. | | `clearable` | `boolean` | `false` | Show a clear (X) button in the trigger to reset the selection. | | `className` | `string` | `""` | Additional CSS classes applied to the trigger element. | | `labelClassName` | `string` | `""` | CSS classes for the label element. | | `wrpClassName` | `string` | `""` | CSS classes for the outer wrapper `<div>`. | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Size variant of the trigger. | | `variant` | `"default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| "link"` | `"default"` | Visual variant of the trigger. | | `useUtc` | `boolean` | `false` | Use UTC time instead of local time. Shows a "UTC" badge. | | `withSeconds` | `boolean` | `false` | Show a seconds column in the time picker. | | `minuteStep` | `number` | `1` | Step interval for minute options (`1`-`59`). | | `secondStep` | `number` | `1` | Step interval for second options (`1`-`59`). | | `minTime` | `Date` | `undefined` | Minimum selectable time. Hours/minutes/seconds before this are disabled. | | `maxTime` | `Date` | `undefined` | Maximum selectable time. Hours/minutes/seconds after this are disabled. | | `id` | `string` | `undefined` | HTML `id` for the trigger element. | | `data-testid` | `string` | `undefined` | `data-testid` attribute for E2E testing (e.g. Playwright). | --- ## Behavior ### Time Selection - **Columns**: The popover displays scrollable columns for hours (00–23) and minutes. Minute values follow `minuteStep` and an optional seconds column follows `secondStep` when `withSeconds` is `true`. - **Scroll-to-selected**: When the popover opens, the selected value is automatically scrolled into view. - **Display format**: The trigger shows time in `HH:MM` format (or `HH:MM:SS` with `withSeconds`). ### Controlled vs Uncontrolled - **Controlled**: Pass `value` and `onChange` to manage state externally. The component detects controlled mode by checking if the `value` prop is provided (even if `undefined`). - **Uncontrolled**: Omit the `value` prop entirely. The component manages its own internal state. ### Clearable - **Trigger clear button**: When `clearable` is `true` and a value is selected, an X button appears in the trigger. - **Popover clear button**: A "Clear" button always appears at the bottom of the popover when a value is selected. - **Callbacks**: Both `onChange(undefined)` and `onClear()` are called when clearing. ### Min/Max Time Constraints - **Disabled options**: When `minTime` or `maxTime` is set, out-of-range hours are visually disabled and not selectable. - **Cascading constraints**: Minutes are constrained only when the selected hour matches the boundary hour. Seconds are constrained only when both hour and minute match. - **UTC-aware**: Constraints respect the `useUtc` setting. ### UTC Mode - When `useUtc` is `true`, all time reading/writing uses UTC methods (`getUTCHours`, `setUTCHours`, etc.). - A small "UTC" badge is displayed in the trigger. --- ## Exported Types ```ts type AppTimePickerSize = "sm" | "default" | "lg"; type AppTimePickerVariant = | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"; interface AppTimePickerProps { /* see Props table above */ } ``` --- ## Examples ### Basic ```tsx import { AppTimePicker } from "laif-ds"; import { useState } from "react"; export function BasicTimePicker() { const [time, setTime] = useState<Date | undefined>(); return ( <AppTimePicker value={time} onChange={setTime} label="Select Time" placeholder="Pick a time" /> ); } ``` ### With Seconds ```tsx import { AppTimePicker } from "laif-ds"; import { useState } from "react"; export function TimePickerWithSeconds() { const [time, setTime] = useState<Date | undefined>(); return ( <AppTimePicker value={time} onChange={setTime} withSeconds label="Time with Seconds" /> ); } ``` ### Clearable ```tsx import { AppTimePicker } from "laif-ds"; import { useState } from "react"; export function ClearableTimePicker() { const [time, setTime] = useState<Date | undefined>(new Date()); return ( <AppTimePicker value={time} onChange={setTime} clearable label="Clearable Picker" /> ); } ``` ### UTC Mode ```tsx import { AppTimePicker } from "laif-ds"; import { useState } from "react"; export function UtcTimePicker() { const [time, setTime] = useState<Date | undefined>(new Date()); return ( <AppTimePicker value={time} onChange={setTime} useUtc label="UTC Time Picker" /> ); } ``` ### Min/Max Time Constraints ```tsx import { AppTimePicker } from "laif-ds"; import { useState } from "react"; export function BusinessHoursTimePicker() { const [time, setTime] = useState<Date | undefined>(); const minTime = new Date(); minTime.setHours(9, 0, 0, 0); const maxTime = new Date(); maxTime.setHours(17, 30, 0, 0); return ( <AppTimePicker value={time} onChange={setTime} minTime={minTime} maxTime={maxTime} label="Business Hours Only" /> ); } ``` ### Sizes and Variants ```tsx import { AppTimePicker } from "laif-ds"; export function TimePickerVariants() { return ( <div className="flex flex-col gap-4"> <AppTimePicker size="sm" label="Small" /> <AppTimePicker size="default" label="Default" /> <AppTimePicker size="lg" label="Large" /> <AppTimePicker variant="outline" label="Outline Variant" /> <AppTimePicker variant="ghost" label="Ghost Variant" /> </div> ); } ``` ### Disabled ```tsx import { AppTimePicker } from "laif-ds"; export function DisabledTimePicker() { return <AppTimePicker value={new Date()} disabled label="Disabled Picker" />; } ``` ### In Dialog ```tsx import { AppTimePicker } from "laif-ds"; import { AppDialog } from "laif-ds"; import { Button } from "laif-ds"; import { useState } from "react"; export function TimePickerInDialog() { const [time, setTime] = useState<Date | undefined>(); const [open, setOpen] = useState(false); return ( <AppDialog open={open} onOpenChange={setOpen} title="Seleziona Orario" description="Scegli l'orario per la tua prenotazione." trigger={<Button>Apri Time Picker Dialog</Button>} footer={ <> <Button variant="outline" onClick={() => setOpen(false)}> Annulla </Button> <Button onClick={() => setOpen(false)}>Conferma</Button> </> } asChild > <div className="space-y-4"> <AppTimePicker value={time} onChange={setTime} label="Orario di inizio" placeholder="Seleziona un'orario" clearable /> {time && ( <div className="bg-d-muted/50 flex items-center gap-2 rounded border p-3 text-sm"> <span className="text-d-muted-foreground">Orario selezionato:</span> <span className="font-medium tabular-nums"> {time.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit", })} </span> </div> )} </div> </AppDialog> ); } ``` --- ## Helper Component: TimePickerColumn The `TimePickerColumn` component is exported and can be used independently (e.g., inside the `DatePicker` component for datetime selection). ### TimePickerColumn Props | Prop | Type | Default | Description | | ---------------- | -------------------------------- | ----------- | ------------------------------------------------------- | | `options` | `number[]` | — | Array of numeric options to display. | | `value` | `number \| undefined` | `undefined` | Currently selected value. | | `onChange` | `(value: number) => void` | — | Callback fired when an option is selected. | | `type` | `"hour" \| "minute" \| "second"` | — | Column type — determines the header label (HH, MM, SS). | | `className` | `string` | `""` | Additional CSS classes for the column wrapper. | | `disabledValues` | `Set<number>` | `undefined` | Set of values that should be disabled (not selectable). | --- ## Notes - **Sizing**: Use `size` to control the trigger height (sm: h-8, default: h-9, lg: h-10). - **Variants**: Use `variant` to change the trigger's visual style (background, border, text color). - **UTC badge**: When `useUtc` is `true`, a small "UTC" label appears in the trigger for clarity. - **Controlled detection**: The component uses `props.hasOwnProperty("value")` to detect controlled mode, so passing `value={undefined}` keeps it controlled. - **Performance**: Option arrays (0–23 for hours, 0–59 for minutes/seconds) are memoized to avoid re-creation on every render. - **Accessibility**: The trigger has proper ARIA attributes. Disabled options in the columns are not focusable. - **Integration with DatePicker**: The `TimePickerColumn` sub-component is reused by `DatePicker` when `showTime` is enabled.