Skip to content

Commit a79c3a7

Browse files
authored
chore: Refactor radio button into internal component (#4021)
1 parent 773369f commit a79c3a7

File tree

16 files changed

+276
-151
lines changed

16 files changed

+276
-151
lines changed

src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20341,7 +20341,7 @@ being included in a form submission. A read-only control is still focusable.",
2034120341
},
2034220342
{
2034320343
"inlineType": {
20344-
"name": "RadioGroupProps.Style",
20344+
"name": "RadioButtonProps.Style",
2034520345
"properties": [
2034620346
{
2034720347
"inlineType": {
@@ -20566,7 +20566,7 @@ being included in a form submission. A read-only control is still focusable.",
2056620566
"systemTags": [
2056720567
"core",
2056820568
],
20569-
"type": "RadioGroupProps.Style",
20569+
"type": "RadioButtonProps.Style",
2057020570
},
2057120571
{
2057220572
"description": "Sets the value of the selected radio button.

src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ exports[`test-utils selectors 1`] = `
389389
"awsui_placeholder_18eso",
390390
"awsui_recovery_vrgzu",
391391
"awsui_root_11n0s",
392+
"awsui_root_15oj2",
392393
"awsui_root_1fcus",
393394
"awsui_root_1kjc7",
394395
"awsui_root_1om0h",
@@ -514,8 +515,7 @@ exports[`test-utils selectors 1`] = `
514515
"awsui_token-editor-token-remove-actions_1heb1",
515516
],
516517
"radio-group": [
517-
"awsui_radio_1mabk",
518-
"awsui_root_1mabk",
518+
"awsui_root_1np5w",
519519
],
520520
"s3-resource-selector": [
521521
"awsui_alert_1u0yw",

src/radio-group/radio-button.tsx renamed to src/internal/components/radio-button/index.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,20 @@ import { useMergeRefs } from '@cloudscape-design/component-toolkit/internal';
77
import { useSingleTabStopNavigation } from '@cloudscape-design/component-toolkit/internal';
88
import { copyAnalyticsMetadataAttribute } from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
99

10-
import AbstractSwitch from '../internal/components/abstract-switch';
11-
import { fireNonCancelableEvent, NonCancelableEventHandler } from '../internal/events';
12-
import { RadioGroupProps } from './interfaces';
13-
import { getAbstractSwitchStyles, getInnerCircleStyle, getOuterCircleStyle } from './style';
10+
import { getAbstractSwitchStyles, getInnerCircleStyle, getOuterCircleStyle } from '../../../radio-group/style';
11+
import { getBaseProps } from '../../base-component';
12+
import AbstractSwitch from '../../components/abstract-switch';
13+
import { fireNonCancelableEvent } from '../../events';
14+
import { InternalBaseComponentProps } from '../../hooks/use-base-component';
15+
import { RadioButtonProps } from './interfaces';
1416

1517
import styles from './styles.css.js';
16-
17-
interface RadioButtonProps extends RadioGroupProps.RadioButtonDefinition {
18-
name: string;
19-
checked: boolean;
20-
onChange?: NonCancelableEventHandler<RadioGroupProps.ChangeDetail>;
21-
readOnly?: boolean;
22-
className?: string;
23-
style?: RadioGroupProps.Style;
24-
}
18+
import testUtilStyles from './test-classes/styles.css.js';
2519

2620
export default React.forwardRef(function RadioButton(
2721
{
2822
name,
29-
label,
23+
children,
3024
value,
3125
checked,
3226
description,
@@ -37,25 +31,28 @@ export default React.forwardRef(function RadioButton(
3731
className,
3832
style,
3933
...rest
40-
}: RadioButtonProps,
34+
}: RadioButtonProps & InternalBaseComponentProps,
4135
ref: React.Ref<HTMLInputElement>
4236
) {
4337
const radioButtonRef = useRef<HTMLInputElement>(null);
4438
const mergedRefs = useMergeRefs(radioButtonRef, ref);
4539

4640
const { tabIndex } = useSingleTabStopNavigation(radioButtonRef);
41+
const baseProps = getBaseProps(rest);
4742

4843
return (
4944
<AbstractSwitch
50-
className={clsx(styles.radio, description && styles['radio--has-description'], className)}
45+
{...baseProps}
46+
className={clsx(testUtilStyles.root, className)}
5147
controlClassName={styles['radio-control']}
5248
outlineClassName={styles.outline}
53-
label={label}
49+
label={children}
5450
description={description}
5551
disabled={disabled}
5652
readOnly={readOnly}
5753
controlId={controlId}
5854
style={getAbstractSwitchStyles(style, checked, disabled, readOnly)}
55+
__internalRootRef={rest.__internalRootRef}
5956
{...copyAnalyticsMetadataAttribute(rest)}
6057
nativeControl={nativeControlProps => (
6158
<input
@@ -73,10 +70,7 @@ export default React.forwardRef(function RadioButton(
7370
)}
7471
onClick={() => {
7572
radioButtonRef.current?.focus();
76-
if (checked) {
77-
return;
78-
}
79-
fireNonCancelableEvent(onChange, { value });
73+
fireNonCancelableEvent(onChange, { checked: !checked });
8074
}}
8175
styledControl={
8276
<svg viewBox="0 0 100 100" focusable="false" aria-hidden="true">
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
5+
import { BaseComponentProps } from '../../base-component';
6+
import { NonCancelableEventHandler } from '../../events';
7+
/**
8+
* @awsuiSystem core
9+
*/
10+
import { NativeAttributes } from '../../utils/with-native-attributes';
11+
12+
export interface RadioButtonProps extends BaseComponentProps {
13+
/**
14+
* Specifies if the component is selected.
15+
*/
16+
checked: boolean;
17+
18+
/**
19+
* Specifies the ID of the native form element. You can use it to relate
20+
* a label element's `for` attribute to this control.
21+
*/
22+
controlId?: string;
23+
24+
/**
25+
* Name of the group that the radio button belongs to.
26+
*/
27+
name: string;
28+
29+
/**
30+
* Description that appears below the label.
31+
*/
32+
description?: React.ReactNode;
33+
34+
/**
35+
* Specifies if the control is disabled, which prevents the
36+
* user from modifying the value and prevents the value from
37+
* being included in a form submission. A disabled control can't
38+
* receive focus.
39+
*/
40+
disabled?: boolean;
41+
42+
/**
43+
* The control's label that's displayed next to the radio button. A state change occurs when a user clicks on it.
44+
* @displayname label
45+
*/
46+
children?: React.ReactNode;
47+
48+
/**
49+
* Attributes to add to the native `input` element.
50+
* Some attributes will be automatically combined with internal attribute values:
51+
* - `className` will be appended.
52+
* - Event handlers will be chained, unless the default is prevented.
53+
*
54+
* We do not support using this attribute to apply custom styling.
55+
*
56+
* @awsuiSystem core
57+
*/
58+
nativeInputAttributes?: NativeAttributes<React.InputHTMLAttributes<HTMLInputElement>>;
59+
60+
/**
61+
* Called when the user changes the component state. The event `detail` contains the current value for the `checked` property.
62+
*/
63+
onChange?: NonCancelableEventHandler<RadioButtonProps.ChangeDetail>;
64+
65+
/**
66+
* Specifies if the radio button is read-only, which prevents the
67+
* user from modifying the value, but does not prevent the value from
68+
* being included in a form submission. A read-only control is still focusable.
69+
*
70+
* This property should be set for either all or none of the radio buttons in a group.
71+
*/
72+
readOnly?: boolean;
73+
74+
/**
75+
* @awsuiSystem core
76+
*/
77+
style?: RadioButtonProps.Style;
78+
79+
/**
80+
* Sets the value attribute to the native control.
81+
* If using native form submission, this value is sent to the server if the radio button is checked.
82+
* It is never shown to the user by their user agent.
83+
* For more details, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/radio#value).
84+
*/
85+
value?: string;
86+
}
87+
88+
export namespace RadioButtonProps {
89+
export interface ChangeDetail {
90+
checked: boolean;
91+
}
92+
93+
export interface Ref {
94+
/**
95+
* Sets input focus onto the UI control.
96+
*/
97+
focus(): void;
98+
}
99+
100+
export interface Style {
101+
input?: {
102+
fill?: {
103+
checked?: string;
104+
default?: string;
105+
disabled?: string;
106+
readOnly?: string;
107+
};
108+
stroke?: {
109+
default?: string;
110+
disabled?: string;
111+
readOnly?: string;
112+
};
113+
circle?: {
114+
fill?: {
115+
checked?: string;
116+
disabled?: string;
117+
readOnly?: string;
118+
};
119+
};
120+
focusRing?: {
121+
borderColor?: string;
122+
borderRadius?: string;
123+
borderWidth?: string;
124+
};
125+
};
126+
label?: {
127+
color?: {
128+
checked?: string;
129+
default?: string;
130+
disabled?: string;
131+
readOnly?: string;
132+
};
133+
};
134+
description?: {
135+
color?: {
136+
checked?: string;
137+
default?: string;
138+
disabled?: string;
139+
readOnly?: string;
140+
};
141+
};
142+
}
143+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
@use '../../styles' as styles;
7+
@use '../../styles/tokens' as awsui;
8+
@use '../../styles/foundation' as foundation;
9+
@use '../../generated/custom-css-properties/index.scss' as custom-props;
10+
11+
$radio-size: awsui.$size-control;
12+
13+
.radio-control {
14+
@include styles.make-control-size($radio-size);
15+
}
16+
17+
.outline {
18+
#{custom-props.$styleFocusRingBoxShadow}: 0 0 0
19+
var(#{custom-props.$styleFocusRingBorderWidth}, foundation.$box-shadow-focused-width)
20+
var(#{custom-props.$styleFocusRingBorderColor}, awsui.$color-border-item-focused);
21+
22+
@include styles.focus-highlight(
23+
$gutter: 2px,
24+
$border-radius: var(#{custom-props.$styleFocusRingBorderRadius}, awsui.$border-radius-control-circular-focus-ring),
25+
$box-shadow: var(#{custom-props.$styleFocusRingBoxShadow})
26+
);
27+
}
28+
29+
.styled-circle-border {
30+
stroke: awsui.$color-border-control-default;
31+
fill: awsui.$color-background-control-default;
32+
&.styled-circle-disabled,
33+
&.styled-circle-readonly {
34+
fill: awsui.$color-background-control-disabled;
35+
stroke: awsui.$color-background-control-disabled;
36+
}
37+
}
38+
39+
.styled-circle-fill {
40+
stroke: awsui.$color-background-control-checked;
41+
fill: awsui.$color-foreground-control-default;
42+
opacity: 0;
43+
@include styles.with-motion {
44+
transition: opacity awsui.$motion-duration-transition-quick awsui.$motion-easing-transition-quick;
45+
}
46+
&.styled-circle-checked {
47+
opacity: 1;
48+
}
49+
&.styled-circle-disabled {
50+
fill: awsui.$color-foreground-control-disabled;
51+
stroke: awsui.$color-background-control-disabled;
52+
}
53+
&.styled-circle-readonly {
54+
fill: awsui.$color-foreground-control-read-only;
55+
stroke: awsui.$color-background-control-disabled;
56+
}
57+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
.root {
7+
/*used in test-utils*/
8+
}

src/radio-group/__integ__/radio-group.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';
55

66
import createWrapper, { RadioGroupWrapper } from '../../../lib/components/test-utils/selectors';
77

8-
import styles from '../../../lib/components/radio-group/styles.selectors.js';
8+
import radioButtonStyles from '../../../lib/components/internal/components/radio-button/styles.selectors.js';
99

1010
const radioGroupWrapper = createWrapper().findRadioGroup('#simple');
1111

@@ -75,8 +75,8 @@ test(
7575

7676
await page.click('[data-testid="1"]');
7777
await page.keys('Tab');
78-
await expect((await browser.$(`.${styles.outline}`).getCSSProperty('box-shadow', '::before')).value).toBe(
79-
'rgb(4,125,149)0px0px0px1px'
80-
);
78+
await expect(
79+
(await browser.$(`.${radioButtonStyles.outline}`).getCSSProperty('box-shadow', '::before')).value
80+
).toBe('rgb(4,125,149)0px0px0px1px');
8181
})
8282
);

src/radio-group/__tests__/radio-group.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import RadioButtonWrapper from '../../../lib/components/test-utils/dom/radio-gro
1515
import customCssProps from '../../internal/generated/custom-css-properties';
1616

1717
import abstractSwitchStyles from '../../../lib/components/internal/components/abstract-switch/styles.css.js';
18-
import styles from '../../../lib/components/radio-group/styles.selectors.js';
18+
import radioButtonStyles from '../../../lib/components/internal/components/radio-button/styles.selectors.js';
1919

2020
const defaultItems: RadioGroupProps.RadioButtonDefinition[] = [
2121
{ value: 'val1', label: 'Option one' },
@@ -434,8 +434,8 @@ test('all style api properties', function () {
434434
/>
435435
);
436436

437-
const outerCircle = wrapper.findByClassName(styles['styled-circle-border'])!.getElement();
438-
const innerCircle = wrapper.findByClassName(styles['styled-circle-fill'])!.getElement();
437+
const outerCircle = wrapper.findByClassName(radioButtonStyles['styled-circle-border'])!.getElement();
438+
const innerCircle = wrapper.findByClassName(radioButtonStyles['styled-circle-fill'])!.getElement();
439439
const label = wrapper.findByClassName(abstractSwitchStyles.label)!.getElement();
440440
const description = wrapper.findByClassName(abstractSwitchStyles.description)!.getElement();
441441
const control = wrapper.findByClassName(abstractSwitchStyles.control)!.getElement();

0 commit comments

Comments
 (0)