@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,105 +1,822 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import {
|
|
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';
|
|
3
13
|
import { DatePicker } from './DatePicker';
|
|
4
14
|
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Docs page content
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const DESCRIPTION_INTRO = [
|
|
20
|
+
'DatePicker combines a native `<input type="date">` with a calendar popover for date selection.',
|
|
21
|
+
'Built on [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover) and',
|
|
22
|
+
'[react-day-picker](https://daypicker.dev/).',
|
|
23
|
+
'',
|
|
24
|
+
'> **Portal rendering.** The calendar panel renders via a Radix portal — do **not** place inside',
|
|
25
|
+
'> a container with `overflow: hidden` or the calendar will be clipped.',
|
|
26
|
+
'',
|
|
27
|
+
'When empty, a decorative span overlays the hint text (`DD/MM/YYYY` or `Pick a date`) because',
|
|
28
|
+
'browsers do not reliably show `placeholder` on native date inputs. When a custom `placeholder` is',
|
|
29
|
+
'provided and the field is empty, the input becomes read-only and the calendar opens on click only',
|
|
30
|
+
'— Tab focus does not trigger it.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
|
|
33
|
+
const USAGE_GUIDANCE = [
|
|
34
|
+
'### When to use',
|
|
35
|
+
'',
|
|
36
|
+
'- **Single date selection** — enrollment dates, event dates, term start/end dates',
|
|
37
|
+
'- **Date of birth entry** — with a clear format hint so users know the expected format',
|
|
38
|
+
'- **Booking and scheduling** — parent evenings, appointment slots, report deadlines',
|
|
39
|
+
'',
|
|
40
|
+
'---',
|
|
41
|
+
'',
|
|
42
|
+
'### When NOT to use',
|
|
43
|
+
'',
|
|
44
|
+
'| Situation | Use instead |',
|
|
45
|
+
'|---|---|',
|
|
46
|
+
'| Date ranges (from/to) | Two separate `DatePicker` fields |',
|
|
47
|
+
'| Date + time combined | `DatePicker` paired with `TimeInput` |',
|
|
48
|
+
'| Year-only or month-only selection | `SelectDropdown` with year/month options |',
|
|
49
|
+
'| Relative dates ("next week", "in 30 days") | `SelectDropdown` with relative option labels |',
|
|
50
|
+
].join('\n');
|
|
51
|
+
|
|
52
|
+
const DEVELOPER_NOTES = [
|
|
53
|
+
'### Critical usage patterns',
|
|
54
|
+
'',
|
|
55
|
+
'**`onChange` receives a `Date` object — never a string.** Format for display with',
|
|
56
|
+
'`date?.toLocaleDateString(\'en-GB\')`. Do not call `date.toString()` or assume ISO format.',
|
|
57
|
+
'',
|
|
58
|
+
'**`value` and `defaultValue` are `Date` objects — not strings.** Month is zero-indexed:',
|
|
59
|
+
'`new Date(2024, 8, 1)` = 1 September 2024. Passing a string is a type error.',
|
|
60
|
+
'',
|
|
61
|
+
'```tsx',
|
|
62
|
+
'// ✅ Correct',
|
|
63
|
+
'defaultValue={new Date(2024, 8, 1)}',
|
|
64
|
+
'',
|
|
65
|
+
'// ❌ Wrong — type error',
|
|
66
|
+
'defaultValue="2024-09-01"',
|
|
67
|
+
'```',
|
|
68
|
+
'',
|
|
69
|
+
'**`hasError` is visual-only when used standalone.** It applies the red border but does NOT',
|
|
70
|
+
'set `aria-invalid`. Always pair both when outside `<FormField>`:',
|
|
71
|
+
'',
|
|
72
|
+
'```tsx',
|
|
73
|
+
'<DatePicker hasError aria-invalid={true} aria-describedby="error-id" />',
|
|
74
|
+
'```',
|
|
75
|
+
'',
|
|
76
|
+
'**Controlled vs uncontrolled.** Pass `value` + `onChange` for controlled mode.',
|
|
77
|
+
'Pass only `defaultValue` for uncontrolled. Do not pass both `value` and `defaultValue`.',
|
|
78
|
+
'',
|
|
79
|
+
'**Time preservation.** DatePicker preserves any time component from the previous value when the',
|
|
80
|
+
'date changes — this enables clean DateTimePicker composition.',
|
|
81
|
+
'',
|
|
82
|
+
'---',
|
|
83
|
+
'',
|
|
84
|
+
'### Accessibility',
|
|
85
|
+
'',
|
|
86
|
+
'- Always label via `<FormField>` or `aria-label` / `aria-labelledby`',
|
|
87
|
+
'- Pair `hasError` with `aria-invalid={true}` when used standalone — `<FormField>` handles both',
|
|
88
|
+
'- `aria-describedby` should point to the error element ID for screen-reader coverage',
|
|
89
|
+
'- The calendar icon button has built-in screen reader text ("Open date picker")',
|
|
90
|
+
'- The calendar closes and returns focus to the input on date selection or Escape',
|
|
91
|
+
'',
|
|
92
|
+
'---',
|
|
93
|
+
'',
|
|
94
|
+
'### TypeScript types',
|
|
95
|
+
'',
|
|
96
|
+
'```ts',
|
|
97
|
+
"import { DatePicker } from '@arbor-education/design-system.components';",
|
|
98
|
+
'',
|
|
99
|
+
'function MyDateField(props: DatePicker.Props) { ... }',
|
|
100
|
+
'```',
|
|
101
|
+
'',
|
|
102
|
+
'| Type | Description |',
|
|
103
|
+
'|---|---|',
|
|
104
|
+
'| `DatePicker.Props` | Full props interface |',
|
|
105
|
+
].join('\n');
|
|
106
|
+
|
|
107
|
+
const RELATED_COMPONENTS = [
|
|
108
|
+
'## Related components',
|
|
109
|
+
'',
|
|
110
|
+
'[FormField](?path=/docs/components-formfield--docs) · [TimeInput](?path=/docs/components-formfield-inputs-timeinput--docs)',
|
|
111
|
+
].join('\n');
|
|
112
|
+
|
|
113
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Docs page component
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
function DatePickerDocsPage() {
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
<Title />
|
|
123
|
+
<Subtitle />
|
|
124
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
125
|
+
<DocHeading>Interactive example</DocHeading>
|
|
126
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
127
|
+
<DocPrimary />
|
|
128
|
+
<Controls />
|
|
129
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
130
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
131
|
+
<DocHeading>Developer notes</DocHeading>
|
|
132
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
133
|
+
<DocHeading>Examples</DocHeading>
|
|
134
|
+
<Stories title="" />
|
|
135
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Meta
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
5
144
|
const meta = {
|
|
6
145
|
title: 'Components/DatePicker',
|
|
7
146
|
component: DatePicker,
|
|
8
|
-
|
|
9
|
-
Story => (
|
|
10
|
-
<div style={{ maxWidth: '220px', width: '100%' }}>
|
|
11
|
-
<Story />
|
|
12
|
-
</div>
|
|
13
|
-
),
|
|
14
|
-
],
|
|
147
|
+
tags: ['autodocs'],
|
|
15
148
|
parameters: {
|
|
16
|
-
layout: '
|
|
149
|
+
layout: 'padded',
|
|
17
150
|
docs: {
|
|
18
|
-
|
|
19
|
-
component:
|
|
20
|
-
'`DatePicker` uses a native `type="date"` input (`yyyy-MM-dd`) with a synced calendar popover. When empty, hint copy uses a decorative span (browsers do not reliably show `placeholder` on native date fields). `displayFormat` controls that hint (`DD/MM/YYYY` vs `Pick a date`). The custom month/year header uses the design-system select control.',
|
|
21
|
-
},
|
|
151
|
+
page: DatePickerDocsPage,
|
|
22
152
|
},
|
|
23
153
|
},
|
|
24
|
-
tags: ['autodocs'],
|
|
25
|
-
args: {
|
|
26
|
-
onChange: fn(),
|
|
27
|
-
},
|
|
28
154
|
argTypes: {
|
|
29
|
-
displayFormat: {
|
|
30
|
-
control: 'inline-radio',
|
|
155
|
+
'displayFormat': {
|
|
156
|
+
control: { type: 'inline-radio' },
|
|
31
157
|
options: ['default', 'friendly'],
|
|
32
|
-
description:
|
|
158
|
+
description: [
|
|
159
|
+
'Controls the empty-state hint overlay.',
|
|
160
|
+
'`"default"` shows `DD/MM/YYYY`; `"friendly"` shows `Pick a date`.',
|
|
161
|
+
'Overridden by `placeholder` when that prop is provided.',
|
|
162
|
+
].join(' '),
|
|
163
|
+
table: {
|
|
164
|
+
type: { summary: "'default' | 'friendly'" },
|
|
165
|
+
defaultValue: { summary: "'default'" },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
'placeholder': {
|
|
169
|
+
control: 'text',
|
|
170
|
+
description: [
|
|
171
|
+
'Custom text for the empty-state overlay.',
|
|
172
|
+
'Overrides the `displayFormat` hint.',
|
|
173
|
+
'When set and the field is empty, the input becomes read-only — the calendar opens on click only, not Tab focus.',
|
|
174
|
+
].join(' '),
|
|
175
|
+
table: {
|
|
176
|
+
type: { summary: 'string' },
|
|
177
|
+
defaultValue: { summary: 'undefined' },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
'onChange': {
|
|
181
|
+
control: false,
|
|
182
|
+
action: 'onChange',
|
|
183
|
+
description: [
|
|
184
|
+
'Callback fired when the selected date changes.',
|
|
185
|
+
'Receives a `Date` object, or `undefined` when the field is cleared.',
|
|
186
|
+
'Never receives a string — format with `date?.toLocaleDateString(\'en-GB\')`.',
|
|
187
|
+
].join(' '),
|
|
188
|
+
table: {
|
|
189
|
+
type: { summary: '(newDate?: Date) => void' },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
'hasError': {
|
|
193
|
+
control: 'boolean',
|
|
194
|
+
description: [
|
|
195
|
+
'Applies error-state visual styling (red border).',
|
|
196
|
+
'Does **not** set `aria-invalid` automatically when used standalone.',
|
|
197
|
+
'Always pair with `aria-invalid={true}` for screen-reader coverage.',
|
|
198
|
+
'`<FormField>` handles both automatically when `errorText` is set.',
|
|
199
|
+
].join(' '),
|
|
200
|
+
table: {
|
|
201
|
+
type: { summary: 'boolean' },
|
|
202
|
+
defaultValue: { summary: 'false' },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
'value': {
|
|
206
|
+
control: false,
|
|
207
|
+
description: [
|
|
208
|
+
'Controlled selected date.',
|
|
209
|
+
'Must be a `Date` object — not a string.',
|
|
210
|
+
'Pair with `onChange` to keep state in sync.',
|
|
211
|
+
].join(' '),
|
|
212
|
+
table: {
|
|
213
|
+
type: { summary: 'Date' },
|
|
214
|
+
defaultValue: { summary: 'undefined' },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
'defaultValue': {
|
|
218
|
+
control: false,
|
|
219
|
+
description: [
|
|
220
|
+
'Uncontrolled initial date.',
|
|
221
|
+
'Must be a `Date` object (month is zero-indexed: `new Date(2024, 8, 1)` = 1 Sep 2024).',
|
|
222
|
+
'Use when you only need the value on form submit.',
|
|
223
|
+
].join(' '),
|
|
224
|
+
table: {
|
|
225
|
+
type: { summary: 'Date' },
|
|
226
|
+
defaultValue: { summary: 'undefined' },
|
|
227
|
+
},
|
|
33
228
|
},
|
|
34
|
-
|
|
229
|
+
'id': {
|
|
35
230
|
control: 'text',
|
|
36
|
-
description: '
|
|
231
|
+
description: 'HTML `id` for the input element. Required when inside `<FormField>` so the label `htmlFor` can be linked.',
|
|
232
|
+
table: {
|
|
233
|
+
type: { summary: 'string' },
|
|
234
|
+
},
|
|
37
235
|
},
|
|
38
|
-
className: {
|
|
236
|
+
'className': {
|
|
39
237
|
control: 'text',
|
|
40
|
-
description: 'Additional CSS class names',
|
|
238
|
+
description: 'Additional CSS class names on the root wrapper element.',
|
|
239
|
+
table: {
|
|
240
|
+
type: { summary: 'string' },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
'autoFocus': {
|
|
244
|
+
control: 'boolean',
|
|
245
|
+
description: 'Autofocuses the date input on mount. Use sparingly — unexpected focus shifts can disorient screen-reader users.',
|
|
246
|
+
table: {
|
|
247
|
+
type: { summary: 'boolean' },
|
|
248
|
+
defaultValue: { summary: 'false' },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
'aria-invalid': {
|
|
252
|
+
control: 'boolean',
|
|
253
|
+
description: [
|
|
254
|
+
'Marks the input as invalid for screen readers.',
|
|
255
|
+
'Must be set manually when used standalone — `<FormField>` sets this automatically when `errorText` is provided.',
|
|
256
|
+
].join(' '),
|
|
257
|
+
table: {
|
|
258
|
+
type: { summary: 'boolean | "true" | "false"' },
|
|
259
|
+
},
|
|
41
260
|
},
|
|
42
|
-
|
|
43
|
-
|
|
261
|
+
'aria-describedby': {
|
|
262
|
+
control: 'text',
|
|
263
|
+
description: 'ID of the element describing the field (e.g. an error message). Links the input to the error for screen readers.',
|
|
264
|
+
table: {
|
|
265
|
+
type: { summary: 'string' },
|
|
266
|
+
},
|
|
44
267
|
},
|
|
45
268
|
},
|
|
46
269
|
} satisfies Meta<typeof DatePicker>;
|
|
47
270
|
|
|
48
271
|
export default meta;
|
|
49
|
-
type Story = StoryObj<typeof
|
|
272
|
+
type Story = StoryObj<typeof DatePicker>;
|
|
50
273
|
|
|
51
|
-
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Helper: attach a per-story description
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
const withDescription = (story: Story, description: string): Story => ({
|
|
279
|
+
...story,
|
|
52
280
|
parameters: {
|
|
281
|
+
...story.parameters,
|
|
53
282
|
docs: {
|
|
283
|
+
...story.parameters?.docs,
|
|
54
284
|
description: {
|
|
55
|
-
story:
|
|
285
|
+
story: description,
|
|
56
286
|
},
|
|
57
287
|
},
|
|
58
288
|
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Named template components for stateful stories
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
const ControlledWithDisplayTemplate = () => {
|
|
296
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
297
|
+
return (
|
|
298
|
+
<div style={{ maxWidth: '280px' }}>
|
|
299
|
+
<DatePicker
|
|
300
|
+
id="controlled-display"
|
|
301
|
+
onChange={setSelectedDate}
|
|
302
|
+
/>
|
|
303
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
|
|
304
|
+
Selected:
|
|
305
|
+
{' '}
|
|
306
|
+
<code>
|
|
307
|
+
{selectedDate ? selectedDate.toLocaleDateString('en-GB') : '(none)'}
|
|
308
|
+
</code>
|
|
309
|
+
</p>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
59
312
|
};
|
|
60
313
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
314
|
+
const ControlledWithValidationTemplate = () => {
|
|
315
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
316
|
+
const [error, setError] = useState<string | undefined>(undefined);
|
|
317
|
+
|
|
318
|
+
const today = new Date();
|
|
319
|
+
today.setHours(0, 0, 0, 0);
|
|
320
|
+
|
|
321
|
+
const handleChange = (date: Date | undefined) => {
|
|
322
|
+
setSelectedDate(date);
|
|
323
|
+
if (!date) {
|
|
324
|
+
setError(undefined);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const picked = new Date(date);
|
|
328
|
+
picked.setHours(0, 0, 0, 0);
|
|
329
|
+
if (picked <= today) {
|
|
330
|
+
setError('The event date must be in the future. Please pick a later date.');
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
setError(undefined);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<div style={{ maxWidth: '280px' }}>
|
|
339
|
+
<DatePicker
|
|
340
|
+
id="validated-date"
|
|
341
|
+
onChange={handleChange}
|
|
342
|
+
hasError={!!error}
|
|
343
|
+
aria-invalid={error ? true : undefined}
|
|
344
|
+
aria-describedby={error ? 'validated-date-error' : undefined}
|
|
345
|
+
/>
|
|
346
|
+
{error && (
|
|
347
|
+
<p
|
|
348
|
+
id="validated-date-error"
|
|
349
|
+
role="alert"
|
|
350
|
+
style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-semantic-destructive-600)', fontSize: '0.875rem' }}
|
|
351
|
+
>
|
|
352
|
+
{error}
|
|
353
|
+
</p>
|
|
354
|
+
)}
|
|
355
|
+
{!error && selectedDate && (
|
|
356
|
+
<p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-semantic-success-600)', fontSize: '0.875rem' }}>
|
|
357
|
+
Event date set:
|
|
358
|
+
{' '}
|
|
359
|
+
{selectedDate.toLocaleDateString('en-GB')}
|
|
360
|
+
</p>
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const WithFormFieldTemplate = () => (
|
|
367
|
+
<div style={{ maxWidth: '320px' }}>
|
|
368
|
+
<FormField
|
|
369
|
+
label="Pupil enrollment date"
|
|
370
|
+
id="ff-enrollment-date"
|
|
371
|
+
inputType="datePicker"
|
|
372
|
+
fieldDescription="Select the date the pupil will join the school."
|
|
373
|
+
inputProps={{}}
|
|
374
|
+
/>
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const WithFormFieldAndErrorTemplate = () => {
|
|
379
|
+
const [submitted, setSubmitted] = useState(false);
|
|
380
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
381
|
+
const hasError = submitted && !selectedDate;
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
|
|
385
|
+
<FormField
|
|
386
|
+
label="Parent evening appointment"
|
|
387
|
+
id="ff-parent-evening"
|
|
388
|
+
inputType="datePicker"
|
|
389
|
+
errorText={hasError ? 'Please select an appointment date before submitting.' : undefined}
|
|
390
|
+
inputProps={{
|
|
391
|
+
'onChange': setSelectedDate,
|
|
392
|
+
hasError,
|
|
393
|
+
'aria-invalid': hasError ? true : undefined,
|
|
394
|
+
}}
|
|
395
|
+
/>
|
|
396
|
+
<div>
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
onClick={() => setSubmitted(true)}
|
|
400
|
+
style={{ padding: 'var(--spacing-small) var(--spacing-medium)' }}
|
|
401
|
+
>
|
|
402
|
+
Submit
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
405
|
+
{submitted && selectedDate && (
|
|
406
|
+
<p style={{ margin: 0, color: 'var(--color-semantic-success-600)', fontSize: '0.875rem' }}>
|
|
407
|
+
Appointment booked for
|
|
408
|
+
{' '}
|
|
409
|
+
{selectedDate.toLocaleDateString('en-GB')}
|
|
410
|
+
.
|
|
411
|
+
</p>
|
|
412
|
+
)}
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Stories
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
export const Default: Story = withDescription(
|
|
422
|
+
{
|
|
423
|
+
args: {
|
|
424
|
+
id: 'default-date',
|
|
425
|
+
},
|
|
426
|
+
render: args => (
|
|
427
|
+
<div style={{ maxWidth: '280px' }}>
|
|
428
|
+
<DatePicker {...args} />
|
|
429
|
+
</div>
|
|
430
|
+
),
|
|
65
431
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
432
|
+
[
|
|
433
|
+
'The interactive canvas — every prop is wired to the Controls panel.',
|
|
434
|
+
'Empty field shows the **DD/MM/YYYY** hint overlay (`displayFormat="default"`).',
|
|
435
|
+
'Click the calendar icon or type directly into the field to select a date.',
|
|
436
|
+
].join(' '),
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
export const FriendlyFormat: Story = withDescription(
|
|
440
|
+
{
|
|
441
|
+
args: {
|
|
442
|
+
displayFormat: 'friendly',
|
|
443
|
+
id: 'friendly-date',
|
|
444
|
+
},
|
|
445
|
+
render: args => (
|
|
446
|
+
<div style={{ maxWidth: '280px' }}>
|
|
447
|
+
<DatePicker {...args} />
|
|
448
|
+
</div>
|
|
449
|
+
),
|
|
450
|
+
parameters: {
|
|
451
|
+
controls: { disable: true },
|
|
452
|
+
docs: {
|
|
453
|
+
source: {
|
|
454
|
+
language: 'tsx',
|
|
455
|
+
code: `
|
|
456
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
457
|
+
|
|
458
|
+
function FriendlyFormatExample() {
|
|
459
|
+
return (
|
|
460
|
+
<DatePicker
|
|
461
|
+
id="event-date"
|
|
462
|
+
displayFormat="friendly"
|
|
463
|
+
onChange={(date) => {
|
|
464
|
+
// date is Date | undefined — not a string
|
|
465
|
+
console.log('Event date:', date?.toLocaleDateString('en-GB'));
|
|
466
|
+
}}
|
|
467
|
+
/>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
export default FriendlyFormatExample;
|
|
471
|
+
`.trim(),
|
|
472
|
+
},
|
|
70
473
|
},
|
|
71
474
|
},
|
|
72
475
|
},
|
|
73
|
-
|
|
476
|
+
'`displayFormat="friendly"` shows **Pick a date** instead of the `DD/MM/YYYY` format hint. Use for contexts where a conversational label reads better than a technical format string.',
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
export const WithDefaultValue: Story = withDescription(
|
|
480
|
+
{
|
|
481
|
+
render: () => (
|
|
482
|
+
<div style={{ maxWidth: '280px' }}>
|
|
483
|
+
{/* September 1 2024 — month is zero-indexed (Jan = 0, Sep = 8) */}
|
|
484
|
+
<DatePicker
|
|
485
|
+
id="default-value-date"
|
|
486
|
+
defaultValue={new Date(2024, 8, 1)}
|
|
487
|
+
/>
|
|
488
|
+
</div>
|
|
489
|
+
),
|
|
490
|
+
parameters: {
|
|
491
|
+
controls: { disable: true },
|
|
492
|
+
docs: {
|
|
493
|
+
source: {
|
|
494
|
+
language: 'tsx',
|
|
495
|
+
code: `
|
|
496
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
74
497
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
498
|
+
function EditEnrollmentDate() {
|
|
499
|
+
// Month is zero-indexed: 8 = September
|
|
500
|
+
const currentEnrollmentDate = new Date(2024, 8, 1);
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<DatePicker
|
|
504
|
+
id="enrollment-date"
|
|
505
|
+
defaultValue={currentEnrollmentDate}
|
|
506
|
+
onChange={(date) => {
|
|
507
|
+
// date is Date | undefined — not a string
|
|
508
|
+
console.log('Updated to:', date?.toLocaleDateString('en-GB'));
|
|
509
|
+
}}
|
|
510
|
+
/>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
export default EditEnrollmentDate;
|
|
514
|
+
`.trim(),
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
81
518
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
519
|
+
[
|
|
520
|
+
'Use `defaultValue` to pre-populate an uncontrolled DatePicker — for example when an admin opens a',
|
|
521
|
+
'pupil\'s enrollment record to edit it. The prop must be a `Date` object (month is zero-indexed:',
|
|
522
|
+
'`new Date(2024, 8, 1)` = 1 September 2024). Passing a string is a type error.',
|
|
523
|
+
].join(' '),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
export const WithPlaceholder: Story = withDescription(
|
|
527
|
+
{
|
|
528
|
+
render: () => (
|
|
529
|
+
<div style={{ maxWidth: '280px' }}>
|
|
530
|
+
<DatePicker
|
|
531
|
+
id="placeholder-date"
|
|
532
|
+
placeholder="Choose term start date"
|
|
533
|
+
/>
|
|
534
|
+
</div>
|
|
535
|
+
),
|
|
536
|
+
parameters: {
|
|
537
|
+
controls: { disable: true },
|
|
538
|
+
docs: {
|
|
539
|
+
source: {
|
|
540
|
+
language: 'tsx',
|
|
541
|
+
code: `
|
|
542
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
543
|
+
|
|
544
|
+
function TermStartDatePicker() {
|
|
545
|
+
return (
|
|
546
|
+
<DatePicker
|
|
547
|
+
id="term-start-date"
|
|
548
|
+
placeholder="Choose term start date"
|
|
549
|
+
onChange={(date) => {
|
|
550
|
+
console.log('Term start:', date?.toLocaleDateString('en-GB'));
|
|
551
|
+
}}
|
|
552
|
+
/>
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
export default TermStartDatePicker;
|
|
556
|
+
`.trim(),
|
|
557
|
+
},
|
|
86
558
|
},
|
|
87
559
|
},
|
|
88
560
|
},
|
|
89
|
-
|
|
561
|
+
[
|
|
562
|
+
'When `placeholder` is set and the field is empty, the input becomes **read-only** and the',
|
|
563
|
+
'calendar opens on **click only** — Tab focus does not trigger it. This prevents the popup',
|
|
564
|
+
'from unexpectedly opening as keyboard users navigate through a form.',
|
|
565
|
+
].join(' '),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
export const ErrorState: Story = withDescription(
|
|
569
|
+
{
|
|
570
|
+
render: () => (
|
|
571
|
+
<div style={{ maxWidth: '280px' }}>
|
|
572
|
+
<DatePicker
|
|
573
|
+
id="error-state-date"
|
|
574
|
+
hasError
|
|
575
|
+
aria-invalid={true}
|
|
576
|
+
aria-describedby="error-state-msg"
|
|
577
|
+
/>
|
|
578
|
+
<p
|
|
579
|
+
id="error-state-msg"
|
|
580
|
+
role="alert"
|
|
581
|
+
style={{ margin: 'var(--spacing-xsmall) 0 0', color: 'var(--color-semantic-destructive-600)', fontSize: '0.875rem' }}
|
|
582
|
+
>
|
|
583
|
+
Please select a valid enrollment date.
|
|
584
|
+
</p>
|
|
585
|
+
</div>
|
|
586
|
+
),
|
|
587
|
+
parameters: {
|
|
588
|
+
controls: { disable: true },
|
|
589
|
+
docs: {
|
|
590
|
+
source: {
|
|
591
|
+
language: 'tsx',
|
|
592
|
+
code: `
|
|
593
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
90
594
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
595
|
+
// Standalone — set both hasError (visual) and aria-invalid (screen reader) yourself.
|
|
596
|
+
// Inside <FormField> both are handled automatically when errorText is set.
|
|
597
|
+
function EnrollmentDateWithError() {
|
|
598
|
+
return (
|
|
599
|
+
<>
|
|
600
|
+
<DatePicker
|
|
601
|
+
id="enrollment-date"
|
|
602
|
+
hasError
|
|
603
|
+
aria-invalid={true}
|
|
604
|
+
aria-describedby="enrollment-date-error"
|
|
605
|
+
/>
|
|
606
|
+
<p id="enrollment-date-error" role="alert">
|
|
607
|
+
Please select a valid enrollment date.
|
|
608
|
+
</p>
|
|
609
|
+
</>
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
export default EnrollmentDateWithError;
|
|
613
|
+
`.trim(),
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
},
|
|
97
617
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
618
|
+
[
|
|
619
|
+
'The error state requires **both** `hasError` (visual red border) and `aria-invalid={true}`',
|
|
620
|
+
'(screen-reader signal). Use `aria-describedby` to link the input to the error message element.',
|
|
621
|
+
'When using `<FormField>`, set `errorText` and the component handles all three automatically.',
|
|
622
|
+
].join(' '),
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
export const ControlledWithDisplay: Story = withDescription(
|
|
626
|
+
{
|
|
627
|
+
render: () => <ControlledWithDisplayTemplate />,
|
|
628
|
+
parameters: {
|
|
629
|
+
controls: { disable: true },
|
|
630
|
+
docs: {
|
|
631
|
+
source: {
|
|
632
|
+
language: 'tsx',
|
|
633
|
+
code: `
|
|
634
|
+
import { useState } from 'react';
|
|
635
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
636
|
+
|
|
637
|
+
function ControlledDatePickerExample() {
|
|
638
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<div>
|
|
642
|
+
<DatePicker
|
|
643
|
+
id="event-date"
|
|
644
|
+
onChange={setSelectedDate}
|
|
645
|
+
/>
|
|
646
|
+
<p>
|
|
647
|
+
Selected:{' '}
|
|
648
|
+
<code>
|
|
649
|
+
{selectedDate
|
|
650
|
+
? selectedDate.toLocaleDateString('en-GB')
|
|
651
|
+
: '(none)'}
|
|
652
|
+
</code>
|
|
653
|
+
</p>
|
|
654
|
+
</div>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
export default ControlledDatePickerExample;
|
|
658
|
+
`.trim(),
|
|
659
|
+
},
|
|
102
660
|
},
|
|
103
661
|
},
|
|
104
662
|
},
|
|
105
|
-
|
|
663
|
+
[
|
|
664
|
+
'`onChange` receives a `Date` object (or `undefined`) — never a string. Use',
|
|
665
|
+
'`date.toLocaleDateString(\'en-GB\')` for human-readable display.',
|
|
666
|
+
'This controlled example stores the value in state and renders it below the field.',
|
|
667
|
+
].join(' '),
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
export const ControlledWithValidation: Story = withDescription(
|
|
671
|
+
{
|
|
672
|
+
render: () => <ControlledWithValidationTemplate />,
|
|
673
|
+
parameters: {
|
|
674
|
+
controls: { disable: true },
|
|
675
|
+
docs: {
|
|
676
|
+
source: {
|
|
677
|
+
language: 'tsx',
|
|
678
|
+
code: `
|
|
679
|
+
import { useState } from 'react';
|
|
680
|
+
import { DatePicker } from '@arbor-education/design-system.components';
|
|
681
|
+
|
|
682
|
+
function FutureDatePicker() {
|
|
683
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
684
|
+
const [error, setError] = useState<string | undefined>(undefined);
|
|
685
|
+
|
|
686
|
+
const today = new Date();
|
|
687
|
+
today.setHours(0, 0, 0, 0);
|
|
688
|
+
|
|
689
|
+
function handleChange(date: Date | undefined) {
|
|
690
|
+
setSelectedDate(date);
|
|
691
|
+
if (!date) { setError(undefined); return; }
|
|
692
|
+
const picked = new Date(date);
|
|
693
|
+
picked.setHours(0, 0, 0, 0);
|
|
694
|
+
if (picked <= today) {
|
|
695
|
+
setError('The event date must be in the future. Please pick a later date.');
|
|
696
|
+
} else {
|
|
697
|
+
setError(undefined);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
<div>
|
|
703
|
+
<DatePicker
|
|
704
|
+
id="event-date"
|
|
705
|
+
onChange={handleChange}
|
|
706
|
+
hasError={!!error}
|
|
707
|
+
aria-invalid={error ? true : undefined}
|
|
708
|
+
aria-describedby={error ? 'event-date-error' : undefined}
|
|
709
|
+
/>
|
|
710
|
+
{error && (
|
|
711
|
+
<p id="event-date-error" role="alert">
|
|
712
|
+
{error}
|
|
713
|
+
</p>
|
|
714
|
+
)}
|
|
715
|
+
</div>
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
export default FutureDatePicker;
|
|
719
|
+
`.trim(),
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
[
|
|
725
|
+
'Real-time validation: only future dates are accepted. Selecting today or a past date triggers',
|
|
726
|
+
'error styling (`hasError` + `aria-invalid` + `aria-describedby`). Clearing the date removes the error.',
|
|
727
|
+
'The accessible `role="alert"` paragraph announces the error to screen readers immediately.',
|
|
728
|
+
].join(' '),
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
export const WithFormField: Story = withDescription(
|
|
732
|
+
{
|
|
733
|
+
render: () => <WithFormFieldTemplate />,
|
|
734
|
+
parameters: {
|
|
735
|
+
controls: { disable: true },
|
|
736
|
+
docs: {
|
|
737
|
+
source: {
|
|
738
|
+
language: 'tsx',
|
|
739
|
+
code: `
|
|
740
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
741
|
+
|
|
742
|
+
function EnrollmentDateField() {
|
|
743
|
+
return (
|
|
744
|
+
<FormField
|
|
745
|
+
label="Pupil enrollment date"
|
|
746
|
+
id="enrollment-date"
|
|
747
|
+
inputType="datePicker"
|
|
748
|
+
fieldDescription="Select the date the pupil will join the school."
|
|
749
|
+
inputProps={{
|
|
750
|
+
onChange: (date) => {
|
|
751
|
+
// date is Date | undefined
|
|
752
|
+
console.log('Enrollment date:', date?.toLocaleDateString('en-GB'));
|
|
753
|
+
},
|
|
754
|
+
}}
|
|
755
|
+
/>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
export default EnrollmentDateField;
|
|
759
|
+
`.trim(),
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
[
|
|
765
|
+
'The recommended form usage: `<FormField inputType="datePicker">` provides the accessible',
|
|
766
|
+
'label, description, and error layout. Pass DatePicker-specific props through `inputProps`.',
|
|
767
|
+
'The `label`, `fieldDescription`, and `errorText` props belong on `<FormField>` itself.',
|
|
768
|
+
].join(' '),
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
export const WithFormFieldAndError: Story = withDescription(
|
|
772
|
+
{
|
|
773
|
+
render: () => <WithFormFieldAndErrorTemplate />,
|
|
774
|
+
parameters: {
|
|
775
|
+
controls: { disable: true },
|
|
776
|
+
docs: {
|
|
777
|
+
source: {
|
|
778
|
+
language: 'tsx',
|
|
779
|
+
code: `
|
|
780
|
+
import { useState } from 'react';
|
|
781
|
+
import { FormField } from '@arbor-education/design-system.components';
|
|
782
|
+
|
|
783
|
+
function ParentEveningForm() {
|
|
784
|
+
const [submitted, setSubmitted] = useState(false);
|
|
785
|
+
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
786
|
+
const hasError = submitted && !selectedDate;
|
|
787
|
+
|
|
788
|
+
return (
|
|
789
|
+
<div>
|
|
790
|
+
<FormField
|
|
791
|
+
label="Parent evening appointment"
|
|
792
|
+
id="parent-evening-date"
|
|
793
|
+
inputType="datePicker"
|
|
794
|
+
errorText={
|
|
795
|
+
hasError
|
|
796
|
+
? 'Please select an appointment date before submitting.'
|
|
797
|
+
: undefined
|
|
798
|
+
}
|
|
799
|
+
inputProps={{
|
|
800
|
+
onChange: setSelectedDate,
|
|
801
|
+
hasError,
|
|
802
|
+
'aria-invalid': hasError ? true : undefined,
|
|
803
|
+
}}
|
|
804
|
+
/>
|
|
805
|
+
<button type="button" onClick={() => setSubmitted(true)}>
|
|
806
|
+
Submit
|
|
807
|
+
</button>
|
|
808
|
+
</div>
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
export default ParentEveningForm;
|
|
812
|
+
`.trim(),
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
[
|
|
818
|
+
'A submit-gated form: clicking Submit without a date triggers the full error pattern.',
|
|
819
|
+
'`errorText` on `<FormField>` renders the message in the correct accessible layout below the input,',
|
|
820
|
+
'linked via `aria-describedby` automatically. Once a date is chosen, the error clears.',
|
|
821
|
+
].join(' '),
|
|
822
|
+
);
|