add `<Switch />` and `<DropdownMenu.SwitchItem />` (#582)
* add <Switch /> * add <DropdownMenu.SwitchItem /> * unify checkbox and switch * Create warm-countries-cry.md * fix displayName
This commit is contained in:
parent
7f215e15e9
commit
433558a131
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@status-im/components": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `<Switch />` and `<DropdownMenu.SwitchItem />`
|
|
@ -47,6 +47,7 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
|
"@radix-ui/react-switch": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { cva } from 'cva'
|
||||||
|
|
||||||
import type { VariantProps } from 'cva'
|
import type { VariantProps } from 'cva'
|
||||||
|
|
||||||
type Variants = VariantProps<typeof styles>
|
type Variants = VariantProps<typeof rootStyles>
|
||||||
|
|
||||||
type Props = Omit<Checkbox.CheckboxProps, 'checked' | 'onCheckedChange'> & {
|
type Props = Omit<Checkbox.CheckboxProps, 'checked' | 'onCheckedChange'> & {
|
||||||
checked: boolean
|
checked: boolean
|
||||||
|
@ -23,7 +23,7 @@ const Root = forwardRef<React.ElementRef<typeof Checkbox.Root>, Props>(
|
||||||
<Checkbox.Root
|
<Checkbox.Root
|
||||||
{...checkboxProps}
|
{...checkboxProps}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={styles({ variant })}
|
className={rootStyles({ variant })}
|
||||||
>
|
>
|
||||||
<Checkbox.Indicator>
|
<Checkbox.Indicator>
|
||||||
<svg
|
<svg
|
||||||
|
@ -50,9 +50,9 @@ const Root = forwardRef<React.ElementRef<typeof Checkbox.Root>, Props>(
|
||||||
|
|
||||||
Root.displayName = Checkbox.Root.displayName
|
Root.displayName = Checkbox.Root.displayName
|
||||||
|
|
||||||
const styles = cva({
|
const rootStyles = cva({
|
||||||
base: [
|
base: [
|
||||||
'group inline-flex size-[18px] shrink-0 items-center justify-center overflow-hidden rounded-6 text-white-100 transition-colors',
|
'group inline-flex size-[18px] shrink-0 cursor-default items-center justify-center overflow-hidden rounded-6 text-white-100 transition-colors',
|
||||||
'border border-neutral-20 hover:border-neutral-30',
|
'border border-neutral-20 hover:border-neutral-30',
|
||||||
'aria-checked:border-customisation-50 aria-checked:bg-customisation-50 aria-checked:hover:border-customisation-60 aria-checked:hover:bg-customisation-60',
|
'aria-checked:border-customisation-50 aria-checked:bg-customisation-50 aria-checked:hover:border-customisation-60 aria-checked:hover:bg-customisation-60',
|
||||||
'focus-visible:ring-2 focus-visible:ring-customisation-50 focus-visible:ring-offset-2',
|
'focus-visible:ring-2 focus-visible:ring-customisation-50 focus-visible:ring-offset-2',
|
||||||
|
|
|
@ -31,9 +31,8 @@ const meta: Meta = {
|
||||||
},
|
},
|
||||||
|
|
||||||
render: args => {
|
render: args => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
const [isChecked, setIsChecked] = useState(false) // eslint-disable-line react-hooks/rules-of-hooks
|
||||||
const [isChecked, setIsChecked] = useState(false)
|
const [isChecked2, setIsChecked2] = useState(false) // eslint-disable-line react-hooks/rules-of-hooks
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root {...args}>
|
<DropdownMenu.Root {...args}>
|
||||||
<DropdownButton>Open</DropdownButton>
|
<DropdownButton>Open</DropdownButton>
|
||||||
|
@ -100,6 +99,13 @@ const meta: Meta = {
|
||||||
onCheckedChange={setIsChecked}
|
onCheckedChange={setIsChecked}
|
||||||
onSelect={e => e.preventDefault()}
|
onSelect={e => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
|
<DropdownMenu.SwitchItem
|
||||||
|
icon={<NotificationsIcon />}
|
||||||
|
label="Toggle alerts"
|
||||||
|
checked={isChecked2}
|
||||||
|
onCheckedChange={setIsChecked2}
|
||||||
|
onSelect={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { cva } from 'cva'
|
||||||
|
|
||||||
import { Checkbox } from '../checkbox'
|
import { Checkbox } from '../checkbox'
|
||||||
import { Input } from '../input'
|
import { Input } from '../input'
|
||||||
|
import { Switch } from '../switch'
|
||||||
|
|
||||||
import type { IconElement } from '../types'
|
import type { IconElement } from '../types'
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ type ItemProps = DropdownMenu.DropdownMenuItemProps & {
|
||||||
|
|
||||||
const itemStyles = cva({
|
const itemStyles = cva({
|
||||||
base: [
|
base: [
|
||||||
'flex h-8 cursor-pointer select-none items-center gap-2 rounded-8 px-2 text-15 transition-colors active:bg-neutral-10',
|
'flex h-8 select-none items-center gap-2 rounded-8 px-2 text-15 transition-colors active:bg-neutral-10',
|
||||||
'outline-none data-[highlighted]:bg-neutral-5',
|
'outline-none data-[highlighted]:bg-neutral-5',
|
||||||
'dark:active:bg-customisation-50/10 dark:hover:bg-customisation-50/5',
|
'dark:active:bg-customisation-50/10 dark:hover:bg-customisation-50/5',
|
||||||
],
|
],
|
||||||
|
@ -198,7 +199,7 @@ export const CheckboxItem = forwardRef<
|
||||||
variant="outline"
|
variant="outline"
|
||||||
checked={itemProps.checked}
|
checked={itemProps.checked}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
// handle by parent
|
// handled by parent
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
|
@ -207,6 +208,49 @@ export const CheckboxItem = forwardRef<
|
||||||
|
|
||||||
CheckboxItem.displayName = DropdownMenu.CheckboxItem.displayName
|
CheckboxItem.displayName = DropdownMenu.CheckboxItem.displayName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SwitchItem
|
||||||
|
*/
|
||||||
|
|
||||||
|
type SwitchItemProps = DropdownMenu.DropdownMenuCheckboxItemProps & {
|
||||||
|
icon?: IconElement
|
||||||
|
label: string
|
||||||
|
checked: boolean
|
||||||
|
onCheckedChange: (checked: boolean) => void
|
||||||
|
danger?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SwitchItem = forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenu.CheckboxItem>,
|
||||||
|
SwitchItemProps
|
||||||
|
>((props, ref) => {
|
||||||
|
const { label, icon, danger, ...itemProps } = props
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={itemStyles()}
|
||||||
|
{...itemProps}
|
||||||
|
>
|
||||||
|
{icon && (
|
||||||
|
<span className={iconStyles({ danger })}>{cloneElement(icon)}</span>
|
||||||
|
)}
|
||||||
|
<span className={labelStyles({ danger })}>{label}</span>
|
||||||
|
<Switch
|
||||||
|
id={id}
|
||||||
|
checked={itemProps.checked}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
// handled by parent
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
SwitchItem.displayName = 'DropdownMenuSwitchItem'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Separator
|
* Separator
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,7 @@ export { StatusProvider } from './provider'
|
||||||
export { Shortcut } from './shortcut'
|
export { Shortcut } from './shortcut'
|
||||||
export { Skeleton } from './skeleton'
|
export { Skeleton } from './skeleton'
|
||||||
export { Step } from './step'
|
export { Step } from './step'
|
||||||
|
export { Switch } from './switch'
|
||||||
export { Tabs } from './tabs'
|
export { Tabs } from './tabs'
|
||||||
export { Tag } from './tag'
|
export { Tag } from './tag'
|
||||||
export { Text } from './text'
|
export { Text } from './text'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export type { SwitchProps } from './switch'
|
||||||
|
export { Switch } from './switch'
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Switch } from './switch'
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
const Variant = (props: React.ComponentProps<typeof Switch>) => {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Switch {...props} />
|
||||||
|
<Switch {...props} defaultChecked />
|
||||||
|
<Switch {...props} disabled />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta: Meta<typeof Switch> = {
|
||||||
|
title: 'Components/Switch',
|
||||||
|
component: Switch,
|
||||||
|
args: {
|
||||||
|
// size: 'md',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/design/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=370-18368&node-type=frame&m=dev',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render: props => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Variant {...props} />
|
||||||
|
<Variant {...props} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof Switch>
|
||||||
|
|
||||||
|
export const Light: Story = {}
|
||||||
|
|
||||||
|
export const Dark: Story = {
|
||||||
|
parameters: {
|
||||||
|
backgrounds: {
|
||||||
|
default: 'dark',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
|
import * as Switch from '@radix-ui/react-switch'
|
||||||
|
import { cva } from 'cva'
|
||||||
|
|
||||||
|
type Props = Switch.SwitchProps & {
|
||||||
|
checked: boolean
|
||||||
|
onCheckedChange: (checked: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Root = forwardRef<React.ElementRef<typeof Switch.Root>, Props>(
|
||||||
|
(props, ref) => {
|
||||||
|
return (
|
||||||
|
<Switch.Root {...props} ref={ref} className={rootStyles()}>
|
||||||
|
<Switch.Thumb className={thumbStyles()} />
|
||||||
|
</Switch.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Root.displayName = Switch.Root.displayName
|
||||||
|
|
||||||
|
const rootStyles = cva({
|
||||||
|
base: [
|
||||||
|
'relative h-5 w-[30px] shrink-0 cursor-default rounded-full outline-none transition-colors',
|
||||||
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-customisation-50 focus-visible:ring-offset-2',
|
||||||
|
'bg-neutral-20 data-[state=checked]:bg-customisation-blue-50 hover:bg-neutral-30 data-[state=checked]:hover:bg-customisation-blue-60',
|
||||||
|
'dark:bg-neutral-80 dark:data-[state=checked]:bg-customisation-blue-60 dark:hover:bg-neutral-70 dark:data-[state=checked]:hover:bg-customisation-blue-50 dark:focus-visible:ring-offset-neutral-80',
|
||||||
|
'data-[disabled]:pointer-events-none data-[disabled]:opacity-[30%]',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const thumbStyles = cva({
|
||||||
|
base: [
|
||||||
|
'block size-4 rounded-full bg-white-100',
|
||||||
|
'translate-x-0.5 transition-transform duration-100 data-[state=checked]:translate-x-[12px]',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export { Root as Switch }
|
||||||
|
export type { Props as SwitchProps }
|
13
yarn.lock
13
yarn.lock
|
@ -2483,6 +2483,19 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-compose-refs" "1.1.0"
|
"@radix-ui/react-compose-refs" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-switch@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.0.tgz#fcf8e778500f1d60d4b2bec2fc3fad77a7c118e3"
|
||||||
|
integrity sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive" "1.1.0"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.0"
|
||||||
|
"@radix-ui/react-context" "1.1.0"
|
||||||
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||||
|
"@radix-ui/react-use-previous" "1.1.0"
|
||||||
|
"@radix-ui/react-use-size" "1.1.0"
|
||||||
|
|
||||||
"@radix-ui/react-tabs@^1.1.0":
|
"@radix-ui/react-tabs@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0"
|
||||||
|
|
Loading…
Reference in New Issue