@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.
- package/dist/Button/index.d.ts +1 -1
- package/dist/Display/AiTag/AiTag.d.ts +33 -0
- package/dist/Display/AiTag/index.d.ts +21 -0
- package/dist/Display/Avatar/index.d.ts +2 -2
- package/dist/Display/Badge/index.d.ts +1 -1
- package/dist/Display/BrandLogo/BrandLogo.d.ts +53 -0
- package/dist/Display/BrandLogo/brandLogoArt.d.ts +29 -0
- package/dist/Display/BrandLogo/index.d.ts +17 -0
- package/dist/Display/FileCard/FileCard.d.ts +102 -41
- package/dist/Display/FileCard/index.d.ts +31 -0
- package/dist/Display/ImageAttachment/ImageAttachment.d.ts +58 -0
- package/dist/Display/ImageAttachment/index.d.ts +2 -0
- package/dist/Display/LinkPreview/LinkPreview.d.ts +65 -0
- package/dist/Display/LinkPreview/index.d.ts +1 -0
- package/dist/Display/LocationAttachment/LocationAttachment.d.ts +71 -0
- package/dist/Display/LocationAttachment/index.d.ts +1 -0
- package/dist/Display/VideoAttachment/VideoAttachment.d.ts +78 -0
- package/dist/Display/VideoAttachment/index.d.ts +1 -0
- package/dist/Display/VoiceMessage/VoiceMessage.d.ts +103 -0
- package/dist/Display/VoiceMessage/index.d.ts +2 -0
- package/dist/Display/VoiceMessage/toWaveformPeaks.d.ts +58 -0
- package/dist/Display/VoiceMessage/useVoiceWaveform.d.ts +90 -0
- package/dist/Feedback/Dialog/Dialog.d.ts +13 -1
- package/dist/Feedback/Dialog/DialogOverlay.d.ts +12 -1
- package/dist/Feedback/Drawer/Drawer.d.ts +68 -0
- package/dist/Feedback/Drawer/DrawerBody.d.ts +18 -0
- package/dist/Feedback/Drawer/DrawerContent.d.ts +36 -0
- package/dist/Feedback/Drawer/DrawerDescription.d.ts +10 -0
- package/dist/Feedback/Drawer/DrawerExpand.d.ts +23 -0
- package/dist/Feedback/Drawer/DrawerTitle.d.ts +13 -0
- package/dist/Feedback/Drawer/index.d.ts +7 -0
- package/dist/Feedback/Drawer/useDrawer.d.ts +30 -0
- package/dist/Feedback/Progress/index.d.ts +1 -1
- package/dist/Forms/Inputs/AiSearch/AiSearch.d.ts +67 -0
- package/dist/Forms/Inputs/AiSearch/AiSearchAsk.d.ts +34 -0
- package/dist/Forms/Inputs/AiSearch/AiSearchPanel.d.ts +50 -0
- package/dist/Forms/Inputs/AiSearch/AiSearchSend.d.ts +30 -0
- package/dist/Forms/Inputs/AiSearch/AiSearchShortcut.d.ts +23 -0
- package/dist/Forms/Inputs/AiSearch/AiSearchSuggestion.d.ts +27 -0
- package/dist/Forms/Inputs/AiSearch/index.d.ts +6 -0
- package/dist/Forms/Inputs/AiSearch/useAiSearch.d.ts +37 -0
- package/dist/Forms/Inputs/TimePicker/TimePicker.d.ts +64 -0
- package/dist/Forms/Inputs/TimePicker/TimePickerField.d.ts +35 -0
- package/dist/Forms/Inputs/TimePicker/TimePickerList.d.ts +23 -0
- package/dist/Forms/Inputs/TimePicker/TimePickerWheel.d.ts +26 -0
- package/dist/Forms/Inputs/TimePicker/TimePickerWheelColumn.d.ts +23 -0
- package/dist/Forms/Inputs/TimePicker/index.d.ts +6 -0
- package/dist/Forms/Inputs/TimePicker/time.d.ts +41 -0
- package/dist/Forms/Inputs/TimePicker/useTimeField.d.ts +62 -0
- package/dist/Forms/Inputs/TimePicker/useTimeList.d.ts +53 -0
- package/dist/Forms/Inputs/TimePicker/useTimePicker.d.ts +35 -0
- package/dist/Forms/Inputs/TimePicker/useWheelColumn.d.ts +71 -0
- package/dist/Forms/Inputs/index.d.ts +1 -0
- package/dist/Navigation/Tabs/Tabs.d.ts +18 -0
- package/dist/Navigation/Tabs/TabsContent.d.ts +7 -0
- package/dist/Navigation/Tabs/TabsList.d.ts +10 -0
- package/dist/Navigation/Tabs/TabsTrigger.d.ts +20 -0
- package/dist/Navigation/Tabs/index.d.ts +4 -0
- package/dist/internal/AttachmentMedia/AttachmentMedia.d.ts +38 -0
- package/dist/internal/AttachmentMedia/index.d.ts +36 -0
- package/dist/internal/index.d.ts +1 -0
- package/dist/main.d.ts +12 -2
- package/dist/react-design-system.css +1 -1
- package/dist/react-design-system.js +12581 -10218
- package/dist/react-design-system.umd.cjs +108 -74
- package/package.json +3 -2
- package/src/assets/styles/theme-v3.css +99 -2
- 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;
|