@arbor-education/design-system.components 0.15.0 → 0.16.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/.gather/skills/write-stories/SKILL.md +207 -271
- package/.storybook/preview.ts +5 -0
- package/CHANGELOG.md +17 -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 +2 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +357 -3
- package/dist/components/table/Table.stories.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/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 +411 -3
- 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/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,202 +1,980 @@
|
|
|
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 type { TimeValue } from 'Components/formField/inputs/time/TimeInput';
|
|
14
|
+
import { DateTimePicker } from './DateTimePicker';
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Docs page content
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const DESCRIPTION_INTRO = [
|
|
21
|
+
'DateTimePicker combines a date input with an embedded time input, keeping a single unified `Date`',
|
|
22
|
+
'value. Built on [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover)',
|
|
23
|
+
'and [react-day-picker](https://daypicker.dev/).',
|
|
24
|
+
'',
|
|
25
|
+
'The calendar popup contains both the date picker and the time input, keeping date and time',
|
|
26
|
+
'selection in a single interaction flow. When a date is picked from the calendar, the existing',
|
|
27
|
+
'time is preserved. When a time is changed, the existing date is preserved.',
|
|
28
|
+
].join('\n');
|
|
29
|
+
|
|
30
|
+
const USAGE_GUIDANCE = [
|
|
31
|
+
'### When to use',
|
|
32
|
+
'',
|
|
33
|
+
'- **Event scheduling** — lesson start times, parent evening appointments, report deadlines',
|
|
34
|
+
'- **Booking flows** — any scenario where both a date AND a specific time are required',
|
|
35
|
+
'- **Audit timestamps** — log entries, registration opens/closes with precise time',
|
|
36
|
+
'',
|
|
37
|
+
'---',
|
|
38
|
+
'',
|
|
39
|
+
'### When NOT to use',
|
|
40
|
+
'',
|
|
41
|
+
'| Situation | Use instead |',
|
|
42
|
+
'|---|---|',
|
|
43
|
+
'| Date only (no time needed) | `DatePicker` |',
|
|
44
|
+
'| Time only (no date needed) | `TimeInput` |',
|
|
45
|
+
'| Date range selection | Two `DatePicker` fields |',
|
|
46
|
+
'| Fixed time slots from a list | `DateTimePicker` with `timeOptions` |',
|
|
47
|
+
].join('\n');
|
|
48
|
+
|
|
49
|
+
const DEVELOPER_NOTES = [
|
|
50
|
+
'### Critical usage patterns',
|
|
51
|
+
'',
|
|
52
|
+
'> **Portal rendering.** The calendar panel renders via a Radix portal — do **not** place inside',
|
|
53
|
+
'> a container with `overflow: hidden` or the calendar will be clipped.',
|
|
54
|
+
'',
|
|
55
|
+
'**`onChange` receives a `Date` object — never a string.** Format for display with',
|
|
56
|
+
'`date?.toLocaleDateString(\'en-GB\')` + time as needed. Do not assume ISO string format.',
|
|
57
|
+
'',
|
|
58
|
+
'**`value` and `defaultValue` are `Date` objects — not strings.** Month is zero-indexed:',
|
|
59
|
+
'`new Date(2026, 3, 14, 9, 0)` = 14 April 2026, 09:00. Passing a string is a type error.',
|
|
60
|
+
'',
|
|
61
|
+
'```tsx',
|
|
62
|
+
'// ✅ Correct',
|
|
63
|
+
'value={new Date(2026, 3, 14, 9, 0)}',
|
|
64
|
+
'',
|
|
65
|
+
'// ❌ Wrong — type error',
|
|
66
|
+
'value="2026-04-14T09:00"',
|
|
67
|
+
'```',
|
|
68
|
+
'',
|
|
69
|
+
'**Three display formats — choose based on your context:**',
|
|
70
|
+
'',
|
|
71
|
+
'| `displayFormat` | Input type | Placeholder hint | Example value |',
|
|
72
|
+
'|---|---|---|---|',
|
|
73
|
+
'| `"native"` (default) | `datetime-local` | `YYYY-MM-DDTHH:mm` | `2026-04-14T14:27` |',
|
|
74
|
+
'| `"default"` | `text` | `DD/MM/YYYY HH:mm` | `14/04/2026 14:27` |',
|
|
75
|
+
'| `"friendly"` | `text` | `Pick a date, HH:mm` | `April 14th, 2026 14:27` |',
|
|
76
|
+
'',
|
|
77
|
+
'**`timeOptions` switches the embedded time input to combobox mode.** Instead of a free-form',
|
|
78
|
+
'time input, the user picks from a predefined list. Use for fixed appointment slots.',
|
|
79
|
+
'',
|
|
80
|
+
'**`hasError` is visual-only when used standalone.** It applies the red border but does NOT',
|
|
81
|
+
'set `aria-invalid`. Always pair both when outside `<FormField>`:',
|
|
82
|
+
'',
|
|
83
|
+
'```tsx',
|
|
84
|
+
'<DateTimePicker hasError aria-invalid={true} aria-describedby="error-id" />',
|
|
85
|
+
'```',
|
|
86
|
+
'',
|
|
87
|
+
'**`granularity="second"` enables seconds** in both the `datetime-local` step attribute',
|
|
88
|
+
'(native mode) and the embedded time input.',
|
|
89
|
+
'',
|
|
90
|
+
'---',
|
|
91
|
+
'',
|
|
92
|
+
'### Accessibility',
|
|
93
|
+
'',
|
|
94
|
+
'- Always label via `<FormField>` or `aria-label` / `aria-labelledby`',
|
|
95
|
+
'- Pair `hasError` with `aria-invalid={true}` when used standalone — `<FormField>` handles both',
|
|
96
|
+
'- The calendar icon button has built-in screen reader text ("Open date and time picker")',
|
|
97
|
+
'- The embedded time input has `aria-label="Select time"`',
|
|
98
|
+
'- The calendar closes and returns focus to the date input on date selection or Escape',
|
|
99
|
+
'',
|
|
100
|
+
'---',
|
|
101
|
+
'',
|
|
102
|
+
'### TypeScript types',
|
|
103
|
+
'',
|
|
104
|
+
'```ts',
|
|
105
|
+
"import { DateTimePicker } from '@arbor-education/design-system.components';",
|
|
106
|
+
'',
|
|
107
|
+
'// Access via namespace:',
|
|
108
|
+
'function MyField(props: DateTimePicker.Props) { ... }',
|
|
109
|
+
'',
|
|
110
|
+
'// Display format type:',
|
|
111
|
+
'const format: DateTimePicker.DisplayFormat = "native"; // "native" | "default" | "friendly"',
|
|
112
|
+
'```',
|
|
113
|
+
'',
|
|
114
|
+
'| Type | Description |',
|
|
115
|
+
'|---|---|',
|
|
116
|
+
'| `DateTimePicker.Props` | Full props interface |',
|
|
117
|
+
'| `DateTimePicker.DisplayFormat` | `"native" \\| "default" \\| "friendly"` |',
|
|
118
|
+
].join('\n');
|
|
119
|
+
|
|
120
|
+
const RELATED_COMPONENTS = [
|
|
121
|
+
'## Related components',
|
|
122
|
+
'',
|
|
123
|
+
'[DatePicker](?path=/docs/components-datepicker--docs) · [TimeInput](?path=/docs/components-formfield-inputs-timeinput--docs) · [FormField](?path=/docs/components-formfield--docs)',
|
|
124
|
+
].join('\n');
|
|
125
|
+
|
|
126
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Docs page component
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function DateTimePickerDocsPage() {
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
<Title />
|
|
136
|
+
<Subtitle />
|
|
137
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
138
|
+
<DocHeading>Interactive example</DocHeading>
|
|
139
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
140
|
+
<DocPrimary />
|
|
141
|
+
<Controls />
|
|
142
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
143
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
144
|
+
<DocHeading>Developer notes</DocHeading>
|
|
145
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
146
|
+
<DocHeading>Examples</DocHeading>
|
|
147
|
+
<Stories title="" />
|
|
148
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Meta
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
8
156
|
|
|
9
157
|
const meta = {
|
|
10
158
|
title: 'Components/DateTimePicker',
|
|
11
159
|
component: DateTimePicker,
|
|
12
|
-
|
|
13
|
-
Story => (
|
|
14
|
-
<div style={{ maxWidth: '280px', width: '100%' }}>
|
|
15
|
-
<Story />
|
|
16
|
-
</div>
|
|
17
|
-
),
|
|
18
|
-
],
|
|
160
|
+
tags: ['autodocs'],
|
|
19
161
|
parameters: {
|
|
20
|
-
layout: '
|
|
162
|
+
layout: 'padded',
|
|
21
163
|
docs: {
|
|
22
|
-
|
|
23
|
-
component:
|
|
24
|
-
'`DateTimePicker` combines a date field with the existing `TimeInput`, keeping a single combined `Date` value. `displayFormat="native"` (default) uses a `datetime-local` input with a decorative empty-state hint span (browsers do not reliably show placeholders on native date/time fields). `"default"` and `"friendly"` use a plain text input with locale-style formatting and a real HTML placeholder.',
|
|
25
|
-
},
|
|
164
|
+
page: DateTimePickerDocsPage,
|
|
26
165
|
},
|
|
27
166
|
},
|
|
28
|
-
tags: ['autodocs'],
|
|
29
|
-
args: {
|
|
30
|
-
onChange: fn(),
|
|
31
|
-
},
|
|
32
167
|
argTypes: {
|
|
33
|
-
displayFormat: {
|
|
34
|
-
control: 'inline-radio',
|
|
168
|
+
'displayFormat': {
|
|
169
|
+
control: { type: 'inline-radio' },
|
|
35
170
|
options: ['native', 'default', 'friendly'],
|
|
36
|
-
description:
|
|
37
|
-
'
|
|
171
|
+
description: [
|
|
172
|
+
'Controls input type and value format.',
|
|
173
|
+
'`"native"` (default): `datetime-local` input with `YYYY-MM-DDTHH:mm` hint.',
|
|
174
|
+
'`"default"`: text input with `DD/MM/YYYY HH:mm` format.',
|
|
175
|
+
'`"friendly"`: text input with `April 14th, 2026 14:27` format.',
|
|
176
|
+
].join(' '),
|
|
177
|
+
table: {
|
|
178
|
+
type: { summary: '"native" | "default" | "friendly"' },
|
|
179
|
+
defaultValue: { summary: '"native"' },
|
|
180
|
+
},
|
|
38
181
|
},
|
|
39
|
-
granularity: {
|
|
40
|
-
control: 'inline-radio',
|
|
182
|
+
'granularity': {
|
|
183
|
+
control: { type: 'inline-radio' },
|
|
41
184
|
options: ['minute', 'second'],
|
|
42
|
-
description:
|
|
185
|
+
description: [
|
|
186
|
+
'Controls time precision.',
|
|
187
|
+
'`"minute"` (default): HH:mm.',
|
|
188
|
+
'`"second"`: HH:mm:ss — sets `step={1}` on the datetime-local input and shows seconds in the embedded time input.',
|
|
189
|
+
].join(' '),
|
|
190
|
+
table: {
|
|
191
|
+
type: { summary: '"minute" | "second"' },
|
|
192
|
+
defaultValue: { summary: '"minute"' },
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
'placeholder': {
|
|
196
|
+
control: 'text',
|
|
197
|
+
description: [
|
|
198
|
+
'Custom text for the empty-state hint overlay (native mode only).',
|
|
199
|
+
'When set and the field is empty, the input becomes read-only — the calendar opens on click only, not Tab focus.',
|
|
200
|
+
].join(' '),
|
|
201
|
+
table: {
|
|
202
|
+
type: { summary: 'string' },
|
|
203
|
+
defaultValue: { summary: 'undefined' },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
'onChange': {
|
|
207
|
+
control: false,
|
|
208
|
+
action: 'onChange',
|
|
209
|
+
description: [
|
|
210
|
+
'Callback fired when the combined date+time changes.',
|
|
211
|
+
'Receives a `Date` object, or `undefined` when cleared.',
|
|
212
|
+
'Never receives a string.',
|
|
213
|
+
].join(' '),
|
|
214
|
+
table: {
|
|
215
|
+
type: { summary: '(newDate?: Date) => void' },
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
'hasError': {
|
|
219
|
+
control: 'boolean',
|
|
220
|
+
description: [
|
|
221
|
+
'Applies error-state visual styling (red border).',
|
|
222
|
+
'Does **not** set `aria-invalid` automatically when used standalone.',
|
|
223
|
+
'Always pair with `aria-invalid={true}`. `<FormField>` handles both automatically.',
|
|
224
|
+
].join(' '),
|
|
225
|
+
table: {
|
|
226
|
+
type: { summary: 'boolean' },
|
|
227
|
+
defaultValue: { summary: 'false' },
|
|
228
|
+
},
|
|
43
229
|
},
|
|
44
|
-
|
|
45
|
-
control:
|
|
46
|
-
description:
|
|
230
|
+
'value': {
|
|
231
|
+
control: false,
|
|
232
|
+
description: [
|
|
233
|
+
'Controlled combined date+time value.',
|
|
234
|
+
'Must be a `Date` object — not a string.',
|
|
235
|
+
'Pair with `onChange` to keep state in sync.',
|
|
236
|
+
].join(' '),
|
|
237
|
+
table: {
|
|
238
|
+
type: { summary: 'Date' },
|
|
239
|
+
defaultValue: { summary: 'undefined' },
|
|
240
|
+
},
|
|
47
241
|
},
|
|
48
|
-
|
|
242
|
+
'defaultValue': {
|
|
49
243
|
control: false,
|
|
50
|
-
description:
|
|
244
|
+
description: [
|
|
245
|
+
'Uncontrolled initial date+time.',
|
|
246
|
+
'Must be a `Date` object (month is zero-indexed: `new Date(2026, 3, 14, 9, 0)` = 14 Apr 2026 09:00).',
|
|
247
|
+
'Use when you only need the value on form submit.',
|
|
248
|
+
].join(' '),
|
|
249
|
+
table: {
|
|
250
|
+
type: { summary: 'Date' },
|
|
251
|
+
defaultValue: { summary: 'undefined' },
|
|
252
|
+
},
|
|
51
253
|
},
|
|
52
|
-
|
|
254
|
+
'timeOptions': {
|
|
53
255
|
control: false,
|
|
54
|
-
description:
|
|
256
|
+
description: [
|
|
257
|
+
'Array of `TimeValue` strings to use as preset time slots.',
|
|
258
|
+
'When provided, the embedded time input switches to **combobox mode** — the user picks from the list instead of typing freely.',
|
|
259
|
+
'Ideal for fixed appointment slots like "09:00", "09:30", "10:00".',
|
|
260
|
+
].join(' '),
|
|
261
|
+
table: {
|
|
262
|
+
type: { summary: 'TimeValue[]' },
|
|
263
|
+
defaultValue: { summary: 'undefined' },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
'searchType': {
|
|
267
|
+
control: { type: 'select' },
|
|
268
|
+
options: ['prefix', 'substring'],
|
|
269
|
+
description: [
|
|
270
|
+
'**Only used when `timeOptions` is provided.**',
|
|
271
|
+
'Controls how the time combobox filters options as the user types.',
|
|
272
|
+
'`"prefix"` (default) matches from the start; `"substring"` matches anywhere.',
|
|
273
|
+
].join(' '),
|
|
274
|
+
table: {
|
|
275
|
+
type: { summary: '"prefix" | "substring"' },
|
|
276
|
+
defaultValue: { summary: '"prefix"' },
|
|
277
|
+
},
|
|
55
278
|
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
description: '
|
|
279
|
+
'highlightStringMatches': {
|
|
280
|
+
control: 'boolean',
|
|
281
|
+
description: '**Only used when `timeOptions` is provided.** Bolds matched characters in the time dropdown.',
|
|
282
|
+
table: {
|
|
283
|
+
type: { summary: 'boolean' },
|
|
284
|
+
defaultValue: { summary: 'false' },
|
|
285
|
+
},
|
|
59
286
|
},
|
|
60
|
-
|
|
287
|
+
'id': {
|
|
61
288
|
control: 'text',
|
|
62
|
-
description: '
|
|
289
|
+
description: 'HTML `id` for the date input element. Required when inside `<FormField>` so the label `htmlFor` can be linked.',
|
|
290
|
+
table: {
|
|
291
|
+
type: { summary: 'string' },
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
'className': {
|
|
295
|
+
control: 'text',
|
|
296
|
+
description: 'Additional CSS class names on the root wrapper element.',
|
|
297
|
+
table: {
|
|
298
|
+
type: { summary: 'string' },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
'aria-invalid': {
|
|
302
|
+
control: 'boolean',
|
|
303
|
+
description: [
|
|
304
|
+
'Marks the date input as invalid for screen readers.',
|
|
305
|
+
'Must be set manually when used standalone — `<FormField>` sets this automatically when `errorText` is provided.',
|
|
306
|
+
].join(' '),
|
|
307
|
+
table: {
|
|
308
|
+
type: { summary: 'boolean | "true" | "false"' },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
'aria-describedby': {
|
|
312
|
+
control: 'text',
|
|
313
|
+
description: 'ID of the element describing the field (e.g. an error message).',
|
|
314
|
+
table: {
|
|
315
|
+
type: { summary: 'string' },
|
|
316
|
+
},
|
|
63
317
|
},
|
|
64
318
|
},
|
|
65
319
|
} satisfies Meta<typeof DateTimePicker>;
|
|
66
320
|
|
|
67
321
|
export default meta;
|
|
322
|
+
type Story = StoryObj<typeof DateTimePicker>;
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Helper: attach a per-story description
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
const withDescription = (story: Story, description: string): Story => ({
|
|
329
|
+
...story,
|
|
330
|
+
parameters: {
|
|
331
|
+
...story.parameters,
|
|
332
|
+
docs: {
|
|
333
|
+
...story.parameters?.docs,
|
|
334
|
+
description: {
|
|
335
|
+
story: description,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
68
340
|
|
|
69
|
-
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Named template components for stateful stories
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
70
344
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
345
|
+
const APPOINTMENT_SLOTS: TimeValue[] = [
|
|
346
|
+
'09:00',
|
|
347
|
+
'09:30',
|
|
348
|
+
'10:00',
|
|
349
|
+
'10:30',
|
|
350
|
+
'11:00',
|
|
351
|
+
'11:30',
|
|
352
|
+
'14:00',
|
|
353
|
+
'14:30',
|
|
354
|
+
'15:00',
|
|
355
|
+
'15:30',
|
|
356
|
+
];
|
|
76
357
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
358
|
+
const DisplayFormatDefaultTemplate = () => {
|
|
359
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 14, 27));
|
|
360
|
+
return (
|
|
361
|
+
<div style={{ maxWidth: '320px' }}>
|
|
362
|
+
<DateTimePicker
|
|
363
|
+
displayFormat="default"
|
|
364
|
+
value={value}
|
|
365
|
+
onChange={setValue}
|
|
366
|
+
id="format-default"
|
|
367
|
+
/>
|
|
368
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
369
|
+
Value:
|
|
370
|
+
{' '}
|
|
371
|
+
<code>
|
|
372
|
+
{value
|
|
373
|
+
? `${value.toLocaleDateString('en-GB')} ${value.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`
|
|
374
|
+
: '(none)'}
|
|
375
|
+
</code>
|
|
376
|
+
</p>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const DisplayFormatFriendlyTemplate = () => {
|
|
382
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 14, 27));
|
|
383
|
+
return (
|
|
384
|
+
<div style={{ maxWidth: '320px' }}>
|
|
385
|
+
<DateTimePicker
|
|
386
|
+
displayFormat="friendly"
|
|
387
|
+
value={value}
|
|
388
|
+
onChange={setValue}
|
|
389
|
+
id="format-friendly"
|
|
390
|
+
/>
|
|
391
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
392
|
+
Value:
|
|
393
|
+
{' '}
|
|
394
|
+
<code>
|
|
395
|
+
{value
|
|
396
|
+
? `${value.toLocaleDateString('en-GB')} ${value.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`
|
|
397
|
+
: '(none)'}
|
|
398
|
+
</code>
|
|
399
|
+
</p>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const WithTimeOptionsTemplate = () => {
|
|
405
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 9, 0));
|
|
406
|
+
return (
|
|
407
|
+
<div style={{ maxWidth: '280px' }}>
|
|
408
|
+
<DateTimePicker
|
|
409
|
+
timeOptions={APPOINTMENT_SLOTS}
|
|
410
|
+
value={value}
|
|
411
|
+
onChange={setValue}
|
|
412
|
+
id="time-options"
|
|
413
|
+
/>
|
|
414
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
415
|
+
Appointment:
|
|
416
|
+
{' '}
|
|
417
|
+
<code>
|
|
418
|
+
{value
|
|
419
|
+
? `${value.toLocaleDateString('en-GB')} ${value.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`
|
|
420
|
+
: '(none)'}
|
|
421
|
+
</code>
|
|
422
|
+
</p>
|
|
423
|
+
</div>
|
|
424
|
+
);
|
|
425
|
+
};
|
|
80
426
|
|
|
427
|
+
const GranularitySecondTemplate = () => {
|
|
428
|
+
const [value, setValue] = useState<Date | undefined>(undefined);
|
|
81
429
|
return (
|
|
82
|
-
<div style={{
|
|
430
|
+
<div style={{ maxWidth: '280px' }}>
|
|
83
431
|
<DateTimePicker
|
|
84
|
-
|
|
432
|
+
granularity="second"
|
|
85
433
|
value={value}
|
|
86
|
-
onChange={
|
|
87
|
-
|
|
88
|
-
|
|
434
|
+
onChange={setValue}
|
|
435
|
+
id="granularity-second"
|
|
436
|
+
/>
|
|
437
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
438
|
+
Value (with seconds):
|
|
439
|
+
{' '}
|
|
440
|
+
<code>
|
|
441
|
+
{value
|
|
442
|
+
? `${value.toLocaleDateString('en-GB')} ${value.toLocaleTimeString('en-GB')}`
|
|
443
|
+
: '(none)'}
|
|
444
|
+
</code>
|
|
445
|
+
</p>
|
|
446
|
+
</div>
|
|
447
|
+
);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const ControlledWithDisplayTemplate = () => {
|
|
451
|
+
const [value, setValue] = useState<Date | undefined>(undefined);
|
|
452
|
+
return (
|
|
453
|
+
<div style={{ maxWidth: '280px' }}>
|
|
454
|
+
<DateTimePicker
|
|
455
|
+
id="controlled-display"
|
|
456
|
+
onChange={setValue}
|
|
457
|
+
/>
|
|
458
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
459
|
+
Selected:
|
|
460
|
+
{' '}
|
|
461
|
+
<code>
|
|
462
|
+
{value
|
|
463
|
+
? `${value.toLocaleDateString('en-GB')} ${value.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`
|
|
464
|
+
: '(none)'}
|
|
465
|
+
</code>
|
|
466
|
+
</p>
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const WithFormFieldTemplate = () => (
|
|
472
|
+
<div style={{ maxWidth: '320px' }}>
|
|
473
|
+
<FormField
|
|
474
|
+
label="Event start date and time"
|
|
475
|
+
id="ff-event-start"
|
|
476
|
+
inputType="dateTimePicker"
|
|
477
|
+
fieldDescription="Select the date and time the event will begin."
|
|
478
|
+
inputProps={{}}
|
|
479
|
+
/>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const WithFormFieldAndErrorTemplate = () => {
|
|
484
|
+
const [submitted, setSubmitted] = useState(false);
|
|
485
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
486
|
+
const hasError = submitted && !selectedDate;
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
490
|
+
<FormField
|
|
491
|
+
label="Lesson start date and time"
|
|
492
|
+
id="ff-lesson-start"
|
|
493
|
+
inputType="dateTimePicker"
|
|
494
|
+
errorText={hasError ? 'Please select a date and time for the lesson.' : undefined}
|
|
495
|
+
inputProps={{
|
|
496
|
+
'onChange': setSelectedDate,
|
|
497
|
+
hasError,
|
|
498
|
+
'aria-invalid': hasError ? true : undefined,
|
|
89
499
|
}}
|
|
90
500
|
/>
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
501
|
+
<div>
|
|
502
|
+
<button
|
|
503
|
+
type="button"
|
|
504
|
+
onClick={() => setSubmitted(true)}
|
|
505
|
+
style={{ padding: 'var(--spacing-small) var(--spacing-medium)' }}
|
|
506
|
+
>
|
|
507
|
+
Submit
|
|
508
|
+
</button>
|
|
509
|
+
</div>
|
|
510
|
+
{submitted && selectedDate && (
|
|
511
|
+
<p style={{ margin: 0, color: 'var(--color-semantic-success-600)', fontSize: '0.875rem' }}>
|
|
512
|
+
Lesson scheduled for
|
|
94
513
|
{' '}
|
|
95
|
-
{
|
|
96
|
-
|
|
514
|
+
{selectedDate.toLocaleDateString('en-GB')}
|
|
515
|
+
{' at '}
|
|
516
|
+
{selectedDate.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}
|
|
517
|
+
.
|
|
518
|
+
</p>
|
|
97
519
|
)}
|
|
98
520
|
</div>
|
|
99
521
|
);
|
|
100
522
|
};
|
|
101
523
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
granularity: 'minute',
|
|
106
|
-
},
|
|
107
|
-
render: args => <ControlledDateTimePicker {...args} />,
|
|
108
|
-
};
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
// Stories
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
109
527
|
|
|
110
|
-
export const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
528
|
+
export const Default: Story = withDescription(
|
|
529
|
+
{
|
|
530
|
+
args: {
|
|
531
|
+
id: 'default-datetime',
|
|
532
|
+
},
|
|
533
|
+
render: args => (
|
|
534
|
+
<div style={{ maxWidth: '280px' }}>
|
|
535
|
+
<DateTimePicker {...args} />
|
|
536
|
+
</div>
|
|
537
|
+
),
|
|
114
538
|
},
|
|
115
|
-
|
|
116
|
-
|
|
539
|
+
[
|
|
540
|
+
'The interactive canvas — every prop is wired to the Controls panel.',
|
|
541
|
+
'Default `displayFormat="native"` shows the `YYYY-MM-DDTHH:mm` hint overlay (a decorative span,',
|
|
542
|
+
'because browsers do not reliably show `placeholder` on `datetime-local` inputs).',
|
|
543
|
+
'Click the calendar icon to open the combined date+time picker.',
|
|
544
|
+
].join(' '),
|
|
545
|
+
);
|
|
117
546
|
|
|
118
|
-
export const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
547
|
+
export const WithDefaultValue: Story = withDescription(
|
|
548
|
+
{
|
|
549
|
+
render: () => (
|
|
550
|
+
<div style={{ maxWidth: '280px' }}>
|
|
551
|
+
{/* 14 April 2026, 14:27 — month is zero-indexed (Apr = 3) */}
|
|
552
|
+
<DateTimePicker
|
|
553
|
+
id="default-value-datetime"
|
|
554
|
+
defaultValue={new Date(2026, 3, 14, 14, 27)}
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
557
|
+
),
|
|
558
|
+
parameters: {
|
|
559
|
+
controls: { disable: true },
|
|
560
|
+
docs: {
|
|
561
|
+
source: {
|
|
562
|
+
language: 'tsx',
|
|
563
|
+
code: `
|
|
564
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
565
|
+
|
|
566
|
+
function EditEventStart() {
|
|
567
|
+
// 14 April 2026, 14:27 — month is zero-indexed (Apr = 3)
|
|
568
|
+
const eventStart = new Date(2026, 3, 14, 14, 27);
|
|
125
569
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
570
|
+
return (
|
|
571
|
+
<DateTimePicker
|
|
572
|
+
id="event-start"
|
|
573
|
+
defaultValue={eventStart}
|
|
574
|
+
onChange={(date) => {
|
|
575
|
+
// date is Date | undefined — not a string
|
|
576
|
+
console.log('Updated to:', date?.toLocaleString('en-GB'));
|
|
577
|
+
}}
|
|
578
|
+
/>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
export default EditEventStart;
|
|
582
|
+
`.trim(),
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
},
|
|
130
586
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
587
|
+
[
|
|
588
|
+
'Use `defaultValue` to pre-populate an uncontrolled DateTimePicker — for example when editing',
|
|
589
|
+
'an existing event. The prop must be a `Date` object (month is zero-indexed: April = 3).',
|
|
590
|
+
].join(' '),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
export const GranularitySecond: Story = withDescription(
|
|
594
|
+
{
|
|
595
|
+
render: () => <GranularitySecondTemplate />,
|
|
596
|
+
parameters: {
|
|
597
|
+
controls: { disable: true },
|
|
598
|
+
docs: {
|
|
599
|
+
source: {
|
|
600
|
+
language: 'tsx',
|
|
601
|
+
code: `
|
|
602
|
+
import { useState } from 'react';
|
|
603
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
604
|
+
|
|
605
|
+
function PreciseDateTimePickerExample() {
|
|
606
|
+
const [value, setValue] = useState<Date | undefined>(undefined);
|
|
607
|
+
|
|
608
|
+
return (
|
|
609
|
+
<DateTimePicker
|
|
610
|
+
granularity="second"
|
|
611
|
+
value={value}
|
|
612
|
+
onChange={setValue}
|
|
613
|
+
id="precise-datetime"
|
|
614
|
+
/>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
export default PreciseDateTimePickerExample;
|
|
618
|
+
`.trim(),
|
|
619
|
+
},
|
|
135
620
|
},
|
|
136
621
|
},
|
|
137
622
|
},
|
|
138
|
-
|
|
623
|
+
[
|
|
624
|
+
'`granularity="second"` enables seconds in both the `datetime-local` native input (sets `step={1}`)',
|
|
625
|
+
'and the embedded time input inside the calendar popup.',
|
|
626
|
+
'Use for precise event logging, stopwatch-style entry, or when sub-minute accuracy matters.',
|
|
627
|
+
].join(' '),
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
export const DisplayFormatDefault: Story = withDescription(
|
|
631
|
+
{
|
|
632
|
+
render: () => <DisplayFormatDefaultTemplate />,
|
|
633
|
+
parameters: {
|
|
634
|
+
controls: { disable: true },
|
|
635
|
+
docs: {
|
|
636
|
+
source: {
|
|
637
|
+
language: 'tsx',
|
|
638
|
+
code: `
|
|
639
|
+
import { useState } from 'react';
|
|
640
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
139
641
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
642
|
+
function FormattedDateTimePickerExample() {
|
|
643
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 14, 27));
|
|
644
|
+
|
|
645
|
+
return (
|
|
646
|
+
<DateTimePicker
|
|
647
|
+
displayFormat="default"
|
|
648
|
+
value={value}
|
|
649
|
+
onChange={setValue}
|
|
650
|
+
id="event-datetime"
|
|
651
|
+
/>
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
export default FormattedDateTimePickerExample;
|
|
655
|
+
`.trim(),
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
},
|
|
144
659
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
660
|
+
[
|
|
661
|
+
'`displayFormat="default"` uses a plain text input showing the value as `DD/MM/YYYY HH:mm`',
|
|
662
|
+
'(e.g. `14/04/2026 14:27`). The appearance is consistent across all browsers — unlike `"native"`,',
|
|
663
|
+
'which uses the OS date/time widget and varies by platform.',
|
|
664
|
+
].join(' '),
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
export const DisplayFormatFriendly: Story = withDescription(
|
|
668
|
+
{
|
|
669
|
+
render: () => <DisplayFormatFriendlyTemplate />,
|
|
670
|
+
parameters: {
|
|
671
|
+
controls: { disable: true },
|
|
672
|
+
docs: {
|
|
673
|
+
source: {
|
|
674
|
+
language: 'tsx',
|
|
675
|
+
code: `
|
|
676
|
+
import { useState } from 'react';
|
|
677
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
678
|
+
|
|
679
|
+
function FriendlyDateTimePickerExample() {
|
|
680
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 14, 27));
|
|
681
|
+
|
|
682
|
+
return (
|
|
683
|
+
<DateTimePicker
|
|
684
|
+
displayFormat="friendly"
|
|
685
|
+
value={value}
|
|
686
|
+
onChange={setValue}
|
|
687
|
+
id="event-datetime"
|
|
688
|
+
/>
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
export default FriendlyDateTimePickerExample;
|
|
692
|
+
`.trim(),
|
|
693
|
+
},
|
|
149
694
|
},
|
|
150
695
|
},
|
|
151
696
|
},
|
|
152
|
-
|
|
697
|
+
[
|
|
698
|
+
'`displayFormat="friendly"` shows the value as `April 14th, 2026 14:27` — human-readable and',
|
|
699
|
+
'locale-style. Best for consumer-facing or less technical contexts where ISO-adjacent formats feel unfriendly.',
|
|
700
|
+
].join(' '),
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
export const WithTimeOptions: Story = withDescription(
|
|
704
|
+
{
|
|
705
|
+
render: () => <WithTimeOptionsTemplate />,
|
|
706
|
+
parameters: {
|
|
707
|
+
controls: { disable: true },
|
|
708
|
+
docs: {
|
|
709
|
+
source: {
|
|
710
|
+
language: 'tsx',
|
|
711
|
+
code: `
|
|
712
|
+
import { useState } from 'react';
|
|
713
|
+
import { DateTimePicker, type TimeValue } from '@arbor-education/design-system.components';
|
|
153
714
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
715
|
+
const APPOINTMENT_SLOTS: TimeValue[] = [
|
|
716
|
+
'09:00', '09:30', '10:00', '10:30', '11:00', '11:30',
|
|
717
|
+
'14:00', '14:30', '15:00', '15:30',
|
|
718
|
+
];
|
|
719
|
+
|
|
720
|
+
function AppointmentBookingExample() {
|
|
721
|
+
const [value, setValue] = useState<Date | undefined>(new Date(2026, 3, 14, 9, 0));
|
|
722
|
+
|
|
723
|
+
return (
|
|
724
|
+
<DateTimePicker
|
|
725
|
+
timeOptions={APPOINTMENT_SLOTS}
|
|
726
|
+
value={value}
|
|
727
|
+
onChange={setValue}
|
|
728
|
+
id="appointment-datetime"
|
|
729
|
+
/>
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
export default AppointmentBookingExample;
|
|
733
|
+
`.trim(),
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
},
|
|
160
737
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
738
|
+
[
|
|
739
|
+
'`timeOptions` switches the embedded time input from free-form entry to a combobox list.',
|
|
740
|
+
'Ideal for appointment booking where only specific slots are valid — the user picks a date',
|
|
741
|
+
'from the calendar, then picks a slot from the dropdown. Both stay in a single `Date` value.',
|
|
742
|
+
].join(' '),
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
export const WithPlaceholder: Story = withDescription(
|
|
746
|
+
{
|
|
747
|
+
render: () => (
|
|
748
|
+
<div style={{ maxWidth: '280px' }}>
|
|
749
|
+
<DateTimePicker
|
|
750
|
+
id="placeholder-datetime"
|
|
751
|
+
placeholder="Choose event start"
|
|
752
|
+
/>
|
|
753
|
+
</div>
|
|
754
|
+
),
|
|
755
|
+
parameters: {
|
|
756
|
+
controls: { disable: true },
|
|
757
|
+
docs: {
|
|
758
|
+
source: {
|
|
759
|
+
language: 'tsx',
|
|
760
|
+
code: `
|
|
761
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
762
|
+
|
|
763
|
+
function GuidedDateTimePickerExample() {
|
|
764
|
+
return (
|
|
765
|
+
<DateTimePicker
|
|
766
|
+
id="event-start"
|
|
767
|
+
placeholder="Choose event start"
|
|
768
|
+
onChange={(date) => {
|
|
769
|
+
console.log('Event start:', date?.toLocaleString('en-GB'));
|
|
770
|
+
}}
|
|
771
|
+
/>
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
export default GuidedDateTimePickerExample;
|
|
775
|
+
`.trim(),
|
|
776
|
+
},
|
|
166
777
|
},
|
|
167
778
|
},
|
|
168
779
|
},
|
|
169
|
-
|
|
780
|
+
[
|
|
781
|
+
'`placeholder` (native mode only) replaces the `YYYY-MM-DDTHH:mm` hint with custom copy.',
|
|
782
|
+
'When set and the field is empty, the input becomes **read-only** and the calendar opens on',
|
|
783
|
+
'**click only** — Tab focus does not trigger it. Prevents accidental popup during keyboard navigation.',
|
|
784
|
+
].join(' '),
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
export const ErrorState: Story = withDescription(
|
|
788
|
+
{
|
|
789
|
+
render: () => (
|
|
790
|
+
<div style={{ maxWidth: '280px' }}>
|
|
791
|
+
<DateTimePicker
|
|
792
|
+
id="error-state-datetime"
|
|
793
|
+
hasError
|
|
794
|
+
aria-invalid={true}
|
|
795
|
+
aria-describedby="error-state-datetime-msg"
|
|
796
|
+
/>
|
|
797
|
+
<p
|
|
798
|
+
id="error-state-datetime-msg"
|
|
799
|
+
role="alert"
|
|
800
|
+
style={{ margin: 'var(--spacing-xsmall) 0 0', color: 'var(--color-semantic-destructive-600)', fontSize: '0.875rem' }}
|
|
801
|
+
>
|
|
802
|
+
Please select a valid date and time.
|
|
803
|
+
</p>
|
|
804
|
+
</div>
|
|
805
|
+
),
|
|
806
|
+
parameters: {
|
|
807
|
+
controls: { disable: true },
|
|
808
|
+
docs: {
|
|
809
|
+
source: {
|
|
810
|
+
language: 'tsx',
|
|
811
|
+
code: `
|
|
812
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
170
813
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
814
|
+
// Standalone — set both hasError (visual) and aria-invalid (screen reader) yourself.
|
|
815
|
+
// Inside <FormField> both are handled automatically when errorText is set.
|
|
816
|
+
function DateTimePickerWithError() {
|
|
817
|
+
return (
|
|
818
|
+
<>
|
|
819
|
+
<DateTimePicker
|
|
820
|
+
id="event-start"
|
|
821
|
+
hasError
|
|
822
|
+
aria-invalid={true}
|
|
823
|
+
aria-describedby="event-start-error"
|
|
824
|
+
/>
|
|
825
|
+
<p id="event-start-error" role="alert">
|
|
826
|
+
Please select a valid date and time.
|
|
827
|
+
</p>
|
|
828
|
+
</>
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
export default DateTimePickerWithError;
|
|
832
|
+
`.trim(),
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
},
|
|
177
836
|
},
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
837
|
+
[
|
|
838
|
+
'The error state requires **both** `hasError` (visual red border) and `aria-invalid={true}`',
|
|
839
|
+
'(screen-reader signal). Use `aria-describedby` to link the input to the error message.',
|
|
840
|
+
'When using `<FormField>`, set `errorText` and the component handles all three automatically.',
|
|
841
|
+
].join(' '),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
export const ControlledWithDisplay: Story = withDescription(
|
|
845
|
+
{
|
|
846
|
+
render: () => <ControlledWithDisplayTemplate />,
|
|
847
|
+
parameters: {
|
|
848
|
+
controls: { disable: true },
|
|
849
|
+
docs: {
|
|
850
|
+
source: {
|
|
851
|
+
language: 'tsx',
|
|
852
|
+
code: `
|
|
853
|
+
import { useState } from 'react';
|
|
854
|
+
import { DateTimePicker } from '@arbor-education/design-system.components';
|
|
855
|
+
|
|
856
|
+
function ControlledDateTimePickerExample() {
|
|
857
|
+
const [value, setValue] = useState<Date | undefined>(undefined);
|
|
858
|
+
|
|
859
|
+
return (
|
|
860
|
+
<div>
|
|
861
|
+
<DateTimePicker
|
|
862
|
+
id="event-datetime"
|
|
863
|
+
onChange={setValue}
|
|
864
|
+
/>
|
|
865
|
+
<p>
|
|
866
|
+
Selected:{' '}
|
|
867
|
+
<code>
|
|
868
|
+
{value
|
|
869
|
+
? \`\${value.toLocaleDateString('en-GB')} \${value.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}\`
|
|
870
|
+
: '(none)'}
|
|
871
|
+
</code>
|
|
872
|
+
</p>
|
|
873
|
+
</div>
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
export default ControlledDateTimePickerExample;
|
|
877
|
+
`.trim(),
|
|
878
|
+
},
|
|
183
879
|
},
|
|
184
880
|
},
|
|
185
881
|
},
|
|
186
|
-
|
|
882
|
+
[
|
|
883
|
+
'`onChange` receives a `Date` object (or `undefined`) — never a string.',
|
|
884
|
+
'This controlled example stores the combined date+time in state and renders it below the field.',
|
|
885
|
+
'Use `toLocaleDateString` + `toLocaleTimeString` (or `date-fns format`) for display.',
|
|
886
|
+
].join(' '),
|
|
887
|
+
);
|
|
187
888
|
|
|
188
|
-
export const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
889
|
+
export const WithFormField: Story = withDescription(
|
|
890
|
+
{
|
|
891
|
+
render: () => <WithFormFieldTemplate />,
|
|
892
|
+
parameters: {
|
|
893
|
+
controls: { disable: true },
|
|
894
|
+
docs: {
|
|
895
|
+
source: {
|
|
896
|
+
language: 'tsx',
|
|
897
|
+
code: `
|
|
898
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
899
|
+
|
|
900
|
+
function EventStartField() {
|
|
901
|
+
return (
|
|
902
|
+
<FormField
|
|
903
|
+
label="Event start date and time"
|
|
904
|
+
id="event-start"
|
|
905
|
+
inputType="dateTimePicker"
|
|
906
|
+
fieldDescription="Select the date and time the event will begin."
|
|
907
|
+
inputProps={{
|
|
908
|
+
onChange: (date) => {
|
|
909
|
+
// date is Date | undefined
|
|
910
|
+
console.log('Event start:', date?.toLocaleString('en-GB'));
|
|
911
|
+
},
|
|
912
|
+
}}
|
|
913
|
+
/>
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
export default EventStartField;
|
|
917
|
+
`.trim(),
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
},
|
|
193
921
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
922
|
+
[
|
|
923
|
+
'The recommended form usage: `<FormField inputType="dateTimePicker">` provides the accessible',
|
|
924
|
+
'label, description, and error layout. DateTimePicker props go in `inputProps`.',
|
|
925
|
+
'The `label`, `fieldDescription`, and `errorText` props belong on `<FormField>` itself.',
|
|
926
|
+
].join(' '),
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
export const WithFormFieldAndError: Story = withDescription(
|
|
930
|
+
{
|
|
931
|
+
render: () => <WithFormFieldAndErrorTemplate />,
|
|
932
|
+
parameters: {
|
|
933
|
+
controls: { disable: true },
|
|
934
|
+
docs: {
|
|
935
|
+
source: {
|
|
936
|
+
language: 'tsx',
|
|
937
|
+
code: `
|
|
938
|
+
import { useState } from 'react';
|
|
939
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
940
|
+
|
|
941
|
+
function LessonSchedulerForm() {
|
|
942
|
+
const [submitted, setSubmitted] = useState(false);
|
|
943
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
944
|
+
const hasError = submitted && !selectedDate;
|
|
945
|
+
|
|
946
|
+
return (
|
|
947
|
+
<div>
|
|
948
|
+
<FormField
|
|
949
|
+
label="Lesson start date and time"
|
|
950
|
+
id="lesson-start"
|
|
951
|
+
inputType="dateTimePicker"
|
|
952
|
+
errorText={
|
|
953
|
+
hasError
|
|
954
|
+
? 'Please select a date and time for the lesson.'
|
|
955
|
+
: undefined
|
|
956
|
+
}
|
|
957
|
+
inputProps={{
|
|
958
|
+
onChange: setSelectedDate,
|
|
959
|
+
hasError,
|
|
960
|
+
'aria-invalid': hasError ? true : undefined,
|
|
961
|
+
}}
|
|
962
|
+
/>
|
|
963
|
+
<button type="button" onClick={() => setSubmitted(true)}>
|
|
964
|
+
Submit
|
|
965
|
+
</button>
|
|
966
|
+
</div>
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
export default LessonSchedulerForm;
|
|
970
|
+
`.trim(),
|
|
971
|
+
},
|
|
198
972
|
},
|
|
199
973
|
},
|
|
200
974
|
},
|
|
201
|
-
|
|
202
|
-
|
|
975
|
+
[
|
|
976
|
+
'A submit-gated form: clicking Submit without a selection triggers the full error pattern.',
|
|
977
|
+
'`errorText` on `<FormField>` renders the message in the correct accessible layout,',
|
|
978
|
+
'linked via `aria-describedby` automatically. Once a date+time is chosen, the error clears.',
|
|
979
|
+
].join(' '),
|
|
980
|
+
);
|