@arbor-education/design-system.components 0.1.5 → 0.2.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.
Files changed (174) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/components/formField/fieldset/Fieldset.d.ts +6 -0
  4. package/dist/components/formField/fieldset/Fieldset.d.ts.map +1 -0
  5. package/dist/components/formField/fieldset/Fieldset.js +7 -0
  6. package/dist/components/formField/fieldset/Fieldset.js.map +1 -0
  7. package/dist/components/formField/fieldset/Fieldset.stories.d.ts +28 -0
  8. package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -0
  9. package/dist/components/formField/fieldset/Fieldset.stories.js +51 -0
  10. package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -0
  11. package/dist/components/formField/fieldset/Fieldset.test.d.ts +2 -0
  12. package/dist/components/formField/fieldset/Fieldset.test.d.ts.map +1 -0
  13. package/dist/components/formField/fieldset/Fieldset.test.js +62 -0
  14. package/dist/components/formField/fieldset/Fieldset.test.js.map +1 -0
  15. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +8 -0
  16. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -0
  17. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js +8 -0
  18. package/dist/components/formField/inputs/checkbox/CheckboxGroup.js.map +1 -0
  19. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts +2 -0
  20. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.d.ts.map +1 -0
  21. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js +86 -0
  22. package/dist/components/formField/inputs/checkbox/CheckboxGroup.test.js.map +1 -0
  23. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +3 -1
  24. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
  25. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +7 -0
  26. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
  27. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +11 -0
  28. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -0
  29. package/dist/components/formField/inputs/radio/RadioButtonGroup.js +8 -0
  30. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -0
  31. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts +2 -0
  32. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.d.ts.map +1 -0
  33. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js +86 -0
  34. package/dist/components/formField/inputs/radio/RadioButtonGroup.test.js.map +1 -0
  35. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  36. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +8 -2
  37. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  38. package/dist/components/modal/Modal.d.ts +25 -0
  39. package/dist/components/modal/Modal.d.ts.map +1 -0
  40. package/dist/components/modal/Modal.js +22 -0
  41. package/dist/components/modal/Modal.js.map +1 -0
  42. package/dist/components/modal/Modal.stories.d.ts +13 -0
  43. package/dist/components/modal/Modal.stories.d.ts.map +1 -0
  44. package/dist/components/modal/Modal.stories.js +23 -0
  45. package/dist/components/modal/Modal.stories.js.map +1 -0
  46. package/dist/components/modal/Modal.test.d.ts +2 -0
  47. package/dist/components/modal/Modal.test.d.ts.map +1 -0
  48. package/dist/components/modal/Modal.test.js +131 -0
  49. package/dist/components/modal/Modal.test.js.map +1 -0
  50. package/dist/components/modal/ModalBody.d.ts +7 -0
  51. package/dist/components/modal/ModalBody.d.ts.map +1 -0
  52. package/dist/components/modal/ModalBody.js +7 -0
  53. package/dist/components/modal/ModalBody.js.map +1 -0
  54. package/dist/components/modal/ModalCloseButton.d.ts +3 -0
  55. package/dist/components/modal/ModalCloseButton.d.ts.map +1 -0
  56. package/dist/components/modal/ModalCloseButton.js +25 -0
  57. package/dist/components/modal/ModalCloseButton.js.map +1 -0
  58. package/dist/components/modal/ModalFooter.d.ts +7 -0
  59. package/dist/components/modal/ModalFooter.d.ts.map +1 -0
  60. package/dist/components/modal/ModalFooter.js +7 -0
  61. package/dist/components/modal/ModalFooter.js.map +1 -0
  62. package/dist/components/modal/ModalHeader.d.ts +7 -0
  63. package/dist/components/modal/ModalHeader.d.ts.map +1 -0
  64. package/dist/components/modal/ModalHeader.js +8 -0
  65. package/dist/components/modal/ModalHeader.js.map +1 -0
  66. package/dist/components/modal/ModalTitle.d.ts +3 -0
  67. package/dist/components/modal/ModalTitle.d.ts.map +1 -0
  68. package/dist/components/modal/ModalTitle.js +8 -0
  69. package/dist/components/modal/ModalTitle.js.map +1 -0
  70. package/dist/components/modal/modalManager/ModalManager.d.ts +7 -0
  71. package/dist/components/modal/modalManager/ModalManager.d.ts.map +1 -0
  72. package/dist/components/modal/modalManager/ModalManager.js +22 -0
  73. package/dist/components/modal/modalManager/ModalManager.js.map +1 -0
  74. package/dist/components/modal/modalManager/ModalManager.stories.d.ts +13 -0
  75. package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -0
  76. package/dist/components/modal/modalManager/ModalManager.stories.js +110 -0
  77. package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -0
  78. package/dist/components/modal/modalManager/ModalManager.test.d.ts +2 -0
  79. package/dist/components/modal/modalManager/ModalManager.test.d.ts.map +1 -0
  80. package/dist/components/modal/modalManager/ModalManager.test.js +191 -0
  81. package/dist/components/modal/modalManager/ModalManager.test.js.map +1 -0
  82. package/dist/components/separator/Separator.d.ts +7 -0
  83. package/dist/components/separator/Separator.d.ts.map +1 -0
  84. package/dist/components/separator/Separator.js +8 -0
  85. package/dist/components/separator/Separator.js.map +1 -0
  86. package/dist/components/separator/Separator.stories.d.ts +10 -0
  87. package/dist/components/separator/Separator.stories.d.ts.map +1 -0
  88. package/dist/components/separator/Separator.stories.js +12 -0
  89. package/dist/components/separator/Separator.stories.js.map +1 -0
  90. package/dist/components/separator/Separator.test.d.ts +2 -0
  91. package/dist/components/separator/Separator.test.d.ts.map +1 -0
  92. package/dist/components/separator/Separator.test.js +10 -0
  93. package/dist/components/separator/Separator.test.js.map +1 -0
  94. package/dist/components/table/Table.d.ts +20 -0
  95. package/dist/components/table/Table.d.ts.map +1 -1
  96. package/dist/components/table/Table.js +45 -7
  97. package/dist/components/table/Table.js.map +1 -1
  98. package/dist/components/table/Table.stories.d.ts.map +1 -1
  99. package/dist/components/table/Table.stories.js +14 -1
  100. package/dist/components/table/Table.stories.js.map +1 -1
  101. package/dist/components/table/Table.test.js +315 -2
  102. package/dist/components/table/Table.test.js.map +1 -1
  103. package/dist/components/table/pagination/TableSettingsDropdown.d.ts +2 -0
  104. package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +1 -0
  105. package/dist/components/table/pagination/TableSettingsDropdown.js +43 -0
  106. package/dist/components/table/pagination/TableSettingsDropdown.js.map +1 -0
  107. package/dist/components/table/useTableSettings.d.ts +22 -0
  108. package/dist/components/table/useTableSettings.d.ts.map +1 -0
  109. package/dist/components/table/useTableSettings.js +36 -0
  110. package/dist/components/table/useTableSettings.js.map +1 -0
  111. package/dist/index.css +102 -1
  112. package/dist/index.css.map +1 -1
  113. package/dist/index.d.ts +4 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +4 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/utils/Constants.d.ts +5 -0
  118. package/dist/utils/Constants.d.ts.map +1 -1
  119. package/dist/utils/Constants.js +5 -0
  120. package/dist/utils/Constants.js.map +1 -1
  121. package/dist/utils/ModalUtils.d.ts +7 -0
  122. package/dist/utils/ModalUtils.d.ts.map +1 -0
  123. package/dist/utils/ModalUtils.js +14 -0
  124. package/dist/utils/ModalUtils.js.map +1 -0
  125. package/dist/utils/hooks/useComponentDidUpdate.d.ts +7 -0
  126. package/dist/utils/hooks/useComponentDidUpdate.d.ts.map +1 -0
  127. package/dist/utils/hooks/useComponentDidUpdate.js +18 -0
  128. package/dist/utils/hooks/useComponentDidUpdate.js.map +1 -0
  129. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts +2 -0
  130. package/dist/utils/hooks/useComponentDidUpdate.test.d.ts.map +1 -0
  131. package/dist/utils/hooks/useComponentDidUpdate.test.js +69 -0
  132. package/dist/utils/hooks/useComponentDidUpdate.test.js.map +1 -0
  133. package/package.json +1 -1
  134. package/src/components/formField/fieldset/Fieldset.stories.tsx +89 -0
  135. package/src/components/formField/fieldset/Fieldset.test.tsx +85 -0
  136. package/src/components/formField/fieldset/Fieldset.tsx +17 -0
  137. package/src/components/formField/fieldset/fieldset.scss +19 -0
  138. package/src/components/formField/inputs/checkbox/CheckboxGroup.test.tsx +127 -0
  139. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +17 -0
  140. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +12 -1
  141. package/src/components/formField/inputs/radio/RadioButtonGroup.test.tsx +190 -0
  142. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +22 -0
  143. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +16 -7
  144. package/src/components/formField/label/label.scss +1 -1
  145. package/src/components/modal/Modal.stories.tsx +37 -0
  146. package/src/components/modal/Modal.test.tsx +244 -0
  147. package/src/components/modal/Modal.tsx +65 -0
  148. package/src/components/modal/ModalBody.tsx +16 -0
  149. package/src/components/modal/ModalCloseButton.tsx +39 -0
  150. package/src/components/modal/ModalFooter.tsx +14 -0
  151. package/src/components/modal/ModalHeader.tsx +20 -0
  152. package/src/components/modal/ModalTitle.tsx +12 -0
  153. package/src/components/modal/modal.scss +74 -0
  154. package/src/components/modal/modalManager/ModalManager.stories.tsx +497 -0
  155. package/src/components/modal/modalManager/ModalManager.test.tsx +255 -0
  156. package/src/components/modal/modalManager/ModalManager.tsx +40 -0
  157. package/src/components/modal/modalManager/modalManager.scss +4 -0
  158. package/src/components/separator/Separator.stories.tsx +15 -0
  159. package/src/components/separator/Separator.test.tsx +10 -0
  160. package/src/components/separator/Separator.tsx +15 -0
  161. package/src/components/separator/separator.scss +6 -0
  162. package/src/components/table/Table.stories.tsx +14 -1
  163. package/src/components/table/Table.test.tsx +553 -1
  164. package/src/components/table/Table.tsx +80 -24
  165. package/src/components/table/pagination/TableSettingsDropdown.tsx +90 -0
  166. package/src/components/table/table.scss +8 -0
  167. package/src/components/table/useTableSettings.ts +59 -0
  168. package/src/index.scss +4 -0
  169. package/src/index.ts +4 -0
  170. package/src/tokens.scss +1 -0
  171. package/src/utils/Constants.ts +6 -0
  172. package/src/utils/ModalUtils.ts +17 -0
  173. package/src/utils/hooks/useComponentDidUpdate.test.ts +107 -0
  174. package/src/utils/hooks/useComponentDidUpdate.ts +19 -0
