@scottish-government/designsystem-react 0.0.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 (111) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/workflows/release-package.yml +96 -0
  3. package/@types/common/ConditionalWrapper.d.ts +6 -0
  4. package/@types/common/HintText.d.ts +6 -0
  5. package/@types/common/Icon.d.ts +11 -0
  6. package/@types/common/ScreenReaderText.d.ts +4 -0
  7. package/@types/common/WrapperTag.d.ts +5 -0
  8. package/@types/components/Accordion.d.ts +15 -0
  9. package/@types/components/AspectBox.d.ts +5 -0
  10. package/@types/components/BackToTop.d.ts +5 -0
  11. package/@types/components/Breadcrumbs.d.ts +14 -0
  12. package/@types/components/Button.d.ts +17 -0
  13. package/@types/components/Checkbox.d.ts +13 -0
  14. package/@types/components/ConfirmationMessage.d.ts +7 -0
  15. package/@types/components/ContentsNav.d.ts +15 -0
  16. package/@types/components/DatePicker.d.ts +19 -0
  17. package/@types/components/Details.d.ts +6 -0
  18. package/@types/components/ErrorMessage.d.ts +6 -0
  19. package/@types/components/Metadata.d.ts +11 -0
  20. package/@types/components/NotificationBanner.d.ts +9 -0
  21. package/@types/components/NotificationPanel.d.ts +7 -0
  22. package/@types/components/PageHeader.d.ts +6 -0
  23. package/@types/components/PhaseBanner.d.ts +5 -0
  24. package/@types/components/Question.d.ts +11 -0
  25. package/@types/components/RadioButton.d.ts +15 -0
  26. package/@types/components/Select.d.ts +14 -0
  27. package/@types/components/SequentialNavigation.d.ts +14 -0
  28. package/@types/components/SideNavigation.d.ts +19 -0
  29. package/@types/components/SiteNavigation.d.ts +13 -0
  30. package/@types/components/SiteSearch.d.ts +14 -0
  31. package/@types/components/SkipLinks.d.ts +14 -0
  32. package/@types/components/Tag.d.ts +7 -0
  33. package/@types/components/TaskList.d.ts +21 -0
  34. package/@types/components/TextInput.d.ts +12 -0
  35. package/@types/components/Textarea.d.ts +4 -0
  36. package/@types/global.d.ts +1 -0
  37. package/@types/sgds.d.ts +35 -0
  38. package/package.json +36 -0
  39. package/src/common/conditional-wrapper.test.tsx +36 -0
  40. package/src/common/conditional-wrapper.tsx +9 -0
  41. package/src/common/hint-text.test.tsx +47 -0
  42. package/src/common/hint-text.tsx +21 -0
  43. package/src/common/icon.test.tsx +100 -0
  44. package/src/common/icon.tsx +28 -0
  45. package/src/common/screen-reader-text.test.tsx +31 -0
  46. package/src/common/screen-reader-text.tsx +17 -0
  47. package/src/common/wrapper-tag.test.tsx +42 -0
  48. package/src/common/wrapper-tag.tsx +15 -0
  49. package/src/components/accordion/accordion.test.tsx +212 -0
  50. package/src/components/accordion/accordion.tsx +108 -0
  51. package/src/components/aspect-box/aspect-box.test.tsx +81 -0
  52. package/src/components/aspect-box/aspect-box.tsx +57 -0
  53. package/src/components/back-to-top/back-to-top.test.tsx +45 -0
  54. package/src/components/back-to-top/back-to-top.tsx +33 -0
  55. package/src/components/breadcrumbs/breadcrumbs.test.tsx +77 -0
  56. package/src/components/breadcrumbs/breadcrumbs.tsx +53 -0
  57. package/src/components/button/button.test.tsx +125 -0
  58. package/src/components/button/button.tsx +48 -0
  59. package/src/components/checkbox/checkbox.test.tsx +180 -0
  60. package/src/components/checkbox/checkbox.tsx +107 -0
  61. package/src/components/confirmation-message/confirmation-message.test.tsx +46 -0
  62. package/src/components/confirmation-message/confirmation-message.tsx +32 -0
  63. package/src/components/contents-nav/contents-nav.test.tsx +136 -0
  64. package/src/components/contents-nav/contents-nav.tsx +54 -0
  65. package/src/components/date-picker/date-picker.test.tsx +209 -0
  66. package/src/components/date-picker/date-picker.tsx +129 -0
  67. package/src/components/details/details.test.tsx +38 -0
  68. package/src/components/details/details.tsx +25 -0
  69. package/src/components/error-message/error-message.test.tsx +40 -0
  70. package/src/components/error-message/error-message.tsx +23 -0
  71. package/src/components/inset-text/inset-text.test.tsx +33 -0
  72. package/src/components/inset-text/inset-text.tsx +19 -0
  73. package/src/components/notification-banner/notification-banner.test.tsx +93 -0
  74. package/src/components/notification-banner/notification-banner.tsx +70 -0
  75. package/src/components/notification-panel/notification-panel.test.tsx +77 -0
  76. package/src/components/notification-panel/notification-panel.tsx +31 -0
  77. package/src/components/page-header/page-header.test.tsx +48 -0
  78. package/src/components/page-header/page-header.tsx +22 -0
  79. package/src/components/page-metadata/page-metadata.test.tsx +56 -0
  80. package/src/components/page-metadata/page-metadata.tsx +39 -0
  81. package/src/components/phase-banner/phase-banner.test.tsx +67 -0
  82. package/src/components/phase-banner/phase-banner.tsx +27 -0
  83. package/src/components/question/question.test.tsx +69 -0
  84. package/src/components/question/question.tsx +33 -0
  85. package/src/components/radio-button/radio-button.test.tsx +190 -0
  86. package/src/components/radio-button/radio-button.tsx +88 -0
  87. package/src/components/select/select.test.tsx +208 -0
  88. package/src/components/select/select.tsx +86 -0
  89. package/src/components/sequential-navigation/sequential-navigation.test.tsx +67 -0
  90. package/src/components/sequential-navigation/sequential-navigation.tsx +55 -0
  91. package/src/components/side-navigation/side-navigation.test.tsx +156 -0
  92. package/src/components/side-navigation/side-navigation.tsx +85 -0
  93. package/src/components/site-navigation/site-navigation.test.tsx +63 -0
  94. package/src/components/site-navigation/site-navigation.tsx +40 -0
  95. package/src/components/site-search/site-search.test.tsx +153 -0
  96. package/src/components/site-search/site-search.tsx +97 -0
  97. package/src/components/skip-links/skip-links.test.tsx +84 -0
  98. package/src/components/skip-links/skip-links.tsx +39 -0
  99. package/src/components/tag/tag.test.tsx +45 -0
  100. package/src/components/tag/tag.tsx +23 -0
  101. package/src/components/task-list/task-list.test.tsx +409 -0
  102. package/src/components/task-list/task-list.tsx +132 -0
  103. package/src/components/text-input/text-input.test.tsx +307 -0
  104. package/src/components/text-input/text-input.tsx +98 -0
  105. package/src/components/textarea/textarea.test.tsx +212 -0
  106. package/src/components/textarea/textarea.tsx +82 -0
  107. package/src/components/warning-text/warning-text.test.tsx +40 -0
  108. package/src/components/warning-text/warning-text.tsx +21 -0
  109. package/tsconfig.json +45 -0
  110. package/vite.config.ts +12 -0
  111. package/vitest-setup.ts +13 -0
