@arbor-education/design-system.components 4.1.0 → 4.3.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 (40) hide show
  1. package/.pre-commit-config.yaml +4 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/components/formField/FormField.d.ts +15 -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 +1 -0
  8. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  9. package/dist/components/formField/FormField.stories.js +71 -5
  10. package/dist/components/formField/FormField.stories.js.map +1 -1
  11. package/dist/components/formField/FormField.test.js +19 -1
  12. package/dist/components/formField/FormField.test.js.map +1 -1
  13. package/dist/components/formField/label/Label.js +2 -2
  14. package/dist/components/formField/label/Label.js.map +1 -1
  15. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts +1 -0
  16. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts.map +1 -1
  17. package/dist/components/widgetCalendar/WidgetCalendar.stories.js +77 -4
  18. package/dist/components/widgetCalendar/WidgetCalendar.stories.js.map +1 -1
  19. package/dist/components/widgetCalendar/WidgetCalendar.test.js +31 -0
  20. package/dist/components/widgetCalendar/WidgetCalendar.test.js.map +1 -1
  21. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts +1 -1
  22. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts.map +1 -1
  23. package/dist/components/widgetCalendar/WidgetCalendarItem.js +1 -3
  24. package/dist/components/widgetCalendar/WidgetCalendarItem.js.map +1 -1
  25. package/dist/index.css +30 -0
  26. package/dist/index.css.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/formField/FormField.stories.tsx +100 -5
  32. package/src/components/formField/FormField.test.tsx +36 -1
  33. package/src/components/formField/FormField.tsx +21 -2
  34. package/src/components/formField/label/Label.tsx +2 -2
  35. package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +81 -4
  36. package/src/components/widgetCalendar/WidgetCalendar.test.tsx +48 -0
  37. package/src/components/widgetCalendar/WidgetCalendarItem.tsx +10 -4
  38. package/src/components/widgetCalendar/widgetCalendar.scss +24 -0
  39. package/src/index.ts +1 -0
  40. package/src/tokens.scss +6 -0
@@ -18,7 +18,7 @@ import { FormField } from './FormField.js';
18
18
 
