feat(ui): new Switch (#11061)

* refactor(ui): migrate switch component from heroui to radix-ui

replace heroui switch implementation with radix-ui for better maintainability
update package.json and yarn.lock to include new dependency

* fix(eslint): enable heroui import restriction for deprecated Switch component

* refactor(ui): update Switch component props from isSelected/onValueChange to checked/onCheckedChange

Standardize Switch component props across the codebase to use checked/onCheckedChange instead of isSelected/onValueChange for better consistency with common React patterns. Also updates loading state prop from isLoading to loading and removes size prop where unnecessary.

The changes include:
- Replacing isSelected with checked
- Replacing onValueChange with onCheckedChange
- Updating isLoading to loading
- Removing redundant size props
- Adjusting styling to accommodate new loading state

* refactor(switch): improve switch component styling and structure

- Add default values for loading and disabled props
- Update styling classes and add group cursor pointer
- Restructure loading indicator and thumb positioning
- Wrap DescriptionSwitch children in flex container

* refactor(ui): improve switch component structure and usage

- Restructure DescriptionSwitch to use explicit props instead of children
- Add label, description, and position props for better customization
- Update all switch usages in SettingsTab to use new props format

* refactor(primitives): simplify switch props by omitting children

Remove redundant children prop from CustomSwitchProps since it's already omitted from the parent type

* fix(switch): add useId for label accessibility in DescriptionSwitch

Ensure proper label association with switch input by generating unique ID using React's useId hook

* refactor(settings): remove commented out SettingRowTitleSmall components

* refactor(SettingsTab): add todo comment for memoization optimization

* feat(switch): add size prop to customize switch dimensions

Add sm, md, and lg size options to the Switch component with corresponding styles. This allows for better visual consistency across different UI contexts.

* style(ui): adjust switch component styling and theme colors

update switch component layout and spacing to improve consistency
modify secondary-foreground color variable to use correct semantic token

* feat(switch): add new switch component styles and animations

- Add new switch.css file with gradient and transition styles
- Update switch.tsx component with new styling classes and animations
- Remove loader icon in favor of animated gradient effect

* fix(i18n): Auto update translations for PR #11061

* style(primitives): remove redundant border style from switch component

* refactor(switch): remove switch.css and update switch component styles

Remove deprecated switch.css file and migrate styles to inline tailwind classes. Update disabled state styling to use opacity instead of linear gradient for better consistency.

* refactor(switch): simplify switch thumb implementation

Replace complex div structure with svg for loading state
Adjust disabled opacity and loading state styling

* style(switch): adjust thumb size and positioning for better consistency

* feat(switch): add storybook documentation for switch component

Add comprehensive Storybook documentation for the Switch component, including:
- Basic usage examples
- Different states (checked, disabled, loading)
- Size variations
- DescriptionSwitch variant
- Real-world usage scenarios
- Accessibility examples
- Form integration examples

Also remove redundant box-content class from switch styles

* fix(switch): adjust thumb positioning for md and lg sizes

* style(primitives): improve switch component styling and spacing

- Add padding to the container
- Simplify label height logic
- Update typography classes for better consistency
- Adjust switch container alignment

* feat(switch): add size prop to DescriptionSwitch component

Add support for sm, md, and lg sizes to DescriptionSwitch component with responsive text sizing. Also includes comprehensive Storybook documentation with examples of all sizes and states.

* style(switch): align label text to right when isLeftSide is true

* refactor(stories): clean up DescriptionSwitch stories by removing unused imports and simplifying JSX

* refactor(ui): rename CustomizedSwitch to Switch for consistency

Simplify component naming by removing redundant 'Customized' prefix and aligning with common naming conventions

* refactor(switch): extract switch root styles into cva variants

Improve maintainability by using class-variance-authority to manage switch root styles and variants

* refactor(switch): extract thumb variants into separate cva function

Improve maintainability by moving switch thumb styling logic into a dedicated variants configuration. This makes the component more readable and easier to modify.

* feat(switch): add classNames prop for custom styling

Allow custom class names to be applied to switch root, thumb, and thumbSvg elements for more flexible styling options.

* feat(switch): add loading animation variants for switch thumb

Extract loading animation logic into separate cva variants for better maintainability and reusability

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Phantom 2025-12-01 17:04:43 +08:00 committed by GitHub
parent 08d4509714
commit 4a38fd6ebc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 1993 additions and 353 deletions

View File

@ -143,19 +143,31 @@ export default defineConfig([
files: ['**/*.{ts,tsx,js,jsx}'],
ignores: [],
rules: {
// 'no-restricted-imports': [
// 'error',
// {
// paths: [
// {
// name: 'antd',
// importNames: ['Flex', 'Switch', 'message', 'Button', 'Tooltip'],
// message:
// '❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
// }
// ]
// }
// ]
'no-restricted-imports': [
'error',
{
paths: [
// {
// name: 'antd',
// importNames: ['Flex', 'Switch', 'message', 'Button', 'Tooltip'],
// message:
// '❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
// },
{
name: 'antd',
importNames: ['Switch'],
message:
'❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
},
{
name: '@heroui/react',
importNames: ['Switch'],
message:
'❌ Do not import the component from heroui directly. It\'s deprecated.'
}
]
}
]
}
},
// Schema key naming convention (cache & preferences)

View File

@ -179,6 +179,7 @@
"@opeoginni/github-copilot-openai-compatible": "^0.1.21",
"@playwright/test": "^1.55.1",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-switch": "^1.2.6",
"@reduxjs/toolkit": "^2.2.5",
"@shikijs/markdown-it": "^3.12.0",
"@swc/plugin-styled-components": "^8.0.4",

View File

@ -1,54 +1,178 @@
import type { SwitchProps } from '@heroui/react'
import { cn, Spinner, Switch } from '@heroui/react'
import { cn } from '@cherrystudio/ui/utils'
import * as SwitchPrimitive from '@radix-ui/react-switch'
import { cva } from 'class-variance-authority'
import * as React from 'react'
import { useId } from 'react'
const switchRootVariants = cva(
[
'cs-switch cs-switch-root',
'group relative cursor-pointer peer inline-flex shrink-0 items-center rounded-full shadow-xs outline-none transition-all',
'data-[state=unchecked]:bg-gray-500/20 data-[state=checked]:bg-primary',
'disabled:cursor-not-allowed disabled:opacity-40',
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50'
],
{
variants: {
size: {
sm: ['w-9 h-5'],
md: ['w-11 h-5.5'],
lg: ['w-11 h-6']
},
loading: {
false: null,
true: ['bg-primary-hover!']
}
},
defaultVariants: {
size: 'md',
loading: false
}
}
)
const switchThumbVariants = cva(
[
'cs-switch cs-switch-thumb',
'pointer-events-none block rounded-full ring-0 transition-all data-[state=unchecked]:translate-x-0'
],
{
variants: {
size: {
sm: ['size-4.5 ml-[1px] data-[state=checked]:translate-x-4'],
md: ['size-[19px] ml-0.5 data-[state=checked]:translate-x-[21px]'],
lg: ['size-5 ml-[3px] data-[state=checked]:translate-x-4.5']
},
loading: {
false: null,
true: ['bg-primary-hover!']
}
},
compoundVariants: [
{
size: 'sm',
loading: true,
className: 'size-3.5 ml-0.5 data-[state=checked]:translate-x-4.5'
},
{
size: 'md',
loading: true,
className: 'size-4 ml-1 data-[state=checked]:translate-x-5'
},
{
size: 'lg',
loading: true,
className: 'size-4.5 ml-1 data-[state=checked]:translate-x-4.5'
}
]
}
)
const switchThumbSvgVariants = cva(['transition-all'], {
variants: {
loading: {
false: null,
true: ['animate-spin']
}
},
defaultVariants: {
loading: false
}
})
// Enhanced Switch component with loading state support
interface CustomSwitchProps extends SwitchProps {
isLoading?: boolean
interface SwitchProps extends Omit<React.ComponentProps<typeof SwitchPrimitive.Root>, 'children'> {
/** When true, displays a loading animation in the switch thumb. Defaults to false when undefined. */
loading?: boolean
size?: 'sm' | 'md' | 'lg'
classNames?: {
root?: string
thumb?: string
thumbSvg?: string
}
}
/**
* A customized Switch component based on HeroUI Switch
* @see https://www.heroui.com/docs/components/switch#api
* @param isLoading When true, displays a loading spinner in the switch thumb
*/
const CustomizedSwitch = ({ isLoading, children, ref, thumbIcon, ...props }: CustomSwitchProps) => {
const finalThumbIcon = isLoading ? <Spinner size="sm" /> : thumbIcon
function Switch({ loading = false, size = 'md', className, classNames, ...props }: SwitchProps) {
return (
<Switch ref={ref} {...props} thumbIcon={finalThumbIcon}>
{children}
</Switch>
)
}
const DescriptionSwitch = ({ children, ...props }: CustomSwitchProps) => {
return (
<CustomizedSwitch
size="sm"
classNames={{
base: cn(
'inline-flex w-full max-w-md flex-row-reverse items-center hover:bg-content2',
'cursor-pointer justify-between gap-2 rounded-lg border-2 border-transparent py-2 pr-1',
'data-[selected=true]:border-primary'
),
wrapper: 'p-0 h-4 overflow-visible',
thumb: cn(
'h-6 w-6 border-2 shadow-lg',
'group-data-[hover=true]:border-primary',
//selected
'group-data-[selected=true]:ms-6',
// pressed
'group-data-[pressed=true]:w-7',
'group-data-pressed:group-data-selected:ms-4'
)
}}
<SwitchPrimitive.Root
data-slot="switch"
className={cn(switchRootVariants({ size, loading }), className, classNames?.root)}
{...props}>
{children}
</CustomizedSwitch>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(switchThumbVariants({ size, loading }), classNames?.thumb)}>
<svg
width="inherit"
height="inherit"
viewBox="0 0 19 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={cn(switchThumbSvgVariants({ loading }), classNames?.thumbSvg)}>
<path
d="M9.5 0C14.7467 0 19 4.25329 19 9.5C19 14.7467 14.7467 19 9.5 19C4.25329 19 0 14.7467 0 9.5C0 4.25329 4.25329 0 9.5 0ZM9.5 6.33301C8.91711 6.33301 8.44445 6.8058 8.44434 7.38867V11.6113C8.44445 12.1942 8.91711 12.667 9.5 12.667C10.0829 12.667 10.5555 12.1942 10.5557 11.6113V7.38867C10.5555 6.8058 10.0829 6.33301 9.5 6.33301Z"
fill="white"
/>
</svg>
</SwitchPrimitive.Thumb>
</SwitchPrimitive.Root>
)
}
CustomizedSwitch.displayName = 'Switch'
interface DescriptionSwitchProps extends SwitchProps {
/** Text label displayed next to the switch. */
label: string
/** Optional helper text shown below the label. */
description?: string
/** Switch position relative to label. Defaults to 'right'. */
position?: 'left' | 'right'
}
export { DescriptionSwitch, CustomizedSwitch as Switch }
export type { CustomSwitchProps as SwitchProps }
// TODO: It's not finished. We need to use Typography components instead of native html element.
const DescriptionSwitch = ({
label,
description,
position = 'right',
size = 'md',
...props
}: DescriptionSwitchProps) => {
const isLeftSide = position === 'left'
const id = useId()
return (
<div className={cn('flex w-full gap-3 justify-between p-4xs', isLeftSide && 'flex-row-reverse')}>
<label className={cn('flex flex-col gap-5xs cursor-pointer')} htmlFor={id}>
{/* TODO: use standard typography component */}
<p
className={cn(
'font-medium tracking-normal',
{
'text-sm leading-4': size === 'sm',
'text-md leading-4.5': size === 'md',
'text-lg leading-5.5': size === 'lg'
},
isLeftSide && 'text-right'
)}>
{label}
</p>
{/* TODO: use standard typography component */}
{description && (
<span
className={cn('text-foreground-secondary', {
'text-[10px] leading-3': size === 'sm',
'text-xs leading-3.5': size === 'md',
'text-sm leading-4': size === 'lg'
})}>
{description}
</span>
)}
</label>
<div className="flex justify-center items-center">
<Switch id={id} size={size} {...props} />
</div>
</div>
)
}
Switch.displayName = 'Switch'
export { DescriptionSwitch, Switch }
export type { SwitchProps }

View File

@ -0,0 +1,823 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Bell, Eye, Lock, Moon, Shield, Wifi, Zap } from 'lucide-react'
import { useState } from 'react'
import { DescriptionSwitch } from '../../../src/components/primitives/switch'
const meta: Meta<typeof DescriptionSwitch> = {
title: 'Components/Primitives/DescriptionSwitch',
component: DescriptionSwitch,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'An enhanced Switch component with integrated label and optional description text. Perfect for settings panels and preference forms where context is important. Built on top of the Radix UI Switch primitive with support for multiple sizes, loading states, and flexible positioning.'
}
}
},
tags: ['autodocs'],
argTypes: {
label: {
control: { type: 'text' },
description: 'Text label displayed next to the switch (required)'
},
description: {
control: { type: 'text' },
description: 'Optional helper text shown below the label'
},
position: {
control: { type: 'select' },
options: ['left', 'right'],
description: 'Switch position relative to label'
},
disabled: {
control: { type: 'boolean' },
description: 'Whether the switch is disabled'
},
loading: {
control: { type: 'boolean' },
description: 'When true, displays a loading animation in the switch thumb'
},
size: {
control: { type: 'select' },
options: ['sm', 'md', 'lg'],
description: 'The size of the switch'
},
defaultChecked: {
control: { type: 'boolean' },
description: 'Default checked state'
},
checked: {
control: { type: 'boolean' },
description: 'Checked state in controlled mode'
}
}
}
export default meta
type Story = StoryObj<typeof meta>
// Default
export const Default: Story = {
render: () => (
<div className="w-[400px]">
<DescriptionSwitch label="Enable notifications" description="Receive alerts for important updates" />
</div>
)
}
// Without Description
export const WithoutDescription: Story = {
render: () => (
<div className="flex w-[400px] flex-col gap-4">
<DescriptionSwitch label="Enable notifications" />
<DescriptionSwitch label="Auto-save changes" defaultChecked />
<DescriptionSwitch label="Dark mode" />
</div>
)
}
// With Description
export const WithDescription: Story = {
render: () => (
<div className="flex w-[400px] flex-col gap-4">
<DescriptionSwitch label="Enable notifications" description="Receive alerts for important updates" />
<DescriptionSwitch
label="Auto-save changes"
description="Automatically save your work as you type"
defaultChecked
/>
<DescriptionSwitch label="Dark mode" description="Use dark theme for better visibility at night" />
</div>
)
}
// Positions
export const Positions: Story = {
render: () => (
<div className="flex w-[500px] flex-col gap-8">
<div>
<h3 className="mb-4 text-sm font-semibold">Switch on Right (Default)</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Get notified about new messages and updates"
position="right"
/>
<DescriptionSwitch
label="Push notifications"
description="Receive instant alerts on your device"
position="right"
defaultChecked
/>
<DescriptionSwitch
label="Marketing emails"
description="Stay informed about new features and offers"
position="right"
/>
</div>
</div>
<div>
<h3 className="mb-4 text-sm font-semibold">Switch on Left</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Get notified about new messages and updates"
position="left"
/>
<DescriptionSwitch
label="Push notifications"
description="Receive instant alerts on your device"
position="left"
defaultChecked
/>
<DescriptionSwitch
label="Marketing emails"
description="Stay informed about new features and offers"
position="left"
/>
</div>
</div>
</div>
)
}
// Sizes
export const Sizes: Story = {
render: () => (
<div className="flex w-[400px] flex-col gap-6">
<div>
<p className="mb-3 text-sm text-muted-foreground">Small (sm)</p>
<DescriptionSwitch
label="Small switch"
description="Compact size for dense layouts and space-constrained interfaces"
size="sm"
/>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Medium (md) - Default</p>
<DescriptionSwitch
label="Medium switch"
description="Default size that works well in most situations"
size="md"
defaultChecked
/>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Large (lg)</p>
<DescriptionSwitch
label="Large switch"
description="Larger size for emphasis and improved touch targets"
size="lg"
/>
</div>
</div>
)
}
// States
export const States: Story = {
render: () => (
<div className="flex w-[400px] flex-col gap-4">
<div>
<p className="mb-3 text-sm text-muted-foreground">Normal (Unchecked)</p>
<DescriptionSwitch label="Normal state" description="Default interactive state, ready to be toggled" />
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Checked</p>
<DescriptionSwitch label="Checked state" description="Currently enabled and active" defaultChecked />
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Disabled (Unchecked)</p>
<DescriptionSwitch label="Disabled state" description="Cannot be toggled, currently inactive" disabled />
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Disabled (Checked)</p>
<DescriptionSwitch
label="Disabled state"
description="Enabled but locked, cannot be changed"
disabled
defaultChecked
/>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Loading</p>
<DescriptionSwitch
label="Loading state"
description="Processing your request, please wait"
loading
defaultChecked
/>
</div>
</div>
)
}
// Controlled
export const Controlled: Story = {
render: function ControlledExample() {
const [checked, setChecked] = useState(false)
return (
<div className="flex flex-col gap-6">
<div className="w-[400px]">
<DescriptionSwitch
label="Controlled switch"
description="This switch is controlled by React state"
checked={checked}
onCheckedChange={setChecked}
/>
</div>
<div className="flex items-center gap-4">
<div className="text-sm text-muted-foreground">Current state: {checked ? 'On' : 'Off'}</div>
<button
type="button"
onClick={() => setChecked(!checked)}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90">
Toggle State
</button>
</div>
</div>
)
}
}
// Long Text
export const LongText: Story = {
render: () => (
<div className="flex w-[500px] flex-col gap-4">
<DescriptionSwitch
label="Enable comprehensive analytics and tracking"
description="When enabled, this feature will collect and analyze detailed usage statistics, user behavior patterns, interaction data, and performance metrics to help improve the application experience and provide personalized recommendations."
/>
<DescriptionSwitch
label="Short label"
description="This is a very long description that explains in great detail what this particular setting does, why it might be useful, what the implications are of enabling or disabling it, and any other relevant information that users should know before making a decision."
defaultChecked
/>
</div>
)
}
// Notification Settings Example
export const NotificationSettings: Story = {
render: function NotificationSettingsExample() {
const [notifications, setNotifications] = useState({
email: true,
push: false,
sms: false,
desktop: true,
mobile: false,
weekly: true
})
return (
<div className="w-[500px] space-y-6">
<div>
<h3 className="mb-4 text-base font-semibold">Notification Preferences</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Receive updates and alerts via email"
checked={notifications.email}
onCheckedChange={(checked) => setNotifications({ ...notifications, email: !!checked })}
/>
<DescriptionSwitch
label="Push notifications"
description="Get instant notifications on this device"
checked={notifications.push}
onCheckedChange={(checked) => setNotifications({ ...notifications, push: !!checked })}
/>
<DescriptionSwitch
label="SMS notifications"
description="Receive text message alerts for critical updates"
checked={notifications.sms}
onCheckedChange={(checked) => setNotifications({ ...notifications, sms: !!checked })}
/>
<DescriptionSwitch
label="Desktop notifications"
description="Show notifications on your desktop"
checked={notifications.desktop}
onCheckedChange={(checked) => setNotifications({ ...notifications, desktop: !!checked })}
/>
<DescriptionSwitch
label="Mobile notifications"
description="Receive alerts on your mobile device"
checked={notifications.mobile}
onCheckedChange={(checked) => setNotifications({ ...notifications, mobile: !!checked })}
/>
<DescriptionSwitch
label="Weekly digest"
description="Get a summary of activity every week"
checked={notifications.weekly}
onCheckedChange={(checked) => setNotifications({ ...notifications, weekly: !!checked })}
/>
</div>
</div>
</div>
)
}
}
// Privacy Settings Example
export const PrivacySettings: Story = {
render: function PrivacySettingsExample() {
const [privacy, setPrivacy] = useState({
profileVisible: true,
activityTracking: false,
dataSharing: false,
personalization: true,
thirdParty: false
})
return (
<div className="w-[500px] space-y-6">
<div>
<h3 className="mb-4 text-base font-semibold">Privacy & Data</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Public profile"
description="Make your profile visible to other users"
checked={privacy.profileVisible}
onCheckedChange={(checked) => setPrivacy({ ...privacy, profileVisible: !!checked })}
/>
<DescriptionSwitch
label="Activity tracking"
description="Allow us to track your activity to improve services"
checked={privacy.activityTracking}
onCheckedChange={(checked) => setPrivacy({ ...privacy, activityTracking: !!checked })}
/>
<DescriptionSwitch
label="Data sharing"
description="Share anonymous usage data with partners"
checked={privacy.dataSharing}
onCheckedChange={(checked) => setPrivacy({ ...privacy, dataSharing: !!checked })}
/>
<DescriptionSwitch
label="Personalization"
description="Use your data to personalize your experience"
checked={privacy.personalization}
onCheckedChange={(checked) => setPrivacy({ ...privacy, personalization: !!checked })}
/>
<DescriptionSwitch
label="Third-party cookies"
description="Allow third-party cookies for enhanced features"
checked={privacy.thirdParty}
onCheckedChange={(checked) => setPrivacy({ ...privacy, thirdParty: !!checked })}
/>
</div>
</div>
</div>
)
}
}
// Application Settings Example
export const ApplicationSettings: Story = {
render: function ApplicationSettingsExample() {
const [settings, setSettings] = useState({
autoSave: true,
spellCheck: true,
darkMode: false,
compactMode: false,
animations: true,
sound: false,
offlineMode: false
})
return (
<div className="w-[500px] space-y-6">
<div>
<h3 className="mb-4 text-base font-semibold">Application Settings</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Auto-save"
description="Automatically save your work every few minutes"
checked={settings.autoSave}
onCheckedChange={(checked) => setSettings({ ...settings, autoSave: !!checked })}
/>
<DescriptionSwitch
label="Spell check"
description="Check spelling as you type"
checked={settings.spellCheck}
onCheckedChange={(checked) => setSettings({ ...settings, spellCheck: !!checked })}
/>
<DescriptionSwitch
label="Dark mode"
description="Use dark theme throughout the application"
checked={settings.darkMode}
onCheckedChange={(checked) => setSettings({ ...settings, darkMode: !!checked })}
/>
<DescriptionSwitch
label="Compact mode"
description="Reduce spacing for a more dense layout"
checked={settings.compactMode}
onCheckedChange={(checked) => setSettings({ ...settings, compactMode: !!checked })}
/>
<DescriptionSwitch
label="Animations"
description="Enable smooth transitions and animations"
checked={settings.animations}
onCheckedChange={(checked) => setSettings({ ...settings, animations: !!checked })}
/>
<DescriptionSwitch
label="Sound effects"
description="Play sounds for notifications and actions"
checked={settings.sound}
onCheckedChange={(checked) => setSettings({ ...settings, sound: !!checked })}
/>
<DescriptionSwitch
label="Offline mode"
description="Enable working without internet connection"
checked={settings.offlineMode}
onCheckedChange={(checked) => setSettings({ ...settings, offlineMode: !!checked })}
/>
</div>
</div>
</div>
)
}
}
// With Icons
export const WithIcons: Story = {
render: () => (
<div className="flex w-[500px] flex-col gap-4">
<div className="flex items-start gap-3">
<Bell className="mt-1 size-5 text-muted-foreground" />
<div className="flex-1">
<DescriptionSwitch label="Notifications" description="Receive alerts for important updates" defaultChecked />
</div>
</div>
<div className="flex items-start gap-3">
<Moon className="mt-1 size-5 text-muted-foreground" />
<div className="flex-1">
<DescriptionSwitch label="Dark mode" description="Use dark theme for better visibility at night" />
</div>
</div>
<div className="flex items-start gap-3">
<Shield className="mt-1 size-5 text-muted-foreground" />
<div className="flex-1">
<DescriptionSwitch
label="Two-factor authentication"
description="Add an extra layer of security to your account"
/>
</div>
</div>
<div className="flex items-start gap-3">
<Wifi className="mt-1 size-5 text-muted-foreground" />
<div className="flex-1">
<DescriptionSwitch label="Offline mode" description="Work without internet connection" defaultChecked />
</div>
</div>
<div className="flex items-start gap-3">
<Zap className="mt-1 size-5 text-muted-foreground" />
<div className="flex-1">
<DescriptionSwitch
label="Performance mode"
description="Optimize for speed and responsiveness"
defaultChecked
/>
</div>
</div>
</div>
)
}
// Loading Simulation
export const LoadingSimulation: Story = {
render: function LoadingSimulationExample() {
const [states, setStates] = useState({
wifi: { enabled: false, loading: false },
bluetooth: { enabled: false, loading: false },
location: { enabled: false, loading: false }
})
const handleToggle = async (setting: keyof typeof states, checked: boolean) => {
setStates((prev) => ({
...prev,
[setting]: { ...prev[setting], loading: true }
}))
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1500))
setStates((prev) => ({
...prev,
[setting]: { enabled: checked, loading: false }
}))
}
return (
<div className="w-[500px] space-y-6">
<div>
<h3 className="mb-4 text-base font-semibold">System Settings</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Wi-Fi"
description="Connect to wireless networks"
checked={states.wifi.enabled}
onCheckedChange={(checked) => handleToggle('wifi', !!checked)}
loading={states.wifi.loading}
disabled={states.wifi.loading}
/>
<DescriptionSwitch
label="Bluetooth"
description="Connect to Bluetooth devices"
checked={states.bluetooth.enabled}
onCheckedChange={(checked) => handleToggle('bluetooth', !!checked)}
loading={states.bluetooth.loading}
disabled={states.bluetooth.loading}
/>
<DescriptionSwitch
label="Location services"
description="Allow apps to use your location"
checked={states.location.enabled}
onCheckedChange={(checked) => handleToggle('location', !!checked)}
loading={states.location.loading}
disabled={states.location.loading}
/>
</div>
</div>
<p className="text-xs text-muted-foreground">Toggle switches to see a simulated 1.5-second loading state</p>
</div>
)
}
}
// Complex Settings Panel
export const ComplexSettingsPanel: Story = {
render: function ComplexSettingsPanelExample() {
const [settings, setSettings] = useState({
notifications: {
email: true,
push: false,
desktop: true
},
privacy: {
profile: true,
activity: false,
analytics: true
},
features: {
autoSave: true,
darkMode: false,
compactView: false
},
security: {
twoFactor: false,
biometric: true,
sessionTimeout: false
}
})
return (
<div className="w-[600px] space-y-8">
{/* Notifications Section */}
<div>
<div className="mb-4 flex items-center gap-2">
<Bell className="size-5" />
<h3 className="text-base font-semibold">Notifications</h3>
</div>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Receive updates and alerts via email"
checked={settings.notifications.email}
onCheckedChange={(checked) =>
setSettings({
...settings,
notifications: { ...settings.notifications, email: !!checked }
})
}
/>
<DescriptionSwitch
label="Push notifications"
description="Get instant notifications on this device"
checked={settings.notifications.push}
onCheckedChange={(checked) =>
setSettings({
...settings,
notifications: { ...settings.notifications, push: !!checked }
})
}
/>
<DescriptionSwitch
label="Desktop notifications"
description="Show notifications on your desktop"
checked={settings.notifications.desktop}
onCheckedChange={(checked) =>
setSettings({
...settings,
notifications: { ...settings.notifications, desktop: !!checked }
})
}
/>
</div>
</div>
{/* Privacy Section */}
<div>
<div className="mb-4 flex items-center gap-2">
<Eye className="size-5" />
<h3 className="text-base font-semibold">Privacy</h3>
</div>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Public profile"
description="Make your profile visible to other users"
checked={settings.privacy.profile}
onCheckedChange={(checked) =>
setSettings({
...settings,
privacy: { ...settings.privacy, profile: !!checked }
})
}
/>
<DescriptionSwitch
label="Activity tracking"
description="Allow us to track your activity"
checked={settings.privacy.activity}
onCheckedChange={(checked) =>
setSettings({
...settings,
privacy: { ...settings.privacy, activity: !!checked }
})
}
/>
<DescriptionSwitch
label="Analytics"
description="Help improve the app by sharing usage data"
checked={settings.privacy.analytics}
onCheckedChange={(checked) =>
setSettings({
...settings,
privacy: { ...settings.privacy, analytics: !!checked }
})
}
/>
</div>
</div>
{/* Features Section */}
<div>
<div className="mb-4 flex items-center gap-2">
<Zap className="size-5" />
<h3 className="text-base font-semibold">Features</h3>
</div>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Auto-save"
description="Automatically save your work"
checked={settings.features.autoSave}
onCheckedChange={(checked) =>
setSettings({
...settings,
features: { ...settings.features, autoSave: !!checked }
})
}
/>
<DescriptionSwitch
label="Dark mode"
description="Use dark theme throughout the app"
checked={settings.features.darkMode}
onCheckedChange={(checked) =>
setSettings({
...settings,
features: { ...settings.features, darkMode: !!checked }
})
}
/>
<DescriptionSwitch
label="Compact view"
description="Reduce spacing for more content"
checked={settings.features.compactView}
onCheckedChange={(checked) =>
setSettings({
...settings,
features: { ...settings.features, compactView: !!checked }
})
}
/>
</div>
</div>
{/* Security Section */}
<div>
<div className="mb-4 flex items-center gap-2">
<Lock className="size-5" />
<h3 className="text-base font-semibold">Security</h3>
</div>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Two-factor authentication"
description="Require a second verification step when signing in"
checked={settings.security.twoFactor}
onCheckedChange={(checked) =>
setSettings({
...settings,
security: { ...settings.security, twoFactor: !!checked }
})
}
/>
<DescriptionSwitch
label="Biometric authentication"
description="Use fingerprint or face recognition"
checked={settings.security.biometric}
onCheckedChange={(checked) =>
setSettings({
...settings,
security: { ...settings.security, biometric: !!checked }
})
}
/>
<DescriptionSwitch
label="Auto session timeout"
description="Automatically sign out after inactivity"
checked={settings.security.sessionTimeout}
onCheckedChange={(checked) =>
setSettings({
...settings,
security: { ...settings.security, sessionTimeout: !!checked }
})
}
/>
</div>
</div>
</div>
)
}
}
// Accessibility Features
export const AccessibilityFeatures: Story = {
render: () => (
<div className="w-[500px] space-y-6">
<div>
<h3 className="mb-4 text-base font-semibold">Keyboard Navigation</h3>
<p className="mb-4 text-sm text-muted-foreground">
Use Tab to navigate between switches and Space/Enter to toggle them. Each switch has a proper label for screen
readers.
</p>
<div className="flex flex-col gap-4">
<DescriptionSwitch label="High contrast mode" description="Increase contrast for better visibility" />
<DescriptionSwitch label="Reduce motion" description="Minimize animations and transitions" />
<DescriptionSwitch
label="Screen reader optimization"
description="Optimize interface for screen readers"
defaultChecked
/>
<DescriptionSwitch label="Large text" description="Increase font size throughout the app" />
</div>
</div>
</div>
)
}
// Responsive Layout
export const ResponsiveLayout: Story = {
render: () => (
<div className="space-y-6">
<div className="w-[300px]">
<h3 className="mb-4 text-sm font-semibold">Narrow Layout (300px)</h3>
<div className="flex flex-col gap-3">
<DescriptionSwitch label="Notifications" description="Receive important alerts" size="sm" />
<DescriptionSwitch label="Auto-save" description="Save automatically" size="sm" defaultChecked />
</div>
</div>
<div className="w-[500px]">
<h3 className="mb-4 text-sm font-semibold">Standard Layout (500px)</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch label="Notifications" description="Receive alerts for important updates and messages" />
<DescriptionSwitch label="Auto-save" description="Automatically save your work as you type" defaultChecked />
</div>
</div>
<div className="w-[700px]">
<h3 className="mb-4 text-sm font-semibold">Wide Layout (700px)</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Notifications"
description="Receive alerts for important updates, messages, and system notifications to stay informed"
size="lg"
/>
<DescriptionSwitch
label="Auto-save"
description="Automatically save your work as you type to prevent data loss and ensure your progress is always preserved"
size="lg"
defaultChecked
/>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,666 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Bell, Moon, Shield, Wifi, Zap } from 'lucide-react'
import { useState } from 'react'
import { DescriptionSwitch, Switch } from '../../../src/components/primitives/switch'
const meta: Meta<typeof Switch> = {
title: 'Components/Primitives/Switch',
component: Switch,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'A switch component based on Radix UI Switch, allowing users to toggle between on/off states. Supports three sizes (sm, md, lg), loading states, and an enhanced DescriptionSwitch variant with label and description. Built with accessibility in mind.'
}
}
},
tags: ['autodocs'],
argTypes: {
disabled: {
control: { type: 'boolean' },
description: 'Whether the switch is disabled'
},
loading: {
control: { type: 'boolean' },
description: 'When true, displays a loading animation in the switch thumb'
},
size: {
control: { type: 'select' },
options: ['sm', 'md', 'lg'],
description: 'The size of the switch'
},
defaultChecked: {
control: { type: 'boolean' },
description: 'Default checked state'
},
checked: {
control: { type: 'boolean' },
description: 'Checked state in controlled mode'
}
}
}
export default meta
type Story = StoryObj<typeof meta>
// Default
export const Default: Story = {
render: () => (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Switch id="default1" />
<label htmlFor="default1" className="cursor-pointer text-sm">
Enable notifications
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="default2" />
<label htmlFor="default2" className="cursor-pointer text-sm">
Auto-save changes
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="default3" />
<label htmlFor="default3" className="cursor-pointer text-sm">
Dark mode
</label>
</div>
</div>
)
}
// With Default Checked
export const WithDefaultChecked: Story = {
render: () => (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Switch id="checked1" defaultChecked />
<label htmlFor="checked1" className="cursor-pointer text-sm">
Option 1 (Default On)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="checked2" />
<label htmlFor="checked2" className="cursor-pointer text-sm">
Option 2
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="checked3" defaultChecked />
<label htmlFor="checked3" className="cursor-pointer text-sm">
Option 3 (Default On)
</label>
</div>
</div>
)
}
// Disabled
export const Disabled: Story = {
render: () => (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Switch id="disabled1" disabled />
<label htmlFor="disabled1" className="cursor-not-allowed text-sm opacity-50">
Disabled (Off)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="disabled2" disabled defaultChecked />
<label htmlFor="disabled2" className="cursor-not-allowed text-sm opacity-50">
Disabled (On)
</label>
</div>
</div>
)
}
// Loading State
export const Loading: Story = {
render: () => (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Switch id="loading1" loading />
<label htmlFor="loading1" className="cursor-pointer text-sm">
Loading state (Off)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="loading2" loading defaultChecked />
<label htmlFor="loading2" className="cursor-pointer text-sm">
Loading state (On)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="loading3" loading disabled defaultChecked />
<label htmlFor="loading3" className="cursor-not-allowed text-sm opacity-50">
Loading + Disabled
</label>
</div>
</div>
)
}
// Controlled
export const Controlled: Story = {
render: function ControlledExample() {
const [checked, setChecked] = useState(false)
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Switch id="controlled" checked={checked} onCheckedChange={setChecked} />
<label htmlFor="controlled" className="cursor-pointer text-sm">
Controlled switch
</label>
</div>
<div className="text-sm text-muted-foreground">Current state: {checked ? 'On' : 'Off'}</div>
<button
onClick={() => setChecked(!checked)}
className="w-fit rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90">
Toggle State
</button>
</div>
)
}
}
// Sizes
export const Sizes: Story = {
render: () => (
<div className="flex flex-col gap-6">
<div>
<p className="mb-3 text-sm text-muted-foreground">Small (sm)</p>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Switch id="size-sm-1" size="sm" />
<label htmlFor="size-sm-1" className="cursor-pointer text-sm">
Small switch
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-sm-2" size="sm" defaultChecked />
<label htmlFor="size-sm-2" className="cursor-pointer text-sm">
Small switch (on)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-sm-3" size="sm" loading defaultChecked />
<label htmlFor="size-sm-3" className="cursor-pointer text-sm">
Small switch (loading)
</label>
</div>
</div>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Medium (md) - Default</p>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Switch id="size-md-1" size="md" />
<label htmlFor="size-md-1" className="cursor-pointer text-sm">
Medium switch
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-md-2" size="md" defaultChecked />
<label htmlFor="size-md-2" className="cursor-pointer text-sm">
Medium switch (on)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-md-3" size="md" loading defaultChecked />
<label htmlFor="size-md-3" className="cursor-pointer text-sm">
Medium switch (loading)
</label>
</div>
</div>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Large (lg)</p>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Switch id="size-lg-1" size="lg" />
<label htmlFor="size-lg-1" className="cursor-pointer text-sm">
Large switch
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-lg-2" size="lg" defaultChecked />
<label htmlFor="size-lg-2" className="cursor-pointer text-sm">
Large switch (on)
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="size-lg-3" size="lg" loading defaultChecked />
<label htmlFor="size-lg-3" className="cursor-pointer text-sm">
Large switch (loading)
</label>
</div>
</div>
</div>
</div>
)
}
// Description Switch - Basic
export const DescriptionSwitchBasic: Story = {
render: () => (
<div className="flex w-96 flex-col gap-4">
<DescriptionSwitch label="Enable notifications" description="Receive alerts for important updates" />
<DescriptionSwitch label="Auto-save" description="Automatically save changes as you work" defaultChecked />
<DescriptionSwitch label="Dark mode" description="Use dark theme for better visibility at night" />
</div>
)
}
// Description Switch - Positions
export const DescriptionSwitchPositions: Story = {
render: () => (
<div className="flex w-96 flex-col gap-6">
<div>
<p className="mb-3 text-sm text-muted-foreground">Switch on Right (Default)</p>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Get notified about new messages"
position="right"
/>
<DescriptionSwitch label="Marketing emails" description="Receive promotional content" position="right" />
</div>
</div>
<div>
<p className="mb-3 text-sm text-muted-foreground">Switch on Left</p>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Email notifications"
description="Get notified about new messages"
position="left"
/>
<DescriptionSwitch label="Marketing emails" description="Receive promotional content" position="left" />
</div>
</div>
</div>
)
}
// Description Switch - Sizes
export const DescriptionSwitchSizes: Story = {
render: () => (
<div className="flex w-96 flex-col gap-6">
<DescriptionSwitch label="Small switch" description="Compact size for dense layouts" size="sm" />
<DescriptionSwitch label="Medium switch" description="Default size for most use cases" size="md" defaultChecked />
<DescriptionSwitch label="Large switch" description="Larger size for emphasis" size="lg" />
</div>
)
}
// Description Switch - States
export const DescriptionSwitchStates: Story = {
render: () => (
<div className="flex w-96 flex-col gap-4">
<DescriptionSwitch label="Normal state" description="Default interactive state" />
<DescriptionSwitch label="Checked state" description="Currently enabled" defaultChecked />
<DescriptionSwitch label="Disabled state" description="Cannot be toggled" disabled />
<DescriptionSwitch label="Disabled + Checked" description="Enabled but locked" disabled defaultChecked />
<DescriptionSwitch label="Loading state" description="Processing your request" loading defaultChecked />
</div>
)
}
// Size Comparison
export const SizeComparison: Story = {
render: () => (
<div className="flex items-center gap-6">
<div className="flex flex-col gap-4">
<p className="text-xs font-medium text-muted-foreground">Off</p>
<div className="flex items-center gap-4">
<div className="flex flex-col items-center gap-2">
<Switch id="compare-sm-1" size="sm" />
<span className="text-xs text-muted-foreground">sm</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-md-1" size="md" />
<span className="text-xs text-muted-foreground">md</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-lg-1" size="lg" />
<span className="text-xs text-muted-foreground">lg</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">
<p className="text-xs font-medium text-muted-foreground">On</p>
<div className="flex items-center gap-4">
<div className="flex flex-col items-center gap-2">
<Switch id="compare-sm-2" size="sm" defaultChecked />
<span className="text-xs text-muted-foreground">sm</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-md-2" size="md" defaultChecked />
<span className="text-xs text-muted-foreground">md</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-lg-2" size="lg" defaultChecked />
<span className="text-xs text-muted-foreground">lg</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">
<p className="text-xs font-medium text-muted-foreground">Loading</p>
<div className="flex items-center gap-4">
<div className="flex flex-col items-center gap-2">
<Switch id="compare-sm-3" size="sm" loading defaultChecked />
<span className="text-xs text-muted-foreground">sm</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-md-3" size="md" loading defaultChecked />
<span className="text-xs text-muted-foreground">md</span>
</div>
<div className="flex flex-col items-center gap-2">
<Switch id="compare-lg-3" size="lg" loading defaultChecked />
<span className="text-xs text-muted-foreground">lg</span>
</div>
</div>
</div>
</div>
)
}
// Real World Examples
export const RealWorldExamples: Story = {
render: function RealWorldExample() {
const [settings, setSettings] = useState({
notifications: true,
autoSave: true,
darkMode: false,
analytics: true
})
const [privacy, setPrivacy] = useState({
shareData: false,
allowCookies: true,
trackLocation: false,
personalizedAds: false
})
return (
<div className="flex w-[500px] flex-col gap-8">
{/* General Settings */}
<div>
<h3 className="mb-4 text-base font-semibold">General Settings</h3>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<Switch
id="settings-notifications"
checked={settings.notifications}
onCheckedChange={(checked) => setSettings({ ...settings, notifications: !!checked })}
/>
<label htmlFor="settings-notifications" className="flex cursor-pointer items-center gap-2 text-sm">
<Bell className="size-4" />
Push Notifications
</label>
</div>
<div className="flex items-center gap-3">
<Switch
id="settings-autosave"
checked={settings.autoSave}
onCheckedChange={(checked) => setSettings({ ...settings, autoSave: !!checked })}
/>
<label htmlFor="settings-autosave" className="flex cursor-pointer items-center gap-2 text-sm">
<Zap className="size-4" />
Auto-save Changes
</label>
</div>
<div className="flex items-center gap-3">
<Switch
id="settings-darkmode"
checked={settings.darkMode}
onCheckedChange={(checked) => setSettings({ ...settings, darkMode: !!checked })}
/>
<label htmlFor="settings-darkmode" className="flex cursor-pointer items-center gap-2 text-sm">
<Moon className="size-4" />
Dark Mode
</label>
</div>
<div className="flex items-center gap-3">
<Switch
id="settings-analytics"
checked={settings.analytics}
onCheckedChange={(checked) => setSettings({ ...settings, analytics: !!checked })}
/>
<label htmlFor="settings-analytics" className="flex cursor-pointer items-center gap-2 text-sm">
<Shield className="size-4" />
Usage Analytics
</label>
</div>
</div>
</div>
{/* Privacy Settings with DescriptionSwitch */}
<div>
<h3 className="mb-4 text-base font-semibold">Privacy Settings</h3>
<div className="flex flex-col gap-4">
<DescriptionSwitch
label="Share usage data"
description="Help us improve by sharing anonymous usage statistics"
checked={privacy.shareData}
onCheckedChange={(checked) => setPrivacy({ ...privacy, shareData: !!checked })}
/>
<DescriptionSwitch
label="Allow cookies"
description="Enable cookies for better user experience"
checked={privacy.allowCookies}
onCheckedChange={(checked) => setPrivacy({ ...privacy, allowCookies: !!checked })}
/>
<DescriptionSwitch
label="Track location"
description="Use your location for personalized content"
checked={privacy.trackLocation}
onCheckedChange={(checked) => setPrivacy({ ...privacy, trackLocation: !!checked })}
/>
<DescriptionSwitch
label="Personalized ads"
description="Show ads based on your interests"
checked={privacy.personalizedAds}
onCheckedChange={(checked) => setPrivacy({ ...privacy, personalizedAds: !!checked })}
/>
</div>
</div>
</div>
)
}
}
// Interactive Loading Example
export const InteractiveLoading: Story = {
render: function InteractiveLoadingExample() {
const [isLoading, setIsLoading] = useState(false)
const [isEnabled, setIsEnabled] = useState(false)
const handleToggle = async (checked: boolean) => {
setIsLoading(true)
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000))
setIsEnabled(checked)
setIsLoading(false)
}
return (
<div className="flex flex-col gap-6">
<div className="flex w-96 flex-col gap-4">
<DescriptionSwitch
label="Wi-Fi Connection"
description="Connect to wireless networks"
checked={isEnabled}
onCheckedChange={handleToggle}
loading={isLoading}
disabled={isLoading}
/>
<div className="rounded-md bg-muted p-4">
<div className="flex items-center gap-2 text-sm">
<Wifi className="size-4" />
<span className="font-medium">Status:</span>
<span className="text-muted-foreground">
{isLoading ? 'Connecting...' : isEnabled ? 'Connected' : 'Disconnected'}
</span>
</div>
</div>
</div>
<p className="text-xs text-muted-foreground">Click the switch to see a simulated 2-second loading state</p>
</div>
)
}
}
// Form Example
export const FormExample: Story = {
render: function FormExample() {
const [formData, setFormData] = useState({
emailNotifications: true,
pushNotifications: false,
smsNotifications: false,
newsletter: true,
twoFactorAuth: false,
biometricAuth: true
})
const [isSaving, setIsSaving] = useState(false)
const [saved, setSaved] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSaving(true)
setSaved(false)
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1500))
setIsSaving(false)
setSaved(true)
setTimeout(() => setSaved(false), 3000)
}
return (
<form onSubmit={handleSubmit} className="w-[500px] space-y-6">
<h3 className="text-base font-semibold">Account Preferences</h3>
<div className="space-y-4">
<div>
<h4 className="mb-3 text-sm font-medium">Notifications</h4>
<div className="space-y-3">
<DescriptionSwitch
label="Email notifications"
description="Receive updates via email"
checked={formData.emailNotifications}
onCheckedChange={(checked) => setFormData({ ...formData, emailNotifications: !!checked })}
disabled={isSaving}
/>
<DescriptionSwitch
label="Push notifications"
description="Get instant alerts on your device"
checked={formData.pushNotifications}
onCheckedChange={(checked) => setFormData({ ...formData, pushNotifications: !!checked })}
disabled={isSaving}
/>
<DescriptionSwitch
label="SMS notifications"
description="Receive text message alerts"
checked={formData.smsNotifications}
onCheckedChange={(checked) => setFormData({ ...formData, smsNotifications: !!checked })}
disabled={isSaving}
/>
<DescriptionSwitch
label="Newsletter subscription"
description="Stay updated with our latest news"
checked={formData.newsletter}
onCheckedChange={(checked) => setFormData({ ...formData, newsletter: !!checked })}
disabled={isSaving}
/>
</div>
</div>
<div>
<h4 className="mb-3 text-sm font-medium">Security</h4>
<div className="space-y-3">
<DescriptionSwitch
label="Two-factor authentication"
description="Add an extra layer of security"
checked={formData.twoFactorAuth}
onCheckedChange={(checked) => setFormData({ ...formData, twoFactorAuth: !!checked })}
disabled={isSaving}
/>
<DescriptionSwitch
label="Biometric authentication"
description="Use fingerprint or face recognition"
checked={formData.biometricAuth}
onCheckedChange={(checked) => setFormData({ ...formData, biometricAuth: !!checked })}
disabled={isSaving}
/>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<button
type="submit"
disabled={isSaving}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50">
{isSaving ? 'Saving...' : 'Save Changes'}
</button>
{saved && <p className="text-sm text-green-600">Settings saved successfully!</p>}
</div>
</form>
)
}
}
// Accessibility Example
export const Accessibility: Story = {
render: () => (
<div className="flex w-96 flex-col gap-6">
<div>
<h3 className="mb-4 text-base font-semibold">Keyboard Navigation</h3>
<p className="mb-4 text-sm text-muted-foreground">
Use Tab to navigate between switches and Space/Enter to toggle them.
</p>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<Switch id="a11y-1" />
<label htmlFor="a11y-1" className="cursor-pointer text-sm">
First switch
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="a11y-2" />
<label htmlFor="a11y-2" className="cursor-pointer text-sm">
Second switch
</label>
</div>
<div className="flex items-center gap-2">
<Switch id="a11y-3" />
<label htmlFor="a11y-3" className="cursor-pointer text-sm">
Third switch
</label>
</div>
</div>
</div>
<div>
<h3 className="mb-4 text-base font-semibold">ARIA Labels</h3>
<p className="mb-4 text-sm text-muted-foreground">
Switches include proper ARIA attributes for screen reader support.
</p>
<DescriptionSwitch
label="Accessibility features"
description="Enable enhanced accessibility options for better usability"
defaultChecked
/>
</div>
</div>
)
}

