-
- setStates({ ...states, xs: v })} />
- Extra Small (xs) — 14×10
-
-
- setStates({ ...states, sm: v })} />
- Small (sm) — 20×12
-
-
- setStates({ ...states, md: v })} />
- Regular (md) — 28×16
-
-
- setStates({ ...states, lg: v })} />
- Large (lg) — 36×20
-
+
+
+ setStates({ ...states, xs: v })} />
+ Extra Small (xs) - 14x10
+
+
+
+
+ setStates({ ...states, sm: v })} />
+ Small (sm) - 20x12
+
+
+
+
+ setStates({ ...states, md: v })} />
+ Regular (md) - 28x16
+
+
+
+
+ setStates({ ...states, lg: v })} />
+ Large (lg) - 36x20
+
+
)
}
@@ -200,30 +220,42 @@ const LoadingDemo = () => {
{loading ? 'Stop Loading' : 'Start Loading'}
-
-
Mutation Loading Guard
-
- Click once to start a simulated mutate call. While the request is pending, the switch enters
- {' '}
- loading
- {' '}
- and rejects duplicate clicks.
-
-
+
+
+
+ Enable auto retry
+
+
+ Retry failed workflow runs without manual intervention.
+
-
-
-
Enable Auto Retry
-
- {isPending ? 'Saving…' : enabled ? 'Saved as on' : 'Saved as off'}
-
-
-
-
-
-
-
-
Committed Value
-
{enabled ? 'On' : 'Off'}
-
-
-
Mutate Count
-
{requestCount}
-
-
+
+ {statusText}
+ {' '}
+ Save attempts:
+ {' '}
+ {updateAutoRetrySetting.requestCount}
+
)
}
@@ -306,7 +353,7 @@ export const MutationLoadingGuard: Story = {
parameters: {
docs: {
description: {
- story: 'Simulates a controlled switch backed by an async mutate call. The component keeps its previous committed value, sets `loading` during the request, and blocks duplicate clicks until the mutation resolves.',
+ story: 'Controlled switch that enters loading while the change is saved.',
},
},
},
@@ -315,19 +362,19 @@ export const MutationLoadingGuard: Story = {
const SkeletonDemo = () => (
-
+
Extra Small skeleton
-
+
Small skeleton
-
+
Regular skeleton
-
+
Large skeleton
@@ -338,7 +385,7 @@ export const Skeleton: Story = {
parameters: {
docs: {
description: {
- story: '`SwitchSkeleton` renders a non-interactive placeholder with `bg-text-quaternary opacity-20`. Exported from `@langgenius/dify-ui/switch` alongside `Switch`.',
+ story: 'Non-interactive placeholders for switch loading layouts.',
},
},
},
diff --git a/packages/dify-ui/src/switch/index.tsx b/packages/dify-ui/src/switch/index.tsx
index dd15ef6f79..8c4bb3e571 100644
--- a/packages/dify-ui/src/switch/index.tsx
+++ b/packages/dify-ui/src/switch/index.tsx
@@ -45,26 +45,34 @@ const switchThumbVariants = cva(
export type SwitchSize = NonNullable
['size']>
-const spinnerSizeConfig: Partial> = {
- md: {
- icon: 'size-2',
- uncheckedPosition: 'left-[calc(50%+6px)]',
- checkedPosition: 'left-[calc(50%-6px)]',
- },
- lg: {
- icon: 'size-2.5',
- uncheckedPosition: 'left-[calc(50%+8px)]',
- checkedPosition: 'left-[calc(50%-8px)]',
+const switchSpinnerVariants = cva(
+ 'absolute top-1/2 -translate-x-1/2 -translate-y-1/2',
+ {
+ variants: {
+ size: {
+ md: 'size-2 left-[calc(50%+6px)] group-data-checked:left-[calc(50%-6px)]',
+ lg: 'size-2.5 left-[calc(50%+8px)] group-data-checked:left-[calc(50%-8px)]',
+ },
+ },
},
+)
+
+type ControlledSwitchProps = {
+ checked: boolean
+ defaultChecked?: never
}
+type UncontrolledSwitchProps = {
+ checked?: never
+ defaultChecked?: boolean
+}
+
+type SwitchControlProps = ControlledSwitchProps | UncontrolledSwitchProps
+
export type SwitchProps
- = Omit
+ = Omit
& VariantProps
+ & SwitchControlProps
& {
onCheckedChange?: (checked: boolean) => void
loading?: boolean
@@ -81,7 +89,6 @@ export function Switch({
...props
}: SwitchProps) {
const isDisabled = disabled || loading
- const spinner = loading && size ? spinnerSizeConfig[size] : undefined
return (
- {spinner
+ {loading && (size === 'md' || size === 'lg')
? (
@@ -131,11 +134,8 @@ const switchSkeletonVariants = cva(
)
export type SwitchSkeletonProps
- = Omit, 'className'>
+ = HTMLAttributes
& VariantProps
- & {
- className?: string
- }
export function SwitchSkeleton({
size = 'md',
diff --git a/packages/dify-ui/src/toast/__tests__/index.spec.tsx b/packages/dify-ui/src/toast/__tests__/index.spec.tsx
index 68ba746f4f..0b06c6e1be 100644
--- a/packages/dify-ui/src/toast/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/toast/__tests__/index.spec.tsx
@@ -41,6 +41,7 @@ describe('@langgenius/dify-ui/toast', () => {
await expect.element(screen.getByRole('region', { name: 'Notifications' })).toHaveAttribute('aria-live', 'polite')
await expect.element(screen.getByRole('region', { name: 'Notifications' })).toHaveClass('z-60')
expect(screen.getByRole('region', { name: 'Notifications' }).element().firstElementChild).toHaveClass('top-4')
+ expect(screen.getByText('Saved').element().closest('[class*="transition-opacity"]')).toHaveClass('motion-reduce:transition-none')
expect(screen.getByRole('dialog').element()).not.toHaveClass('outline-hidden')
expect(document.body.querySelector('[aria-hidden="true"].i-ri-checkbox-circle-fill')).toBeInTheDocument()
expect(document.body.querySelector('button[aria-label="Close notification"][aria-hidden="true"]')).toBeInTheDocument()
diff --git a/packages/dify-ui/src/toast/index.tsx b/packages/dify-ui/src/toast/index.tsx
index 7d4e867faf..3bdeafdad8 100644
--- a/packages/dify-ui/src/toast/index.tsx
+++ b/packages/dify-ui/src/toast/index.tsx
@@ -171,7 +171,7 @@ function ToastCard({
aria-hidden="true"
className={cn('absolute -inset-px bg-linear-to-r opacity-40', getToneGradientClasses(toastType))}
/>
-
+