@arbor-education/design-system.components 0.15.0 → 0.16.1
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/.gather/skills/write-stories/SKILL.md +207 -271
- package/.storybook/preview.ts +5 -0
- package/CHANGELOG.md +23 -0
- package/README.md +8 -0
- package/component-library.md +144 -13
- package/dist/components/articleCard/ArticleCard.stories.d.ts +137 -11
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.js +358 -91
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
- package/dist/components/avatar/Avatar.stories.d.ts +6 -6
- package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.stories.js +393 -49
- package/dist/components/avatar/Avatar.stories.js.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts +9 -7
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.stories.js +688 -65
- package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +7 -3
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +105 -4
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +336 -18
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts +134 -21
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +676 -175
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.d.ts +119 -27
- package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.js +575 -47
- package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +155 -39
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.stories.js +674 -103
- package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -1
- package/dist/components/editableText/EditableText.stories.d.ts +53 -12
- package/dist/components/editableText/EditableText.stories.d.ts.map +1 -1
- package/dist/components/editableText/EditableText.stories.js +401 -64
- package/dist/components/editableText/EditableText.stories.js.map +1 -1
- package/dist/components/formField/FormField.d.ts +4 -0
- package/dist/components/formField/FormField.d.ts.map +1 -1
- package/dist/components/formField/FormField.js +2 -1
- package/dist/components/formField/FormField.js.map +1 -1
- package/dist/components/formField/FormField.test.js +5 -0
- package/dist/components/formField/FormField.test.js.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts +56 -4
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.js +534 -28
- package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +3 -1
- package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.js +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.js.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts +95 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js +386 -9
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +6 -2
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +61 -49
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +188 -166
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +821 -160
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +176 -22
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.js +851 -92
- package/dist/components/formField/inputs/time/TimeInput.stories.js.map +1 -1
- package/dist/components/formField/label/Label.stories.d.ts +54 -5
- package/dist/components/formField/label/Label.stories.d.ts.map +1 -1
- package/dist/components/formField/label/Label.stories.js +238 -4
- package/dist/components/formField/label/Label.stories.js.map +1 -1
- package/dist/components/icoText/IcoText.stories.d.ts +32 -6
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -1
- package/dist/components/icoText/IcoText.stories.js +309 -14
- package/dist/components/icoText/IcoText.stories.js.map +1 -1
- package/dist/components/kpiCard/KPICard.stories.d.ts +100 -2
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -1
- package/dist/components/kpiCard/KPICard.stories.js +354 -10
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -1
- package/dist/components/kvpList/KVPList.stories.d.ts +57 -4
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -1
- package/dist/components/kvpList/KVPList.stories.js +403 -10
- package/dist/components/kvpList/KVPList.stories.js.map +1 -1
- package/dist/components/modal/Modal.stories.d.ts +113 -9
- package/dist/components/modal/Modal.stories.d.ts.map +1 -1
- package/dist/components/modal/Modal.stories.js +633 -13
- package/dist/components/modal/Modal.stories.js.map +1 -1
- package/dist/components/modal/modalManager/ModalManager.stories.d.ts +34 -10
- package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -1
- package/dist/components/modal/modalManager/ModalManager.stories.js +463 -85
- package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -1
- package/dist/components/pill/Pill.d.ts.map +1 -1
- package/dist/components/pill/Pill.js +1 -1
- package/dist/components/pill/Pill.js.map +1 -1
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +11 -13
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/row/Row.stories.d.ts +1 -2
- package/dist/components/row/Row.stories.d.ts.map +1 -1
- package/dist/components/row/Row.stories.js +360 -50
- package/dist/components/row/Row.stories.js.map +1 -1
- package/dist/components/searchBar/SearchBar.stories.d.ts +52 -4
- package/dist/components/searchBar/SearchBar.stories.d.ts.map +1 -1
- package/dist/components/searchBar/SearchBar.stories.js +428 -36
- package/dist/components/searchBar/SearchBar.stories.js.map +1 -1
- package/dist/components/section/Section.stories.d.ts +11 -41
- package/dist/components/section/Section.stories.d.ts.map +1 -1
- package/dist/components/section/Section.stories.js +494 -56
- package/dist/components/section/Section.stories.js.map +1 -1
- package/dist/components/singleUser/SingleUser.stories.d.ts +5 -4
- package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -1
- package/dist/components/singleUser/SingleUser.stories.js +303 -31
- package/dist/components/singleUser/SingleUser.stories.js.map +1 -1
- package/dist/components/slideoverManager/SlideoverManager.stories.d.ts +32 -11
- package/dist/components/slideoverManager/SlideoverManager.stories.d.ts.map +1 -1
- package/dist/components/slideoverManager/SlideoverManager.stories.js +380 -84
- package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
- package/dist/components/table/DSDefaultColDef.d.ts.map +1 -1
- package/dist/components/table/DSDefaultColDef.js +4 -3
- package/dist/components/table/DSDefaultColDef.js.map +1 -1
- package/dist/components/table/Table.d.ts +6 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +8 -3
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +3 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +384 -5
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +30 -0
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/TableFooter.stories.d.ts +49 -0
- package/dist/components/table/TableFooter.stories.d.ts.map +1 -0
- package/dist/components/table/TableFooter.stories.js +137 -0
- package/dist/components/table/TableFooter.stories.js.map +1 -0
- package/dist/components/table/TableHeader.stories.d.ts +93 -0
- package/dist/components/table/TableHeader.stories.d.ts.map +1 -0
- package/dist/components/table/TableHeader.stories.js +176 -0
- package/dist/components/table/TableHeader.stories.js.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts +44 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.js +186 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts +40 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js +209 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts +48 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js +244 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.js +3 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.js.map +1 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts +64 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js +241 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts +55 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js +245 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts +67 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js +221 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts +75 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js +270 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts +57 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.js +198 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.d.ts +58 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.js +207 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.js.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.stories.d.ts +113 -0
- package/dist/components/table/pagination/PaginationPanel.stories.d.ts.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.stories.js +272 -0
- package/dist/components/table/pagination/PaginationPanel.stories.js.map +1 -0
- package/dist/components/table/tableControls/HideColumnsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/HideColumnsDropdown.js +9 -3
- package/dist/components/table/tableControls/HideColumnsDropdown.js.map +1 -1
- package/dist/components/table/tableControls/TableControls.stories.d.ts +151 -0
- package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -0
- package/dist/components/table/tableControls/TableControls.stories.js +356 -0
- package/dist/components/table/tableControls/TableControls.stories.js.map +1 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.d.ts +27 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.js +53 -26
- package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts +2 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts.map +1 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.js +178 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.js.map +1 -0
- package/dist/components/tabs/Tabs.stories.d.ts +22 -4
- package/dist/components/tabs/Tabs.stories.d.ts.map +1 -1
- package/dist/components/tabs/Tabs.stories.js +398 -22
- package/dist/components/tabs/Tabs.stories.js.map +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +54 -1
- package/dist/components/tabs/TabsItem.stories.d.ts.map +1 -1
- package/dist/components/tabs/TabsItem.stories.js +61 -9
- package/dist/components/tabs/TabsItem.stories.js.map +1 -1
- package/dist/components/toast/Toast.stories.d.ts +103 -10
- package/dist/components/toast/Toast.stories.d.ts.map +1 -1
- package/dist/components/toast/Toast.stories.js +409 -47
- package/dist/components/toast/Toast.stories.js.map +1 -1
- package/dist/components/toggle/Toggle.stories.d.ts +61 -46
- package/dist/components/toggle/Toggle.stories.d.ts.map +1 -1
- package/dist/components/toggle/Toggle.stories.js +311 -122
- package/dist/components/toggle/Toggle.stories.js.map +1 -1
- package/dist/components/tooltip/Tooltip.stories.d.ts +78 -6
- package/dist/components/tooltip/Tooltip.stories.d.ts.map +1 -1
- package/dist/components/tooltip/Tooltip.stories.js +413 -7
- package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts +71 -7
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts.map +1 -1
- package/dist/components/tooltip/TooltipWrapper.stories.js +238 -10
- package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
- package/dist/index.css +8 -0
- package/dist/index.css.map +1 -1
- package/dist/utils/PopupParentContext.stories.d.ts +17 -0
- package/dist/utils/PopupParentContext.stories.d.ts.map +1 -0
- package/dist/utils/PopupParentContext.stories.js +266 -0
- package/dist/utils/PopupParentContext.stories.js.map +1 -0
- package/dist/utils/getDefaultPopupParent.d.ts.map +1 -1
- package/dist/utils/getDefaultPopupParent.js +6 -0
- package/dist/utils/getDefaultPopupParent.js.map +1 -1
- package/package.json +1 -1
- package/src/components/articleCard/ArticleCard.stories.tsx +524 -111
- package/src/components/avatar/Avatar.stories.tsx +504 -59
- package/src/components/avatarGroup/AvatarGroup.stories.tsx +977 -175
- package/src/components/banner/Banner.stories.tsx +7 -3
- package/src/components/card/Card.stories.tsx +466 -36
- package/src/components/combobox/Combobox.stories.tsx +867 -260
- package/src/components/datePicker/DatePicker.stories.tsx +777 -60
- package/src/components/dateTimePicker/DateTimePicker.stories.tsx +910 -132
- package/src/components/editableText/EditableText.stories.tsx +567 -91
- package/src/components/formField/FormField.test.tsx +6 -0
- package/src/components/formField/FormField.tsx +5 -0
- package/src/components/formField/fieldset/Fieldset.stories.tsx +761 -51
- package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +1 -1
- package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +1 -1
- package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.tsx +504 -11
- package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +17 -4
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +71 -59
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +1079 -168
- package/src/components/formField/inputs/time/TimeInput.stories.tsx +1140 -104
- package/src/components/formField/label/Label.stories.tsx +317 -8
- package/src/components/icoText/IcoText.stories.tsx +442 -31
- package/src/components/kpiCard/KPICard.stories.tsx +475 -30
- package/src/components/kvpList/KVPList.stories.tsx +593 -26
- package/src/components/modal/Modal.stories.tsx +963 -26
- package/src/components/modal/modalManager/ModalManager.stories.tsx +612 -454
- package/src/components/pill/Pill.stories.tsx +11 -13
- package/src/components/pill/Pill.tsx +1 -0
- package/src/components/row/Row.stories.tsx +474 -58
- package/src/components/searchBar/SearchBar.stories.tsx +570 -38
- package/src/components/section/Section.stories.tsx +723 -70
- package/src/components/singleUser/SingleUser.stories.tsx +393 -34
- package/src/components/slideoverManager/SlideoverManager.stories.tsx +572 -342
- package/src/components/table/DSDefaultColDef.ts +25 -5
- package/src/components/table/Table.stories.tsx +460 -5
- package/src/components/table/Table.test.tsx +53 -0
- package/src/components/table/Table.tsx +9 -2
- package/src/components/table/TableFooter.stories.tsx +196 -0
- package/src/components/table/TableHeader.stories.tsx +251 -0
- package/src/components/table/cellEditors/DateCellEditor.stories.tsx +245 -0
- package/src/components/table/cellRenderers/BooleanCellRenderer.stories.tsx +278 -0
- package/src/components/table/cellRenderers/ButtonCellRenderer.stories.tsx +333 -0
- package/src/components/table/cellRenderers/CheckboxCellRenderer.stories.tsx +337 -0
- package/src/components/table/cellRenderers/CheckboxCellRenderer.tsx +5 -1
- package/src/components/table/cellRenderers/DefaultCellRenderer.stories.tsx +342 -0
- package/src/components/table/cellRenderers/InlineTextCellRenderer.stories.tsx +292 -0
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +369 -0
- package/src/components/table/columnFilters/BooleanFilter.stories.tsx +268 -0
- package/src/components/table/columnFilters/TimeFilter.stories.tsx +281 -0
- package/src/components/table/pagination/PaginationPanel.stories.tsx +327 -0
- package/src/components/table/tableControls/HideColumnsDropdown.tsx +11 -2
- package/src/components/table/tableControls/TableControls.stories.tsx +415 -0
- package/src/components/table/tableControls/TableSettingsDropdown.test.tsx +207 -0
- package/src/components/table/tableControls/TableSettingsDropdown.tsx +103 -39
- package/src/components/tabs/Tabs.stories.tsx +540 -60
- package/src/components/tabs/TabsItem.stories.tsx +82 -8
- package/src/components/toast/Toast.stories.tsx +539 -77
- package/src/components/toggle/Toggle.stories.tsx +371 -135
- package/src/components/tooltip/Tooltip.stories.tsx +606 -15
- package/src/components/tooltip/TooltipWrapper.stories.tsx +348 -12
- package/src/docs/Contributing.mdx +241 -0
- package/src/docs/UsingComponents.mdx +93 -0
- package/src/docs/Welcome.mdx +68 -0
- package/src/global.scss +7 -0
- package/src/utils/PopupParentContext.stories.tsx +367 -0
- package/src/utils/getDefaultPopupParent.ts +6 -0
- package/.ralph/storybook-upgrade/knowledge.md +0 -308
- package/.ralph/storybook-upgrade/prd.json +0 -777
- package/.ralph/storybook-upgrade/progress.md +0 -342
- package/src/components/table/TableWIP.mdx +0 -3
|
@@ -1,55 +1,341 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
Controls,
|
|
5
|
+
Heading as DocHeading,
|
|
6
|
+
Markdown,
|
|
7
|
+
Primary as DocPrimary,
|
|
8
|
+
Stories,
|
|
9
|
+
Subtitle,
|
|
10
|
+
Title,
|
|
11
|
+
} from '@storybook/addon-docs/blocks';
|
|
12
|
+
import { FormField } from 'Components/formField/FormField';
|
|
13
|
+
import { TimeInput, type TimeValue } from './TimeInput';
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Docs page content
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const DESCRIPTION_INTRO = [
|
|
20
|
+
'TimeInput is a flexible time-entry field that operates in **two distinct modes** depending on',
|
|
21
|
+
'whether the `options` prop is supplied.',
|
|
22
|
+
'',
|
|
23
|
+
'**Native mode** (no `options`) renders a browser-native `<input type="time">` decorated with a',
|
|
24
|
+
'clock icon. The browser handles the picker UI, spin-button keyboard navigation, and locale-aware',
|
|
25
|
+
'display. This mode supports `min`, `max`, `granularity`, `className`, a forwarded `ref`, and the',
|
|
26
|
+
'native `onChange` event.',
|
|
27
|
+
'',
|
|
28
|
+
'**Combobox mode** (`options` provided) replaces the native input with a searchable Combobox.',
|
|
29
|
+
'The user can type to filter or click to select from a predefined list of time values. This mode',
|
|
30
|
+
'supports `searchType` and `highlightStringMatches`, but ignores `min`, `max`, `granularity`,',
|
|
31
|
+
'`className`, `ref`, and `onChange`.',
|
|
32
|
+
'',
|
|
33
|
+
'Both modes share: `value`, `defaultValue`, `onValueChange`, `hasError`, `disabled`, `id`, `name`,',
|
|
34
|
+
'`placeholder`, and ARIA attributes.',
|
|
35
|
+
].join('\n');
|
|
36
|
+
|
|
37
|
+
const USAGE_GUIDANCE = [
|
|
38
|
+
'### When to use',
|
|
39
|
+
'',
|
|
40
|
+
'- **Any valid time** — use native mode (the default). The browser enforces valid time entry and',
|
|
41
|
+
' supports `min`/`max` bounds for school hours (e.g. `min="08:00" max="17:00"`).',
|
|
42
|
+
'- **Fixed set of allowed times** — use combobox mode with `options`. Ideal for lesson periods,',
|
|
43
|
+
' appointment slots, or any scenario where only specific times are valid.',
|
|
44
|
+
'- **Inside a form** — always wrap in `<FormField inputType="time">` so label, error text, and',
|
|
45
|
+
' ARIA attributes are wired up automatically.',
|
|
46
|
+
'',
|
|
47
|
+
'---',
|
|
48
|
+
'',
|
|
49
|
+
'### When NOT to use',
|
|
50
|
+
'',
|
|
51
|
+
'| Situation | Use instead |',
|
|
52
|
+
'|---|---|',
|
|
53
|
+
'| Date + time combined | Separate `DateInput` and `TimeInput` fields |',
|
|
54
|
+
'| Free-form duration entry | `TextInput` with format hint |',
|
|
55
|
+
'| More than ~50 time options | Native mode with `min`/`max` bounds |',
|
|
56
|
+
'| Status or category selection | `SelectDropdown` with meaningful labels |',
|
|
57
|
+
].join('\n');
|
|
58
|
+
|
|
59
|
+
const DEVELOPER_NOTES = [
|
|
60
|
+
'### Critical usage patterns',
|
|
61
|
+
'',
|
|
62
|
+
'**Prop availability varies by mode.** Props that are native-only are silently ignored in combobox',
|
|
63
|
+
'mode, and vice versa:',
|
|
64
|
+
'',
|
|
65
|
+
'| Prop | Native mode | Combobox mode |',
|
|
66
|
+
'|---|---|---|',
|
|
67
|
+
'| `granularity` | Sets `step={1}` for second-level precision | **Ignored** |',
|
|
68
|
+
'| `min` / `max` | Restricts picker range | **Ignored** — filter `options` array instead |',
|
|
69
|
+
'| `className` | Applied to the wrapper `<div>` | **Ignored** |',
|
|
70
|
+
'| `ref` (forwarded) | Attached to the `<input>` element | **Ignored** |',
|
|
71
|
+
'| `onChange` | Native `ChangeEvent<HTMLInputElement>` | **Ignored** — use `onValueChange` |',
|
|
72
|
+
'| `searchType` | **Ignored** | Controls `prefix`/`substring` matching |',
|
|
73
|
+
'| `highlightStringMatches` | **Ignored** | Bolds matched characters in dropdown |',
|
|
74
|
+
'| `onValueChange` | Fires with the string value | Fires with the string value |',
|
|
75
|
+
'',
|
|
76
|
+
'**`aria-invalid` is NOT automatic.** When using TimeInput standalone (outside FormField),',
|
|
77
|
+
'`hasError` only applies visual error styling. It does NOT set `aria-invalid`:',
|
|
78
|
+
'',
|
|
79
|
+
'```tsx',
|
|
80
|
+
'// Standalone — set aria-invalid yourself:',
|
|
81
|
+
'<TimeInput hasError aria-invalid={true} aria-label="Start time" />',
|
|
82
|
+
'',
|
|
83
|
+
'// Inside FormField — aria-invalid is set automatically when errorText is present:',
|
|
84
|
+
'<FormField label="Start time" id="start" inputType="time" errorText="Required." inputProps={{}} />',
|
|
85
|
+
'```',
|
|
86
|
+
'',
|
|
87
|
+
'**`onValueChange` is the preferred callback** — it works in both modes and receives the plain',
|
|
88
|
+
'string value (`HH:MM` or `HH:MM:SS`). The native `onChange` event is only available in native',
|
|
89
|
+
'mode and is not forwarded in combobox mode.',
|
|
90
|
+
'',
|
|
91
|
+
'**`TimeValue` is format-shaped, not strictly validated.** The TypeScript type',
|
|
92
|
+
'`` `${string}:${string}` `` accepts `"99:99"` without complaint. The browser rejects it at render',
|
|
93
|
+
'time for `<input type="time">`, but the component itself does no validation.',
|
|
94
|
+
'',
|
|
95
|
+
'---',
|
|
96
|
+
'',
|
|
97
|
+
'### Accessibility',
|
|
98
|
+
'',
|
|
99
|
+
'- Always provide a visible label via `<FormField>` or `aria-label` / `aria-labelledby`.',
|
|
100
|
+
'- In native mode, the browser\'s built-in time picker is fully keyboard-navigable (arrow keys',
|
|
101
|
+
' cycle through the hour/minute/second segments; AM/PM toggles with A/P).',
|
|
102
|
+
'- In combobox mode, the Combobox component implements the ARIA combobox pattern',
|
|
103
|
+
' (`role="combobox"`, `aria-expanded`, `aria-activedescendant`).',
|
|
104
|
+
'- The clock icon is decorative and carries `aria-hidden="true"`.',
|
|
105
|
+
'- Pair `hasError` with `aria-invalid={true}` when used standalone — one is visual, the other',
|
|
106
|
+
' is for screen readers. `<FormField>` handles this automatically when `errorText` is set.',
|
|
107
|
+
'',
|
|
108
|
+
'---',
|
|
109
|
+
'',
|
|
110
|
+
'### TypeScript types',
|
|
111
|
+
'',
|
|
112
|
+
'```ts',
|
|
113
|
+
"import { TimeInput, type TimeValue, type TimeGranularity } from '@arbor-education/design-system.components';",
|
|
114
|
+
'',
|
|
115
|
+
'// TimeValue: HH:MM or HH:MM:SS (format-shaped, not strictly validated)',
|
|
116
|
+
'type TimeValue = `${string}:${string}` | `${string}:${string}:${string}`;',
|
|
117
|
+
"type TimeGranularity = 'minute' | 'second';",
|
|
118
|
+
'',
|
|
119
|
+
'// Access types via the namespace:',
|
|
120
|
+
'function MyField(props: TimeInput.Props) { ... }',
|
|
121
|
+
'```',
|
|
122
|
+
].join('\n');
|
|
123
|
+
|
|
124
|
+
const RELATED_COMPONENTS = [
|
|
125
|
+
'## Related components',
|
|
126
|
+
'',
|
|
127
|
+
'[FormField](?path=/docs/components-formfield--docs) · [DateInput](?path=/docs/components-formfield-inputs-dateinput--docs) · [Combobox](?path=/docs/components-combobox--docs)',
|
|
128
|
+
].join('\n');
|
|
129
|
+
|
|
130
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Docs page component
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
function TimeInputDocsPage() {
|
|
137
|
+
return (
|
|
138
|
+
<>
|
|
139
|
+
<Title />
|
|
140
|
+
<Subtitle />
|
|
141
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
142
|
+
<DocHeading>Interactive example</DocHeading>
|
|
143
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
144
|
+
<DocPrimary />
|
|
145
|
+
<Controls />
|
|
146
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
147
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
148
|
+
<DocHeading>Developer notes</DocHeading>
|
|
149
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
150
|
+
<DocHeading>Examples</DocHeading>
|
|
151
|
+
<Stories title="" />
|
|
152
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
153
|
+
</>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Meta
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
8
160
|
|
|
9
161
|
const meta = {
|
|
10
162
|
title: 'Components/FormField/Inputs/TimeInput',
|
|
11
163
|
component: TimeInput,
|
|
164
|
+
tags: ['autodocs'],
|
|
12
165
|
parameters: {
|
|
13
|
-
layout: '
|
|
166
|
+
layout: 'padded',
|
|
14
167
|
docs: {
|
|
15
|
-
|
|
16
|
-
component:
|
|
17
|
-
'`TimeInput` supports both native time entry and a combobox-backed time list. When you pass `value`, treat it as a controlled component and update it in `onValueChange`; use `defaultValue` for uncontrolled usage.',
|
|
18
|
-
},
|
|
168
|
+
page: TimeInputDocsPage,
|
|
19
169
|
},
|
|
20
170
|
},
|
|
21
|
-
tags: ['autodocs'],
|
|
22
|
-
args: {
|
|
23
|
-
onValueChange: fn(),
|
|
24
|
-
},
|
|
25
171
|
argTypes: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
172
|
+
'value': {
|
|
173
|
+
description: [
|
|
174
|
+
'Controlled value in `HH:MM` or `HH:MM:SS` format.',
|
|
175
|
+
'Pair with `onValueChange` to keep state in sync.',
|
|
176
|
+
'If `undefined`, the component manages its own state (uncontrolled).',
|
|
177
|
+
].join(' '),
|
|
178
|
+
control: 'text',
|
|
179
|
+
table: {
|
|
180
|
+
type: { summary: "TimeValue | ''" },
|
|
181
|
+
defaultValue: { summary: 'undefined' },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
'defaultValue': {
|
|
185
|
+
description: [
|
|
186
|
+
'Uncontrolled initial value in `HH:MM` or `HH:MM:SS` format.',
|
|
187
|
+
'Use when you only need the value on form submit, not on every change.',
|
|
188
|
+
].join(' '),
|
|
189
|
+
control: 'text',
|
|
190
|
+
table: {
|
|
191
|
+
type: { summary: "TimeValue | ''" },
|
|
192
|
+
defaultValue: { summary: "''" },
|
|
193
|
+
},
|
|
30
194
|
},
|
|
31
|
-
|
|
195
|
+
'onValueChange': {
|
|
196
|
+
description: [
|
|
197
|
+
'Callback fired with the plain string value on every change.',
|
|
198
|
+
'Works in **both** native and combobox modes.',
|
|
199
|
+
'Prefer this over the native `onChange` event for cross-mode compatibility.',
|
|
200
|
+
].join(' '),
|
|
201
|
+
action: 'onValueChange',
|
|
202
|
+
control: false,
|
|
203
|
+
table: {
|
|
204
|
+
type: { summary: '(value: string) => void' },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
'options': {
|
|
208
|
+
description: [
|
|
209
|
+
'Array of `TimeValue` strings to display as selectable options.',
|
|
210
|
+
'Providing this prop switches to **combobox mode** — the native `<input type="time">` is',
|
|
211
|
+
'replaced by a searchable Combobox. Omit to use the browser-native time picker.',
|
|
212
|
+
].join(' '),
|
|
32
213
|
control: 'object',
|
|
33
|
-
|
|
214
|
+
table: {
|
|
215
|
+
type: { summary: 'TimeValue[]' },
|
|
216
|
+
defaultValue: { summary: 'undefined' },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
'granularity': {
|
|
220
|
+
description: [
|
|
221
|
+
'**Native mode only.** Controls the precision of the time input.',
|
|
222
|
+
'`"second"` sets `step={1}` so the seconds segment is visible.',
|
|
223
|
+
'Silently ignored when `options` is provided.',
|
|
224
|
+
].join(' '),
|
|
225
|
+
control: { type: 'select' },
|
|
226
|
+
options: ['minute', 'second'],
|
|
227
|
+
table: {
|
|
228
|
+
type: { summary: "'minute' | 'second'" },
|
|
229
|
+
defaultValue: { summary: "'minute'" },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
'searchType': {
|
|
233
|
+
description: [
|
|
234
|
+
'**Combobox mode only.** Controls how options are filtered as the user types.',
|
|
235
|
+
'`"prefix"` (default) matches from the start of the string;',
|
|
236
|
+
'`"substring"` matches anywhere in the string.',
|
|
237
|
+
'Silently ignored in native mode.',
|
|
238
|
+
].join(' '),
|
|
239
|
+
control: { type: 'select' },
|
|
240
|
+
options: ['prefix', 'substring'],
|
|
241
|
+
table: {
|
|
242
|
+
type: { summary: "'prefix' | 'substring'" },
|
|
243
|
+
defaultValue: { summary: "'prefix'" },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
'highlightStringMatches': {
|
|
247
|
+
description: [
|
|
248
|
+
'**Combobox mode only.** When `true`, matched characters in dropdown options are bolded.',
|
|
249
|
+
'Silently ignored in native mode.',
|
|
250
|
+
].join(' '),
|
|
251
|
+
control: 'boolean',
|
|
252
|
+
table: {
|
|
253
|
+
type: { summary: 'boolean' },
|
|
254
|
+
defaultValue: { summary: 'false' },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
'hasError': {
|
|
258
|
+
description: [
|
|
259
|
+
'Applies error-state visual styling (red border).',
|
|
260
|
+
'Does **not** set `aria-invalid` automatically when used standalone outside FormField.',
|
|
261
|
+
'Always pair with `aria-invalid={true}` for screen-reader coverage.',
|
|
262
|
+
].join(' '),
|
|
263
|
+
control: 'boolean',
|
|
264
|
+
table: {
|
|
265
|
+
type: { summary: 'boolean' },
|
|
266
|
+
defaultValue: { summary: 'false' },
|
|
267
|
+
},
|
|
34
268
|
},
|
|
35
|
-
|
|
269
|
+
'disabled': {
|
|
270
|
+
description: 'Disables the input. The current value remains visible.',
|
|
271
|
+
control: 'boolean',
|
|
272
|
+
table: {
|
|
273
|
+
type: { summary: 'boolean' },
|
|
274
|
+
defaultValue: { summary: 'false' },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
'id': {
|
|
278
|
+
description: [
|
|
279
|
+
'HTML `id` for the input element.',
|
|
280
|
+
'Required when inside FormField so the `<label>` `htmlFor` can be linked.',
|
|
281
|
+
].join(' '),
|
|
36
282
|
control: 'text',
|
|
37
|
-
|
|
283
|
+
table: {
|
|
284
|
+
type: { summary: 'string' },
|
|
285
|
+
},
|
|
38
286
|
},
|
|
39
|
-
|
|
287
|
+
'name': {
|
|
288
|
+
description: 'HTML `name` attribute for native form submission.',
|
|
40
289
|
control: 'text',
|
|
41
|
-
|
|
290
|
+
table: {
|
|
291
|
+
type: { summary: 'string' },
|
|
292
|
+
},
|
|
42
293
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
294
|
+
'placeholder': {
|
|
295
|
+
description: 'Placeholder text shown when no value is selected. Most useful in combobox mode.',
|
|
296
|
+
control: 'text',
|
|
297
|
+
table: {
|
|
298
|
+
type: { summary: 'string' },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
'aria-label': {
|
|
302
|
+
description: [
|
|
303
|
+
'Accessible label for the input.',
|
|
304
|
+
'Required when the component is used standalone without a visible `<label>`.',
|
|
305
|
+
].join(' '),
|
|
306
|
+
control: 'text',
|
|
307
|
+
table: {
|
|
308
|
+
type: { summary: 'string' },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
'aria-invalid': {
|
|
312
|
+
description: [
|
|
313
|
+
'Marks the input as invalid for screen readers.',
|
|
314
|
+
'Must be set manually when used standalone — FormField sets this automatically',
|
|
315
|
+
'when `errorText` is provided.',
|
|
316
|
+
].join(' '),
|
|
317
|
+
control: 'boolean',
|
|
318
|
+
table: {
|
|
319
|
+
type: { summary: 'boolean' },
|
|
320
|
+
defaultValue: { summary: 'false' },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
'aria-describedby': {
|
|
324
|
+
description: 'ID of the element that describes the field (e.g. an error or hint message).',
|
|
325
|
+
control: 'text',
|
|
326
|
+
table: {
|
|
327
|
+
type: { summary: 'string' },
|
|
328
|
+
},
|
|
46
329
|
},
|
|
47
330
|
},
|
|
48
331
|
} satisfies Meta<typeof TimeInput>;
|
|
49
332
|
|
|
50
333
|
export default meta;
|
|
334
|
+
type Story = StoryObj<typeof TimeInput>;
|
|
51
335
|
|
|
52
|
-
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Helper: attach a per-story description
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
53
339
|
|
|
54
340
|
const withDescription = (story: Story, description: string): Story => ({
|
|
55
341
|
...story,
|
|
@@ -64,107 +350,857 @@ const withDescription = (story: Story, description: string): Story => ({
|
|
|
64
350
|
},
|
|
65
351
|
});
|
|
66
352
|
|
|
67
|
-
|
|
68
|
-
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Lesson period options (shared across combobox stories)
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
const LESSON_PERIOD_OPTIONS: TimeValue[] = [
|
|
358
|
+
'09:00',
|
|
359
|
+
'09:45',
|
|
360
|
+
'10:30',
|
|
361
|
+
'11:15',
|
|
362
|
+
'13:00',
|
|
363
|
+
'13:45',
|
|
364
|
+
'14:30',
|
|
365
|
+
'15:15',
|
|
366
|
+
'16:00',
|
|
367
|
+
];
|
|
69
368
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// Named template components for stateful stories
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
73
372
|
|
|
373
|
+
const ControlledTemplate = () => {
|
|
374
|
+
const [value, setValue] = useState<TimeValue | ''>('09:30');
|
|
74
375
|
return (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
376
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
377
|
+
<TimeInput
|
|
378
|
+
value={value}
|
|
379
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
380
|
+
id="controlled-time"
|
|
381
|
+
name="start-time"
|
|
382
|
+
/>
|
|
383
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
384
|
+
Selected:
|
|
385
|
+
{' '}
|
|
386
|
+
<code>{value || '(none)'}</code>
|
|
387
|
+
</p>
|
|
388
|
+
</div>
|
|
83
389
|
);
|
|
84
390
|
};
|
|
85
391
|
|
|
86
|
-
const
|
|
87
|
-
const [value, setValue] = useState<TimeValue | ''>(
|
|
392
|
+
const GranularitySecondTemplate = () => {
|
|
393
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
394
|
+
return (
|
|
395
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
396
|
+
<TimeInput
|
|
397
|
+
granularity="second"
|
|
398
|
+
value={value}
|
|
399
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
400
|
+
id="second-granularity"
|
|
401
|
+
name="precise-time"
|
|
402
|
+
/>
|
|
403
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
404
|
+
Selected:
|
|
405
|
+
{' '}
|
|
406
|
+
<code>{value || '(none)'}</code>
|
|
407
|
+
</p>
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
};
|
|
88
411
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
412
|
+
const WithMinMaxBoundsTemplate = () => {
|
|
413
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
414
|
+
return (
|
|
415
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
416
|
+
<TimeInput
|
|
417
|
+
min="08:00"
|
|
418
|
+
max="17:00"
|
|
419
|
+
value={value}
|
|
420
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
421
|
+
id="bounded-time"
|
|
422
|
+
name="school-time"
|
|
423
|
+
aria-label="School hours time"
|
|
424
|
+
/>
|
|
425
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
426
|
+
Restricted to school hours: 08:00 – 17:00
|
|
427
|
+
</p>
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
};
|
|
92
431
|
|
|
432
|
+
const WithOptionsTemplate = () => {
|
|
433
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
93
434
|
return (
|
|
94
|
-
<
|
|
95
|
-
style={{ display: 'grid', gap: 12, width: 280 }}
|
|
96
|
-
onSubmit={(event) => {
|
|
97
|
-
event.preventDefault();
|
|
98
|
-
const form = event.currentTarget;
|
|
99
|
-
if (!form.reportValidity()) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
}}
|
|
103
|
-
>
|
|
435
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
104
436
|
<TimeInput
|
|
105
|
-
{
|
|
437
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
106
438
|
value={value}
|
|
107
|
-
onValueChange={
|
|
108
|
-
|
|
109
|
-
|
|
439
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
440
|
+
id="lesson-period"
|
|
441
|
+
name="lesson-start"
|
|
442
|
+
placeholder="Select lesson period..."
|
|
443
|
+
aria-label="Lesson period"
|
|
444
|
+
/>
|
|
445
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
446
|
+
Selected:
|
|
447
|
+
{' '}
|
|
448
|
+
<code>{value || '(none)'}</code>
|
|
449
|
+
</p>
|
|
450
|
+
</div>
|
|
451
|
+
);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const SubstringSearchTemplate = () => {
|
|
455
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
456
|
+
return (
|
|
457
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
458
|
+
<TimeInput
|
|
459
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
460
|
+
searchType="substring"
|
|
461
|
+
value={value}
|
|
462
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
463
|
+
id="substring-time"
|
|
464
|
+
name="lesson-substring"
|
|
465
|
+
placeholder="Type to search..."
|
|
466
|
+
aria-label="Lesson period"
|
|
467
|
+
/>
|
|
468
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
469
|
+
Try typing
|
|
470
|
+
{' '}
|
|
471
|
+
<code>45</code>
|
|
472
|
+
{' '}
|
|
473
|
+
— matches any period containing "45".
|
|
474
|
+
</p>
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const HighlightedMatchesTemplate = () => {
|
|
480
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
481
|
+
return (
|
|
482
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
483
|
+
<TimeInput
|
|
484
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
485
|
+
searchType="substring"
|
|
486
|
+
highlightStringMatches
|
|
487
|
+
value={value}
|
|
488
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
489
|
+
id="highlighted-time"
|
|
490
|
+
name="lesson-highlighted"
|
|
491
|
+
placeholder="Type to filter..."
|
|
492
|
+
aria-label="Lesson period"
|
|
493
|
+
/>
|
|
494
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
495
|
+
Matched characters are bolded in the dropdown list.
|
|
496
|
+
</p>
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const ControlledComboboxTemplate = () => {
|
|
502
|
+
const [value, setValue] = useState<TimeValue | ''>('13:00');
|
|
503
|
+
return (
|
|
504
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
505
|
+
<TimeInput
|
|
506
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
507
|
+
value={value}
|
|
508
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
509
|
+
id="controlled-combobox"
|
|
510
|
+
name="lesson-controlled"
|
|
511
|
+
placeholder="Select lesson period..."
|
|
512
|
+
aria-label="Lesson period"
|
|
513
|
+
/>
|
|
514
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
515
|
+
Selected:
|
|
516
|
+
{' '}
|
|
517
|
+
<code>{value || '(none)'}</code>
|
|
518
|
+
</p>
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// FormField template components
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
const InFormFieldTemplate = () => (
|
|
528
|
+
<div style={{ maxWidth: '320px' }}>
|
|
529
|
+
<FormField
|
|
530
|
+
label="Lesson start time"
|
|
531
|
+
id="ff-start-time"
|
|
532
|
+
inputType="time"
|
|
533
|
+
inputProps={{ name: 'lesson-start', defaultValue: '09:00' }}
|
|
534
|
+
/>
|
|
535
|
+
</div>
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const InFormFieldWithErrorTemplate = () => (
|
|
539
|
+
<div style={{ maxWidth: '320px' }}>
|
|
540
|
+
<FormField
|
|
541
|
+
label="Registration start time"
|
|
542
|
+
id="ff-error-time"
|
|
543
|
+
inputType="time"
|
|
544
|
+
errorText="Please enter a valid registration time."
|
|
545
|
+
inputProps={{ name: 'registration-time' }}
|
|
546
|
+
/>
|
|
547
|
+
</div>
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const InFormFieldComboboxTemplate = () => {
|
|
551
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
552
|
+
return (
|
|
553
|
+
<div style={{ maxWidth: '320px' }}>
|
|
554
|
+
<FormField
|
|
555
|
+
label="Lesson period"
|
|
556
|
+
id="ff-lesson-period"
|
|
557
|
+
inputType="time"
|
|
558
|
+
inputProps={{
|
|
559
|
+
name: 'lesson-period',
|
|
560
|
+
options: LESSON_PERIOD_OPTIONS,
|
|
561
|
+
value,
|
|
562
|
+
onValueChange: v => setValue(v as TimeValue | ''),
|
|
563
|
+
placeholder: 'Select a lesson period...',
|
|
110
564
|
}}
|
|
111
565
|
/>
|
|
112
|
-
|
|
113
|
-
Submit
|
|
114
|
-
</Button>
|
|
115
|
-
</form>
|
|
566
|
+
</div>
|
|
116
567
|
);
|
|
117
568
|
};
|
|
118
569
|
|
|
119
|
-
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Stories — Native mode
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
export const Default: Story = {
|
|
120
575
|
args: {
|
|
121
|
-
|
|
122
|
-
|
|
576
|
+
'id': 'default-time',
|
|
577
|
+
'name': 'default-time',
|
|
578
|
+
'aria-label': 'Start time',
|
|
123
579
|
},
|
|
124
|
-
render: args =>
|
|
125
|
-
|
|
580
|
+
render: args => (
|
|
581
|
+
<div style={{ maxWidth: '280px' }}>
|
|
582
|
+
<TimeInput {...args} />
|
|
583
|
+
</div>
|
|
584
|
+
),
|
|
585
|
+
};
|
|
126
586
|
|
|
127
|
-
export const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
587
|
+
export const Controlled: Story = withDescription(
|
|
588
|
+
{
|
|
589
|
+
parameters: {
|
|
590
|
+
controls: { disable: true },
|
|
591
|
+
docs: {
|
|
592
|
+
source: {
|
|
593
|
+
language: 'tsx',
|
|
594
|
+
code: `
|
|
595
|
+
import { useState } from 'react';
|
|
596
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
597
|
+
|
|
598
|
+
function ControlledTimeInputExample() {
|
|
599
|
+
const [value, setValue] = useState<TimeValue | ''>('09:30');
|
|
600
|
+
|
|
601
|
+
return (
|
|
602
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)', maxWidth: '280px' }}>
|
|
603
|
+
<TimeInput
|
|
604
|
+
value={value}
|
|
605
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
606
|
+
id="start-time"
|
|
607
|
+
name="start-time"
|
|
608
|
+
aria-label="Start time"
|
|
609
|
+
/>
|
|
610
|
+
<p style={{ margin: 0, color: 'var(--color-grey-600)' }}>
|
|
611
|
+
Selected: <code>{value || '(none)'}</code>
|
|
612
|
+
</p>
|
|
613
|
+
</div>
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export default ControlledTimeInputExample;
|
|
618
|
+
`.trim(),
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
render: () => <ControlledTemplate />,
|
|
131
623
|
},
|
|
132
|
-
|
|
133
|
-
|
|
624
|
+
'A controlled TimeInput keeps the value in React state via `onValueChange`. This is the recommended pattern for forms where you need to read, validate, or derive other values from the selected time.',
|
|
625
|
+
);
|
|
134
626
|
|
|
135
|
-
export const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
627
|
+
export const Uncontrolled: Story = withDescription(
|
|
628
|
+
{
|
|
629
|
+
parameters: {
|
|
630
|
+
controls: { disable: true },
|
|
631
|
+
docs: {
|
|
632
|
+
source: {
|
|
633
|
+
language: 'tsx',
|
|
634
|
+
code: `
|
|
635
|
+
import { TimeInput } from '@arbor-education/design-system.components';
|
|
636
|
+
|
|
637
|
+
function UncontrolledTimeInputExample() {
|
|
638
|
+
return (
|
|
639
|
+
<TimeInput
|
|
640
|
+
defaultValue="08:45"
|
|
641
|
+
id="uncontrolled-time"
|
|
642
|
+
name="registration-time"
|
|
643
|
+
aria-label="Registration time"
|
|
644
|
+
/>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export default UncontrolledTimeInputExample;
|
|
649
|
+
`.trim(),
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
render: () => (
|
|
654
|
+
<div style={{ maxWidth: '280px' }}>
|
|
655
|
+
<TimeInput
|
|
656
|
+
defaultValue="08:45"
|
|
657
|
+
id="uncontrolled-time"
|
|
658
|
+
name="registration-time"
|
|
659
|
+
aria-label="Registration time"
|
|
660
|
+
/>
|
|
661
|
+
</div>
|
|
662
|
+
),
|
|
141
663
|
},
|
|
142
|
-
|
|
143
|
-
|
|
664
|
+
'An uncontrolled TimeInput uses `defaultValue` to set the initial time. React does not track subsequent changes — the current value is read via a form `FormData` or a forwarded `ref`. Suitable for simple forms where you only need the value on submit.',
|
|
665
|
+
);
|
|
144
666
|
|
|
145
|
-
export const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
667
|
+
export const GranularitySecond: Story = withDescription(
|
|
668
|
+
{
|
|
669
|
+
parameters: {
|
|
670
|
+
controls: { disable: true },
|
|
671
|
+
docs: {
|
|
672
|
+
source: {
|
|
673
|
+
language: 'tsx',
|
|
674
|
+
code: `
|
|
675
|
+
import { useState } from 'react';
|
|
676
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
677
|
+
|
|
678
|
+
function PreciseTimeInputExample() {
|
|
679
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
680
|
+
|
|
681
|
+
return (
|
|
682
|
+
<TimeInput
|
|
683
|
+
granularity="second"
|
|
684
|
+
value={value}
|
|
685
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
686
|
+
id="precise-time"
|
|
687
|
+
name="precise-time"
|
|
688
|
+
aria-label="Precise time"
|
|
689
|
+
/>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export default PreciseTimeInputExample;
|
|
694
|
+
`.trim(),
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
render: () => <GranularitySecondTemplate />,
|
|
150
699
|
},
|
|
151
|
-
|
|
152
|
-
|
|
700
|
+
'Setting `granularity="second"` adds a seconds segment to the native time input (`step={1}`). Use this for stopwatch-style timing or precise event logging. **Native mode only** — silently ignored when `options` is provided.',
|
|
701
|
+
);
|
|
153
702
|
|
|
154
|
-
export const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
703
|
+
export const WithMinMaxBounds: Story = withDescription(
|
|
704
|
+
{
|
|
705
|
+
parameters: {
|
|
706
|
+
controls: { disable: true },
|
|
707
|
+
docs: {
|
|
708
|
+
source: {
|
|
709
|
+
language: 'tsx',
|
|
710
|
+
code: `
|
|
711
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
712
|
+
|
|
713
|
+
function BoundedTimeInputExample() {
|
|
714
|
+
return (
|
|
715
|
+
<TimeInput
|
|
716
|
+
min="08:00"
|
|
717
|
+
max="17:00"
|
|
718
|
+
id="school-time"
|
|
719
|
+
name="school-time"
|
|
720
|
+
aria-label="School hours time"
|
|
721
|
+
/>
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export default BoundedTimeInputExample;
|
|
726
|
+
`.trim(),
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
render: () => <WithMinMaxBoundsTemplate />,
|
|
161
731
|
},
|
|
162
|
-
|
|
163
|
-
|
|
732
|
+
'The `min` and `max` props restrict the selectable range in the native browser picker to school hours. **Native mode only** — in combobox mode, restrict available times by filtering the `options` array instead.',
|
|
733
|
+
);
|
|
164
734
|
|
|
165
|
-
export const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
735
|
+
export const WithError: Story = withDescription(
|
|
736
|
+
{
|
|
737
|
+
parameters: {
|
|
738
|
+
controls: { disable: true },
|
|
739
|
+
docs: {
|
|
740
|
+
source: {
|
|
741
|
+
language: 'tsx',
|
|
742
|
+
code: `
|
|
743
|
+
import { TimeInput } from '@arbor-education/design-system.components';
|
|
744
|
+
|
|
745
|
+
// Standalone usage — you MUST set aria-invalid yourself.
|
|
746
|
+
// Inside <FormField> this is handled automatically when errorText is present.
|
|
747
|
+
function ErrorTimeInputExample() {
|
|
748
|
+
return (
|
|
749
|
+
<TimeInput
|
|
750
|
+
hasError
|
|
751
|
+
aria-invalid={true}
|
|
752
|
+
aria-label="End time"
|
|
753
|
+
id="end-time"
|
|
754
|
+
name="end-time"
|
|
755
|
+
/>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export default ErrorTimeInputExample;
|
|
760
|
+
`.trim(),
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
render: () => (
|
|
765
|
+
<div style={{ maxWidth: '280px' }}>
|
|
766
|
+
<TimeInput
|
|
767
|
+
hasError
|
|
768
|
+
aria-invalid={true}
|
|
769
|
+
aria-label="End time"
|
|
770
|
+
id="end-time-error-story"
|
|
771
|
+
name="end-time"
|
|
772
|
+
/>
|
|
773
|
+
</div>
|
|
774
|
+
),
|
|
775
|
+
},
|
|
776
|
+
'`hasError` applies error-state visual styling. **Important:** it does NOT automatically set `aria-invalid` when used standalone — add that attribute yourself for screen-reader coverage. Inside `<FormField>`, both are set automatically when `errorText` is provided.',
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
export const Disabled: Story = withDescription(
|
|
780
|
+
{
|
|
781
|
+
parameters: {
|
|
782
|
+
controls: { disable: true },
|
|
783
|
+
docs: {
|
|
784
|
+
source: {
|
|
785
|
+
language: 'tsx',
|
|
786
|
+
code: `
|
|
787
|
+
import { TimeInput } from '@arbor-education/design-system.components';
|
|
788
|
+
|
|
789
|
+
function DisabledTimeInputExample() {
|
|
790
|
+
return (
|
|
791
|
+
<TimeInput
|
|
792
|
+
disabled
|
|
793
|
+
value="09:00"
|
|
794
|
+
aria-label="Lesson start time"
|
|
795
|
+
id="disabled-time"
|
|
796
|
+
name="disabled-time"
|
|
797
|
+
/>
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
export default DisabledTimeInputExample;
|
|
802
|
+
`.trim(),
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
render: () => (
|
|
807
|
+
<div style={{ maxWidth: '280px' }}>
|
|
808
|
+
<TimeInput
|
|
809
|
+
disabled
|
|
810
|
+
value="09:00"
|
|
811
|
+
aria-label="Lesson start time"
|
|
812
|
+
id="disabled-time-story"
|
|
813
|
+
name="disabled-time"
|
|
814
|
+
/>
|
|
815
|
+
</div>
|
|
816
|
+
),
|
|
817
|
+
},
|
|
818
|
+
'A disabled TimeInput prevents user interaction. The current value remains visible, so users can still read the scheduled time. Pass `disabled` via `inputProps` when using inside `<FormField>`.',
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
// ---------------------------------------------------------------------------
|
|
822
|
+
// Stories — Combobox mode
|
|
823
|
+
// ---------------------------------------------------------------------------
|
|
824
|
+
|
|
825
|
+
export const WithOptions: Story = withDescription(
|
|
826
|
+
{
|
|
827
|
+
parameters: {
|
|
828
|
+
controls: { disable: true },
|
|
829
|
+
docs: {
|
|
830
|
+
source: {
|
|
831
|
+
language: 'tsx',
|
|
832
|
+
code: `
|
|
833
|
+
import { useState } from 'react';
|
|
834
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
835
|
+
|
|
836
|
+
const LESSON_PERIODS: TimeValue[] = [
|
|
837
|
+
'09:00', '09:45', '10:30', '11:15',
|
|
838
|
+
'13:00', '13:45', '14:30', '15:15', '16:00',
|
|
839
|
+
];
|
|
840
|
+
|
|
841
|
+
function LessonPeriodPickerExample() {
|
|
842
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
843
|
+
|
|
844
|
+
return (
|
|
845
|
+
<TimeInput
|
|
846
|
+
options={LESSON_PERIODS}
|
|
847
|
+
value={value}
|
|
848
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
849
|
+
id="lesson-period"
|
|
850
|
+
name="lesson-start"
|
|
851
|
+
placeholder="Select lesson period..."
|
|
852
|
+
aria-label="Lesson period"
|
|
853
|
+
/>
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export default LessonPeriodPickerExample;
|
|
858
|
+
`.trim(),
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
render: () => <WithOptionsTemplate />,
|
|
863
|
+
},
|
|
864
|
+
'Providing an `options` array switches the component to **combobox mode**. The user can type to filter or click to select from the predefined list. Ideal for scheduling interfaces where only specific time slots are valid.',
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
export const SubstringSearch: Story = withDescription(
|
|
868
|
+
{
|
|
869
|
+
parameters: {
|
|
870
|
+
controls: { disable: true },
|
|
871
|
+
docs: {
|
|
872
|
+
source: {
|
|
873
|
+
language: 'tsx',
|
|
874
|
+
code: `
|
|
875
|
+
import { useState } from 'react';
|
|
876
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
877
|
+
|
|
878
|
+
const LESSON_PERIODS: TimeValue[] = [
|
|
879
|
+
'09:00', '09:45', '10:30', '11:15',
|
|
880
|
+
'13:00', '13:45', '14:30', '15:15', '16:00',
|
|
881
|
+
];
|
|
882
|
+
|
|
883
|
+
function SubstringSearchPickerExample() {
|
|
884
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
885
|
+
|
|
886
|
+
return (
|
|
887
|
+
<TimeInput
|
|
888
|
+
options={LESSON_PERIODS}
|
|
889
|
+
searchType="substring"
|
|
890
|
+
value={value}
|
|
891
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
892
|
+
id="lesson-substring"
|
|
893
|
+
name="lesson-substring"
|
|
894
|
+
placeholder="Type to search..."
|
|
895
|
+
aria-label="Lesson period"
|
|
896
|
+
/>
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export default SubstringSearchPickerExample;
|
|
901
|
+
`.trim(),
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
render: () => <SubstringSearchTemplate />,
|
|
906
|
+
},
|
|
907
|
+
'`searchType="substring"` matches anywhere in the option string, not just the start. Try typing `"45"` to match `09:45`, `13:45`, and `14:45`. **Combobox mode only** — silently ignored in native mode.',
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
export const HighlightedMatches: Story = withDescription(
|
|
911
|
+
{
|
|
912
|
+
parameters: {
|
|
913
|
+
controls: { disable: true },
|
|
914
|
+
docs: {
|
|
915
|
+
source: {
|
|
916
|
+
language: 'tsx',
|
|
917
|
+
code: `
|
|
918
|
+
import { useState } from 'react';
|
|
919
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
920
|
+
|
|
921
|
+
const LESSON_PERIODS: TimeValue[] = [
|
|
922
|
+
'09:00', '09:45', '10:30', '11:15',
|
|
923
|
+
'13:00', '13:45', '14:30', '15:15', '16:00',
|
|
924
|
+
];
|
|
925
|
+
|
|
926
|
+
function HighlightedMatchesPickerExample() {
|
|
927
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
928
|
+
|
|
929
|
+
return (
|
|
930
|
+
<TimeInput
|
|
931
|
+
options={LESSON_PERIODS}
|
|
932
|
+
searchType="substring"
|
|
933
|
+
highlightStringMatches
|
|
934
|
+
value={value}
|
|
935
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
936
|
+
id="lesson-highlighted"
|
|
937
|
+
name="lesson-highlighted"
|
|
938
|
+
placeholder="Type to filter..."
|
|
939
|
+
aria-label="Lesson period"
|
|
940
|
+
/>
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export default HighlightedMatchesPickerExample;
|
|
945
|
+
`.trim(),
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
render: () => <HighlightedMatchesTemplate />,
|
|
950
|
+
},
|
|
951
|
+
'`highlightStringMatches` bolds the matched characters in each dropdown option. Combine with `searchType="substring"` for maximum clarity when filtering. **Combobox mode only.**',
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
export const ComboboxWithError: Story = withDescription(
|
|
955
|
+
{
|
|
956
|
+
parameters: {
|
|
957
|
+
controls: { disable: true },
|
|
958
|
+
docs: {
|
|
959
|
+
source: {
|
|
960
|
+
language: 'tsx',
|
|
961
|
+
code: `
|
|
962
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
963
|
+
|
|
964
|
+
const LESSON_PERIODS: TimeValue[] = ['09:00', '09:45', '10:30', '11:15'];
|
|
965
|
+
|
|
966
|
+
function ErrorComboboxTimeInputExample() {
|
|
967
|
+
return (
|
|
968
|
+
<TimeInput
|
|
969
|
+
options={LESSON_PERIODS}
|
|
970
|
+
hasError
|
|
971
|
+
aria-invalid={true}
|
|
972
|
+
aria-label="Lesson period"
|
|
973
|
+
id="lesson-error"
|
|
974
|
+
name="lesson-error"
|
|
975
|
+
placeholder="Select a lesson period..."
|
|
976
|
+
/>
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export default ErrorComboboxTimeInputExample;
|
|
981
|
+
`.trim(),
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
},
|
|
985
|
+
render: () => (
|
|
986
|
+
<div style={{ maxWidth: '280px' }}>
|
|
987
|
+
<TimeInput
|
|
988
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
989
|
+
hasError
|
|
990
|
+
aria-invalid={true}
|
|
991
|
+
aria-label="Lesson period"
|
|
992
|
+
id="combobox-error-story"
|
|
993
|
+
name="lesson-error"
|
|
994
|
+
placeholder="Select a lesson period..."
|
|
995
|
+
/>
|
|
996
|
+
</div>
|
|
997
|
+
),
|
|
998
|
+
},
|
|
999
|
+
'Combobox mode supports `hasError` for error-state styling. As with native mode, `aria-invalid` must be set manually when used outside `<FormField>`.',
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
export const ComboboxDisabled: Story = withDescription(
|
|
1003
|
+
{
|
|
1004
|
+
parameters: {
|
|
1005
|
+
controls: { disable: true },
|
|
1006
|
+
docs: {
|
|
1007
|
+
source: {
|
|
1008
|
+
language: 'tsx',
|
|
1009
|
+
code: `
|
|
1010
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
1011
|
+
|
|
1012
|
+
const LESSON_PERIODS: TimeValue[] = ['09:00', '09:45', '10:30', '11:15'];
|
|
1013
|
+
|
|
1014
|
+
function DisabledComboboxTimeInputExample() {
|
|
1015
|
+
return (
|
|
1016
|
+
<TimeInput
|
|
1017
|
+
options={LESSON_PERIODS}
|
|
1018
|
+
disabled
|
|
1019
|
+
value="09:00"
|
|
1020
|
+
aria-label="Lesson period"
|
|
1021
|
+
id="lesson-disabled"
|
|
1022
|
+
name="lesson-disabled"
|
|
1023
|
+
/>
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export default DisabledComboboxTimeInputExample;
|
|
1028
|
+
`.trim(),
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
render: () => (
|
|
1033
|
+
<div style={{ maxWidth: '280px' }}>
|
|
1034
|
+
<TimeInput
|
|
1035
|
+
options={LESSON_PERIOD_OPTIONS}
|
|
1036
|
+
disabled
|
|
1037
|
+
value="09:00"
|
|
1038
|
+
aria-label="Lesson period"
|
|
1039
|
+
id="combobox-disabled-story"
|
|
1040
|
+
name="lesson-disabled"
|
|
1041
|
+
/>
|
|
1042
|
+
</div>
|
|
1043
|
+
),
|
|
1044
|
+
},
|
|
1045
|
+
'Combobox mode respects the `disabled` prop. The trigger button is non-interactive and visually muted, but the selected value remains readable.',
|
|
1046
|
+
);
|
|
1047
|
+
|
|
1048
|
+
export const ControlledCombobox: Story = withDescription(
|
|
1049
|
+
{
|
|
1050
|
+
parameters: {
|
|
1051
|
+
controls: { disable: true },
|
|
1052
|
+
docs: {
|
|
1053
|
+
source: {
|
|
1054
|
+
language: 'tsx',
|
|
1055
|
+
code: `
|
|
1056
|
+
import { useState } from 'react';
|
|
1057
|
+
import { TimeInput, type TimeValue } from '@arbor-education/design-system.components';
|
|
1058
|
+
|
|
1059
|
+
const LESSON_PERIODS: TimeValue[] = [
|
|
1060
|
+
'09:00', '09:45', '10:30', '11:15',
|
|
1061
|
+
'13:00', '13:45', '14:30', '15:15', '16:00',
|
|
1062
|
+
];
|
|
1063
|
+
|
|
1064
|
+
function ControlledComboboxTimeInputExample() {
|
|
1065
|
+
const [value, setValue] = useState<TimeValue | ''>('13:00');
|
|
1066
|
+
|
|
1067
|
+
return (
|
|
1068
|
+
<TimeInput
|
|
1069
|
+
options={LESSON_PERIODS}
|
|
1070
|
+
value={value}
|
|
1071
|
+
onValueChange={v => setValue(v as TimeValue | '')}
|
|
1072
|
+
id="lesson-controlled"
|
|
1073
|
+
name="lesson-controlled"
|
|
1074
|
+
placeholder="Select lesson period..."
|
|
1075
|
+
aria-label="Lesson period"
|
|
1076
|
+
/>
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export default ControlledComboboxTimeInputExample;
|
|
1081
|
+
`.trim(),
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
render: () => <ControlledComboboxTemplate />,
|
|
1086
|
+
},
|
|
1087
|
+
'A controlled combobox TimeInput pre-selects the afternoon session ("13:00") via the `value` prop. `onValueChange` keeps React state in sync whenever the user picks a different period.',
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
// ---------------------------------------------------------------------------
|
|
1091
|
+
// Stories — FormField integration
|
|
1092
|
+
// ---------------------------------------------------------------------------
|
|
1093
|
+
|
|
1094
|
+
export const InFormField: Story = withDescription(
|
|
1095
|
+
{
|
|
1096
|
+
parameters: {
|
|
1097
|
+
controls: { disable: true },
|
|
1098
|
+
docs: {
|
|
1099
|
+
source: {
|
|
1100
|
+
language: 'tsx',
|
|
1101
|
+
code: `
|
|
1102
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
1103
|
+
|
|
1104
|
+
function InFormFieldExample() {
|
|
1105
|
+
return (
|
|
1106
|
+
<div style={{ maxWidth: '320px' }}>
|
|
1107
|
+
<FormField
|
|
1108
|
+
label="Lesson start time"
|
|
1109
|
+
id="lesson-start"
|
|
1110
|
+
inputType="time"
|
|
1111
|
+
inputProps={{ name: 'lesson-start', defaultValue: '09:00' }}
|
|
1112
|
+
/>
|
|
1113
|
+
</div>
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
export default InFormFieldExample;
|
|
1118
|
+
`.trim(),
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
render: () => <InFormFieldTemplate />,
|
|
1123
|
+
},
|
|
1124
|
+
'The recommended usage pattern — `<FormField inputType="time">` wires the accessible `<label>`, `aria-describedby`, `hasError`, and `aria-invalid` automatically. No manual prop threading required.',
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
export const InFormFieldWithError: Story = withDescription(
|
|
1128
|
+
{
|
|
1129
|
+
parameters: {
|
|
1130
|
+
controls: { disable: true },
|
|
1131
|
+
docs: {
|
|
1132
|
+
source: {
|
|
1133
|
+
language: 'tsx',
|
|
1134
|
+
code: `
|
|
1135
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
1136
|
+
|
|
1137
|
+
function InFormFieldWithErrorExample() {
|
|
1138
|
+
return (
|
|
1139
|
+
<div style={{ maxWidth: '320px' }}>
|
|
1140
|
+
<FormField
|
|
1141
|
+
label="Registration start time"
|
|
1142
|
+
id="registration-time"
|
|
1143
|
+
inputType="time"
|
|
1144
|
+
errorText="Please enter a valid registration time."
|
|
1145
|
+
inputProps={{ name: 'registration-time' }}
|
|
1146
|
+
/>
|
|
1147
|
+
</div>
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export default InFormFieldWithErrorExample;
|
|
1152
|
+
`.trim(),
|
|
1153
|
+
},
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
render: () => <InFormFieldWithErrorTemplate />,
|
|
1157
|
+
},
|
|
1158
|
+
'Setting `errorText` on `<FormField>` automatically applies error styling to the input, renders the error message, sets `aria-invalid`, and links `aria-describedby` to the error element. No extra props needed on `TimeInput` itself.',
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1161
|
+
export const InFormFieldCombobox: Story = withDescription(
|
|
1162
|
+
{
|
|
1163
|
+
parameters: {
|
|
1164
|
+
controls: { disable: true },
|
|
1165
|
+
docs: {
|
|
1166
|
+
source: {
|
|
1167
|
+
language: 'tsx',
|
|
1168
|
+
code: `
|
|
1169
|
+
import { useState } from 'react';
|
|
1170
|
+
import { FormField, type TimeValue } from '@arbor-education/design-system.components';
|
|
1171
|
+
|
|
1172
|
+
const LESSON_PERIODS: TimeValue[] = [
|
|
1173
|
+
'09:00', '09:45', '10:30', '11:15',
|
|
1174
|
+
'13:00', '13:45', '14:30', '15:15', '16:00',
|
|
1175
|
+
];
|
|
1176
|
+
|
|
1177
|
+
function InFormFieldComboboxExample() {
|
|
1178
|
+
const [value, setValue] = useState<TimeValue | ''>('');
|
|
1179
|
+
|
|
1180
|
+
return (
|
|
1181
|
+
<div style={{ maxWidth: '320px' }}>
|
|
1182
|
+
<FormField
|
|
1183
|
+
label="Lesson period"
|
|
1184
|
+
id="lesson-period"
|
|
1185
|
+
inputType="time"
|
|
1186
|
+
inputProps={{
|
|
1187
|
+
name: 'lesson-period',
|
|
1188
|
+
options: LESSON_PERIODS,
|
|
1189
|
+
value,
|
|
1190
|
+
onValueChange: v => setValue(v as TimeValue | ''),
|
|
1191
|
+
placeholder: 'Select a lesson period...',
|
|
1192
|
+
}}
|
|
1193
|
+
/>
|
|
1194
|
+
</div>
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
export default InFormFieldComboboxExample;
|
|
1199
|
+
`.trim(),
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
},
|
|
1203
|
+
render: () => <InFormFieldComboboxTemplate />,
|
|
169
1204
|
},
|
|
170
|
-
|
|
1205
|
+
'Combobox mode works seamlessly inside `<FormField>`. Pass `options` (and any combobox-mode props) via `inputProps`. The label, ARIA wiring, and error handling are all handled identically to native mode.',
|
|
1206
|
+
);
|