View File

@ -415,7 +415,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
</Form.Item>
{!rawContent && (
<Form.Item label={i18n.t('chat.topics.export.obsidian_reasoning')}>
<Switch isSelected={exportReasoning} onValueChange={setExportReasoning} />
<Switch checked={exportReasoning} onCheckedChange={setExportReasoning} />
</Form.Item>
)}
</Form>

View File

@ -115,6 +115,7 @@ const SettingsTab: FC<Props> = (props) => {
const { theme } = useTheme()
const { themeNames } = useCodeStyle()
// FIXME: We should use useMemo to calculate these states instead of using useEffect to sync
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
const [enableTemperature, setEnableTemperature] = useState(assistant?.settings?.enableTemperature ?? true)
const [contextCount, setContextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONTEXTCOUNT)
@ -270,10 +271,9 @@ const SettingsTab: FC<Props> = (props) => {
<HelpTooltip title={t('chat.settings.temperature.tip')} />
</SettingRowTitleSmall>
<Switch
size="sm"
style={{ marginLeft: 'auto' }}
isSelected={enableTemperature}
onValueChange={(enabled) => {
checked={enableTemperature}
onCheckedChange={(enabled) => {
setEnableTemperature(enabled)
onUpdateAssistantSettings({ enableTemperature: enabled })
}}
@ -340,9 +340,8 @@ const SettingsTab: FC<Props> = (props) => {
<SettingRow>
<SettingRowTitleSmall>{t('models.stream_output')}</SettingRowTitleSmall>
<Switch
size="sm"
isSelected={streamOutput}
onValueChange={(checked) => {
checked={streamOutput}
onCheckedChange={(checked) => {
setStreamOutput(checked)
onUpdateAssistantSettings({ streamOutput: checked })
}}
@ -357,9 +356,8 @@ const SettingsTab: FC<Props> = (props) => {
</SettingRowTitleSmall>
</Row>
<Switch
size="sm"
isSelected={enableMaxTokens}
onValueChange={async (enabled) => {
checked={enableMaxTokens}
onCheckedChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),
@ -410,38 +408,36 @@ const SettingsTab: FC<Props> = (props) => {
<CollapsibleSettingGroup title={t('settings.messages.title')} defaultExpanded={true}>
<SettingGroup>
<SettingRow>
<DescriptionSwitch size="sm" isSelected={showPrompt} onValueChange={setShowPrompt}>
<SettingRowTitleSmall>{t('settings.messages.prompt')}</SettingRowTitleSmall>
</DescriptionSwitch>
</SettingRow>
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>{t('settings.messages.use_serif_font')}</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={messageFont === 'serif'}
onValueChange={(checked) => setMessageFont(checked ? 'serif' : 'system')}>
<SettingRowTitleSmall>{t('settings.messages.use_serif_font')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={showPrompt}
onCheckedChange={setShowPrompt}
label={t('settings.messages.prompt')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>
{t('chat.settings.thought_auto_collapse.label')}
<HelpTooltip title={t('chat.settings.thought_auto_collapse.tip')} />
</SettingRowTitleSmall> */}
<DescriptionSwitch isSelected={thoughtAutoCollapse} onValueChange={setThoughtAutoCollapse}>
<SettingRowTitleSmall>
{t('chat.settings.thought_auto_collapse.label')}
<HelpTooltip content={t('chat.settings.thought_auto_collapse.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={messageFont === 'serif'}
onCheckedChange={(checked) => setMessageFont(checked ? 'serif' : 'system')}
label={t('settings.messages.use_serif_font')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch isSelected={showMessageOutline} onValueChange={setShowMessageOutline}>
<SettingRowTitleSmall>{t('settings.messages.show_message_outline')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={thoughtAutoCollapse}
onCheckedChange={setThoughtAutoCollapse}
label={t('chat.settings.thought_auto_collapse.label')}
description={t('chat.settings.thought_auto_collapse.tip')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch
checked={showMessageOutline}
onCheckedChange={setShowMessageOutline}
label={t('settings.messages.show_message_outline')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -534,16 +530,12 @@ const SettingsTab: FC<Props> = (props) => {
</SettingRow>
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>
{t('settings.math.single_dollar.label')}
<HelpTooltip title={t('settings.math.single_dollar.tip')} />
</SettingRowTitleSmall> */}
<DescriptionSwitch size="sm" isSelected={mathEnableSingleDollar} onValueChange={setMathEnableSingleDollar}>
<SettingRowTitleSmall>
{t('settings.math.single_dollar.label')}
<HelpTooltip content={t('settings.math.single_dollar.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={mathEnableSingleDollar}
onCheckedChange={setMathEnableSingleDollar}
label={t('settings.math.single_dollar.label')}
description={t('settings.math.single_dollar.tip')}
/>
</SettingRow>
<SettingDivider />
</SettingGroup>
@ -567,32 +559,21 @@ const SettingsTab: FC<Props> = (props) => {
</SettingRow>
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>
{t('chat.settings.code_fancy_block.label')}
<HelpTooltip title={t('chat.settings.code_fancy_block.tip')} />
</SettingRowTitleSmall> */}
<DescriptionSwitch size="sm" isSelected={codeFancyBlock} onValueChange={setCodeFancyBlock}>
<SettingRowTitleSmall>
{t('chat.settings.code_fancy_block.label')}
<HelpTooltip content={t('chat.settings.code_fancy_block.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={codeFancyBlock}
onCheckedChange={setCodeFancyBlock}
label={t('chat.settings.code_fancy_block.label')}
description={t('chat.settings.code_fancy_block.tip')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>
{t('chat.settings.code_execution.title')}
<HelpTooltip title={t('chat.settings.code_execution.tip')} />
</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeExecution.enabled}
onValueChange={(checked) => setCodeExecution({ enabled: checked })}>
<SettingRowTitleSmall>
{t('chat.settings.code_execution.title')}
<HelpTooltip content={t('chat.settings.code_execution.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeExecution.enabled}
onCheckedChange={(checked) => setCodeExecution({ enabled: checked })}
label={t('chat.settings.code_execution.title')}
description={t('chat.settings.code_execution.tip')}
/>
</SettingRow>
{codeExecution.enabled && (
<>
@ -616,90 +597,80 @@ const SettingsTab: FC<Props> = (props) => {
)}
<SettingDivider />
<SettingRow>
{/* <SettingRowTitleSmall>{t('chat.settings.code_editor.title')}</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeEditor.enabled}
onValueChange={(checked) => setCodeEditor({ enabled: checked })}>
<SettingRowTitleSmall>{t('chat.settings.code_editor.title')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeEditor.enabled}
onCheckedChange={(checked) => setCodeEditor({ enabled: checked })}
label={t('chat.settings.code_editor.title')}
/>
</SettingRow>
{codeEditor.enabled && (
<>
<SettingDivider />
<SettingRow style={{ paddingLeft: 8 }}>
{/* <SettingRowTitleSmall>
{t('chat.settings.code_editor.highlight_active_line')}
<HelpTooltip title={t('chat.settings.code_editor.highlight_active_line.tip')} />
</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeEditor.highlightActiveLine}
onValueChange={(checked) => setCodeEditor({ highlightActiveLine: checked })}>
<SettingRowTitleSmall>
{t('chat.settings.code_editor.highlight_active_line')}
<HelpTooltip content={t('chat.settings.code_editor.highlight_active_line.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeEditor.highlightActiveLine}
onCheckedChange={(checked) => setCodeEditor({ highlightActiveLine: checked })}
label={t('chat.settings.code_editor.highlight_active_line')}
/>
</SettingRow>
<SettingDivider />
<SettingRow style={{ paddingLeft: 8 }}>
{/* <SettingRowTitleSmall>{t('chat.settings.code_editor.fold_gutter')}</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeEditor.foldGutter}
onValueChange={(checked) => setCodeEditor({ foldGutter: checked })}>
<SettingRowTitleSmall>{t('chat.settings.code_editor.fold_gutter')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeEditor.foldGutter}
onCheckedChange={(checked) => setCodeEditor({ foldGutter: checked })}
label={t('chat.settings.code_editor.fold_gutter')}
/>
</SettingRow>
<SettingDivider />
<SettingRow style={{ paddingLeft: 8 }}>
{/* <SettingRowTitleSmall>{t('chat.settings.code_editor.autocompletion')}</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeEditor.autocompletion}
onValueChange={(checked) => setCodeEditor({ autocompletion: checked })}>
<SettingRowTitleSmall>{t('chat.settings.code_editor.autocompletion')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeEditor.autocompletion}
onCheckedChange={(checked) => setCodeEditor({ autocompletion: checked })}
label={t('chat.settings.code_editor.autocompletion')}
/>
</SettingRow>
<SettingDivider />
<SettingRow style={{ paddingLeft: 8 }}>
{/* <SettingRowTitleSmall>{t('chat.settings.code_editor.keymap')}</SettingRowTitleSmall> */}
<DescriptionSwitch
size="sm"
isSelected={codeEditor.keymap}
onValueChange={(checked) => setCodeEditor({ keymap: checked })}>
<SettingRowTitleSmall>{t('chat.settings.code_editor.keymap')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={codeEditor.keymap}
onCheckedChange={(checked) => setCodeEditor({ keymap: checked })}
label={t('chat.settings.code_editor.keymap')}
/>
</SettingRow>
</>
)}
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={codeShowLineNumbers} onValueChange={setCodeShowLineNumbers}>
<SettingRowTitleSmall>{t('chat.settings.show_line_numbers')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={codeShowLineNumbers}
onCheckedChange={setCodeShowLineNumbers}
label={t('chat.settings.show_line_numbers')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={codeCollapsible} onValueChange={setCodeCollapsible}>
<SettingRowTitleSmall>{t('chat.settings.code_collapsible')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={codeCollapsible}
onCheckedChange={setCodeCollapsible}
label={t('chat.settings.code_collapsible')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={codeWrappable} onValueChange={setCodeWrappable}>
<SettingRowTitleSmall>{t('chat.settings.code_wrappable')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={codeWrappable}
onCheckedChange={setCodeWrappable}
label={t('chat.settings.code_wrappable')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={codeImageTools} onValueChange={setCodeImageTools}>
<SettingRowTitleSmall>
{t('chat.settings.code_image_tools.label')}
<HelpTooltip content={t('chat.settings.code_image_tools.tip')} />
</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={codeImageTools}
onCheckedChange={setCodeImageTools}
label={t('chat.settings.code_image_tools.label')}
description={t('chat.settings.code_image_tools.tip')}
/>
</SettingRow>
</SettingGroup>
<SettingDivider />
@ -708,17 +679,18 @@ const SettingsTab: FC<Props> = (props) => {
<SettingGroup>
<SettingRow>
<DescriptionSwitch
size="sm"
isSelected={showInputEstimatedTokens}
onValueChange={setShowInputEstimatedTokens}>
<SettingRowTitleSmall>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={showInputEstimatedTokens}
onCheckedChange={setShowInputEstimatedTokens}
label={t('settings.messages.input.show_estimated_tokens')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={pasteLongTextAsFile} onValueChange={setPasteLongTextAsFile}>
<SettingRowTitleSmall>{t('settings.messages.input.paste_long_text_as_file')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={pasteLongTextAsFile}
onCheckedChange={setPasteLongTextAsFile}
label={t('settings.messages.input.paste_long_text_as_file')}
/>
</SettingRow>
{pasteLongTextAsFile && (
<>
@ -740,54 +712,54 @@ const SettingsTab: FC<Props> = (props) => {
<SettingDivider />
<SettingRow>
<DescriptionSwitch
size="sm"
isSelected={renderInputMessageAsMarkdown}
onValueChange={setRenderInputMessageAsMarkdown}>
<SettingRowTitleSmall>{t('settings.messages.markdown_rendering_input_message')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={renderInputMessageAsMarkdown}
onCheckedChange={setRenderInputMessageAsMarkdown}
label={t('settings.messages.markdown_rendering_input_message')}
/>
</SettingRow>
<SettingDivider />
{!(language || navigator.language).startsWith('en') && (
<>
<SettingRow>
<DescriptionSwitch
size="sm"
isSelected={autoTranslateWithSpace}
onValueChange={setAutoTranslateWithSpace}>
<SettingRowTitleSmall>{t('settings.input.auto_translate_with_space')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={autoTranslateWithSpace}
onCheckedChange={setAutoTranslateWithSpace}
label={t('settings.input.auto_translate_with_space')}
/>
</SettingRow>
<SettingDivider />
</>
)}
<SettingRow>
<DescriptionSwitch size="sm" isSelected={showTranslateConfirm} onValueChange={setShowTranslateConfirm}>
<SettingRowTitleSmall>{t('settings.input.show_translate_confirm')}</SettingRowTitleSmall>
</DescriptionSwitch>
<DescriptionSwitch
checked={showTranslateConfirm}
onCheckedChange={setShowTranslateConfirm}
label={t('settings.input.show_translate_confirm')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch
size="sm"
isSelected={enableQuickPanelTriggers}
onValueChange={setEnableQuickPanelTriggers}>
<SettingRowTitleSmall>{t('settings.messages.input.enable_quick_triggers')}</SettingRowTitleSmall>
</DescriptionSwitch>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch size="sm" isSelected={confirmDeleteMessage} onValueChange={setConfirmDeleteMessage}>
<SettingRowTitleSmall>{t('settings.messages.input.confirm_delete_message')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={enableQuickPanelTriggers}
onCheckedChange={setEnableQuickPanelTriggers}
label={t('settings.messages.input.enable_quick_triggers')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch
size="sm"
isSelected={confirmRegenerateMessage}
onValueChange={setConfirmRegenerateMessage}>
<SettingRowTitleSmall>{t('settings.messages.input.confirm_regenerate_message')}</SettingRowTitleSmall>
</DescriptionSwitch>
checked={confirmDeleteMessage}
onCheckedChange={setConfirmDeleteMessage}
label={t('settings.messages.input.confirm_delete_message')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<DescriptionSwitch
checked={confirmRegenerateMessage}
onCheckedChange={setConfirmRegenerateMessage}
label={t('settings.messages.input.confirm_regenerate_message')}
/>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -96,7 +96,7 @@ const MiniAppSettings: FC = () => {
<SettingLabelGroup>
<SettingRowTitle>{t('settings.miniapps.open_link_external.title')}</SettingRowTitle>
</SettingLabelGroup>
<Switch isSelected={minappsOpenLinkExternal} onValueChange={(checked) => setMinappsOpenLinkExternal(checked)} />
<Switch checked={minappsOpenLinkExternal} onCheckedChange={(checked) => setMinappsOpenLinkExternal(checked)} />
</SettingRow>
<SettingDivider />
{/* 缓存小程序数量设置 */}
@ -134,8 +134,8 @@ const MiniAppSettings: FC = () => {
<SettingDescription>{t('settings.miniapps.sidebar_description')}</SettingDescription>
</SettingLabelGroup>
<Switch
isSelected={showOpenedMinappsInSidebar}
onValueChange={(checked) => setShowOpenedMinappsInSidebar(checked)}
checked={showOpenedMinappsInSidebar}
onCheckedChange={(checked) => setShowOpenedMinappsInSidebar(checked)}
/>
</SettingRow>
</Container>

View File

@ -800,8 +800,8 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
return (
<RowFlex>
<Switch
isSelected={(painting[item.key!] || item.initialValue) as boolean}
onValueChange={(checked) => updatePaintingState({ [item.key!]: checked })}
checked={(painting[item.key!] || item.initialValue) as boolean}
onCheckedChange={(checked) => updatePaintingState({ [item.key!]: checked })}
/>
</RowFlex>
)

View File

@ -938,7 +938,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
<InfoTooltip content={t('paintings.auto_create_paint_tip')} />
</SettingTitle>
<RowFlex>
<Switch isSelected={painting.autoCreate} onValueChange={(checked) => onChangeAutoCreate(checked)} />
<Switch checked={painting.autoCreate} onCheckedChange={(checked) => onChangeAutoCreate(checked)} />
</RowFlex>
</LeftContainer>
<MainContainer>

View File

@ -464,8 +464,8 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
</SettingTitle>
<RowFlex>
<Switch
isSelected={painting.promptEnhancement}
onValueChange={(checked) => updatePaintingState({ promptEnhancement: checked })}
checked={painting.promptEnhancement}
onCheckedChange={(checked) => updatePaintingState({ promptEnhancement: checked })}
/>
</RowFlex>
</LeftContainer>

View File

@ -198,8 +198,8 @@ export const DynamicFormRender: React.FC<DynamicFormRenderProps> = ({
if (type === 'boolean') {
return (
<Switch
isSelected={value !== undefined ? value : defaultValue}
onValueChange={(checked) => onChange(propertyName, checked)}
checked={value !== undefined ? value : defaultValue}
onCheckedChange={(checked) => onChange(propertyName, checked)}
style={{ width: '2px' }}
/>
)

View File

@ -228,13 +228,13 @@ const AboutSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.auto_check_update.title')}</SettingRowTitle>
<Switch isSelected={autoCheckUpdate} onValueChange={(v) => setAutoCheckUpdate(v)} />
<Switch checked={autoCheckUpdate} onCheckedChange={(v) => setAutoCheckUpdate(v)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.test_plan.title')}</SettingRowTitle>
<Tooltip content={t('settings.general.test_plan.tooltip')}>
<Switch isSelected={testPlan} onValueChange={(v) => handleSetTestPlan(v)} />
<Switch checked={testPlan} onCheckedChange={(v) => handleSetTestPlan(v)} />
</Tooltip>
</SettingRow>
{testPlan && (

View File

@ -1,3 +1,4 @@
import { Switch } from '@cherrystudio/ui'
import { permissionModeCards } from '@renderer/config/agent'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import useScrollPosition from '@renderer/hooks/useScrollPosition'
@ -13,7 +14,7 @@ import type {
} from '@renderer/types'
import { AgentConfigurationSchema } from '@renderer/types'
import { Modal, Tag } from 'antd'
import { Alert, Card, Input, Switch } from 'antd'
import { Alert, Card, Input } from 'antd'
import { ShieldAlert, Wrench } from 'lucide-react'
import type { FC } from 'react'
import { useCallback, useMemo, useState } from 'react'
@ -401,8 +402,8 @@ export const ToolingSettings: FC<AgentToolingSettingsProps> = ({ agentBase, upda
})}
checked={isApproved}
disabled={isAuto || isUpdatingTools}
size="small"
onChange={(checked) => handleToggleTool(tool.id, checked)}
size="sm"
onCheckedChange={(checked) => handleToggleTool(tool.id, checked)}
/>
</div>
}
@ -483,9 +484,9 @@ export const ToolingSettings: FC<AgentToolingSettingsProps> = ({ agentBase, upda
name: server.name
})}
checked={isSelected}
size="small"
size="sm"
disabled={!server.isActive || isUpdatingMcp}
onChange={(checked) => handleToggleMcp(server.id, checked)}
onCheckedChange={(checked) => handleToggleMcp(server.id, checked)}
/>
</div>
}

View File

@ -87,10 +87,9 @@ const AssistantMCPSettings: React.FC<Props> = ({ assistant, updateAssistant }) =
: undefined
}>
<Switch
isSelected={isEnabled}
checked={isEnabled}
disabled={!server.isActive}
onValueChange={() => handleServerToggle(server.id)}
size="sm"
onCheckedChange={() => handleServerToggle(server.id)}
/>
</Tooltip>
</ServerItem>

View File

@ -94,8 +94,8 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
: ''
}>
<Switch
isSelected={assistant.enableMemory || false}
onValueChange={handleMemoryToggle}
checked={assistant.enableMemory || false}
onCheckedChange={handleMemoryToggle}
disabled={!isMemoryEnabled}
/>
</Tooltip>

View File

@ -246,8 +246,8 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
</Label>
</RowFlex>
<Switch
isSelected={enableTemperature}
onValueChange={(enabled) => {
checked={enableTemperature}
onCheckedChange={(enabled) => {
setEnableTemperature(enabled)
updateAssistantSettings({ enableTemperature: enabled })
}}
@ -295,8 +295,8 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
/>
</RowFlex>
<Switch
isSelected={enableTopP}
onValueChange={(enabled) => {
checked={enableTopP}
onCheckedChange={(enabled) => {
setEnableTopP(enabled)
updateAssistantSettings({ enableTopP: enabled })
}}
@ -387,8 +387,8 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
/>
</RowFlex>
<Switch
isSelected={enableMaxTokens}
onValueChange={async (enabled) => {
checked={enableMaxTokens}
onCheckedChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),
@ -430,8 +430,8 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<SettingRow style={{ minHeight: 30 }}>
<Label>{t('models.stream_output')}</Label>
<Switch
isSelected={streamOutput}
onValueChange={(checked) => {
checked={streamOutput}
onCheckedChange={(checked) => {
setStreamOutput(checked)
updateAssistantSettings({ streamOutput: checked })
}}

View File

@ -6,7 +6,7 @@ import {
WifiOutlined,
YuqueOutlined
} from '@ant-design/icons'
import { Button, RowFlex } from '@cherrystudio/ui'
import { Button, RowFlex, Switch } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import DividerWithText from '@renderer/components/DividerWithText'
import { NutstoreIcon } from '@renderer/components/Icons/NutstoreIcons'
@ -22,7 +22,7 @@ import { reset } from '@renderer/services/BackupService'
import type { AppInfo } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import { occupiedDirs } from '@shared/config/constant'
import { Progress, Switch, Typography } from 'antd'
import { Progress, Typography } from 'antd'
import { FileText, FolderCog, FolderInput, FolderOpen, SaveIcon } from 'lucide-react'
import type { FC } from 'react'
import { useEffect, useState } from 'react'
@ -291,7 +291,7 @@ const DataSettings: FC = () => {
<MigrationPathRow style={{ marginTop: '20px', flexDirection: 'row', alignItems: 'center' }}>
<Switch
defaultChecked={shouldCopyData}
onChange={(checked) => (shouldCopyData = checked)}
onCheckedChange={(checked) => (shouldCopyData = checked)}
style={{ marginRight: '8px' }}
title={t('settings.data.app_data.copy_data_option')}
/>
@ -616,7 +616,7 @@ const DataSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.skip_file_data_title')}</SettingRowTitle>
<Switch checked={skipBackupFile} onChange={onSkipBackupFilesChange} />
<Switch checked={skipBackupFile} onCheckedChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>

View File

@ -35,18 +35,15 @@ const ExportMenuOptions: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.image')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.image}
onValueChange={(checked) => handleToggleOption('image', checked)}
/>
<Switch checked={exportMenuOptions.image} onCheckedChange={(checked) => handleToggleOption('image', checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.markdown')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.markdown}
onValueChange={(checked) => handleToggleOption('markdown', checked)}
checked={exportMenuOptions.markdown}
onCheckedChange={(checked) => handleToggleOption('markdown', checked)}
/>
</SettingRow>
<SettingDivider />
@ -54,8 +51,8 @@ const ExportMenuOptions: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.markdown_reason')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.markdown_reason}
onValueChange={(checked) => handleToggleOption('markdown_reason', checked)}
checked={exportMenuOptions.markdown_reason}
onCheckedChange={(checked) => handleToggleOption('markdown_reason', checked)}
/>
</SettingRow>
<SettingDivider />
@ -63,26 +60,23 @@ const ExportMenuOptions: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.notion')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.notion}
onValueChange={(checked) => handleToggleOption('notion', checked)}
checked={exportMenuOptions.notion}
onCheckedChange={(checked) => handleToggleOption('notion', checked)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.yuque')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.yuque}
onValueChange={(checked) => handleToggleOption('yuque', checked)}
/>
<Switch checked={exportMenuOptions.yuque} onCheckedChange={(checked) => handleToggleOption('yuque', checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.joplin')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.joplin}
onValueChange={(checked) => handleToggleOption('joplin', checked)}
checked={exportMenuOptions.joplin}
onCheckedChange={(checked) => handleToggleOption('joplin', checked)}
/>
</SettingRow>
<SettingDivider />
@ -90,8 +84,8 @@ const ExportMenuOptions: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.obsidian')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.obsidian}
onValueChange={(checked) => handleToggleOption('obsidian', checked)}
checked={exportMenuOptions.obsidian}
onCheckedChange={(checked) => handleToggleOption('obsidian', checked)}
/>
</SettingRow>
<SettingDivider />
@ -99,23 +93,23 @@ const ExportMenuOptions: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.siyuan')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.siyuan}
onValueChange={(checked) => handleToggleOption('siyuan', checked)}
checked={exportMenuOptions.siyuan}
onCheckedChange={(checked) => handleToggleOption('siyuan', checked)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.docx')}</SettingRowTitle>
<Switch isSelected={exportMenuOptions.docx} onValueChange={(checked) => handleToggleOption('docx', checked)} />
<Switch checked={exportMenuOptions.docx} onCheckedChange={(checked) => handleToggleOption('docx', checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.export_menu.plain_text')}</SettingRowTitle>
<Switch
isSelected={exportMenuOptions.plain_text}
onValueChange={(checked) => handleToggleOption('plain_text', checked)}
checked={exportMenuOptions.plain_text}
onCheckedChange={(checked) => handleToggleOption('plain_text', checked)}
/>
</SettingRow>
</SettingGroup>

View File

@ -122,7 +122,7 @@ const JoplinSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.joplin.export_reasoning.title')}</SettingRowTitle>
<Switch isSelected={joplinExportReasoning} onValueChange={handleToggleJoplinExportReasoning} />
<Switch checked={joplinExportReasoning} onCheckedChange={handleToggleJoplinExportReasoning} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.joplin.export_reasoning.help')}</SettingHelpText>

View File

@ -261,7 +261,7 @@ const LocalBackupSettings: React.FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.skip_file_data_title')}</SettingRowTitle>
<Switch isSelected={localBackupSkipBackupFile} onValueChange={onSkipBackupFilesChange} />
<Switch checked={localBackupSkipBackupFile} onCheckedChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>

View File

@ -98,7 +98,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.force_dollar_math.title')}</SettingRowTitle>
<Switch isSelected={forceDollarMathInMarkdown} onValueChange={handleToggleForceDollarMath} />
<Switch checked={forceDollarMathInMarkdown} onCheckedChange={handleToggleForceDollarMath} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.force_dollar_math.help')}</SettingHelpText>
@ -106,7 +106,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.message_title.use_topic_naming.title')}</SettingRowTitle>
<Switch isSelected={useTopicNamingForMessageTitle} onValueChange={handleToggleTopicNaming} />
<Switch checked={useTopicNamingForMessageTitle} onCheckedChange={handleToggleTopicNaming} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.message_title.use_topic_naming.help')}</SettingHelpText>
@ -114,7 +114,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.show_model_name.title')}</SettingRowTitle>
<Switch isSelected={showModelNameInExport} onValueChange={handleToggleShowModelName} />
<Switch checked={showModelNameInExport} onCheckedChange={handleToggleShowModelName} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.show_model_name.help')}</SettingHelpText>
@ -122,7 +122,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.show_model_provider.title')}</SettingRowTitle>
<Switch isSelected={showModelProviderInMarkdown} onValueChange={handleToggleShowModelProvider} />
<Switch checked={showModelProviderInMarkdown} onCheckedChange={handleToggleShowModelProvider} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.show_model_provider.help')}</SettingHelpText>
@ -130,7 +130,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.exclude_citations.title')}</SettingRowTitle>
<Switch isSelected={excludeCitationsInExport} onValueChange={handleToggleExcludeCitations} />
<Switch checked={excludeCitationsInExport} onCheckedChange={handleToggleExcludeCitations} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.exclude_citations.help')}</SettingHelpText>
@ -138,7 +138,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.standardize_citations.title')}</SettingRowTitle>
<Switch isSelected={standardizeCitationsInExport} onValueChange={handleToggleStandardizeCitations} />
<Switch checked={standardizeCitationsInExport} onCheckedChange={handleToggleStandardizeCitations} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.standardize_citations.help')}</SettingHelpText>

View File

@ -128,7 +128,7 @@ const NotionSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.export_reasoning.title')}</SettingRowTitle>
<Switch isSelected={notionExportReasoning} onValueChange={handleNotionExportReasoningChange} />
<Switch checked={notionExportReasoning} onCheckedChange={handleNotionExportReasoningChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.notion.export_reasoning.help')}</SettingHelpText>

View File

@ -319,7 +319,7 @@ const NutstoreSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.skip_file_data_title')}</SettingRowTitle>
<Switch isSelected={nutstoreSkipBackupFile} onValueChange={onSkipBackupFilesChange} />
<Switch checked={nutstoreSkipBackupFile} onCheckedChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>

View File

@ -243,7 +243,7 @@ const S3Settings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.s3.skipBackupFile.label')}</SettingRowTitle>
<Switch isSelected={s3SkipBackupFile} onValueChange={onSkipBackupFilesChange} />
<Switch checked={s3SkipBackupFile} onCheckedChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.s3.skipBackupFile.help')}</SettingHelpText>

View File

@ -201,7 +201,7 @@ const WebDavSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.skip_file_data_title')}</SettingRowTitle>
<Switch isSelected={webdavSkipBackupFile} onValueChange={onSkipBackupFilesChange} />
<Switch checked={webdavSkipBackupFile} onCheckedChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>
@ -209,7 +209,7 @@ const WebDavSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.webdav.disableStream.title')}</SettingRowTitle>
<Switch isSelected={webdavDisableStream} onValueChange={onDisableStreamChange} />
<Switch checked={webdavDisableStream} onCheckedChange={onDisableStreamChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.webdav.disableStream.help')}</SettingHelpText>

View File

@ -231,7 +231,7 @@ const DisplaySettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.theme.window.style.transparent')}</SettingRowTitle>
<Switch isSelected={windowStyle === 'transparent'} onValueChange={handleWindowStyleChange} />
<Switch checked={windowStyle === 'transparent'} onCheckedChange={handleWindowStyleChange} />
</SettingRow>
</>
)}
@ -355,8 +355,8 @@ const DisplaySettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.advanced.auto_switch_to_topics')}</SettingRowTitle>
<Switch
isSelected={clickAssistantToShowTopic}
onValueChange={(checked) => setClickAssistantToShowTopic(checked)}
checked={clickAssistantToShowTopic}
onCheckedChange={(checked) => setClickAssistantToShowTopic(checked)}
/>
</SettingRow>
<SettingDivider />
@ -364,12 +364,12 @@ const DisplaySettings: FC = () => {
)}
<SettingRow>
<SettingRowTitle>{t('settings.topic.show.time')}</SettingRowTitle>
<Switch isSelected={showTopicTime} onValueChange={(checked) => setShowTopicTime(checked)} />
<Switch checked={showTopicTime} onCheckedChange={(checked) => setShowTopicTime(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.topic.pin_to_top')}</SettingRowTitle>
<Switch isSelected={pinTopicsToTop} onValueChange={(checked) => setPinTopicsToTop(checked)} />
<Switch checked={pinTopicsToTop} onCheckedChange={(checked) => setPinTopicsToTop(checked)} />
</SettingRow>
</SettingGroup>
<SettingGroup theme={theme}>

View File

@ -268,12 +268,12 @@ const GeneralSettings: FC = () => {
/>
)}
</RowFlex>
<Switch isSelected={enableSpellCheck} onValueChange={handleSpellCheckChange} />
<Switch checked={enableSpellCheck} onCheckedChange={handleSpellCheckChange} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.hardware_acceleration.title')}</SettingRowTitle>
<Switch isSelected={disableHardwareAcceleration} onValueChange={handleHardwareAccelerationChange} />
<Switch checked={disableHardwareAcceleration} onCheckedChange={handleHardwareAccelerationChange} />
</SettingRow>
</SettingGroup>
<SettingGroup theme={theme}>
@ -289,24 +289,24 @@ const GeneralSettings: FC = () => {
/>
</SettingRowTitle>
<Switch
isSelected={notificationSettings.assistant}
onValueChange={(v) => handleNotificationChange('assistant', v)}
checked={notificationSettings.assistant}
onCheckedChange={(v) => handleNotificationChange('assistant', v)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.notification.backup')}</SettingRowTitle>
<Switch
isSelected={notificationSettings.backup}
onValueChange={(v) => handleNotificationChange('backup', v)}
checked={notificationSettings.backup}
onCheckedChange={(v) => handleNotificationChange('backup', v)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.notification.knowledge_embed')}</SettingRowTitle>
<Switch
isSelected={notificationSettings.knowledge}
onValueChange={(v) => handleNotificationChange('knowledge', v)}
checked={notificationSettings.knowledge}
onCheckedChange={(v) => handleNotificationChange('knowledge', v)}
/>
</SettingRow>
</SettingGroup>
@ -315,12 +315,12 @@ const GeneralSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.launch.onboot')}</SettingRowTitle>
<Switch isSelected={launchOnBoot} onValueChange={(checked) => updateLaunchOnBoot(checked)} />
<Switch checked={launchOnBoot} onCheckedChange={(checked) => updateLaunchOnBoot(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.launch.totray')}</SettingRowTitle>
<Switch isSelected={launchToTray} onValueChange={(checked) => updateLaunchToTray(checked)} />
<Switch checked={launchToTray} onCheckedChange={(checked) => updateLaunchToTray(checked)} />
</SettingRow>
</SettingGroup>
<SettingGroup theme={theme}>
@ -328,12 +328,12 @@ const GeneralSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tray.show')}</SettingRowTitle>
<Switch isSelected={tray} onValueChange={(checked) => updateTray(checked)} />
<Switch checked={tray} onCheckedChange={(checked) => updateTray(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tray.onclose')}</SettingRowTitle>
<Switch isSelected={trayOnClose} onValueChange={(checked) => updateTrayOnClose(checked)} />
<Switch checked={trayOnClose} onCheckedChange={(checked) => updateTrayOnClose(checked)} />
</SettingRow>
</SettingGroup>
<SettingGroup theme={theme}>
@ -342,8 +342,8 @@ const GeneralSettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.privacy.enable_privacy_mode')}</SettingRowTitle>
<Switch
isSelected={enableDataCollection}
onValueChange={(v) => {
checked={enableDataCollection}
onCheckedChange={(v) => {
setEnableDataCollection(v)
window.api.config.set('enableDataCollection', v)
}}
@ -358,7 +358,7 @@ const GeneralSettings: FC = () => {
<SettingRowTitle>{t('settings.developer.enable_developer_mode')}</SettingRowTitle>
<InfoTooltip content={t('settings.developer.help')} />
</Flex>
<Switch isSelected={enableDeveloperMode} onValueChange={setEnableDeveloperMode} />
<Switch checked={enableDeveloperMode} onCheckedChange={setEnableDeveloperMode} />
</SettingRow>
</SettingGroup>
</SettingContainer>

View File

@ -116,11 +116,10 @@ const McpServerCard: FC<McpServerCardProps> = ({
</ServerNameWrapper>
<ToolbarWrapper onClick={(e) => e.stopPropagation()}>
<Switch
isSelected={server.isActive}
checked={server.isActive}
key={server.id}
disabled={isLoading}
onValueChange={onToggle}
size="sm"
onCheckedChange={onToggle}
data-no-dnd
/>
<Button size="sm" variant="destructive" className="rounded-full" onClick={onDelete}>

View File

@ -653,7 +653,7 @@ const McpSettings: React.FC = () => {
tooltip={t('settings.mcp.longRunningTooltip')}
layout="horizontal"
valuePropName="checked">
<Switch size="sm" className="ml-2.5" />
<Switch className="ml-2.5" />
</Form.Item>
<Form.Item
name="timeout"
@ -758,10 +758,10 @@ const McpSettings: React.FC = () => {
</Flex>
<Flex className="items-center gap-4">
<Switch
isSelected={server.isActive}
checked={server.isActive}
key={server.id}
isLoading={loadingServer === server.id}
onValueChange={onToggleActive}
loading={loadingServer === server.id}
onCheckedChange={onToggleActive}
/>
<Button
size="sm"

View File

@ -146,7 +146,7 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
width: 150, // Fixed width might be good for alignment
align: 'center',
render: (_, tool) => (
<Switch isSelected={isToolEnabled(tool)} onValueChange={(checked) => handleToggle(tool, checked)} size="sm" />
<Switch checked={isToolEnabled(tool)} onCheckedChange={(checked) => handleToggle(tool, checked)} />
)
},
{
@ -169,10 +169,9 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
: t('settings.mcp.tools.autoApprove.tooltip.disabled')
}>
<Switch
isSelected={isToolAutoApproved(tool, server)}
checked={isToolAutoApproved(tool, server)}
disabled={!isToolEnabled(tool)}
onValueChange={(checked) => handleAutoApproveToggle(tool, checked)}
size="sm"
onCheckedChange={(checked) => handleAutoApproveToggle(tool, checked)}
/>
</Tooltip>
)

View File

@ -585,7 +585,7 @@ const MemorySettings = () => {
<TextBadge text="Beta" />
</RowFlex>
<RowFlex className="items-center gap-2.5">
<Switch isSelected={globalMemoryEnabled} onValueChange={handleGlobalMemoryToggle} />
<Switch checked={globalMemoryEnabled} onCheckedChange={handleGlobalMemoryToggle} />
<Button variant="ghost" onClick={() => setSettingsModalVisible(true)} size="icon">
<Settings2 size={16} />
</Button>

View File

@ -173,8 +173,8 @@ const AssistantSettings: FC = () => {
</RowFlex>
<Switch
style={{ marginLeft: 10 }}
isSelected={enableTemperature}
onValueChange={(enabled) => {
checked={enableTemperature}
onCheckedChange={(enabled) => {
setEnableTemperature(enabled)
onUpdateAssistantSettings({ enableTemperature: enabled })
}}
@ -215,8 +215,8 @@ const AssistantSettings: FC = () => {
</RowFlex>
<Switch
style={{ marginLeft: 10 }}
isSelected={enableTopP}
onValueChange={(enabled) => {
checked={enableTopP}
onCheckedChange={(enabled) => {
setEnableTopP(enabled)
onUpdateAssistantSettings({ enableTopP: enabled })
}}
@ -280,8 +280,8 @@ const AssistantSettings: FC = () => {
</RowFlex>
<Switch
style={{ marginLeft: 10 }}
isSelected={enableMaxTokens}
onValueChange={async (enabled) => {
checked={enableMaxTokens}
onCheckedChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),

View File

@ -59,7 +59,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<ColFlex className="items-stretch gap-2">
<RowFlex className="items-center gap-4">
<div>{t('settings.models.topic_naming.auto')}</div>
<Switch isSelected={enableTopicNaming} onValueChange={setEnableTopicNaming} />
<Switch checked={enableTopicNaming} onCheckedChange={setEnableTopicNaming} />
</RowFlex>
<Divider style={{ margin: 0 }} />
<div>

View File

@ -164,8 +164,8 @@ const NotesSettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('notes.settings.display.compress_content')}</SettingRowTitle>
<Switch
isSelected={!settings.isFullWidth}
onValueChange={(checked) => updateSettings({ isFullWidth: !checked })}
checked={!settings.isFullWidth}
onCheckedChange={(checked) => updateSettings({ isFullWidth: !checked })}
/>
</SettingRow>
<SettingHelpText>{t('notes.settings.display.compress_content_description')}</SettingHelpText>
@ -188,8 +188,8 @@ const NotesSettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('notes.settings.display.show_table_of_contents')}</SettingRowTitle>
<Switch
isSelected={settings.showTableOfContents}
onValueChange={(checked) => updateSettings({ showTableOfContents: checked })}
checked={settings.showTableOfContents}
onCheckedChange={(checked) => updateSettings({ showTableOfContents: checked })}
/>
</SettingRow>
<SettingHelpText>{t('notes.settings.display.show_table_of_contents_description')}</SettingHelpText>

View File

@ -124,7 +124,7 @@ const ApiOptionsSettings = ({ providerId }: Props) => {
</label>
<InfoTooltip content={item.tip}></InfoTooltip>
</RowFlex>
<Switch id={item.key} isSelected={item.checked} onValueChange={item.onChange} />
<Switch id={item.key} checked={item.checked} onCheckedChange={item.onChange} />
</RowFlex>
))}
</ColFlex>

View File

@ -343,10 +343,9 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
label={t('settings.models.add.supported_text_delta.label')}
tooltip={t('settings.models.add.supported_text_delta.tooltip')}>
<Switch
isSelected={supportedTextDelta}
checked={supportedTextDelta}
className="ml-auto"
size="sm"
onValueChange={(checked) => {
onCheckedChange={(checked) => {
setSupportedTextDelta(checked)
// 直接传递新值给autoSave
autoSave({ supported_text_delta: checked })

View File

@ -410,9 +410,9 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
)}
</Flex>
<Switch
isSelected={provider.enabled}
checked={provider.enabled}
key={provider.id}
onValueChange={(enabled) => {
onCheckedChange={(enabled) => {
updateProvider({ apiHost, enabled })
if (enabled) {
moveProviderToTop(provider.id)

View File

@ -82,14 +82,14 @@ const QuickAssistantSettings: FC = () => {
iconProps={{ className: 'cursor-pointer' }}
/>
</SettingRowTitle>
<Switch isSelected={enableQuickAssistant} onValueChange={handleEnableQuickAssistant} />
<Switch checked={enableQuickAssistant} onCheckedChange={handleEnableQuickAssistant} />
</SettingRow>
{enableQuickAssistant && (
<>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.quickAssistant.click_tray_to_show')}</SettingRowTitle>
<Switch isSelected={clickTrayToShowQuickAssistant} onValueChange={handleClickTrayToShowQuickAssistant} />
<Switch checked={clickTrayToShowQuickAssistant} onCheckedChange={handleClickTrayToShowQuickAssistant} />
</SettingRow>
</>
)}
@ -98,7 +98,7 @@ const QuickAssistantSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.quickAssistant.read_clipboard_at_startup')}</SettingRowTitle>
<Switch isSelected={readClipboardAtStartup} onValueChange={handleClickReadClipboardAtStartup} />
<Switch checked={readClipboardAtStartup} onCheckedChange={handleClickReadClipboardAtStartup} />
</SettingRow>
</>
)}

View File

@ -101,8 +101,8 @@ const SelectionAssistantSettings: FC = () => {
{!isSupportedOS && <SettingDescription>{t('selection.settings.enable.description')}</SettingDescription>}
</SettingLabel>
<Switch
isSelected={isSupportedOS && selectionEnabled}
onValueChange={handleEnableCheckboxChange}
checked={isSupportedOS && selectionEnabled}
onCheckedChange={handleEnableCheckboxChange}
disabled={!isSupportedOS}
/>
</SettingRow>
@ -162,7 +162,7 @@ const SelectionAssistantSettings: FC = () => {
<SettingRowTitle>{t('selection.settings.toolbar.compact_mode.title')}</SettingRowTitle>
<SettingDescription>{t('selection.settings.toolbar.compact_mode.description')}</SettingDescription>
</SettingLabel>
<Switch isSelected={isCompact} onValueChange={setIsCompact} />
<Switch checked={isCompact} onCheckedChange={setIsCompact} />
</SettingRow>
</SettingGroup>
@ -174,7 +174,7 @@ const SelectionAssistantSettings: FC = () => {
<SettingRowTitle>{t('selection.settings.window.follow_toolbar.title')}</SettingRowTitle>
<SettingDescription>{t('selection.settings.window.follow_toolbar.description')}</SettingDescription>
</SettingLabel>
<Switch isSelected={isFollowToolbar} onValueChange={setIsFollowToolbar} />
<Switch checked={isFollowToolbar} onCheckedChange={setIsFollowToolbar} />
</SettingRow>
<SettingDivider />
<SettingRow>
@ -182,7 +182,7 @@ const SelectionAssistantSettings: FC = () => {
<SettingRowTitle>{t('selection.settings.window.remember_size.title')}</SettingRowTitle>
<SettingDescription>{t('selection.settings.window.remember_size.description')}</SettingDescription>
</SettingLabel>
<Switch isSelected={isRemeberWinSize} onValueChange={setIsRemeberWinSize} />
<Switch checked={isRemeberWinSize} onCheckedChange={setIsRemeberWinSize} />
</SettingRow>
<SettingDivider />
<SettingRow>
@ -190,7 +190,7 @@ const SelectionAssistantSettings: FC = () => {
<SettingRowTitle>{t('selection.settings.window.auto_close.title')}</SettingRowTitle>
<SettingDescription>{t('selection.settings.window.auto_close.description')}</SettingDescription>
</SettingLabel>
<Switch isSelected={isAutoClose} onValueChange={setIsAutoClose} />
<Switch checked={isAutoClose} onCheckedChange={setIsAutoClose} />
</SettingRow>
<SettingDivider />
<SettingRow>
@ -198,7 +198,7 @@ const SelectionAssistantSettings: FC = () => {
<SettingRowTitle>{t('selection.settings.window.auto_pin.title')}</SettingRowTitle>
<SettingDescription>{t('selection.settings.window.auto_pin.description')}</SettingDescription>
</SettingLabel>
<Switch isSelected={isAutoPin} onValueChange={setIsAutoPin} />
<Switch checked={isAutoPin} onCheckedChange={setIsAutoPin} />
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -392,7 +392,7 @@ const ShortcutSettings: FC = () => {
align: 'right',
width: '50px',
render: (record: Shortcut) => (
<Switch size="sm" isSelected={record.enabled} onValueChange={() => dispatch(toggleShortcut(record.key))} />
<Switch checked={record.enabled} onCheckedChange={() => dispatch(toggleShortcut(record.key))} />
)
}
]

View File

@ -23,7 +23,7 @@ const BasicSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tool.websearch.search_with_time')}</SettingRowTitle>
<Switch isSelected={searchWithTime} onValueChange={(checked) => dispatch(setSearchWithTime(checked))} />
<Switch checked={searchWithTime} onCheckedChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 10 }} />
<SettingRow style={{ height: 40 }}>

View File

@ -67,8 +67,8 @@ const TranslateSettings: FC<{
<Flex className="items-center justify-between">
<div style={{ fontWeight: 500 }}>{t('translate.settings.preview')}</div>
<Switch
isSelected={enableMarkdown}
onValueChange={(checked) => {
checked={enableMarkdown}
onCheckedChange={(checked) => {
setEnableMarkdown(checked)
db.settings.put({ id: 'translate:markdown:enabled', value: checked })
}}
@ -80,9 +80,9 @@ const TranslateSettings: FC<{
<RowFlex className="items-center justify-between">
<div style={{ fontWeight: 500 }}>{t('translate.settings.autoCopy')}</div>
<Switch
isSelected={autoCopy}
checked={autoCopy}
color="primary"
onValueChange={(isSelected) => {
onCheckedChange={(isSelected) => {
updateSettings({ autoCopy: isSelected })
}}
/>
@ -93,9 +93,9 @@ const TranslateSettings: FC<{
<Flex className="items-center justify-between">
<div style={{ fontWeight: 500 }}>{t('translate.settings.scroll_sync')}</div>
<Switch
isSelected={isScrollSyncEnabled}
checked={isScrollSyncEnabled}
color="primary"
onValueChange={(isSelected) => {
onCheckedChange={(isSelected) => {
setIsScrollSyncEnabled(isSelected)
db.settings.put({ id: 'translate:scroll:sync', value: isSelected })
}}
@ -145,9 +145,9 @@ const TranslateSettings: FC<{
</RowFlex>
</div>
<Switch
isSelected={isBidirectional}
checked={isBidirectional}
color="primary"
onValueChange={(isSelected) => {
onCheckedChange={(isSelected) => {
setIsBidirectional(isSelected)
// 双向翻译设置不需要持久化,它只是界面状态
}}

View File

@ -231,7 +231,7 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:1.0.27, @ai-sdk/openai-compatible@npm:^1.0.19":
"@ai-sdk/openai-compatible@npm:1.0.27":
version: 1.0.27
resolution: "@ai-sdk/openai-compatible@npm:1.0.27"
dependencies:
@ -243,6 +243,18 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:^1.0.19":
version: 1.0.19
resolution: "@ai-sdk/openai-compatible@npm:1.0.19"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/5b7b21fb515e829c3d8a499a5760ffc035d9b8220695996110e361bd79e9928859da4ecf1ea072735bcbe4977c6dd0661f543871921692e86f8b5bfef14fe0e5
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch":
version: 1.0.27
resolution: "@ai-sdk/openai-compatible@patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch::version=1.0.27&hash=c44b76"
@ -291,6 +303,19 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.10":
version: 3.0.10
resolution: "@ai-sdk/provider-utils@npm:3.0.10"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@standard-schema/spec": "npm:^1.0.0"
eventsource-parser: "npm:^3.0.5"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/d2c16abdb84ba4ef48c9f56190b5ffde224b9e6ae5147c5c713d2623627732d34b96aa9aef2a2ea4b0c49e1b863cc963c7d7ff964a1dc95f0f036097aaaaaa98
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.17, @ai-sdk/provider-utils@npm:^3.0.10, @ai-sdk/provider-utils@npm:^3.0.17":
version: 3.0.17
resolution: "@ai-sdk/provider-utils@npm:3.0.17"
@ -7730,6 +7755,31 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-switch@npm:^1.2.6":
version: 1.2.6
resolution: "@radix-ui/react-switch@npm:1.2.6"
dependencies:
"@radix-ui/primitive": "npm:1.1.3"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-primitive": "npm:2.1.3"
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
"@radix-ui/react-use-previous": "npm:1.1.1"
"@radix-ui/react-use-size": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/888303cbeb0e69ebba5676b225f9ea0f00f61453c6b8a6b66384b5c5c4c7fb0ccc53493c1eb14ec6d436e5b867b302aadd6af51a1f2e6c04581c583fd9be65be
languageName: node
linkType: hard
"@radix-ui/react-tabs@npm:^1.1.13":
version: 1.1.13
resolution: "@radix-ui/react-tabs@npm:1.1.13"
@ -13822,6 +13872,7 @@ __metadata:
"@paymoapp/electron-shutdown-handler": "npm:^1.1.2"
"@playwright/test": "npm:^1.55.1"
"@radix-ui/react-context-menu": "npm:^2.2.16"
"@radix-ui/react-switch": "npm:^1.2.6"
"@reduxjs/toolkit": "npm:^2.2.5"
"@shikijs/markdown-it": "npm:^3.12.0"
"@strongtz/win32-arm64-msvc": "npm:^0.4.7"
@ -18519,7 +18570,7 @@ __metadata:
languageName: node
linkType: hard
"eventsource-parser@npm:^3.0.0":
"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.5":
version: 3.0.5
resolution: "eventsource-parser@npm:3.0.5"
checksum: 10c0/5cb75e3f84ff1cfa1cee6199d4fd430c4544855ab03e953ddbe5927e7b31bc2af3933ab8aba6440ba160ed2c48972b6c317f27b8a1d0764c7b12e34e249de631