# 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.