19
19
  const DESCRIPTION_INTRO = [
20
20
  'FormField is the **complete form field assembly**: it wires together a label, an optional',
21
- 'description, any of thirteen input controls, an optional helper link, and an error message — all',
21
+ 'description, any of fourteen input controls (or your own via `custom`), an optional helper link, and an error message — all',
22
22
  'with the correct accessibility connections built in automatically.',
23
23
  '',
24
24
  'Think of it as a filing cabinet drawer with everything pre-labelled: you tell it what kind of',
@@ -63,6 +63,7 @@ const USAGE_GUIDANCE = [
63
63
  '| `"toggle"` | `Toggle` | Instant on/off settings embedded in a form |',
64
64
  '| `"radio"` | `RadioButtonGroup` | One choice from a short, mutually-exclusive list |',
65
65
  '| `"singleFile"` | `SingleFileInput` | One attachment submitted with a native form POST |',
66
+ '| `"custom"` | Your own element, via `inputProps.renderCustomInput` | A control the DS does not ship, such as a rich-text editor |',
66
67
  ].join('\n');
67
68
 
68
69
  const DEVELOPER_NOTES = [
@@ -235,7 +236,7 @@ const meta = {
235
236
  },
236
237
  inputType: {
237
238
  control: 'select',
238
- options: ['text', 'textarea', 'number', 'time', 'colourPicker', 'selectDropdown', 'datePicker', 'combobox', 'checkbox', 'checkboxGroup', 'toggle', 'radio', 'singleFile'],
239
+ options: ['text', 'textarea', 'number', 'time', 'colourPicker', 'selectDropdown', 'datePicker', 'combobox', 'checkbox', 'checkboxGroup', 'toggle', 'radio', 'singleFile', 'custom'],
239
240
  description: [
240
241
  'Determines which input component is rendered.',
241
242
  'Defaults to `"text"` (renders `TextInput`).',
@@ -249,11 +250,12 @@ const meta = {
249
250
  '`"checkboxGroup"` → `CheckboxGroup` (several independent selections);',
250
251
  '`"toggle"` → `Toggle` (instant on/off switch);',
251
252
  '`"radio"` → `RadioButtonGroup` (one choice from a short list);',
252
- '`"singleFile"` → `SingleFileInput` (one file for a native form POST).',
253
+ '`"singleFile"` → `SingleFileInput` (one file for a native form POST);',
254
+ '`"custom"` → the element returned by `inputProps.renderCustomInput` (a control the DS does not ship).',
253
255
  '`"checkbox"`, `"checkboxGroup"`, `"toggle"` and `"radio"` require `inputProps` — see the developer notes.',
254
256
  ].join(' '),
255
257
  table: {
256
- type: { summary: "'text' | 'textarea' | 'number' | 'time' | 'colourPicker' | 'selectDropdown' | 'datePicker' | 'combobox' | 'checkbox' | 'checkboxGroup' | 'toggle' | 'radio' | 'singleFile'" },
258
+ type: { summary: "'text' | 'textarea' | 'number' | 'time' | 'colourPicker' | 'selectDropdown' | 'datePicker' | 'combobox' | 'checkbox' | 'checkboxGroup' | 'toggle' | 'radio' | 'singleFile' | 'custom'" },
257
259
  defaultValue: { summary: "'text'" },
258
260
  },
259
261
  },
@@ -328,7 +330,7 @@ const meta = {
328
330
  '**Required, not optional, for `"checkbox"`, `"checkboxGroup"`, `"toggle"`, and `"radio"`.**',
329
331
  ].join(' '),
330
332
  table: {
331
- type: { summary: 'TextInputProps | TextAreaProps | NumberInputProps | TimeInputProps | ColourPickerDropdownProps | SelectDropdownInputProps | DatePickerProps | DateTimePickerProps | ComboboxProps | CheckboxInputProps | CheckboxGroupProps | Toggle.Props | RadioButtonGroupProps | SingleFileInputProps' },
333
+ type: { summary: 'TextInputProps | TextAreaProps | NumberInputProps | TimeInputProps | ColourPickerDropdownProps | SelectDropdownInputProps | DatePickerProps | DateTimePickerProps | ComboboxProps | CheckboxInputProps | CheckboxGroupProps | Toggle.Props | RadioButtonGroupProps | SingleFileInputProps | CustomInputProps' },
332
334
  },
333
335
  },
334
336
  },
@@ -1990,3 +1992,96 @@ export default WithRadioExample;
1990
1992
  'convention as `checkboxGroup`.',
1991
1993
  ].join('\n'),
1992
1994
  );
