@arbor-education/design-system.components 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/components/avatar/Avatar.js +2 -2
  3. package/dist/components/avatar/Avatar.js.map +1 -1
  4. package/dist/components/badge/Badge.stories.d.ts +0 -1
  5. package/dist/components/badge/Badge.stories.d.ts.map +1 -1
  6. package/dist/components/badge/Badge.stories.js +0 -69
  7. package/dist/components/badge/Badge.stories.js.map +1 -1
  8. package/dist/components/datePicker/DatePicker.stories.d.ts +5 -0
  9. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  10. package/dist/components/datePicker/DatePicker.stories.js +211 -2
  11. package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
  12. package/dist/components/datePicker/DatePickerPopover.d.ts +11 -0
  13. package/dist/components/datePicker/DatePickerPopover.d.ts.map +1 -0
  14. package/dist/components/datePicker/DatePickerPopover.js +33 -0
  15. package/dist/components/datePicker/DatePickerPopover.js.map +1 -0
  16. package/dist/components/datePicker/DatePickerPopover.test.d.ts +2 -0
  17. package/dist/components/datePicker/DatePickerPopover.test.d.ts.map +1 -0
  18. package/dist/components/datePicker/DatePickerPopover.test.js +101 -0
  19. package/dist/components/datePicker/DatePickerPopover.test.js.map +1 -0
  20. package/dist/components/dot/Dot.d.ts.map +1 -1
  21. package/dist/components/dot/Dot.js +1 -1
  22. package/dist/components/dot/Dot.js.map +1 -1
  23. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts +2 -2
  24. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts.map +1 -1
  25. package/dist/components/formField/inputs/checkbox/CheckboxInput.js +1 -1
  26. package/dist/components/formField/inputs/checkbox/CheckboxInput.js.map +1 -1
  27. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +2 -0
  28. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  29. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +100 -0
  30. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  31. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.js +17 -1
  32. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.js.map +1 -1
  33. package/dist/components/searchBar/SearchBar.d.ts.map +1 -1
  34. package/dist/components/searchBar/SearchBar.js +1 -1
  35. package/dist/components/searchBar/SearchBar.js.map +1 -1
  36. package/dist/components/searchBar/SearchBar.test.js +3 -3
  37. package/dist/components/searchBar/SearchBar.test.js.map +1 -1
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/utils/popupParent/PopupParentContext.stories.d.ts.map +1 -1
  43. package/dist/utils/popupParent/PopupParentContext.stories.js +1 -1
  44. package/dist/utils/popupParent/PopupParentContext.stories.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/components/avatar/Avatar.tsx +2 -2
  47. package/src/components/badge/Badge.stories.tsx +0 -103
  48. package/src/components/datePicker/DatePicker.stories.tsx +293 -0
  49. package/src/components/datePicker/DatePickerPopover.test.tsx +144 -0
  50. package/src/components/datePicker/DatePickerPopover.tsx +72 -0
  51. package/src/components/dot/Dot.tsx +1 -0
  52. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +178 -1
  53. package/src/components/formField/inputs/checkbox/CheckboxInput.test.tsx +58 -0
  54. package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +3 -3
  55. package/src/components/searchBar/SearchBar.test.tsx +3 -3
  56. package/src/components/searchBar/SearchBar.tsx +1 -0
  57. package/src/index.ts +1 -0
  58. package/src/utils/popupParent/PopupParentContext.stories.tsx +1 -0
@@ -170,7 +170,7 @@ const WithCustomContainerTemplate = () => {
170
170
  borderRadius: 'var(--border-radius-small)',
171
171
  padding: 'var(--spacing-xsmall)',
172
172
  minHeight: '2rem',
173
- }, "aria-label": "Custom popup container" }), _jsx(PopupParentContext.Provider, { value: popupContainerRef, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "dropdown", children: "Actions" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { children: "View pupil profile" }), _jsx(Dropdown.Item, { children: "Edit enrolment" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.Item, { children: "Remove from group" })] })] }) })] }));
173
+ }, "aria-label": "Custom popup container", "aria-hidden": true }), _jsx(PopupParentContext.Provider, { value: popupContainerRef, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "dropdown", children: "Actions" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { children: "View pupil profile" }), _jsx(Dropdown.Item, { children: "Edit enrolment" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.Item, { children: "Remove from group" })] })] }) })] }));
174
174
  };