@@ -0,0 +1,56 @@
1
+ import { test, expect } from 'vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import Metadata, {MetadataItem} from './page-metadata';
4
+
5
+ const name = 'Directorate';
6
+ const value = 'Equality, Inclusion and Human Rights Directorate';
7
+
8
+ test('metadata renders correctly', () => {
9
+ render(
10
+ <Metadata>
11
+ <MetadataItem name={name}>
12
+ {value}
13
+ </MetadataItem>
14
+ </Metadata>
15
+ );
16
+
17
+ const metadata = document.querySelector('.ds_metadata');
18
+ const metadataItem = document.querySelector('.ds_metadata__item');
19
+ const metadataItemKey = screen.getByRole('term');
20
+ const metadataItemValue = screen.getByRole('definition');
21
+
22
+ expect(metadata).toBeInTheDocument();
23
+ expect(metadataItem).toBeInTheDocument();
24
+ expect(metadataItemKey).toHaveClass('ds_metadata__key');
25
+ expect(metadataItemKey.textContent).toEqual(name);
26
+ expect(metadataItemValue).toHaveClass('ds_metadata__value');
27
+ expect(metadataItemValue.textContent).toEqual(value);
28
+ });
29
+
30
+ test('inline metadata', () => {
31
+ render(
32
+ <Metadata inline>
33
+ <MetadataItem name={name}>
34
+ {value}
35
+ </MetadataItem>
36
+ </Metadata>
37
+ );
38
+
39
+ const metadata = document.querySelector('.ds_metadata');
40
+ expect(metadata).toHaveClass('ds_metadata--inline');
41
+ });
42
+
43
+ test('passing additional props', () => {
44
+ render(
45
+ <Metadata data-test="foo">
46
+ <MetadataItem data-test="bar" name="Last updated">
47
+ 21/04/2020
48
+ </MetadataItem>
49
+ </Metadata>
50
+ )
51
+
52
+ const metadata = document.querySelector('.ds_metadata');
53
+ const metadataItem = document.querySelector('.ds_metadata__item');
54
+ expect(metadata?.dataset.test).toEqual('foo');
55
+ expect(metadataItem?.dataset.test).toEqual('bar');
56
+ });
@@ -0,0 +1,39 @@
1
+ export const MetadataItem: React.FC<SGDS.Component.Metadata.Item> = ({
2
+ children,
3
+ name,
4
+ ...props
5
+ }) => {
6
+ return (
7
+ <div className="ds_metadata__item"
8
+ {...props}
9
+ >
10
+ <dt className="ds_metadata__key">{name}</dt>{' '}
11
+ <dd className="ds_metadata__value">
12
+ {children}
13
+ </dd>
14
+ </div>
15
+ );
16
+ };
17
+
18
+ const Metadata: React.FC<SGDS.Component.Metadata> = ({
19
+ children,
20
+ inline,
21
+ ...props
22
+ }) => {
23
+ return (
24
+ <dl
25
+ className={[
26
+ 'ds_metadata',
27
+ inline && 'ds_metadata--inline',
28
+ ].join(' ')}
29
+ {...props}
30
+ >
31
+ {children}
32
+ </dl>
33
+ );
34
+ };
35
+
36
+ Metadata.displayName = 'Metadata';
37
+ MetadataItem.displayName = 'MetadataItem';
38
+
39
+ export default Metadata;
@@ -0,0 +1,67 @@
1
+ import { test, expect } from 'vitest';
2
+ import { render } from '@testing-library/react';
3
+ import PhaseBanner from './phase-banner';
4
+
5
+ const text = 'This is a new service. Your feedback will help us to improve it.';
6
+ const defaultText = 'This is a new service';
7
+
8
+ test('inset text renders correctly', () => {
9
+ render(
10
+ <PhaseBanner>
11
+ {text}
12
+ </PhaseBanner>
13
+ );
14
+
15
+ const phaseBanner = document.querySelector('.ds_phase-banner');
16
+ const phaseBannerContent = phaseBanner?.querySelector('.ds_phase-banner__content');
17
+ const phaseBannerWrapper = phaseBannerContent?.parentNode;
18
+ const phaseBannerText = phaseBannerContent?.querySelector('.ds_phase-banner__text');
19
+
20
+ expect(phaseBanner).toBeInTheDocument();
21
+ expect(phaseBanner?.tagName).toEqual('DIV');
22
+ expect(phaseBannerWrapper).toHaveClass('ds_wrapper');
23
+ expect(phaseBannerWrapper.tagName).toEqual('DIV');
24
+ expect(phaseBannerContent.tagName).toEqual('P');
25
+ expect(phaseBannerText.tagName).toEqual('SPAN');
26
+ expect(phaseBannerText.textContent).toEqual(text);
27
+ });
28
+
29
+ test('phase banner with default text', () => {
30
+ render(
31
+ <PhaseBanner>
32
+ </PhaseBanner>
33
+ );
34
+
35
+ const phaseBanner = document.querySelector('.ds_phase-banner');
36
+ const phaseBannerContent = phaseBanner?.querySelector('.ds_phase-banner__content');
37
+ const phaseBannerText = phaseBannerContent?.querySelector('.ds_phase-banner__text');
38
+
39
+ expect(phaseBannerText?.textContent).toEqual(defaultText);
40
+ });
41
+
42
+ test('phase banner with phase tag', () => {
43
+ const phase = 'Beta';
44
+
45
+ render(
46
+ <PhaseBanner phaseName={phase}></PhaseBanner>
47
+ );
48
+
49
+ const phaseBanner = document.querySelector('.ds_phase-banner');
50
+ const phaseBannerTag = phaseBanner?.querySelector('.ds_phase-banner__tag');
51
+
52
+ expect(phaseBannerTag).toBeInTheDocument();
53
+ expect(phaseBannerTag.tagName).toEqual('SPAN');
54
+ expect(phaseBannerTag).toHaveClass('ds_tag', 'ds_phase-banner__tag');
55
+ expect(phaseBannerTag.textContent).toEqual(phase);
56
+ });
57
+
58
+ test('passing additional props', () => {
59
+ render(
60
+ <PhaseBanner data-test="foo">
61
+ This is a new service. Your <a href="#feedback">feedback</a> will help us to improve it.
62
+ </PhaseBanner>
63
+ )
64
+
65
+ const phaseBanner = document.querySelector('.ds_phase-banner');
66
+ expect(phaseBanner?.dataset.test).toEqual('foo');
67
+ });
@@ -0,0 +1,27 @@
1
+ import Tag from "../tag/tag";
2
+
3
+ const PhaseBanner: React.FC<SGDS.Component.PhaseBanner> = ({
4
+ children,
5
+ phaseName,
6
+ ...props
7
+ }) => {
8
+ return (
9
+ <div
10
+ className="ds_phase-banner"
11
+ {...props}
12
+ >
13
+ <div className="ds_wrapper">
14
+ <p className="ds_phase-banner__content">
15
+ {phaseName && <Tag title={phaseName} className="ds_phase-banner__tag" />}
16
+ <span className="ds_phase-banner__text">
17
+ {children || "This is a new service"}
18
+ </span>
19
+ </p>
20
+ </div>
21
+ </div>
22
+ )
23
+ }
24
+
25
+ PhaseBanner.displayName = 'PhaseBanner';
26
+
27
+ export default PhaseBanner;
@@ -0,0 +1,69 @@
1
+ import { test, expect } from 'vitest';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import Question from './question';
4
+
5
+ test('question renders correctly (basic q, just a wrapper, invalid example)', () => {
6
+ render(
7
+ <Question>
8
+ </Question>
9
+ );
10
+
11
+ const questionElement = document.querySelector('.ds_question');
12
+ expect(questionElement).toBeInTheDocument();
13
+ expect(questionElement?.tagName).toEqual('DIV');
14
+ });
15
+
16
+ test('fieldset question with legend', () => {
17
+ const legendText = 'My legend';
18
+
19
+ render(
20
+ <Question tagName="fieldset" legend={legendText}>
21
+ </Question>
22
+ );
23
+
24
+ const questionElement = screen.getByRole('group');
25
+ const legendElement = within(questionElement).getByText(legendText);
26
+ expect(questionElement?.tagName).toEqual('FIELDSET');
27
+ expect(legendElement.tagName).toEqual('LEGEND');
28
+ });
29
+
30
+ test('question with hint text', () => {
31
+ const hintText = 'My hint text';
32
+
33
+ render(
34
+ <Question hintText={hintText}>
35
+ </Question>
36
+ );
37
+
38
+ const hintTextElement = screen.getByRole('paragraph');
39
+ const firstQuestionChild = document.querySelector('.ds_question')?.childNodes[0]
40
+ expect(hintTextElement).toHaveClass('ds_hint-text');
41
+ expect(hintTextElement.textContent).toEqual(hintText);
42
+ expect(hintTextElement).toBe(firstQuestionChild);
43
+ });
44
+
45
+ test('question with error', () => {
46
+ const errorMessage = 'My error message';
47
+
48
+ render(
49
+ <Question error errorMessage={errorMessage}>
50
+ </Question>
51
+ );
52
+
53
+ const questionElement = document.querySelector('.ds_question');
54
+ const errorMessageElement = questionElement.querySelector('.ds_question__error-message');
55
+
56
+ expect(questionElement).toHaveClass('ds_question--error');
57
+ expect(errorMessageElement).toBeInTheDocument();
58
+ expect(errorMessageElement.textContent).toEqual(errorMessage);
59
+ });
60
+
61
+ test('passing additional props', () => {
62
+ render(
63
+ <Question data-test="foo">
64
+ </Question>
65
+ )
66
+
67
+ const questionElement = document.querySelector('.ds_question');
68
+ expect(questionElement?.dataset.test).toEqual('foo');
69
+ });
@@ -0,0 +1,33 @@
1
+ import ErrorMessage from '../error-message/error-message';
2
+ import HintText from '../../common/hint-text';
3
+ import WrapperTag from '../../common/wrapper-tag';
4
+
5
+ const Question: React.FC<SGDS.Component.Question> = function ({
6
+ children,
7
+ error,
8
+ errorMessage,
9
+ hintText,
10
+ legend,
11
+ tagName = 'div',
12
+ ...props
13
+ }) {
14
+ return (
15
+ <WrapperTag
16
+ tagName={tagName}
17
+ className={[
18
+ 'ds_question',
19
+ error && 'ds_question--error'
20
+ ].join(' ')}
21
+ {...props}
22
+ >
23
+ {legend && <legend>{legend}</legend>}
24
+ {hintText && <HintText text={hintText} />}
25
+ {error && errorMessage && <ErrorMessage text={errorMessage} />}
26
+ {children}
27
+ </WrapperTag>
28
+ );
29
+ };
30
+
31
+ Question.displayName = 'Question';
32
+
33
+ export default Question;
@@ -0,0 +1,190 @@
1
+ import { test, expect, vi } from 'vitest';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import RadioGroup, { Radio } from './radio-button';
4
+
5
+ test('radio group renders correct children', () => {
6
+ const items = [
7
+ {
8
+ id: 'universal-credit',
9
+ label: 'Universal Credit',
10
+ checked: true
11
+ },
12
+ {
13
+ id: 'pensioncredit',
14
+ label: 'Pension Credit'
15
+ },
16
+ {
17
+ id: 'jsa',
18
+ label: 'Income-based Job Seeker\'s Allowance',
19
+ },
20
+ {
21
+ id: 'none',
22
+ label: 'I do not receive any of these benefits',
23
+ }
24
+ ];
25
+ const groupName = "foo"
26
+
27
+ render(
28
+ <RadioGroup name={groupName} items={items} />
29
+ );
30
+
31
+ const radios = screen.getAllByRole('radio');
32
+ const groupContainer = radios[0].parentNode.parentNode;
33
+ expect(radios.length).toEqual(items.length);
34
+ expect(groupContainer).toHaveClass('ds_radios', 'ds_field-group');
35
+ });
36
+
37
+ test('inline radio group', () => {
38
+ const items = [
39
+ {
40
+ id: 'radio-yes',
41
+ label: 'Yes'
42
+ },
43
+ {
44
+ id: 'radio-no',
45
+ label: 'No'
46
+ }
47
+ ];
48
+ const groupName = "yesno"
49
+
50
+ render(
51
+ <RadioGroup inline name={groupName} items={items} />
52
+ );
53
+
54
+
55
+ const radio = screen.getAllByRole('radio')[0];
56
+ const groupContainer = radio.parentNode.parentNode;
57
+ expect(groupContainer).toHaveClass('ds_field-group--inline');
58
+ });
59
+
60
+ test('radio group passes all expected item params', () => {
61
+ const onBlurFn = vi.fn();
62
+ const onChangeFn = vi.fn();
63
+ const groupName = "foo"
64
+
65
+ render(
66
+ <RadioGroup name={groupName} small items={[
67
+ {
68
+ checked: true,
69
+ exclusive: true,
70
+ hintText: 'hint text',
71
+ id: 'myid',
72
+ label: 'label text',
73
+ onBlur: {onBlurFn},
74
+ onChange: {onChangeFn},
75
+ small: true
76
+ }
77
+ ]}/>
78
+ );
79
+
80
+ const radio = screen.getByRole('radio');
81
+ const radioContainer = radio.parentNode;
82
+ const hintText = screen.getByText('hint text');
83
+
84
+ expect(radio).toHaveAttribute('checked');
85
+ expect(radio).toHaveAttribute('name', groupName);
86
+ expect(radio.id).toEqual('myid');
87
+ expect(radioContainer).toHaveClass('ds_radio--small');
88
+ expect(hintText).toBeInTheDocument();
89
+ expect(radio).toHaveAttribute('aria-describedby', hintText.id);
90
+
91
+ // fireEvent.blur(radio);
92
+ // expect(onBlurFn).toHaveBeenCalled();
93
+
94
+ // fireEvent.click(radio);
95
+ // expect(onChangeFn).toHaveBeenCalled();
96
+ });
97
+
98
+ test('individual radio renders correctly', () => {
99
+ render(
100
+ <Radio name="benefitType" label="Pension Credit" id="pensioncredit" />
101
+ );
102
+
103
+ const radio = screen.getByRole('radio');
104
+ const radioContainer = radio.parentNode;
105
+ const label = screen.getByText('Pension Credit');
106
+
107
+ expect(radioContainer).toHaveClass('ds_radio');
108
+ expect(radio.tagName).toEqual('INPUT');
109
+ expect(radio).toHaveAttribute('type', 'radio');
110
+ expect(radio.id).toEqual('pensioncredit');
111
+ expect(radio).toHaveAttribute('name', 'benefitType');
112
+ expect(radio).toHaveClass('ds_radio__input');
113
+ expect(label).toHaveAttribute('for', radio.id);
114
+ expect(radio).not.toHaveAttribute('aria-describedby');
115
+ expect(label).toHaveClass('ds_radio__label');
116
+ });
117
+
118
+ test('checked radio', () => {
119
+ render(
120
+ <Radio name="benefitType" checked label="Pension Credit" id="pensioncredit" />
121
+ );
122
+
123
+ const radio = screen.getByRole('radio');
124
+
125
+ expect(radio).toHaveAttribute('checked')
126
+ });
127
+
128
+ test('radio with blur fn', () => {
129
+ const onBlurFn = vi.fn();
130
+
131
+ render(
132
+ <Radio onBlur={onBlurFn} name="benefitType" label="Pension Credit" id="pensioncredit" />
133
+ );
134
+
135
+ const radio = screen.getByRole('radio');
136
+
137
+ fireEvent.blur(radio);
138
+
139
+ expect(onBlurFn).toHaveBeenCalled();
140
+ });
141
+
142
+ test('radio with change fn', () => {
143
+ const onChangeFn = vi.fn();
144
+
145
+ render(
146
+ <Radio onChange={onChangeFn} name="benefitType" label="Pension Credit" id="pensioncredit" />
147
+ );
148
+
149
+ const radio = screen.getByRole('radio');
150
+
151
+ fireEvent.click(radio);
152
+
153
+ expect(onChangeFn).toHaveBeenCalled();
154
+ });
155
+
156
+ test('radio with hint text', () => {
157
+ render(
158
+ <Radio hintText="hint text" name="benefitType" label="Pension Credit" id="pensioncredit" />
159
+ );
160
+
161
+ const hintText = screen.getByText('hint text');
162
+ const radio = screen.getByRole('radio');
163
+
164
+ expect(hintText).toBeInTheDocument();
165
+ expect(radio).toHaveAttribute('aria-describedby', hintText.id);
166
+ });
167
+
168
+ test('small radio', () => {
169
+ render(
170
+ <Radio small name="benefitType" label="Pension Credit" id="pensioncredit" />
171
+ );
172
+
173
+ const radio = screen.getByRole('radio');
174
+ const radioContainer = radio.parentNode;
175
+
176
+ expect(radioContainer).toHaveClass('ds_radio--small');
177
+ });
178
+
179
+ test('passing additional props', () => {
180
+ render(
181
+ <RadioGroup data-test="foo" items={[{
182
+ id: 'universal-credit',
183
+ label: 'Universal Credit'
184
+ }]} />
185
+ );
186
+
187
+ const radios = screen.getAllByRole('radio');
188
+ const groupContainer = radios[0]?.parentNode?.parentNode;
189
+ expect(groupContainer?.dataset.test).toEqual('foo');
190
+ });
@@ -0,0 +1,88 @@
1
+ import HintText from '../../common/hint-text';
2
+
3
+ export const Radio: React.FC<SGDS.Component.RadioButton> = ({
4
+ checked,
5
+ hintText,
6
+ id,
7
+ label,
8
+ name,
9
+ onBlur,
10
+ onChange,
11
+ small
12
+ }) => {
13
+ const hintTextId = `hint-text-${id}`;
14
+
15
+ function handleBlur(event: React.FocusEvent) {
16
+ if (typeof onBlur === 'function') {
17
+ onBlur(event);
18
+ }
19
+ }
20
+
21
+ function handleChange(event: React.ChangeEvent) {
22
+ if (typeof onChange === 'function') {
23
+ onChange(event);
24
+ }
25
+ }
26
+
27
+ return (
28
+ <div
29
+ className={[
30
+ 'ds_radio',
31
+ small && 'ds_radio--small'
32
+ ].join(' ')}>
33
+ <input
34
+ aria-describedby={hintText ? hintTextId : undefined}
35
+ className="ds_radio__input"
36
+ defaultChecked={!!checked}
37
+ id={id}
38
+ name={name}
39
+ onBlur={handleBlur}
40
+ onChange={handleChange}
41
+ type="radio" />
42
+ <label
43
+ className="ds_radio__label"
44
+ htmlFor={id}
45
+ >{label}</label>
46
+ {hintText && <HintText id={hintTextId} text={hintText} />}
47
+ </div>
48
+ );
49
+ };
50
+
51
+ const RadioGroup: React.FC<SGDS.Component.RadioButton.Group> = ({
52
+ inline,
53
+ items,
54
+ name,
55
+ small,
56
+ ...props
57
+ }) => {
58
+ return (
59
+ <div
60
+ className={[
61
+ 'ds_radios',
62
+ 'ds_field-group',
63
+ inline && 'ds_field-group--inline'
64
+ ].join(' ')}
65
+ {...props}
66
+ >
67
+
68
+ {items && items.map((item, index: number) => (
69
+ <Radio
70
+ checked={item.checked}
71
+ hintText={item.hintText}
72
+ id={item.id}
73
+ key={'radio' + index}
74
+ label={item.label}
75
+ name={name}
76
+ onBlur={item.onBlur}
77
+ onChange={item.onChange}
78
+ small={small || item.small}
79
+ />
80
+ ))}
81
+ </div>
82
+ )
83
+ };
84
+
85
+ Radio.displayName = 'Radio';
86
+ RadioGroup.displayName = 'RadioGroup';
87
+
88
+ export default RadioGroup;