Skip to content

Commit dd04246

Browse files
refactor: implement CSS-only variant styling for RadioCard
- Remove JavaScript context passing from RadioGroup and Radio components - Add data-variant attribute to RadioGroup for CSS targeting - Move card styling from Radio.module.css to RadioGroup.module.css using descendant selectors - Maintain existing slot-based content structure and functionality - Use pure CSS parent-to-child styling instead of Provider/useContext pattern Co-Authored-By: Naomi Hironaka <[email protected]>
1 parent d35393e commit dd04246

File tree

4 files changed

+107
-123
lines changed

4 files changed

+107
-123
lines changed

packages/components/src/Radio.tsx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { VariantProps } from 'class-variance-authority';
21
import type { Ref } from 'react';
32
import type {
43
RadioProps as AriaRadioProps,
@@ -7,26 +6,16 @@ import type {
76
} from 'react-aria-components';
87

98
import { cva } from 'class-variance-authority';
10-
import { createContext, useContext } from 'react';
9+
import { createContext } from 'react';
1110
import { Radio as AriaRadio, composeRenderProps } from 'react-aria-components';
1211

1312
import styles from './styles/Radio.module.css';
1413
import { useLPContextProps } from './utils';
1514

16-
const radioStyles = cva(styles.radio, {
17-
variants: {
18-
variant: {
19-
default: '',
20-
card: styles.card,
21-
},
22-
},
23-
defaultVariants: {
24-
variant: 'default',
25-
},
26-
});
15+
const radioStyles = cva(styles.radio);
2716
const radioIconStyles = cva(styles.circle);
2817

29-
interface RadioProps extends AriaRadioProps, VariantProps<typeof radioStyles> {
18+
interface RadioProps extends AriaRadioProps {
3019
ref?: Ref<HTMLLabelElement>;
3120
}
3221

@@ -53,15 +42,13 @@ const RadioIcon = ({ isSelected }: Partial<RadioRenderProps>) => (
5342
*/
5443
const Radio = ({ ref, ...props }: RadioProps) => {
5544
[props, ref] = useLPContextProps(props, ref, RadioContext);
56-
const contextProps = useContext(RadioContext) as any;
57-
const { variant = 'default' } = { ...contextProps, ...props };
5845

5946
return (
6047
<AriaRadio
6148
{...props}
6249
ref={ref}
6350
className={composeRenderProps(props.className, (className, renderProps) =>
64-
radioStyles({ ...renderProps, variant, className }),
51+
radioStyles({ ...renderProps, className }),
6552
)}
6653
>
6754
{composeRenderProps(props.children, (children, { isSelected }) => (

packages/components/src/RadioGroup.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import type { RadioGroupProps as AriaRadioGroupProps, ContextValue } from 'react
44

55
import { cva } from 'class-variance-authority';
66
import { createContext } from 'react';
7-
import { RadioGroup as AriaRadioGroup, composeRenderProps, Provider } from 'react-aria-components';
7+
import { RadioGroup as AriaRadioGroup, composeRenderProps } from 'react-aria-components';
88

9-
import { RadioContext } from './Radio';
109
import styles from './styles/RadioGroup.module.css';
1110
import { useLPContextProps } from './utils';
1211

@@ -35,20 +34,17 @@ const RadioGroupContext = createContext<ContextValue<RadioGroupProps, HTMLDivEle
3534
*/
3635
const RadioGroup = ({ ref, ...props }: RadioGroupProps) => {
3736
[props, ref] = useLPContextProps(props, ref, RadioGroupContext);
38-
const { variant = 'default' } = props;
37+
const { variant = 'default', ...restProps } = props;
3938

4039
return (
4140
<AriaRadioGroup
42-
{...props}
41+
{...restProps}
4342
ref={ref}
43+
data-variant={variant}
4444
className={composeRenderProps(props.className, (className, renderProps) =>
4545
radioGroupStyles({ ...renderProps, variant, className }),
4646
)}
47-
>
48-
{composeRenderProps(props.children, (children) => (
49-
<Provider values={[[RadioContext, { variant } as any]]}>{children}</Provider>
50-
))}
51-
</AriaRadioGroup>
47+
/>
5248
);
5349
};
5450

packages/components/src/styles/Radio.module.css

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -69,100 +69,3 @@
6969
}
7070
}
7171
}
72-
73-
.card {
74-
border: var(--lp-border-width-200) solid var(--lp-color-border-ui-primary);
75-
border-radius: var(--lp-border-radius-regular);
76-
padding: var(--lp-spacing-400);
77-
background: var(--lp-color-bg-ui-primary);
78-
transition: all var(--lp-duration-100) ease-in-out;
79-
flex-direction: column;
80-
align-items: flex-start;
81-
gap: var(--lp-spacing-300);
82-
83-
&[data-hovered] {
84-
border-color: var(--lp-color-border-interactive-primary-hover);
85-
background: var(--lp-color-bg-interactive-secondary-hover);
86-
}
87-
88-
&[data-pressed] {
89-
border-color: var(--lp-color-border-interactive-primary-active);
90-
background: var(--lp-color-bg-interactive-secondary-active);
91-
}
92-
93-
&[data-focus-visible] {
94-
outline: 1px solid var(--lp-color-shadow-interactive-focus);
95-
outline-offset: 1px;
96-
border-color: var(--lp-color-border-interactive-primary-base);
97-
}
98-
99-
&[data-selected] {
100-
border-color: var(--lp-color-border-interactive-primary-base);
101-
background: var(--lp-color-bg-interactive-primary-subtle);
102-
}
103-
104-
&[data-disabled] {
105-
border-color: var(--lp-color-border-ui-secondary);
106-
background: var(--lp-color-bg-ui-secondary);
107-
color: var(--lp-color-text-interactive-disabled);
108-
cursor: not-allowed;
109-
}
110-
111-
& .circle {
112-
position: absolute;
113-
top: var(--lp-spacing-400);
114-
right: var(--lp-spacing-400);
115-
width: var(--lp-size-20);
116-
height: var(--lp-size-20);
117-
}
118-
119-
&:not(:has([slot='description'])) {
120-
border-bottom-left-radius: 0;
121-
border-bottom-right-radius: 0;
122-
}
123-
124-
&:has([slot='heading']) {
125-
display: grid;
126-
grid-template-areas:
127-
'heading radio'
128-
'description description';
129-
grid-template-columns: 1fr auto;
130-
gap: var(--lp-spacing-300);
131-
align-items: flex-start;
132-
133-
& .circle {
134-
grid-area: radio;
135-
position: static;
136-
}
137-
}
138-
139-
&:has([slot='heading']):not(:has([slot='description'])) {
140-
grid-template-areas: 'heading radio';
141-
}
142-
143-
& [slot='heading'] {
144-
grid-area: heading;
145-
display: flex;
146-
align-items: center;
147-
gap: var(--lp-spacing-300);
148-
}
149-
150-
& [slot='heading'] [slot='icon'] {
151-
display: flex;
152-
align-items: center;
153-
justify-content: center;
154-
flex-shrink: 0;
155-
}
156-
157-
& [slot='heading'] [slot='label'] {
158-
font: var(--lp-text-label-1-semibold);
159-
color: var(--lp-color-text-ui-primary);
160-
}
161-
162-
& [slot='description'] {
163-
grid-area: description;
164-
font: var(--lp-text-body-2-regular);
165-
color: var(--lp-color-text-ui-secondary);
166-
margin-top: var(--lp-spacing-200);
167-
}
168-
}

packages/components/src/styles/RadioGroup.module.css

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,101 @@
1515
.card {
1616
/* Card variant styles for RadioGroup */
1717
}
18+
19+
/* Card variant styling - targets child Radio components when parent has data-variant="card" */
20+
.group[data-variant='card'] label[data-rac] {
21+
border: var(--lp-border-width-200) solid var(--lp-color-border-ui-primary);
22+
border-radius: var(--lp-border-radius-regular);
23+
padding: var(--lp-spacing-400);
24+
background: var(--lp-color-bg-ui-primary);
25+
transition: all var(--lp-duration-100) ease-in-out;
26+
flex-direction: column;
27+
align-items: flex-start;
28+
gap: var(--lp-spacing-300);
29+
30+
&[data-hovered] {
31+
border-color: var(--lp-color-border-interactive-primary-hover);
32+
background: var(--lp-color-bg-interactive-secondary-hover);
33+
}
34+
35+
&[data-pressed] {
36+
border-color: var(--lp-color-border-interactive-primary-active);
37+
background: var(--lp-color-bg-interactive-secondary-active);
38+
}
39+
40+
&[data-focus-visible] {
41+
outline: 1px solid var(--lp-color-shadow-interactive-focus);
42+
outline-offset: 1px;
43+
border-color: var(--lp-color-border-interactive-primary-base);
44+
}
45+
46+
&[data-selected] {
47+
border-color: var(--lp-color-border-interactive-primary-base);
48+
background: var(--lp-color-bg-interactive-primary-subtle);
49+
}
50+
51+
&[data-disabled] {
52+
border-color: var(--lp-color-border-ui-secondary);
53+
background: var(--lp-color-bg-ui-secondary);
54+
color: var(--lp-color-text-interactive-disabled);
55+
cursor: not-allowed;
56+
}
57+
58+
& .circle {
59+
position: absolute;
60+
top: var(--lp-spacing-400);
61+
right: var(--lp-spacing-400);
62+
width: var(--lp-size-20);
63+
height: var(--lp-size-20);
64+
}
65+
66+
&:not(:has([slot='description'])) {
67+
border-bottom-left-radius: 0;
68+
border-bottom-right-radius: 0;
69+
}
70+
71+
&:has([slot='heading']) {
72+
display: grid;
73+
grid-template-areas:
74+
'heading radio'
75+
'description description';
76+
grid-template-columns: 1fr auto;
77+
gap: var(--lp-spacing-300);
78+
align-items: flex-start;
79+
80+
& .circle {
81+
grid-area: radio;
82+
position: static;
83+
}
84+
}
85+
86+
&:has([slot='heading']):not(:has([slot='description'])) {
87+
grid-template-areas: 'heading radio';
88+
}
89+
90+
& [slot='heading'] {
91+
grid-area: heading;
92+
display: flex;
93+
align-items: center;
94+
gap: var(--lp-spacing-300);
95+
}
96+
97+
& [slot='heading'] [slot='icon'] {
98+
display: flex;
99+
align-items: center;
100+
justify-content: center;
101+
flex-shrink: 0;
102+
}
103+
104+
& [slot='heading'] [slot='label'] {
105+
font: var(--lp-text-label-1-semibold);
106+
color: var(--lp-color-text-ui-primary);
107+
}
108+
109+
& [slot='description'] {
110+
grid-area: description;
111+
font: var(--lp-text-body-2-regular);
112+
color: var(--lp-color-text-ui-secondary);
113+
margin-top: var(--lp-spacing-200);
114+
}
115+
}

0 commit comments

Comments
 (0)