[T-4866: feat] Switch component (#92)
* ⚡️ feat: create switch component * 📝 docs: add switch to the example page * 🔧 chore: install `@radix-ui/react-switch` * 🎨 style: add inset shadow * 🎨 style: addjust input outline when error and focus
This commit is contained in:
parent
d2ca4df35a
commit
7d1810ebd9
@ -7,6 +7,7 @@
|
|||||||
"@material-tailwind/react": "^2.1.7",
|
"@material-tailwind/react": "^2.1.7",
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
@ -50,6 +50,7 @@ export const inputTheme = tv(
|
|||||||
'outline-offset-0',
|
'outline-offset-0',
|
||||||
'outline-border-danger',
|
'outline-border-danger',
|
||||||
'shadow-none',
|
'shadow-none',
|
||||||
|
'focus:outline-border-danger',
|
||||||
],
|
],
|
||||||
helperText: 'text-elements-danger',
|
helperText: 'text-elements-danger',
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
import { tv, type VariantProps } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const switchTheme = tv({
|
||||||
|
slots: {
|
||||||
|
wrapper: ['flex', 'items-start', 'gap-4', 'w-[375px]'],
|
||||||
|
switch: [
|
||||||
|
'h-6',
|
||||||
|
'w-12',
|
||||||
|
'rounded-full',
|
||||||
|
'transition-all',
|
||||||
|
'duration-500',
|
||||||
|
'relative',
|
||||||
|
'cursor-default',
|
||||||
|
'shadow-inset',
|
||||||
|
'focus-ring',
|
||||||
|
'outline-none',
|
||||||
|
],
|
||||||
|
thumb: [
|
||||||
|
'block',
|
||||||
|
'h-4',
|
||||||
|
'w-4',
|
||||||
|
'translate-x-1',
|
||||||
|
'transition-transform',
|
||||||
|
'duration-100',
|
||||||
|
'will-change-transform',
|
||||||
|
'rounded-full',
|
||||||
|
'shadow-button',
|
||||||
|
'data-[state=checked]:translate-x-7',
|
||||||
|
'bg-controls-elevated',
|
||||||
|
],
|
||||||
|
label: [
|
||||||
|
'flex',
|
||||||
|
'flex-1',
|
||||||
|
'flex-col',
|
||||||
|
'px-1',
|
||||||
|
'gap-1',
|
||||||
|
'text-sm',
|
||||||
|
'text-elements-high-em',
|
||||||
|
'tracking-[-0.006em]',
|
||||||
|
],
|
||||||
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
checked: {
|
||||||
|
true: {
|
||||||
|
switch: [
|
||||||
|
'bg-controls-primary',
|
||||||
|
'hover:bg-controls-primary-hovered',
|
||||||
|
'focus-visible:bg-controls-primary-hovered',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
false: {
|
||||||
|
switch: [
|
||||||
|
'bg-controls-inset',
|
||||||
|
'hover:bg-controls-inset-hovered',
|
||||||
|
'focus-visible:bg-controls-inset-hovered',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
true: {
|
||||||
|
switch: ['bg-controls-disabled', 'cursor-not-allowed'],
|
||||||
|
thumb: ['bg-elements-on-disabled'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
true: {
|
||||||
|
wrapper: ['w-full', 'justify-between'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
checked: true,
|
||||||
|
disabled: true,
|
||||||
|
class: {
|
||||||
|
switch: ['bg-controls-disabled-active'],
|
||||||
|
thumb: ['bg-snowball-900'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SwitchVariants = VariantProps<typeof switchTheme>;
|
85
packages/frontend/src/components/shared/Switch/Switch.tsx
Normal file
85
packages/frontend/src/components/shared/Switch/Switch.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import React, { type ComponentPropsWithoutRef } from 'react';
|
||||||
|
import { type SwitchProps as SwitchRadixProps } from '@radix-ui/react-switch';
|
||||||
|
import * as SwitchRadix from '@radix-ui/react-switch';
|
||||||
|
|
||||||
|
import { switchTheme, type SwitchVariants } from './Switch.theme';
|
||||||
|
|
||||||
|
interface SwitchProps
|
||||||
|
extends Omit<SwitchRadixProps, 'checked'>,
|
||||||
|
SwitchVariants {
|
||||||
|
/**
|
||||||
|
* The label of the switch.
|
||||||
|
*/
|
||||||
|
label?: string;
|
||||||
|
/**
|
||||||
|
* The description of the switch.
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* Custom wrapper props for the switch.
|
||||||
|
*/
|
||||||
|
wrapperProps?: ComponentPropsWithoutRef<'div'>;
|
||||||
|
/**
|
||||||
|
* Function that is called when the checked state of the switch changes.
|
||||||
|
* @param checked The new checked state of the switch.
|
||||||
|
*/
|
||||||
|
onCheckedChange?(checked: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A switch is a component used for toggling between two states.
|
||||||
|
*/
|
||||||
|
export const Switch = ({
|
||||||
|
className,
|
||||||
|
checked,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
disabled,
|
||||||
|
name,
|
||||||
|
wrapperProps,
|
||||||
|
fullWidth,
|
||||||
|
...props
|
||||||
|
}: SwitchProps) => {
|
||||||
|
const {
|
||||||
|
wrapper,
|
||||||
|
switch: switchClass,
|
||||||
|
thumb,
|
||||||
|
label: labelClass,
|
||||||
|
description: descriptionClass,
|
||||||
|
} = switchTheme({
|
||||||
|
checked,
|
||||||
|
disabled,
|
||||||
|
fullWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchComponent = (
|
||||||
|
<SwitchRadix.Root
|
||||||
|
{...props}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
className={switchClass({ className })}
|
||||||
|
>
|
||||||
|
<SwitchRadix.Thumb className={thumb()} />
|
||||||
|
</SwitchRadix.Root>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If a label is provided, wrap the switch in a label element.
|
||||||
|
if (label) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...wrapperProps}
|
||||||
|
className={wrapper({ className: wrapperProps?.className })}
|
||||||
|
>
|
||||||
|
<label className={labelClass()} htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
{description && (
|
||||||
|
<span className={descriptionClass()}>{description}</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
{switchComponent}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return switchComponent;
|
||||||
|
};
|
1
packages/frontend/src/components/shared/Switch/index.ts
Normal file
1
packages/frontend/src/components/shared/Switch/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Switch';
|
@ -18,6 +18,7 @@ import {
|
|||||||
renderTabs,
|
renderTabs,
|
||||||
renderVerticalTabs,
|
renderVerticalTabs,
|
||||||
} from './renders/tabs';
|
} from './renders/tabs';
|
||||||
|
import { Switch } from 'components/shared/Switch';
|
||||||
import { RADIO_OPTIONS } from './renders/radio';
|
import { RADIO_OPTIONS } from './renders/radio';
|
||||||
import { Radio } from 'components/shared/Radio';
|
import { Radio } from 'components/shared/Radio';
|
||||||
import {
|
import {
|
||||||
@ -30,6 +31,7 @@ import { renderTooltips } from './renders/tooltip';
|
|||||||
const Page = () => {
|
const Page = () => {
|
||||||
const [singleDate, setSingleDate] = useState<Value>();
|
const [singleDate, setSingleDate] = useState<Value>();
|
||||||
const [dateRange, setDateRange] = useState<Value>();
|
const [dateRange, setDateRange] = useState<Value>();
|
||||||
|
const [switchValue, setSwitchValue] = useState(false);
|
||||||
const [selectedRadio, setSelectedRadio] = useState<string>('');
|
const [selectedRadio, setSelectedRadio] = useState<string>('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -163,6 +165,28 @@ const Page = () => {
|
|||||||
|
|
||||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
|
{/* Switch */}
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Switch</h1>
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-center">
|
||||||
|
<Switch
|
||||||
|
label="Label"
|
||||||
|
checked={switchValue}
|
||||||
|
onCheckedChange={setSwitchValue}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
label="Label"
|
||||||
|
description="Additional information or context"
|
||||||
|
checked={switchValue}
|
||||||
|
onCheckedChange={setSwitchValue}
|
||||||
|
/>
|
||||||
|
<Switch disabled label="Disabled unchecked" />
|
||||||
|
<Switch disabled checked label="Disabled checked" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
{/* Radio */}
|
{/* Radio */}
|
||||||
<div className="flex flex-col gap-10 items-center justify-between">
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold">Radio</h1>
|
<h1 className="text-2xl font-bold">Radio</h1>
|
||||||
|
@ -154,6 +154,7 @@ export default withMT({
|
|||||||
calendar:
|
calendar:
|
||||||
'0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)',
|
'0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)',
|
||||||
field: '0px 1px 2px rgba(0, 0, 0, 0.04)',
|
field: '0px 1px 2px rgba(0, 0, 0, 0.04)',
|
||||||
|
inset: 'inset 0px 1px 0px rgba(8, 47, 86, 0.06)',
|
||||||
},
|
},
|
||||||
spacing: {
|
spacing: {
|
||||||
2.5: '0.625rem',
|
2.5: '0.625rem',
|
||||||
|
14
yarn.lock
14
yarn.lock
@ -3480,6 +3480,20 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-compose-refs" "1.0.1"
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
|
||||||
|
"@radix-ui/react-switch@^1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.3.tgz#6119f16656a9eafb4424c600fdb36efa5ec5837e"
|
||||||
|
integrity sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==
|
||||||
|
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-primitive" "1.0.3"
|
||||||
|
"@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-tabs@^1.0.4":
|
"@radix-ui/react-tabs@^1.0.4":
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"
|
||||||
|
Loading…
Reference in New Issue
Block a user