fix(dify-ui): restore pagination jump focus (#37393)

This commit is contained in:
yyh 2026-06-13 06:25:17 +08:00 committed by GitHub
parent 5d77c0af08
commit ca3cb2a902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 306 additions and 286 deletions

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import {
AlertDialog,
AlertDialogActions,
@ -84,8 +84,8 @@ export const NonDestructive: Story = {
}
const ControlledDemo = () => {
const [open, setOpen] = useState(false)
const [count, setCount] = useState(0)
const [open, setOpen] = React.useState(false)
const [count, setCount] = React.useState(0)
return (
<div className="flex flex-col items-center gap-3">
@ -130,8 +130,8 @@ export const Controlled: Story = {
}
const LoadingConfirmDemo = () => {
const [pending, setPending] = useState(false)
const [open, setOpen] = useState(false)
const [pending, setPending] = React.useState(false)
const [open, setOpen] = React.useState(false)
const handleConfirm = () => {
setPending(true)

View File

@ -1,6 +1,6 @@
'use client'
import type { ComponentPropsWithoutRef, ReactNode } from 'react'
import type * as React from 'react'
import type { ButtonProps } from '../button'
import { AlertDialog as BaseAlertDialog } from '@base-ui/react/alert-dialog'
import { Button } from '../button'
@ -12,7 +12,7 @@ export const AlertDialogTitle = BaseAlertDialog.Title
export const AlertDialogDescription = BaseAlertDialog.Description
type AlertDialogContentProps = {
children: ReactNode
children: React.ReactNode
className?: string
backdropClassName?: string
backdropProps?: Omit<BaseAlertDialog.Backdrop.Props, 'className'>
@ -47,7 +47,7 @@ export function AlertDialogContent({
)
}
type AlertDialogActionsProps = ComponentPropsWithoutRef<'div'>
type AlertDialogActionsProps = React.ComponentProps<'div'>
export function AlertDialogActions({ className, ...props }: AlertDialogActionsProps) {
return (
@ -59,7 +59,7 @@ export function AlertDialogActions({ className, ...props }: AlertDialogActionsPr
}
type AlertDialogCancelButtonProps = Omit<ButtonProps, 'children'> & {
children: ReactNode
children: React.ReactNode
closeProps?: Omit<BaseAlertDialog.Close.Props, 'children' | 'render'>
}

View File

@ -1,4 +1,4 @@
import type { ReactNode } from 'react'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Autocomplete,
@ -18,7 +18,7 @@ import {
AutocompleteTrigger,
} from '../index'
const renderWithSafeViewport = (ui: ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,
@ -31,13 +31,13 @@ const renderAutocomplete = ({
open = false,
defaultValue = 'workflow',
}: {
children?: ReactNode
children?: React.ReactNode
open?: boolean
defaultValue?: string
} = {}) => renderWithSafeViewport(
<Autocomplete open={open} defaultValue={defaultValue} items={['workflow', 'dataset']}>
{children ?? (
<>
<React.Fragment>
<AutocompleteInputGroup data-testid="input-group">
<AutocompleteInput aria-label="Search suggestions" data-testid="input" />
<AutocompleteClear data-testid="clear" />
@ -65,7 +65,7 @@ const renderAutocomplete = ({
</AutocompleteList>
<AutocompleteEmpty data-testid="empty">No suggestions</AutocompleteEmpty>
</AutocompleteContent>
</>
</React.Fragment>
)}
</Autocomplete>,
)
@ -150,7 +150,7 @@ describe('Autocomplete wrappers', () => {
it('should rely on aria-labelledby when provided instead of injecting fallback labels', async () => {
const screen = await renderAutocomplete({
children: (
<>
<React.Fragment>
<span id="clear-label">Clear from label</span>
<span id="trigger-label">Trigger from label</span>
<AutocompleteInputGroup>
@ -158,7 +158,7 @@ describe('Autocomplete wrappers', () => {
<AutocompleteClear aria-labelledby="clear-label" />
<AutocompleteTrigger aria-labelledby="trigger-label" />
</AutocompleteInputGroup>
</>
</React.Fragment>
),
})

View File

@ -1,8 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Virtualizer } from '@tanstack/react-virtual'
import type { RefObject } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { useEffect, useMemo, useRef, useState, useTransition } from 'react'
import * as React from 'react'
import {
Autocomplete,
AutocompleteClear,
@ -336,12 +335,12 @@ const LimitedStatus = ({
}
const AsyncSearchDemo = () => {
const [searchValue, setSearchValue] = useState('')
const [searchResults, setSearchResults] = useState<Suggestion[]>([])
const [error, setError] = useState<string | null>(null)
const [isPending, startTransition] = useTransition()
const [searchValue, setSearchValue] = React.useState('')
const [searchResults, setSearchResults] = React.useState<Suggestion[]>([])
const [error, setError] = React.useState<string | null>(null)
const [isPending, startTransition] = React.useTransition()
const { contains } = useAutocompleteFilter()
const abortControllerRef = useRef<AbortController | null>(null)
const abortControllerRef = React.useRef<AbortController | null>(null)
const status = (() => {
if (isPending)
@ -419,9 +418,9 @@ const AsyncSearchDemo = () => {
const VirtualizedSuggestionList = ({
virtualizerRef,
}: {
virtualizerRef: RefObject<StoryVirtualizer | null>
virtualizerRef: React.RefObject<StoryVirtualizer | null>
}) => {
const scrollRef = useRef<HTMLDivElement | null>(null)
const scrollRef = React.useRef<HTMLDivElement | null>(null)
const filteredItems = useAutocompleteFilteredItems<Suggestion>()
const virtualizer = useVirtualizer({
count: filteredItems.length,
@ -430,7 +429,7 @@ const VirtualizedSuggestionList = ({
overscan: 6,
})
useEffect(() => {
React.useEffect(() => {
virtualizerRef.current = virtualizer
return () => {
@ -490,7 +489,7 @@ const FuzzyHighlight = ({
text: string
query: string
}) => {
const parts = useMemo(() => {
const parts = React.useMemo(() => {
const trimmed = query.trim()
if (!trimmed)
@ -501,18 +500,18 @@ const FuzzyHighlight = ({
}, [query, text])
return (
<>
<React.Fragment>
{parts.map((part, index) => (
part.toLowerCase() === query.trim().toLowerCase()
? <mark key={`${part}-${index}`} className="bg-transparent text-text-accent">{part}</mark>
: part
))}
</>
</React.Fragment>
)
}
const FuzzyMatchingDemo = () => {
const [value, setValue] = useState('retr')
const [value, setValue] = React.useState('retr')
const { contains } = useAutocompleteFilter({ sensitivity: 'base' })
return (
@ -702,7 +701,7 @@ export const CommandPalette: Story = {
}
const VirtualizedLongSuggestionsDemo = () => {
const virtualizerRef = useRef<StoryVirtualizer | null>(null)
const virtualizerRef = React.useRef<StoryVirtualizer | null>(null)
return (
<div className={inputWidth}>

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { HTMLAttributes, ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Autocomplete as BaseAutocomplete } from '@base-ui/react/autocomplete'
import { cva } from 'class-variance-authority'
@ -223,7 +223,7 @@ export function AutocompleteIcon({
}
type AutocompleteContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
@ -302,7 +302,7 @@ export function AutocompleteItem({
)
}
export type AutocompleteItemTextProps = HTMLAttributes<HTMLSpanElement>
export type AutocompleteItemTextProps = React.ComponentProps<'span'>
export function AutocompleteItemText({
className,
@ -368,7 +368,7 @@ export function AutocompleteItemIndicator({
className,
children,
...props
}: HTMLAttributes<HTMLSpanElement>) {
}: React.ComponentProps<'span'>) {
return (
<span
className={cn(overlayIndicatorClassName, className)}

View File

@ -1,4 +1,4 @@
import type { FormEvent } from 'react'
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import { userEvent } from 'vitest/browser'
import { Button } from '../index'
@ -174,7 +174,7 @@ describe('Button', () => {
})
it('does not submit a form when a loading submit button is clicked', async () => {
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => event.preventDefault())
const onSubmit = vi.fn((event: React.FormEvent<HTMLFormElement>) => event.preventDefault())
const screen = await render(
<form onSubmit={onSubmit}>
<Button type="submit" loading>Submit</Button>
@ -187,7 +187,7 @@ describe('Button', () => {
})
it('does not implicitly submit a form through a loading submit button', async () => {
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => event.preventDefault())
const onSubmit = vi.fn((event: React.FormEvent<HTMLFormElement>) => event.preventDefault())
const screen = await render(
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>

View File

@ -1,4 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import { Button } from '.'
@ -112,10 +113,10 @@ export const WithIcon: Story = {
args: {
variant: 'primary',
children: (
<>
<React.Fragment>
<span aria-hidden className="mr-1.5 i-ri-rocket-line size-4 shrink-0" />
Launch
</>
</React.Fragment>
),
},
}

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox'
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
@ -10,7 +10,7 @@ const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElem
describe('CheckboxGroup', () => {
it('should manage selected values and parent mixed state', async () => {
function PermissionsDemo() {
const [value, setValue] = useState(['read'])
const [value, setValue] = React.useState(['read'])
return (
<CheckboxGroup value={value} onValueChange={setValue} allValues={['read', 'write']}>

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useId, useState } from 'react'
import * as React from 'react'
import { CheckboxGroup } from '.'
import { Checkbox } from '../checkbox'
import {
@ -29,8 +29,8 @@ type Story = StoryObj<typeof meta>
function DocumentSelectionDemo() {
const documentIds = ['doc-1', 'doc-2', 'doc-3']
const [selected, setSelected] = useState<string[]>(['doc-1'])
const groupLabelId = useId()
const [selected, setSelected] = React.useState<string[]>(['doc-1'])
const groupLabelId = React.useId()
return (
<CheckboxGroup
@ -77,7 +77,7 @@ function DynamicFormFieldDemo() {
{ value: 'pdf', label: 'PDF' },
{ value: 'html', label: 'HTML' },
]
const [selected, setSelected] = useState<string[]>(['markdown'])
const [selected, setSelected] = React.useState<string[]>(['markdown'])
return (
<FieldRoot name="allowed_file_types" className="flex w-80 flex-col gap-2">

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ComponentProps } from 'react'
import { useState } from 'react'
import * as React from 'react'
import {
Checkbox,
CheckboxSkeleton,
@ -42,8 +41,8 @@ const meta = {
export default meta
type Story = StoryObj<typeof meta>
function CheckboxDemo(args: Partial<ComponentProps<typeof Checkbox>>) {
const [checked, setChecked] = useState(args.checked ?? false)
function CheckboxDemo(args: Partial<React.ComponentProps<typeof Checkbox>>) {
const [checked, setChecked] = React.useState(args.checked ?? false)
return (
<label className="flex items-center gap-2 system-sm-medium text-text-secondary">

View File

@ -1,7 +1,7 @@
'use client'
import type { Checkbox as BaseCheckboxNS } from '@base-ui/react/checkbox'
import type { HTMLAttributes } from 'react'
import type * as React from 'react'
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox'
import { cn } from '../cn'
@ -82,7 +82,7 @@ export function Checkbox({
}
export type CheckboxSkeletonProps
= Omit<HTMLAttributes<HTMLDivElement>, 'className'>
= Omit<React.ComponentProps<'div'>, 'className'>
& {
className?: string
}

View File

@ -46,7 +46,7 @@ function RecoveryKeys({
panelProps?: React.ComponentProps<typeof CollapsiblePanel>
}) {
return (
<>
<React.Fragment>
<CollapsibleTrigger className={triggerClassName}>
Recovery keys
<TriggerIcon />
@ -58,7 +58,7 @@ function RecoveryKeys({
<div>horse-battery-staple</div>
</div>
</CollapsiblePanel>
</>
</React.Fragment>
)
}

View File

@ -1,4 +1,4 @@
import type { ReactNode } from 'react'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Combobox,
@ -24,7 +24,7 @@ import {
ComboboxValue,
} from '../index'
const renderWithSafeViewport = (ui: ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,
@ -36,12 +36,12 @@ const renderSelectLikeCombobox = ({
children,
open = false,
}: {
children?: ReactNode
children?: React.ReactNode
open?: boolean
} = {}) => renderWithSafeViewport(
<Combobox open={open} defaultValue="workflow" items={['workflow', 'dataset']}>
{children ?? (
<>
<React.Fragment>
<ComboboxLabel data-testid="label">Resource type</ComboboxLabel>
<ComboboxTrigger aria-label="Resource type" data-testid="trigger">
<ComboboxValue placeholder="Select resource" />
@ -68,7 +68,7 @@ const renderSelectLikeCombobox = ({
</ComboboxList>
<ComboboxEmpty data-testid="empty">No options</ComboboxEmpty>
</ComboboxContent>
</>
</React.Fragment>
)}
</Combobox>,
)
@ -77,12 +77,12 @@ const renderInputCombobox = ({
children,
open = false,
}: {
children?: ReactNode
children?: React.ReactNode
open?: boolean
} = {}) => renderWithSafeViewport(
<Combobox open={open} defaultValue="workflow" items={['workflow', 'dataset']}>
{children ?? (
<>
<React.Fragment>
<ComboboxInputGroup data-testid="input-group">
<ComboboxInput aria-label="Search resources" data-testid="input" />
<ComboboxClear data-testid="clear" />
@ -96,7 +96,7 @@ const renderInputCombobox = ({
</ComboboxItem>
</ComboboxList>
</ComboboxContent>
</>
</React.Fragment>
)}
</Combobox>,
)
@ -208,7 +208,7 @@ describe('Combobox wrappers', () => {
it('should rely on aria-labelledby when provided instead of injecting fallback labels', async () => {
const screen = await renderInputCombobox({
children: (
<>
<React.Fragment>
<span id="clear-label">Clear from label</span>
<span id="trigger-label">Trigger from label</span>
<ComboboxInputGroup>
@ -216,7 +216,7 @@ describe('Combobox wrappers', () => {
<ComboboxClear aria-labelledby="clear-label" />
<ComboboxInputTrigger aria-labelledby="trigger-label" />
</ComboboxInputGroup>
</>
</React.Fragment>
),
})
@ -349,7 +349,7 @@ describe('Combobox wrappers', () => {
<ComboboxChips className="custom-chips" data-testid="chips">
<ComboboxValue>
{(selectedValue: string[]) => (
<>
<React.Fragment>
{selectedValue.map(item => (
<ComboboxChip key={item} className="custom-chip">
<span>{item}</span>
@ -357,7 +357,7 @@ describe('Combobox wrappers', () => {
</ComboboxChip>
))}
<ComboboxInput aria-label="Reviewers" />
</>
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
@ -378,7 +378,7 @@ describe('Combobox wrappers', () => {
<ComboboxChips>
<ComboboxValue>
{(selectedValue: string[]) => (
<>
<React.Fragment>
{selectedValue.map(item => (
<ComboboxChip key={item}>
<span id="remove-maya">Remove Maya</span>
@ -386,7 +386,7 @@ describe('Combobox wrappers', () => {
</ComboboxChip>
))}
<ComboboxInput aria-label="Reviewers" />
</>
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>

View File

@ -1,8 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Virtualizer } from '@tanstack/react-virtual'
import type { RefObject } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { useEffect, useMemo, useRef, useState, useTransition } from 'react'
import * as React from 'react'
import {
Combobox,
ComboboxChip,
@ -278,9 +277,9 @@ const GroupedToolList = () => {
const VirtualizedModelList = ({
virtualizerRef,
}: {
virtualizerRef: RefObject<StoryVirtualizer | null>
virtualizerRef: React.RefObject<StoryVirtualizer | null>
}) => {
const scrollRef = useRef<HTMLDivElement | null>(null)
const scrollRef = React.useRef<HTMLDivElement | null>(null)
const filteredItems = useComboboxFilteredItems<Option>()
const virtualizer = useVirtualizer({
count: filteredItems.length,
@ -289,7 +288,7 @@ const VirtualizedModelList = ({
overscan: 6,
})
useEffect(() => {
React.useEffect(() => {
virtualizerRef.current = virtualizer
return () => {
@ -345,8 +344,8 @@ const FilteredModelStatus = () => {
}
const VirtualizedLongListDemo = () => {
const [value, setValue] = useState<Option | null>(modelCatalogOptions[137]!)
const virtualizerRef = useRef<StoryVirtualizer | null>(null)
const [value, setValue] = React.useState<Option | null>(modelCatalogOptions[137]!)
const virtualizerRef = React.useRef<StoryVirtualizer | null>(null)
return (
<div className={fieldWidth}>
@ -375,15 +374,15 @@ const VirtualizedLongListDemo = () => {
}
const AsyncDirectoryDemo = () => {
const [searchResults, setSearchResults] = useState<Option[]>([])
const [selectedValue, setSelectedValue] = useState<Option | null>(null)
const [searchValue, setSearchValue] = useState('')
const [error, setError] = useState<string | null>(null)
const [isPending, startTransition] = useTransition()
const [searchResults, setSearchResults] = React.useState<Option[]>([])
const [selectedValue, setSelectedValue] = React.useState<Option | null>(null)
const [searchValue, setSearchValue] = React.useState('')
const [error, setError] = React.useState<string | null>(null)
const [isPending, startTransition] = React.useTransition()
const { contains } = useComboboxFilter()
const abortControllerRef = useRef<AbortController | null>(null)
const abortControllerRef = React.useRef<AbortController | null>(null)
const trimmedSearchValue = searchValue.trim()
const items = useMemo(() => {
const items = React.useMemo(() => {
if (!selectedValue || searchResults.some(option => option.value === selectedValue.value))
return searchResults
@ -477,18 +476,18 @@ const AsyncDirectoryDemo = () => {
}
const AsyncReviewerDemo = () => {
const [searchResults, setSearchResults] = useState<Option[]>([])
const [selectedValues, setSelectedValues] = useState<Option[]>(defaultAsyncReviewers)
const [searchValue, setSearchValue] = useState('')
const [error, setError] = useState<string | null>(null)
const [blockStartStatus, setBlockStartStatus] = useState(false)
const [isPending, startTransition] = useTransition()
const [searchResults, setSearchResults] = React.useState<Option[]>([])
const [selectedValues, setSelectedValues] = React.useState<Option[]>(defaultAsyncReviewers)
const [searchValue, setSearchValue] = React.useState('')
const [error, setError] = React.useState<string | null>(null)
const [blockStartStatus, setBlockStartStatus] = React.useState(false)
const [isPending, startTransition] = React.useTransition()
const { contains } = useComboboxFilter()
const abortControllerRef = useRef<AbortController | null>(null)
const selectedValuesRef = useRef<Option[]>(defaultAsyncReviewers)
const abortControllerRef = React.useRef<AbortController | null>(null)
const selectedValuesRef = React.useRef<Option[]>(defaultAsyncReviewers)
const trimmedSearchValue = searchValue.trim()
const items = useMemo(() => {
const items = React.useMemo(() => {
if (selectedValues.length === 0)
return searchResults
@ -587,7 +586,7 @@ const AsyncReviewerDemo = () => {
<ComboboxChips>
<ComboboxValue>
{(selectedValue: Option[]) => (
<>
<React.Fragment>
{selectedValue.map(item => (
<ComboboxChip key={item.value} aria-label={item.label}>
<span className="max-w-32 truncate">{item.label}</span>
@ -595,7 +594,7 @@ const AsyncReviewerDemo = () => {
</ComboboxChip>
))}
<ComboboxInput placeholder={selectedValue.length ? '' : 'Search reviewers…'} className="min-w-24 px-1 py-0.5" />
</>
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
@ -735,7 +734,7 @@ export const Grouped: Story = {
}
const MultipleChipsDemo = () => {
const [value, setValue] = useState<Option[]>(defaultReviewers)
const [value, setValue] = React.useState<Option[]>(defaultReviewers)
return (
<FieldRoot name="reviewers" className={fieldWidth}>
@ -745,7 +744,7 @@ const MultipleChipsDemo = () => {
<ComboboxChips>
<ComboboxValue>
{(selectedValue: Option[]) => (
<>
<React.Fragment>
{selectedValue.map(item => (
<ComboboxChip key={item.value}>
<span className="max-w-32 truncate">{item.label}</span>
@ -753,7 +752,7 @@ const MultipleChipsDemo = () => {
</ComboboxChip>
))}
<ComboboxInput placeholder={selectedValue.length ? '' : 'Assign reviewers…'} className="min-w-24 px-1 py-0.5" />
</>
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
@ -829,7 +828,7 @@ export const DisabledAndReadOnly: Story = {
}
const ControlledDemo = () => {
const [value, setValue] = useState<Option | null>(defaultTag)
const [value, setValue] = React.useState<Option | null>(defaultTag)
return (
<div className="flex w-80 flex-col items-start gap-3">

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { HTMLAttributes, ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Combobox as BaseCombobox } from '@base-ui/react/combobox'
import { cva } from 'class-variance-authority'
@ -80,7 +80,7 @@ type ComboboxTriggerProps
& VariantProps<typeof comboboxTriggerVariants>
& {
className?: string
icon?: ReactNode | false
icon?: React.ReactNode | false
}
export function ComboboxTrigger({
@ -286,7 +286,7 @@ export function ComboboxIcon({
}
type ComboboxContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
@ -365,7 +365,7 @@ export function ComboboxItem({
)
}
export type ComboboxItemTextProps = HTMLAttributes<HTMLSpanElement>
export type ComboboxItemTextProps = React.ComponentProps<'span'>
export function ComboboxItemText({
className,
@ -383,7 +383,7 @@ export function ComboboxItemIndicator({
className,
children,
...props
}: Omit<BaseCombobox.ItemIndicator.Props, 'children'> & { children?: ReactNode }) {
}: Omit<BaseCombobox.ItemIndicator.Props, 'children'> & { children?: React.ReactNode }) {
return (
<BaseCombobox.ItemIndicator
className={cn(overlayIndicatorClassName, className)}

View File

@ -1,3 +1,4 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
ContextMenu,
@ -11,7 +12,7 @@ import {
ContextMenuTrigger,
} from '../index'
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import {
ContextMenu,
ContextMenuCheckboxItem,
@ -100,7 +100,7 @@ export const WithGroupLabel: Story = {
}
const WithRadioItemsDemo = () => {
const [value, setValue] = useState('comfortable')
const [value, setValue] = React.useState('comfortable')
return (
<ContextMenu>
@ -130,9 +130,9 @@ export const WithRadioItems: Story = {
}
const WithCheckboxItemsDemo = () => {
const [showToolbar, setShowToolbar] = useState(true)
const [showSidebar, setShowSidebar] = useState(false)
const [showStatusBar, setShowStatusBar] = useState(true)
const [showToolbar, setShowToolbar] = React.useState(true)
const [showSidebar, setShowSidebar] = React.useState(false)
const [showStatusBar, setShowStatusBar] = React.useState(true)
return (
<ContextMenu>

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { OverlayItemVariant } from '../overlay-shared'
import type { Placement } from '../placement'
import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu'
@ -27,7 +27,7 @@ export type ContextMenuActions = BaseContextMenu.Root.Actions
// Intentionally no public Backdrop export; Base UI handles context-menu modal dismissal internally.
type ContextMenuContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
@ -225,7 +225,7 @@ export function ContextMenuSubTrigger({
}
type ContextMenuSubContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import {
Dialog,
DialogCloseButton,
@ -95,7 +95,7 @@ export const WithoutCloseButton: Story = {
}
const ControlledDemo = () => {
const [open, setOpen] = useState(false)
const [open, setOpen] = React.useState(false)
return (
<div className="flex flex-col items-center gap-3">
@ -148,7 +148,7 @@ type ApiExtensionFormValues = {
}
const FormDialogDemo = () => {
const [open, setOpen] = useState(false)
const [open, setOpen] = React.useState(false)
return (
<Dialog open={open} onOpenChange={setOpen} disablePointerDismissal>

View File

@ -1,13 +1,6 @@
'use client'
// z-index strategy (relies on root `isolation: isolate` in layout.tsx):
// All @langgenius/dify-ui/* overlay primitives — z-50
// Toast stays one layer above overlays at z-60.
// Overlays share the same z-index; DOM order handles stacking when multiple are open.
// This ensures overlays inside a Dialog (e.g. a Tooltip on a dialog button) render
// above the dialog backdrop instead of being clipped by it.
import type { ReactNode } from 'react'
import type * as React from 'react'
import { Dialog as BaseDialog } from '@base-ui/react/dialog'
import { cn } from '../cn'
@ -39,7 +32,7 @@ export function DialogCloseButton({
}
type DialogContentProps = {
children: ReactNode
children: React.ReactNode
className?: string
backdropClassName?: string
backdropProps?: Omit<BaseDialog.Backdrop.Props, 'className'>

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import { Drawer as BaseDrawer } from '@base-ui/react/drawer'
import { cn } from '../cn'
@ -90,7 +90,7 @@ export function DrawerContent({
}
type DrawerCloseButtonProps = Omit<BaseDrawer.Close.Props, 'children'> & {
children?: ReactNode
children?: React.ReactNode
}
export function DrawerCloseButton({

View File

@ -1,3 +1,4 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
DropdownMenu,
@ -11,7 +12,7 @@ import {
DropdownMenuTrigger,
} from '../index'
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
@ -133,7 +133,7 @@ export const WithSubmenu: Story = {
}
const WithRadioItemsDemo = () => {
const [value, setValue] = useState('comfortable')
const [value, setValue] = React.useState('comfortable')
return (
<DropdownMenu>
@ -163,9 +163,9 @@ export const WithRadioItems: Story = {
}
const WithCheckboxItemsDemo = () => {
const [showToolbar, setShowToolbar] = useState(true)
const [showSidebar, setShowSidebar] = useState(false)
const [showStatusBar, setShowStatusBar] = useState(true)
const [showToolbar, setShowToolbar] = React.useState(true)
const [showSidebar, setShowSidebar] = React.useState(false)
const [showStatusBar, setShowStatusBar] = React.useState(true)
return (
<DropdownMenu>
@ -252,8 +252,8 @@ export const WithLinkItems: Story = {
}
const ComplexDemo = () => {
const [sortOrder, setSortOrder] = useState('newest')
const [showArchived, setShowArchived] = useState(false)
const [sortOrder, setSortOrder] = React.useState('newest')
const [showArchived, setShowArchived] = React.useState(false)
return (
<DropdownMenu>

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { OverlayItemVariant } from '../overlay-shared'
import type { Placement } from '../placement'
import { Menu } from '@base-ui/react/menu'
@ -89,7 +89,7 @@ export function DropdownMenuLabel({
}
type DropdownMenuContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
@ -197,7 +197,7 @@ export function DropdownMenuSubTrigger({
}
type DropdownMenuSubContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number

View File

@ -1,3 +1,4 @@
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox'
import { CheckboxGroup } from '../../checkbox-group'
@ -96,7 +97,7 @@ describe('Field primitives', () => {
it('should apply design-system control sizes when requested', async () => {
const screen = await render(
<>
<React.Fragment>
<FieldRoot name="name">
<FieldLabel>Name</FieldLabel>
<FieldControl size="large" />
@ -105,7 +106,7 @@ describe('Field primitives', () => {
<FieldLabel>Alias</FieldLabel>
<FieldControl size="small" />
</FieldRoot>
</>,
</React.Fragment>,
)
await expect.element(screen.getByRole('textbox', { name: 'Name' })).toHaveClass('rounded-[10px]', 'py-[7px]', 'system-md-regular')

View File

@ -1,7 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ReactNode } from 'react'
import type { FileTreeIconType } from '.'
import { useState } from 'react'
import * as React from 'react'
import {
FileTreeBadge,
FileTreeFile,
@ -118,7 +117,7 @@ function FileTreeNodeRows({
}
function ComposedFileTree() {
const [selectedItemId, setSelectedItemId] = useState<string | null>('button')
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('button')
return (
<FileTreeRoot
@ -177,7 +176,7 @@ function ComposedFileTree() {
}
function DataDrivenFileTree() {
const [selectedItemId, setSelectedItemId] = useState<string | null>('app-components-file-tree')
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('app-components-file-tree')
return (
<FileTreeRoot
@ -241,7 +240,7 @@ function StateFrame({
children,
}: {
label: string
children: ReactNode
children: React.ReactNode
}) {
return (
<div className="w-80 min-w-0 space-y-1">

View File

@ -1,22 +1,18 @@
'use client'
import type { ReactNode } from 'react'
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
import { mergeProps } from '@base-ui/react/merge-props'
import { useRender } from '@base-ui/react/use-render'
import {
createContext,
useContext,
} from 'react'
import * as React from 'react'
import { cn } from '../cn'
const FileTreeLevelContext = createContext(1)
const FileTreeLevelContext = React.createContext(1)
function useFileTreeLevel() {
return useContext(FileTreeLevelContext)
return React.useContext(FileTreeLevelContext)
}
function getLabelText(children: ReactNode) {
function getLabelText(children: React.ReactNode) {
return typeof children === 'string' || typeof children === 'number'
? String(children)
: undefined
@ -201,12 +197,12 @@ export function FileTreeFile({
'aria-current': selected ? 'true' : undefined,
'className': fileTreeRowClassName({ className }),
'children': (
<>
<React.Fragment>
{renderGuides(level)}
<div className="flex min-w-0 flex-[1_0_0] items-center py-0.5">
{children}
</div>
</>
</React.Fragment>
),
} as useRender.ElementProps<'button'>
@ -272,7 +268,7 @@ export type FileTreeIconProps
= Omit<useRender.ComponentProps<'span'>, 'children'>
& {
type?: FileTreeIconType
children?: ReactNode
children?: React.ReactNode
}
export function FileTreeIcon({
@ -286,18 +282,18 @@ export function FileTreeIcon({
'aria-hidden': true,
'className': cn('relative flex size-5 shrink-0 items-center justify-center text-text-secondary', className),
'children': (
<>
<React.Fragment>
{children ?? (
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-accent i-ri-folder-open-line group-data-panel-open/file-tree-row:block" />
</>
</React.Fragment>
)
: <span className={cn('size-4', fileTreeIconClassNames[type])} />
)}
</>
</React.Fragment>
),
}

View File

@ -1,3 +1,4 @@
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '../../field'
import { Form } from '../../form'
@ -22,7 +23,7 @@ describe('Input', () => {
it('should apply size variants shared with FieldControl', async () => {
const screen = await render(
<>
<React.Fragment>
<label>
Small input
<Input size="small" />
@ -34,7 +35,7 @@ describe('Input', () => {
<FieldControl size="large" />
</FieldRoot>
</div>
</>,
</React.Fragment>,
)
await expect.element(screen.getByRole('textbox', { name: 'Small input' })).toHaveClass('rounded-md', 'py-[3px]', 'system-xs-regular')

View File

@ -0,0 +1,7 @@
import * as React from 'react'
const noop: typeof React.useLayoutEffect = () => {}
export const useIsoLayoutEffect = typeof document !== 'undefined'
? React.useLayoutEffect
: noop

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { ComponentProps } from 'react'
import type * as React from 'react'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
@ -28,7 +28,7 @@ const kbdVariants = cva(
export type KbdColor = NonNullable<VariantProps<typeof kbdVariants>['color']>
export type KbdProps
= Omit<ComponentProps<'kbd'>, 'color'>
= Omit<React.ComponentProps<'kbd'>, 'color'>
& VariantProps<typeof kbdVariants>
export function Kbd({
@ -46,7 +46,7 @@ export function Kbd({
)
}
export type KbdGroupProps = ComponentProps<'span'>
export type KbdGroupProps = React.ComponentProps<'span'>
export function KbdGroup({
className,

View File

@ -1,4 +1,3 @@
import type { ReactNode } from 'react'
import type {
NumberFieldButtonProps,
NumberFieldControlsProps,
@ -6,6 +5,7 @@ import type {
NumberFieldInputProps,
NumberFieldUnitProps,
} from '../index'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import {
NumberField,
@ -21,7 +21,7 @@ type RenderNumberFieldOptions = {
defaultValue?: number
groupProps?: Partial<NumberFieldGroupProps>
inputProps?: Partial<NumberFieldInputProps>
unitProps?: Partial<NumberFieldUnitProps> & { children?: ReactNode }
unitProps?: Partial<NumberFieldUnitProps> & { children?: React.ReactNode }
controlsProps?: Partial<NumberFieldControlsProps>
incrementProps?: Partial<NumberFieldButtonProps>
decrementProps?: Partial<NumberFieldButtonProps>
@ -191,7 +191,7 @@ describe('NumberField wrapper', () => {
it('should rely on aria-labelledby when provided instead of injecting a fallback aria-label', async () => {
const screen = await render(
<>
<React.Fragment>
<span id="increment-label">Increment from label</span>
<span id="decrement-label">Decrement from label</span>
<NumberField defaultValue={8}>
@ -203,7 +203,7 @@ describe('NumberField wrapper', () => {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</>,
</React.Fragment>,
)
await expect.element(screen.getByRole('button', { name: 'Increment from label' })).not.toHaveAttribute('aria-label')

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useId, useState } from 'react'
import * as React from 'react'
import {
NumberField,
NumberFieldControls,
@ -67,8 +67,8 @@ const DemoField = ({
widthClassName,
formatValue,
}: DemoFieldProps) => {
const inputId = useId()
const [value, setValue] = useState<number | null>(defaultValue ?? null)
const inputId = React.useId()
const [value, setValue] = React.useState<number | null>(defaultValue ?? null)
return (
<div className={cn('flex w-full max-w-80 flex-col gap-2', widthClassName)}>

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { HTMLAttributes } from 'react'
import type * as React from 'react'
import { NumberField as BaseNumberField } from '@base-ui/react/number-field'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
@ -97,7 +97,7 @@ export const numberFieldUnitVariants = cva(
},
)
export type NumberFieldUnitProps = HTMLAttributes<HTMLSpanElement> & VariantProps<typeof numberFieldUnitVariants>
export type NumberFieldUnitProps = React.ComponentProps<'span'> & VariantProps<typeof numberFieldUnitVariants>
export function NumberFieldUnit({
className,
@ -116,7 +116,7 @@ const numberFieldControlsVariants = cva(
'flex shrink-0 flex-col items-stretch border-l border-divider-subtle bg-transparent text-text-tertiary',
)
export type NumberFieldControlsProps = HTMLAttributes<HTMLDivElement>
export type NumberFieldControlsProps = React.ComponentProps<'div'>
export function NumberFieldControls({
className,

View File

@ -152,7 +152,11 @@ describe('Pagination primitive', () => {
cancelable: true,
}))
await expect.element(screen.getByRole('button', { name: 'Edit page number, current page 2 of 200' })).toBeInTheDocument()
const summaryButton = screen.getByRole('button', { name: 'Edit page number, current page 2 of 200' })
await expect.element(summaryButton).toBeInTheDocument()
await vi.waitFor(() => {
expect(document.activeElement).toBe(summaryButton.element())
})
})
it('cancels the page input editing mode with Escape', async () => {

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ComponentProps } from 'react'
import { useState } from 'react'
import * as React from 'react'
import {
Pagination,
PaginationSkeleton,
@ -15,8 +14,8 @@ function PaginationExample({
initialPageSize?: number
totalPages?: number
}) {
const [page, setPage] = useState(initialPage)
const [pageSize, setPageSize] = useState(initialPageSize)
const [page, setPage] = React.useState(initialPage)
const [pageSize, setPageSize] = React.useState(initialPageSize)
return (
<Pagination
@ -32,7 +31,7 @@ function PaginationExample({
)
}
function PaginationDemo(props: ComponentProps<typeof PaginationExample>) {
function PaginationDemo(props: React.ComponentProps<typeof PaginationExample>) {
return (
<div className="w-236 max-w-full bg-components-panel-bg px-16 py-10">
<PaginationExample {...props} />

View File

@ -1,12 +1,12 @@
'use client'
import type { Button as BaseButtonNS } from '@base-ui/react/button'
import type { ReactNode } from 'react'
import { Button as BaseButton } from '@base-ui/react/button'
import { mergeProps } from '@base-ui/react/merge-props'
import { useRender } from '@base-ui/react/use-render'
import { createContext, useContext, useMemo, useRef, useState } from 'react'
import * as React from 'react'
import { cn } from '../cn'
import { useIsoLayoutEffect } from '../internals/use-iso-layout-effect'
import {
NumberField,
NumberFieldGroup,
@ -28,10 +28,10 @@ type PaginationContextValue = {
items: PageItem[]
}
const PaginationContext = createContext<PaginationContextValue | null>(null)
const PaginationContext = React.createContext<PaginationContextValue | null>(null)
function usePaginationContext(component: string) {
const context = useContext(PaginationContext)
const context = React.useContext(PaginationContext)
if (!context)
throw new Error(`${component} must be used inside PaginationRoot.`)
@ -156,7 +156,7 @@ export function PaginationRoot({
const normalizedPage = clampPage(page, normalizedTotalPages)
const hasPages = normalizedTotalPages > 0
const disabled = normalizedTotalPages <= 1
const items = useMemo(() => getPageItems({
const items = React.useMemo(() => getPageItems({
page: normalizedPage,
totalPages: normalizedTotalPages,
siblingCount,
@ -170,7 +170,7 @@ export function PaginationRoot({
visiblePageCount,
])
const context = useMemo<PaginationContextValue>(() => ({
const context = React.useMemo<PaginationContextValue>(() => ({
page: normalizedPage,
totalPages: normalizedTotalPages,
hasPages,
@ -239,7 +239,7 @@ export function PaginationNavigation({
}
type PaginationButtonProps = Omit<BaseButtonNS.Props, 'children'> & {
children?: ReactNode
children?: React.ReactNode
}
const paginationArrowButtonClassName = [
@ -316,7 +316,7 @@ export function PaginationNext({
export type PaginationPageJumpProps = Omit<BaseButtonNS.Props, 'children'> & {
inputLabel?: string
children?: ReactNode
children?: React.ReactNode
}
export function PaginationPageJump({
@ -327,8 +327,26 @@ export function PaginationPageJump({
...props
}: PaginationPageJumpProps) {
const pagination = usePaginationContext('PaginationPageJump')
const [editing, setEditing] = useState(false)
const summaryButtonRef = useRef<HTMLButtonElement | null>(null)
const [editing, setEditing] = React.useState(false)
const summaryButtonRef = React.useRef<HTMLButtonElement | null>(null)
const restoreSummaryFocusRef = React.useRef(false)
useIsoLayoutEffect(() => {
if (editing || !restoreSummaryFocusRef.current)
return
restoreSummaryFocusRef.current = false
const summaryButton = summaryButtonRef.current
if (!summaryButton)
return
const activeElement = summaryButton.ownerDocument.activeElement
if (activeElement && activeElement !== summaryButton.ownerDocument.body)
return
summaryButton.focus({ preventScroll: true })
}, [editing])
if (!pagination.hasPages)
return null
@ -367,14 +385,15 @@ export function PaginationPageJump({
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault()
restoreSummaryFocusRef.current = true
event.currentTarget.blur()
return
}
if (event.key === 'Escape') {
event.preventDefault()
restoreSummaryFocusRef.current = true
setEditing(false)
requestAnimationFrame(() => summaryButtonRef.current?.focus())
}
}}
/>
@ -402,11 +421,11 @@ export function PaginationPageJump({
}}
>
{children ?? (
<>
<React.Fragment>
<span>{pagination.page}</span>
<span className="text-text-quaternary">/</span>
<span>{pagination.totalPages}</span>
</>
</React.Fragment>
)}
</BaseButton>
)
@ -444,7 +463,7 @@ export function PaginationPageList({
export type PaginationPageProps = Omit<BaseButtonNS.Props, 'children'> & {
page: number
children?: ReactNode
children?: React.ReactNode
}
export function PaginationPage({
@ -505,7 +524,7 @@ export type PaginationPageSizeProps<Value extends number = number> = {
'value': Value
'options': readonly Value[]
'onValueChange': (value: Value) => void
'label'?: ReactNode
'label'?: React.ReactNode
'aria-label'?: string
'className'?: string
}
@ -563,7 +582,7 @@ export type PaginationPageSizeConfig<Value extends number = number> = {
value: Value
options: readonly Value[]
onValueChange: (value: Value) => void
label?: ReactNode
label?: React.ReactNode
ariaLabel?: string
}

View File

@ -1,3 +1,4 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Popover,
@ -5,7 +6,7 @@ import {
PopoverTrigger,
} from '..'
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Placement } from '.'
import { Button } from '@langgenius/dify-ui/button'
import { useState } from 'react'
import * as React from 'react'
import {
Popover,
PopoverClose,
@ -159,7 +159,7 @@ const PLACEMENTS: Placement[] = [
]
const PlacementsDemo = () => {
const [placement, setPlacement] = useState<Placement>('bottom')
const [placement, setPlacement] = React.useState<Placement>('bottom')
return (
<div className="flex flex-col items-center gap-4 p-20">
@ -208,7 +208,7 @@ export const Placements: Story = {
}
const ControlledDemo = () => {
const [open, setOpen] = useState(false)
const [open, setOpen] = React.useState(false)
return (
<div className="flex items-center gap-3">

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Popover as BasePopover } from '@base-ui/react/popover'
import { cn } from '../cn'
@ -16,7 +16,7 @@ export const PopoverDescription = BasePopover.Description
export const createPopoverHandle = BasePopover.createHandle
type PopoverContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number

View File

@ -1,3 +1,4 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
PreviewCard,
@ -5,7 +6,7 @@ import {
PreviewCardTrigger,
} from '..'
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Placement } from '.'
import { useState } from 'react'
import * as React from 'react'
import {
createPreviewCardHandle,
PreviewCard,
@ -144,7 +144,7 @@ const PLACEMENTS: Placement[] = [
]
const PlacementsDemo = () => {
const [placement, setPlacement] = useState<Placement>('bottom')
const [placement, setPlacement] = React.useState<Placement>('bottom')
return (
<div className="flex flex-col items-center gap-4 p-20">

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { PreviewCard as BasePreviewCard } from '@base-ui/react/preview-card'
import { cn } from '../cn'
@ -27,7 +27,7 @@ export const PreviewCardTrigger = BasePreviewCard.Trigger
export const createPreviewCardHandle = BasePreviewCard.createHandle
type PreviewCardContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ProgressCircleColor, ProgressCircleSize } from '.'
import { Fragment } from 'react'
import * as React from 'react'
import { ProgressCircle } from '.'
const colors: ProgressCircleColor[] = ['gray', 'white', 'blue', 'warning', 'error']
@ -48,7 +48,7 @@ export const CircleMatrix: Story = {
</div>
))}
{colors.map(color => (
<Fragment key={color}>
<React.Fragment key={color}>
<div className="system-xs-semibold-uppercase text-text-secondary">
{color}
</div>
@ -61,7 +61,7 @@ export const CircleMatrix: Story = {
aria-label={`${color} ${size} progress`}
/>
))}
</Fragment>
</React.Fragment>
))}
</div>
),

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
@ -12,7 +12,7 @@ const clickElement = (element: HTMLElement | SVGElement) => {
describe('RadioGroup', () => {
it('should manage a controlled single selection', async () => {
function StorageDemo() {
const [value, setValue] = useState('ssd')
const [value, setValue] = React.useState('ssd')
return (
<FieldRoot name="storageType">

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import { RadioGroup } from '.'
import {
FieldDescription,
@ -28,7 +28,7 @@ export default meta
type Story = StoryObj<typeof meta>
function StandardFormRowsDemo() {
const [value, setValue] = useState('vector')
const [value, setValue] = React.useState('vector')
return (
<FieldRoot name="retrievalIndex" className="w-80">
@ -67,7 +67,7 @@ export const StandardFormRows: Story = {
}
function BooleanInlineDemo() {
const [value, setValue] = useState(true)
const [value, setValue] = React.useState(true)
return (
<FieldRoot name="streaming" className="w-80">
@ -108,7 +108,7 @@ export const BooleanInline: Story = {
}
function OptionCardsDemo() {
const [value, setValue] = useState('default')
const [value, setValue] = React.useState('default')
return (
<FieldRoot name="promptMode" className="w-100">
@ -174,7 +174,7 @@ function DynamicFormFieldDemo() {
{ value: 'high_quality', label: 'High quality' },
{ value: 'economy', label: 'Economy' },
]
const [selected, setSelected] = useState('automatic')
const [selected, setSelected] = React.useState('automatic')
return (
<FieldRoot name="generation_mode" className="flex w-80 flex-col gap-2">

View File

@ -1,4 +1,4 @@
import type { ComponentProps, ReactNode } from 'react'
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
@ -15,8 +15,8 @@ const clickElement = (element: HTMLElement | SVGElement) => {
element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
}
type TestRadioGroupProps = ComponentProps<typeof RadioGroup> & {
children: ReactNode
type TestRadioGroupProps = React.ComponentProps<typeof RadioGroup> & {
children: React.ReactNode
label: string
name?: string
}
@ -37,8 +37,8 @@ function TestRadioGroup({
)
}
type TestRadioOptionProps = ComponentProps<typeof Radio> & {
children: ReactNode
type TestRadioOptionProps = React.ComponentProps<typeof Radio> & {
children: React.ReactNode
}
function TestRadioOption({

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ComponentProps } from 'react'
import { useState } from 'react'
import * as React from 'react'
import {
Radio,
RadioSkeleton,
@ -36,8 +35,8 @@ const meta = {
export default meta
type Story = StoryObj<typeof meta>
function RadioDemo(args: Partial<ComponentProps<typeof Radio>>) {
const [value, setValue] = useState('ssd')
function RadioDemo(args: Partial<React.ComponentProps<typeof Radio>>) {
const [value, setValue] = React.useState('ssd')
return (
<FieldRoot name="storageType">

View File

@ -1,7 +1,7 @@
'use client'
import type { Radio as BaseRadioNS } from '@base-ui/react/radio'
import type { HTMLAttributes } from 'react'
import type * as React from 'react'
import { Radio as BaseRadio } from '@base-ui/react/radio'
import { cn } from '../cn'
@ -87,7 +87,7 @@ export function Radio<Value = string>({
}
export type RadioSkeletonProps
= Omit<HTMLAttributes<HTMLDivElement>, 'className'>
= Omit<React.ComponentProps<'div'>, 'className'>
& {
className?: string
}

View File

@ -1,3 +1,4 @@
import * as React from 'react'
import { render } from 'vitest-browser-react'
import {
ScrollArea,
@ -84,7 +85,7 @@ describe('scroll-area wrapper', () => {
it('should render the convenience wrapper and apply slot props', async () => {
const screen = await render(
<>
<React.Fragment>
<p id="installed-apps-label">Installed apps</p>
<ScrollArea
className="h-40 w-40"
@ -98,7 +99,7 @@ describe('scroll-area wrapper', () => {
>
<div className="h-48 w-20">Scrollable content</div>
</ScrollArea>
</>,
</React.Fragment>,
)
const viewport = screen.getByRole('region', { name: 'Installed apps' })

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import type * as React from 'react'
import {
ScrollAreaContent,
ScrollAreaCorner,

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area'
import { cn } from '../cn'
@ -16,7 +16,7 @@ type ScrollAreaSlotClassNames = {
}
type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
children: ReactNode
children: React.ReactNode
orientation?: 'vertical' | 'horizontal'
slotClassNames?: ScrollAreaSlotClassNames
label?: string

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ReactNode } from 'react'
import * as React from 'react'
import {
SegmentedControl,
SegmentedControlDivider,
@ -35,10 +35,10 @@ const Icon = () => (
)
const Item = () => (
<>
<React.Fragment>
<Icon />
<span className="px-0.5">Item</span>
</>
</React.Fragment>
)
function SegmentedControlExample({
@ -93,7 +93,7 @@ function SpecPanel({
children,
}: {
className?: string
children: ReactNode
children: React.ReactNode
}) {
return (
<div className={className}>

View File

@ -2,7 +2,7 @@
import type { Toggle as BaseToggleNS } from '@base-ui/react/toggle'
import type { ToggleGroup as BaseToggleGroupNS } from '@base-ui/react/toggle-group'
import type { HTMLAttributes } from 'react'
import type * as React from 'react'
import { Toggle as BaseToggle } from '@base-ui/react/toggle'
import { ToggleGroup as BaseToggleGroup } from '@base-ui/react/toggle-group'
import { cn } from '../cn'
@ -39,7 +39,7 @@ export function SegmentedControlItem<Value extends string = string>({
)
}
export type SegmentedControlDividerProps = Omit<HTMLAttributes<HTMLSpanElement>, 'className'> & {
export type SegmentedControlDividerProps = Omit<React.ComponentProps<'span'>, 'className'> & {
className?: string
}

View File

@ -1,3 +1,4 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Select,
@ -13,7 +14,7 @@ import {
} from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import {
Select,
SelectContent,
@ -263,7 +263,7 @@ export const ReadOnly: Story = {
}
const ControlledDemo = () => {
const [value, setValue] = useState<string | null>('balanced')
const [value, setValue] = React.useState<string | null>('balanced')
return (
<div className="flex flex-col items-start gap-3">

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Select as BaseSelect } from '@base-ui/react/select'
import { cva } from 'class-variance-authority'
@ -107,7 +107,7 @@ export function SelectSeparator({
}
type SelectContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type * as React from 'react'
import { useState } from 'react'
import * as React from 'react'
import {
Slider,
SliderControl,
@ -51,7 +50,7 @@ function SliderDemo({
defaultValue: _defaultValue,
...args
}: React.ComponentProps<typeof Slider>) {
const [value, setValue] = useState(initialValue)
const [value, setValue] = React.useState(initialValue)
return (
<div className="w-[320px] space-y-3">

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { StatusDotSize, StatusDotStatus } from '.'
import { Fragment } from 'react'
import * as React from 'react'
import { StatusDot, StatusDotSkeleton } from '.'
const statuses: StatusDotStatus[] = ['success', 'warning', 'error', 'normal', 'disabled']
@ -39,14 +39,14 @@ export const Matrix: Story = {
<div className="system-xs-medium text-text-tertiary">Small</div>
<div className="system-xs-medium text-text-tertiary">Medium</div>
{statuses.map(status => (
<Fragment key={status}>
<React.Fragment key={status}>
<div className="system-xs-semibold-uppercase text-text-secondary">
{status}
</div>
{sizes.map(size => (
<StatusDot key={`${status}-${size}`} status={status} size={size} />
))}
</Fragment>
</React.Fragment>
))}
</div>
),

View File

@ -1,7 +1,7 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { ComponentProps } from 'react'
import type * as React from 'react'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
@ -49,14 +49,14 @@ export type StatusDotStatus = NonNullable<StatusDotVariants['status']>
export type StatusDotSize = NonNullable<StatusDotVariants['size']>
export type StatusDotProps
= Omit<ComponentProps<'span'>, 'children'>
= Omit<React.ComponentProps<'span'>, 'children'>
& {
status?: StatusDotStatus
size?: StatusDotSize
}
export type StatusDotSkeletonProps
= Omit<ComponentProps<'span'>, 'children'>
= Omit<React.ComponentProps<'span'>, 'children'>
& {
size?: StatusDotSize
}

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ComponentProps } from 'react'
import { useState, useTransition } from 'react'
import * as React from 'react'
import { Switch, SwitchSkeleton } from '.'
import {
FieldDescription,
@ -47,12 +46,12 @@ const meta = {
export default meta
type Story = StoryObj<typeof meta>
type SwitchDemoProps = Partial<Omit<ComponentProps<typeof Switch>, 'checked' | 'defaultChecked' | 'onCheckedChange'>> & {
type SwitchDemoProps = Partial<Omit<React.ComponentProps<typeof Switch>, 'checked' | 'defaultChecked' | 'onCheckedChange'>> & {
checked?: boolean
}
const SwitchDemo = (args: SwitchDemoProps) => {
const [enabled, setEnabled] = useState(args.checked ?? false)
const [enabled, setEnabled] = React.useState(args.checked ?? false)
return (
<FieldRoot name="autoRetry" className="w-72">
@ -167,7 +166,7 @@ export const AllStates: Story = {
}
const SizeComparisonDemo = () => {
const [states, setStates] = useState({
const [states, setStates] = React.useState({
xs: false,
sm: false,
md: true,
@ -209,7 +208,7 @@ export const SizeComparison: Story = {
}
const LoadingDemo = () => {
const [loading, setLoading] = useState(true)
const [loading, setLoading] = React.useState(true)
return (
<div className="flex flex-col items-center space-y-4">
@ -275,7 +274,7 @@ export const Loading: Story = {
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
function useMockAutoRetrySettingQuery() {
const [enabled, setEnabled] = useState(false)
const [enabled, setEnabled] = React.useState(false)
return {
data: {
@ -290,8 +289,8 @@ function useMockUpdateAutoRetrySettingMutation({
}: {
onSuccess: (enabled: boolean) => void
}) {
const [requestCount, setRequestCount] = useState(0)
const [isPending, startTransition] = useTransition()
const [requestCount, setRequestCount] = React.useState(0)
const [isPending, startTransition] = React.useTransition()
const mutate = (nextValue: boolean) => {
if (isPending)

View File

@ -2,7 +2,7 @@
import type { Switch as BaseSwitchNS } from '@base-ui/react/switch'
import type { VariantProps } from 'class-variance-authority'
import type { HTMLAttributes } from 'react'
import type * as React from 'react'
import { Switch as BaseSwitch } from '@base-ui/react/switch'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
@ -134,7 +134,7 @@ const switchSkeletonVariants = cva(
)
export type SwitchSkeletonProps
= HTMLAttributes<HTMLDivElement>
= React.ComponentProps<'div'>
& VariantProps<typeof switchSkeletonVariants>
export function SwitchSkeleton({

View File

@ -1,4 +1,4 @@
import type { FocusEvent } from 'react'
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
FieldDescription,
@ -131,7 +131,7 @@ describe('Textarea', () => {
it('should route field props through Base UI Field.Control and textarea-only props to textarea', async () => {
const onFormSubmit = vi.fn()
const onBlur = vi.fn((event: FocusEvent<HTMLTextAreaElement>) => {
const onBlur = vi.fn((event: React.FocusEvent<HTMLTextAreaElement>) => {
expect(event.currentTarget.tagName).toBe('TEXTAREA')
})
const screen = await render(

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { useState } from 'react'
import * as React from 'react'
import { Button } from '../button'
import {
FieldDescription,
@ -91,7 +91,7 @@ export const States: Story = {
}
const FormDemo = () => {
const [savedDescription, setSavedDescription] = useState<string | null>(null)
const [savedDescription, setSavedDescription] = React.useState<string | null>(null)
return (
<Form
@ -134,7 +134,7 @@ export const WithField: Story = {
}
const ControlledDemo = () => {
const [value, setValue] = useState('Summarize customer feedback into actionable product themes.')
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="prompt">
@ -160,7 +160,7 @@ export const Controlled: Story = {
const CharacterCounterDemo = () => {
const maxLength = 120
const [value, setValue] = useState('Summarize customer feedback into actionable product themes.')
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="limitedPrompt">

View File

@ -2,7 +2,7 @@
import type { Field as BaseFieldNS } from '@base-ui/react/field'
import type { VariantProps } from 'class-variance-authority'
import type { ComponentPropsWithRef } from 'react'
import type * as React from 'react'
import { Field as BaseField } from '@base-ui/react/field'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
@ -50,7 +50,7 @@ type UncontrolledTextareaProps = {
onValueChange?: TextareaOnValueChange
}
type TextareaNativeProps = ComponentPropsWithRef<'textarea'>
type TextareaNativeProps = React.ComponentPropsWithRef<'textarea'>
type TextareaOnlyProps = Pick<TextareaNativeProps, 'cols' | 'rows' | 'wrap'>
type TextareaElementProps = Omit<
TextareaNativeProps,

View File

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { ReactNode } from 'react'
import { useRef } from 'react'
import * as React from 'react'
import { toast, ToastHost } from '.'
const buttonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-2 text-sm text-text-secondary shadow-xs transition-colors hover:bg-state-base-hover'
@ -15,7 +14,7 @@ const ExampleCard = ({
eyebrow: string
title: string
description: string
children: ReactNode
children: React.ReactNode
}) => {
return (
<section className={cardClassName}>
@ -250,7 +249,7 @@ const ActionExamples = () => {
}
const DeduplicateExamples = () => {
const saveCountRef = useRef(0)
const saveCountRef = React.useRef(0)
const saveDraft = () => {
saveCountRef.current += 1
@ -314,7 +313,7 @@ const UpdateExamples = () => {
const ToastDocsDemo = () => {
return (
<>
<React.Fragment>
<ToastHost />
<div className="min-h-screen bg-background-default-subtle px-6 py-12">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
@ -339,7 +338,7 @@ const ToastDocsDemo = () => {
</div>
</div>
</div>
</>
</React.Fragment>
)
}

View File

@ -5,7 +5,7 @@ import type {
ToastManagerUpdateOptions,
ToastObject,
} from '@base-ui/react/toast'
import type { ReactNode } from 'react'
import type * as React from 'react'
import { Toast as BaseToast } from '@base-ui/react/toast'
import { cn } from '../cn'
@ -64,11 +64,11 @@ type ToastHostProps = {
}
type ToastDismiss = (toastId?: string) => void
type ToastCall = (title: ReactNode, options?: ToastOptions) => string
type TypedToastCall = (title: ReactNode, options?: TypedToastOptions) => string
type ToastCall = (title: React.ReactNode, options?: ToastOptions) => string
type TypedToastCall = (title: React.ReactNode, options?: TypedToastOptions) => string
type ToastApi = {
(title: ReactNode, options?: ToastOptions): string
(title: React.ReactNode, options?: ToastOptions): string
success: TypedToastCall
error: TypedToastCall
warning: TypedToastCall

View File

@ -1,7 +1,8 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import { Tooltip, TooltipContent, TooltipTrigger } from '../index'
const renderWithSafeViewport = (ui: import('react').ReactNode) => render(
const renderWithSafeViewport = (ui: React.ReactNode) => render(
<div style={{ minHeight: '100vh', minWidth: '100vw', padding: '240px' }}>
{ui}
</div>,

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { Placement } from '.'
import { useState } from 'react'
import * as React from 'react'
import {
Tooltip,
TooltipContent,
@ -107,7 +107,7 @@ const PLACEMENTS: Placement[] = [
]
const PlacementsDemo = () => {
const [placement, setPlacement] = useState<Placement>('top')
const [placement, setPlacement] = React.useState<Placement>('top')
return (
<div className="flex flex-col items-center gap-4 p-24">

View File

@ -1,6 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'
import { cn } from '../cn'
@ -32,7 +32,7 @@ export const Tooltip = BaseTooltip.Root
export const TooltipTrigger = BaseTooltip.Trigger
type TooltipContentProps = {
children: ReactNode
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number