Skip to content

Commit 6b1cabe

Browse files
committed
feat: Add radio button component to Core
1 parent 08da1c8 commit 6b1cabe

File tree

27 files changed

+998
-211
lines changed

27 files changed

+998
-211
lines changed

build-tools/utils/pluralize.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const pluralizationMap = {
5959
ProgressBar: 'ProgressBars',
6060
PromptInput: 'PromptInputs',
6161
PropertyFilter: 'PropertyFilters',
62+
RadioButton: 'RadioButtons',
6263
RadioGroup: 'RadioGroups',
6364
S3ResourceSelector: 'S3ResourceSelectors',
6465
SegmentedControl: 'SegmentedControls',

pages/radio-button/styles.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
@use '~design-tokens' as awsui;
7+
8+
.radio-group {
9+
display: flex;
10+
column-gap: awsui.$space-scaled-m;
11+
row-gap: awsui.$space-scaled-xs;
12+
&--horizontal {
13+
flex-direction: row;
14+
flex-wrap: wrap;
15+
}
16+
&--vertical {
17+
flex-direction: column;
18+
}
19+
}

pages/radio-button/test.page.tsx

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useContext, useState } from 'react';
4+
import clsx from 'clsx';
5+
6+
import Box from '~components/box';
7+
import Checkbox from '~components/checkbox';
8+
import ColumnLayout from '~components/column-layout';
9+
import FormField from '~components/form-field';
10+
import RadioButton, { RadioButtonProps } from '~components/radio-button';
11+
import RadioGroup from '~components/radio-group';
12+
import SpaceBetween from '~components/space-between';
13+
14+
import AppContext, { AppContextType } from '../app/app-context';
15+
import ScreenshotArea from '../utils/screenshot-area';
16+
import useCustomStyle from './use-custom-style';
17+
18+
import styles from './styles.scss';
19+
20+
type Orientation = 'horizontal' | 'vertical';
21+
type Wrapper = 'form-field' | 'fieldset' | 'div';
22+
23+
type RadioButtonDemoContext = React.Context<
24+
AppContextType<{
25+
customStyle?: boolean;
26+
descriptions?: boolean;
27+
disabled?: boolean;
28+
interactiveElementsInDescriptions?: boolean;
29+
interactiveElementsInLabels?: boolean;
30+
orientation?: Orientation;
31+
radioGroupRole?: boolean;
32+
readOnly?: boolean;
33+
styled?: boolean;
34+
wrapper: Wrapper;
35+
}>
36+
>;
37+
38+
const label = 'Choose a quantity';
39+
40+
const CustomRadioButton = ({
41+
value,
42+
checked,
43+
setValue,
44+
description,
45+
children,
46+
disabled,
47+
readOnly,
48+
}: Omit<RadioButtonProps, 'name'> & { setValue: (_a: string) => void }) => {
49+
const { urlParams } = useContext(AppContext as RadioButtonDemoContext);
50+
const customStyle = useCustomStyle();
51+
52+
const fullDescription = urlParams.interactiveElementsInDescriptions ? (
53+
<>
54+
{description}. <a href="#">Learn more</a>
55+
</>
56+
) : (
57+
description
58+
);
59+
60+
return (
61+
<RadioButton
62+
value={value}
63+
name="quantity"
64+
checked={checked}
65+
disabled={disabled}
66+
readOnly={readOnly}
67+
onChange={({ detail }) => setValue(detail.value)}
68+
description={fullDescription}
69+
style={urlParams.customStyle ? customStyle : undefined}
70+
>
71+
{children}
72+
{urlParams.interactiveElementsInLabels && (
73+
<>
74+
{' '}
75+
<button>Button</button>
76+
</>
77+
)}
78+
</RadioButton>
79+
);
80+
};
81+
82+
export default function RadioButtonsPage() {
83+
const { urlParams, setUrlParams } = useContext(AppContext as RadioButtonDemoContext);
84+
85+
const [value, setValue] = useState<string>('');
86+
87+
const className = clsx(styles['radio-group'], styles[`radio-group--${urlParams.orientation || 'vertical'}`]);
88+
89+
const radioButtons = (
90+
<div role={urlParams.radioGroupRole ? 'radioGroup' : undefined} className={className}>
91+
<CustomRadioButton
92+
value="one"
93+
checked={value === 'one'}
94+
setValue={setValue}
95+
description={urlParams.descriptions ? 'Option one' : undefined}
96+
>
97+
One
98+
</CustomRadioButton>
99+
<CustomRadioButton
100+
value="two"
101+
checked={value === 'two'}
102+
disabled={urlParams.disabled}
103+
readOnly={urlParams.readOnly}
104+
setValue={setValue}
105+
description={urlParams.descriptions ? 'Option two' : undefined}
106+
>
107+
Two
108+
</CustomRadioButton>
109+
<CustomRadioButton
110+
value="three"
111+
checked={value === 'three'}
112+
setValue={setValue}
113+
description={urlParams.descriptions ? 'Option three' : undefined}
114+
>
115+
Three
116+
</CustomRadioButton>
117+
</div>
118+
);
119+
return (
120+
<article>
121+
<h1>Radio button</h1>
122+
<Box margin={{ horizontal: 'm' }}>
123+
<ColumnLayout columns={2}>
124+
<SpaceBetween size="s">
125+
<FormField label="Orientation">
126+
<RadioGroup
127+
value={urlParams.orientation || 'vertical'}
128+
onChange={({ detail }) => setUrlParams({ ...urlParams, orientation: detail.value as Orientation })}
129+
items={[
130+
{
131+
label: 'Vertical',
132+
value: 'vertical',
133+
},
134+
{
135+
label: 'Horizontal',
136+
value: 'horizontal',
137+
},
138+
]}
139+
/>
140+
</FormField>
141+
<FormField label="Wrapper">
142+
<RadioGroup
143+
value={urlParams.wrapper || 'div'}
144+
onChange={({ detail }) => setUrlParams({ ...urlParams, wrapper: detail.value as Wrapper })}
145+
items={[
146+
{
147+
label: 'Cloudscape Form Field around div',
148+
value: 'form-field',
149+
},
150+
{
151+
label: 'Native HTML fieldset around div',
152+
value: 'fieldset',
153+
},
154+
{
155+
label: 'Div only',
156+
value: 'div',
157+
},
158+
]}
159+
/>
160+
</FormField>
161+
<FormField label="Accessibility">
162+
<Checkbox
163+
checked={!!urlParams.radioGroupRole}
164+
onChange={({ detail }) => setUrlParams({ ...urlParams, radioGroupRole: detail.checked })}
165+
description="Add `radiogroup` role to the wrapping div"
166+
>
167+
Use radiogroup role
168+
</Checkbox>
169+
</FormField>
170+
</SpaceBetween>
171+
<FormField label="Other options">
172+
<SpaceBetween size="s">
173+
<Checkbox
174+
checked={!!urlParams.disabled}
175+
onChange={({ detail }) => setUrlParams({ ...urlParams, disabled: detail.checked })}
176+
description="Make one of the radio buttons disabled"
177+
>
178+
Disabled
179+
</Checkbox>
180+
181+
<Checkbox
182+
checked={!!urlParams.readOnly}
183+
onChange={({ detail }) => setUrlParams({ ...urlParams, readOnly: detail.checked })}
184+
description="Make one of the radio buttons read-only"
185+
>
186+
Read-only
187+
</Checkbox>
188+
189+
<Checkbox
190+
checked={!!urlParams.descriptions}
191+
onChange={({ detail }) => setUrlParams({ ...urlParams, descriptions: detail.checked })}
192+
description="Show descriptions"
193+
>
194+
Descriptions
195+
</Checkbox>
196+
197+
{urlParams.descriptions && (
198+
<Checkbox
199+
checked={!!urlParams.interactiveElementsInDescriptions}
200+
onChange={({ detail }) =>
201+
setUrlParams({ ...urlParams, interactiveElementsInDescriptions: detail.checked })
202+
}
203+
>
204+
Interactive elements in descriptions
205+
</Checkbox>
206+
)}
207+
208+
<Checkbox
209+
checked={!!urlParams.interactiveElementsInLabels}
210+
onChange={({ detail }) => setUrlParams({ ...urlParams, interactiveElementsInLabels: detail.checked })}
211+
>
212+
Interactive elements in labels
213+
</Checkbox>
214+
215+
<Checkbox
216+
checked={!!urlParams.customStyle}
217+
onChange={({ detail }) => setUrlParams({ ...urlParams, customStyle: detail.checked })}
218+
description="Use the style API to customize the component styles"
219+
>
220+
Use custom style
221+
</Checkbox>
222+
</SpaceBetween>
223+
</FormField>
224+
</ColumnLayout>
225+
</Box>
226+
<hr />
227+
<ScreenshotArea disableAnimations={true}>
228+
{urlParams.wrapper === 'form-field' ? (
229+
<FormField label={label}>{radioButtons}</FormField>
230+
) : urlParams.wrapper === 'fieldset' ? (
231+
<fieldset>
232+
<legend>{label}</legend>
233+
{radioButtons}
234+
</fieldset>
235+
) : (
236+
radioButtons
237+
)}
238+
</ScreenshotArea>
239+
</article>
240+
);
241+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { useRef } from 'react';
4+
5+
import { useCurrentMode } from '@cloudscape-design/component-toolkit/internal';
6+
7+
import { palette } from '../app/themes/style-api';
8+
9+
const colors = {
10+
light: {
11+
checked: palette.neutral100,
12+
default: palette.neutral100,
13+
disabled: palette.neutral80,
14+
readOnly: palette.neutral80,
15+
},
16+
dark: {
17+
checked: palette.neutral10,
18+
default: palette.neutral10,
19+
disabled: palette.neutral40,
20+
readOnly: palette.neutral40,
21+
},
22+
};
23+
24+
const getCustomStyle = (mode: 'dark' | 'light') => ({
25+
input: {
26+
stroke: {
27+
default: palette.neutral80,
28+
disabled: palette.neutral100,
29+
readOnly: palette.neutral90,
30+
},
31+
fill: {
32+
checked: palette.teal80,
33+
default: palette.neutral10,
34+
disabled: palette.neutral60,
35+
readOnly: palette.neutral40,
36+
},
37+
circle: {
38+
fill: {
39+
checked: palette.neutral10,
40+
disabled: palette.neutral10,
41+
readOnly: palette.neutral80,
42+
},
43+
},
44+
focusRing: {
45+
borderColor: palette.teal80,
46+
borderRadius: '2px',
47+
borderWidth: '1px',
48+
},
49+
},
50+
label: {
51+
color: { ...colors[mode] },
52+
},
53+
description: {
54+
color: { ...colors[mode] },
55+
},
56+
});
57+
58+
const useCustomStyle = () => {
59+
const mode = useCurrentMode(useRef(document.body));
60+
return getCustomStyle(mode);
61+
};
62+
63+
export default useCustomStyle;

0 commit comments

Comments
 (0)