@arbor-education/design-system.components 3.4.5 → 3.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 (57) hide show
  1. package/.github/workflows/pr-review-approve.yaml +2 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/components/formField/FormField.d.ts +4 -0
  4. package/dist/components/formField/FormField.d.ts.map +1 -1
  5. package/dist/components/formField/FormField.js +2 -1
  6. package/dist/components/formField/FormField.js.map +1 -1
  7. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  8. package/dist/components/formField/FormField.stories.js +10 -7
  9. package/dist/components/formField/FormField.stories.js.map +1 -1
  10. package/dist/components/formField/FormField.test.js +14 -0
  11. package/dist/components/formField/FormField.test.js.map +1 -1
  12. package/dist/components/formField/inputs/singleFile/SingleFileInput.d.ts +14 -0
  13. package/dist/components/formField/inputs/singleFile/SingleFileInput.d.ts.map +1 -0
  14. package/dist/components/formField/inputs/singleFile/SingleFileInput.js +35 -0
  15. package/dist/components/formField/inputs/singleFile/SingleFileInput.js.map +1 -0
  16. package/dist/components/formField/inputs/singleFile/SingleFileInput.stories.d.ts +170 -0
  17. package/dist/components/formField/inputs/singleFile/SingleFileInput.stories.d.ts.map +1 -0
  18. package/dist/components/formField/inputs/singleFile/SingleFileInput.stories.js +242 -0
  19. package/dist/components/formField/inputs/singleFile/SingleFileInput.stories.js.map +1 -0
  20. package/dist/components/formField/inputs/singleFile/SingleFileInput.test.d.ts +2 -0
  21. package/dist/components/formField/inputs/singleFile/SingleFileInput.test.d.ts.map +1 -0
  22. package/dist/components/formField/inputs/singleFile/SingleFileInput.test.js +135 -0
  23. package/dist/components/formField/inputs/singleFile/SingleFileInput.test.js.map +1 -0
  24. package/dist/components/widgetCalendar/WidgetCalendar.d.ts +1 -0
  25. package/dist/components/widgetCalendar/WidgetCalendar.d.ts.map +1 -1
  26. package/dist/components/widgetCalendar/WidgetCalendar.js +1 -1
  27. package/dist/components/widgetCalendar/WidgetCalendar.js.map +1 -1
  28. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts +2 -0
  29. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts.map +1 -1
  30. package/dist/components/widgetCalendar/WidgetCalendar.stories.js +39 -1
  31. package/dist/components/widgetCalendar/WidgetCalendar.stories.js.map +1 -1
  32. package/dist/components/widgetCalendar/WidgetCalendar.test.js +48 -1
  33. package/dist/components/widgetCalendar/WidgetCalendar.test.js.map +1 -1
  34. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts +1 -0
  35. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts.map +1 -1
  36. package/dist/components/widgetCalendar/WidgetCalendarItem.js +7 -2
  37. package/dist/components/widgetCalendar/WidgetCalendarItem.js.map +1 -1
  38. package/dist/index.css +18 -0
  39. package/dist/index.css.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -0
  43. package/dist/index.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/components/formField/FormField.stories.tsx +10 -7
  46. package/src/components/formField/FormField.test.tsx +35 -0
  47. package/src/components/formField/FormField.tsx +5 -0
  48. package/src/components/formField/inputs/singleFile/SingleFileInput.stories.tsx +339 -0
  49. package/src/components/formField/inputs/singleFile/SingleFileInput.test.tsx +181 -0
  50. package/src/components/formField/inputs/singleFile/SingleFileInput.tsx +101 -0
  51. package/src/components/formField/inputs/singleFile/singleFileInput.scss +17 -0
  52. package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +43 -1
  53. package/src/components/widgetCalendar/WidgetCalendar.test.tsx +73 -1
  54. package/src/components/widgetCalendar/WidgetCalendar.tsx +2 -0
  55. package/src/components/widgetCalendar/WidgetCalendarItem.tsx +14 -1
  56. package/src/index.scss +1 -0
  57. package/src/index.ts +1 -0