@@ -0,0 +1,127 @@
1
+ import { expect, test, describe, vi } from 'vitest';
2
+ import { CheckboxGroup } from './CheckboxGroup';
3
+ import { render, screen, fireEvent } from '@testing-library/react';
4
+ import '@testing-library/jest-dom/vitest';
5
+
6
+ describe('CheckboxGroup component', () => {
7
+ const mockOptions = [
8
+ { id: 'option1', label: 'Option 1', checked: false, onChange: vi.fn() },
9
+ { id: 'option2', label: 'Option 2', checked: true, onChange: vi.fn() },
10
+ { id: 'option3', label: 'Option 3', checked: false, onChange: vi.fn() },
11
+ ];
12
+
13
+ test('renders fieldset with legend', () => {
14
+ render(
15
+ <CheckboxGroup legend="Choose options" options={mockOptions} />,
16
+ );
17
+ expect(screen.getByText('Choose options')).toBeInTheDocument();
18
+ });
19
+
20
+ test('renders all checkbox options', () => {
21
+ render(
22
+ <CheckboxGroup legend="Choose options" options={mockOptions} />,
23
+ );
24
+ expect(screen.getByRole('checkbox', { name: 'Option 1' })).toBeInTheDocument();
25
+ expect(screen.getByRole('checkbox', { name: 'Option 2' })).toBeInTheDocument();
26
+ expect(screen.getByRole('checkbox', { name: 'Option 3' })).toBeInTheDocument();
27
+ });
28
+
29
+ test('applies checked state to checkboxes', () => {
30
+ render(
31
+ <CheckboxGroup legend="Choose options" options={mockOptions} />,
32
+ );
33
+ const option1 = screen.getByRole('checkbox', { name: 'Option 1' }) as HTMLInputElement;
34
+ const option2 = screen.getByRole('checkbox', { name: 'Option 2' }) as HTMLInputElement;
35
+ const option3 = screen.getByRole('checkbox', { name: 'Option 3' }) as HTMLInputElement;
36
+
37
+ expect(option1.checked).toBe(false);
38
+ expect(option2.checked).toBe(true);
39
+ expect(option3.checked).toBe(false);
40
+ });
41
+
42
+ test('calls onChange when a checkbox is clicked', () => {
43
+ const handleChange1 = vi.fn();
44
+ const options = [
45
+ { id: 'option1', label: 'Option 1', checked: false, onChange: handleChange1, value: 'value1' },
46
+ ];
47
+
48
+ render(
49
+ <CheckboxGroup legend="Choose options" options={options} />,
50
+ );
51
+
52
+ const checkbox = screen.getByRole('checkbox', { name: 'Option 1' });
53
+ fireEvent.click(checkbox);
54
+ expect(handleChange1).toHaveBeenCalledExactlyOnceWith(
55
+ expect.objectContaining({
56
+ target: checkbox,
57
+ }),
58
+ );
59
+ });
60
+
61
+ test('renders empty group when options array is empty', () => {
62
+ render(
63
+ <CheckboxGroup legend="Choose options" options={[]} />,
64
+ );
65
+ expect(screen.getByText('Choose options')).toBeInTheDocument();
66
+ const checkboxes = screen.queryAllByRole('checkbox');
67
+ expect(checkboxes).toHaveLength(0);
68
+ });
69
+
70
+ test('passes through fieldset props', () => {
71
+ render(
72
+ <CheckboxGroup
73
+ legend="Choose options"
74
+ options={mockOptions}
75
+ className="custom-class"
76
+ data-testid="test-fieldset"
77
+ />,
78
+ );
79
+ const fieldset = screen.getByTestId('test-fieldset');
80
+ expect(fieldset).toBeInTheDocument();
81
+ expect(fieldset).toHaveClass('custom-class');
82
+ });
83
+
84
+ test('renders options without labels', () => {
85
+ const optionsWithoutLabels = [
86
+ { id: 'option1', checked: false, onChange: vi.fn() },
87
+ { id: 'option2', checked: false, onChange: vi.fn() },
88
+ ];
89
+ render(
90
+ <CheckboxGroup legend="Choose options" options={optionsWithoutLabels} />,
91
+ );
92
+ const checkboxes = screen.getAllByRole('checkbox');
93
+ expect(checkboxes).toHaveLength(2);
94
+ });
95
+
96
+ test('supports disabled fieldset', () => {
97
+ render(
98
+ <CheckboxGroup
99
+ legend="Choose options"
100
+ options={mockOptions}
101
+ disabled
102
+ />,
103
+ );
104
+ const fieldset = screen.getByText('Choose options').closest('fieldset');
105
+ expect(fieldset).toBeDisabled();
106
+ });
107
+
108
+ test('passes additional HTML attributes to checkboxes', () => {
109
+ const optionsWithAttributes = [
110
+ {
111
+ 'id': 'option1',
112
+ 'label': 'Option 1',
113
+ 'checked': false,
114
+ 'onChange': vi.fn(),
115
+ 'data-custom': 'value1',
116
+ 'aria-describedby': 'description1',
117
+ },
118
+ ];
119
+ render(
120
+ <CheckboxGroup legend="Choose options" options={optionsWithAttributes} />,
121
+ );
122
+ const checkbox = screen.getByRole('checkbox', { name: 'Option 1' });
123
+
124
+ expect(checkbox).toHaveAttribute('data-custom', 'value1');
125
+ expect(checkbox).toHaveAttribute('aria-describedby', 'description1');
126
+ });
127
+ });
@@ -0,0 +1,17 @@
1
+ import { Fieldset, type FieldsetProps } from 'Components/formField/fieldset/Fieldset';
2
+ import { CheckboxInput, type CheckboxInputProps } from './CheckboxInput';
3
+
4
+ type CheckboxGroupProps = {
5
+ options: CheckboxInputProps[];
6
+ } & FieldsetProps;
7
+
8
+ export const CheckboxGroup = (props: CheckboxGroupProps) => {
9
+ const { options, ...rest } = props;
10
+ return (
11
+ <Fieldset {...rest}>
12
+ {options.map((option: CheckboxInputProps) => (
13
+ <CheckboxInput key={option.id} {...option} />
14
+ ))}
15
+ </Fieldset>
16
+ );
17
+ };
@@ -1,11 +1,14 @@
1
- import type { Meta } from '@storybook/react-vite';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { CheckboxInput } from './CheckboxInput';
3
+ import { CheckboxGroup } from './CheckboxGroup';
3
4
 
