Compare commits

..

3 Commits

Author SHA1 Message Date
yyh
457f7ba632 fix(dify-ui): finish ESLint dependency cleanup 2026-07-11 20:26:28 +08:00
yyh
3b68fb2088 fix(dify-ui): address ESLint review findings 2026-07-11 20:19:18 +08:00
yyh
5c8c8ba967 chore(dify-ui): enforce strict ESLint rules 2026-07-11 20:02:09 +08:00
33 changed files with 1155 additions and 186 deletions

View File

@ -176,9 +176,9 @@ jobs:
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .eslintcache
key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
- name: Style check
if: steps.changed-files.outputs.any_changed == 'true'

View File

@ -188,6 +188,7 @@ See `[web/docs/overlay.md](../../web/docs/overlay.md)` for the web app overlay b
## Development
- `vp run @langgenius/dify-ui#lint` (from the repository root) — strict ESLint checks for component source, stories, tests, and package configuration.
- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives.
- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`.
- `pnpm -C packages/dify-ui test:storybook` — Storybook component tests in Vitest browser mode. Stories without `play` are render and a11y smoke tests; stories with `play` should cover public UI contracts such as opening overlays, keyboard navigation, disabled/loading guards, form submission, and controlled state updates.

View File

@ -0,0 +1,109 @@
// @ts-check
import path from 'node:path'
import antfu, { GLOB_MARKDOWN_CODE, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config'
import tailwindcss from 'eslint-plugin-better-tailwindcss'
import jsxA11y from 'eslint-plugin-jsx-a11y'
import storybook from 'eslint-plugin-storybook'
const SOURCE_FILES = [GLOB_TS, GLOB_TSX]
const TEST_FILES = ['**/__tests__/**/*.{ts,tsx}', '**/*.spec.{ts,tsx}']
export default antfu(
{
ignores: [
'coverage/',
'dist/',
'storybook-static/',
],
react: {
overrides: {
'react/exhaustive-deps': ['error', { additionalHooks: 'useIsoLayoutEffect' }],
'react/no-context-provider': 'off',
'react/no-unnecessary-use-prefix': 'error',
'react/no-use-context': 'off',
'react/rules-of-hooks': 'error',
'react/set-state-in-effect': 'error',
'react/set-state-in-render': 'error',
'react/static-components': 'error',
'react-refresh/only-export-components': 'off',
},
},
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'type'],
'ts/no-explicit-any': 'error',
'ts/no-redeclare': 'off',
},
erasableOnly: true,
},
test: {
overrides: {
'test/prefer-lowercase-title': 'off',
},
},
stylistic: {
overrides: {
'antfu/top-level-function': 'off',
},
},
e18e: false,
pnpm: false,
},
{
files: [GLOB_TSX],
...jsxA11y.flatConfigs.recommended,
rules: {
...jsxA11y.flatConfigs.recommended.rules,
'jsx-a11y/anchor-has-content': 'off',
'jsx-a11y/label-has-associated-control': 'off',
},
},
...storybook.configs['flat/recommended'],
{
name: 'dify-ui/storybook',
files: ['**/.storybook/main.{js,cjs,mjs,ts}'],
rules: {
'storybook/no-uninstalled-addons': ['error', {
packageJsonLocation: path.resolve(import.meta.dirname, 'package.json'),
}],
},
},
{
name: 'dify-ui/tailwind',
files: SOURCE_FILES,
ignores: [GLOB_MARKDOWN_CODE, ...TEST_FILES],
plugins: {
tailwindcss,
},
rules: {
'tailwindcss/enforce-consistent-class-order': 'error',
'tailwindcss/no-duplicate-classes': 'error',
'tailwindcss/no-deprecated-classes': 'error',
'tailwindcss/no-unknown-classes': 'error',
'tailwindcss/no-unnecessary-whitespace': 'error',
},
settings: {
'better-tailwindcss': {
cwd: import.meta.dirname,
entryPoint: path.resolve(import.meta.dirname, './.storybook/storybook.css'),
},
},
},
{
files: TEST_FILES,
rules: {
'react/purity': 'off',
},
},
{
rules: {
'node/prefer-global/process': 'off',
},
},
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
},
)

View File

@ -151,6 +151,7 @@
}
},
"scripts": {
"lint": "eslint --cache --max-warnings=0 .",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build",
"test": "vp test --project unit",
@ -170,10 +171,12 @@
"tailwind-merge": "catalog:"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@base-ui/react": "catalog:",
"@chromatic-com/storybook": "catalog:",
"@dify/tsconfig": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",
"@iconify-json/ri": "catalog:",
"@storybook/addon-a11y": "catalog:",
"@storybook/addon-docs": "catalog:",
@ -192,6 +195,11 @@
"@vitest/browser-playwright": "catalog:",
"@vitest/coverage-v8": "catalog:",
"class-variance-authority": "catalog:",
"eslint": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-react-refresh": "catalog:",
"eslint-plugin-storybook": "catalog:",
"playwright": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",

View File

@ -254,7 +254,7 @@ const BasicTagAutocomplete = ({
openOnInputClick
>
<AutocompleteInputGroup size={size}>
<span className="i-ri-search-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-search-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput size={size} placeholder="Search tags or type a new one…" aria-label="Search tags or type a new one" />
<AutocompleteClear size={size} />
<AutocompleteTrigger size={size} />
@ -396,7 +396,7 @@ const AsyncSearchDemo = () => {
mode="list"
>
<AutocompleteInputGroup>
<span className="i-ri-cloud-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-cloud-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Search remote resources…" aria-label="Search remote resources" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -441,7 +441,7 @@ const VirtualizedSuggestionList = ({
return (
<div
ref={scrollRef}
className="max-h-[min(22rem,var(--available-height))] overflow-y-auto overflow-x-hidden overscroll-contain outline-hidden"
className="max-h-[min(22rem,var(--available-height))] overflow-x-hidden overflow-y-auto overscroll-contain outline-hidden"
>
<AutocompleteList
className="relative max-h-none overflow-visible p-0"
@ -504,6 +504,7 @@ const FuzzyHighlight = ({
<React.Fragment>
{parts.map((part, index) => (
part.toLowerCase() === query.trim().toLowerCase()
// eslint-disable-next-line react/no-array-index-key -- Repeated text fragments have no stable identity and never preserve state.
? <mark key={`${part}-${index}`} className="bg-transparent text-text-accent">{part}</mark>
: part
))}
@ -527,7 +528,7 @@ const FuzzyMatchingDemo = () => {
openOnInputClick
>
<AutocompleteInputGroup>
<span className="i-ri-sparkling-2-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-sparkling-2-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Fuzzy search workflow suggestions…" aria-label="Fuzzy search workflow suggestions" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -600,7 +601,7 @@ export const InlineAutocomplete: Story = {
openOnInputClick
>
<AutocompleteInputGroup>
<span className="i-ri-text-snippet ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-text-snippet size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Type a prompt starter…" aria-label="Type a prompt starter" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -628,7 +629,7 @@ export const GroupedSuggestions: Story = {
openOnInputClick
>
<AutocompleteInputGroup>
<span className="i-ri-command-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-command-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Search tags, nodes, or prompt starters…" aria-label="Search tags, nodes, or prompt starters" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -657,7 +658,7 @@ export const LimitResults: Story = {
openOnInputClick
>
<AutocompleteInputGroup>
<span className="i-ri-tools-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-tools-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Search workflow suggestions…" aria-label="Search workflow suggestions" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -691,7 +692,7 @@ export const CommandPalette: Story = {
keepHighlight
>
<AutocompleteInputGroup className="mb-2">
<span className="i-ri-search-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-search-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Run a command…" aria-label="Run a command" aria-expanded="true" />
<AutocompleteClear />
</AutocompleteInputGroup>
@ -717,7 +718,7 @@ const VirtualizedLongSuggestionsDemo = () => {
}}
>
<AutocompleteInputGroup>
<span className="i-ri-search-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-search-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Search 1,000 workspace suggestions…" aria-label="Search 1,000 workspace suggestions" />
<AutocompleteClear />
<AutocompleteTrigger />
@ -751,7 +752,7 @@ export const Empty: Story = {
openOnInputClick
>
<AutocompleteInputGroup>
<span className="i-ri-search-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<span className="ml-2 i-ri-search-line size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Search tags or type a new one…" aria-label="Search tags or type a new one" />
<AutocompleteClear />
<AutocompleteTrigger />

View File

@ -12,7 +12,7 @@ const buttonVariants = cva(
variants: {
variant: {
'primary': [
'border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text shadow',
'border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text shadow-sm',
'hover:border-components-button-primary-border-hover hover:bg-components-button-primary-bg-hover',
'data-[disabled]:border-components-button-primary-border-disabled data-[disabled]:bg-components-button-primary-bg-disabled data-[disabled]:text-components-button-primary-text-disabled data-[disabled]:shadow-none',
],

View File

@ -9,7 +9,7 @@ const checkboxRootClassName = cn(
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-sm shadow-xs shadow-shadow-shadow-3 transition-colors motion-reduce:transition-none',
'border border-components-checkbox-border bg-components-checkbox-bg-unchecked text-components-checkbox-icon',
'hover:border-components-checkbox-border-hover hover:bg-components-checkbox-bg-unchecked-hover',
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0 focus-visible:outline-hidden',
'data-checked:border-transparent data-checked:bg-components-checkbox-bg data-checked:hover:bg-components-checkbox-bg-hover',
'data-indeterminate:border-transparent data-indeterminate:bg-components-checkbox-bg data-indeterminate:hover:bg-components-checkbox-bg-hover',
'data-disabled:cursor-not-allowed data-disabled:border-components-checkbox-border-disabled data-disabled:bg-components-checkbox-bg-disabled',

View File

@ -89,25 +89,27 @@ export const DefaultOpen: Story = {
),
}
export const Controlled: Story = {
render: () => {
const [open, setOpen] = React.useState(true)
function ControlledDemo() {
const [open, setOpen] = React.useState(true)
return (
<div className="flex flex-col items-start gap-3">
<button
type="button"
className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 system-sm-medium text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
onClick={() => setOpen(value => !value)}
>
{open ? 'Close panel' : 'Open panel'}
</button>
<Collapsible open={open} onOpenChange={setOpen} className={rootClassName}>
<RecoveryKeys />
</Collapsible>
</div>
)
},
return (
<div className="flex flex-col items-start gap-3">
<button
type="button"
className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 system-sm-medium text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
onClick={() => setOpen(value => !value)}
>
{open ? 'Close panel' : 'Open panel'}
</button>
<Collapsible open={open} onOpenChange={setOpen} className={rootClassName}>
<RecoveryKeys />
</Collapsible>
</div>
)
}
export const Controlled: Story = {
render: () => <ControlledDemo />,
}
export const Disabled: Story = {
@ -172,7 +174,7 @@ export const SettingsSections: Story = {
<TriggerIcon />
</CollapsibleTrigger>
<CollapsiblePanel className={sectionPanelClassName}>
<div className="px-3 pb-3 pt-1 system-sm-regular text-text-secondary">
<div className="px-3 pt-1 pb-3 system-sm-regular text-text-secondary">
{section.description}
</div>
</CollapsiblePanel>

View File

@ -212,8 +212,8 @@ const renderOptionItem = (option: Option) => (
<ComboboxItemText className="flex items-center gap-2 px-0">
{option.icon && <span aria-hidden className={cn(option.icon, 'size-4 shrink-0 text-text-tertiary')} />}
<span className="min-w-0 flex-1">
<span className="block truncate text-text-secondary system-sm-medium">{option.label}</span>
{option.meta && <span className="block truncate text-text-tertiary system-xs-regular">{option.meta}</span>}
<span className="block truncate system-sm-medium text-text-secondary">{option.label}</span>
{option.meta && <span className="block truncate system-xs-regular text-text-tertiary">{option.meta}</span>}
</span>
</ComboboxItemText>
<ComboboxItemIndicator />
@ -233,8 +233,8 @@ const renderVirtualizedOptionItem = (option: Option, index: number) => (
<ComboboxItemText className="flex items-center gap-2 px-0">
{option.icon && <span aria-hidden className={cn(option.icon, 'size-4 shrink-0 text-text-tertiary')} />}
<span className="min-w-0 flex-1">
<span className="block truncate text-text-secondary system-sm-medium">{option.label}</span>
{option.meta && <span className="block truncate text-text-tertiary system-xs-regular">{option.meta}</span>}
<span className="block truncate system-sm-medium text-text-secondary">{option.label}</span>
{option.meta && <span className="block truncate system-xs-regular text-text-tertiary">{option.meta}</span>}
</span>
</ComboboxItemText>
<ComboboxItemIndicator />
@ -300,7 +300,7 @@ const VirtualizedModelList = ({
return (
<div
ref={scrollRef}
className="max-h-[min(22rem,var(--available-height))] overflow-y-auto overflow-x-hidden overscroll-contain outline-hidden"
className="max-h-[min(22rem,var(--available-height))] overflow-x-hidden overflow-y-auto overscroll-contain outline-hidden"
>
<ComboboxList
className="relative max-h-none overflow-visible p-0"
@ -854,7 +854,7 @@ const ControlledDemo = () => {
</ComboboxContent>
</Combobox>
</div>
<span className="rounded-md border border-divider-subtle bg-components-panel-bg px-2 py-1 text-text-tertiary system-xs-regular">
<span className="rounded-md border border-divider-subtle bg-components-panel-bg px-2 py-1 system-xs-regular text-text-tertiary">
Selected:
{' '}
{value?.label ?? 'None'}

View File

@ -478,7 +478,7 @@ export function ComboboxChip({
}: BaseCombobox.Chip.Props) {
return (
<BaseCombobox.Chip
className={cn('inline-flex max-w-full min-w-0 items-center gap-1 rounded-md bg-state-base-hover px-1.5 py-0.5 text-text-secondary system-xs-medium', className)}
className={cn('inline-flex max-w-full min-w-0 items-center gap-1 rounded-md bg-state-base-hover px-1.5 py-0.5 system-xs-medium text-text-secondary', className)}
{...props}
/>
)

View File

@ -324,7 +324,7 @@ const OutsideScrollingContentDemo = () => {
<DialogPopup
ref={popupRef}
initialFocus={popupRef}
className="relative mx-auto flex w-120 max-w-[calc(100vw-2rem)] flex-col overflow-hidden outline-hidden transition-[translate] duration-[700ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-starting-style:translate-y-[100dvh] data-starting-style:scale-100 data-starting-style:opacity-100 data-ending-style:translate-y-[max(100dvh,100%)] data-ending-style:scale-100 data-ending-style:opacity-100 data-ending-style:duration-[350ms] data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)]"
className="relative mx-auto flex w-120 max-w-[calc(100vw-2rem)] flex-col overflow-hidden outline-hidden transition-[translate] duration-[700ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-ending-style:translate-y-[max(100dvh,100%)] data-ending-style:scale-100 data-ending-style:opacity-100 data-ending-style:duration-[350ms] data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)] data-starting-style:translate-y-[100dvh] data-starting-style:scale-100 data-starting-style:opacity-100"
>
<DialogCloseButton />
<ReleaseNoteHeader
@ -361,10 +361,10 @@ export const OutsidePopupElements: Story = {
<DialogPortal>
<DialogBackdrop className="min-h-dvh" />
<DialogViewport className="grid place-items-center px-4 py-12 xl:py-6">
<DialogPopup className="group/popup relative flex h-full w-full max-w-[70rem] justify-center border-0 bg-transparent shadow-none pointer-events-none transition-opacity data-starting-style:scale-100 data-starting-style:opacity-0 data-ending-style:scale-100 data-ending-style:opacity-0">
<DialogPopup className="group/popup pointer-events-none relative flex h-full w-full max-w-[70rem] justify-center border-0 bg-transparent shadow-none transition-opacity data-ending-style:scale-100 data-ending-style:opacity-0 data-starting-style:scale-100 data-starting-style:opacity-0">
<DialogCloseButton
aria-label="Close"
className="pointer-events-auto absolute right-0 -top-10 z-10 flex size-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg text-text-tertiary shadow-xs outline-hidden hover:bg-components-button-secondary-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid xl:top-0"
className="pointer-events-auto absolute -top-10 right-0 z-10 flex size-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg text-text-tertiary shadow-xs outline-hidden hover:bg-components-button-secondary-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid xl:top-0"
>
</DialogCloseButton>
<div className="pointer-events-auto flex h-full w-full max-w-[70rem] flex-col overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl transition-[scale] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-starting-style/popup:scale-105">
@ -401,7 +401,7 @@ const InsideScrollingContentDemo = () => {
<DialogBackdrop />
<DialogViewport className="flex items-center justify-center overflow-hidden p-4">
<DialogPopup
className="relative flex h-[min(44rem,calc(100dvh-2rem))] w-120 max-w-[calc(100vw-2rem)] min-h-0 flex-col overflow-hidden"
className="relative flex h-[min(44rem,calc(100dvh-2rem))] min-h-0 w-120 max-w-[calc(100vw-2rem)] flex-col overflow-hidden"
>
<DialogCloseButton />
<ReleaseNoteHeader

View File

@ -78,7 +78,7 @@ export function DialogCloseButton({
aria-label={ariaLabel}
{...props}
className={cn(
'absolute top-6 end-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
'absolute inset-e-6 top-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
>

View File

@ -232,7 +232,7 @@ export const Positions: Story = {
<DrawerPortal>
<DrawerBackdrop className="fixed" />
<DrawerViewport className="flex items-end justify-center">
<DrawerPopup className="-mb-12 touch-auto data-[swipe-direction=down]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]">
<DrawerPopup className="-mb-12 touch-auto data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-[swipe-direction=down]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))]">
<div className={handleClassName} />
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-12">
<div className="mx-auto w-full max-w-lg px-6 pt-6 pb-4 text-center">
@ -263,7 +263,7 @@ export const Positions: Story = {
<DrawerPortal>
<DrawerBackdrop className="fixed" />
<DrawerViewport className="flex items-start justify-center">
<DrawerPopup className="-mt-12 touch-auto data-[swipe-direction=up]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%_+_3rem_-_2px))] data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%_+_3rem_-_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]">
<DrawerPopup className="-mt-12 touch-auto data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-[swipe-direction=up]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%_+_3rem_-_2px))] data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%_+_3rem_-_2px))]">
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pt-12">
<div className="mx-auto w-full max-w-128 px-6 pt-6 pb-4 text-center">
<DrawerTitle className="text-lg/6 font-semibold text-text-primary">
@ -314,12 +314,12 @@ function SnapPointsDemo() {
<DrawerViewport className="flex touch-none items-end justify-center">
<DrawerPopup
className={cn(
'relative overflow-visible touch-none',
'relative touch-none overflow-visible',
'[--bleed:3rem] [--top-margin:1rem]',
'pb-[max(0px,calc(var(--drawer-snap-point-offset,0px)_+_var(--drawer-swipe-movement-y,0px)))]',
'after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-(--bleed) after:bg-inherit after:content-[""]',
'data-[swipe-direction=down]:max-h-[calc(100dvh_-_var(--top-margin))]',
'data-starting-style:pb-0 data-ending-style:pb-0',
'data-ending-style:pb-0 data-starting-style:pb-0',
)}
>
<div className="shrink-0 touch-none border-b-[0.5px] border-divider-subtle px-6 pt-3.5 pb-4">
@ -374,13 +374,13 @@ export const NestedDrawers: Story = {
<DrawerViewport>
<DrawerPopup
className={cn(
'[--bleed:3rem] [--stack-step:0.05] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))]',
'[--bleed:3rem] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))] [--stack-step:0.05]',
'[--stack-height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))_-_var(--bleed)))]',
'-mb-12 touch-auto origin-bottom',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:content-[""] after:transition-[background-color] after:duration-200',
'-mb-12 origin-bottom touch-auto',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-[background-color] after:duration-200 after:content-[""]',
'data-[swipe-direction=down]:h-(--drawer-height) data-[swipe-direction=down]:max-h-[calc(82dvh_+_3rem)]',
'data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)_+_var(--drawer-swipe-movement-y,0px)))_scale(var(--stack-scale))]',
'data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]',
'data-nested-drawer-open:data-[swipe-direction=down]:h-[calc(var(--stack-height)_+_var(--bleed))]',
'data-nested-drawer-open:overflow-hidden data-nested-drawer-open:shadow-lg',
@ -411,13 +411,13 @@ export const NestedDrawers: Story = {
<DrawerViewport className="flex items-end justify-center">
<DrawerPopup
className={cn(
'[--bleed:3rem] [--stack-step:0.05] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))]',
'[--bleed:3rem] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))] [--stack-step:0.05]',
'[--stack-height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))_-_var(--bleed)))]',
'-mb-12 touch-auto origin-bottom',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:content-[""] after:transition-[background-color] after:duration-200',
'-mb-12 origin-bottom touch-auto',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-[background-color] after:duration-200 after:content-[""]',
'data-[swipe-direction=down]:h-(--drawer-height) data-[swipe-direction=down]:max-h-[calc(82dvh_+_3rem)]',
'data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)_+_var(--drawer-swipe-movement-y,0px)))_scale(var(--stack-scale))]',
'data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]',
'data-nested-drawer-open:data-[swipe-direction=down]:h-[calc(var(--stack-height)_+_var(--bleed))]',
'data-nested-drawer-open:overflow-hidden data-nested-drawer-open:shadow-lg',
@ -454,13 +454,13 @@ export const NestedDrawers: Story = {
<DrawerViewport className="flex items-end justify-center">
<DrawerPopup
className={cn(
'[--bleed:3rem] [--stack-step:0.05] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))]',
'[--bleed:3rem] [--stack-scale:calc(1_-_(var(--nested-drawers,0)_*_var(--stack-step)))] [--stack-step:0.05]',
'[--stack-height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))_-_var(--bleed)))]',
'-mb-12 touch-auto origin-bottom',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:content-[""] after:transition-[background-color] after:duration-200',
'-mb-12 origin-bottom touch-auto',
'after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-[background-color] after:duration-200 after:content-[""]',
'data-[swipe-direction=down]:h-(--drawer-height) data-[swipe-direction=down]:max-h-[calc(82dvh_+_3rem)]',
'data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)_+_var(--drawer-swipe-movement-y,0px)))_scale(var(--stack-scale))]',
'data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_var(--bleed)_+_2px))]',
'data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]',
'data-nested-drawer-open:data-[swipe-direction=down]:h-[calc(var(--stack-height)_+_var(--bleed))]',
'data-nested-drawer-open:overflow-hidden data-nested-drawer-open:shadow-lg',
@ -533,7 +533,7 @@ function IndentEffectDemo() {
<DrawerPortal container={portalContainer}>
<DrawerBackdrop />
<DrawerViewport className="absolute flex items-end justify-center">
<DrawerPopup className="absolute -mb-12 touch-auto data-[swipe-direction=down]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]">
<DrawerPopup className="absolute -mb-12 touch-auto data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-[swipe-direction=down]:max-h-[calc(80dvh_+_3rem)] data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_-_3rem_+_2px))]">
<div className={handleClassName} />
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-12">
<div className="mx-auto w-full max-w-96 px-6 pt-6 pb-4 text-center">
@ -590,7 +590,7 @@ function NonModalDemo() {
</div>
<DrawerPortal>
<DrawerViewport className="pointer-events-none">
<DrawerPopup className="pointer-events-auto touch-auto data-[swipe-direction=right]:right-3 data-[swipe-direction=right]:top-16 data-[swipe-direction=right]:bottom-3 data-[swipe-direction=right]:h-auto data-[swipe-direction=right]:max-w-[420px] data-[swipe-direction=right]:rounded-2xl data-[swipe-direction=right]:border-r-[0.5px]">
<DrawerPopup className="pointer-events-auto touch-auto data-[swipe-direction=right]:top-16 data-[swipe-direction=right]:right-3 data-[swipe-direction=right]:bottom-3 data-[swipe-direction=right]:h-auto data-[swipe-direction=right]:max-w-[420px] data-[swipe-direction=right]:rounded-2xl data-[swipe-direction=right]:border-r-[0.5px]">
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-0">
<div className="flex shrink-0 items-start justify-between gap-4 px-6 pt-6 pb-4">
<div className="min-w-0">
@ -666,11 +666,11 @@ export const MobileNavigation: Story = {
<DrawerViewport className="group">
<ScrollAreaRoot
style={{ position: undefined }}
className="h-full overscroll-contain transition-transform duration-600 ease-[cubic-bezier(0.45,1.005,0,1.005)] motion-reduce:transition-none group-data-starting-style:translate-y-[100dvh] group-data-ending-style:pointer-events-none"
className="h-full overscroll-contain transition-transform duration-600 ease-[cubic-bezier(0.45,1.005,0,1.005)] group-data-ending-style:pointer-events-none group-data-starting-style:translate-y-[100dvh] motion-reduce:transition-none"
>
<ScrollAreaViewport className="size-full touch-auto overscroll-contain" role="region" aria-label="Mobile drawer viewport">
<ScrollAreaContent className="flex min-h-full min-w-0 items-end justify-center pt-8 min-[42rem]:px-16 min-[42rem]:py-16">
<DrawerPopup className="relative w-full max-w-[42rem] touch-auto overflow-visible transition-transform duration-600 ease-[cubic-bezier(0.45,1.005,0,1.005)] motion-reduce:transition-none data-[swipe-direction=down]:inset-auto data-[swipe-direction=down]:max-h-none data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-swiping:select-none data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(max(100dvh,100%)_+_2px))] data-ending-style:duration-350 data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)] min-[42rem]:rounded-2xl min-[42rem]:border-[0.5px] min-[42rem]:border-b-[0.5px]">
<DrawerPopup className="relative w-full max-w-[42rem] touch-auto overflow-visible transition-transform duration-600 ease-[cubic-bezier(0.45,1.005,0,1.005)] data-ending-style:duration-350 data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)] data-swiping:select-none data-[swipe-direction=down]:inset-auto data-[swipe-direction=down]:max-h-none data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(max(100dvh,100%)_+_2px))] motion-reduce:transition-none min-[42rem]:rounded-2xl min-[42rem]:border-[0.5px] min-[42rem]:border-b-[0.5px]">
<nav aria-label="Mobile navigation" className="relative flex flex-col bg-components-panel-bg">
<div className="grid grid-cols-[1fr_auto_1fr] items-start px-6 pt-4">
<div aria-hidden className="h-9 w-9" />
@ -745,7 +745,7 @@ function SwipeToOpenDemo() {
<DrawerPortal container={portalContainer}>
<DrawerBackdrop className="absolute" />
<DrawerViewport className="absolute">
<DrawerPopup className="absolute touch-auto data-[swipe-direction=right]:-right-12 data-[swipe-direction=right]:h-full data-[swipe-direction=right]:w-[calc(22.5rem_+_3rem)] data-[swipe-direction=right]:max-w-[calc(100%_+_3rem)] data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_-_3rem_+_2px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_-_3rem_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]">
<DrawerPopup className="absolute touch-auto data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-[swipe-direction=right]:-right-12 data-[swipe-direction=right]:h-full data-[swipe-direction=right]:w-[calc(22.5rem_+_3rem)] data-[swipe-direction=right]:max-w-[calc(100%_+_3rem)] data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_-_3rem_+_2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_-_3rem_+_2px))]">
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pr-12 pb-0">
<div className="flex shrink-0 items-start justify-between gap-4 px-6 pt-6 pb-4">
<div className="min-w-0">
@ -792,7 +792,7 @@ function ActionSheetDemo() {
<DrawerPortal>
<DrawerBackdrop className="fixed" />
<DrawerViewport className="flex items-end justify-center">
<DrawerPopup className="pointer-events-none relative flex w-full max-w-80 flex-col gap-3 overflow-visible border-none bg-transparent px-4 pb-[calc(1rem_+_env(safe-area-inset-bottom,0px))] shadow-none data-[swipe-direction=down]:inset-auto data-[swipe-direction=down]:max-h-none data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_+_1rem_+_2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_+_1rem_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)]">
<DrawerPopup className="pointer-events-none relative flex w-full max-w-80 flex-col gap-3 overflow-visible border-none bg-transparent px-4 pb-[calc(1rem_+_env(safe-area-inset-bottom,0px))] shadow-none data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-[swipe-direction=down]:inset-auto data-[swipe-direction=down]:max-h-none data-[swipe-direction=down]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_+_1rem_+_2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%_+_1rem_+_2px))]">
<DrawerContent className="pointer-events-auto flex-none overflow-hidden rounded-2xl border-[0.5px] border-divider-subtle bg-components-panel-bg p-0 pb-0 shadow-xl">
<DrawerTitle className="sr-only">App actions</DrawerTitle>
<DrawerDescription className="sr-only">
@ -942,7 +942,7 @@ export const StackingAndAnimations: Story = {
<DrawerPortal>
<DrawerBackdrop className="fixed" />
<DrawerViewport>
<DrawerPopup className="duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-swiping:shadow-none data-nested-drawer-open:brightness-95 data-nested-drawer-open:[&_[data-animation-content]]:opacity-0 data-nested-drawer-swiping:[&_[data-animation-content]]:opacity-100">
<DrawerPopup className="duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-nested-drawer-open:brightness-95 data-swiping:shadow-none data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-nested-drawer-open:[&_[data-animation-content]]:opacity-0 data-nested-drawer-swiping:[&_[data-animation-content]]:opacity-100">
<DrawerContent data-animation-content className="flex min-h-0 flex-1 flex-col p-0 pb-0 transition-opacity duration-300">
<div className="flex shrink-0 items-start justify-between gap-4 px-6 pt-6 pb-4">
<div className="min-w-0">
@ -966,7 +966,7 @@ export const StackingAndAnimations: Story = {
</DrawerTrigger>
<DrawerPortal>
<DrawerViewport>
<DrawerPopup className="duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-swiping:shadow-none">
<DrawerPopup className="duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] data-ending-style:duration-[calc(var(--drawer-swipe-strength)_*_400ms)] data-swiping:shadow-none data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%_+_2px))]">
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-0">
<div className="flex shrink-0 items-start justify-between gap-4 px-6 pt-6 pb-4">
<div className="min-w-0">
@ -1027,7 +1027,7 @@ export const InstantRightPanel: Story = {
</DrawerTrigger>
<DrawerPortal>
<DrawerViewport className="pointer-events-none">
<DrawerPopup className="pointer-events-auto transition-none duration-0 data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(0)] data-ending-style:data-[swipe-direction=right]:transform-[translateX(0)] data-swiping:shadow-none">
<DrawerPopup className="pointer-events-auto transition-none duration-0 data-swiping:shadow-none data-[swipe-direction=right]:max-w-105 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(0)] data-starting-style:data-[swipe-direction=right]:transform-[translateX(0)]">
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-0">
<div className="flex shrink-0 items-start justify-between gap-4 px-6 pt-6 pb-4">
<div className="min-w-0">

View File

@ -60,16 +60,16 @@ export function DrawerPopup({
return (
<BaseDrawer.Popup
className={cn(
'fixed z-50 flex min-h-0 flex-col overflow-hidden border-[0.5px] border-components-panel-border bg-components-panel-bg text-text-primary shadow-xl outline-hidden touch-none',
'transition-[transform,opacity,box-shadow] duration-200 data-swiping:select-none data-swiping:duration-0 motion-reduce:transition-none',
'data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:h-dvh data-[swipe-direction=right]:w-120 data-[swipe-direction=right]:max-w-[calc(100vw-2rem)] data-[swipe-direction=right]:rounded-l-2xl data-[swipe-direction=right]:border-r-0 data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))]',
'data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))] data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))]',
'data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:h-dvh data-[swipe-direction=left]:w-120 data-[swipe-direction=left]:max-w-[calc(100vw-2rem)] data-[swipe-direction=left]:rounded-r-2xl data-[swipe-direction=left]:border-l-0 data-[swipe-direction=left]:transform-[translateX(var(--drawer-swipe-movement-x,0px))]',
'data-starting-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))] data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))]',
'data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=down]:w-full data-[swipe-direction=down]:rounded-t-2xl data-[swipe-direction=down]:border-b-0 data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px)))]',
'data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))] data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))]',
'data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=up]:w-full data-[swipe-direction=up]:rounded-b-2xl data-[swipe-direction=up]:border-t-0 data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))]',
'data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))] data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))]',
'fixed z-50 flex min-h-0 touch-none flex-col overflow-hidden border-[0.5px] border-components-panel-border bg-components-panel-bg text-text-primary shadow-xl outline-hidden',
'transition-[transform,opacity,box-shadow] duration-200 data-swiping:duration-0 data-swiping:select-none motion-reduce:transition-none',
'data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:h-dvh data-[swipe-direction=right]:w-120 data-[swipe-direction=right]:max-w-[calc(100vw-2rem)] data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=right]:rounded-l-2xl data-[swipe-direction=right]:border-r-0',
'data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))]',
'data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:h-dvh data-[swipe-direction=left]:w-120 data-[swipe-direction=left]:max-w-[calc(100vw-2rem)] data-[swipe-direction=left]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=left]:rounded-r-2xl data-[swipe-direction=left]:border-l-0',
'data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))] data-starting-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))]',
'data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=down]:w-full data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px)))] data-[swipe-direction=down]:rounded-t-2xl data-[swipe-direction=down]:border-b-0',
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))]',
'data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=up]:w-full data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-[swipe-direction=up]:rounded-b-2xl data-[swipe-direction=up]:border-t-0',
'data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))] data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))]',
className,
)}
{...props}

View File

@ -98,7 +98,7 @@ export function FieldDescription({
}: FieldDescriptionProps) {
return (
<BaseField.Description
className={cn('py-0.5 text-text-tertiary body-xs-regular', className)}
className={cn('py-0.5 body-xs-regular text-text-tertiary', className)}
{...props}
/>
)
@ -116,7 +116,7 @@ export function FieldError({
}: FieldErrorProps) {
return (
<BaseField.Error
className={cn('py-0.5 text-text-destructive body-xs-regular', className)}
className={cn('py-0.5 body-xs-regular text-text-destructive', className)}
{...props}
/>
)

View File

@ -34,7 +34,7 @@ export function FieldsetLegend({
}: FieldsetLegendProps) {
return (
<BaseFieldset.Legend
className={cn('mb-1 py-1 text-text-secondary system-sm-medium', className)}
className={cn('mb-1 py-1 system-sm-medium text-text-secondary', className)}
{...props}
/>
)

View File

@ -226,7 +226,7 @@ export function FileTreeGuide({
const defaultProps: useRender.ElementProps<'span'> = {
'aria-hidden': true,
'className': cn(
'relative h-6 w-5 shrink-0 before:absolute before:bottom-[-1px] before:left-1/2 before:top-0 before:w-px before:-translate-x-1/2 before:bg-divider-subtle',
'relative h-6 w-5 shrink-0 before:absolute before:top-0 before:bottom-[-1px] before:left-1/2 before:w-px before:-translate-x-1/2 before:bg-divider-subtle',
className,
),
}
@ -287,8 +287,8 @@ export function FileTreeIcon({
type === 'folder'
? (
<React.Fragment>
<span className="size-4 i-ri-folder-line group-data-panel-open/file-tree-row:hidden" />
<span className="hidden size-4 text-text-secondary i-ri-folder-open-line group-data-panel-open/file-tree-row:block" />
<span className="i-ri-folder-line size-4 group-data-panel-open/file-tree-row:hidden" />
<span className="i-ri-folder-open-line hidden size-4 text-text-secondary group-data-panel-open/file-tree-row:block" />
</React.Fragment>
)
: <span className={cn('size-4', fileTreeIconClassNames[type])} />

View File

@ -30,7 +30,7 @@ type Story = StoryObj<typeof meta>
export const Basic: Story = {
render: () => (
<div className="w-80">
<label htmlFor="workspace-name" className="mb-1 block w-fit py-1 text-text-secondary system-sm-medium">
<label htmlFor="workspace-name" className="mb-1 block w-fit py-1 system-sm-medium text-text-secondary">
Workspace name
</label>
<Input
@ -46,15 +46,15 @@ export const Basic: Story = {
export const Sizes: Story = {
render: () => (
<div className="grid w-80 gap-3">
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="small-input">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="small-input">
Small
<Input id="small-input" size="small" name="smallInput" placeholder="e.g. tag…" autoComplete="off" />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="medium-input">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="medium-input">
Medium
<Input id="medium-input" name="mediumInput" placeholder="e.g. Production API…" autoComplete="off" />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="large-input">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="large-input">
Large
<Input id="large-input" size="large" name="largeInput" placeholder="e.g. Customer portal…" autoComplete="off" />
</label>
@ -66,11 +66,11 @@ export const States: Story = {
render: () => (
<div className="grid w-80 gap-3">
<div className="grid gap-1">
<label className="text-text-secondary system-sm-medium" htmlFor="placeholder-state">Placeholder</label>
<label className="system-sm-medium text-text-secondary" htmlFor="placeholder-state">Placeholder</label>
<Input id="placeholder-state" name="placeholderState" placeholder="e.g. Search datasets…" autoComplete="off" />
</div>
<div className="grid gap-1">
<label className="text-text-secondary system-sm-medium" htmlFor="filled-state">Filled</label>
<label className="system-sm-medium text-text-secondary" htmlFor="filled-state">Filled</label>
<Input id="filled-state" name="filledState" defaultValue="Customer knowledge base" autoComplete="off" />
</div>
<div className="grid gap-1">
@ -88,11 +88,11 @@ export const States: Story = {
</Field>
</div>
<div className="grid gap-1">
<label className="text-text-secondary system-sm-medium" htmlFor="disabled-state">Disabled</label>
<label className="system-sm-medium text-text-secondary" htmlFor="disabled-state">Disabled</label>
<Input id="disabled-state" disabled name="disabledEmail" type="email" inputMode="email" placeholder="name@example.com…" autoComplete="email" spellCheck={false} />
</div>
<div className="grid gap-1">
<label className="text-text-secondary system-sm-medium" htmlFor="readonly-state">Read-only</label>
<label className="system-sm-medium text-text-secondary" htmlFor="readonly-state">Read-only</label>
<Input id="readonly-state" readOnly name="endpoint" type="url" inputMode="url" defaultValue="https://api.example.com" autoComplete="url" spellCheck={false} />
</div>
</div>

View File

@ -71,6 +71,7 @@ const HotkeyKbdGroup = ({
}) => (
<KbdGroup>
{displayKeys(hotkey, platform).map((key, index) => (
// eslint-disable-next-line react/no-array-index-key -- Repeated display keys are static, ordered tokens with no component state.
<Kbd key={`${key}-${index}`} color={color}>
{key}
</Kbd>

View File

@ -6,7 +6,7 @@ import { cva } from 'class-variance-authority'
import { cn } from '../cn'
const kbdVariants = cva(
'pointer-events-none inline-flex h-4 min-w-4 select-none items-center justify-center rounded-sm px-px font-sans system-kbd capitalize not-italic',
'pointer-events-none inline-flex h-4 min-w-4 items-center justify-center rounded-sm px-px font-sans system-kbd capitalize not-italic select-none',
{
variants: {
color: {

View File

@ -67,7 +67,7 @@ function NumberFieldExample({
}: NumberFieldExampleProps) {
return (
<div className="grid w-80 gap-1">
<label htmlFor={id} className="text-text-secondary system-sm-medium">
<label htmlFor={id} className="system-sm-medium text-text-secondary">
{label}
</label>
<NumberField
@ -281,7 +281,7 @@ function FormDemo() {
<Button type="submit" variant="primary">Save Settings</Button>
</div>
{savedValue && (
<div className="rounded-lg bg-background-section px-3 py-2 text-text-secondary system-xs-regular">
<div className="rounded-lg bg-background-section px-3 py-2 system-xs-regular text-text-secondary">
Saved:
{' '}
{savedValue}

View File

@ -255,6 +255,18 @@ describe('Pagination primitive', () => {
expect(screen.container.querySelector('ol')).not.toBeInTheDocument()
})
it('does not invoke a custom page list renderer when there are no pages', async () => {
const renderPageList = vi.fn(() => <ol />)
await render(
<PaginationRoot page={1} totalPages={0} onPageChange={vi.fn()}>
<PaginationPageList render={renderPageList} />
</PaginationRoot>,
)
expect(renderPageList).not.toHaveBeenCalled()
})
it('allows custom page rendering while keeping the shared context', async () => {
const onPageChange = vi.fn()
const screen = await render(

View File

@ -228,7 +228,7 @@ export function PaginationNavigation({
...props
}: PaginationNavigationProps) {
const defaultProps: useRender.ElementProps<'div'> = {
className: cn('flex shrink-0 items-center justify-self-start gap-0.5 rounded-[10px] bg-background-section-burn p-0.5', className),
className: cn('flex shrink-0 items-center gap-0.5 justify-self-start rounded-[10px] bg-background-section-burn p-0.5', className),
}
return useRender({
@ -355,7 +355,7 @@ export function PaginationPageJump({
return (
<span
data-page-summary={`${pagination.page}/${pagination.totalPages}`}
className="inline-grid h-7 system-xs-medium tabular-nums after:invisible after:col-start-1 after:row-start-1 after:py-1.5 after:pe-3 after:ps-2 after:content-[attr(data-page-summary)]"
className="inline-grid h-7 system-xs-medium tabular-nums after:invisible after:col-start-1 after:row-start-1 after:py-1.5 after:ps-2 after:pe-3 after:content-[attr(data-page-summary)]"
>
<NumberField
key={pagination.page}
@ -375,6 +375,7 @@ export function PaginationPageJump({
>
<NumberFieldInput
aria-label={inputLabel}
// eslint-disable-next-line jsx-a11y/no-autofocus -- Editing starts after an explicit user action and focus must move to the replacement input.
autoFocus
className="px-2 py-1.5 text-center system-xs-medium tabular-nums"
onBlur={() => requestAnimationFrame(() => setEditing(false))}
@ -410,7 +411,7 @@ export function PaginationPageJump({
type="button"
aria-label={ariaLabel ?? `Edit page number, current page ${pagination.page} of ${pagination.totalPages}`}
className={cn(
'inline-flex h-7 touch-manipulation items-center justify-center gap-0.5 rounded-lg px-2 py-1.5 system-xs-medium tabular-nums text-text-secondary outline-hidden transition-colors hover:cursor-text hover:bg-state-base-hover-alt focus-visible:ring-2 focus-visible:ring-state-accent-solid motion-reduce:transition-none',
'inline-flex h-7 touch-manipulation items-center justify-center gap-0.5 rounded-lg px-2 py-1.5 system-xs-medium text-text-secondary tabular-nums outline-hidden transition-colors hover:cursor-text hover:bg-state-base-hover-alt focus-visible:ring-2 focus-visible:ring-state-accent-solid motion-reduce:transition-none',
className,
)}
onClick={(event) => {
@ -440,11 +441,8 @@ export function PaginationPageList({
}: PaginationPageListProps) {
const pagination = usePaginationContext('PaginationPageList')
if (!pagination.hasPages)
return null
const defaultProps: useRender.ElementProps<'ol'> = {
className: cn('col-start-2 flex min-w-0 list-none items-center justify-self-center gap-0.5', className),
className: cn('col-start-2 flex min-w-0 list-none items-center gap-0.5 justify-self-center', className),
children: pagination.items.map(item => (
<li key={item}>
{typeof item === 'number'
@ -458,6 +456,7 @@ export function PaginationPageList({
defaultTagName: 'ol',
render,
props: mergeProps<'ol'>(defaultProps, props),
enabled: pagination.hasPages,
})
}
@ -483,7 +482,7 @@ export function PaginationPage({
aria-current={current ? 'page' : undefined}
aria-label={ariaLabel ?? (current ? `Page ${page}, current page` : `Go to page ${page}`)}
className={cn(
'inline-flex h-8 min-w-8 touch-manipulation items-center justify-center rounded-lg px-1 py-2 system-sm-medium tabular-nums text-text-tertiary outline-hidden hover:bg-components-button-ghost-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
'inline-flex h-8 min-w-8 touch-manipulation items-center justify-center rounded-lg px-1 py-2 system-sm-medium text-text-tertiary tabular-nums outline-hidden hover:bg-components-button-ghost-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
current && 'bg-components-button-tertiary-bg text-components-button-tertiary-text hover:bg-components-button-ghost-bg-hover',
className,
)}
@ -537,8 +536,8 @@ export function PaginationPageSize<Value extends number = number>({
className,
}: PaginationPageSizeProps<Value>) {
return (
<div className={cn('group/page-size col-start-3 flex shrink-0 items-center justify-end justify-self-end gap-2', className)}>
<div className="w-13 shrink-0 text-end system-2xs-regular-uppercase text-text-tertiary opacity-0 transition-opacity group-hover/page-size:opacity-100 group-focus-within/page-size:opacity-100 motion-reduce:transition-none">
<div className={cn('group/page-size col-start-3 flex shrink-0 items-center justify-end gap-2 justify-self-end', className)}>
<div className="w-13 shrink-0 text-end system-2xs-regular-uppercase text-text-tertiary opacity-0 transition-opacity group-focus-within/page-size:opacity-100 group-hover/page-size:opacity-100 motion-reduce:transition-none">
{label}
</div>
<SegmentedControl
@ -648,12 +647,12 @@ export function PaginationSkeleton({
'className': cn('flex w-full min-w-0 items-center justify-between px-6 py-3 select-none', className),
'children': (
<div className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-2">
<div className="flex shrink-0 items-center justify-self-start gap-0.5 rounded-[10px] bg-background-section-burn p-0.5">
<div className="flex shrink-0 items-center gap-0.5 justify-self-start rounded-[10px] bg-background-section-burn p-0.5">
<div className="size-7 animate-pulse rounded-lg bg-state-base-hover motion-reduce:animate-none" />
<div className="h-7 min-w-14 animate-pulse rounded-lg bg-state-base-hover motion-reduce:animate-none" />
<div className="size-7 animate-pulse rounded-lg bg-state-base-hover motion-reduce:animate-none" />
</div>
<div className="col-start-2 flex items-center justify-self-center gap-0.5">
<div className="col-start-2 flex items-center gap-0.5 justify-self-center">
{range(1, 8).map(item => (
<div key={item} className="h-8 min-w-8 animate-pulse rounded-lg bg-state-base-hover motion-reduce:animate-none" />
))}

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Placement } from '.'
import { Button } from '@langgenius/dify-ui/button'
import * as React from 'react'
import {
Popover,
@ -10,6 +9,7 @@ import {
PopoverTitle,
PopoverTrigger,
} from '.'
import { Button } from '../button'
import { Kbd, KbdGroup } from '../kbd'
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'

View File

@ -61,7 +61,7 @@ export function RadioControl({
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-full p-0 transition-colors motion-reduce:transition-none',
'border border-components-radio-border bg-components-radio-bg shadow-xs shadow-shadow-shadow-3',
'hover:border-components-radio-border-hover hover:bg-components-radio-bg-hover',
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0 focus-visible:outline-hidden',
'data-checked:border-[5px] data-checked:border-components-radio-border-checked data-checked:hover:border-components-radio-border-checked-hover',
'data-disabled:cursor-not-allowed data-disabled:border-components-radio-border-disabled data-disabled:bg-components-radio-bg-disabled',
'data-disabled:hover:border-components-radio-border-disabled data-disabled:hover:bg-components-radio-bg-disabled',
@ -86,7 +86,7 @@ export function Radio<Value = string>({
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-full p-0 transition-colors motion-reduce:transition-none',
'border border-components-radio-border bg-components-radio-bg shadow-xs shadow-shadow-shadow-3',
'hover:border-components-radio-border-hover hover:bg-components-radio-bg-hover',
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0 focus-visible:outline-hidden',
'data-checked:border-[5px] data-checked:border-components-radio-border-checked data-checked:hover:border-components-radio-border-checked-hover',
'data-disabled:cursor-not-allowed data-disabled:border-components-radio-border-disabled data-disabled:bg-components-radio-bg-disabled',
'data-disabled:hover:border-components-radio-border-disabled data-disabled:hover:bg-components-radio-bg-disabled',

View File

@ -68,7 +68,7 @@ function StorySection({
<div className="space-y-1">
<div className="system-xs-medium-uppercase text-text-tertiary">{eyebrow}</div>
<h3 className="system-md-semibold text-text-primary">{title}</h3>
<p className="max-w-[72ch] text-pretty system-sm-regular text-text-secondary">{description}</p>
<p className="max-w-[72ch] system-sm-regular text-pretty text-text-secondary">{description}</p>
</div>
<div className="mt-5 flex justify-center">
{children}
@ -103,7 +103,7 @@ export const Anatomy: Story = {
>
<ScrollAreaRoot className="relative h-75 w-full max-w-105 min-w-0">
<ScrollAreaViewport aria-label="Scrollable anatomy example" role="region" className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg">
<VerticalContent className="flex flex-col gap-4 py-2 pl-3 pr-5 text-text-secondary system-sm-regular leading-6">
<VerticalContent className="flex flex-col gap-4 py-2 pr-5 pl-3 system-sm-regular leading-6 text-text-secondary">
{articleParagraphs.map(paragraph => (
<p key={paragraph}>
{paragraph}
@ -128,7 +128,7 @@ export const Vertical: Story = {
>
<ScrollAreaRoot className="relative h-90 w-full max-w-130 min-w-0">
<ScrollAreaViewport aria-label="Long form content" role="region" className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg">
<VerticalContent className="flex flex-col gap-4 p-4 pr-6 text-text-secondary system-sm-regular leading-6">
<VerticalContent className="flex flex-col gap-4 p-4 pr-6 system-sm-regular leading-6 text-text-secondary">
<div className="space-y-1">
<div className="system-xs-medium-uppercase text-text-tertiary">Article</div>
<div className="system-md-semibold text-text-primary">Scrollable text region</div>
@ -201,7 +201,7 @@ export const ScrollFade: Story = {
'mask-linear-[to_bottom,transparent_0,black_min(40px,var(--scroll-area-overflow-y-start)),black_calc(100%_-_min(40px,var(--scroll-area-overflow-y-end,40px))),transparent_100%] mask-no-repeat',
)}
>
<VerticalContent className="flex flex-col gap-4 px-4 py-3 pr-6 text-text-secondary system-sm-regular leading-6">
<VerticalContent className="flex flex-col gap-4 px-4 py-3 pr-6 system-sm-regular leading-6 text-text-secondary">
{Array.from({ length: 5 }, (_, groupIndex) => (
articleParagraphs.map(paragraph => (
<p key={`${groupIndex}-${paragraph}`}>
@ -232,7 +232,7 @@ export const Horizontal: Story = {
<ScrollAreaContent className="min-h-full min-w-max p-4 pb-6">
<div className="grid grid-cols-[repeat(18,6.25rem)] gap-3">
{gridCells.slice(0, 18).map(cell => (
<div key={cell} className="flex h-24 items-center justify-center rounded-xl border border-divider-subtle bg-components-panel-bg-alt tabular-nums system-md-semibold text-text-secondary">
<div key={cell} className="flex h-24 items-center justify-center rounded-xl border border-divider-subtle bg-components-panel-bg-alt system-md-semibold text-text-secondary tabular-nums">
{cell}
</div>
))}
@ -259,7 +259,7 @@ export const BothAxes: Story = {
<ScrollAreaContent className="pt-3 pr-6 pb-6 pl-3">
<div className="grid grid-cols-[repeat(10,6.25rem)] grid-rows-[repeat(10,6.25rem)] gap-3">
{gridCells.map(cell => (
<div key={cell} className="flex items-center justify-center rounded-lg border border-divider-subtle bg-components-panel-bg-alt tabular-nums system-md-semibold text-text-secondary">
<div key={cell} className="flex items-center justify-center rounded-lg border border-divider-subtle bg-components-panel-bg-alt system-md-semibold text-text-secondary tabular-nums">
{cell}
</div>
))}

View File

@ -42,7 +42,7 @@ const scrollAreaThumbClassName = cn(
const scrollAreaViewportClassName = cn(
'size-full min-h-0 min-w-0',
'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-solid focus-visible:outline-state-accent-solid',
'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-state-accent-solid focus-visible:outline-solid',
)
const scrollAreaCornerClassName = 'bg-transparent'

View File

@ -33,7 +33,7 @@ export function SegmentedControlItem<Value extends string = string>({
}: SegmentedControlItemProps<Value>) {
return (
<BaseToggle
className={cn('relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden whitespace-nowrap rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium text-text-secondary transition-colors duration-150 hover:bg-state-base-hover hover:text-text-secondary focus-visible:outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid data-pressed:border-components-segmented-control-item-active-border data-pressed:bg-components-segmented-control-item-active-bg data-pressed:text-text-accent-light-mode-only data-pressed:shadow-xs data-pressed:shadow-shadow-shadow-3 data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled motion-reduce:transition-none', className)}
className={cn('relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium whitespace-nowrap text-text-secondary transition-colors duration-150 hover:bg-state-base-hover hover:text-text-secondary focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled data-pressed:border-components-segmented-control-item-active-border data-pressed:bg-components-segmented-control-item-active-bg data-pressed:text-text-accent-light-mode-only data-pressed:shadow-xs data-pressed:shadow-shadow-shadow-3 motion-reduce:transition-none', className)}
{...props}
/>
)

View File

@ -50,8 +50,8 @@ const switchSpinnerVariants = cva(
{
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)]',
md: 'left-[calc(50%+6px)] size-2 group-data-checked:left-[calc(50%-6px)]',
lg: 'left-[calc(50%+8px)] size-2.5 group-data-checked:left-[calc(50%-8px)]',
},
},
},

View File

@ -34,7 +34,7 @@ export function TabsTab({
}: TabsTabProps) {
return (
<BaseTabs.Tab
className={cn('touch-manipulation focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid relative flex cursor-pointer items-center border-b-2 border-transparent pt-2.5 pb-2 system-md-semibold text-text-tertiary data-active:border-components-tab-active data-active:text-text-primary data-disabled:cursor-not-allowed data-disabled:text-text-tertiary data-disabled:opacity-30 data-active:data-disabled:text-text-primary', className)}
className={cn('relative flex cursor-pointer touch-manipulation items-center border-b-2 border-transparent pt-2.5 pb-2 system-md-semibold text-text-tertiary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-active:border-components-tab-active data-active:text-text-primary data-disabled:cursor-not-allowed data-disabled:text-text-tertiary data-disabled:opacity-30 data-active:data-disabled:text-text-primary', className)}
{...props}
/>
)

View File

@ -31,7 +31,7 @@ type Story = StoryObj<typeof meta>
export const Basic: Story = {
render: () => (
<div className="w-80">
<label htmlFor="workspace-description" className="mb-1 block w-fit py-1 text-text-secondary system-sm-medium">
<label htmlFor="workspace-description" className="mb-1 block w-fit py-1 system-sm-medium text-text-secondary">
Workspace description
</label>
<Textarea
@ -46,15 +46,15 @@ export const Basic: Story = {
export const Sizes: Story = {
render: () => (
<div className="grid w-80 gap-3">
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="small-textarea">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="small-textarea">
Small
<Textarea id="small-textarea" size="small" name="smallTextarea" placeholder="Short note..." rows={3} />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="medium-textarea">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="medium-textarea">
Medium
<Textarea id="medium-textarea" name="mediumTextarea" placeholder="Add context..." rows={3} />
</label>
<label className="grid gap-1 text-text-secondary system-sm-medium" htmlFor="large-textarea">
<label className="grid gap-1 system-sm-medium text-text-secondary" htmlFor="large-textarea">
Large
<Textarea id="large-textarea" size="large" name="largeTextarea" placeholder="Write a longer instruction..." rows={3} />
</label>
@ -119,7 +119,7 @@ const FormDemo = () => {
<Button type="submit" variant="primary">Save Settings</Button>
</div>
{savedDescription && (
<div className="rounded-lg bg-background-section px-3 py-2 text-text-secondary system-xs-regular">
<div className="rounded-lg bg-background-section px-3 py-2 system-xs-regular text-text-secondary">
Saved:
{' '}
{savedDescription}
@ -173,7 +173,7 @@ const CharacterCounterDemo = () => {
rows={4}
className="resize-y pb-8"
/>
<div className="pointer-events-none absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-text-quaternary system-xs-medium">
<div className="pointer-events-none absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 system-xs-medium text-text-quaternary">
<span>{value.length}</span>
/
<span className="text-text-tertiary">{maxLength}</span>

View File

@ -159,7 +159,7 @@ function ToastCard({
'[transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms] motion-reduce:transition-none',
'transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-current-height))))_scale(var(--toast-scale))]',
'data-expanded:h-(--toast-height) data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-offset-y)+var(--toast-swipe-movement-y)+(var(--toast-index)*8px)))_scale(1)]',
'data-ending-style:pointer-events-none data-ending-style:after:pointer-events-none data-ending-style:transform-[translateY(-150%)] data-ending-style:opacity-0',
'data-ending-style:pointer-events-none data-ending-style:transform-[translateY(-150%)] data-ending-style:opacity-0 data-ending-style:after:pointer-events-none',
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+150%))]',
'data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+150%))]',
'data-limited:pointer-events-none data-limited:opacity-0 data-starting-style:transform-[translateY(-150%)] data-starting-style:opacity-0',

948
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff