refactor(dify-ui): standardize primitive boundary names (#38669)

This commit is contained in:
yyh 2026-07-10 22:17:10 +08:00 committed by GitHub
parent e3a08b40f2
commit 6e5ba18d65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
97 changed files with 622 additions and 616 deletions

View File

@ -28,9 +28,12 @@ Flag:
- Missing `package.json#exports` entry for a new primitive.
- Internal package imports using workspace subpaths instead of relative paths.
- Exported props using internal-only types that consumers cannot import from the component subpath.
- Canonical primitive boundaries or their associated public types using a redundant `Root` suffix when no higher-level convenience component exists in the same subpath.
Consumers use subpath exports such as `@langgenius/dify-ui/button`.
Canonical boundaries use the primitive name and matching public types (`Select` / `SelectProps`). Keep `Root` only to distinguish a low-level anatomy root from a higher-level convenience component (`CheckboxRoot` / `Checkbox`); implementation aliases should still show their Base UI source (`BaseSelect.Root.Props`).
## Props And State
Flag:
@ -45,17 +48,17 @@ Flag:
Prefer Base UI/Dify UI data attributes and CSS variables for visual state: `data-open`, `data-checked`, `data-disabled`, `data-highlighted`, `data-popup-open`, `group-data-*`, `peer-data-*`, `has-[:focus-visible]`, and primitive CSS variables such as anchor width or transform origin. Use JS conditional classes for product/business state that the primitive does not expose.
For non-string `Select` and `RadioGroup` values, prefer explicit domain generics at the root and at child value carriers. JSX children do not inherit the parent generic, so `RadioGroup<PromptMode>` should compose with `Radio<PromptMode>`, `RadioRoot<PromptMode>`, or option values from a typed collection. For `Select`, prefer the Base UI `items` collection pattern for typed value-to-label rendering, and flag string coercion helpers used only to recover display labels.
For non-string `Select` and `RadioGroup` values, prefer explicit domain generics at the root and at child value carriers. JSX children do not inherit the parent generic, so `RadioGroup<PromptMode>` should compose with `Radio<PromptMode>`, `RadioItem<PromptMode>`, or option values from a typed collection. For `Select`, prefer the Base UI `items` collection pattern for typed value-to-label rendering, and flag string coercion helpers used only to recover display labels.
## Forms
Flag:
- Form-like UI using unrelated `Input` and `Button` pieces without a submit boundary.
- Text-like fields not composed through `FieldRoot`, `FieldLabel`, and `FieldControl` when using Dify UI form semantics.
- Text-like fields not composed through `Field`, `FieldLabel`, and `FieldControl` when using Dify UI form semantics.
- Select fields using `FieldLabel` instead of `SelectLabel`.
- Slider fields using a generic label instead of `SliderLabel`.
- Checkbox/radio groups missing `FieldsetRoot` and `FieldsetLegend`.
- Checkbox/radio groups missing `Fieldset` and `FieldsetLegend`.
- Field errors or descriptions rendered without `FieldDescription` / `FieldError` relationships.
`Form` is the submit boundary. Dify UI form primitives are not a form state-management framework; business validation and schema-driven behavior belong in `web/`.

View File

@ -8,6 +8,7 @@ Shared design tokens, the `cn()` utility, CSS-first Tailwind styles, and headles
- Inside dify-ui, cross-component imports use relative paths (`../button`). External consumers use subpath exports (`@langgenius/dify-ui/button`).
- No imports from `web/`. No dependencies on next / i18next / ky / jotai / zustand.
- One component per folder: `src/<name>/index.tsx`, optional `index.stories.tsx` and `__tests__/index.spec.tsx`. Add a matching `./<name>` subpath to `package.json#exports`.
- Name the canonical public boundary and its associated public types after the primitive without a `Root` suffix (`Select` / `SelectProps`). Keep `Root` only when the subpath also exports a higher-level convenience component that must be distinguished from the low-level anatomy root (`CheckboxRoot` / `Checkbox`). Preserve the upstream anatomy in implementation type sources such as `BaseSelect.Root.Props`.
- Props pattern: `Omit<BaseXxx.Root.Props, 'className' | ...> & VariantProps<typeof xxxVariants> & { /* custom */ }`.
- Use plain `Omit<...>` only for non-union Base UI props. When a prop changes the valid shape of related props (for example `value` / `defaultValue`, `multiple` / `value`, or `clearable` / `onChange`), model that relationship with an explicit discriminated union or a distributive helper instead of flattening the props.
- Preserve Base UI generic value contracts in wrappers. If the upstream primitive is generic, expose the same generic parameters and pass them through to the Base UI part, such as `Select.Root<Value, Multiple>`, `RadioGroup<Value>`, or `Radio.Root<Value>`.

View File

@ -30,7 +30,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog'
import { Drawer, DrawerPopup, DrawerTrigger } from '@langgenius/dify-ui/drawer'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
@ -41,6 +41,8 @@ import '@langgenius/dify-ui/styles.css' // once, in the app root
Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive.
The canonical boundary exported from a primitive subpath uses the primitive name without a `Root` suffix, and its public types follow the same name (`Select` / `SelectProps`, `Drawer` / `DrawerProps`). Keep `Root` only when the subpath exposes both a low-level anatomy root and a higher-level convenience component, such as `CheckboxRoot` / `Checkbox` or `PaginationRoot` / `Pagination`. Implementation code should continue to reference the upstream Base UI anatomy explicitly through names such as `BaseSelect.Root.Props`.
## Primitives
| Category | Subpath | Notes |
@ -79,15 +81,15 @@ Dify UI's form primitives are a Base UI composition layer for native form semant
Use `Form` for the submit boundary. It renders a native `<form>`, preserves Enter-to-submit and submit-button behavior, and adds Base UI's `onFormSubmit`, `errors`, `actionsRef`, and `validationMode` APIs for structured values and consolidated field validation. Prefer it over a bare `<form>` when the form is composed with Dify UI fields.
Use `FieldRoot` for each standalone named field. A field must have a stable `name`, a label relationship, and either a `FieldControl` or another control that participates in the same Base UI field context. Prefer a visible label for normal form rows; when the surrounding UI already supplies the visible text, use the matching label primitive visually hidden or put `aria-label` on the actual interactive control. `FieldDescription` and `FieldError` provide the message relationships that screen readers need, while the Dify wrapper adds the default Form Input Set styling from the design system.
Use `Field` for each standalone named field. A field must have a stable `name`, a label relationship, and either a `FieldControl` or another control that participates in the same Base UI field context. Prefer a visible label for normal form rows; when the surrounding UI already supplies the visible text, use the matching label primitive visually hidden or put `aria-label` on the actual interactive control. `FieldDescription` and `FieldError` provide the message relationships that screen readers need, while the Dify wrapper adds the default Form Input Set styling from the design system.
Choose the label primitive by the control semantics. Text-like inputs, `Textarea`, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels.
Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label:
Use `Fieldset` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label:
```tsx
<FieldRoot name="allowedNetworkProtocols">
<FieldsetRoot render={<CheckboxGroup />}>
<Field name="allowedNetworkProtocols">
<Fieldset render={<CheckboxGroup />}>
<FieldsetLegend>Allowed network protocols</FieldsetLegend>
<FieldItem>
<FieldLabel className="flex items-center gap-2">
@ -95,11 +97,11 @@ Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group
HTTPS
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
```
`FieldsetRoot` provides the group semantics and legend relationship. It does not own the interactive state of the grouped control. Pass `disabled`, `value`, `defaultValue`, and change handlers to the actual group primitive (`CheckboxGroup`, radio group, slider root, etc.) instead of relying on the fieldset wrapper to manage them.
`Fieldset` provides the group semantics and legend relationship. It does not own the interactive state of the grouped control. Pass `disabled`, `value`, `defaultValue`, and change handlers to the actual group primitive (`CheckboxGroup`, radio group, slider root, etc.) instead of relying on the fieldset wrapper to manage them.
## Typed value contracts

View File

@ -17,26 +17,26 @@ import { parsePlacement } from '../placement'
export type { Placement }
export type AutocompleteRootProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
export type AutocompleteRootGroupedProps<
export type AutocompleteProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
export type AutocompleteGroupedProps<
Items extends readonly { items: readonly unknown[] }[],
> = Omit<AutocompleteRootProps<Items[number]['items'][number]>, 'items'> & {
> = Omit<AutocompleteProps<Items[number]['items'][number]>, 'items'> & {
items: Items
}
export type AutocompleteRootFlatProps<ItemValue>
= Omit<AutocompleteRootProps<ItemValue>, 'items'>
export type AutocompleteFlatProps<ItemValue>
= Omit<AutocompleteProps<ItemValue>, 'items'>
& {
items?: readonly ItemValue[]
}
export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
props: AutocompleteRootGroupedProps<Items>,
props: AutocompleteGroupedProps<Items>,
): React.JSX.Element
export function Autocomplete<ItemValue>(
props: AutocompleteRootFlatProps<ItemValue>,
props: AutocompleteFlatProps<ItemValue>,
): React.JSX.Element
export function Autocomplete(
props: AutocompleteRootProps<unknown>,
props: AutocompleteProps<unknown>,
): React.JSX.Element {
return <BaseAutocomplete.Root {...props} />
}
@ -48,8 +48,8 @@ export const AutocompleteRow = BaseAutocomplete.Row
export const useAutocompleteFilter = BaseAutocomplete.useFilter
export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
export type AutocompleteRootChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
export type AutocompleteRootHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
export type AutocompleteChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
export type AutocompleteHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
const autocompletePopupClassName = [
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',

View File

@ -1,8 +1,8 @@
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox'
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
import { Field, FieldItem, FieldLabel } from '../../field'
import { Fieldset, FieldsetLegend } from '../../fieldset'
import { CheckboxGroup } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -46,8 +46,8 @@ describe('CheckboxGroup', () => {
it('should compose with Dify UI Field and Fieldset without losing labels', async () => {
const onValueChange = vi.fn()
const screen = await render(
<FieldRoot name="features">
<FieldsetRoot render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}>
<Field name="features">
<Fieldset render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}>
<FieldsetLegend>Features</FieldsetLegend>
<FieldItem>
<FieldLabel>
@ -61,8 +61,8 @@ describe('CheckboxGroup', () => {
Analytics
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>,
</Fieldset>
</Field>,
)
const analytics = screen.getByRole('checkbox', { name: 'Analytics' })

View File

@ -3,12 +3,12 @@ import * as React from 'react'
import { CheckboxGroup } from '.'
import { Checkbox } from '../checkbox'
import {
Field,
FieldDescription,
FieldItem,
FieldLabel,
FieldRoot,
} from '../field'
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
import { Fieldset, FieldsetLegend } from '../fieldset'
const meta = {
title: 'Base/Form/CheckboxGroup',
@ -80,11 +80,11 @@ function DynamicFormFieldDemo() {
const [selected, setSelected] = React.useState<string[]>(['markdown'])
return (
<FieldRoot name="allowed_file_types" className="flex w-80 flex-col gap-2">
<Field name="allowed_file_types" className="flex w-80 flex-col gap-2">
<FieldDescription className="body-xs-regular text-text-tertiary">
This mirrors Dify dynamic form fields where checkbox options are controlled by schema and persisted as a string array.
</FieldDescription>
<FieldsetRoot
<Fieldset
render={(
<CheckboxGroup
value={selected}
@ -104,8 +104,8 @@ function DynamicFormFieldDemo() {
</FieldLabel>
</FieldItem>
))}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}

View File

@ -1,7 +1,7 @@
import { render } from 'vitest-browser-react'
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '../index'
@ -10,10 +10,10 @@ const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElem
describe('Collapsible wrappers', () => {
it('renders the Base UI anatomy with an accessible trigger', async () => {
const screen = await render(
<CollapsibleRoot defaultOpen data-testid="collapsible-root">
<Collapsible defaultOpen data-testid="collapsible-root">
<CollapsibleTrigger>Recovery keys</CollapsibleTrigger>
<CollapsiblePanel>Panel content</CollapsiblePanel>
</CollapsibleRoot>,
</Collapsible>,
)
await expect.element(screen.getByTestId('collapsible-root')).toBeInTheDocument()
@ -23,10 +23,10 @@ describe('Collapsible wrappers', () => {
it('toggles open state through the trigger without caller-owned state', async () => {
const screen = await render(
<CollapsibleRoot>
<Collapsible>
<CollapsibleTrigger>Toggle section</CollapsibleTrigger>
<CollapsiblePanel>Hidden content</CollapsiblePanel>
</CollapsibleRoot>,
</Collapsible>,
)
const trigger = screen.getByRole('button', { name: 'Toggle section' })
@ -40,10 +40,10 @@ describe('Collapsible wrappers', () => {
it('forwards className to every compound part', async () => {
const screen = await render(
<CollapsibleRoot defaultOpen className="custom-root">
<Collapsible defaultOpen className="custom-root">
<CollapsibleTrigger className="custom-trigger">Custom</CollapsibleTrigger>
<CollapsiblePanel className="custom-panel">Custom panel</CollapsiblePanel>
</CollapsibleRoot>,
</Collapsible>,
)
await expect.element(screen.getByRole('button', { name: 'Custom' })).toHaveClass('custom-trigger')
@ -53,10 +53,10 @@ describe('Collapsible wrappers', () => {
it('passes Base UI panel props through to the panel', async () => {
const screen = await render(
<CollapsibleRoot defaultOpen>
<Collapsible defaultOpen>
<CollapsibleTrigger>Styled trigger</CollapsibleTrigger>
<CollapsiblePanel keepMounted>Styled panel</CollapsiblePanel>
</CollapsibleRoot>,
</Collapsible>,
)
await expect.element(screen.getByRole('button', { name: 'Styled trigger' })).toHaveAttribute('data-panel-open', '')

View File

@ -1,15 +1,15 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '.'
import { cn } from '../cn'
const meta = {
title: 'Base/UI/Collapsible',
component: CollapsibleRoot,
component: Collapsible,
parameters: {
layout: 'centered',
docs: {
@ -19,7 +19,7 @@ const meta = {
},
},
tags: ['autodocs'],
} satisfies Meta<typeof CollapsibleRoot>
} satisfies Meta<typeof Collapsible>
export default meta
type Story = StoryObj<typeof meta>
@ -67,25 +67,25 @@ export const Anatomy: Story = {
defaultOpen: true,
},
render: args => (
<CollapsibleRoot {...args} className={rootClassName}>
<Collapsible {...args} className={rootClassName}>
<RecoveryKeys />
</CollapsibleRoot>
</Collapsible>
),
}
export const DefaultClosed: Story = {
render: () => (
<CollapsibleRoot className={rootClassName}>
<Collapsible className={rootClassName}>
<RecoveryKeys />
</CollapsibleRoot>
</Collapsible>
),
}
export const DefaultOpen: Story = {
render: () => (
<CollapsibleRoot defaultOpen className={rootClassName}>
<Collapsible defaultOpen className={rootClassName}>
<RecoveryKeys />
</CollapsibleRoot>
</Collapsible>
),
}
@ -102,9 +102,9 @@ export const Controlled: Story = {
>
{open ? 'Close panel' : 'Open panel'}
</button>
<CollapsibleRoot open={open} onOpenChange={setOpen} className={rootClassName}>
<Collapsible open={open} onOpenChange={setOpen} className={rootClassName}>
<RecoveryKeys />
</CollapsibleRoot>
</Collapsible>
</div>
)
},
@ -112,25 +112,25 @@ export const Controlled: Story = {
export const Disabled: Story = {
render: () => (
<CollapsibleRoot disabled className={rootClassName}>
<Collapsible disabled className={rootClassName}>
<RecoveryKeys />
</CollapsibleRoot>
</Collapsible>
),
}
export const KeepMounted: Story = {
render: () => (
<CollapsibleRoot className={rootClassName}>
<Collapsible className={rootClassName}>
<RecoveryKeys panelProps={{ keepMounted: true }} />
</CollapsibleRoot>
</Collapsible>
),
}
export const HiddenUntilFound: Story = {
render: () => (
<CollapsibleRoot className={rootClassName}>
<Collapsible className={rootClassName}>
<RecoveryKeys panelProps={{ hiddenUntilFound: true }} />
</CollapsibleRoot>
</Collapsible>
),
}
@ -159,7 +159,7 @@ export const SettingsSections: Story = {
render: () => (
<div className={sectionRootClassName}>
{settingSections.map((section, index) => (
<CollapsibleRoot
<Collapsible
key={section.title}
defaultOpen={section.defaultOpen}
className={cn(index > 0 && 'mt-px')}
@ -176,7 +176,7 @@ export const SettingsSections: Story = {
{section.description}
</div>
</CollapsiblePanel>
</CollapsibleRoot>
</Collapsible>
))}
</div>
),

View File

@ -4,16 +4,16 @@ import type { Collapsible as BaseCollapsibleNS } from '@base-ui/react/collapsibl
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
import { cn } from '../cn'
export type CollapsibleRootProps
export type CollapsibleProps
= Omit<BaseCollapsibleNS.Root.Props, 'className'>
& {
className?: string
}
export function CollapsibleRoot({
export function Collapsible({
className,
...props
}: CollapsibleRootProps) {
}: CollapsibleProps) {
return (
<BaseCollapsible.Root
className={cn('flex min-w-0 flex-col', className)}

View File

@ -31,9 +31,9 @@ import {
} from '.'
import { cn } from '../cn'
import {
Field,
FieldDescription,
FieldLabel,
FieldRoot,
} from '../field'
type Option = {
@ -411,7 +411,7 @@ const AsyncDirectoryDemo = () => {
: 'Try a different owner search.'
return (
<FieldRoot name="owner" className={fieldWidth}>
<Field name="owner" className={fieldWidth}>
<FieldLabel>Owner</FieldLabel>
<Combobox
items={items}
@ -472,7 +472,7 @@ const AsyncDirectoryDemo = () => {
<ComboboxEmpty>{emptyMessage}</ComboboxEmpty>
</ComboboxContent>
</Combobox>
</FieldRoot>
</Field>
)
}
@ -523,7 +523,7 @@ const AsyncReviewerDemo = () => {
: 'Try a different reviewer search.'
return (
<FieldRoot name="asyncReviewers" className={fieldWidth}>
<Field name="asyncReviewers" className={fieldWidth}>
<FieldLabel>Async reviewers</FieldLabel>
<Combobox
items={items}
@ -609,7 +609,7 @@ const AsyncReviewerDemo = () => {
</ComboboxContent>
</Combobox>
<FieldDescription>Selected reviewers stay available while async matches change.</FieldDescription>
</FieldRoot>
</Field>
)
}
@ -632,7 +632,7 @@ type Story = StoryObj<typeof meta>
export const Default: Story = {
render: () => (
<FieldRoot name="dataSource" className={fieldWidth}>
<Field name="dataSource" className={fieldWidth}>
<FieldLabel>Connect source</FieldLabel>
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource}>
<ComboboxInputGroup className="h-8 min-h-8 px-2">
@ -645,13 +645,13 @@ export const Default: Story = {
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
</ComboboxContent>
</Combobox>
</FieldRoot>
</Field>
),
}
export const FormField: Story = {
render: () => (
<FieldRoot name="sourceConnector" className={fieldWidth}>
<Field name="sourceConnector" className={fieldWidth}>
<FieldLabel>Connect source</FieldLabel>
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource}>
<ComboboxInputGroup className="h-8 min-h-8 px-2">
@ -665,7 +665,7 @@ export const FormField: Story = {
</ComboboxContent>
</Combobox>
<FieldDescription>Type to filter, then choose a remembered data source.</FieldDescription>
</FieldRoot>
</Field>
),
}
@ -698,7 +698,7 @@ export const Sizes: Story = {
render: () => (
<div className="flex w-80 flex-col gap-3">
{(['small', 'medium', 'large'] as const).map(size => (
<FieldRoot key={size} name={`provider-${size}`}>
<Field key={size} name={`provider-${size}`}>
<FieldLabel>{`${size[0]!.toUpperCase()}${size.slice(1)}`}</FieldLabel>
<Combobox items={sizeOptions} defaultValue={defaultProvider}>
<ComboboxInputGroup size={size} className="px-2">
@ -711,7 +711,7 @@ export const Sizes: Story = {
<ComboboxList>{renderOptionItem}</ComboboxList>
</ComboboxContent>
</Combobox>
</FieldRoot>
</Field>
))}
</div>
),
@ -738,7 +738,7 @@ const MultipleChipsDemo = () => {
const [value, setValue] = React.useState<Option[]>(defaultReviewers)
return (
<FieldRoot name="reviewers" className={fieldWidth}>
<Field name="reviewers" className={fieldWidth}>
<FieldLabel>Reviewers</FieldLabel>
<Combobox items={reviewerOptions} multiple value={value} onValueChange={setValue}>
<ComboboxInputGroup className="h-auto min-h-8 items-start py-1">
@ -763,7 +763,7 @@ const MultipleChipsDemo = () => {
</ComboboxContent>
</Combobox>
<FieldDescription>Selected reviewers wrap inside the input instead of scrolling horizontally.</FieldDescription>
</FieldRoot>
</Field>
)
}
@ -786,7 +786,7 @@ export const VirtualizedLongList: Story = {
export const EmptyAndStatus: Story = {
render: () => (
<FieldRoot name="connector" className={fieldWidth}>
<Field name="connector" className={fieldWidth}>
<FieldLabel>Connector</FieldLabel>
<Combobox items={emptyOptions} defaultInputValue="salesforce">
<ComboboxInputGroup className="h-8 min-h-8 px-2">
@ -801,14 +801,14 @@ export const EmptyAndStatus: Story = {
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
</ComboboxContent>
</Combobox>
</FieldRoot>
</Field>
),
}
export const DisabledAndReadOnly: Story = {
render: () => (
<div className="flex w-80 flex-col gap-3">
<FieldRoot name="disabledProvider" disabled>
<Field name="disabledProvider" disabled>
<Combobox items={providerOptions} defaultValue={disabledProvider} disabled>
<ComboboxLabel>Disabled provider</ComboboxLabel>
<ComboboxTrigger aria-label="Disabled model provider">
@ -819,8 +819,8 @@ export const DisabledAndReadOnly: Story = {
<ComboboxList>{renderOptionItem}</ComboboxList>
</ComboboxContent>
</Combobox>
</FieldRoot>
<FieldRoot name="readOnlySource">
</Field>
<Field name="readOnlySource">
<FieldLabel>Read-only source</FieldLabel>
<Combobox items={dataSourceOptions} defaultValue={readOnlyDataSource} readOnly>
<ComboboxInputGroup className="h-8 min-h-8 px-2">
@ -832,7 +832,7 @@ export const DisabledAndReadOnly: Story = {
<ComboboxList>{renderOptionItem}</ComboboxList>
</ComboboxContent>
</Combobox>
</FieldRoot>
</Field>
</div>
),
}

View File

@ -17,11 +17,11 @@ import { parsePlacement } from '../placement'
export type { Placement }
export type ComboboxRootProps<Value, Multiple extends boolean | undefined = false>
export type ComboboxProps<Value, Multiple extends boolean | undefined = false>
= BaseCombobox.Root.Props<Value, Multiple>
export function Combobox<Value, Multiple extends boolean | undefined = false>(
props: ComboboxRootProps<Value, Multiple>,
props: ComboboxProps<Value, Multiple>,
): React.JSX.Element {
return <BaseCombobox.Root {...props} />
}
@ -33,8 +33,8 @@ export const ComboboxRow = BaseCombobox.Row
export const useComboboxFilter = BaseCombobox.useFilter
export const useComboboxFilteredItems = BaseCombobox.useFilteredItems
export type ComboboxRootChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
export type ComboboxRootHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
export type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
export type ComboboxHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
const comboboxPopupClassName = [
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',

View File

@ -14,7 +14,7 @@ import {
DialogViewport,
} from '.'
import { Button } from '../button'
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from '../field'
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from '../field'
import { Form } from '../form'
import { Input } from '../input'
import {
@ -251,12 +251,12 @@ const FormDialogDemo = () => {
className="grid gap-4 pt-5"
onFormSubmit={() => setOpen(false)}
>
<FieldRoot name="name">
<Field name="name">
<FieldLabel>Name</FieldLabel>
<FieldControl required placeholder="Production API" />
<FieldError match="valueMissing">Name is required.</FieldError>
</FieldRoot>
<FieldRoot name="endpoint">
</Field>
<Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription>
@ -271,8 +271,8 @@ const FormDialogDemo = () => {
</FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot>
<FieldRoot
</Field>
<Field
name="apiKey"
validate={(value) => {
if (typeof value === 'string' && value.length > 0 && value.length < 5)
@ -285,7 +285,7 @@ const FormDialogDemo = () => {
<FieldControl required placeholder="sk-..." />
<FieldError match="valueMissing">API key is required.</FieldError>
<FieldError match="customError" />
</FieldRoot>
</Field>
<div className="mt-2 flex items-center justify-end gap-2">
<Button type="button" onClick={() => setOpen(false)}>
Cancel

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import type { DrawerRootSnapPoint } from '.'
import type { DrawerSnapPoint } from '.'
import * as React from 'react'
import {
createDrawerHandle,
@ -293,11 +293,11 @@ export const Positions: Story = {
const snapTopMarginRem = 1
const visibleSnapPointRem = 30
const initialSnapPoint: DrawerRootSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem`
const snapPoints = [initialSnapPoint, 1] satisfies DrawerRootSnapPoint[]
const initialSnapPoint: DrawerSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem`
const snapPoints = [initialSnapPoint, 1] satisfies DrawerSnapPoint[]
function SnapPointsDemo() {
const [snapPoint, setSnapPoint] = React.useState<DrawerRootSnapPoint | null>(initialSnapPoint)
const [snapPoint, setSnapPoint] = React.useState<DrawerSnapPoint | null>(initialSnapPoint)
return (
<Drawer

View File

@ -16,13 +16,13 @@ export const DrawerDescription = BaseDrawer.Description
export const DrawerClose = BaseDrawer.Close
export const createDrawerHandle = BaseDrawer.createHandle
export type DrawerRootProps<Payload = unknown> = BaseDrawer.Root.Props<Payload>
export type DrawerRootActions = BaseDrawer.Root.Actions
export type DrawerRootChangeEventDetails = BaseDrawer.Root.ChangeEventDetails
export type DrawerRootChangeEventReason = BaseDrawer.Root.ChangeEventReason
export type DrawerRootSnapPoint = BaseDrawer.Root.SnapPoint
export type DrawerRootSnapPointChangeEventDetails = BaseDrawer.Root.SnapPointChangeEventDetails
export type DrawerRootSnapPointChangeEventReason = BaseDrawer.Root.SnapPointChangeEventReason
export type DrawerProps<Payload = unknown> = BaseDrawer.Root.Props<Payload>
export type DrawerActions = BaseDrawer.Root.Actions
export type DrawerChangeEventDetails = BaseDrawer.Root.ChangeEventDetails
export type DrawerChangeEventReason = BaseDrawer.Root.ChangeEventReason
export type DrawerSnapPoint = BaseDrawer.Root.SnapPoint
export type DrawerSnapPointChangeEventDetails = BaseDrawer.Root.SnapPointChangeEventDetails
export type DrawerSnapPointChangeEventReason = BaseDrawer.Root.SnapPointChangeEventReason
export type DrawerTriggerProps<Payload = unknown> = BaseDrawer.Trigger.Props<Payload>
export function DrawerBackdrop({

View File

@ -1,15 +1,15 @@
import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox'
import { CheckboxGroup } from '../../checkbox-group'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
import { Fieldset, FieldsetLegend } from '../../fieldset'
import { Form } from '../../form'
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldItem,
FieldLabel,
FieldRoot,
} from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -19,12 +19,12 @@ describe('Field primitives', () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="profile form" onFormSubmit={onFormSubmit}>
<FieldRoot name="email">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<FieldControl type="email" required />
<FieldDescription>Used for account notifications.</FieldDescription>
<FieldError match="valueMissing">Email is required.</FieldError>
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)
@ -55,10 +55,10 @@ describe('Field primitives', () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="settings form" onFormSubmit={onFormSubmit}>
<FieldRoot name="apiKey">
<Field name="apiKey">
<FieldLabel>API key</FieldLabel>
<FieldControl defaultValue="sk-test" required />
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)
@ -71,8 +71,8 @@ describe('Field primitives', () => {
it('should support external invalid state without requiring FieldControl', async () => {
const screen = await render(
<FieldRoot name="features" invalid>
<FieldsetRoot render={<CheckboxGroup value={['search']} />}>
<Field name="features" invalid>
<Fieldset render={<CheckboxGroup value={['search']} />}>
<FieldsetLegend>Features</FieldsetLegend>
<FieldItem>
<FieldLabel className="flex items-center gap-2">
@ -81,8 +81,8 @@ describe('Field primitives', () => {
</FieldLabel>
</FieldItem>
<FieldError match>Choose at least one feature.</FieldError>
</FieldsetRoot>
</FieldRoot>,
</Fieldset>
</Field>,
)
await expect.element(screen.getByRole('group', { name: 'Features' })).toBeInTheDocument()
@ -91,10 +91,10 @@ describe('Field primitives', () => {
it('should expose the read-only state', async () => {
const screen = await render(
<FieldRoot name="token">
<Field name="token">
<FieldLabel>Token</FieldLabel>
<FieldControl readOnly defaultValue="readonly-token" />
</FieldRoot>,
</Field>,
)
await expect.element(screen.getByRole('textbox', { name: 'Token' })).toHaveAttribute('readonly')

View File

@ -1,26 +1,26 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { Button } from '../button'
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from './index'
const meta = {
title: 'Base/Form/Field',
component: FieldRoot,
component: Field,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Field primitives built on Base UI Field. Use FieldRoot with FieldLabel, FieldControl, FieldDescription, and FieldError for one named form field. External form libraries can control invalid, dirty, and touched on FieldRoot.',
component: 'Field primitives built on Base UI Field. Use Field with FieldLabel, FieldControl, FieldDescription, and FieldError for one named form field. External form libraries can control invalid, dirty, and touched on Field.',
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof FieldRoot>
} satisfies Meta<typeof Field>
export default meta
@ -29,13 +29,13 @@ type Story = StoryObj<typeof meta>
export const TextField: Story = {
render: () => (
<form className="grid w-96 gap-4">
<FieldRoot name="endpoint">
<Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription>Used as the base URL for extension requests.</FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button>
</div>
@ -46,24 +46,24 @@ export const TextField: Story = {
export const MultipleFields: Story = {
render: () => (
<form className="grid w-96 gap-4">
<FieldRoot name="name">
<Field name="name">
<FieldLabel>Name</FieldLabel>
<FieldControl required placeholder="Production API" />
<FieldError match="valueMissing">Name is required.</FieldError>
</FieldRoot>
<FieldRoot name="endpoint">
</Field>
<Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription>Used as the base URL for extension requests.</FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot>
<FieldRoot name="apiKey">
</Field>
<Field name="apiKey">
<FieldLabel>API key</FieldLabel>
<FieldControl required placeholder="sk-..." />
<FieldDescription>Stored with the extension configuration.</FieldDescription>
<FieldError match="valueMissing">API key is required.</FieldError>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button>
</div>
@ -73,39 +73,39 @@ export const MultipleFields: Story = {
export const ExternalInvalidState: Story = {
render: () => (
<FieldRoot name="apiKey" invalid className="w-96">
<Field name="apiKey" invalid className="w-96">
<FieldLabel>API key</FieldLabel>
<FieldControl defaultValue="expired-key" />
<FieldError match>API key has expired.</FieldError>
</FieldRoot>
</Field>
),
}
export const Sizes: Story = {
render: () => (
<div className="grid w-96 gap-4">
<FieldRoot name="smallEndpoint">
<Field name="smallEndpoint">
<FieldLabel>Small</FieldLabel>
<FieldControl size="small" placeholder="Small input" />
</FieldRoot>
<FieldRoot name="regularEndpoint">
</Field>
<Field name="regularEndpoint">
<FieldLabel>Regular</FieldLabel>
<FieldControl placeholder="Regular input" />
</FieldRoot>
<FieldRoot name="largeEndpoint">
</Field>
<Field name="largeEndpoint">
<FieldLabel>Large</FieldLabel>
<FieldControl size="large" placeholder="Large input" />
</FieldRoot>
</Field>
</div>
),
}
export const ReadOnly: Story = {
render: () => (
<FieldRoot name="readonlyEndpoint" className="w-96">
<Field name="readonlyEndpoint" className="w-96">
<FieldLabel>Endpoint</FieldLabel>
<FieldControl readOnly defaultValue="https://api.example.com" />
<FieldDescription>This value is managed by the workspace owner.</FieldDescription>
</FieldRoot>
</Field>
),
}

View File

@ -6,18 +6,18 @@ import { Field as BaseField } from '@base-ui/react/field'
import { cn } from '../cn'
import { formLabelClassName, textControlVariants } from '../form-control-shared'
export type FieldRootProps
export type FieldProps
= Omit<BaseFieldNS.Root.Props, 'className'>
& {
className?: string
}
export type FieldRootActions = BaseFieldNS.Root.Actions
export type FieldActions = BaseFieldNS.Root.Actions
export function FieldRoot({
export function Field({
className,
...props
}: FieldRootProps) {
}: FieldProps) {
return (
<BaseField.Root
className={cn('group/field grid min-w-0 gap-1', className)}

View File

@ -1,15 +1,15 @@
import { render } from 'vitest-browser-react'
import {
Fieldset,
FieldsetLegend,
FieldsetRoot,
} from '../index'
describe('Fieldset primitives', () => {
it('should forward className to the fieldset and legend', async () => {
const screen = await render(
<FieldsetRoot className="custom-root">
<Fieldset className="custom-root">
<FieldsetLegend className="custom-legend">Permissions</FieldsetLegend>
</FieldsetRoot>,
</Fieldset>,
)
const legend = screen.getByText('Permissions').element() as HTMLElement

View File

@ -1,25 +1,25 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { Checkbox } from '../checkbox'
import { CheckboxGroup } from '../checkbox-group'
import { FieldItem, FieldLabel, FieldRoot } from '../field'
import { Field, FieldItem, FieldLabel } from '../field'
import {
Fieldset,
FieldsetLegend,
FieldsetRoot,
} from './index'
const meta = {
title: 'Base/Form/Fieldset',
component: FieldsetRoot,
component: Fieldset,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Fieldset primitives built on Base UI Fieldset. Use FieldsetRoot and FieldsetLegend when one field is represented by a group of related controls such as checkbox groups, radio groups, or multi-thumb sliders. Fieldset provides group semantics and labeling; pass interactive state such as disabled and value to the actual group primitive.',
component: 'Fieldset primitives built on Base UI Fieldset. Use Fieldset and FieldsetLegend when one field is represented by a group of related controls such as checkbox groups, radio groups, or multi-thumb sliders. Fieldset provides group semantics and labeling; pass interactive state such as disabled and value to the actual group primitive.',
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof FieldsetRoot>
} satisfies Meta<typeof Fieldset>
export default meta
@ -27,8 +27,8 @@ type Story = StoryObj<typeof meta>
export const CheckboxGroupField: Story = {
render: () => (
<FieldRoot name="scopes" className="w-80">
<FieldsetRoot render={<CheckboxGroup defaultValue={['read']} />}>
<Field name="scopes" className="w-80">
<Fieldset render={<CheckboxGroup defaultValue={['read']} />}>
<FieldsetLegend>Scopes</FieldsetLegend>
<div className="grid gap-2">
<FieldItem>
@ -50,7 +50,7 @@ export const CheckboxGroupField: Story = {
</FieldLabel>
</FieldItem>
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
),
}

View File

@ -4,16 +4,16 @@ import type { Fieldset as BaseFieldsetNS } from '@base-ui/react/fieldset'
import { Fieldset as BaseFieldset } from '@base-ui/react/fieldset'
import { cn } from '../cn'
export type FieldsetRootProps
export type FieldsetProps
= Omit<BaseFieldsetNS.Root.Props, 'className'>
& {
className?: string
}
export function FieldsetRoot({
export function Fieldset({
className,
...props
}: FieldsetRootProps) {
}: FieldsetProps) {
return (
<BaseFieldset.Root
className={cn('m-0 min-w-0 border-0 p-0', className)}

View File

@ -1,5 +1,6 @@
import { render } from 'vitest-browser-react'
import {
FileTree,
FileTreeFile,
FileTreeFolder,
FileTreeFolderPanel,
@ -7,7 +8,6 @@ import {
FileTreeIcon,
FileTreeLabel,
FileTreeList,
FileTreeRoot,
} from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -18,7 +18,7 @@ function TestFileTree({
onPreview?: (itemId: string) => void
}) {
return (
<FileTreeRoot aria-label="Project files">
<FileTree aria-label="Project files">
<FileTreeList>
<FileTreeFolder defaultOpen>
<FileTreeFolderTrigger>
@ -53,7 +53,7 @@ function TestFileTree({
<FileTreeLabel>package.json</FileTreeLabel>
</FileTreeFile>
</FileTreeList>
</FileTreeRoot>
</FileTree>
)
}
@ -101,14 +101,14 @@ describe('FileTree', () => {
it('does not activate disabled file buttons', async () => {
const onPreview = vi.fn()
const screen = await render(
<FileTreeRoot aria-label="Disabled files">
<FileTree aria-label="Disabled files">
<FileTreeList>
<FileTreeFile disabled onClick={() => onPreview('disabled')}>
<FileTreeIcon type="file" />
<FileTreeLabel>disabled.txt</FileTreeLabel>
</FileTreeFile>
</FileTreeList>
</FileTreeRoot>,
</FileTree>,
)
asHTMLElement(screen.getByRole('button', { name: 'disabled.txt' }).element()).click()
@ -121,7 +121,7 @@ describe('FileTree', () => {
it('resolves disabled folder triggers through the collapsible state', async () => {
const onOpenChange = vi.fn()
const screen = await render(
<FileTreeRoot aria-label="Disabled folders">
<FileTree aria-label="Disabled folders">
<FileTreeList>
<FileTreeFolder disabled defaultOpen onOpenChange={onOpenChange}>
<FileTreeFolderTrigger>
@ -136,7 +136,7 @@ describe('FileTree', () => {
</FileTreeFolderPanel>
</FileTreeFolder>
</FileTreeList>
</FileTreeRoot>,
</FileTree>,
)
const trigger = screen.getByRole('button', { name: 'locked' })

View File

@ -3,6 +3,7 @@ import type { FileTreeIconType } from '.'
import * as React from 'react'
import { expect } from 'storybook/test'
import {
FileTree,
FileTreeBadge,
FileTreeFile,
FileTreeFolder,
@ -12,12 +13,11 @@ import {
FileTreeLabel,
FileTreeList,
FileTreeMeta,
FileTreeRoot,
} from '.'
const meta = {
title: 'Base/UI/FileTree',
component: FileTreeRoot,
component: FileTree,
parameters: {
layout: 'centered',
docs: {
@ -28,7 +28,7 @@ const meta = {
},
},
tags: ['autodocs'],
} satisfies Meta<typeof FileTreeRoot>
} satisfies Meta<typeof FileTree>
export default meta
type Story = StoryObj<typeof meta>
@ -121,7 +121,7 @@ function ComposedFileTree() {
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('button')
return (
<FileTreeRoot
<FileTree
aria-label="Project files"
className="w-80 rounded-lg border border-divider-subtle bg-background-default-subtle"
>
@ -172,7 +172,7 @@ function ComposedFileTree() {
<FileTreeMeta>root</FileTreeMeta>
</FileTreeFile>
</FileTreeList>
</FileTreeRoot>
</FileTree>
)
}
@ -180,7 +180,7 @@ function DataDrivenFileTree() {
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('app-components-file-tree')
return (
<FileTreeRoot
<FileTree
aria-label="Data-driven project files"
className="w-80 rounded-lg border border-divider-subtle bg-background-default-subtle"
>
@ -191,7 +191,7 @@ function DataDrivenFileTree() {
onPreview={setSelectedItemId}
/>
</FileTreeList>
</FileTreeRoot>
</FileTree>
)
}
@ -211,7 +211,7 @@ function IconGallery() {
] as const
return (
<FileTreeRoot aria-label="File icon examples" className="w-64 rounded-lg border border-divider-subtle bg-background-default-subtle">
<FileTree aria-label="File icon examples" className="w-64 rounded-lg border border-divider-subtle bg-background-default-subtle">
<FileTreeList>
{iconTypes.map(type => (
type === 'folder'
@ -232,7 +232,7 @@ function IconGallery() {
)
))}
</FileTreeList>
</FileTreeRoot>
</FileTree>
)
}
@ -246,11 +246,11 @@ function StateFrame({
return (
<div className="w-80 min-w-0 space-y-1">
<div className="system-xs-medium-uppercase text-text-tertiary">{label}</div>
<FileTreeRoot aria-label={label} className="rounded-lg border border-divider-subtle bg-background-default-subtle">
<FileTree aria-label={label} className="rounded-lg border border-divider-subtle bg-background-default-subtle">
<FileTreeList>
{children}
</FileTreeList>
</FileTreeRoot>
</FileTree>
</div>
)
}

View File

@ -45,14 +45,14 @@ function fileTreeRowClassName({
)
}
export type FileTreeRootProps = useRender.ComponentProps<'section'>
export type FileTreeProps = useRender.ComponentProps<'section'>
export function FileTreeRoot({
export function FileTree({
render,
className,
children,
...props
}: FileTreeRootProps) {
}: FileTreeProps) {
const defaultProps: useRender.ElementProps<'section'> = {
className: cn('flex min-w-0 flex-col gap-px p-1', className),
children: (

View File

@ -1,5 +1,5 @@
import { render } from 'vitest-browser-react'
import { FieldControl, FieldLabel, FieldRoot } from '../../field'
import { Field, FieldControl, FieldLabel } from '../../field'
import { Form } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -8,10 +8,10 @@ describe('Form primitive', () => {
it('should render a native named form and merge custom class names', async () => {
const screen = await render(
<Form aria-label="profile form" className="custom-form">
<FieldRoot name="name">
<Field name="name">
<FieldLabel>Name</FieldLabel>
<FieldControl defaultValue="Ada" />
</FieldRoot>
</Field>
</Form>,
)
@ -22,10 +22,10 @@ describe('Form primitive', () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="api form" onFormSubmit={onFormSubmit}>
<FieldRoot name="endpoint">
<Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel>
<FieldControl defaultValue="https://api.example.com" />
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)
@ -41,10 +41,10 @@ describe('Form primitive', () => {
it('should expose externally supplied errors through FieldError consumers', async () => {
const screen = await render(
<Form aria-label="server form" errors={{ token: 'Token has expired.' }}>
<FieldRoot name="token">
<Field name="token">
<FieldLabel>Token</FieldLabel>
<FieldControl defaultValue="expired" />
</FieldRoot>
</Field>
</Form>,
)

View File

@ -3,14 +3,14 @@ import { Button } from '../button'
import { Checkbox } from '../checkbox'
import { CheckboxGroup } from '../checkbox-group'
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldItem,
FieldLabel,
FieldRoot,
} from '../field'
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
import { Fieldset, FieldsetLegend } from '../fieldset'
import { Form } from './index'
const meta = {
@ -28,22 +28,22 @@ type Story = StoryObj<typeof meta>
export const Basic: Story = {
render: () => (
<Form className="grid w-96 gap-4" onFormSubmit={() => undefined}>
<FieldRoot name="name">
<Field name="name">
<FieldLabel>Name</FieldLabel>
<FieldControl required placeholder="Enter a name" />
<FieldError match="valueMissing">Name is required.</FieldError>
</FieldRoot>
</Field>
<FieldRoot name="email">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<FieldControl type="email" required placeholder="name@example.com" />
<FieldDescription>Used for account notifications.</FieldDescription>
<FieldError match="valueMissing">Email is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
</FieldRoot>
</Field>
<FieldRoot name="features">
<FieldsetRoot render={<CheckboxGroup defaultValue={['search']} />}>
<Field name="features">
<Fieldset render={<CheckboxGroup defaultValue={['search']} />}>
<FieldsetLegend>Features</FieldsetLegend>
<div className="grid gap-2">
<FieldItem>
@ -59,8 +59,8 @@ export const Basic: Story = {
</FieldLabel>
</FieldItem>
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button>

View File

@ -1,5 +1,5 @@
import { render } from 'vitest-browser-react'
import { FieldError, FieldLabel, FieldRoot } from '../../field'
import { Field, FieldError, FieldLabel } from '../../field'
import { Form } from '../../form'
import { Input } from '../index'
@ -19,12 +19,12 @@ describe('Input', () => {
await expect.element(input).toHaveValue('Dify')
})
it('should use FieldRoot invalid state', async () => {
it('should use Field invalid state', async () => {
const screen = await render(
<FieldRoot name="repositoryUrl" invalid>
<Field name="repositoryUrl" invalid>
<FieldLabel>Repository URL</FieldLabel>
<Input defaultValue="github.com/langgenius" />
</FieldRoot>,
</Field>,
)
const input = screen.getByRole('textbox', { name: 'Repository URL' })
@ -33,15 +33,15 @@ describe('Input', () => {
await expect.element(input).toHaveAttribute('data-invalid')
})
it('should integrate with FieldRoot and Base UI Form validation', async () => {
it('should integrate with Field and Base UI Form validation', async () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="account form" onFormSubmit={onFormSubmit}>
<FieldRoot name="email">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input type="email" required />
<FieldError match="valueMissing">Email is required.</FieldError>
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)

View File

@ -1,10 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { Button } from '../button'
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../field'
import { Form } from '../form'
import { Input } from './index'
@ -16,7 +16,7 @@ const meta = {
layout: 'centered',
docs: {
description: {
component: 'A standalone text input primitive built on Base UI Input. Use it for labelled text boxes outside FieldControl, and keep FieldControl for full FieldRoot form composition.',
component: 'A standalone text input primitive built on Base UI Input. Use it for labelled text boxes outside FieldControl, and keep FieldControl for full Field form composition.',
},
},
},
@ -74,7 +74,7 @@ export const States: Story = {
<Input id="filled-state" name="filledState" defaultValue="Customer knowledge base" autoComplete="off" />
</div>
<div className="grid gap-1">
<FieldRoot name="repositoryUrl" invalid>
<Field name="repositoryUrl" invalid>
<FieldLabel>Invalid</FieldLabel>
<Input
id="invalid-state"
@ -85,7 +85,7 @@ export const States: Story = {
spellCheck={false}
/>
<FieldError match>Enter a full URL including https://.</FieldError>
</FieldRoot>
</Field>
</div>
<div className="grid gap-1">
<label className="text-text-secondary system-sm-medium" htmlFor="disabled-state">Disabled</label>
@ -102,20 +102,20 @@ export const States: Story = {
export const WithField: Story = {
render: () => (
<Form aria-label="Account form" className="grid w-80 gap-4" onFormSubmit={() => undefined}>
<FieldRoot name="email">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input type="email" inputMode="email" required autoComplete="email" placeholder="name@example.com…" spellCheck={false} />
<FieldDescription>Used for account notifications.</FieldDescription>
<FieldError match="valueMissing">Email is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
</FieldRoot>
<FieldRoot name="repositoryUrl">
</Field>
<Field name="repositoryUrl">
<FieldLabel>Repository URL</FieldLabel>
<Input type="url" inputMode="url" required autoComplete="off" placeholder="https://github.com/langgenius/dify…" spellCheck={false} />
<FieldDescription>Use the full GitHub repository URL.</FieldDescription>
<FieldError match="valueMissing">Repository URL is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button>
</div>

View File

@ -1,8 +1,8 @@
import { render } from 'vitest-browser-react'
import {
Meter,
MeterIndicator,
MeterLabel,
MeterRoot,
MeterTrack,
MeterValue,
} from '../index'
@ -10,11 +10,11 @@ import {
describe('Meter compound primitives', () => {
it('exposes role="meter" with ARIA value metadata', async () => {
const screen = await render(
<MeterRoot value={40} aria-label="Quota">
<Meter value={40} aria-label="Quota">
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</MeterRoot>,
</Meter>,
)
const meter = screen.getByLabelText('Quota')
@ -26,11 +26,11 @@ describe('Meter compound primitives', () => {
it('respects custom min and max', async () => {
const screen = await render(
<MeterRoot value={3} min={1} max={5} aria-label="Quota">
<Meter value={3} min={1} max={5} aria-label="Quota">
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</MeterRoot>,
</Meter>,
)
const meter = screen.getByLabelText('Quota')
@ -41,11 +41,11 @@ describe('Meter compound primitives', () => {
it('sets indicator width from value/min/max', async () => {
const screen = await render(
<MeterRoot value={42} aria-label="Quota">
<Meter value={42} aria-label="Quota">
<MeterTrack>
<MeterIndicator data-testid="indicator" />
</MeterTrack>
</MeterRoot>,
</Meter>,
)
const indicator = screen.getByTestId('indicator').element() as HTMLElement
@ -54,11 +54,11 @@ describe('Meter compound primitives', () => {
it('forwards className to MeterTrack', async () => {
const screen = await render(
<MeterRoot value={10} aria-label="Quota">
<Meter value={10} aria-label="Quota">
<MeterTrack className="custom-track" data-testid="track">
<MeterIndicator />
</MeterTrack>
</MeterRoot>,
</Meter>,
)
const track = screen.getByTestId('track').element() as HTMLElement
@ -67,7 +67,7 @@ describe('Meter compound primitives', () => {
it('renders MeterLabel and MeterValue inside a compound layout', async () => {
const screen = await render(
<MeterRoot
<Meter
value={0.42}
min={0}
max={1}
@ -79,7 +79,7 @@ describe('Meter compound primitives', () => {
<MeterIndicator />
</MeterTrack>
<MeterValue />
</MeterRoot>,
</Meter>,
)
await expect.element(screen.getByText('Score')).toBeInTheDocument()

View File

@ -1,23 +1,23 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { MeterIndicator, MeterLabel, MeterRoot, MeterTrack, MeterValue } from '.'
import { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue } from '.'
const meta = {
title: 'Base/UI/Meter',
component: MeterRoot,
component: Meter,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'A graphical display of a numeric value within a known range. '
+ 'Use the compound primitives (`MeterRoot / MeterTrack / MeterIndicator / '
+ 'Use the compound primitives (`Meter / MeterTrack / MeterIndicator / '
+ 'MeterValue / MeterLabel`) for quota, capacity, or score indicators; do '
+ 'not use for task-completion progress.',
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof MeterRoot>
} satisfies Meta<typeof Meter>
export default meta
@ -30,11 +30,11 @@ export const Default: Story = {
},
render: args => (
<div className="w-[320px]">
<MeterRoot {...args}>
<Meter {...args}>
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
),
}
@ -46,11 +46,11 @@ export const Warning: Story = {
},
render: args => (
<div className="w-[320px]">
<MeterRoot {...args}>
<Meter {...args}>
<MeterTrack>
<MeterIndicator tone="warning" />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
),
}
@ -62,11 +62,11 @@ export const Error: Story = {
},
render: args => (
<div className="w-[320px]">
<MeterRoot {...args}>
<Meter {...args}>
<MeterTrack>
<MeterIndicator tone="error" />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
),
}
@ -78,7 +78,7 @@ export const ComposedWithLabelAndValue: Story = {
},
render: args => (
<div className="w-[320px] space-y-2 rounded-xl bg-components-panel-bg p-4">
<MeterRoot {...args}>
<Meter {...args}>
<div className="flex items-center justify-between">
<MeterLabel>Storage</MeterLabel>
<MeterValue />
@ -86,7 +86,7 @@ export const ComposedWithLabelAndValue: Story = {
<MeterTrack className="mt-2">
<MeterIndicator tone="warning" />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
),
}
@ -101,7 +101,7 @@ export const PercentFormatted: Story = {
},
render: args => (
<div className="w-[320px] space-y-2">
<MeterRoot {...args}>
<Meter {...args}>
<div className="flex items-center justify-between">
<MeterLabel>Score</MeterLabel>
<MeterValue />
@ -109,7 +109,7 @@ export const PercentFormatted: Story = {
<MeterTrack className="mt-2">
<MeterIndicator />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
),
}

View File

@ -15,8 +15,8 @@ import { Meter as BaseMeter } from '@base-ui/react/meter'
import { cva } from 'class-variance-authority'
import { cn } from '../cn'
export const MeterRoot = BaseMeter.Root
export type MeterRootProps = BaseMeter.Root.Props
export const Meter = BaseMeter.Root
export type MeterProps = BaseMeter.Root.Props
const meterTrackClassName
= 'relative block h-1 w-full overflow-hidden rounded-md bg-components-progress-bar-bg'

View File

@ -8,8 +8,8 @@ import type {
import * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Field,
FieldLabel,
FieldRoot,
} from '../../field'
import {
NumberField,
@ -79,14 +79,14 @@ describe('NumberField wrapper', () => {
it('should surface field invalid state on the visual group', async () => {
const screen = await render(
<FieldRoot name="amount" invalid>
<Field name="amount" invalid>
<FieldLabel>Amount</FieldLabel>
<NumberField defaultValue={8}>
<NumberFieldGroup data-testid="group">
<NumberFieldInput />
</NumberFieldGroup>
</NumberField>
</FieldRoot>,
</Field>,
)
await expect.element(screen.getByTestId('group')).toHaveAttribute('data-invalid')

View File

@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import { Button } from '../button'
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../field'
import { Form } from '../form'
import {
@ -25,7 +25,7 @@ const meta = {
layout: 'centered',
docs: {
description: {
component: 'Compound numeric input built on Base UI NumberField. Use it with FieldRoot for labelled, described, and validated form fields.',
component: 'Compound numeric input built on Base UI NumberField. Use it with Field for labelled, described, and validated form fields.',
},
},
},
@ -140,7 +140,7 @@ export const Sizes: Story = {
export const States: Story = {
render: () => (
<div className="grid w-80 gap-3">
<FieldRoot name="placeholderState">
<Field name="placeholderState">
<FieldLabel>Placeholder</FieldLabel>
<NumberField min={0} max={100}>
<NumberFieldGroup>
@ -152,8 +152,8 @@ export const States: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
<FieldRoot name="filledState">
</Field>
<Field name="filledState">
<FieldLabel>Filled</FieldLabel>
<NumberField defaultValue={85} min={0} max={100}>
<NumberFieldGroup>
@ -165,8 +165,8 @@ export const States: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
<FieldRoot name="invalidState" invalid>
</Field>
<Field name="invalidState" invalid>
<FieldLabel>Invalid</FieldLabel>
<NumberField defaultValue={120} min={0} max={100}>
<NumberFieldGroup>
@ -179,8 +179,8 @@ export const States: Story = {
</NumberFieldGroup>
</NumberField>
<FieldError match>Use a value from 0 to 100.</FieldError>
</FieldRoot>
<FieldRoot name="disabledState">
</Field>
<Field name="disabledState">
<FieldLabel>Disabled</FieldLabel>
<NumberField defaultValue={5} min={0} max={10} disabled>
<NumberFieldGroup>
@ -191,8 +191,8 @@ export const States: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
<FieldRoot name="readonlyState">
</Field>
<Field name="readonlyState">
<FieldLabel>Read-only</FieldLabel>
<NumberField defaultValue={92} min={0} max={100} readOnly>
<NumberFieldGroup>
@ -204,7 +204,7 @@ export const States: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
</Field>
</div>
),
}
@ -213,7 +213,7 @@ function ControlledDemo() {
const [value, setValue] = React.useState<number | null>(0.82)
return (
<FieldRoot name="controlledThreshold">
<Field name="controlledThreshold">
<FieldLabel>Score threshold</FieldLabel>
<NumberField
value={value}
@ -238,7 +238,7 @@ function ControlledDemo() {
{' '}
{value === null ? 'Empty' : value.toFixed(2)}
</FieldDescription>
</FieldRoot>
</Field>
)
}
@ -261,7 +261,7 @@ function FormDemo() {
setSavedValue(String(values.topK ?? ''))
}}
>
<FieldRoot name="topK">
<Field name="topK">
<FieldLabel>Top K</FieldLabel>
<NumberField required defaultValue={3} min={1} max={10} step={1}>
<NumberFieldGroup>
@ -276,7 +276,7 @@ function FormDemo() {
<FieldError match="valueMissing">Top K is required.</FieldError>
<FieldError match="rangeUnderflow">Use at least 1.</FieldError>
<FieldError match="rangeOverflow">Use 10 or fewer.</FieldError>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button>
</div>
@ -298,7 +298,7 @@ export const WithField: Story = {
export const Formatting: Story = {
render: () => (
<div className="grid w-80 gap-3">
<FieldRoot name="currencyBudget">
<Field name="currencyBudget">
<FieldLabel>Budget</FieldLabel>
<NumberField
defaultValue={1200}
@ -318,8 +318,8 @@ export const Formatting: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
<FieldRoot name="temperature">
</Field>
<Field name="temperature">
<FieldLabel>Temperature</FieldLabel>
<NumberField
defaultValue={0.7}
@ -338,7 +338,7 @@ export const Formatting: Story = {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
</Field>
</div>
),
}

View File

@ -8,7 +8,7 @@ import { cn } from '../cn'
import { textControlCompoundFocusClassName } from '../form-control-shared'
export const NumberField = BaseNumberField.Root
export type NumberFieldRootProps = BaseNumberField.Root.Props
export type NumberFieldProps = BaseNumberField.Root.Props
export const numberFieldGroupVariants = cva(
[

View File

@ -1,8 +1,8 @@
import type { RadioGroupProps } from '../index'
import * as React from 'react'
import { render } from 'vitest-browser-react'
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
import { Field, FieldItem, FieldLabel } from '../../field'
import { Fieldset, FieldsetLegend } from '../../fieldset'
import { Radio, RadioControl, RadioGroup, RadioItem, RadioSkeleton } from '../index'
const clickElement = (element: HTMLElement | SVGElement) => {
@ -22,12 +22,12 @@ function TestRadioGroup<Value = string>({
...props
}: TestRadioGroupProps<Value>) {
return (
<FieldRoot name={name}>
<FieldsetRoot render={<RadioGroup<Value> {...props} />}>
<Field name={name}>
<Fieldset render={<RadioGroup<Value> {...props} />}>
<FieldsetLegend>{label}</FieldsetLegend>
{children}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}

View File

@ -8,12 +8,12 @@ import {
RadioSkeleton,
} from '.'
import {
Field,
FieldDescription,
FieldItem,
FieldLabel,
FieldRoot,
} from '../field'
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
import { Fieldset, FieldsetLegend } from '../fieldset'
const meta = {
title: 'Base/Form/Radio',
@ -36,8 +36,8 @@ function StandardFormRowsDemo() {
const [value, setValue] = React.useState('vector')
return (
<FieldRoot name="retrievalIndex" className="w-80">
<FieldsetRoot
<Field name="retrievalIndex" className="w-80">
<Fieldset
render={(
<RadioGroup value={value} onValueChange={setValue} className="flex-col items-start gap-3" />
)}
@ -55,8 +55,8 @@ function StandardFormRowsDemo() {
</FieldLabel>
</FieldItem>
))}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
@ -75,8 +75,8 @@ function BooleanInlineDemo() {
const [value, setValue] = React.useState(true)
return (
<FieldRoot name="streaming" className="w-80">
<FieldsetRoot
<Field name="streaming" className="w-80">
<Fieldset
render={(
<RadioGroup<boolean> value={value} onValueChange={setValue} className="gap-3" />
)}
@ -96,8 +96,8 @@ function BooleanInlineDemo() {
</FieldLabel>
</FieldItem>
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
@ -135,8 +135,8 @@ function OptionCardsDemo() {
}>
return (
<FieldRoot name="promptMode" className="w-100">
<FieldsetRoot
<Field name="promptMode" className="w-100">
<Fieldset
render={(
<RadioGroup<PromptMode> value={value} onValueChange={setValue} className="flex-col items-stretch gap-3" />
)}
@ -164,8 +164,8 @@ function OptionCardsDemo() {
</RadioItem>
</FieldItem>
))}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
@ -189,11 +189,11 @@ function DynamicFormFieldDemo() {
const [selected, setSelected] = React.useState('automatic')
return (
<FieldRoot name="generation_mode" className="flex w-80 flex-col gap-2">
<Field name="generation_mode" className="flex w-80 flex-col gap-2">
<FieldDescription className="body-xs-regular text-text-tertiary">
This mirrors Dify dynamic form fields where radio options are controlled by schema and persisted as a single value.
</FieldDescription>
<FieldsetRoot
<Fieldset
render={(
<RadioGroup
value={selected}
@ -213,8 +213,8 @@ function DynamicFormFieldDemo() {
</FieldLabel>
</FieldItem>
))}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
@ -232,8 +232,8 @@ export const DynamicFormField: Story = {
export const StateMatrix: Story = {
render: () => (
<div className="flex flex-col gap-3">
<FieldRoot name="radioStates">
<FieldsetRoot render={<RadioGroup defaultValue="checked" className="flex-col items-start gap-3" />}>
<Field name="radioStates">
<Fieldset render={<RadioGroup defaultValue="checked" className="flex-col items-start gap-3" />}>
<FieldsetLegend>Interactive radio states</FieldsetLegend>
<FieldItem>
<FieldLabel className="flex items-center gap-2 system-sm-medium text-text-secondary">
@ -247,10 +247,10 @@ export const StateMatrix: Story = {
Checked
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
<FieldRoot name="disabledRadioStates">
<FieldsetRoot render={<RadioGroup defaultValue="disabled-checked" disabled className="flex-col items-start gap-3" />}>
</Fieldset>
</Field>
<Field name="disabledRadioStates">
<Fieldset render={<RadioGroup defaultValue="disabled-checked" disabled className="flex-col items-start gap-3" />}>
<FieldsetLegend>Disabled radio states</FieldsetLegend>
<FieldItem>
<FieldLabel className="flex items-center gap-2 system-sm-medium text-text-secondary">
@ -264,8 +264,8 @@ export const StateMatrix: Story = {
Disabled checked
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
<div className="flex items-center gap-2 system-sm-medium text-text-secondary">
<RadioSkeleton aria-hidden="true" />
Skeleton

View File

@ -15,7 +15,7 @@ import {
SelectValue,
} from '.'
import { Button } from '../button'
import { FieldDescription, FieldRoot } from '../field'
import { Field, FieldDescription } from '../field'
import { Form } from '../form'
const triggerWidth = 'w-64'
@ -330,7 +330,7 @@ export const Controlled: Story = {
export const InForm: Story = {
render: () => (
<Form aria-label="Timezone form" className="grid w-72 gap-3" onFormSubmit={() => undefined}>
<FieldRoot name="timezone">
<Field name="timezone">
<Select name="timezone" defaultValue="utc">
<SelectLabel>Timezone</SelectLabel>
<SelectTrigger>
@ -352,7 +352,7 @@ export const InForm: Story = {
</SelectContent>
</Select>
<FieldDescription>Used to schedule workflow runs.</FieldDescription>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button>
</div>

View File

@ -16,13 +16,13 @@ import { parsePlacement } from '../placement'
export type { Placement }
export type SelectRootProps<
export type SelectProps<
Value,
Multiple extends boolean | undefined = false,
> = BaseSelect.Root.Props<Value, Multiple>
export function Select<Value, Multiple extends boolean | undefined = false>(
props: SelectRootProps<Value, Multiple>,
props: SelectProps<Value, Multiple>,
): React.JSX.Element {
return <BaseSelect.Root {...props} />
}

View File

@ -3,9 +3,9 @@ import * as React from 'react'
import { expect } from 'storybook/test'
import { Switch, SwitchSkeleton } from '.'
import {
Field,
FieldDescription,
FieldLabel,
FieldRoot,
} from '../field'
const meta = {
@ -55,7 +55,7 @@ const SwitchDemo = (args: SwitchDemoProps) => {
const [enabled, setEnabled] = React.useState(args.checked ?? false)
return (
<FieldRoot name="autoRetry" className="w-72">
<Field name="autoRetry" className="w-72">
<FieldLabel className="flex items-center justify-between gap-3">
<span>Enable auto retry</span>
<Switch
@ -67,7 +67,7 @@ const SwitchDemo = (args: SwitchDemoProps) => {
<FieldDescription>
{enabled ? 'Failures will retry automatically.' : 'Failures require manual retry.'}
</FieldDescription>
</FieldRoot>
</Field>
)
}
@ -187,30 +187,30 @@ const SizeComparisonDemo = () => {
return (
<div className="flex flex-col items-center space-y-4">
<FieldRoot name="extraSmallSwitch">
<Field name="extraSmallSwitch">
<FieldLabel className="flex items-center gap-3">
<Switch size="xs" checked={states.xs} onCheckedChange={v => setStates({ ...states, xs: v })} />
Extra Small (xs) - 14x10
</FieldLabel>
</FieldRoot>
<FieldRoot name="smallSwitch">
</Field>
<Field name="smallSwitch">
<FieldLabel className="flex items-center gap-3">
<Switch size="sm" checked={states.sm} onCheckedChange={v => setStates({ ...states, sm: v })} />
Small (sm) - 20x12
</FieldLabel>
</FieldRoot>
<FieldRoot name="regularSwitch">
</Field>
<Field name="regularSwitch">
<FieldLabel className="flex items-center gap-3">
<Switch size="md" checked={states.md} onCheckedChange={v => setStates({ ...states, md: v })} />
Regular (md) - 28x16
</FieldLabel>
</FieldRoot>
<FieldRoot name="largeSwitch">
</Field>
<Field name="largeSwitch">
<FieldLabel className="flex items-center gap-3">
<Switch size="lg" checked={states.lg} onCheckedChange={v => setStates({ ...states, lg: v })} />
Large (lg) - 36x20
</FieldLabel>
</FieldRoot>
</Field>
</div>
)
}
@ -231,42 +231,42 @@ const LoadingDemo = () => {
{loading ? 'Stop Loading' : 'Start Loading'}
</button>
<div className="space-y-3">
<FieldRoot name="largeUncheckedLoading">
<Field name="largeUncheckedLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="lg" checked={false} loading={loading} />
Large unchecked
</FieldLabel>
</FieldRoot>
<FieldRoot name="largeCheckedLoading">
</Field>
<Field name="largeCheckedLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="lg" checked={true} loading={loading} />
Large checked
</FieldLabel>
</FieldRoot>
<FieldRoot name="regularUncheckedLoading">
</Field>
<Field name="regularUncheckedLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="md" checked={false} loading={loading} />
Regular unchecked
</FieldLabel>
</FieldRoot>
<FieldRoot name="regularCheckedLoading">
</Field>
<Field name="regularCheckedLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="md" checked={true} loading={loading} />
Regular checked
</FieldLabel>
</FieldRoot>
<FieldRoot name="smallLoading">
</Field>
<Field name="smallLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="sm" checked={false} loading={loading} />
Small
</FieldLabel>
</FieldRoot>
<FieldRoot name="extraSmallLoading">
</Field>
<Field name="extraSmallLoading">
<FieldLabel className="flex items-center gap-3">
<Switch size="xs" checked={false} loading={loading} />
Extra Small
</FieldLabel>
</FieldRoot>
</Field>
</div>
</div>
)
@ -335,7 +335,7 @@ const MutationLoadingDemo = () => {
return (
<div className="grid w-90 gap-3 rounded-lg border border-components-panel-border bg-components-panel-bg p-4 shadow-sm">
<FieldRoot name="autoRetry">
<Field name="autoRetry">
<FieldLabel className="flex items-center justify-between gap-4">
<span className="system-sm-medium text-text-secondary">Enable auto retry</span>
<Switch
@ -346,7 +346,7 @@ const MutationLoadingDemo = () => {
/>
</FieldLabel>
<FieldDescription>Retry failed workflow runs without manual intervention.</FieldDescription>
</FieldRoot>
</Field>
<span className="text-xs text-text-tertiary" aria-live="polite">
{statusText}

View File

@ -1,10 +1,10 @@
import type * as React from 'react'
import { render } from 'vitest-browser-react'
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../../field'
import { Form } from '../../form'
import { Textarea } from '../index'
@ -20,11 +20,11 @@ const setTextareaValue = (element: HTMLElement | SVGElement, value: string) => {
describe('Textarea', () => {
it('should render a labelled textarea through Base UI Field.Control', async () => {
const screen = await render(
<FieldRoot name="description">
<Field name="description">
<FieldLabel>Description</FieldLabel>
<Textarea defaultValue="A workspace for support automation." />
<FieldDescription>Shown to workspace members.</FieldDescription>
</FieldRoot>,
</Field>,
)
const textarea = screen.getByRole('textbox', { name: 'Description' })
@ -73,12 +73,12 @@ describe('Textarea', () => {
const onFormSubmit = vi.fn()
const screen = await render(
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
<FieldRoot name="summary">
<Field name="summary">
<FieldLabel>Summary</FieldLabel>
<Textarea required minLength={10} />
<FieldError match="valueMissing">Summary is required.</FieldError>
<FieldError match="tooShort">Summary is too short.</FieldError>
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)
@ -94,12 +94,12 @@ describe('Textarea', () => {
await screen.rerender(
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
<FieldRoot name="summary">
<Field name="summary">
<FieldLabel>Summary</FieldLabel>
<Textarea key="valid-summary" required minLength={10} defaultValue="Long enough summary" />
<FieldError match="valueMissing">Summary is required.</FieldError>
<FieldError match="tooShort">Summary is too short.</FieldError>
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)
@ -129,7 +129,7 @@ describe('Textarea', () => {
})
const screen = await render(
<Form aria-label="profile form" onFormSubmit={onFormSubmit}>
<FieldRoot name="profileSummary">
<Field name="profileSummary">
<FieldLabel>Profile summary</FieldLabel>
<Textarea
id="profile-summary"
@ -141,11 +141,11 @@ describe('Textarea', () => {
maxLength={80}
onBlur={onBlur}
/>
</FieldRoot>
<FieldRoot disabled>
</Field>
<Field disabled>
<FieldLabel>Disabled note</FieldLabel>
<Textarea name="disabledNote" defaultValue="Disabled value" />
</FieldRoot>
</Field>
<button type="submit">Save</button>
</Form>,
)

View File

@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import { Button } from '../button'
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
FieldRoot,
} from '../field'
import { Form } from '../form'
import { Textarea } from './index'
@ -17,7 +17,7 @@ const meta = {
layout: 'centered',
docs: {
description: {
component: 'Multiline text control built on Base UI Field.Control. Use it with FieldRoot for labelled, described, and validated form fields.',
component: 'Multiline text control built on Base UI Field.Control. Use it with Field for labelled, described, and validated form fields.',
},
},
},
@ -65,27 +65,27 @@ export const Sizes: Story = {
export const States: Story = {
render: () => (
<div className="grid w-80 gap-3">
<FieldRoot name="placeholderState">
<Field name="placeholderState">
<FieldLabel>Placeholder</FieldLabel>
<Textarea placeholder="Add a description..." rows={3} />
</FieldRoot>
<FieldRoot name="filledState">
</Field>
<Field name="filledState">
<FieldLabel>Filled</FieldLabel>
<Textarea defaultValue="Use this dataset for support articles and product FAQs." rows={3} />
</FieldRoot>
<FieldRoot name="invalidState" invalid>
</Field>
<Field name="invalidState" invalid>
<FieldLabel>Invalid</FieldLabel>
<Textarea defaultValue="Too short" rows={3} />
<FieldError match>Use at least 20 characters.</FieldError>
</FieldRoot>
<FieldRoot name="disabledState">
</Field>
<Field name="disabledState">
<FieldLabel>Disabled</FieldLabel>
<Textarea disabled placeholder="Editing is unavailable..." rows={3} />
</FieldRoot>
<FieldRoot name="readonlyState">
</Field>
<Field name="readonlyState">
<FieldLabel>Read-only</FieldLabel>
<Textarea readOnly defaultValue="Generated from the published workflow configuration." rows={3} />
</FieldRoot>
</Field>
</div>
),
}
@ -101,7 +101,7 @@ const FormDemo = () => {
setSavedDescription(String(values.description ?? ''))
}}
>
<FieldRoot name="description">
<Field name="description">
<FieldLabel>Description</FieldLabel>
<Textarea
required
@ -114,7 +114,7 @@ const FormDemo = () => {
<FieldDescription>Shown to teammates when they choose a knowledge source.</FieldDescription>
<FieldError match="valueMissing">Description is required.</FieldError>
<FieldError match="tooShort">Use at least 20 characters.</FieldError>
</FieldRoot>
</Field>
<div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button>
</div>
@ -137,7 +137,7 @@ const ControlledDemo = () => {
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="prompt">
<Field name="prompt">
<FieldLabel>Prompt</FieldLabel>
<Textarea
value={value}
@ -146,7 +146,7 @@ const ControlledDemo = () => {
className="resize-y"
/>
<FieldDescription>The saved value is updated from the controlled state.</FieldDescription>
</FieldRoot>
</Field>
)
}
@ -163,7 +163,7 @@ const CharacterCounterDemo = () => {
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return (
<FieldRoot name="limitedPrompt">
<Field name="limitedPrompt">
<FieldLabel>Prompt</FieldLabel>
<div className="relative">
<Textarea
@ -180,7 +180,7 @@ const CharacterCounterDemo = () => {
</div>
</div>
<FieldDescription>Character counters are composed at the usage site when the workflow needs one.</FieldDescription>
</FieldRoot>
</Field>
)
}

View File

@ -1,5 +1,5 @@
'use client'
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { Button } from '@langgenius/dify-ui/button'
@ -71,7 +71,7 @@ export default function AddMemberOrGroupDialog() {
setOpen(nextOpen)
}
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press')
setKeyword(inputValue)
}

View File

@ -2,7 +2,7 @@ import type { FC } from 'react'
import type { VersionHistory } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
@ -85,7 +85,7 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
</button>
</div>
<div className="flex flex-col gap-y-4 px-6 py-3">
<FieldRoot name="title" invalid={titleError} className="gap-y-1">
<Field name="title" invalid={titleError} className="gap-y-1">
<FieldLabel className="flex h-6 items-center py-0 system-sm-semibold text-text-secondary">
{t($ => $['versionHistory.editField.title'], { ns: 'workflow' })}
</FieldLabel>
@ -94,8 +94,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
placeholder={`${t($ => $['versionHistory.nameThisVersion'], { ns: 'workflow' })}${t($ => $['panel.optional'], { ns: 'workflow' })}`}
onValueChange={setTitle}
/>
</FieldRoot>
<FieldRoot name="releaseNotes" invalid={releaseNotesError} className="gap-y-1">
</Field>
<Field name="releaseNotes" invalid={releaseNotesError} className="gap-y-1">
<FieldLabel className="flex h-6 items-center py-0 system-sm-semibold text-text-secondary">
{t($ => $['versionHistory.editField.releaseNotes'], { ns: 'workflow' })}
</FieldLabel>
@ -104,7 +104,7 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
placeholder={`${t($ => $['versionHistory.releaseNotesPlaceholder'], { ns: 'workflow' })}${t($ => $['panel.optional'], { ns: 'workflow' })}`}
onValueChange={handleDescriptionChange}
/>
</FieldRoot>
</Field>
</div>
<div className="flex justify-end p-6 pt-5">
<div className="flex items-center gap-x-3">

View File

@ -2,7 +2,7 @@
import type { FC } from 'react'
import type { AgentConfig } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import { RiCloseLine } from '@remixicon/react'
import { useClickAway } from 'ahooks'
@ -101,7 +101,7 @@ const AgentSetting: FC<Props> = ({
name={maximumIterationsLabel}
description={t($ => $['agent.setting.maximumIterations.description'], { ns: 'appDebug' })}
>
<FieldsetRoot className="flex items-center">
<Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{maximumIterationsLabel}</FieldsetLegend>
<Slider
className="mr-3 w-[156px]"
@ -138,7 +138,7 @@ const AgentSetting: FC<Props> = ({
})
}}
/>
</FieldsetRoot>
</Fieldset>
</ItemPanel>
{!isFunctionCall && (

View File

@ -5,7 +5,7 @@ import type { AppIconType, Language, SiteConfig } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldDescription, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Input } from '@langgenius/dify-ui/input'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
@ -360,14 +360,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({
>
{/* name & icon */}
<div className="flex gap-4">
<FieldRoot name="title" className="grow">
<Field name="title" className="grow">
<FieldLabel>{t($ => $[`${prefixSettings}.webName`], { ns: 'appOverview' })}</FieldLabel>
<FieldControl
value={inputInfo.title}
onValueChange={value => setInputInfo(item => ({ ...item, title: value }))}
placeholder={t($ => $.appNamePlaceholder, { ns: 'app' }) || ''}
/>
</FieldRoot>
</Field>
<AppIcon
size="xxl"
onClick={() => { setShowAppIconPicker(true) }}
@ -379,7 +379,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
/>
</div>
{/* description */}
<FieldRoot name="description">
<Field name="description">
<FieldLabel>{t($ => $[`${prefixSettings}.webDesc`], { ns: 'appOverview' })}</FieldLabel>
<Textarea
value={inputInfo.desc}
@ -387,11 +387,11 @@ const SettingsModal: FC<ISettingsModalProps> = ({
placeholder={t($ => $[`${prefixSettings}.webDescPlaceholder`], { ns: 'appOverview' }) as string}
/>
<FieldDescription>{t($ => $[`${prefixSettings}.webDescTip`], { ns: 'appOverview' })}</FieldDescription>
</FieldRoot>
</Field>
<Divider className="my-0 h-px" />
{/* answer icon */}
{isChat && (
<FieldRoot name="use_icon_as_answer_icon" className="w-full">
<Field name="use_icon_as_answer_icon" className="w-full">
<div className="flex items-center justify-between gap-3">
<FieldLabel>{t($ => $['answerIcon.title'], { ns: 'app' })}</FieldLabel>
<Switch
@ -400,7 +400,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
/>
</div>
<FieldDescription>{t($ => $['answerIcon.description'], { ns: 'app' })}</FieldDescription>
</FieldRoot>
</Field>
)}
{/* language */}
<div className="flex items-center">
@ -433,7 +433,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>{t($ => $[`${prefixSettings}.chatColorTheme`], { ns: 'appOverview' })}</div>
<div className="pb-0.5 body-xs-regular text-text-tertiary">{t($ => $[`${prefixSettings}.chatColorThemeDesc`], { ns: 'appOverview' })}</div>
</div>
<FieldRoot name="chat_color_theme" className="w-[200px] shrink-0">
<Field name="chat_color_theme" className="w-[200px] shrink-0">
<FieldControl
className="mb-1"
value={inputInfo.chatColorTheme ?? ''}
@ -444,11 +444,11 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<span>{t($ => $[`${prefixSettings}.chatColorThemeInverted`], { ns: 'appOverview' })}</span>
<Switch checked={inputInfo.chatColorThemeInverted} onCheckedChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch>
</div>
</FieldRoot>
</Field>
</div>
)}
{/* workflow detail */}
<FieldRoot name="show_workflow_steps" className="w-full">
<Field name="show_workflow_steps" className="w-full">
<div className="flex items-center justify-between gap-3">
<FieldLabel>{t($ => $[`${prefixSettings}.workflow.subTitle`], { ns: 'appOverview' })}</FieldLabel>
<Switch
@ -458,7 +458,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
/>
</div>
<FieldDescription>{t($ => $[`${prefixSettings}.workflow.showDesc`], { ns: 'appOverview' })}</FieldDescription>
</FieldRoot>
</Field>
<Divider className="my-0 h-px" />
<div className="space-y-5">
{INPUT_PLACEHOLDER_SUPPORTED_MODES.includes(appInfo.mode) && (

View File

@ -23,7 +23,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
@ -692,7 +692,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{t($ => $.deleteAppConfirmContent, { ns: 'app' })}
</AlertDialogDescription>
<FieldRoot name="confirm-app-name" className="mt-2">
<Field name="confirm-app-name" className="mt-2">
<FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
<Trans
i18nKey={$ => $.deleteAppConfirmInputLabel}
@ -712,7 +712,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
onValueChange={setConfirmDeleteInput}
className="border-components-input-border-hover bg-components-input-bg-normal focus:border-components-input-border-active focus:bg-components-input-bg-active"
/>
</FieldRoot>
</Field>
</div>
<AlertDialogActions>
<AlertDialogCancelButton type="button" disabled={isDeleting}>
@ -1286,7 +1286,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{t($ => $.deleteAppConfirmContent, { ns: 'app' })}
</AlertDialogDescription>
<FieldRoot name="confirm-app-name" className="mt-2">
<Field name="confirm-app-name" className="mt-2">
<FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
<Trans
i18nKey={$ => $.deleteAppConfirmInputLabel}
@ -1315,7 +1315,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
{t($ => $['operation.fill'], { ns: 'common' })}
</button>
</div>
</FieldRoot>
</Field>
</div>
<AlertDialogActions>
<AlertDialogCancelButton type="button" disabled={isDeleting}>

View File

@ -3,8 +3,8 @@ import { Button } from '@langgenius/dify-ui/button'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldDescription, FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldDescription, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
@ -69,8 +69,8 @@ export const CheckboxList = ({
)
return (
<FieldRoot name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}>
<FieldsetRoot
<Field name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}>
<Fieldset
render={(
<CheckboxGroup
aria-label={!label && title ? title : undefined}
@ -191,7 +191,7 @@ export const CheckboxList = ({
)}
</div>
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}

View File

@ -8,8 +8,8 @@ import type {
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { FieldItem, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { produce } from 'immer'
@ -147,8 +147,8 @@ const FollowUpSettingModal = ({
hideDebugWithMultipleModel
/>
</div>
<FieldRoot name="follow_up_prompt_mode" className="contents">
<FieldsetRoot
<Field name="follow_up_prompt_mode" className="contents">
<Fieldset
render={(
<RadioGroup<PromptMode>
className="flex-col items-stretch gap-3"
@ -227,8 +227,8 @@ const FollowUpSettingModal = ({
)}
</RadioItem>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
</div>
<div className="mt-6 flex items-center justify-end gap-2">
<Button onClick={onCancel}>

View File

@ -1,8 +1,8 @@
import type { AnyFieldApi } from '@tanstack/react-form'
import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base/form/types'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
import {
Select,
@ -391,8 +391,8 @@ const BaseField = ({
}
{
formItemType === FormTypeEnum.radio && (
<FieldRoot name={name} className="contents">
<FieldsetRoot
<Field name={name} className="contents">
<Fieldset
render={(
<RadioGroup
value={stringValue}
@ -436,14 +436,14 @@ const BaseField = ({
</FieldItem>
))
}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
{
formItemType === FormTypeEnum.boolean && (
<FieldRoot name={name} className="contents">
<FieldsetRoot
<Field name={name} className="contents">
<Fieldset
render={(
<RadioGroup<boolean>
className="w-fit gap-3"
@ -465,8 +465,8 @@ const BaseField = ({
False
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (

View File

@ -1,4 +1,4 @@
import type { NumberFieldInputProps, NumberFieldRootProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
import type { NumberFieldInputProps, NumberFieldProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
import type { ReactNode } from 'react'
import type { LabelProps } from '../label'
import { cn } from '@langgenius/dify-ui/cn'
@ -22,7 +22,7 @@ type NumberInputFieldProps = {
inputClassName?: string
unit?: ReactNode
size?: NumberFieldSize
} & Omit<NumberFieldRootProps, 'children' | 'className' | 'id' | 'value' | 'defaultValue' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onBlur' | 'className' | 'onChange'>
} & Omit<NumberFieldProps, 'children' | 'className' | 'id' | 'value' | 'defaultValue' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onBlur' | 'className' | 'onChange'>
const NumberInputField = ({
label,

View File

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import {
NumberField,
NumberFieldControls,
@ -32,7 +32,7 @@ type Props = Readonly<{
const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1, min = 0, max, value, enable, onChange, disabled = false, hasSwitch, onSwitchChange }) => {
return (
<FieldsetRoot className={className}>
<Fieldset className={className}>
<FieldsetLegend className="sr-only">{name}</FieldsetLegend>
<div className="flex items-center justify-between">
<div className="flex h-6 items-center">
@ -86,7 +86,7 @@ const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1,
/>
</div>
</div>
</FieldsetRoot>
</Fieldset>
)
}
export default ParamItem

View File

@ -3,7 +3,7 @@ import type { MeterTone } from '@langgenius/dify-ui/meter'
import type { FC } from 'react'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { MeterIndicator, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
import { useAtomValue } from 'jotai'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@ -72,11 +72,11 @@ const AppsFull: FC<{ loc: string, className?: string }> = ({
{total}
</div>
</div>
<MeterRoot value={Math.min(percent, 100)} max={100} aria-label={buildAppsLabel}>
<Meter value={Math.min(percent, 100)} max={100} aria-label={buildAppsLabel}>
<MeterTrack>
<MeterIndicator tone={tone} />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
</div>
)

View File

@ -2,7 +2,7 @@
import type { MeterTone } from '@langgenius/dify-ui/meter'
import type { ComponentType, FC, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { MeterIndicator, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@ -150,11 +150,11 @@ const UsageInfo: FC<Props> = ({
</div>
)
: (
<MeterRoot value={effectivePercent} max={100} aria-label={name}>
<Meter value={effectivePercent} max={100} aria-label={name}>
<MeterTrack>
<MeterIndicator tone={tone} />
</MeterTrack>
</MeterRoot>
</Meter>
)
const wrapWithStorageTooltip = (children: ReactNode) => {

View File

@ -1,5 +1,5 @@
'use client'
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ParentMode, SimpleDocumentDetail } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import {
@ -119,7 +119,7 @@ export function DocumentPicker({
})
const documentsList = data?.data ?? []
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press')
setSearchValue(inputValue)
}

View File

@ -1,4 +1,4 @@
import type { NumberFieldInputProps, NumberFieldRootProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
import type { NumberFieldInputProps, NumberFieldProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
import type { FC, PropsWithChildren, ReactNode } from 'react'
import type { InputProps } from '@/app/components/base/input'
import {
@ -71,7 +71,7 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = ({ tooltip,
)
}
type CompoundNumberInputProps = Omit<NumberFieldRootProps, 'children' | 'className' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onChange'> & {
type CompoundNumberInputProps = Omit<NumberFieldProps, 'children' | 'className' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onChange'> & {
label: string
unit?: ReactNode
size?: NumberFieldSize

View File

@ -1,6 +1,6 @@
'use client'
import type { ComboboxRootChangeEventDetails, Placement } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails, Placement } from '@langgenius/dify-ui/combobox'
import type { BuiltInMetadataItem, MetadataItem } from '../types'
import { cn } from '@langgenius/dify-ui/cn'
import {
@ -83,7 +83,7 @@ export function DatasetMetadataPicker({
resetPicker()
}
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press')
setQuery(inputValue)
}

View File

@ -1,4 +1,4 @@
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import {
NumberField,
NumberFieldControls,
@ -33,7 +33,7 @@ const KeyWordNumber = ({
}, [onKeywordNumberChange])
return (
<FieldsetRoot className="flex items-center gap-x-1">
<Fieldset className="flex items-center gap-x-1">
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<div className="flex grow items-center gap-x-0.5">
<div className="truncate system-xs-medium text-text-secondary">
@ -69,7 +69,7 @@ const KeyWordNumber = ({
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldsetRoot>
</Fieldset>
)
}

View File

@ -4,7 +4,7 @@ import type {
} from '@dify/contracts/api/console/api-based-extension/types.gen'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import { useMutation } from '@tanstack/react-query'
@ -81,7 +81,7 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
: t($ => $['apiBasedExtension.modal.title'], { ns: 'common' })}
</DialogTitle>
<Form<ApiBasedExtensionPayload> className="grid gap-4 pt-2" onFormSubmit={handleSubmit}>
<FieldRoot name="name">
<Field name="name">
<FieldLabel>{nameLabel}</FieldLabel>
<FieldControl
required
@ -89,9 +89,9 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
placeholder={t($ => $['apiBasedExtension.modal.name.placeholder'], { ns: 'common' }) || ''}
/>
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: nameLabel })}</FieldError>
</FieldRoot>
</Field>
<FieldRoot name="api_endpoint">
<Field name="api_endpoint">
<FieldLabel>{apiEndpointLabel}</FieldLabel>
<FieldControl
required
@ -110,9 +110,9 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
</a>
</FieldDescription>
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: apiEndpointLabel })}</FieldError>
</FieldRoot>
</Field>
<FieldRoot
<Field
name="api_key"
validate={(value) => {
if (typeof value === 'string' && value.length > 0 && value.length < 5)
@ -129,7 +129,7 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
/>
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: apiKeyLabel })}</FieldError>
<FieldError match="customError" />
</FieldRoot>
</Field>
<div className="mt-2 flex items-center justify-end gap-2">
<Button type="button" onClick={() => onOpenChange(false)}>

View File

@ -13,8 +13,8 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
import { useCallback, useState } from 'react'
@ -223,8 +223,8 @@ function Form<
const translatedLabel = label[language] || label.en_US
return (
<FieldRoot key={variable} name={variable} className="contents">
<FieldsetRoot
<Field key={variable} name={variable} className="contents">
<Fieldset
render={(
<RadioGroup
value={selectedValue}
@ -264,8 +264,8 @@ function Form<
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}
@ -347,8 +347,8 @@ function Form<
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<FieldRoot name={variable} className="contents">
<FieldsetRoot
<Field name={variable} className="contents">
<Fieldset
render={(
<RadioGroup<boolean>
className="flex items-center justify-between gap-3 py-2"
@ -378,8 +378,8 @@ function Form<
</FieldLabel>
</FieldItem>
</div>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
{fieldMoreInfo?.(formSchema)}
</div>
)

View File

@ -4,8 +4,8 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select'
import { Slider } from '@langgenius/dify-ui/slider'
@ -181,7 +181,7 @@ function ParameterItem({
}
return (
<FieldsetRoot className="flex items-center">
<Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
<Slider
className="w-[120px]"
@ -203,7 +203,7 @@ function ParameterItem({
onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur}
/>
</FieldsetRoot>
</Fieldset>
)
}
@ -225,7 +225,7 @@ function ParameterItem({
}
return (
<FieldsetRoot className="flex items-center">
<Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
<Slider
className="w-[120px]"
@ -247,7 +247,7 @@ function ParameterItem({
onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur}
/>
</FieldsetRoot>
</Fieldset>
)
}
@ -256,8 +256,8 @@ function ParameterItem({
const translatedLabel = parameterRule.label[language] || parameterRule.label.en_US
return (
<FieldRoot name={parameterRule.name} className="contents">
<FieldsetRoot
<Field name={parameterRule.name} className="contents">
<Fieldset
render={(
<RadioGroup<boolean>
className="w-[150px] gap-3"
@ -279,8 +279,8 @@ function ParameterItem({
False
</FieldLabel>
</FieldItem>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
}

View File

@ -1,4 +1,4 @@
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { DefaultModel, Model, ModelFeatureEnum, ModelItem } from '../declarations'
import type { ModelSelectorModelPredicate, ModelSelectorValue } from './types'
import { cn } from '@langgenius/dify-ui/cn'
@ -115,7 +115,7 @@ function ModelSelector({
handleSelect(provider.provider, model)
}, [handleSelect, modelList])
const handleInputValueChange = useCallback((inputValue: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = useCallback((inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press')
setInputValue(inputValue)
}, [])

View File

@ -1,4 +1,4 @@
import { MeterIndicator, MeterLabel, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
import { Meter, MeterIndicator, MeterLabel, MeterTrack } from '@langgenius/dify-ui/meter'
import { Trans, useTranslation } from 'react-i18next'
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
import { IS_CLOUD_EDITION } from '@/config'
@ -55,7 +55,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
/>
</div>
</div>
<MeterRoot value={meterValue} max={meterMax} className="mt-3 flex flex-col gap-1">
<Meter value={meterValue} max={meterMax} className="mt-3 flex flex-col gap-1">
<div className="flex items-center justify-between">
<MeterLabel className="system-xs-medium text-text-tertiary">
{t($ => $['modelProvider.card.usageLabel'], { ns: 'common' })}
@ -73,7 +73,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
<MeterTrack className="bg-components-progress-error-bg">
<MeterIndicator tone="error" />
</MeterTrack>
</MeterRoot>
</Meter>
</div>
)
}

View File

@ -5,7 +5,7 @@ import type { InstallState } from '@/app/components/plugins/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import * as React from 'react'
@ -204,7 +204,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, in
onFormSubmit={handleUrlSubmit}
className="flex flex-col items-start gap-4 self-stretch"
>
<FieldRoot name="repoUrl" className="gap-4 self-stretch">
<Field name="repoUrl" className="gap-4 self-stretch">
<FieldLabel className="flex w-full flex-col items-start justify-center p-0 text-text-secondary">
<span className="system-sm-semibold">{t($ => $['installFromGitHub.gitHubRepo'], { ns: 'plugin' })}</span>
</FieldLabel>
@ -217,7 +217,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, in
className="flex grow items-center gap-0.5 self-stretch overflow-hidden rounded-lg border-components-input-border-active bg-components-input-bg-active p-2 text-ellipsis"
placeholder="Please enter GitHub repo URL"
/>
</FieldRoot>
</Field>
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
<Button
variant="secondary"

View File

@ -2,7 +2,7 @@
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
import { Button } from '@langgenius/dify-ui/button'
import { FieldRoot } from '@langgenius/dify-ui/field'
import { Field } from '@langgenius/dify-ui/field'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@ -78,7 +78,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
return (
<>
<FieldRoot name="version" className="gap-4 self-stretch">
<Field name="version" className="gap-4 self-stretch">
<Select
value={selectedVersionOption?.value ?? null}
onValueChange={(value) => {
@ -120,8 +120,8 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
))}
</SelectContent>
</Select>
</FieldRoot>
<FieldRoot name="package" className="gap-4 self-stretch">
</Field>
<Field name="package" className="gap-4 self-stretch">
<Select
value={selectedPackageOption?.value ?? null}
readOnly={!selectedVersion}
@ -148,7 +148,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
))}
</SelectContent>
</Select>
</FieldRoot>
</Field>
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
{!isEdit
&& (

View File

@ -12,8 +12,8 @@ import {
DrawerTitle,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Input } from '@langgenius/dify-ui/input'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
@ -123,8 +123,8 @@ export default function ConfigCredential({
content: 'space-y-4 pt-2 pr-8 pl-6',
}}
>
<FieldRoot name="auth_type" className="contents">
<FieldsetRoot
<Field name="auth_type" className="contents">
<Fieldset
render={(
<RadioGroup<AuthType>
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
@ -151,12 +151,12 @@ export default function ConfigCredential({
value={AuthType.apiKeyQuery}
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
/>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
{tempCredential.auth_type === AuthType.apiKeyHeader && (
<>
<FieldRoot name="api_key_header_prefix" className="contents">
<FieldsetRoot
<Field name="api_key_header_prefix" className="contents">
<Fieldset
render={(
<RadioGroup<AuthHeaderPrefix>
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
@ -183,8 +183,8 @@ export default function ConfigCredential({
value={AuthHeaderPrefix.custom}
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom}
/>
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
<div>
<div className="flex items-center py-2 system-sm-medium text-text-primary">
{t($ => $['createTool.authMethod.key'], { ns: 'tools' })}

View File

@ -1,5 +1,5 @@
'use client'
import type { DrawerRootProps } from '@langgenius/dify-ui/drawer'
import type { DrawerProps } from '@langgenius/dify-ui/drawer'
import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderOutputSchema, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
@ -83,7 +83,7 @@ const InfoTooltip = ({ children }: { children: string }) => {
}
const WorkflowToolDrawerFrame = ({ title, closeLabel, onHide, children }: WorkflowToolDrawerFrameProps) => {
const handleOpenChange = React.useCallback<NonNullable<DrawerRootProps['onOpenChange']>>((open) => {
const handleOpenChange = React.useCallback<NonNullable<DrawerProps['onOpenChange']>>((open) => {
if (!open)
onHide()
}, [onHide])

View File

@ -1,5 +1,5 @@
import type { AgentInviteOptionResponse } from '@dify/contracts/api/console/agent/types.gen'
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { NodeDefault } from '../types'
import type { AgentRosterNodeData } from './types'
import { cn } from '@langgenius/dify-ui/cn'
@ -83,7 +83,7 @@ export function AgentSelectorContent({
return option.name
}
const handleInputValueChange = (nextSearchText: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = (nextSearchText: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press')
setSearchText(nextSearchText)
}

View File

@ -4,7 +4,7 @@ import type { NodeOutPutVar } from '../../../types'
import type { ToolVarInputs } from '../../tool/types'
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { PluginMeta } from '@/app/components/plugins/types'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import {
NumberField,
NumberFieldControls,
@ -146,7 +146,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
tooltip={def.tooltip && renderI18nObject(def.tooltip)}
inline
>
<FieldsetRoot className="flex w-[200px] items-center gap-3">
<Fieldset className="flex w-[200px] items-center gap-3">
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<Slider
value={value}
@ -170,7 +170,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldsetRoot>
</Fieldset>
</Field>
)
}

View File

@ -4,12 +4,12 @@ import type {
} from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible'
type CollapseProps = Omit<ComponentProps<typeof CollapsibleRoot>, 'open' | 'onOpenChange'> & {
type CollapseProps = Omit<ComponentProps<typeof Collapsible>, 'open' | 'onOpenChange'> & {
collapsed?: boolean
onCollapse?: (collapsed: boolean) => void
}
@ -20,7 +20,7 @@ export function Collapse({
...props
}: CollapseProps) {
return (
<CollapsibleRoot
<Collapsible
open={collapsed === undefined ? undefined : !collapsed}
onOpenChange={open => onCollapse?.(!open)}
{...props}

View File

@ -1,5 +1,5 @@
'use client'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import * as React from 'react'
import { useCallback } from 'react'
@ -41,7 +41,7 @@ function InputNumberWithSlider({
}, [onChange])
return (
<FieldsetRoot>
<Fieldset>
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<div className="flex h-8 items-center justify-between space-x-2">
<input
@ -67,7 +67,7 @@ function InputNumberWithSlider({
aria-label={label}
/>
</div>
</FieldsetRoot>
</Fieldset>
)
}
export default React.memo(InputNumberWithSlider)

View File

@ -2,7 +2,7 @@
import type { FC } from 'react'
import type { Memory } from '../../../types'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch'
import { produce } from 'immer'
@ -160,7 +160,7 @@ const MemoryConfig: FC<Props> = ({
/>
<div className="system-xs-medium-uppercase text-text-tertiary">{windowSizeLabel}</div>
</div>
<FieldsetRoot className="flex h-8 items-center space-x-2">
<Fieldset className="flex h-8 items-center space-x-2">
<FieldsetLegend className="sr-only">{windowSizeLabel}</FieldsetLegend>
<Slider
className="w-[144px]"
@ -185,7 +185,7 @@ const MemoryConfig: FC<Props> = ({
onBlur={handleBlur}
disabled={readonly || !payload.window?.enabled}
/>
</FieldsetRoot>
</Fieldset>
</div>
{canSetRoleName && (
<div className="mt-4">

View File

@ -1,7 +1,7 @@
import type {
Node,
} from '@/app/components/workflow/types'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
@ -68,7 +68,7 @@ const RetryOnPanel = ({
{
retry_config?.retry_enabled && (
<div className="px-4 pb-2">
<FieldsetRoot className="mb-1 flex w-full items-center">
<Fieldset className="mb-1 flex w-full items-center">
<FieldsetLegend className="sr-only">{maxRetriesLabel}</FieldsetLegend>
<div className="mr-2 grow system-xs-medium-uppercase text-text-secondary">{maxRetriesLabel}</div>
<Slider
@ -91,8 +91,8 @@ const RetryOnPanel = ({
unit={t($ => $['nodes.common.retry.times'], { ns: 'workflow' }) || ''}
className={s.input}
/>
</FieldsetRoot>
<FieldsetRoot className="flex items-center">
</Fieldset>
<Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{retryIntervalLabel}</FieldsetLegend>
<div className="mr-2 grow system-xs-medium-uppercase text-text-secondary">{retryIntervalLabel}</div>
<Slider
@ -115,7 +115,7 @@ const RetryOnPanel = ({
unit={t($ => $['nodes.common.retry.ms'], { ns: 'workflow' }) || ''}
className={s.input}
/>
</FieldsetRoot>
</Fieldset>
</div>
)
}

View File

@ -1,6 +1,6 @@
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible'
import { useTranslation } from 'react-i18next'
@ -9,7 +9,7 @@ export function AgentAdvancedSettings() {
const { t } = useTranslation()
return (
<CollapsibleRoot className="border-b border-divider-subtle py-2">
<Collapsible className="border-b border-divider-subtle py-2">
<CollapsibleTrigger className="group h-8 min-h-0 justify-start gap-0 rounded-none px-4 py-0 hover:not-data-disabled:bg-transparent hover:not-data-disabled:text-text-secondary data-panel-open:text-text-secondary">
<span className="min-w-0 truncate system-sm-semibold-uppercase text-text-secondary">
{t($ => $['nodes.agent.advancedSetting'], { ns: 'workflow' })}
@ -22,6 +22,6 @@ export function AgentAdvancedSettings() {
<CollapsiblePanel>
<div className="px-4" />
</CollapsiblePanel>
</CollapsibleRoot>
</Collapsible>
)
}

View File

@ -1,8 +1,8 @@
import type { DeclaredOutputConfig } from '@dify/contracts/api/console/apps/types.gen'
import type { EditableOutputConfig, EditingState, OutputDraft } from './utils'
import { Button } from '@langgenius/dify-ui/button'
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
import { Switch } from '@langgenius/dify-ui/switch'
@ -83,7 +83,7 @@ export function OutputEditCard({
>
<div className="px-2 pt-2">
<div className="flex h-6 items-center gap-x-2">
<FieldRoot name="name" invalid={hasNameError} className="contents">
<Field name="name" invalid={hasNameError} className="contents">
<FieldLabel className="sr-only">
{t($ => $['nodes.agent.outputVars.nameLabel'], { ns: 'workflow' })}
</FieldLabel>
@ -99,12 +99,12 @@ export function OutputEditCard({
className="h-6 w-24 px-1.5 py-0 code-sm-semibold"
onChange={event => updateDraft({ name: event.currentTarget.value })}
/>
</FieldRoot>
</Field>
<OutputTypeSelect
value={draft.type}
onChange={value => updateDraft({ type: value })}
/>
<FieldRoot name="required" className="contents">
<Field name="required" className="contents">
<FieldLabel className="flex h-6 items-center gap-x-1 system-xs-regular text-text-tertiary">
<Switch
aria-label={t($ => $['nodes.agent.outputVars.requiredLabel'], { ns: 'workflow' })}
@ -114,18 +114,18 @@ export function OutputEditCard({
/>
{t($ => $['nodes.agent.outputVars.requiredLabel'], { ns: 'workflow' })}
</FieldLabel>
</FieldRoot>
</Field>
</div>
{hasNameError && (
<FieldRoot name="nameError" invalid className="contents">
<Field name="nameError" invalid className="contents">
<FieldError id={nameErrorId} match className="mt-1 px-1 py-0 system-xs-regular text-text-destructive">
{duplicateName
? t($ => $['nodes.agent.outputVars.nameDuplicate'], { ns: 'workflow' })
: t($ => $['nodes.agent.outputVars.nameInvalid'], { ns: 'workflow' })}
</FieldError>
</FieldRoot>
</Field>
)}
<FieldRoot name="description" className="contents">
<Field name="description" className="contents">
<FieldLabel className="sr-only">
{t($ => $['nodes.agent.outputVars.descriptionLabel'], { ns: 'workflow' })}
</FieldLabel>
@ -136,10 +136,10 @@ export function OutputEditCard({
className="mt-2 h-5 border-transparent bg-transparent px-1 py-0 system-xs-regular shadow-none hover:border-transparent hover:bg-transparent focus:bg-transparent"
onChange={event => updateDraft({ description: event.currentTarget.value })}
/>
</FieldRoot>
</Field>
</div>
{allowDefaultValue && (
<CollapsibleRoot>
<Collapsible>
<CollapsibleTrigger className="h-8 min-h-8 justify-start gap-x-0.5 rounded-none border-y border-divider-subtle pr-2 pl-2.5 system-xs-regular text-text-tertiary hover:not-data-disabled:bg-state-base-hover hover:not-data-disabled:text-text-tertiary focus-visible:bg-state-base-hover focus-visible:ring-inset data-panel-open:text-text-tertiary">
<span
aria-hidden="true"
@ -149,7 +149,7 @@ export function OutputEditCard({
</CollapsibleTrigger>
<CollapsiblePanel className="border-t border-divider-subtle">
<div className="px-3 py-2">
<FieldRoot name="defaultValue" className="gap-1">
<Field name="defaultValue" className="gap-1">
<FieldLabel className="py-0 system-xs-medium text-text-secondary">
{t($ => $['nodes.agent.outputVars.defaultValueLabel'], { ns: 'workflow' })}
</FieldLabel>
@ -165,10 +165,10 @@ export function OutputEditCard({
{t($ => $[defaultValueErrorKey], { ns: 'workflow' })}
</FieldError>
)}
</FieldRoot>
</Field>
</div>
</CollapsiblePanel>
</CollapsibleRoot>
</Collapsible>
)}
<div className="flex h-12 items-center justify-end gap-x-2 px-3">
<Button type="button" size="small" variant="secondary" onClick={onCancel}>

View File

@ -19,7 +19,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import {
Popover,
PopoverContent,
@ -369,7 +369,7 @@ export function AgentRosterField({
)
return (
<FieldRoot name="agent_binding" className="gap-1 px-4 py-2">
<Field name="agent_binding" className="gap-1 px-4 py-2">
<div className="flex h-6 items-center gap-2">
<FieldLabel className="min-w-0 flex-1 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $['nodes.agent.roster.label'], { ns: 'workflow' })}
@ -476,6 +476,6 @@ export function AgentRosterField({
</span>
</div>
)}
</FieldRoot>
</Field>
)
}

View File

@ -3,7 +3,7 @@ import type { AgentV2NodeType } from '../types'
import type { AgentOutputTypeOptionValue } from '@/app/components/base/prompt-editor/plugins/agent-output-block/utils'
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useBoolean } from 'ahooks'
import { $insertNodes } from 'lexical'
@ -101,7 +101,7 @@ export function AgentTaskField({
}, {})
return (
<FieldRoot name="agent_task" className="gap-1 px-4 py-2">
<Field name="agent_task" className="gap-1 px-4 py-2">
<div className="flex h-6 items-center gap-1">
<FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $[`${i18nPrefix}.task.label`], { ns: 'workflow' })}
@ -156,6 +156,6 @@ export function AgentTaskField({
</PromptEditor>
</div>
</div>
</FieldRoot>
</Field>
)
}

View File

@ -1,7 +1,7 @@
import type { FC } from 'react'
import type { IterationNodeType } from './types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
import { Slider } from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch'
@ -103,7 +103,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
inputs.is_parallel && (
<div className="px-4 pb-2">
<Field title={maxParallelismLabel} isSubTitle tooltip={<div className="w-[230px]">{t($ => $[`${i18nPrefix}.MaxParallelismDesc`], { ns: 'workflow' })}</div>}>
<FieldsetRoot className="row flex">
<Fieldset className="row flex">
<FieldsetLegend className="sr-only">{maxParallelismLabel}</FieldsetLegend>
<Input aria-label={maxParallelismLabel} type="number" wrapperClassName="w-18 mr-4" max={MAX_PARALLEL_LIMIT} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} />
<Slider
@ -114,7 +114,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
className="mt-4 flex-1 shrink-0"
aria-label={maxParallelismLabel}
/>
</FieldsetRoot>
</Fieldset>
</Field>
</div>

View File

@ -1,5 +1,5 @@
import { cn } from '@langgenius/dify-ui/cn'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import {
memo,
@ -93,7 +93,7 @@ const IndexMethod = ({
onClick={handleIndexMethodChange}
effectColor="blue"
>
<FieldsetRoot className="flex items-center">
<Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{keywordNumberLabel}</FieldsetLegend>
<div className="flex grow items-center">
<div className="truncate system-xs-medium text-text-secondary">
@ -122,7 +122,7 @@ const IndexMethod = ({
value={keywordNumber}
onChange={handleInputChange}
/>
</FieldsetRoot>
</Fieldset>
</OptionCard>
)
}

View File

@ -1,6 +1,6 @@
import type { ComponentType, SVGProps } from 'react'
import { FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { RadioGroup } from '@langgenius/dify-ui/radio'
import {
fireEvent,
@ -111,8 +111,8 @@ function renderSearchMethodOption(props: ReturnType<typeof createProps>) {
} = props
render(
<FieldRoot name="retrieval_search_method">
<FieldsetRoot
<Field name="retrieval_search_method">
<Fieldset
render={(
<RadioGroup
value={props.searchMethod}
@ -122,8 +122,8 @@ function renderSearchMethodOption(props: ReturnType<typeof createProps>) {
>
<FieldsetLegend>Retrieval search method</FieldsetLegend>
<SearchMethodOption {...optionProps} />
</FieldsetRoot>
</FieldRoot>,
</Fieldset>
</Field>,
)
}

View File

@ -9,14 +9,14 @@ import type {
TopKFieldProps,
VisibleScoreThresholdFieldProps,
} from './top-k-and-score-threshold'
import { FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { RadioGroup } from '@langgenius/dify-ui/radio'
import {
memo,
} from 'react'
import { useTranslation } from 'react-i18next'
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
import { Field as WorkflowField } from '@/app/components/workflow/nodes/_base/components/layout'
import { useDocLink } from '@/context/i18n'
import { useRetrievalSetting } from './hooks'
import { SearchMethodOption } from './search-method-option'
@ -71,7 +71,7 @@ const RetrievalSetting = ({
} = useRetrievalSetting(indexMethod)
return (
<Field
<WorkflowField
fieldTitleProps={{
title: t($ => $['form.retrievalSetting.title'], { ns: 'datasetSettings' }),
subTitle: (
@ -83,8 +83,8 @@ const RetrievalSetting = ({
),
}}
>
<FieldRoot name="retrieval_search_method" className="gap-0">
<FieldsetRoot
<Field name="retrieval_search_method" className="gap-0">
<Fieldset
render={(
<RadioGroup<RetrievalSearchMethodEnum>
value={searchMethod}
@ -131,9 +131,9 @@ const RetrievalSetting = ({
readonly={readonly}
/>
))}
</FieldsetRoot>
</FieldRoot>
</Field>
</Fieldset>
</Field>
</WorkflowField>
)
}

View File

@ -12,8 +12,8 @@ import type {
Option,
} from './type'
import { cn } from '@langgenius/dify-ui/cn'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
@ -252,8 +252,8 @@ export function SearchMethodOption({
<div className="space-y-3">
{isHybridSearch
? (
<FieldRoot name="hybrid_search_mode" className="gap-0">
<FieldsetRoot
<Field name="hybrid_search_mode" className="gap-0">
<Fieldset
render={(
<RadioGroup<HybridSearchModeEnum>
value={hybridSearch.mode}
@ -271,8 +271,8 @@ export function SearchMethodOption({
readonly={readonly}
/>
))}
</FieldsetRoot>
</FieldRoot>
</Fieldset>
</Field>
)
: null}
{isHybridSearch && isHybridSearchWeightedScoreMode
@ -289,7 +289,7 @@ export function SearchMethodOption({
<div>
{showRerankModelSelectorSwitch
? (
<FieldRoot name="reranking_model_enabled" className="mb-1 gap-0">
<Field name="reranking_model_enabled" className="mb-1 gap-0">
<div className="flex items-center">
<FieldLabel className="flex min-w-0 items-center py-0 system-sm-semibold text-text-secondary">
<Switch
@ -307,7 +307,7 @@ export function SearchMethodOption({
{rerankModelTip}
</Infotip>
</div>
</FieldRoot>
</Field>
)
: null}
<RerankingModelSelector

View File

@ -1,5 +1,5 @@
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import {
NumberField,
NumberFieldControls,
@ -64,7 +64,7 @@ export function TopKAndScoreThreshold({
return (
<div className="grid grid-cols-2 gap-4">
<FieldRoot name="top_k" className="gap-0">
<Field name="top_k" className="gap-0">
<div className="mb-0.5 flex h-6 items-center">
<FieldLabel className="py-0 system-xs-medium text-text-secondary">
{topKLabel}
@ -92,13 +92,13 @@ export function TopKAndScoreThreshold({
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
</Field>
{scoreThresholdHidden
? null
: (
<FieldsetRoot className="min-w-0">
<Fieldset className="min-w-0">
<FieldsetLegend className="sr-only">{scoreThresholdLabel}</FieldsetLegend>
<FieldRoot name="score_threshold_enabled" className="mb-0.5 gap-0">
<Field name="score_threshold_enabled" className="mb-0.5 gap-0">
<div className="flex h-6 items-center">
<FieldLabel className="flex w-full min-w-0 grow items-center py-0 system-sm-medium text-text-secondary">
<Switch
@ -118,8 +118,8 @@ export function TopKAndScoreThreshold({
{scoreThresholdTip}
</Infotip>
</div>
</FieldRoot>
<FieldRoot name="score_threshold" className="gap-0">
</Field>
<Field name="score_threshold" className="gap-0">
<FieldLabel className="sr-only">{scoreThresholdLabel}</FieldLabel>
<NumberField
disabled={readonly || !scoreThresholdEnabled}
@ -137,8 +137,8 @@ export function TopKAndScoreThreshold({
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</FieldRoot>
</FieldsetRoot>
</Field>
</Fieldset>
)}
</div>
)

View File

@ -1,5 +1,5 @@
import { Button } from '@langgenius/dify-ui/button'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
@ -59,7 +59,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
void handleGetEMailVerificationCode()
}}
>
<FieldRoot name="email" disabled={isInvite} className="mb-2">
<Field name="email" disabled={isInvite} className="mb-2">
<FieldLabel className="my-2 py-0 system-md-semibold text-text-secondary">{t($ => $.email, { ns: 'login' })}</FieldLabel>
<FieldControl
type="email"
@ -73,7 +73,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
<div className="mt-3">
<Button type="submit" loading={loading} disabled={loading || !email} variant="primary" className="w-full">{t($ => $['signup.verifyMail'], { ns: 'login' })}</Button>
</div>
</FieldRoot>
</Field>
</Form>
)
}

View File

@ -1,5 +1,5 @@
import { Button } from '@langgenius/dify-ui/button'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import { useQueryClient } from '@tanstack/react-query'
@ -116,7 +116,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
void handleEmailPasswordLogin()
}}
>
<FieldRoot name="email" disabled={isInvite} className="mb-3">
<Field name="email" disabled={isInvite} className="mb-3">
<FieldLabel className="my-2 py-0 system-md-semibold text-text-secondary">
{t($ => $.email, { ns: 'login' })}
</FieldLabel>
@ -129,9 +129,9 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
spellCheck={false}
placeholder={t($ => $.emailPlaceholder, { ns: 'login' }) || ''}
/>
</FieldRoot>
</Field>
<FieldRoot name="password" className="mb-3">
<Field name="password" className="mb-3">
<div className="my-2 flex items-center justify-between">
<FieldLabel className="py-0 system-md-semibold text-text-secondary">{t($ => $.password, { ns: 'login' })}</FieldLabel>
<Link
@ -168,7 +168,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
</Button>
</div>
</div>
</FieldRoot>
</Field>
<div className="mb-2">
<Button

View File

@ -2,7 +2,7 @@
import type { AgentBuildDraftChangeSummary } from './build-draft-changes-context'
import { Button } from '@langgenius/dify-ui/button'
import { CollapsiblePanel, CollapsibleRoot } from '@langgenius/dify-ui/collapsible'
import { Collapsible, CollapsiblePanel } from '@langgenius/dify-ui/collapsible'
import { useId, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AgentBuildGridTexture } from '../build-grid-texture'
@ -53,7 +53,7 @@ export function AgentBuildDraftBar({
}
return (
<CollapsibleRoot
<Collapsible
open={open}
onOpenChange={handleOpenChange}
role="group"
@ -115,6 +115,6 @@ export function AgentBuildDraftBar({
{tCustom($ => $.apply)}
</Button>
</div>
</CollapsibleRoot>
</Collapsible>
)
}

View File

@ -4,8 +4,8 @@ import type { ReactNode } from 'react'
import type { AgentBuildDraftChangeSection } from '../build-draft-changes-context'
import { cn } from '@langgenius/dify-ui/cn'
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible'
import { Infotip } from '@/app/components/base/infotip'
@ -62,7 +62,7 @@ export function ConfigureSection({
const isBuildDraftChanged = useIsAgentBuildDraftSectionChanged(buildDraftChangeSection)
return (
<CollapsibleRoot
<Collapsible
render={<section />}
defaultOpen={defaultOpen}
className={rootClassName}
@ -109,6 +109,6 @@ export function ConfigureSection({
{children}
</div>
</CollapsiblePanel>
</CollapsibleRoot>
</Collapsible>
)
}

View File

@ -4,6 +4,7 @@ import type { ReactNode } from 'react'
import type { AgentFileNode } from '@/features/agent-v2/agent-composer/form-state'
import { cn } from '@langgenius/dify-ui/cn'
import {
FileTree,
FileTreeFile,
FileTreeFolder,
FileTreeFolderPanel,
@ -11,7 +12,6 @@ import {
FileTreeIcon,
FileTreeLabel,
FileTreeList,
FileTreeRoot,
} from '@langgenius/dify-ui/file-tree'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
import { Fragment } from 'react'
@ -187,7 +187,7 @@ export function AgentFileTree({
scrollbar: 'hidden',
}}
>
<FileTreeRoot
<FileTree
id={id}
aria-label={treeLabel}
className={cn('w-full max-w-full min-w-0 p-0', rootClassName)}
@ -207,7 +207,7 @@ export function AgentFileTree({
renderFolderPanel={renderFolderPanel}
/>
</FileTreeList>
</FileTreeRoot>
</FileTree>
</ScrollArea>
</div>
)

View File

@ -2,7 +2,7 @@
import type { FormValue, Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
@ -27,7 +27,7 @@ export function AgentModelField({
const canConfigureModelSettings = !readOnly && !!currentModel?.provider && !!currentModel.model
return (
<FieldRoot name="model" className="gap-1 pb-4">
<Field name="model" className="gap-1 pb-4">
<FieldLabel className="py-0 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $['agentDetail.configure.model.label'])}
</FieldLabel>
@ -97,6 +97,6 @@ export function AgentModelField({
</div>
)}
</div>
</FieldRoot>
</Field>
)
}

View File

@ -3,7 +3,7 @@
import type { AgentConfigSnapshotSummaryResponse, AgentReferencingWorkflowResponse, AgentReferencingWorkflowsResponse } from '@dify/contracts/api/console/agent/types.gen'
import type { RegisterableHotkey } from '@tanstack/react-hotkeys'
import { Button } from '@langgenius/dify-ui/button'
import { CollapsiblePanel, CollapsibleRoot } from '@langgenius/dify-ui/collapsible'
import { Collapsible, CollapsiblePanel } from '@langgenius/dify-ui/collapsible'
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { toast } from '@langgenius/dify-ui/toast'
@ -282,7 +282,7 @@ export function AgentConfigurePublishBar({
const impactReferences = publishBarMode.status === 'confirmingImpact' ? publishBarMode.references : []
return (
<CollapsibleRoot
<Collapsible
open={isConfirmingImpact}
className="group/publish-bar pointer-events-auto w-full overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]"
>
@ -306,7 +306,7 @@ export function AgentConfigurePublishBar({
onOpenVersions={() => onOpenVersions?.()}
onPublishRequest={handlePublishRequest}
/>
</CollapsibleRoot>
</Collapsible>
)
}

View File

@ -3,7 +3,7 @@
import type { AgentCliTool, EnvScope, EnvVariable } from '@/features/agent-v2/agent-composer/form-state'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldDescription, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useState } from 'react'
@ -143,7 +143,7 @@ export function CliToolDialog({
onFormSubmit={handleSubmit}
>
<div className="flex flex-col gap-4">
<FieldRoot name="installCommand">
<Field name="installCommand">
<FieldLabel>
{t($ => $['agentDetail.configure.tools.cliDialog.installCommand.label'])}
</FieldLabel>
@ -156,8 +156,8 @@ export function CliToolDialog({
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.installCommand.placeholder'])}
value={installCommand}
/>
</FieldRoot>
<FieldRoot name="name">
</Field>
<Field name="name">
<FieldLabel>
{t($ => $['agentDetail.configure.tools.cliDialog.name.label'])}
</FieldLabel>
@ -167,7 +167,7 @@ export function CliToolDialog({
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.name.placeholder'])}
value={toolName}
/>
</FieldRoot>
</Field>
<div className="pt-1">
<div className="mb-3 h-px bg-divider-subtle" />
<div className="mb-1 flex min-h-6 items-center gap-1">

View File

@ -5,8 +5,8 @@ import type { AgentProviderTool, AgentToolAction } from '@/features/agent-v2/age
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
Collapsible,
CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible'
import {
@ -248,7 +248,7 @@ export const AgentProviderToolItem = memo(({
const displayName = tool.displayName ?? tool.name
return (
<CollapsibleRoot
<Collapsible
open={isExpanded}
onOpenChange={onOpenChange}
className="overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-1 shadow-xs shadow-shadow-shadow-3"
@ -311,6 +311,6 @@ export const AgentProviderToolItem = memo(({
</div>
)}
</CollapsiblePanel>
</CollapsibleRoot>
</Collapsible>
)
})

View File

@ -1,5 +1,5 @@
import type { AgentIconSelection } from './agent-form'
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
@ -50,7 +50,7 @@ export function AgentFormFields({
/>
</button>
<div className="flex min-w-0 flex-1 gap-3 pb-1">
<FieldRoot
<Field
name="name"
className="relative min-w-0 flex-1"
validate={(value) => {
@ -77,8 +77,8 @@ export function AgentFormFields({
<FieldError match="valueMissing">{t($ => $['roster.createForm.nameRequired'])}</FieldError>
<FieldError match="customError" />
</div>
</FieldRoot>
<FieldRoot
</Field>
<Field
name="role"
className="relative min-w-0 flex-1"
>
@ -95,10 +95,10 @@ export function AgentFormFields({
placeholder={t($ => $['roster.createForm.rolePlaceholder'])}
value={role}
/>
</FieldRoot>
</Field>
</div>
</div>
<FieldRoot name="description">
<Field name="description">
<FieldLabel>
{t($ => $['roster.createForm.descriptionLabel'])}
<span className="ml-1 system-xs-regular text-text-tertiary">
@ -112,7 +112,7 @@ export function AgentFormFields({
placeholder={t($ => $['roster.createForm.descriptionPlaceholder'])}
value={description}
/>
</FieldRoot>
</Field>
</div>
)
}

View File

@ -4,7 +4,7 @@ import type { AgentAppCopyPayload, AgentAppPartial } from '@dify/contracts/api/c
import type { AgentFormValues, AgentIconSelection } from './agent-form'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
@ -147,7 +147,7 @@ export function DuplicateAgentDialog({
/>
</button>
<div className="flex min-w-0 flex-1 gap-3 pb-1">
<FieldRoot name="name" className="relative min-w-0 flex-1">
<Field name="name" className="relative min-w-0 flex-1">
<FieldLabel>
{t($ => $['roster.createForm.nameLabel'])}
<span className="ml-1 system-xs-regular text-text-tertiary">
@ -163,8 +163,8 @@ export function DuplicateAgentDialog({
placeholder={defaultCopyName}
value={name}
/>
</FieldRoot>
<FieldRoot
</Field>
<Field
name="role"
className="relative min-w-0 flex-1"
>
@ -181,10 +181,10 @@ export function DuplicateAgentDialog({
placeholder={t($ => $['roster.createForm.rolePlaceholder'])}
value={role}
/>
</FieldRoot>
</Field>
</div>
</div>
<FieldRoot name="description">
<Field name="description">
<FieldLabel>
{t($ => $['roster.createForm.descriptionLabel'])}
<span className="ml-1 system-xs-regular text-text-tertiary">
@ -198,7 +198,7 @@ export function DuplicateAgentDialog({
placeholder={t($ => $['roster.createForm.descriptionPlaceholder'])}
value={description}
/>
</FieldRoot>
</Field>
</div>
<div className="flex shrink-0 justify-end gap-2 px-6 pt-5 pb-6">
<Button type="button" className="min-w-18" onClick={() => handleOpenChange(false)} disabled={duplicateAgentMutation.isPending}>

View File

@ -7,7 +7,7 @@ import {
DialogContent,
DialogTitle,
} from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
@ -96,7 +96,7 @@ function EditDeploymentForm() {
<>
<DialogCloseButton disabled={updateInstance.isPending} />
<Form<EditDeploymentFormValues> className="flex flex-col gap-4" onFormSubmit={handleSubmit}>
<FieldRoot name="name" className="gap-2">
<Field name="name" className="gap-2">
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{nameLabel}
</FieldLabel>
@ -107,8 +107,8 @@ function EditDeploymentForm() {
className="h-8"
/>
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: nameLabel })}</FieldError>
</FieldRoot>
<FieldRoot name="description" className="gap-2">
</Field>
<Field name="description" className="gap-2">
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{t($ => $['settings.description'])}
</FieldLabel>
@ -116,7 +116,7 @@ function EditDeploymentForm() {
defaultValue={initialValues.description}
className="min-h-24"
/>
</FieldRoot>
</Field>
<div className="flex justify-end gap-2 pt-2">
<Button
type="button"

View File

@ -1,6 +1,6 @@
'use client'
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type { AccessSubjectSelectionProps } from './types'
import type { AccessControlGroup, Subject } from '@/models/access-control'
import {
@ -91,7 +91,7 @@ export function AccessSubjectAddButton({
setOpen(true)
}
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (!disabled && details.reason !== 'item-press')
setKeyword(inputValue)
}

View File

@ -9,7 +9,7 @@ import {
DialogDescription,
DialogTitle,
} from '@langgenius/dify-ui/dialog'
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Form } from '@langgenius/dify-ui/form'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
@ -73,7 +73,7 @@ function EditReleaseForm({
return (
<Form<EditReleaseFormValues> className="flex flex-col gap-4" onFormSubmit={handleSubmit}>
<FieldRoot name="name" className="gap-2">
<Field name="name" className="gap-2">
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{nameLabel}
</FieldLabel>
@ -86,8 +86,8 @@ function EditReleaseForm({
className="h-8"
/>
<FieldError match="valueMissing">{t($ => $['versions.releaseNameRequired'])}</FieldError>
</FieldRoot>
<FieldRoot name="description" className="gap-2">
</Field>
<Field name="description" className="gap-2">
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{t($ => $['versions.releaseDescriptionLabel'])}
<span className="ml-1.5 system-xs-regular text-text-quaternary">{t($ => $['versions.optional'])}</span>
@ -98,7 +98,7 @@ function EditReleaseForm({
autoComplete="off"
className="min-h-24"
/>
</FieldRoot>
</Field>
<div className="flex justify-end gap-2 pt-2">
<Button
type="button"

View File

@ -1,5 +1,5 @@
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
import type { ComboboxRootProps } from '@langgenius/dify-ui/combobox'
import type { ComboboxProps } from '@langgenius/dify-ui/combobox'
import { cn } from '@langgenius/dify-ui/cn'
import { Combobox, ComboboxContent, ComboboxTrigger } from '@langgenius/dify-ui/combobox'
import { useQuery } from '@tanstack/react-query'
@ -9,7 +9,7 @@ import XCircleIcon from '@/app/components/base/icons/src/vender/solid/general/XC
import { consoleQuery } from '@/service/client'
import { TagSearchContent } from './tag-search-content'
const tagFilterComboboxFilter: NonNullable<ComboboxRootProps<Tag, true>['filter']> = (tag, query) => tag.name.includes(query)
const tagFilterComboboxFilter: NonNullable<ComboboxProps<Tag, true>['filter']> = (tag, query) => tag.name.includes(query)
const tagToString = (tag: Tag) => tag.name
const isSameTag = (item: Tag, value: Tag) => item.id === value.id

View File

@ -1,5 +1,5 @@
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
import type { ComboboxRootProps } from '@langgenius/dify-ui/combobox'
import type { ComboboxProps } from '@langgenius/dify-ui/combobox'
import type { ComponentProps } from 'react'
import type { TagComboboxItem } from './tag-combobox-item'
import { cn } from '@langgenius/dify-ui/cn'
@ -18,12 +18,12 @@ import { isCreateTagOption } from './tag-combobox-item'
import { TagSearchContent } from './tag-search-content'
import { TagTrigger } from './tag-trigger'
const TAG_COMBOBOX_FILTER: NonNullable<ComboboxRootProps<TagComboboxItem, true>['filter']> = (tag, query) => tag.name.includes(query)
const TAG_COMBOBOX_FILTER: NonNullable<ComboboxProps<TagComboboxItem, true>['filter']> = (tag, query) => tag.name.includes(query)
const tagToString = (tag: TagComboboxItem) => tag.name
const isSameTag = (item: TagComboboxItem, value: TagComboboxItem) => item.id === value.id
type TagSelectorRootProps = Omit<
ComboboxRootProps<TagComboboxItem, true>,
ComboboxProps<TagComboboxItem, true>,
| 'items'
| 'multiple'
| 'value'