1995
+
1996
+ export const WithCustomInput: Story = withDescription(
1997
+ {
1998
+ parameters: {
1999
+ docs: {
2000
+ source: {
2001
+ language: 'tsx',
2002
+ code: `
2003
+ import { FormField } from '@arbor-education/design-system.components';
2004
+
2005
+ function WithCustomInputExample() {
2006
+ return (
2007
+ <FormField
2008
+ id="report-notes"
2009
+ label="Report notes"
2010
+ inputType="custom"
2011
+ fieldDescription="Shown to parents on the termly report"
2012
+ helperLinkText="Formatting help"
2013
+ helperLinkUrl="https://example.com/formatting"
2014
+ errorText="Report notes are required"
2015
+ inputProps={{
2016
+ renderCustomInput: ({ id, labelId, hasError, 'aria-describedby': describedBy, 'aria-invalid': invalid }) => (
2017
+ <div
2018
+ id={id}
2019
+ role="textbox"
2020
+ aria-multiline="true"
2021
+ aria-labelledby={labelId}
2022
+ aria-describedby={describedBy}
2023
+ aria-invalid={invalid}
2024
+ contentEditable
2025
+ suppressContentEditableWarning
2026
+ className={hasError ? 'my-editor my-editor--error' : 'my-editor'}
2027
+ />
2028
+ ),
2029
+ }}
2030
+ />
2031
+ );
2032
+ }
2033
+ export default WithCustomInputExample;
2034
+ `.trim(),
2035
+ },
2036
+ },
2037
+ },
2038
+ render: () => (
2039
+ <div style={{ maxWidth: '28rem', padding: 'var(--spacing-xlarge)' }}>
2040
+ <FormField
2041
+ id="report-notes"
2042
+ label="Report notes"
2043
+ inputType="custom"
2044
+ fieldDescription="Shown to parents on the termly report"
2045
+ helperLinkText="Formatting help"
2046
+ helperLinkUrl="https://example.com/formatting"
2047
+ errorText="Report notes are required"
2048
+ inputProps={{
2049
+ renderCustomInput: ({ id, labelId, hasError, 'aria-describedby': describedBy, 'aria-invalid': invalid }) => (
2050
+ <div
2051
+ id={id}
2052
+ role="textbox"
2053
+ aria-multiline="true"
2054
+ aria-labelledby={labelId}
2055
+ aria-describedby={describedBy}
2056
+ aria-invalid={invalid}
2057
+ contentEditable
2058
+ suppressContentEditableWarning
2059
+ style={{
2060
+ minHeight: '6rem',
2061
+ padding: 'var(--spacing-small)',
2062
+ border: `1px solid ${hasError ? 'var(--color-border-error, red)' : 'var(--color-border-default, grey)'}`,
2063
+ borderRadius: 'var(--border-radius-small, 4px)',
2064
+ }}
2065
+ />
2066
+ ),
2067
+ }}
2068
+ />
2069
+ </div>
2070
+ ),
2071
+ },
2072
+ [
2073
+ '`inputType="custom"` hosts an input the DS does not ship, such as a rich-text editor. `FormField`',
2074
+ 'still renders the label, `fieldDescription`, helper link and `errorText`; your',
2075
+ '`inputProps.renderCustomInput` function returns the control. `inputProps` is **required**.',
2076
+ '',
2077
+ '**`renderCustomInput` receives the same wiring the built-in inputs get:** `id`, `aria-describedby`,',
2078
+ '`aria-invalid` and `hasError`. Apply them to the element that takes focus, otherwise the',
2079
+ 'description and error are not announced.',
2080
+ '',
2081
+ '**Use `labelId` with `aria-labelledby` when the control is not a native input.** The label\'s',
2082
+ '`htmlFor` only names `input`, `textarea` and `select`. A `contentEditable` element or an iframe',
2083
+ 'needs `aria-labelledby={labelId}` to get its accessible name.',
2084
+ '',
2085
+ '**The DS styles none of the custom control.** Use `hasError` to apply your own error treatment.',
2086
+ ].join('\n'),
2087
+ );
@@ -1,6 +1,6 @@
1
1
  import '@testing-library/jest-dom/vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
- import { describe, expect, test } from 'vitest';
3
+ import { describe, expect, test, vi } from 'vitest';
4
4
  import { FormField } from './FormField.js';
5
5
 
6
6
  describe('FormField component', () => {
@@ -229,4 +229,39 @@ describe('FormField component', () => {
229
229
  expect(field).toHaveClass('ds-input--error');
230
230
  expect(field).toHaveAccessibleDescription('File is too large');
231
231
  });
232
+
233
+ test('renders the consumer element inside the field chrome when inputType is custom', () => {
234
+ render(
235
+ <FormField
236
+ id="notes"
237
+ label="Notes"
238
+ inputType="custom"
239
+ fieldDescription="Shown on the report"
240
+ helperLinkText="Formatting help"
241
+ helperLinkUrl="https://example.com/help"
242
+ errorText="Notes are required"
243
+ inputProps={{
244
+ renderCustomInput: ({ id, labelId, 'aria-describedby': describedBy, 'aria-invalid': invalid }) => (
245
+ <div role="textbox" id={id} aria-multiline="true" aria-labelledby={labelId} aria-describedby={describedBy} aria-invalid={invalid} />
246
+ ),
247
+ }}
248
+ />,
249
+ );
250
+
251
+ const editor = screen.getByRole('textbox', { name: 'Notes' });
252
+ expect(editor).toHaveAccessibleDescription('Shown on the report Notes are required');
253
+ expect(editor).toBeInvalid();
254
+ expect(screen.getByText('Notes are required')).toBeInTheDocument();
255
+ expect(screen.getByRole('link', { name: 'Notes helper link' })).toBeInTheDocument();
256
+ expect(editor.closest('.ds-form-field')).not.toBeNull();
257
+ });
258
+
259
+ test('tells a custom input whether the field has an error', () => {
260
+ const renderCustomInput = vi.fn(() => <div />);
261
+ const { rerender } = render(<FormField id="notes" inputType="custom" inputProps={{ renderCustomInput }} />);
262
+ expect(renderCustomInput).toHaveBeenLastCalledWith(expect.objectContaining({ 'hasError': false, 'aria-invalid': false }));
263
+
264
+ rerender(<FormField id="notes" inputType="custom" errorText="Required" inputProps={{ renderCustomInput }} />);
265
+ expect(renderCustomInput).toHaveBeenLastCalledWith(expect.objectContaining({ 'hasError': true, 'aria-invalid': true }));
266
+ });
232
267
  });
