forked from cerc-io/snowballtools-base
[T-4864: feat] Radio component (#90)
* 🎨 style: make the cursor of the tab trigger wrapper to default * ⚡️ feat: create radio component * 📝 docs: add radio component to the example page * 🔧 chore: install `@radix-ui/react-radio-group`
This commit is contained in:
parent
30bbe4d766
commit
d2ca4df35a
@ -7,6 +7,7 @@
|
||||
"@material-tailwind/react": "^2.1.7",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
|
54
packages/frontend/src/components/shared/Radio/Radio.theme.ts
Normal file
54
packages/frontend/src/components/shared/Radio/Radio.theme.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const radioTheme = tv({
|
||||
slots: {
|
||||
root: ['flex', 'gap-3', 'flex-wrap'],
|
||||
wrapper: ['flex', 'items-center', 'gap-2', 'group'],
|
||||
label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'],
|
||||
radio: [
|
||||
'w-5',
|
||||
'h-5',
|
||||
'rounded-full',
|
||||
'border',
|
||||
'group',
|
||||
'border-border-interactive/10',
|
||||
'shadow-button',
|
||||
'group-hover:border-border-interactive/[0.14]',
|
||||
'focus-ring',
|
||||
// Checked
|
||||
'data-[state=checked]:bg-controls-primary',
|
||||
'data-[state=checked]:group-hover:bg-controls-primary-hovered',
|
||||
],
|
||||
indicator: [
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'relative',
|
||||
'w-full',
|
||||
'h-full',
|
||||
'after:content-[""]',
|
||||
'after:block',
|
||||
'after:w-2.5',
|
||||
'after:h-2.5',
|
||||
'after:rounded-full',
|
||||
'after:bg-transparent',
|
||||
'after:group-hover:bg-controls-disabled',
|
||||
'after:group-focus-visible:bg-controls-disabled',
|
||||
// Checked
|
||||
'after:data-[state=checked]:bg-elements-on-primary',
|
||||
'after:data-[state=checked]:group-hover:bg-elements-on-primary',
|
||||
'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary',
|
||||
],
|
||||
},
|
||||
variants: {
|
||||
orientation: {
|
||||
vertical: { root: ['flex-col'] },
|
||||
horizontal: { root: ['flex-row'] },
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'vertical',
|
||||
},
|
||||
});
|
||||
|
||||
export type RadioTheme = VariantProps<typeof radioTheme>;
|
63
packages/frontend/src/components/shared/Radio/Radio.tsx
Normal file
63
packages/frontend/src/components/shared/Radio/Radio.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Root as RadixRoot,
|
||||
RadioGroupProps,
|
||||
} from '@radix-ui/react-radio-group';
|
||||
import { RadioTheme, radioTheme } from './Radio.theme';
|
||||
import { RadioItem, RadioItemProps } from './RadioItem';
|
||||
|
||||
export interface RadioOption extends RadioItemProps {
|
||||
/**
|
||||
* The label of the radio option.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The value of the radio option.
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface RadioProps extends RadioGroupProps, RadioTheme {
|
||||
/**
|
||||
* The options of the radio.
|
||||
* @default []
|
||||
* @example
|
||||
* ```tsx
|
||||
* const options = [
|
||||
* {
|
||||
* label: 'Label 1',
|
||||
* value: '1',
|
||||
* },
|
||||
* {
|
||||
* label: 'Label 2',
|
||||
* value: '2',
|
||||
* },
|
||||
* {
|
||||
* label: 'Label 3',
|
||||
* value: '3',
|
||||
* },
|
||||
* ];
|
||||
* ```
|
||||
*/
|
||||
options: RadioOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The Radio component is used to select one option from a list of options.
|
||||
*/
|
||||
export const Radio = ({
|
||||
className,
|
||||
options,
|
||||
orientation,
|
||||
...props
|
||||
}: RadioProps) => {
|
||||
const { root } = radioTheme({ orientation });
|
||||
|
||||
return (
|
||||
<RadixRoot {...props} className={root({ className })}>
|
||||
{options.map((option) => (
|
||||
<RadioItem key={option.value} {...option} />
|
||||
))}
|
||||
</RadixRoot>
|
||||
);
|
||||
};
|
74
packages/frontend/src/components/shared/Radio/RadioItem.tsx
Normal file
74
packages/frontend/src/components/shared/Radio/RadioItem.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React, { ComponentPropsWithoutRef } from 'react';
|
||||
import {
|
||||
Item as RadixRadio,
|
||||
Indicator as RadixIndicator,
|
||||
RadioGroupItemProps,
|
||||
RadioGroupIndicatorProps,
|
||||
} from '@radix-ui/react-radio-group';
|
||||
import { radioTheme } from './Radio.theme';
|
||||
|
||||
export interface RadioItemProps extends RadioGroupItemProps {
|
||||
/**
|
||||
* The wrapper props of the radio item.
|
||||
* You can use this prop to customize the wrapper props.
|
||||
*/
|
||||
wrapperProps?: ComponentPropsWithoutRef<'div'>;
|
||||
/**
|
||||
* The label props of the radio item.
|
||||
* You can use this prop to customize the label props.
|
||||
*/
|
||||
labelProps?: ComponentPropsWithoutRef<'label'>;
|
||||
/**
|
||||
* The indicator props of the radio item.
|
||||
* You can use this prop to customize the indicator props.
|
||||
*/
|
||||
indicatorProps?: RadioGroupIndicatorProps;
|
||||
/**
|
||||
* The id of the radio item.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* The label of the radio item.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RadioItem component is used to render a radio item.
|
||||
*/
|
||||
export const RadioItem = ({
|
||||
className,
|
||||
wrapperProps,
|
||||
labelProps,
|
||||
indicatorProps,
|
||||
label,
|
||||
id,
|
||||
...props
|
||||
}: RadioItemProps) => {
|
||||
const { wrapper, label: labelClass, radio, indicator } = radioTheme();
|
||||
|
||||
// Generate a unique id for the radio item from the label if the id is not provided
|
||||
const kebabCaseLabel = label?.toLowerCase().replace(/\s+/g, '-');
|
||||
const componentId = id ?? kebabCaseLabel;
|
||||
|
||||
return (
|
||||
<div className={wrapper({ className: wrapperProps?.className })}>
|
||||
<RadixRadio {...props} className={radio({ className })} id={componentId}>
|
||||
<RadixIndicator
|
||||
forceMount
|
||||
{...indicatorProps}
|
||||
className={indicator({ className: indicatorProps?.className })}
|
||||
/>
|
||||
</RadixRadio>
|
||||
{label && (
|
||||
<label
|
||||
{...labelProps}
|
||||
className={labelClass({ className: labelProps?.className })}
|
||||
htmlFor={componentId}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
2
packages/frontend/src/components/shared/Radio/index.ts
Normal file
2
packages/frontend/src/components/shared/Radio/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Radio';
|
||||
export * from './RadioItem';
|
@ -9,6 +9,8 @@ export const tabsTheme = tv({
|
||||
// Horizontal – default
|
||||
'px-1',
|
||||
'pb-5',
|
||||
'cursor-default',
|
||||
'select-none',
|
||||
'text-elements-low-em',
|
||||
'border-b-2',
|
||||
'border-transparent',
|
||||
|
@ -18,6 +18,8 @@ import {
|
||||
renderTabs,
|
||||
renderVerticalTabs,
|
||||
} from './renders/tabs';
|
||||
import { RADIO_OPTIONS } from './renders/radio';
|
||||
import { Radio } from 'components/shared/Radio';
|
||||
import {
|
||||
renderInlineNotificationWithDescriptions,
|
||||
renderInlineNotifications,
|
||||
@ -28,6 +30,7 @@ import { renderTooltips } from './renders/tooltip';
|
||||
const Page = () => {
|
||||
const [singleDate, setSingleDate] = useState<Value>();
|
||||
const [dateRange, setDateRange] = useState<Value>();
|
||||
const [selectedRadio, setSelectedRadio] = useState<string>('');
|
||||
|
||||
return (
|
||||
<div className="relative h-full min-h-full">
|
||||
@ -160,6 +163,27 @@ const Page = () => {
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
{/* Radio */}
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Radio</h1>
|
||||
<div className="flex gap-20 items-start">
|
||||
<Radio
|
||||
options={RADIO_OPTIONS}
|
||||
orientation="vertical"
|
||||
value={selectedRadio}
|
||||
onValueChange={setSelectedRadio}
|
||||
/>
|
||||
<Radio
|
||||
options={RADIO_OPTIONS}
|
||||
orientation="horizontal"
|
||||
value={selectedRadio}
|
||||
onValueChange={setSelectedRadio}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
{/* Inline notification */}
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Inline Notification</h1>
|
||||
|
16
packages/frontend/src/pages/components/renders/radio.ts
Normal file
16
packages/frontend/src/pages/components/renders/radio.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { RadioOption } from 'components/shared/Radio';
|
||||
|
||||
export const RADIO_OPTIONS: RadioOption[] = [
|
||||
{
|
||||
label: 'Label 1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'Label 2',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Label 3',
|
||||
value: '3',
|
||||
},
|
||||
];
|
17
yarn.lock
17
yarn.lock
@ -3439,6 +3439,23 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
|
||||
"@radix-ui/react-radio-group@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz#3197f5dcce143bcbf961471bf89320735c0212d3"
|
||||
integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-roving-focus" "1.0.4"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-previous" "1.0.1"
|
||||
"@radix-ui/react-use-size" "1.0.1"
|
||||
|
||||
"@radix-ui/react-roving-focus@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
|
||||
|
Loading…
Reference in New Issue
Block a user