@@ -227,7 +227,7 @@ const meta = {
227
227
  description: [
228
228
  'Array of `WidgetCalendar.ItemData` objects rendered as event rows. Each item requires `id`,',
229
229
  '`eventPrimaryTerm`, `eventPrimaryDefinition`, `eventSecondaryTerm`, `eventDateTime`, and `eventTimeLabel`.',
230
- '`eventType`, `eventLocation`, `eventSecondaryDefinition`, and `buttonA11yText` are optional.',
230
+ '`eventType`, `eventLocation`, `eventSecondaryDefinition`, `buttonA11yText`, and `tooltip` are optional.',
231
231
  ].join(' '),
232
232
  table: {
233
233
  type: { summary: 'WidgetCalendar.ItemData[]' },
@@ -609,6 +609,48 @@ export const WithTooltip: Story = withDescription(
609
609
  'Adding `tooltip` shows supplementary context alongside the heading. A single event keeps the focus on the tooltip.',
610
610
  );
611
611
 
612
+ export const WithItemTooltips: Story = withDescription(
613
+ {
614
+ args: {
615
+ heading: 'Calendar',
616
+ items: [
617
+ {
618
+ id: 'year-10-10g',
619
+ eventType: 'primary',
620
+ eventPrimaryTerm: 'Class',
621
+ eventPrimaryDefinition: 'Year 10: 10G',
622
+ tooltip: 'Room change — this lesson has moved from Classroom 204',
623
+ ...classEventTime,
624
+ },
625
+ {
626
+ id: 'class-generic',
627
+ eventType: 'secondary',
628
+ eventPrimaryTerm: 'Class',
629
+ eventPrimaryDefinition: 'Class',
630
+ ...classEventTime,
631
+ },
632
+ {
633
+ id: 'maths-year-10',
634
+ eventType: 'primary',
635
+ eventPrimaryTerm: 'Class',
636
+ eventPrimaryDefinition: 'Maths: Year 10: 10x/Ma3',
637
+ tooltip: 'Cover teacher: Mrs Stanhope',
638
+ ...classEventTime,
639
+ },
640
+ ],
641
+ onItemClick: fn(),
642
+ },
643
+ render: args => <WidgetCalendar {...args} />,
644
+ parameters: { controls: { disable: true } },
645
+ },
646
+ [
647
+ 'Per-item `tooltip` text, set on `ItemData`, shown on hover or focus of the row. Use it for',
648
+ 'short supplementary context that would clutter the row itself — a room change, a cover teacher.',
649
+ 'Items without a `tooltip` render exactly as before. Tooltips work on both interactive and',
650
+ 'non-interactive rows, and keyboard interaction (Enter/Space) is unaffected.',
651
+ ].join(' '),
652
+ );
653
+
612
654
  export const Empty: Story = withDescription(
613
655
  {
614
656
  args: {
@@ -1,5 +1,5 @@
1
1
  import '@testing-library/jest-dom/vitest';
2
- import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { describe, expect, test, vi } from 'vitest';
5
5
  import { WidgetCalendar } from './WidgetCalendar.js';
@@ -134,4 +134,76 @@ describe('WidgetCalendar', () => {
134
134
 
135
135
  expect(screen.getByRole('heading', { name: 'Nothing scheduled' })).toBeInTheDocument();
136
136
  });
137
+
138
+ describe('item tooltips', () => {
139
+ const tooltipItems: WidgetCalendar.ItemData[] = [
140
+ {
141
+ id: 'year-10',
142
+ eventType: 'primary',
143
+ eventPrimaryTerm: 'Class',
144
+ eventPrimaryDefinition: 'Year 10: 10G',
145
+ eventSecondaryTerm: 'Time and location',
146
+ eventDateTime: '2026-06-08T08:45/2026-06-08T09:00',
147
+ eventTimeLabel: '08:45 – 09:00',
148
+ eventLocation: 'Classroom 102',
149
+ tooltip: 'Double maths with Mr Cullen',
150
+ },
151
+ {
152
+ id: 'class-label',
153
+ eventType: 'secondary',
154
+ eventPrimaryTerm: 'Class',
155
+ eventPrimaryDefinition: 'Class',
156
+ eventSecondaryTerm: 'Time and location',
157
+ eventDateTime: '2026-06-08T10:00/2026-06-08T10:45',
158
+ eventTimeLabel: '10:00 – 10:45',
159
+ eventLocation: 'Hall',
160
+ },
161
+ ];
162
+
163
+ test('shows tooltip text when hovering an item with a tooltip', async () => {
164
+ render(
165
+ <WidgetCalendar heading="Calendar" items={tooltipItems} />,
166
+ );
167
+
168
+ await userEvent.hover(screen.getByText('Year 10: 10G'));
169
+
170
+ await waitFor(() => {
171
+ expect(screen.getAllByText('Double maths with Mr Cullen').length).toBeGreaterThan(0);
172
+ });
173
+ });
174
+
175
+ test('does not show tooltip text until the item is hovered', () => {
176
+ render(
177
+ <WidgetCalendar heading="Calendar" items={tooltipItems} />,
178
+ );
179
+
180
+ expect(screen.queryByText('Double maths with Mr Cullen')).not.toBeInTheDocument();
181
+ });
182
+
183
+ test('renders items without a tooltip unchanged', () => {
184
+ render(
185
+ <WidgetCalendar heading="Calendar" items={tooltipItems} />,
186
+ );
187
+
188
+ expect(screen.getByText('10:00 – 10:45')).toBeInTheDocument();
189
+ });
190
+
191
+ test('tooltip does not break item activation on Enter key', () => {
192
+ const onItemClick = vi.fn();
193
+
194
+ render(
195
+ <WidgetCalendar
196
+ heading="Calendar"
197
+ items={tooltipItems}
198
+ onItemClick={onItemClick}
199
+ />,
200
+ );
201
+
202
+ const item = screen.getByRole('button', { name: 'Open Year 10: 10G' });
203
+
204
+ fireEvent.keyDown(item, { key: 'Enter' });
205
+
206
+ expect(onItemClick).toHaveBeenCalledWith('year-10');
207
+ });
208
+ });
137
209
  });
@@ -15,6 +15,7 @@ type WidgetCalendarItemData = {
15
15
  eventLocation?: string;
16
16
  eventSecondaryDefinition?: React.ReactNode;
17
17
  buttonA11yText?: string;
18
+ tooltip?: string;
18
19
  };
19
20
 
20
21
  type WidgetCalendarProps = {
@@ -71,6 +72,7 @@ export const WidgetCalendar = (props: WidgetCalendarProps): React.JSX.Element =>
71
72
  eventSecondaryDefinition={item.eventSecondaryDefinition}
72
73
  buttonA11yText={item.buttonA11yText}
73
74
  onItemClick={onItemClick ? () => onItemClick(item.id) : undefined}
75
+ tooltip={item.tooltip}
74
76
  />
75
77
  ))}
76
78
  </ul>
@@ -1,5 +1,6 @@
1
1
  import classNames from 'classnames';
2
2
  import { Icon } from 'Components/icon/Icon';
3
+ import { TooltipWrapper } from 'Components/tooltip/TooltipWrapper';
3
4
  import { ENTER_KEY, SPACE_KEY } from 'Utils/keyboardConstants';
4
5
 
5
6
  export type WidgetCalendarEventType = 'primary' | 'secondary';
@@ -17,6 +18,7 @@ export type WidgetCalendarItemProps = {
17
18
  buttonA11yText?: string;
18
19
  onItemClick?: () => void;
19
20
  className?: string;
21
+ tooltip?: string;
20
22
  };
21
23
 
22
24
  export const WidgetCalendarItem = (props: WidgetCalendarItemProps): React.JSX.Element => {
@@ -33,6 +35,7 @@ export const WidgetCalendarItem = (props: WidgetCalendarItemProps): React.JSX.El
33
35
  buttonA11yText,
34
36
  onItemClick,
35
37
  className,
38
+ tooltip,
36
39
  } = props;
37
40
 
38
41
  const isInteractive = Boolean(onItemClick);
@@ -61,7 +64,7 @@ export const WidgetCalendarItem = (props: WidgetCalendarItemProps): React.JSX.El
61
64
  }
