@sesamehr/react-design-system 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/Button/index.d.ts +1 -1
  2. package/dist/Display/AiTag/AiTag.d.ts +33 -0
  3. package/dist/Display/AiTag/index.d.ts +21 -0
  4. package/dist/Display/Avatar/index.d.ts +2 -2
  5. package/dist/Display/Badge/index.d.ts +1 -1
  6. package/dist/Display/BrandLogo/BrandLogo.d.ts +53 -0
  7. package/dist/Display/BrandLogo/brandLogoArt.d.ts +29 -0
  8. package/dist/Display/BrandLogo/index.d.ts +17 -0
  9. package/dist/Display/FileCard/FileCard.d.ts +102 -41
  10. package/dist/Display/FileCard/index.d.ts +31 -0
  11. package/dist/Display/ImageAttachment/ImageAttachment.d.ts +58 -0
  12. package/dist/Display/ImageAttachment/index.d.ts +2 -0
  13. package/dist/Display/LinkPreview/LinkPreview.d.ts +65 -0
  14. package/dist/Display/LinkPreview/index.d.ts +1 -0
  15. package/dist/Display/LocationAttachment/LocationAttachment.d.ts +71 -0
  16. package/dist/Display/LocationAttachment/index.d.ts +1 -0
  17. package/dist/Display/VideoAttachment/VideoAttachment.d.ts +78 -0
  18. package/dist/Display/VideoAttachment/index.d.ts +1 -0
  19. package/dist/Display/VoiceMessage/VoiceMessage.d.ts +103 -0
  20. package/dist/Display/VoiceMessage/index.d.ts +2 -0
  21. package/dist/Display/VoiceMessage/toWaveformPeaks.d.ts +58 -0
  22. package/dist/Display/VoiceMessage/useVoiceWaveform.d.ts +90 -0
  23. package/dist/Feedback/Dialog/Dialog.d.ts +13 -1
  24. package/dist/Feedback/Dialog/DialogOverlay.d.ts +12 -1
  25. package/dist/Feedback/Drawer/Drawer.d.ts +68 -0
  26. package/dist/Feedback/Drawer/DrawerBody.d.ts +18 -0
  27. package/dist/Feedback/Drawer/DrawerContent.d.ts +36 -0
  28. package/dist/Feedback/Drawer/DrawerDescription.d.ts +10 -0
  29. package/dist/Feedback/Drawer/DrawerExpand.d.ts +23 -0
  30. package/dist/Feedback/Drawer/DrawerTitle.d.ts +13 -0
  31. package/dist/Feedback/Drawer/index.d.ts +7 -0
  32. package/dist/Feedback/Drawer/useDrawer.d.ts +30 -0
  33. package/dist/Feedback/Progress/index.d.ts +1 -1
  34. package/dist/Forms/Inputs/AiSearch/AiSearch.d.ts +67 -0
  35. package/dist/Forms/Inputs/AiSearch/AiSearchAsk.d.ts +34 -0
  36. package/dist/Forms/Inputs/AiSearch/AiSearchPanel.d.ts +50 -0
  37. package/dist/Forms/Inputs/AiSearch/AiSearchSend.d.ts +30 -0
  38. package/dist/Forms/Inputs/AiSearch/AiSearchShortcut.d.ts +23 -0
  39. package/dist/Forms/Inputs/AiSearch/AiSearchSuggestion.d.ts +27 -0
  40. package/dist/Forms/Inputs/AiSearch/index.d.ts +6 -0
  41. package/dist/Forms/Inputs/AiSearch/useAiSearch.d.ts +37 -0
  42. package/dist/Forms/Inputs/TimePicker/TimePicker.d.ts +64 -0
  43. package/dist/Forms/Inputs/TimePicker/TimePickerField.d.ts +35 -0
  44. package/dist/Forms/Inputs/TimePicker/TimePickerList.d.ts +23 -0
  45. package/dist/Forms/Inputs/TimePicker/TimePickerWheel.d.ts +26 -0
  46. package/dist/Forms/Inputs/TimePicker/TimePickerWheelColumn.d.ts +23 -0
  47. package/dist/Forms/Inputs/TimePicker/index.d.ts +6 -0
  48. package/dist/Forms/Inputs/TimePicker/time.d.ts +41 -0
  49. package/dist/Forms/Inputs/TimePicker/useTimeField.d.ts +62 -0
  50. package/dist/Forms/Inputs/TimePicker/useTimeList.d.ts +53 -0
  51. package/dist/Forms/Inputs/TimePicker/useTimePicker.d.ts +35 -0
  52. package/dist/Forms/Inputs/TimePicker/useWheelColumn.d.ts +71 -0
  53. package/dist/Forms/Inputs/index.d.ts +1 -0
  54. package/dist/Navigation/Tabs/Tabs.d.ts +18 -0
  55. package/dist/Navigation/Tabs/TabsContent.d.ts +7 -0
  56. package/dist/Navigation/Tabs/TabsList.d.ts +10 -0
  57. package/dist/Navigation/Tabs/TabsTrigger.d.ts +20 -0
  58. package/dist/Navigation/Tabs/index.d.ts +4 -0
  59. package/dist/internal/AttachmentMedia/AttachmentMedia.d.ts +38 -0
  60. package/dist/internal/AttachmentMedia/index.d.ts +36 -0
  61. package/dist/internal/index.d.ts +1 -0
  62. package/dist/main.d.ts +12 -2
  63. package/dist/react-design-system.css +1 -1
  64. package/dist/react-design-system.js +12581 -10218
  65. package/dist/react-design-system.umd.cjs +108 -74
  66. package/package.json +3 -2
  67. package/src/assets/styles/theme-v3.css +99 -2
  68. package/src/assets/styles/theme-v3.test.ts +5 -5