4
5
  const meta: Meta<typeof CheckboxInput> = {
5
6
  title: 'Components/FormField/Inputs/Checkbox',
6
7
  component: CheckboxInput,
7
8
  };
8
9
 
10
+ type Story = StoryObj<typeof meta>;
11
+
9
12
  export const Default = {
10
13
  parameters: {
11
14
  layout: 'centered',
@@ -19,4 +22,12 @@ export const Default = {
19
22
  },
20
23
  };
21
24
 
25
+ export const CheckboxGroupStory: Story = {
26
+ render: () => {
27
+ return (
28
+ <CheckboxGroup legend="Checkbox group" options={[{ id: 'option1', label: 'Option 1' }, { id: 'option2', label: 'Option 2' }, { id: 'option3', label: 'Option 3' }]} />
29
+ );
30
+ },
31
+ };
32
+
22
33
  export default meta;
@@ -0,0 +1,190 @@
1
+ import { expect, test, describe, vi } from 'vitest';
2
+ import { RadioButtonGroup } from './RadioButtonGroup';
3
+ import { render, screen, fireEvent } from '@testing-library/react';
4
+ import '@testing-library/jest-dom/vitest';
5
+
6
+ describe('RadioButtonGroup component', () => {
7
+ const mockOptions = [
8
+ { id: 'option1', value: 'value1', label: 'Option 1' },
9
+ { id: 'option2', value: 'value2', label: 'Option 2' },
10
+ { id: 'option3', value: 'value3', label: 'Option 3' },
11
+ ];
12
+
13
+ test('renders fieldset with legend', () => {
14
+ render(
15
+ <RadioButtonGroup
16
+ legend="Choose an option"
17
+ name="test-group"
18
+ options={mockOptions}
19
+ checkedValue="value1"
20
+ onChange={vi.fn()}
21
+ />,
22
+ );
23
+ expect(screen.getByText('Choose an option')).toBeInTheDocument();
24
+ });
25
+
26
+ test('renders all radio button options', () => {
27
+ render(
28
+ <RadioButtonGroup
29
+ legend="Choose an option"
30
+ name="test-group"
31
+ options={mockOptions}
32
+ checkedValue="value1"
33
+ onChange={vi.fn()}
34
+ />,
35
+ );
36
+ expect(screen.getByRole('radio', { name: 'Option 1' })).toBeInTheDocument();
37
+ expect(screen.getByRole('radio', { name: 'Option 2' })).toBeInTheDocument();
38
+ expect(screen.getByRole('radio', { name: 'Option 3' })).toBeInTheDocument();
39
+ });
40
+
41
+ test('applies the same name attribute to all radio buttons', () => {
42
+ render(
43
+ <RadioButtonGroup
44
+ legend="Choose an option"
45
+ name="test-group"
46
+ options={mockOptions}
47
+ checkedValue="value1"
48
+ onChange={vi.fn()}
49
+ />,
50
+ );
51
+ const radios = screen.getAllByRole('radio');
52
+ radios.forEach((radio) => {
53
+ expect(radio).toHaveAttribute('name', 'test-group');
54
+ });
55
+ });
56
+
57
+ test('initially checks the correct radio button based on checkedValue', () => {
58
+ render(
59
+ <RadioButtonGroup
60
+ legend="Choose an option"
61
+ name="test-group"
62
+ options={mockOptions}
63
+ checkedValue="value2"
64
+ onChange={vi.fn()}
65
+ />,
66
+ );
67
+ const option1 = screen.getByRole('radio', { name: 'Option 1' }) as HTMLInputElement;
68
+ const option2 = screen.getByRole('radio', { name: 'Option 2' }) as HTMLInputElement;
69
+ const option3 = screen.getByRole('radio', { name: 'Option 3' }) as HTMLInputElement;
70
+
71
+ expect(option1.checked).toBe(false);
72
+ expect(option2.checked).toBe(true);
73
+ expect(option3.checked).toBe(false);
74
+ });
75
+
76
+ test('calls onChange when a radio button is clicked', () => {
77
+ const handleChange = vi.fn();
78
+ render(
79
+ <RadioButtonGroup
80
+ legend="Choose an option"
81
+ name="test-group"
82
+ options={mockOptions}
83
+ checkedValue="value1"
84
+ onChange={handleChange}
85
+ />,
86
+ );
87
+ const option2 = screen.getByRole('radio', { name: 'Option 2' });
88
+ fireEvent.click(option2);
89
+ expect(handleChange).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({
90
+ target: expect.objectContaining({
91
+ value: 'value2',
92
+ }),
93
+ }));
94
+ });
95
+
96
+ test('renders with single option', () => {
97
+ const singleOption = [{ id: 'option1', value: 'value1', label: 'Only Option' }];
98
+ render(
99
+ <RadioButtonGroup
100
+ legend="Choose an option"
101
+ name="test-group"
102
+ options={singleOption}
103
+ checkedValue="value1"
104
+ onChange={vi.fn()}
105
+ />,
106
+ );
107
+ const radios = screen.getAllByRole('radio');
108
+ expect(radios).toHaveLength(1);
109
+ expect(screen.getByRole('radio', { name: 'Only Option' })).toBeInTheDocument();
110
+ });
111
+
112
+ test('renders empty group when options array is empty', () => {
113
+ render(
114
+ <RadioButtonGroup
115
+ legend="Choose an option"
116
+ name="test-group"
117
+ options={[]}
118
+ checkedValue=""
119
+ onChange={vi.fn()}
120
+ />,
121
+ );
122
+ expect(screen.getByText('Choose an option')).toBeInTheDocument();
123
+ const radios = screen.queryAllByRole('radio');
124
+ expect(radios).toHaveLength(0);
125
+ });
126
+
127
+ test('passes through fieldset props', () => {
128
+ render(
129
+ <RadioButtonGroup
130
+ legend="Choose an option"
131
+ name="test-group"
132
+ options={mockOptions}
133
+ checkedValue="value1"
134
+ onChange={vi.fn()}
135
+ className="custom-class"
136
+ data-testid="test-fieldset"
137
+ />,
138
+ );
139
+ const fieldset = screen.getByTestId('test-fieldset');
140
+ expect(fieldset).toBeInTheDocument();
141
+ expect(fieldset).toHaveClass('custom-class');
142
+ });
143
+
144
+ test('updates checked state when checkedValue changes', () => {
145
+ const { rerender } = render(
146
+ <RadioButtonGroup
147
+ legend="Choose an option"
148
+ name="test-group"
149
+ options={mockOptions}
150
+ checkedValue="value1"
151
+ onChange={vi.fn()}
152
+ />,
153
+ );
154
+
155
+ let option1 = screen.getByRole('radio', { name: 'Option 1' }) as HTMLInputElement;
156
+ let option2 = screen.getByRole('radio', { name: 'Option 2' }) as HTMLInputElement;
157
+ expect(option1.checked).toBe(true);
158
+ expect(option2.checked).toBe(false);
159
+
160
+ rerender(
161
+ <RadioButtonGroup
162
+ legend="Choose an option"
163
+ name="test-group"
164
+ options={mockOptions}
165
+ checkedValue="value2"
166
+ onChange={vi.fn()}
167
+ />,
168
+ );
169
+
170
+ option1 = screen.getByRole('radio', { name: 'Option 1' }) as HTMLInputElement;
171
+ option2 = screen.getByRole('radio', { name: 'Option 2' }) as HTMLInputElement;
172
+ expect(option1.checked).toBe(false);
173
+ expect(option2.checked).toBe(true);
174
+ });
175
+
176
+ test('supports disabled fieldset', () => {
177
+ render(
178
+ <RadioButtonGroup
179
+ legend="Choose an option"
180
+ name="test-group"
181
+ options={mockOptions}
182
+ checkedValue="value1"
183
+ onChange={vi.fn()}
184
+ disabled
185
+ />,
186
+ );
187
+ const fieldset = screen.getByText('Choose an option').closest('fieldset');
188
+ expect(fieldset).toBeDisabled();
189
+ });
190
+ });
@@ -0,0 +1,22 @@
1
+ import type { ChangeEvent } from 'react';
2
+ import { RadioButtonInput, type RadioButtonInputProps } from './RadioButtonInput';
3
+ import { Fieldset, type FieldsetProps } from '../../fieldset/Fieldset';
4
+
5
+ export type RadioButtonGroupProps = {
6
+ name: string;
7
+ options: RadioButtonInputProps[];
8
+ checkedValue: string;
9
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void;
10
+ } & Omit<FieldsetProps, 'onChange'>;
11
+
12
+ export const RadioButtonGroup = (props: RadioButtonGroupProps) => {
13
+ const { legend, name, options, checkedValue, onChange, ...rest } = props;
14
+
15
+ return (
16
+ <Fieldset legend={legend} {...rest}>
17
+ {options.map((option: RadioButtonInputProps) => (
18
+ <RadioButtonInput key={option.id} name={name} value={option.value} label={option.label} checked={checkedValue === option.value} onChange={onChange} />
19
+ ))}
20
+ </Fieldset>
21
+ );
22
+ };
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { fn } from 'storybook/test';
3
3
 
