Skip to content

Commit e71dda8

Browse files
committed
Improvements
- add Card/Segement/Button variant to radio - improve button icon style - improve tab segement style - improve checkbox
1 parent b49fb6c commit e71dda8

12 files changed

Lines changed: 453 additions & 344 deletions

src/button/button.styles.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function getButtonStyles(
5555
'group inline-flex gap-x-2 justify-center items-center font-semibold',
5656
'text-base/6 text-(--btn-color) sm:text-sm/6 [&_svg[data-ui=icon]:not([class*=size-])]:size-(--icon-size)',
5757
isPressed && 'scale-[0.98]',
58+
!isIconOnly && '[--icon-color:var(--btn-color)]/50',
5859
!isHovered &&
5960
!isPressed &&
6061
'[&_svg[data-ui=icon]:not([class*=text-])]:text-(--icon-color)',
@@ -74,25 +75,16 @@ export function getButtonStyles(
7475
color === 'green' && '[--btn-color:var(--color-green-600)]',
7576
],
7677
],
77-
solid: [
78-
'bg-(--btn-bg)',
79-
isHovered && !isDisabled && 'opacity-85',
80-
!isIconOnly && '[--icon-color:var(--btn-color)]/75',
81-
],
78+
solid: ['bg-(--btn-bg)', isHovered && !isDisabled && 'opacity-85'],
8279
outline: [
8380
'bg-white dark:bg-white/5',
8481
'shadow-outline in-[[data-ui=button-group]]:shadow-none dark:in-[[data-ui=button-group]]:shadow-none',
8582
isHovered && !isDisabled && 'bg-zinc-50 dark:bg-zinc-800',
86-
!isIconOnly && '[--icon-color:var(--btn-color)]/75',
87-
],
88-
plain: [
89-
isHovered && !isDisabled && 'bg-zinc-200/50 dark:bg-zinc-800/95',
90-
!isIconOnly && '[--icon-color:var(--btn-color)]/75',
9183
],
84+
plain: [isHovered && !isDisabled && 'bg-zinc-200/50 dark:bg-zinc-800/95'],
9285
link: [
9386
'underline underline-offset-4 decoration-(--btn-color)/25',
9487
isHovered && !isDisabled && 'decoration-(--btn-color)',
95-
!isIconOnly && '[--icon-color:var(--btn-color)]',
9688
],
9789
},
9890
size: {
@@ -147,7 +139,7 @@ export function getButtonGroupStyles(
147139
'[&>button:focus-visible]:z-10',
148140
'[&>*:not(:first-child):not(:last-child)]:rounded-none',
149141
'[&>*[data-variant=solid]:not(:first-child)]:border-s',
150-
'[&>*[data-variant=solid]:not(:first-child)]:border-s-[oklch(from_var(--btn-bg)_calc(l*0.85)_c_h)]',
142+
'[&>*[data-variant=solid]:not(:first-child)]:border-s-[oklch(from_var(--btn-color)_calc(l*0.4)_c_h)]',
151143

152144
'[&:has([data-variant=outline])]:rounded-lg',
153145
'[&:has([data-variant=outline])]:shadow-outline',

src/checkbox.tsx

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,70 @@ export interface CheckboxGroupProps
1717
extends Omit<RACCheckboxGroupProps, 'children'> {
1818
children?: ReactNode;
1919
orientation?: 'vertical' | 'horizontal';
20+
variant?: 'checkbox' | 'card' | 'button';
21+
labelPlacement?: 'start' | 'end';
2022
}
2123

24+
type CheckBoxGroupVariant = Pick<
25+
CheckboxGroupProps,
26+
'orientation' | 'variant' | 'labelPlacement'
27+
>;
28+
29+
const CheckboxVariantContext = React.createContext<CheckBoxGroupVariant | null>(
30+
null,
31+
);
32+
33+
const useCheckboxGroupVariantContext = () => {
34+
const {
35+
labelPlacement = 'end',
36+
orientation = 'vertical',
37+
variant = 'checkbox',
38+
} = React.useContext(CheckboxVariantContext) ?? {};
39+
40+
return {
41+
labelPlacement,
42+
orientation,
43+
variant,
44+
};
45+
};
46+
2247
export function CheckboxGroup({
2348
orientation = 'vertical',
49+
variant = 'checkbox',
50+
labelPlacement = 'end',
2451
...props
2552
}: CheckboxGroupProps) {
2653
return (
27-
<RACCheckboxGroup
28-
{...props}
29-
data-orientation={orientation}
30-
className={composeRenderProps(props.className, (className) => {
31-
return twMerge(groupBox, className);
32-
})}
33-
/>
54+
<CheckboxVariantContext.Provider
55+
value={{ variant, orientation, labelPlacement }}
56+
>
57+
<RACCheckboxGroup
58+
{...props}
59+
className={composeRenderProps(props.className, (className) => {
60+
return twMerge(groupBox, className);
61+
})}
62+
/>
63+
</CheckboxVariantContext.Provider>
3464
);
3565
}
3666

3767
export function Checkboxes({
3868
className,
3969
...props
4070
}: React.JSX.IntrinsicElements['div']) {
71+
const { orientation } = useCheckboxGroupVariantContext();
4172
return (
4273
<div
4374
data-ui="box"
4475
className={twMerge(
4576
'flex flex-col',
46-
'group-data-[orientation=horizontal]:flex-row',
47-
'group-data-[orientation=horizontal]:flex-wrap',
77+
orientation === 'horizontal' && [
78+
'flex-row flex-wrap',
79+
'gap-x-4 gap-y-2',
80+
'has([data-ui=description]):not([class*=gap-y])]:gap-y-4',
81+
],
4882
'has-data-[ui=description]:[&_label]:font-medium',
83+
4984
className,
5085
)}
5186
{...props}
@@ -75,11 +110,9 @@ export function CheckboxField({
75110
);
76111
}
77112

78-
type LabelPlacement = 'start' | 'end';
79-
80113
interface CheckboxProps extends RACCheckboxProps {
81-
labelPlacement?: LabelPlacement;
82114
render?: never;
115+
labelPlacement?: CheckboxGroupProps['labelPlacement'];
83116
}
84117

85118
export interface CustomRenderCheckboxProps
@@ -92,6 +125,7 @@ export interface CustomRenderCheckboxProps
92125

93126
export function Checkbox(props: CheckboxProps | CustomRenderCheckboxProps) {
94127
const descriptionContext = React.useContext(DescriptionContext);
128+
const { labelPlacement, orientation } = useCheckboxGroupVariantContext();
95129

96130
if (props.render) {
97131
const { render, ...restProps } = props;
@@ -119,7 +153,8 @@ export function Checkbox(props: CheckboxProps | CustomRenderCheckboxProps) {
119153
);
120154
}
121155

122-
const { labelPlacement = 'end', ...restProps } = props;
156+
const { labelPlacement: itemLabelPlacement, ...restProps } = props;
157+
const placement = itemLabelPlacement ?? labelPlacement;
123158

124159
return (
125160
<RACCheckbox
@@ -130,8 +165,9 @@ export function Checkbox(props: CheckboxProps | CustomRenderCheckboxProps) {
130165
props.className,
131166
(className, renderProps) => {
132167
return twMerge(
133-
'group flex items-center text-base/6 group-data-[orientation=horizontal]:text-nowrap sm:text-sm/6',
134-
labelPlacement === 'start' && 'flex-row-reverse justify-between',
168+
'group flex items-center text-base/6 sm:text-sm/6',
169+
orientation === 'horizontal' && ['text-nowrap'],
170+
placement === 'start' && 'flex-row-reverse justify-between',
135171
renderProps.isDisabled && 'opacity-50',
136172
className,
137173
);
@@ -142,9 +178,9 @@ export function Checkbox(props: CheckboxProps | CustomRenderCheckboxProps) {
142178
return (
143179
<>
144180
<CheckToggle
145-
{...renderProps}
181+
renderProps={renderProps}
146182
className={twMerge(
147-
labelPlacement === 'end' ? 'me-3' : 'ms-3',
183+
placement === 'end' ? 'me-3' : 'ms-3',
148184
'size-4.5 sm:size-4',
149185
)}
150186
/>
@@ -159,35 +195,28 @@ export function Checkbox(props: CheckboxProps | CustomRenderCheckboxProps) {
159195
);
160196
}
161197

162-
type BoxProps = Partial<CheckboxRenderProps> &
163-
Omit<React.JSX.IntrinsicElements['div'], 'children'>;
198+
type BoxProps = {
199+
renderProps?: Partial<CheckboxRenderProps>;
200+
} & Omit<React.JSX.IntrinsicElements['div'], 'children'>;
164201

165-
export function CheckToggle({
166-
isReadOnly,
167-
isHovered,
168-
isSelected,
169-
isIndeterminate,
170-
isInvalid,
171-
isFocusVisible,
172-
className,
173-
...props
174-
}: BoxProps) {
202+
export function CheckToggle({ renderProps, className, ...props }: BoxProps) {
175203
return (
176204
<div
177205
{...props}
178206
data-check-indicator
179207
className={twMerge([
180208
'size-4',
181209
'flex shrink-0 items-center justify-center rounded-sm shadow ring ring-zinc-950/15 dark:ring-white/20',
182-
isReadOnly
210+
renderProps?.isReadOnly
183211
? 'opacity-50'
184-
: isHovered && 'ring-zinc-950/25 dark:ring-white/25',
185-
isSelected || isIndeterminate
212+
: renderProps?.isHovered && 'ring-zinc-950/25 dark:ring-white/25',
213+
renderProps?.isSelected || renderProps?.isIndeterminate
186214
? 'ring-accent bg-accent shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] dark:ring-0'
187215
: 'dark:bg-white/5 dark:[--contract:1.05]',
188216

189-
isInvalid && 'ring-red-600 dark:ring-red-600',
190-
isFocusVisible && 'outline-ring outline-2 outline-offset-3',
217+
renderProps?.isInvalid && 'ring-red-600 dark:ring-red-600',
218+
renderProps?.isFocusVisible &&
219+
'outline-ring outline-2 outline-offset-3',
191220

192221
// when used in menu item as the selected indicator
193222
'in-[&[data-ui=content][data-hovered=true]]:ring-zinc-950/25',
@@ -204,15 +233,15 @@ export function CheckToggle({
204233
<CheckIcon
205234
className={twMerge(
206235
'hidden size-4 text-[lch(from_var(--accent)_calc((49.44_-_l)_*_infinity)_0_0)]',
207-
isSelected && !isIndeterminate && 'inline',
236+
renderProps?.isSelected && !renderProps.isIndeterminate && 'inline',
208237
'in-[&[data-ui=content][data-selected=true]]:inline',
209238
)}
210239
/>
211240

212241
<MinusIcon
213242
className={twMerge(
214243
'hidden size-4 text-[lch(from_var(--accent)_calc((49.44_-_l)_*_infinity)_0_0)]',
215-
isIndeterminate && 'inline',
244+
renderProps?.isIndeterminate && 'inline',
216245
)}
217246
/>
218247
</div>

0 commit comments

Comments
 (0)