175
175
  // ---------------------------------------------------------------------------
176
176
  // Stories
@@ -1 +1 @@
1
- {"version":3,"file":"PopupParentContext.stories.js","sourceRoot":"","sources":["../../../src/utils/popupParent/PopupParentContext.stories.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PopupParentContext.stories.js","sourceRoot":"","sources":["../../../src/utils/popupParent/PopupParentContext.stories.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbor-education/design-system.components",
3
- "version": "4.4.0",
3
+ "version": "4.6.0",
4
4
  "description": "The component library for the design system (the baby)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -34,14 +34,14 @@ export const Avatar = (props: AvatarProps) => {
34
34
 
35
35
  if (initials) {
36
36
  return (
37
- <span className="ds-avatar__initials" aria-label={alt}>
37
+ <span className="ds-avatar__initials" aria-label={alt} role="img">
38
38
  {initials}
39
39
  </span>
40
40
  );
41
41
  }
42
42
 
43
43
  return (
44
- <span className="ds-avatar__placeholder" aria-label={alt || 'User avatar'}>
44
+ <span className="ds-avatar__placeholder" aria-label={alt || 'User avatar'} role="img">
45
45
  <svg
46
46
  viewBox="0 0 24 24"
47
47
  fill="currentColor"
@@ -433,42 +433,6 @@ const AllVariantsTemplate = () => (
433
433
  </div>
434
434
  );
435
435
 
436
- const A11yWithLabelTemplate = () => (
437
- <div
438
- style={{
439
- padding: 'var(--spacing-xlarge)',
440
- display: 'flex',
441
- flexDirection: 'column',
442
- gap: 'var(--spacing-xlarge)',
443
- }}
444
- >
445
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
446
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
447
- Without a11yLabel — screen reader announces "5" (no context)
448
- </p>
449
- <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
450
- <Icon name="mail" size={16} screenReaderText="Notifications" />
451
- <Badge>5</Badge>
452
- </div>
453
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
454
- Screen reader: "mail Notifications, 5"
455
- </p>
456
- </div>
457
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
458
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-success-600)' }}>
459
- With a11yLabel — screen reader announces the full meaningful label
460
- </p>
461
- <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
462
- <Icon name="mail" size={16} />
463
- <Badge a11yLabel="5 unread messages">5</Badge>
464
- </div>
465
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
466
- Screen reader: "5 unread messages"
467
- </p>
468
- </div>
469
- </div>
470
- );
471
-
472
436
  // ---------------------------------------------------------------------------
473
437
  // Stories
474
438
  // ---------------------------------------------------------------------------
@@ -832,70 +796,3 @@ export default AllVariantsTemplate;
832
796
  },
833
797
  'Full 3 × 8 grid: all three sizes crossed with all eight colour states (default charcoal + 7 palette colours). Use this story as a complete visual reference for the Badge component. Each cell uses the same count (`"9"`) to make size and colour comparisons accurate.',
834
798
  );
835
-
836
- export const A11yWithLabel: Story = withDescription(
837
- {
838
- render: A11yWithLabelTemplate,
839
- parameters: {
840
- docs: {
841
- source: {
842
- language: 'tsx',
843
- code: `
844
- import { Badge, Icon } from '@arbor-education/design-system.components';
845
-
846
- function A11yWithLabelTemplate() {
847
- return (
848
- <div
849
- style={{
850
- padding: 'var(--spacing-xlarge)',
851
- display: 'flex',
852
- flexDirection: 'column',
853
- gap: 'var(--spacing-xlarge)',
854
- }}
855
- >
856
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
857
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-destructive-600)' }}>
858
- Without a11yLabel — screen reader announces "5" (no context)
859
- </p>
860
- <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
861
- <Icon name="mail" size={16} screenReaderText="Notifications" />
862
- <Badge>5</Badge>
863
- </div>
864
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
865
- Screen reader: "mail Notifications, 5"
866
- </p>
867
- </div>
868
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
869
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-semantic-success-600)' }}>
870
- With a11yLabel — screen reader announces the full meaningful label
871
- </p>
872
- <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-large)' }}>
873
- <Icon name="mail" size={16} />
874
- <Badge a11yLabel="5 unread messages">5</Badge>
875
- </div>
876
- <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
877
- Screen reader: "5 unread messages"
878
- </p>
879
- </div>
880
- </div>
881
- );
882
- }
883
- export default A11yWithLabelTemplate;
884
- `.trim(),
885
- },
886
- },
887
- },
888
- },
889
- [
890
- 'Accessibility contrast: top row shows a badge **without** `a11yLabel` — a screen reader',
891
- 'moving through the icon and badge would announce the icon text and then the bare number `"5"`,',
892
- 'providing no context about what those 5 items are.',
893
- '',
894
- 'The bottom row adds `a11yLabel="5 unread messages"`. The component sets `aria-label` on the',
895
- 'wrapper `<span>` and wraps `children` in an `aria-hidden="true"` span. The screen reader now',
896
- 'announces `"5 unread messages"` as a single coherent label — no duplicate number, full context.',
897
- '',
898
- 'Use `a11yLabel` whenever the badge sits next to an icon-only element or in a context where',
899
- 'the number alone would be ambiguous to a screen reader user.',
900
- ].join(' '),
901
- );
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import {
4
+ ArgTypes,
4
5
  Controls,
5
6
  Heading as DocHeading,
6
7
  Markdown,
@@ -9,8 +10,13 @@ import {
9
10
  Subtitle,
10
11
  Title,
11
12
  } from '@storybook/addon-docs/blocks';
13
+ import { fn } from 'storybook/test';
14
+ import { format } from 'date-fns';
15
+ import { Button } from 'Components/button/Button';
12
16
  import { FormField } from 'Components/formField/FormField';
17
+ import { Icon } from 'Components/icon/Icon';
13
18
  import { DatePicker } from './DatePicker.js';
19
+ import { DatePickerPopover } from './DatePickerPopover.js';
14
20
 
15
21
  // ---------------------------------------------------------------------------
16
22
  // Docs page content
@@ -104,6 +110,44 @@ const DEVELOPER_NOTES = [
104
110
  '| `DatePicker.Props` | Full props interface |',
105
111
  ].join('\n');
106
112
 
113
+ const POPOVER_NOTES = [
114
+ '`DatePickerPopover` is the calendar on its own: no date input, no icon button. You supply the',
115
+ 'trigger (usually a `Button`) and the component opens the same calendar popover as `DatePicker`.',
116
+ 'Use it when the date is shown elsewhere in your UI and you only need a way to pick or jump to a day,',
117
+ 'for example a "jump to day" icon in a calendar header. For a form field where the user types or',
118
+ 'sees the chosen date, use `DatePicker` instead.',
119
+ '',
120
+ '```tsx',
121
+ "import { Button, DatePickerPopover, Icon } from '@arbor-education/design-system.components';",
122
+ '',
123
+ '<DatePickerPopover',
124
+ ' value={date}',
125
+ ' onChange={setDate}',
126
+ ' trigger={(',
127
+ ' <Button variant="tertiary">',
128
+ ' <Icon name="date" screenReaderText="Jump to day" />',
129
+ ' </Button>',
130
+ ' )}',
131
+ '/>',
132
+ '```',
133
+ '',
134
+ '**Give the trigger an accessible name.** `DatePickerPopover` does not add one for you. A text',
135
+ 'button is named by its text. An icon-only button needs `screenReaderText` on the `Icon` (or an',
136
+ '`aria-label` on the `Button`), otherwise screen reader users hear an unlabelled button.',
137
+ '',
138
+ '**The trigger must forward its ref and spread props onto a DOM node.** The design system `Button`',
139
+ 'does. A custom component that does not will break popover anchoring and focus return to the trigger.',
140
+ '',
141
+ '**You own the value.** `onChange` receives a `Date`; store it and pass it back as `value`. If you',
142
+ 'omit `value`, the calendar opens on the current month.',
143
+ '',
144
+ '**Time of day is kept.** Picking a day keeps the hours and minutes of the `value` you passed in.',
145
+ 'Picking **Today** does the same.',
146
+ '',
147
+ '**Keyboard and focus.** Focus moves into the calendar when it opens. Selecting a day, choosing',
148
+ '**Today**, or pressing **Escape** closes the calendar and returns focus to your trigger.',
149
+ ].join('\n');
150
+
107
151
  const RELATED_COMPONENTS = [
108
152
  '## Related components',
109
153
  '',
@@ -130,6 +174,9 @@ function DatePickerDocsPage() {
130
174
  <Markdown>{USAGE_GUIDANCE}</Markdown>
131
175
  <DocHeading>Developer notes</DocHeading>
132
176
  <Markdown>{DEVELOPER_NOTES}</Markdown>
177
+ <DocHeading>DatePickerPopover</DocHeading>
178
+ <Markdown>{POPOVER_NOTES}</Markdown>
179
+ <ArgTypes of={DatePickerPopover} />
133
180
  <DocHeading>Examples</DocHeading>
134
181
  <Stories title="" />
135
182
  <Markdown>{RELATED_COMPONENTS}</Markdown>
@@ -820,3 +867,249 @@ export default ParentEveningForm;
820
867
  'linked via `aria-describedby` automatically. Once a date is chosen, the error clears.',
821
868
  ].join(' '),
822
869
  );
870
+
871
+ // ---------------------------------------------------------------------------
872
+ // DatePickerPopover stories
873
+ // ---------------------------------------------------------------------------
874
+
875
+ type PopoverStory = StoryObj<typeof DatePickerPopover>;
876
+
877
+ const withPopoverDescription = (story: PopoverStory, description: string): PopoverStory => ({
878
+ ...story,
879
+ parameters: {
880
+ ...story.parameters,
881
+ docs: {
882
+ ...story.parameters?.docs,
883
+ description: {
884
+ story: description,
885
+ },
886
+ },
887
+ },
888
+ });
889
+
890
+ // Fixed date so the play functions are deterministic (15 June 2025, 14:30)
891
+ const POPOVER_INITIAL_DATE = new Date(2025, 5, 15, 14, 30);
892
+
893
+ const formatLongDate = (date: Date) => date.toLocaleDateString('en-GB', {
894
+ weekday: 'long',
895
+ day: 'numeric',
896
+ month: 'long',
897
+ year: 'numeric',
898
+ });
899
+
900
+ type PopoverTemplateProps = {
901
+ onChange?: (newDate: Date) => void;
902
+ };
903
+
904
+ const PopoverTextTriggerTemplate = ({ onChange }: PopoverTemplateProps) => {
905
+ const [date, setDate] = useState(POPOVER_INITIAL_DATE);
906
+
907
+ const handleChange = (newDate: Date) => {
908
+ setDate(newDate);
909
+ onChange?.(newDate);
910
+ };
911
+
912
+ return (
913
+ <div>
914
+ <DatePickerPopover
915
+ value={date}
916
+ onChange={handleChange}
917
+ trigger={<Button variant="secondary">Choose date</Button>}
918
+ />
919
+ <p className="ds-text">
920
+ Selected:
921
+ {' '}
922
+ <code>{`${date.toLocaleDateString('en-GB')} ${format(date, 'HH:mm')}`}</code>
923
+ </p>
924
+ </div>
925
+ );
926
+ };
927
+
928
+ const PopoverIconTriggerTemplate = ({ onChange }: PopoverTemplateProps) => {
929
+ const [date, setDate] = useState(POPOVER_INITIAL_DATE);
930
+
931
+ const handleChange = (newDate: Date) => {
932
+ setDate(newDate);
933
+ onChange?.(newDate);
934
+ };
935
+
936
+ return (
937
+ <div>
938
+ <DatePickerPopover
939
+ value={date}
940
+ onChange={handleChange}
941
+ trigger={(
942
+ <Button variant="tertiary">
943
+ <Icon name="date" screenReaderText="Choose date" />
944
+ </Button>
945
+ )}
946
+ />
947
+ <p className="ds-text">
948
+ Selected:
949
+ {' '}
950
+ <code>{date.toLocaleDateString('en-GB')}</code>
951
+ </p>
952
+ </div>
953
+ );
954
+ };
955
+
956
+ const PopoverCalendarHeaderTemplate = ({ onChange }: PopoverTemplateProps) => {
957
+ const [viewedDate, setViewedDate] = useState(POPOVER_INITIAL_DATE);
958
+
959
+ const handleChange = (newDate: Date) => {
960
+ setViewedDate(newDate);
961
+ onChange?.(newDate);
962
+ };
963
+
964
+ return (
965
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}>
966
+ <DatePickerPopover
967
+ value={viewedDate}
968
+ onChange={handleChange}
969
+ trigger={(
970
+ <Button variant="tertiary" borderless>
971
+ <Icon name="date" screenReaderText="Jump to day" />
972
+ </Button>
973
+ )}
974
+ />
975
+ <p className="ds-text">
976
+ <time dateTime={format(viewedDate, 'yyyy-MM-dd')}>{formatLongDate(viewedDate)}</time>
977
+ </p>
978
+ </div>
979
+ );
980
+ };
981
+
982
+ export const PopoverWithTextTrigger: PopoverStory = withPopoverDescription(
983
+ {
984
+ args: { onChange: fn() },
985
+ render: args => <PopoverTextTriggerTemplate onChange={args.onChange} />,
986
+ parameters: {
987
+ controls: { disable: true },
988
+ docs: {
989
+ source: {
990
+ language: 'tsx',
991
+ code: `
992
+ import { useState } from 'react';
993
+ import { Button, DatePickerPopover } from '@arbor-education/design-system.components';
994
+
995
+ function ChooseDateButton() {
996
+ const [date, setDate] = useState(new Date(2025, 5, 15, 14, 30));
997
+
998
+ return (
999
+ <DatePickerPopover
1000
+ value={date}
1001
+ onChange={setDate}
1002
+ // A text button is named by its text content
1003
+ trigger={<Button variant="secondary">Choose date</Button>}
1004
+ />
1005
+ );
1006
+ }
1007
+ export default ChooseDateButton;
1008
+ `.trim(),
1009
+ },
1010
+ },
1011
+ },
1012
+ },
1013
+ [
1014
+ 'The simplest setup: a text `Button` as the trigger, with the date held in your own state.',
1015
+ 'The calendar opens on the month of `value`. Selecting a day (or **Today**) calls `onChange` with a',
1016
+ '`Date`, keeps the time of day from `value`, closes the calendar and returns focus to the button.',
1017
+ 'The button is named by its text, so no extra accessibility props are needed.',
1018
+ ].join(' '),
1019
+ );
1020
+
1021
+ export const PopoverWithIconTrigger: PopoverStory = withPopoverDescription(
1022
+ {
1023
+ args: { onChange: fn() },
1024
+ render: args => <PopoverIconTriggerTemplate onChange={args.onChange} />,
1025
+ parameters: {
1026
+ controls: { disable: true },
1027
+ docs: {
1028
+ source: {
1029
+ language: 'tsx',
1030
+ code: `
1031
+ import { useState } from 'react';
1032
+ import { Button, DatePickerPopover, Icon } from '@arbor-education/design-system.components';
1033
+
1034
+ function ChooseDateIconButton() {
1035
+ const [date, setDate] = useState(new Date(2025, 5, 15, 14, 30));
1036
+
1037
+ return (
1038
+ <DatePickerPopover
1039
+ value={date}
1040
+ onChange={setDate}
1041
+ trigger={(
1042
+ <Button variant="tertiary">
1043
+ {/* screenReaderText is the button's accessible name */}
1044
+ <Icon name="date" screenReaderText="Choose date" />
1045
+ </Button>
1046
+ )}
1047
+ />
1048
+ );
1049
+ }
1050
+ export default ChooseDateIconButton;
1051
+ `.trim(),
1052
+ },
1053
+ },
1054
+ },
1055
+ },
1056
+ [
1057
+ 'An icon-only trigger. The `Icon` is hidden from assistive technology, so **you must give the button',
1058
+ 'an accessible name**: use `screenReaderText` on the `Icon` (shown here) or `aria-label` on the `Button`.',
1059
+ 'Keyboard users can open the calendar with Enter or Space, move between days with the arrow keys,',
1060
+ 'and press **Escape** to close it without choosing a date. Focus returns to the icon button.',
1061
+ ].join(' '),
1062
+ );
1063
+
1064
+ export const PopoverCalendarHeaderJumpToDay: PopoverStory = withPopoverDescription(
1065
+ {
1066
+ args: { onChange: fn() },
1067
+ render: args => <PopoverCalendarHeaderTemplate onChange={args.onChange} />,
1068
+ parameters: {
1069
+ controls: { disable: true },
1070
+ docs: {
1071
+ source: {
1072
+ language: 'tsx',
1073
+ code: `
1074
+ import { useState } from 'react';
1075
+ import { format } from 'date-fns';
1076
+ import { Button, DatePickerPopover, Icon } from '@arbor-education/design-system.components';
1077
+
1078
+ function CalendarHeader() {
1079
+ const [viewedDate, setViewedDate] = useState(new Date(2025, 5, 15, 14, 30));
1080
+
1081
+ return (
1082
+ <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-small)' }}>
1083
+ <DatePickerPopover
1084
+ value={viewedDate}
1085
+ onChange={setViewedDate}
1086
+ trigger={(
1087
+ <Button variant="tertiary" borderless>
1088
+ <Icon name="date" screenReaderText="Jump to day" />
1089
+ </Button>
1090
+ )}
1091
+ />
1092
+ <time dateTime={format(viewedDate, 'yyyy-MM-dd')}>
1093
+ {viewedDate.toLocaleDateString('en-GB', {
1094
+ weekday: 'long',
1095
+ day: 'numeric',
1096
+ month: 'long',
1097
+ year: 'numeric',
1098
+ })}
1099
+ </time>
1100
+ </div>
1101
+ );
1102
+ }
1103
+ export default CalendarHeader;
1104
+ `.trim(),
1105
+ },
1106
+ },
1107
+ },
1108
+ },
1109
+ [
1110
+ 'The motivating use case: a "jump to day" icon button in a calendar header, with the currently viewed',
1111
+ 'date shown beside it. The date lives in your state, so the heading updates as soon as a day is picked',
1112
+ 'and the same value is passed back in as `value` the next time the calendar opens.',
1113
+ 'There is no date input, so the header stays compact and the selection is communicated by the text next to the button.',
1114
+ ].join(' '),
1115
+ );
@@ -0,0 +1,144 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom/vitest';
5
+ import { Button } from 'Components/button/Button';
6
+ import { DatePickerPopover } from './DatePickerPopover.js';
7
+
8
+ const renderPopover = (props: Partial<React.ComponentProps<typeof DatePickerPopover>> = {}) => render(
9
+ <DatePickerPopover
10
+ trigger={<Button>Jump to date</Button>}
11
+ value={new Date(2025, 5, 15, 14, 30)}
12
+ {...props}
13
+ />,
14
+ );
15
+
16
+ const openCalendar = () => userEvent.click(screen.getByRole('button', { name: 'Jump to date' }));
17
+
18
+ describe('DatePickerPopover', () => {
19
+ test('renders the consumer trigger and no date input', () => {
20
+ const { container } = renderPopover();
21
+ expect(screen.getByRole('button', { name: 'Jump to date' })).toBeInTheDocument();
22
+ expect(container.querySelector('input')).not.toBeInTheDocument();
23
+ });
24
+
25
+ test('calendar is hidden until the trigger is activated', async () => {
26
+ renderPopover();
27
+ expect(screen.queryByRole('application')).not.toBeInTheDocument();
28
+
29
+ await openCalendar();
30
+
31
+ expect(screen.getByRole('application')).toBeInTheDocument();
32
+ });
33
+
34
+ test('opens on the month of the supplied value', async () => {
35
+ renderPopover();
36
+ await openCalendar();
37
+
38
+ expect(within(screen.getByRole('application')).getByRole('button', { name: /June 20/ })).toBeInTheDocument();
39
+ });
40
+
41
+ test('selecting a day calls onChange with a Date and closes the calendar', async () => {
42
+ const onChange = vi.fn();
43
+ renderPopover({ onChange });
44
+ await openCalendar();
45
+
46
+ await userEvent.click(within(screen.getByRole('application')).getByRole('button', { name: /June 20/ }));
47
+
48
+ expect(onChange).toHaveBeenCalledTimes(1);
49
+ const selected = onChange.mock.lastCall?.[0] as Date;
50
+ expect(selected).toBeInstanceOf(Date);
51
+ expect([selected.getFullYear(), selected.getMonth(), selected.getDate()]).toEqual([2025, 5, 20]);
52
+ expect(screen.queryByRole('application')).not.toBeInTheDocument();
53
+ });
54
+
55
+ test('selecting a day preserves the time of the supplied value', async () => {
56
+ const onChange = vi.fn();
57
+ renderPopover({ onChange });
58
+ await openCalendar();
59
+
60
+ await userEvent.click(within(screen.getByRole('application')).getByRole('button', { name: /June 20/ }));
61
+
62
+ const selected = onChange.mock.lastCall?.[0] as Date;
63
+ expect([selected.getHours(), selected.getMinutes()]).toEqual([14, 30]);
64
+ });
65
+
66
+ test('clicking the already selected day still calls onChange and closes the calendar', async () => {
67
+ const onChange = vi.fn();
68
+ renderPopover({ onChange });
69
+ await openCalendar();
70
+
71
+ await userEvent.click(within(screen.getByRole('application')).getByRole('button', { name: /June 15/ }));
72
+
73
+ expect(onChange).toHaveBeenCalledTimes(1);
74
+ expect(onChange.mock.lastCall?.[0]).toBeInstanceOf(Date);
75
+ expect(screen.queryByRole('application')).not.toBeInTheDocument();
76
+ });
77
+
78
+ test('clicking Today calls onChange with today and closes the calendar', async () => {
79
+ const onChange = vi.fn();
80
+ renderPopover({ onChange });
81
+ await openCalendar();
82
+
83
+ await userEvent.click(screen.getByRole('button', { name: 'Today' }));
84
+
85
+ const today = new Date();
86
+ const selected = onChange.mock.lastCall?.[0] as Date;
87
+ expect([selected.getFullYear(), selected.getMonth(), selected.getDate()])
88
+ .toEqual([today.getFullYear(), today.getMonth(), today.getDate()]);
89
+ expect(screen.queryByRole('application')).not.toBeInTheDocument();
90
+ });
91
+
92
+ test('works without a value, opening on the current month', async () => {
93
+ const onChange = vi.fn();
94
+ renderPopover({ value: undefined, onChange });
95
+ await openCalendar();
96
+
97
+ await userEvent.click(screen.getByRole('button', { name: 'Today' }));
98
+
99
+ expect(onChange).toHaveBeenCalledWith(expect.any(Date));
100
+ });
101
+
102
+ test('focus moves into the calendar on open', async () => {
103
+ renderPopover();
104
+ await openCalendar();
105
+
106
+ expect(screen.getByRole('application')).toContainElement(document.activeElement as HTMLElement);
107
+ });
108
+
109
+ test('Escape closes the calendar and returns focus to the trigger', async () => {
110
+ renderPopover();
111
+ await openCalendar();
112
+
113
+ await userEvent.keyboard('{Escape}');
114
+
115
+ expect(screen.queryByRole('application')).not.toBeInTheDocument();
116
+ expect(screen.getByRole('button', { name: 'Jump to date' })).toHaveFocus();
117
+ });
118
+
119
+ test('selecting a day returns focus to the trigger', async () => {
120
+ renderPopover();
121
+ await openCalendar();
122
+
123
+ await userEvent.click(within(screen.getByRole('application')).getByRole('button', { name: /June 20/ }));
124
+
125
+ expect(screen.getByRole('button', { name: 'Jump to date' })).toHaveFocus();
126
+ });
127
+
128
+ test('reopening shows the month of the latest value after the user browsed away', async () => {
129
+ const { rerender } = renderPopover();
130
+ await openCalendar();
131
+ await userEvent.click(screen.getByRole('button', { name: 'Go to the Next Month' }));
132
+ await userEvent.keyboard('{Escape}');
133
+
134
+ rerender(
135
+ <DatePickerPopover
136
+ trigger={<Button>Jump to date</Button>}
137
+ value={new Date(2025, 8, 3)}
138
+ />,
139
+ );
140
+ await openCalendar();
141
+
142
+ expect(within(screen.getByRole('application')).getByRole('button', { name: /September 10/ })).toBeInTheDocument();
143
+ });
144
+ });