4
4
  import { RadioButtonInput } from './RadioButtonInput';
5
+ import { RadioButtonGroup } from './RadioButtonGroup';
6
+ import { useState } from 'react';
5
7
 
6
8
  const meta = {
7
9
  title: 'Components/FormField/Inputs/RadioButton',
@@ -87,11 +89,18 @@ export const DisabledChecked: Story = {
87
89
 
88
90
  // Radio button group example
89
91
  export const RadioGroup: Story = {
90
- render: () => (
91
- <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
92
- <RadioButtonInput name="group" value="option1" label="Option 1" />
93
- <RadioButtonInput name="group" value="option2" label="Option 2" checked />
94
- <RadioButtonInput name="group" value="option3" label="Option 3" />
95
- </div>
96
- ),
92
+ render: () => {
93
+ const options = [{ label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' }, { label: 'Option 3', value: 'option3' }];
94
+ const [checkedValue, setCheckedValue] = useState<string>(options[0]?.value ?? '');
95
+
96
+ return (
97
+ <RadioButtonGroup
98
+ legend="Radio group"
99
+ name="group"
100
+ options={options}
101
+ checkedValue={checkedValue}
102
+ onChange={e => setCheckedValue(e.target.value)}
103
+ />
104
+ );
105
+ },
97
106
  };
@@ -1,7 +1,7 @@
1
1
  .ds-label {
2
2
  font-family: var(--font-family-standard);
3
3
  font-size: var(--font-size-2-13);
4
- font-weight: var(--font-weight-bold);
4
+ font-weight: var(--type-body-bold-weight);
5
5
  color: var(--form-field-label-color-text);
6
6
  line-height: 150%;
7
7
  margin-bottom: var(--form-field-spacing-vertical);
@@ -0,0 +1,37 @@
1
+ import type { Meta } from '@storybook/react-vite';
2
+ import { fn } from 'storybook/test';
3
+ import { Modal, type ModalProps } from './Modal';
4
+ import { Button } from 'Components/button/Button';
5
+ import { useState } from 'react';
6
+
7
+ const meta: Meta<typeof Modal> = {
8
+ title: 'Components/Modals/Modal',
9
+ component: Modal,
10
+ tags: ['autodocs'],
11
+ };
12
+
13
+ export const Default = {
14
+ args: {
15
+ open: false,
16
+ closeHandler: fn(),
17
+ title: 'Modal Title',
18
+ },
19
+ render: (args: ModalProps) => {
20
+ const [showModal, setShowModal] = useState(args.open);
21
+ return (
22
+ <>
23
+ <Button type="primary" onClick={() => setShowModal(true)}>Open Modal</Button>
24
+ <Modal {...args} open={showModal} closeHandler={() => setShowModal(false)}>
25
+ <Modal.Body>Cheddar and Pickle were Norwich terriers who lived next door. Cheddar, the older, was cautious and methodical; Pickle, the younger, was bold and impulsive. One morning, Pickle spotted a squirrel in the garden and gave chase, while Cheddar watched from the porch. When Pickle got stuck under the neighbor’s fence, Cheddar squeezed through a gap and nudged him free. From then on, they worked as a team: Pickle scouted, and Cheddar planned. They became inseparable, exploring the neighborhood together and always looking out for each other.</Modal.Body>
26
+ <Modal.Footer>
27
+ <Button variant="tertiary" onClick={() => setShowModal(false)}>Close</Button>
28
+ <Button variant="primary" onClick={() => setShowModal(false)}>Primary Action</Button>
29
+ </Modal.Footer>
30
+ </Modal>
31
+ </>
32
+
33
+ );
34
+ },
35
+ };
36
+
37
+ export default meta;