Skip to content

Commit b7e42b1

Browse files
Elena Rashkovanlena.rashkovan
andauthored
feat: add RadioButton and RadioGroup (#543)
* feat(radio-button): add experimental component * feat(radio-group): add experimental component * feat: update exports * fix: fix linter errors --------- Co-authored-by: lena.rashkovan <[email protected]>
1 parent 912fe47 commit b7e42b1

File tree

8 files changed

+213
-2
lines changed

8 files changed

+213
-2
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React, { FC } from 'react';
2+
import styled from 'styled-components';
3+
import { Radio as BaseRadio, RadioProps } from 'react-aria-components';
4+
5+
import { getSemanticValue } from '../../../essentials/experimental';
6+
import { themeGet } from '../../../utils/experimental';
7+
8+
const Indicator = styled.span<{ $selected: boolean }>`
9+
position: relative;
10+
flex-shrink: 0;
11+
12+
top: calc((var(--wave-exp-typescale-body-1-line-height) - 1rem) / 2);
13+
width: 1rem;
14+
height: 1rem;
15+
16+
box-sizing: border-box;
17+
border-style: solid;
18+
border-color: currentColor;
19+
border-width: ${props => (props.$selected ? '5px' : '2px')};
20+
border-radius: 50%;
21+
background-color: ${getSemanticValue('surface')};
22+
transition: border-color 200ms ease, border-width 50ms ease;
23+
`;
24+
25+
const Radio = styled(BaseRadio)`
26+
display: flex;
27+
gap: ${themeGet('space.2')};
28+
cursor: pointer;
29+
30+
font-family: var(--wave-exp-typescale-body-1-font), sans-serif;
31+
font-size: var(--wave-exp-typescale-body-1-size);
32+
font-weight: var(--wave-exp-typescale-body-1-weight);
33+
line-height: var(--wave-exp-typescale-body-1-line-height);
34+
35+
color: ${getSemanticValue('on-surface')};
36+
37+
${Indicator} {
38+
color: ${getSemanticValue('divider')};
39+
}
40+
41+
&[data-hovered] ${Indicator} {
42+
color: ${getSemanticValue('interactive')};
43+
}
44+
45+
&[data-pressed] ${Indicator} {
46+
color: ${getSemanticValue('surface-variant')};
47+
}
48+
49+
&[data-focus-visible] {
50+
outline: 2px solid ${getSemanticValue('surface-variant')};
51+
outline-offset: 2px;
52+
}
53+
54+
&[data-disabled] {
55+
cursor: not-allowed;
56+
opacity: 0.38;
57+
}
58+
59+
&[data-invalid] {
60+
color: ${getSemanticValue('negative-variant')};
61+
}
62+
63+
&[data-hovered][data-invalid] ${Indicator} {
64+
color: ${getSemanticValue('negative')};
65+
}
66+
67+
&[data-selected] ${Indicator} {
68+
color: ${getSemanticValue('accent')};
69+
}
70+
71+
&[data-selected][data-hovered] ${Indicator} {
72+
color: ${getSemanticValue('on-interactive-container')};
73+
}
74+
75+
&[data-selected][data-pressed] ${Indicator} {
76+
color: ${getSemanticValue('interactive')};
77+
}
78+
79+
&[data-selected][data-disabled] ${Indicator} {
80+
color: ${getSemanticValue('surface-variant')};
81+
}
82+
`;
83+
84+
export const RadioButton: FC<RadioProps> = ({ children, ...rest }) => (
85+
<Radio {...rest}>
86+
{({ isSelected }) => (
87+
<>
88+
<Indicator $selected={isSelected} />
89+
{children}
90+
</>
91+
)}
92+
</Radio>
93+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import { Meta, StoryObj } from '@storybook/react';
3+
import { RadioGroup } from 'react-aria-components';
4+
import { RadioButton } from '../RadioButton';
5+
6+
const meta: Meta = {
7+
title: 'Experimental/Components/RadioButton',
8+
component: RadioButton,
9+
args: {
10+
children: 'Label'
11+
},
12+
decorators: [
13+
story => (
14+
<RadioGroup aria-label="Test" defaultValue="test">
15+
{story}
16+
</RadioGroup>
17+
)
18+
]
19+
};
20+
21+
export default meta;
22+
23+
type Story = StoryObj<typeof RadioButton>;
24+
25+
export const Default: Story = {};
26+
27+
export const Disabled: Story = {
28+
args: {
29+
isDisabled: true
30+
}
31+
};
32+
33+
export const Selected: Story = {
34+
args: {
35+
value: 'test'
36+
}
37+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { RadioGroup as BaseRadioGroup } from 'react-aria-components';
2+
import styled from 'styled-components';
3+
4+
import { themeGet } from '../../../utils/experimental';
5+
6+
export const List = styled.div`
7+
display: flex;
8+
gap: ${themeGet('space.4')};
9+
`;
10+
11+
export const RadioGroup = styled(BaseRadioGroup)`
12+
&[data-orientation='horizontal'] ${List} {
13+
flex-direction: row;
14+
}
15+
16+
&[data-orientation='vertical'] ${List} {
17+
flex-direction: column;
18+
}
19+
`;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { RadioGroupProps as BaseRadioGroupProps } from 'react-aria-components';
2+
import React, { FC, ReactNode } from 'react';
3+
4+
import * as Styled from './RadioGroup.styled';
5+
6+
interface RadioGroupProps extends Omit<BaseRadioGroupProps, 'children'> {
7+
children: ReactNode;
8+
label: string;
9+
}
10+
11+
export const RadioGroup: FC<RadioGroupProps> = ({ label, children, ...props }) => (
12+
<Styled.RadioGroup aria-label={label} {...props}>
13+
<Styled.List>{children}</Styled.List>
14+
</Styled.RadioGroup>
15+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { Meta, StoryObj } from '@storybook/react';
3+
import { RadioGroup } from '../RadioGroup';
4+
import { RadioButton } from '../../RadioButton/RadioButton';
5+
6+
const meta: Meta = {
7+
title: 'Experimental/Components/RadioGroup',
8+
component: RadioGroup,
9+
args: {
10+
children: [
11+
<RadioButton value="one">one</RadioButton>,
12+
<RadioButton value="two">two</RadioButton>,
13+
<RadioButton value="three">three</RadioButton>
14+
],
15+
label: 'Example',
16+
defaultValue: 'one'
17+
}
18+
};
19+
20+
export default meta;
21+
22+
type Story = StoryObj<typeof RadioGroup>;
23+
24+
export const Default: Story = {};
25+
26+
export const Horizontal: Story = {
27+
args: {
28+
orientation: 'horizontal'
29+
}
30+
};
31+
32+
export const Invalid: Story = {
33+
args: {
34+
isInvalid: true
35+
}
36+
};

src/components/experimental/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export { Label } from './Label/Label';
1515
export { ListBox, ListBoxItem, LabelText, DescriptionText } from './ListBox/ListBox';
1616
export { Modal } from './Modal/Modal';
1717
export { Popover } from './Popover/Popover';
18+
export { RadioButton } from './RadioButton/RadioButton';
19+
export { RadioGroup } from './RadioGroup/RadioGroup';
1820
export { Search } from './Search/Search';
1921
export { Select } from './Select/Select';
2022
export { Snackbar, SnackbarProps } from './Snackbar/Snackbar';

src/essentials/experimental/cssVariables.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { Join, Leaves } from '../../utils/types';
3030
import { ColorPaletteSchema, SemanticColorsSchema } from './types';
3131

3232
const DS_PREFIX = 'wave-exp';
33-
type NameSpace = 'color' | 'palette';
33+
type NameSpace = 'color' | 'palette' | 'typescale';
3434

3535
type BareColorToken = Join<Leaves<ColorPaletteSchema>, '-'>;
3636
type SemanticColorToken = Join<Leaves<SemanticColorsSchema>, '-'>;

src/essentials/experimental/globalStyles.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createGlobalStyle, css, CSSObject, GlobalStyleComponent, DefaultTheme } from 'styled-components';
22

33
import { TokenObject } from '../../utils/cssVariables';
4-
import { generateBareCssVariables, generateSemanticCssVariables } from './cssVariables';
4+
import { generateBareCssVariables, generateCssVariables, generateSemanticCssVariables } from './cssVariables';
55
import { SemanticColorsSchema } from './types';
66

77
export const DARK_THEME_CLASS = 'dark-scheme';
@@ -35,6 +35,15 @@ export const createThemeGlobalStyle = (
3535
color-scheme: light;
3636
${bareCssVariables}
3737
${semanticCssVariablesForLightTheme}
38+
${generateCssVariables(
39+
{
40+
'body-1-font': 'Roboto Flex',
41+
'body-1-size': '1rem',
42+
'body-1-weight': 400,
43+
'body-1-line-height': '1.5rem'
44+
},
45+
'typescale'
46+
)}
3847
}
3948
4049
.${DARK_THEME_CLASS} {

0 commit comments

Comments
 (0)