62
65
  };
63
66
 
64
- return (
67
+ const item = (
65
68
  <li
66
69
  className={classNames(
67
70
  'ds-widget-calendar__item',
@@ -102,4 +105,14 @@ export const WidgetCalendarItem = (props: WidgetCalendarItemProps): React.JSX.El
102
105
  )}
103
106
  </li>
104
107
  );
108
+
109
+ if (tooltip) {
110
+ return (
111
+ <TooltipWrapper tooltipContent={tooltip}>
112
+ {item}
113
+ </TooltipWrapper>
114
+ );
115
+ }
116
+
117
+ return item;
105
118
  };
package/src/index.scss CHANGED
@@ -17,6 +17,7 @@
17
17
  @use "components/formField/label/label.scss";
18
18
  @use "components/formField/inputs/number/numberInput.scss";
19
19
  @use "components/formField/inputs/time/timeInput.scss";
20
+ @use "components/formField/inputs/singleFile/singleFileInput.scss";
20
21
  @use "components/formField/inputs/radio/radioButtonInput.scss";
21
22
  @use "components/formField/inputs/checkbox/checkboxInput.scss";
22
23
  @use "components/formField/inputs/selectDropdown/selectDropdown";
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ export { NumberInput } from 'Components/formField/inputs/number/NumberInput';
35
35
  export { RadioButtonGroup } from 'Components/formField/inputs/radio/RadioButtonGroup';
36
36
  export { RadioButtonInput } from 'Components/formField/inputs/radio/RadioButtonInput';
37
37
  export { SelectDropdown } from 'Components/formField/inputs/selectDropdown/SelectDropdown';
38
+ export { SingleFileInput } from 'Components/formField/inputs/singleFile/SingleFileInput';
38
39
  export { Skeleton } from 'Components/skeleton/Skeleton';
39
40
  export { TextInput } from 'Components/formField/inputs/text/TextInput';
40
41
  export { TextArea } from 'Components/formField/inputs/textArea/TextArea';