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-dropdown-menu": "^2.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-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
|
|
|
@ -7,7 +7,7 @@ import { cva } from 'cva'
|
|||
|
||||
import type { VariantProps } from 'cva'
|
||||
|
||||
type Variants = VariantProps<typeof styles>
|
||||
type Variants = VariantProps<typeof rootStyles>
|
||||
|
||||
type Props = Omit<Checkbox.CheckboxProps, 'checked' | 'onCheckedChange'> & {
|
||||
checked: boolean
|
||||
|
@ -23,7 +23,7 @@ const Root = forwardRef<React.ElementRef<typeof Checkbox.Root>, Props>(
|
|||
<Checkbox.Root
|
||||
{...checkboxProps}
|
||||
ref={ref}
|
||||
className={styles({ variant })}
|
||||
className={rootStyles({ variant })}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<svg
|
||||
|
@ -50,9 +50,9 @@ const Root = forwardRef<React.ElementRef<typeof Checkbox.Root>, Props>(
|
|||
|
||||
Root.displayName = Checkbox.Root.displayName
|
||||
|
||||
const styles = cva({
|
||||
const rootStyles = cva({
|
||||
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',
|
||||
'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',
|
||||
|
|
|
@ -31,9 +31,8 @@ const meta: Meta = {
|
|||
},
|
||||
|
||||
render: args => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [isChecked, setIsChecked] = useState(false)
|
||||
|
||||
const [isChecked, setIsChecked] = useState(false) // eslint-disable-line react-hooks/rules-of-hooks
|
||||
const [isChecked2, setIsChecked2] = useState(false) // eslint-disable-line react-hooks/rules-of-hooks
|
||||
return (
|
||||
<DropdownMenu.Root {...args}>
|
||||
<DropdownButton>Open</DropdownButton>
|
||||
|
@ -100,6 +99,13 @@ const meta: Meta = {
|
|||
onCheckedChange={setIsChecked}
|
||||
onSelect={e => e.preventDefault()}
|
||||
/>
|
||||
<DropdownMenu.SwitchItem
|
||||
icon={<NotificationsIcon />}
|
||||
label="Toggle alerts"
|
||||
checked={isChecked2}
|
||||
onCheckedChange={setIsChecked2}
|
||||
onSelect={e => e.preventDefault()}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { cva } from 'cva'
|
|||
|
||||
import { Checkbox } from '../checkbox'
|
||||
import { Input } from '../input'
|
||||
import { Switch } from '../switch'
|
||||
|
||||
import type { IconElement } from '../types'
|
||||
|
||||
|
@ -111,7 +112,7 @@ type ItemProps = DropdownMenu.DropdownMenuItemProps & {
|
|||
|
||||
const itemStyles = cva({
|
||||
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',
|
||||
'dark:active:bg-customisation-50/10 dark:hover:bg-customisation-50/5',
|
||||
],
|
||||
|
@ -198,7 +199,7 @@ export const CheckboxItem = forwardRef<
|
|||
variant="outline"
|
||||
checked={itemProps.checked}
|
||||
onCheckedChange={() => {
|
||||
// handle by parent
|
||||
// handled by parent
|
||||
}}
|
||||
/>
|
||||
</DropdownMenu.CheckboxItem>
|
||||
|
@ -207,6 +208,49 @@ export const CheckboxItem = forwardRef<
|
|||
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,7 @@ export { StatusProvider } from './provider'
|
|||
export { Shortcut } from './shortcut'
|
||||
export { Skeleton } from './skeleton'
|
||||
export { Step } from './step'
|
||||
export { Switch } from './switch'
|
||||
export { Tabs } from './tabs'
|
||||
export { Tag } from './tag'
|
||||
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:
|
||||
"@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":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0"
|
||||
|
|
Loading…
Reference in New Issue