@@ -16,6 +16,21 @@ import { CheckboxGroup } from './inputs/checkbox/CheckboxGroup.js';
16
16
  import { RadioButtonGroup } from './inputs/radio/RadioButtonGroup.js';
17
17
  import { Toggle } from 'Components/toggle/Toggle.js';
18
18
 
19
+ export type FormFieldSharedProps = {
20
+ 'id'?: string;
21
+ 'aria-describedby': string;
22
+ 'aria-invalid': boolean;
23
+ 'hasError': boolean;
24
+ };
25
+
26
+ export type CustomInputRenderProps = FormFieldSharedProps & {
27
+ labelId?: string;
28
+ };
29
+
30
+ export type CustomInputProps = {
31
+ renderCustomInput: (renderProps: CustomInputRenderProps) => React.ReactNode;
32
+ };
33
+
19
34
  export type FormFieldProps = {
20
35
  className?: string;
21
36
  label?: string;
@@ -39,6 +54,7 @@ export type FormFieldProps = {
39
54
  | { inputType?: 'toggle'; inputProps: Toggle.Props }
40
55
  | { inputType?: 'radio'; inputProps: RadioButtonGroup.Props }
41
56
  | { inputType?: 'singleFile'; inputProps?: SingleFileInput.Props }
57
+ | { inputType?: 'custom'; inputProps: CustomInputProps }
42
58
  );
43
59
 
44
60
  export const FormField = (props: FormFieldProps) => {
@@ -52,7 +68,9 @@ export const FormField = (props: FormFieldProps) => {
52
68
  describedByIds.push(`${id}-error`);
53
69
  }
54
70
 
55
- const sharedProps = {
71
+ const labelId = inputType === 'custom' && id ? `${id}-label` : undefined;
72
+
73
+ const sharedProps: FormFieldSharedProps = {
56
74
  id,
57
75
  'aria-describedby': describedByIds.join(' '),
58
76
  'hasError': !!errorText,
@@ -62,7 +80,7 @@ export const FormField = (props: FormFieldProps) => {
62
80
  return (
63
81
  <div className={classes}>
64
82
  {label && (
65
- <Label htmlFor={id}>
83
+ <Label htmlFor={id} id={labelId}>
66
84
  {label}
67
85
  </Label>
68
86
  )}
@@ -111,6 +129,7 @@ export const FormField = (props: FormFieldProps) => {
111
129
  {inputType === 'singleFile' && (
112
130
  <SingleFileInput {...sharedProps} {...(inputProps as SingleFileInput.Props)} />
113
131
  )}
132
+ {inputType === 'custom' && (inputProps as CustomInputProps).renderCustomInput({ ...sharedProps, labelId })}
114
133
  {((helperLinkText && helperLinkUrl) || errorText) && (
115
134
  <div className="ds-form-field__message">
116
135
  {errorText && (
@@ -2,7 +2,7 @@ import classNames from 'classnames';
2
2
  import type { HTMLProps } from 'react';
3
3
 
4
4
  export const Label = (props: HTMLProps<HTMLLabelElement>) => {
5
- const { className, htmlFor, children } = props;
5
+ const { className, htmlFor, id, children } = props;
6
6
  const classes = classNames('ds-label', className);
7
- return <label className={classes} htmlFor={htmlFor}>{children}</label>;
7
+ return <label className={classes} htmlFor={htmlFor} id={id}>{children}</label>;
8
8
  };
@@ -166,7 +166,7 @@ const groupedItems: WidgetCalendar.ItemData[] = [
166
166
 
167
167
  const DESCRIPTION_INTRO = [
168
168
  '`WidgetCalendar` is a pre-composed dashboard widget for date/time-anchored calendar events.',
169
- 'It wraps [`Widget`](?path=/docs/components-widget--docs) with an event list, primary/secondary type indicators, and',
169
+ 'It wraps [`Widget`](?path=/docs/components-widget--docs) with an event list, an eight-value event type indicator, and',
170
170
  'optional heading navigation. Each item renders a semantic `<time>` element from an ISO `eventDateTime` and a',
171
171
  'human-readable `eventTimeLabel`.',
172
172
  ].join('\n');
@@ -214,9 +214,12 @@ const DEVELOPER_NOTES = [
214
214
  'fall into the ungrouped bucket, which renders above all groups to prevent ambiguity about which group',
215
215
  'an item belongs to. Groups appear in the order their labels are first declared.',
216
216
  '',
217
- '**The type-bar colour is semantic, not decorative.** `eventType="primary"` renders the brand-green bar',
218
- '(`--widget-calendar-type-primary-color`), `"secondary"` renders neutral grey',
219
- '(`--widget-calendar-type-secondary-color`). Choose deliberately — don\'t treat it as a purely visual toggle.',
217
+ '**`eventType` selects one of eight type-bar colours.** The accepted values are `primary` (the default),',
218
+ '`secondary`, `destructive`, `caution`, `info`, `warning`, `green`, and `purple`, matching the eight index',
219
+ 'colours in the design. The colour is chosen by the school per event and carries no meaning of its own —',
220
+ '`WidgetCalendar` attaches no semantics to the value, so the same colour can represent different event',
221
+ 'categories in different schools. Pass the value the data source supplies rather than inferring one from',
222
+ 'the event content.',
220
223
  '',
221
224
  '**No loading, disabled, or error state exists on this component.** Handle those states in the consumer',
222
225
  '(e.g. render a Skeleton in place of `WidgetCalendar` while data loads). `isLoading` and `loadingLabel` are',
@@ -688,6 +691,80 @@ export const SecondaryEventType: Story = withDescription(
688
691
  'Isolates `eventType="secondary"` — the neutral grey type bar, used for lower-priority or generic events.',
689
692
  );
690
693
 
694
+ export const AllEventTypes: Story = withDescription(
695
+ {
696
+ args: {
697
+ heading: 'Calendar',
698
+ items: [
699
+ {
700
+ id: 'type-primary',
701
+ eventType: 'primary',
702
+ eventPrimaryTerm: 'Class',
703
+ eventPrimaryDefinition: 'Year 10: 10G',
704
+ ...classEventTime,
705
+ },
706
+ {
707
+ id: 'type-secondary',
708
+ eventType: 'secondary',
709
+ eventPrimaryTerm: 'Class',
710
+ eventPrimaryDefinition: 'Year 8: 8B',
711
+ ...classEventTime,
712
+ },
713
+ {
714
+ id: 'type-destructive',
715
+ eventType: 'destructive',
716
+ eventPrimaryTerm: 'Exam',
717
+ eventPrimaryDefinition: 'GCSE Maths: Paper 1',
718
+ ...classEventTime,
719
+ },
720
+ {
721
+ id: 'type-caution',
722
+ eventType: 'caution',
723
+ eventPrimaryTerm: 'Trip',
724
+ eventPrimaryDefinition: 'Geography field trip',
725
+ ...classEventTime,
726
+ },
727
+ {
728
+ id: 'type-info',
729
+ eventType: 'info',
730
+ eventPrimaryTerm: 'Meeting',
731
+ eventPrimaryDefinition: 'Parents\' evening',
732
+ ...classEventTime,
733
+ },
734
+ {
735
+ id: 'type-warning',
736
+ eventType: 'warning',
737
+ eventPrimaryTerm: 'Deadline',
738
+ eventPrimaryDefinition: 'Reports due to head of year',
739
+ ...classEventTime,
740
+ },
741
+ {
742
+ id: 'type-green',
743
+ eventType: 'green',
744
+ eventPrimaryTerm: 'Club',
745
+ eventPrimaryDefinition: 'Football: Year 9',
746
+ ...classEventTime,
747
+ },
748
+ {
749
+ id: 'type-purple',
750
+ eventType: 'purple',
751
+ eventPrimaryTerm: 'Assembly',
752
+ eventPrimaryDefinition: 'Whole-school assembly',
753
+ ...classEventTime,
754
+ },
755
+ ],
756
+ onItemClick: fn(),
757
+ },
758
+ render: args => <WidgetCalendar {...args} />,
759
+ parameters: { controls: { disable: true } },
760
+ },
761
+ [
762
+ 'All eight `eventType` values side by side, so the full set of type-bar colours can be compared in one view.',
763
+ 'The colour is chosen by the school per event — `WidgetCalendar` attaches no semantics to any of the eight',
764
+ 'values. See the Developer notes above for the full list.',
765
+ ].join(' '),
766
+ );
767
+
691
768
  export const NonInteractiveItems: Story = withDescription(
692
769
  {
693
770
  args: {
@@ -60,6 +60,54 @@ describe('WidgetCalendar', () => {
60
60
  expect(container.querySelector('.ds-widget-calendar__item--secondary')).toBeInTheDocument();
61
61
  });
62
62
 
63
+ const eventTypes: WidgetCalendar.EventType[] = [
64
+ 'primary',
65
+ 'secondary',
66
+ 'destructive',
67
+ 'caution',
68
+ 'info',
69
+ 'warning',
70
+ 'green',
71
+ 'purple',
72
+ ];
73
+
74
+ test.each(eventTypes)('applies the %s modifier class and no other event type class', (eventType) => {
75
+ const { container } = render(
76
+ <WidgetCalendar
77
+ heading="Calendar"
78
+ items={[{ ...mockItems[0]!, eventType }]}
79
+ />,
80
+ );
81
+
82
+ const item = container.querySelector('.ds-widget-calendar__item');
83
+
84
+ expect(item).toHaveClass(`ds-widget-calendar__item--${eventType}`);
85
+
86
+ eventTypes
87
+ .filter(otherType => otherType !== eventType)
88
+ .forEach((otherType) => {
89
+ expect(item).not.toHaveClass(`ds-widget-calendar__item--${otherType}`);
90
+ });
91
+ });
92
+
93
+ test('defaults to the primary modifier class when eventType is omitted', () => {
94
+ const { container } = render(
95
+ <WidgetCalendar
96
+ heading="Calendar"
97
+ items={[{
98
+ id: 'no-type',
99
+ eventPrimaryTerm: 'Class',
100
+ eventPrimaryDefinition: 'Year 10: 10G',
101
+ eventSecondaryTerm: 'Time and location',
102
+ eventDateTime: '2026-06-08T08:45/2026-06-08T09:00',
103
+ eventTimeLabel: '08:45 – 09:00',
104
+ }]}
105
+ />,
106
+ );
107
+
108
+ expect(container.querySelector('.ds-widget-calendar__item')).toHaveClass('ds-widget-calendar__item--primary');
109
+ });
110
+
63
111
  test('calls onItemClick with item id', async () => {
64
112
  const user = userEvent.setup();
65
113
  const onItemClick = vi.fn();
@@ -3,7 +3,15 @@ import { Icon } from 'Components/icon/Icon';
3
3
  import { TooltipWrapper } from 'Components/tooltip/TooltipWrapper';
4
4
  import { ENTER_KEY, SPACE_KEY } from 'Utils/keyboardConstants';
5
5
 
6
- export type WidgetCalendarEventType = 'primary' | 'secondary';
6
+ export type WidgetCalendarEventType
7
+ = 'primary'
8
+ | 'secondary'
9
+ | 'destructive'
10
+ | 'caution'
11
+ | 'info'
12
+ | 'warning'
13
+ | 'green'
14
+ | 'purple';
7
15
 
8
16
  export type WidgetCalendarItemProps = {
9
17
  id: string;
@@ -68,9 +76,7 @@ export const WidgetCalendarItem = (props: WidgetCalendarItemProps): React.JSX.El
68
76
  <li
69
77
  className={classNames(
70
78
  'ds-widget-calendar__item',
71
- eventType === 'secondary'
72
- ? 'ds-widget-calendar__item--secondary'
73
- : 'ds-widget-calendar__item--primary',
79
+ `ds-widget-calendar__item--${eventType}`,
74
80
  isInteractive && 'ds-widget-calendar__item--interactive',
75
81
  className,
76
82
  )}
@@ -75,6 +75,30 @@
75
75
  background-color: var(--widget-calendar-type-secondary-color);
76
76
  }
77
77
 
78
+ .ds-widget-calendar__item--destructive .ds-widget-calendar__content::before {
79
+ background-color: var(--widget-calendar-type-destructive-color);
80
+ }
81
+
82
+ .ds-widget-calendar__item--caution .ds-widget-calendar__content::before {
83
+ background-color: var(--widget-calendar-type-caution-color);
84
+ }
85
+
86
+ .ds-widget-calendar__item--info .ds-widget-calendar__content::before {
87
+ background-color: var(--widget-calendar-type-info-color);
88
+ }
89
+
90
+ .ds-widget-calendar__item--warning .ds-widget-calendar__content::before {
91
+ background-color: var(--widget-calendar-type-warning-color);
92
+ }
93
+
94
+ .ds-widget-calendar__item--green .ds-widget-calendar__content::before {
95
+ background-color: var(--widget-calendar-type-green-color);
96
+ }
97
+
98
+ .ds-widget-calendar__item--purple .ds-widget-calendar__content::before {
99
+ background-color: var(--widget-calendar-type-purple-color);
100
+ }
101
+
78
102
  .ds-widget-calendar__details {
79
103
  margin: 0;
80
104
  }
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export {
28
28
  } from 'Components/filterBar/FilterBar';
29
29
  export { Fieldset } from 'Components/formField/fieldset/Fieldset';
30
30
  export { FormField } from 'Components/formField/FormField';
31
+ export type { CustomInputProps, CustomInputRenderProps, FormFieldSharedProps } from 'Components/formField/FormField';
31
32
  export { CheckboxGroup } from 'Components/formField/inputs/checkbox/CheckboxGroup';
32
33
  export { CheckboxInput } from 'Components/formField/inputs/checkbox/CheckboxInput';
33
34
  export { ColourPickerDropdown } from 'Components/formField/inputs/colourPickerDropdown/ColourPickerDropdown';
package/src/tokens.scss CHANGED
@@ -841,6 +841,12 @@
841
841
  --widget-calendar-type-bar-radius: var(--border-radius-xsmall);
842
842
  --widget-calendar-type-primary-color: var(--color-brand-500);
843
843
  --widget-calendar-type-secondary-color: var(--color-grey-300);
844
+ --widget-calendar-type-destructive-color: var(--color-semantic-destructive-500);
845
+ --widget-calendar-type-caution-color: var(--color-semantic-caution-500);
846
+ --widget-calendar-type-info-color: var(--color-semantic-info-500);
847
+ --widget-calendar-type-warning-color: var(--color-semantic-warning-500);
848
+ --widget-calendar-type-green-color: var(--color-ramp-gradient-sequential-500);
849
+ --widget-calendar-type-purple-color: var(--color-extended-colours-purple-500);
844
850
  --widget-calendar-color-title: var(--color-grey-900);
845
851
  --widget-list-row-hover-hover-bg: var(--color-grey-050);
846
852
  --widget-list-row-hover-color-icon-arrow: var(--color-grey-900);