@@ -0,0 +1,67 @@
1
+ export interface AiSearchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'value' | 'defaultValue' | 'onChange' | 'onSelect' | 'className' | 'children'> {
2
+ /**
3
+ * The answer is being waited for: the field shows the thinking row and
4
+ * `OxAiSearchSend` turns into a stop button.
5
+ */
6
+ thinking?: boolean;
7
+ /** What is in the field. */
8
+ value?: string;
9
+ onValueChange?: (value: string) => void;
10
+ /** A row was chosen — by click or by Enter. Carries that row's `value`. */
11
+ onSelect?: (value: string) => void;
12
+ /** Escape was pressed. What closing means is the caller's to decide. */
13
+ onClose?: () => void;
14
+ /**
15
+ * Inside the pill, after the field: `OxAiSearchShortcut` and
16
+ * `OxAiSearchSend`. The Vue original's `trailing` slot.
17
+ */
18
+ trailing?: React.ReactNode;
19
+ /** Lands on the root, which is the card the field sits inside. */
20
+ className?: string;
21
+ children?: React.ReactNode;
22
+ dataTestid: string;
23
+ }
24
+ /**
25
+ * The metasearch pill: search the application and ask Sesame AI from one field.
26
+ * Mirrors `@sesame/orxata-core` AiSearch.
27
+ *
28
+ * Built on cmdk, the way `OxSearchPanel` is, and for the reason the Vue port is
29
+ * built on reka's `Listbox` rather than a combobox primitive. The board asks for
30
+ * `role="combobox"`, and that is an ARIA pattern rather than a primitive: a
31
+ * combobox's *model* is "pick a value, the field then shows it", which is wrong
32
+ * here twice over — the typed words have to survive a highlight moving over the
33
+ * rows, and a row that navigated somewhere is not a value this field holds
34
+ * afterwards. What is wanted is the half cmdk brings: the roving highlight with
35
+ * focus left in the field.
36
+ *
37
+ * `role="listbox"` and the row collection land on `OxAiSearchPanel`'s row
38
+ * container, which is cmdk's `CommandList` — not on this root. cmdk also owns
39
+ * `aria-controls`, pointing the input at that list; with nothing typed there is
40
+ * no panel and no list, which is why `aria-expanded` has to be `open` and not
41
+ * cmdk's hard-coded `true`. The input below is handed to `CommandInput` through
42
+ * `asChild` precisely so that one attribute can be corrected: Radix's `Slot`
43
+ * lets the child's props win over the primitive's, and cmdk writes its own
44
+ * *after* the spread.
45
+ *
46
+ * **The card is this element, not the panel's.** The board draws the field
47
+ * *inside* the open panel, so opening is this root growing a surface around
48
+ * itself rather than a second box appearing below. `OxAiSearchPanel` is
49
+ * everything under the field.
50
+ *
51
+ * **It does not make itself a modal.** The board's behaviour notes say the pill
52
+ * opens centred over an overlay with the page scroll locked, and that is a
53
+ * composition the application makes — `OxDialog` already owns the overlay, the
54
+ * focus trap and the scroll lock, and a search field that decided it was modal
55
+ * could not be put in a header. The MDX shows the composition.
56
+ *
57
+ * **Controlled when the caller listens, not when it passes a value.** The Vue
58
+ * original is a `defineModel` with a `''` default, so `<OxAiSearch value="x" />`
59
+ * with no listener is an ordinary field that still types. The gate is the
60
+ * handler, and a new `value` still wins while uncontrolled so a story's args
61
+ * panel steers it.
62
+ *
63
+ * Everything the caller writes that is not named above lands on the input, not
64
+ * on the root: a `placeholder`, a `name` or an `aria-label` belongs on the
65
+ * control, and `className` is declared separately so it reaches the card.
66
+ */
67
+ export declare const AiSearch: import('react').ForwardRefExoticComponent<AiSearchProps & import('react').RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,34 @@
1
+ import { CommandItem } from 'cmdk';
2
+ export interface AiSearchAskProps extends Omit<React.ComponentPropsWithoutRef<typeof CommandItem>, 'onSelect' | 'children'> {
3
+ /**
4
+ * Handed back through the root's `onSelect`. Defaults to `ask`, which is the
5
+ * value a caller switches on to open the answer.
6
+ */
7
+ value?: string;
8
+ dataTestid: string;
9
+ }
10
+ /**
11
+ * «Ask Sesame AI» — the first row of the panel, and the one Enter runs. Mirrors
12
+ * `@sesame/orxata-core` AiSearchAsk.
13
+ *
14
+ * Always first, which the board says twice: the behaviour notes ("la fila
15
+ * «Preguntar a Sesame IA» siempre es la primera") and the footer hint that
16
+ * promises Enter asks the AI. cmdk highlights the first row whenever the
17
+ * collection changes, exactly as reka does, so putting this row first is what
18
+ * makes that promise true — there is no code here enforcing the order, only a
19
+ * caller writing it before the groups.
20
+ *
21
+ * It quotes the query back off the context rather than taking it as a prop. The
22
+ * root already holds it, and a copy passed in could show yesterday's words while
23
+ * the field shows today's.
24
+ *
25
+ * The `↵` chip is `aria-hidden`: it is a picture of the key that already runs
26
+ * the row, and a screen reader reading "corner down left" after the row's name
27
+ * is noise. The row's own name is its text.
28
+ *
29
+ * One child inside `CommandItem asChild` and one only. Radix's `Slot`, which
30
+ * cmdk uses, throws on a second child rather than merging onto the first the way
31
+ * reka does — so the Vue original's warning about a silently role-less sibling
32
+ * is a hard error here instead.
33
+ */
34
+ export declare const AiSearchAsk: import('react').ForwardRefExoticComponent<AiSearchAskProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,50 @@
1
+ export interface AiSearchPanelProps extends React.ComponentPropsWithoutRef<'div'> {
2
+ dataTestid: string;
3
+ }
4
+ /**
5
+ * Everything under the field: the ask row, the suggestions, the results and the
6
+ * keyboard hints. Mirrors `@sesame/orxata-core` AiSearchPanel.
7
+ *
8
+ * Not the card. The board draws the field *inside* the open panel, so the
9
+ * surface belongs to `OxAiSearch` — this is the part of it below the row, and it
10
+ * is named `Panel` because that is what the board calls the open state.
11
+ *
12
+ * It shows itself on the first keystroke and hides when the field empties,
13
+ * reading that off the root's context. The caller does not write the condition:
14
+ * it is the root's to know, and a caller's copy of it would be a second one to
15
+ * keep in step.
16
+ *
17
+ * **`CommandList` is in here and not in the root, because that is where the role
18
+ * goes.** It carries `role="listbox"` *and* gathers the rows into the collection
19
+ * the arrows walk, exactly as reka's `ListboxContent` does on the Vue side. Put
20
+ * it in the root and nothing registers: no `aria-activedescendant`, no
21
+ * highlight, and Enter selects nothing — all of which look exactly like a
22
+ * component that simply has no keyboard.
23
+ *
24
+ * The hints sit **outside** that element. A `listbox` holds options, and three
25
+ * lines of keyboard legend are not options; inside, a screen reader would walk
26
+ * them as rows that cannot be chosen.
27
+ *
28
+ * **Two things cmdk decides that this component cannot.** `CommandList` is not
29
+ * handed an element through `asChild`: cmdk's own slot helper reads `.render`
30
+ * off the child's type, so a plain `<div>` there is a crash rather than a merge,
31
+ * and the class and the `data-slot` go straight onto cmdk's element instead.
32
+ * And it writes its attributes *after* the spread, so its `aria-label` — a
33
+ * hard-coded English "Suggestions" — stands where reka names the Vue list
34
+ * nothing. `OxSearchPanelList` carries exactly the same name for the same
35
+ * reason; it is a real difference between the two ports rather than a choice
36
+ * made here.
37
+ *
38
+ * cmdk also wraps the rows in a sizer element of its own, which reka has no
39
+ * equivalent of. It is given `display: contents` below so that the rows stay
40
+ * direct flex children of this list instead of collapsing into one — the single
41
+ * declaration in this component that the Vue original does not have, which is
42
+ * why `AiSearchPanel.vue` is named in `parity-component`'s `allowCss`.
43
+ *
44
+ * It declares none of the row styling itself: the sections inside are
45
+ * `OxSearchPanelGroup` and `OxSearchPanelItem`, which read their padding, corner
46
+ * and type size off `--ox-listbox-item-*`. Those are declared on this element,
47
+ * which is the only place that can — a rule in this file aimed at the shared row
48
+ * would weigh exactly what the shared row's own rule weighs.
49
+ */
50
+ export declare const AiSearchPanel: import('react').ForwardRefExoticComponent<AiSearchPanelProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,30 @@
1
+ export interface AiSearchSendProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
2
+ /** The query should be asked. Carries what is in the field. */
3
+ onSend?: (query: string) => void;
4
+ /** The answer in flight should be cancelled. */
5
+ onStop?: () => void;
6
+ dataTestid: string;
7
+ }
8
+ /**
9
+ * The round button at the right of the field: ask, or stop. Mirrors
10
+ * `@sesame/orxata-core` AiSearchSend.
11
+ *
12
+ * Three looks and one element, because the board draws one element: grey and
13
+ * inert with an empty field, brand once there is a query, and the foreground
14
+ * colour with a stop glyph while the answer is coming. It reads all three off
15
+ * the root's context rather than taking props, so the caller writes
16
+ * `<OxAiSearchSend>` once and never has to keep a `thinking` flag in step in two
17
+ * places.
18
+ *
19
+ * It owns its own `onSend` and `onStop` rather than routing them through the
20
+ * root. The caller writes this element, so it can listen on it directly — and
21
+ * the alternative, a handler passed down through the context, is the prop
22
+ * drilling the context exists to avoid.
23
+ *
24
+ * Its `data-slot` is `send` and not the controller role the other buttons inside
25
+ * a field wear. That role means "I am the thing the label names", and the thing a
26
+ * label names here is the input: this button sits inside the field rather than
27
+ * beside it, so an `OxFieldGroup` never has to order it and nothing is gained by
28
+ * claiming the role. Its own `aria-label` is what names it.
29
+ */
30
+ export declare const AiSearchSend: import('react').ForwardRefExoticComponent<AiSearchSendProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,23 @@
1
+ export interface AiSearchShortcutProps extends React.ComponentPropsWithoutRef<'kbd'> {
2
+ dataTestid: string;
3
+ }
4
+ /**
5
+ * The global shortcut, shown in the field until there is something in it.
6
+ * Mirrors `@sesame/orxata-core` AiSearchShortcut.
7
+ *
8
+ * It hides itself rather than being hidden, which is the house pattern for a
9
+ * part whose visibility follows the root's state: the root never sees this
10
+ * element — the caller writes it into `trailing` — so a condition up there is
11
+ * not available, and a `show` prop would be the same condition written twice.
12
+ *
13
+ * Figma spells it as a `Show shortcut` boolean, and here it is whether the part
14
+ * is written at all. The board's own geometry draws the chip in all four states;
15
+ * its properties table says "solo Default y Focus" and its behaviour notes say
16
+ * "el atajo se oculta al escribir". Two prose sources against the geometry, and
17
+ * the geometry is only showing the boolean's default — so the prose wins and the
18
+ * chip goes on the query.
19
+ *
20
+ * The content is `children` because `⌘K` is a Mac keyboard: an application on
21
+ * Windows writes `Ctrl K` and nothing here should pretend to know which.
22
+ */
23
+ export declare const AiSearchShortcut: import('react').ForwardRefExoticComponent<AiSearchShortcutProps & import('react').RefAttributes<HTMLElement>>;
@@ -0,0 +1,27 @@
1
+ import { CommandItem } from 'cmdk';
2
+ export interface AiSearchSuggestionProps extends Omit<React.ComponentPropsWithoutRef<typeof CommandItem>, 'onSelect'> {
3
+ /** Handed back through the root's `onSelect`. Unique within the panel. */
4
+ value: string;
5
+ dataTestid: string;
6
+ }
7
+ /**
8
+ * One suggested question, as a chip. Mirrors `@sesame/orxata-core`
9
+ * AiSearchSuggestion.
10
+ *
11
+ * A row of the same listbox as the results, not a decoration beside it: the
12
+ * arrows have to reach it, which is what the footer's "↑↓ navigate" promises,
13
+ * and it is picked the same way — the root calls `onSelect` with this `value`.
14
+ * cmdk walks the rows in document order, so a row of chips is arrowed through
15
+ * left to right.
16
+ *
17
+ * A `<button>` rather than cmdk's default `div`, so it is a control in its own
18
+ * right for anything that does not go through the listbox's roving focus.
19
+ * `CommandItem` adds `role="option"`, which replaces the button role — the
20
+ * element stays a real button for the pointer and reads as an option to a screen
21
+ * reader, which is both halves right.
22
+ *
23
+ * The label is `children`: a suggestion is whatever the application's model
24
+ * suggested, and a design system that shipped a list of them would be wrong by
25
+ * the following release.
26
+ */
27
+ export declare const AiSearchSuggestion: import('react').ForwardRefExoticComponent<AiSearchSuggestionProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,6 @@
1
+ export { AiSearch, type AiSearchProps } from './AiSearch.tsx';
2
+ export { AiSearchAsk, type AiSearchAskProps } from './AiSearchAsk.tsx';
3
+ export { AiSearchPanel, type AiSearchPanelProps } from './AiSearchPanel.tsx';
4
+ export { AiSearchSend, type AiSearchSendProps } from './AiSearchSend.tsx';
5
+ export { AiSearchShortcut, type AiSearchShortcutProps, } from './AiSearchShortcut.tsx';
6
+ export { AiSearchSuggestion, type AiSearchSuggestionProps, } from './AiSearchSuggestion.tsx';
@@ -0,0 +1,37 @@
1
+ export interface AiSearchContextValue {
2
+ /** What is in the field right now. */
3
+ query: string;
4
+ /** Whether the answer is being waited for. */
5
+ thinking: boolean;
6
+ /**
7
+ * Whether the panel is showing. Derived from the query rather than held: the
8
+ * board opens the panel on the first keystroke and closes it when the field
9
+ * empties, so a second source of truth could only disagree with the first.
10
+ */
11
+ open: boolean;
12
+ /** Called with a row's `value` when that row is chosen. */
13
+ select: (value: string) => void;
14
+ }
15
+ export declare const AiSearchContext: import('react').Context<AiSearchContextValue | null>;
16
+ /**
17
+ * The state every part reads to decide what it looks like and whether it is
18
+ * there at all.
19
+ *
20
+ * Four parts need it and none of them need the same thing: the shortcut hides
21
+ * once there is a query, the send button changes colour on the query and its
22
+ * icon on `thinking`, the ask row quotes the query back, and the panel shows
23
+ * itself when the field is not empty. Passing those down as props would be prop
24
+ * drilling through `children` — the caller writes the parts, so the root never
25
+ * sees them.
26
+ *
27
+ * Throws rather than returning `undefined`: a part outside the root would
28
+ * otherwise render looking fine and never update, which is the failure that
29
+ * takes longest to notice. The message is the Vue original's, word for word.
30
+ *
31
+ * **No `listboxId` here, unlike the Vue context.** reka leaves `aria-controls`
32
+ * to the author, so the Vue root generates an id and passes it down to the
33
+ * element `ListboxContent` lands on. cmdk generates that id itself and wires
34
+ * both ends — `aria-controls` on the input, `id` on the list — so carrying a
35
+ * second one would be an id nothing points at.
36
+ */
37
+ export declare const useAiSearch: () => AiSearchContextValue;
@@ -0,0 +1,64 @@
1
+ import { TimeFormat } from './time.ts';
2
+ export interface TimePickerProps {
3
+ /** Two segments or three. `OxTimePickerList` only supports `HH:MM`. */
4
+ format?: TimeFormat;
5
+ disabled?: boolean;
6
+ /**
7
+ * The chosen time — `"15:17"`, or `"15:17:30"` under `HH:MM:SS`. `undefined`
8
+ * for as long as any segment is still empty.
9
+ */
10
+ value?: string;
11
+ onValueChange?: (value: string | undefined) => void;
12
+ /** Whether the dropdown is showing. */
13
+ open?: boolean;
14
+ onOpenChange?: (open: boolean) => void;
15
+ /**
16
+ * Lands on the field, which is the only part of this in the page's layout —
17
+ * the panel is portalled.
18
+ */
19
+ className?: string;
20
+ /** The dropdown's body. Falls back to `<OxTimePickerWheel>`. */
21
+ children?: React.ReactNode;
22
+ dataTestid: string;
23
+ }
24
+ /**
25
+ * A field showing a time segment by segment, and a dropdown to pick one without
26
+ * typing. Mirrors `@sesame/orxata-core` TimePicker.
27
+ *
28
+ * The root owns the value and nothing else is allowed to. The field, the
29
+ * 15-minute list and the wheel are three ways of reading and writing the same
30
+ * three numbers, so the format handling, the empty-segment state and the
31
+ * conversion to and from the value all live here — see `useTimePicker.ts`.
32
+ *
33
+ * **The body is composed, not configured.** The board models the choice as a
34
+ * `Type = List | Wheel` property; this ships it as `<OxTimePickerList>` and
35
+ * `<OxTimePickerWheel>` in `children` instead, for three reasons. A snapping,
36
+ * looping, momentum-carrying wheel and a 96-row listbox are two unrelated
37
+ * behaviours, and one component holding both is what the single responsibility
38
+ * rule exists to prevent. The board's own constraint — *List is always `HH:MM`*
39
+ * — becomes a property of the list component rather than a runtime check on a
40
+ * prop combination the variant axis lets you select and no design exists for.
41
+ * And a `type` prop would be the fifth on a component that already has to carry
42
+ * the format, the disabled state and the class.
43
+ *
44
+ * The wheel is the fallback, because it is the one that supports both formats.
45
+ *
46
+ * `value` is a string, and it is `undefined` for as long as any segment is still
47
+ * empty — which the field draws as `--`: a half-typed time is not a time, and
48
+ * there is no shape of `string` that honestly says "17 past some hour".
49
+ *
50
+ * **Controlled when the caller listens, not when it passes a value.** The Vue
51
+ * original is a `defineModel`, which given a value and no `update:` listener
52
+ * seeds itself from it and goes on working, so `<OxTimePicker value="15:17" />`
53
+ * has to keep typing. Gating on `value !== undefined` alone would turn that into
54
+ * a field pinned to its initial value with nothing able to move it; gating on
55
+ * the handler keeps both paths, and a new `value` still wins while uncontrolled
56
+ * so the Storybook args panel steers it. Same for `open`.
57
+ *
58
+ * No `defaultValue` / `defaultOpen`: the Vue original publishes neither, and a
59
+ * prop with no twin on the other side is drift rather than convenience.
60
+ */
61
+ export declare const TimePicker: {
62
+ ({ format, disabled, value, onValueChange, open, onOpenChange, className, children, dataTestid, }: TimePickerProps): import("react/jsx-runtime").JSX.Element;
63
+ displayName: string;
64
+ };
@@ -0,0 +1,35 @@
1
+ export type TimePickerFieldProps = React.ComponentPropsWithoutRef<'div'>;
2
+ /**
3
+ * The closed control: the segments on the left, a clock on the right. Mirrors
4
+ * `@sesame/orxata-core` TimePickerField.
5
+ *
6
+ * **Radix has no `TimeField`.** The Vue original is reka's `TimeFieldRoot` /
7
+ * `TimeFieldInput`, and everything about editing a segment comes out of that
8
+ * primitive — the per-segment `role="spinbutton"` with its `aria-value*`, the
9
+ * arrow keys, typing two digits and rolling on to the next segment, Backspace
10
+ * emptying one, and holding a half-entered time that is not yet a value. None
11
+ * of it has a Radix counterpart, so it is written out in `useTimeField.ts`,
12
+ * which reproduces reka's rendered DOM, its ARIA and its keyboard. Read that
13
+ * file's docblock before changing anything here.
14
+ *
15
+ * `role="group"` is what makes `useFieldControlGroup` work: a bare `<div>`
16
+ * cannot take a name from the author, so without the role the `aria-labelledby`
17
+ * below would be dropped on the floor and the segments would announce as two
18
+ * anonymous spin buttons. Same reasoning as `OxInputOtp`, and the same hook —
19
+ * one `id` cannot be on two focusable segments.
20
+ *
21
+ * **The clock is a button, where the board draws a bare glyph.** It is the only
22
+ * deviation in this file and it is deliberate: the segments own clicks and the
23
+ * keyboard inside the field, so a field that was itself the trigger would mean
24
+ * clicking a segment opened the panel, and a glyph that was not a button would
25
+ * leave the dropdown unreachable without a mouse. The button has no fill, no
26
+ * padding and no border, so it is the 16px glyph the board draws and nothing
27
+ * more.
28
+ *
29
+ * It takes no size prop: the height, radius, type and gap come from the
30
+ * `--ox-controller-*` variables every input in the package reads, so
31
+ * `inputSizeClass[size]` on any ancestor sizes it. Its declarations are
32
+ * `OxSelectTrigger`'s and `OxDatePickerTrigger`'s, so the three sit level in one
33
+ * form.
34
+ */
35
+ export declare const TimePickerField: import('react').ForwardRefExoticComponent<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ export type TimePickerListProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'children'>;
2
+ /**
3
+ * The list dropdown: every quarter hour of the day, as rows. Mirrors
4
+ * `@sesame/orxata-core` TimePickerList.
5
+ *
6
+ * **`HH:MM` only**, and that is the board's rule rather than a shortcut here —
7
+ * a list stepping in quarter hours with the seconds pinned to `00` adds a
8
+ * column of zeroes and no choice. The Figma variant axis lets `List` be paired
9
+ * with `HH:MM:SS` because the property is not scoped to `Type`, but no such
10
+ * component is drawn. Pairing them is a modelling slip, so this warns in
11
+ * development and goes on showing `HH:MM` rows; the seconds of whatever is
12
+ * picked are set to zero.
13
+ *
14
+ * The Vue original gets the roles, the roving cursor, the arrows, Home/End and
15
+ * the typeahead from reka's standalone `Listbox`, which has no counterpart in
16
+ * the React stack — see `useTimeList.ts` for why cmdk is the wrong model here
17
+ * rather than a missing one, and for what had to be written out instead. What
18
+ * *is* shared is the row: the internal `ListboxItem` gives the 44px frame, the
19
+ * 6px corner and the highlight that `OxSelect` and `OxCombobox` already use, so
20
+ * the board's one row is drawn once, down to the 32px of right padding the
21
+ * check mark sits in.
22
+ */
23
+ export declare const TimePickerList: import('react').ForwardRefExoticComponent<TimePickerListProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,26 @@
1
+ export type TimePickerWheelProps = React.ComponentPropsWithoutRef<'div'>;
2
+ /**
3
+ * The wheel dropdown: a column per segment, scrolling under one band. Mirrors
4
+ * `@sesame/orxata-core` TimePickerWheel.
5
+ *
6
+ * Supports both formats — it is the only body that does, which is why it is
7
+ * what `<OxTimePicker>` falls back to.
8
+ *
9
+ * The band is drawn once, here, rather than as a selected state on each
10
+ * column's middle row, and that is the structural difference between this and
11
+ * `OxTimePickerList`. In the list, "selected" is a property of a row; in the
12
+ * wheel it is a property of a *position* — one rectangle behind all three
13
+ * columns and the colons between them, so the highlight reads as a single
14
+ * horizontal band and not as three stacked pills. The board draws it exactly
15
+ * that way: one absolutely positioned rectangle, first child, full inner width,
16
+ * with every row frame transparent.
17
+ *
18
+ * The board puts the band 1px above the row it marks. Centred here instead —
19
+ * that is a slip in the file, not a 1px design intention.
20
+ *
21
+ * The colon is a plain glyph, not a column of its own. On the board it is a
22
+ * full five-row column with text in the middle cell only, which renders to the
23
+ * same pixels as one centred character in a 12px box and costs four empty
24
+ * frames less.
25
+ */
26
+ export declare const TimePickerWheel: import('react').ForwardRefExoticComponent<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ import { TimePart } from './time.ts';
2
+ export interface TimePickerWheelColumnProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {
3
+ /** Which of the three this column edits. */
4
+ part: TimePart;
5
+ }
6
+ /**
7
+ * One scrolling column of a wheel, and the board's `role="spinbutton"`. Mirrors
8
+ * `@sesame/orxata-core` TimePickerWheelColumn.
9
+ *
10
+ * Neither reka nor Radix has a wheel primitive, so the behaviour is written
11
+ * here — see `useWheelColumn.ts`, which is where the scrolling, the snapping
12
+ * and the endless loop live — and keep the ARIA contract in mind when changing
13
+ * it: a spin button owes `aria-valuemin`, `aria-valuemax`, `aria-valuenow`,
14
+ * `aria-valuetext` and arrow keys, all of which are below.
15
+ *
16
+ * It takes a `part` and derives the rest from the picker above it, rather than
17
+ * being handed its values, its current number and its label. A column of
18
+ * minutes is not a configuration of a generic column — it is the minutes.
19
+ */
20
+ export declare const TimePickerWheelColumn: {
21
+ ({ className, part, onScroll: onCallerScroll, onPointerDown: onCallerPointerDown, onKeyDown: onCallerKeyDown, ...props }: TimePickerWheelColumnProps): import("react/jsx-runtime").JSX.Element;
22
+ displayName: string;
23
+ };
@@ -0,0 +1,6 @@
1
+ export { TimePicker, type TimePickerProps } from './TimePicker.tsx';
2
+ export { TimePickerField, type TimePickerFieldProps, } from './TimePickerField.tsx';
3
+ export { TimePickerList, type TimePickerListProps } from './TimePickerList.tsx';
4
+ export { TimePickerWheel, type TimePickerWheelProps, } from './TimePickerWheel.tsx';
5
+ export { TimePickerWheelColumn, type TimePickerWheelColumnProps, } from './TimePickerWheelColumn.tsx';
6
+ export type { TimeFormat, TimePart } from './time.ts';
@@ -0,0 +1,41 @@
1
+ import { Time } from '@internationalized/date';
2
+ /** `HH:MM` draws two segments, `HH:MM:SS` three. */
3
+ export type TimeFormat = 'HH:MM' | 'HH:MM:SS';
4
+ export declare const TIME_PARTS: readonly ["hour", "minute", "second"];
5
+ export type TimePart = (typeof TIME_PARTS)[number];
6
+ /** Inclusive upper bound of each part, in a 24-hour day. */
7
+ export declare const PART_MAX: Record<TimePart, number>;
8
+ /** The parts a format draws, in reading order. */
9
+ export declare function partsOf(format: TimeFormat): TimePart[];
10
+ /**
11
+ * The same thing as `partsOf`, said the way a date library says it.
12
+ *
13
+ * Kept because the Vue original hands it to reka's `TimeFieldRoot`, which calls
14
+ * the depth of a time field its `granularity`. React has no `TimeField` to hand
15
+ * it to — `useTimeField.ts` draws `partsOf(format)` directly — so here it is a
16
+ * published helper and the name of the concept, not plumbing.
17
+ */
18
+ export declare function granularityOf(format: TimeFormat): 'minute' | 'second';
19
+ export declare function pad(value: number): string;
20
+ /**
21
+ * `"15:17"` or `"15:17:30"` to a `Time`, anything else to `undefined`.
22
+ *
23
+ * Strict about shape and about range both: `"24:00"` is not a time of day, so
24
+ * it is refused rather than quietly wrapped into the next morning.
25
+ */
26
+ export declare function parseTime(value: string | undefined | null): Time | undefined;
27
+ /**
28
+ * A `Time` back to the string the model carries. The format decides the
29
+ * length, not the value — a whole minute still writes `:00` under `HH:MM:SS`.
30
+ */
31
+ export declare function formatTime(time: Time | undefined, format: TimeFormat): string | undefined;
32
+ /**
33
+ * One part replaced, the rest kept.
34
+ *
35
+ * With nothing chosen yet it starts from midnight, which is the only answer a
36
+ * wheel can give: every column shows a number whether the user has touched it
37
+ * or not, so moving one of them necessarily commits the other two.
38
+ */
39
+ export declare function withPart(time: Time | undefined, part: TimePart, value: number): Time;
40
+ /** `0 … max` inclusive, stepping by `step`: the values one wheel column holds. */
41
+ export declare function range(max: number, step?: number): number[];
@@ -0,0 +1,62 @@
1
+ import { Time } from '@internationalized/date';
2
+ import { TimeFormat, TimePart } from './time.ts';
3
+ export interface TimeSegment {
4
+ /** `'literal'` is the separator: not focusable, not a value. */
5
+ part: TimePart | 'literal';
6
+ key: string;
7
+ value: string;
8
+ }
9
+ export interface TimeFieldOptions {
10
+ format: TimeFormat;
11
+ /** The chosen time, or `undefined` while any segment is still empty. */
12
+ time: Time | undefined;
13
+ setTime: (next: Time | undefined) => void;
14
+ disabled: boolean;
15
+ }
16
+ export interface TimeField {
17
+ /** Every segment in reading order, separators included. */
18
+ segments: TimeSegment[];
19
+ /** Everything one editable segment needs: ARIA, flags, handlers and its ref. */
20
+ segmentProps: (part: TimePart) => Record<string, unknown>;
21
+ /** Left and right between segments. Belongs on the element with `role="group"`. */
22
+ onKeyDown: (event: React.KeyboardEvent) => void;
23
+ }
24
+ /**
25
+ * The segmented time field: reka's `TimeFieldRoot` / `TimeFieldInput`, written
26
+ * out, because **Radix has no `TimeField`**.
27
+ *
28
+ * This is the one piece of the port with no primitive underneath it, so it is
29
+ * an implementation and not a translation. It reproduces reka's rendered DOM
30
+ * and ARIA — a `role="spinbutton"` per segment carrying all four `aria-value*`,
31
+ * `contenteditable` with the caret suppressed, `inputmode="numeric"` and
32
+ * `data-placeholder` on an unfilled one — and reproduces its behaviour:
33
+ *
34
+ * - **`↑` / `↓` step the segment**, wrapping through `Time.cycle`, so `23` goes
35
+ * to `00` and `00` back to `23`. On an empty segment the first press takes the
36
+ * placeholder's value for the hour and `0` / `59` for the others, which is
37
+ * reka's rule rather than one invented here.
38
+ * - **Digits type into the segment and roll on.** Two digits move to the next
39
+ * segment; so does a first digit that cannot start a valid two-digit number
40
+ * (`3` in an hour, `6` in a minute); and a `0` is remembered, so `0` then `9`
41
+ * reads `09` and moves on.
42
+ * - **Backspace removes one digit**, and clears the value when it removes the
43
+ * last one.
44
+ * - **`←` / `→` move between segments**, handled on the group rather than on the
45
+ * segment so that one listener serves all three.
46
+ * - **A half-entered time is not a value.** The model is written only once every
47
+ * segment holds a number; until then it stays `undefined` and the partial
48
+ * state lives here. Deleting a digit from a filled field therefore clears the
49
+ * model and leaves `15:--` on screen, which is the state the whole component
50
+ * is built around.
51
+ *
52
+ * Every key but Tab has its default prevented, which is what stops a
53
+ * `contenteditable` segment from ever actually receiving typed text: the value
54
+ * on screen is rendered from state and never read back off the DOM.
55
+ *
56
+ * Deliberately **not** reproduced: the visually hidden `<input>` reka renders
57
+ * for form submission. `OxTimePickerField` passes it no `name`, no `id` and no
58
+ * `required`, so on the Vue side it submits nothing and validates nothing — it
59
+ * is a `tabindex="-1"` node forwarding focus to the first segment, and carrying
60
+ * it over would add DOM this port's public API cannot reach.
61
+ */
62
+ export declare function useTimeField({ format, time, setTime, disabled, }: TimeFieldOptions): TimeField;
@@ -0,0 +1,53 @@
1
+ export interface TimeListOptions {
2
+ /** Every row's value, in order. Also what typeahead matches against. */
3
+ options: string[];
4
+ /** The selected row, or `undefined` when the value is between two rows. */
5
+ selected: string | undefined;
6
+ onSelect: (value: string) => void;
7
+ disabled: boolean;
8
+ }
9
+ export interface TimeList {
10
+ /** Goes on the scrolling element, together with `role="listbox"`. */
11
+ listProps: {
12
+ tabIndex: number;
13
+ 'aria-activedescendant': string | undefined;
14
+ onKeyDown: (event: React.KeyboardEvent) => void;
15
+ };
16
+ /** Everything one row needs but its content. */
17
+ optionProps: (option: string) => {
18
+ id: string;
19
+ role: 'option';
20
+ 'aria-selected': boolean;
21
+ 'aria-disabled': true | undefined;
22
+ 'data-highlighted': '' | undefined;
23
+ ref: (element: HTMLElement | null) => void;
24
+ onClick: () => void;
25
+ onMouseMove: () => void;
26
+ };
27
+ }
28
+ /**
29
+ * The roving focus, the arrows, Home/End and the typeahead of a single-select
30
+ * listbox, because **nothing in the React stack supplies them for a list that
31
+ * is not welded to a trigger.**
32
+ *
33
+ * The Vue original is reka's standalone `Listbox`. Radix has no counterpart —
34
+ * its option lists (`Select`, `DropdownMenu`) all come with a trigger and a
35
+ * popover of their own, and this list already has both. cmdk, the package's
36
+ * stand-in for reka's `Combobox` in `OxSearchPanel` and `OxCombobox`, is the
37
+ * wrong model rather than a missing one: it puts `aria-selected` on the
38
+ * *highlighted* row, which is right for a command palette where nothing stays
39
+ * chosen and wrong here, where a row carries a check mark and `aria-selected`
40
+ * has to mean the time the field is showing. Overloading it would leave the
41
+ * selection and the cursor claiming the same attribute.
42
+ *
43
+ * So this is reka's behaviour written out, matching what it renders:
44
+ * `role="listbox"` and `aria-activedescendant` on the scrolling element,
45
+ * `role="option"` with `aria-selected` on the rows, `data-highlighted` for the
46
+ * cursor, arrows and Home/End moving it without wrapping, Enter and Space
47
+ * choosing, and the cursor following the pointer — reka's `highlight-on-hover`.
48
+ *
49
+ * The cursor starts on the selected row and is scrolled into view, which is
50
+ * what opens a 96-row list on the time it is already showing rather than at
51
+ * midnight.
52
+ */
53
+ export declare function useTimeList({ options, selected, onSelect, disabled, }: TimeListOptions): TimeList;