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. - Missing `package.json#exports` entry for a new primitive.
- Internal package imports using workspace subpaths instead of relative paths. - Internal package imports using workspace subpaths instead of relative paths.
- Exported props using internal-only types that consumers cannot import from the component subpath. - 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`. 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 ## Props And State
Flag: 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. 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 ## Forms
Flag: Flag:
- Form-like UI using unrelated `Input` and `Button` pieces without a submit boundary. - 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`. - Select fields using `FieldLabel` instead of `SelectLabel`.
- Slider fields using a generic label instead of `SliderLabel`. - 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. - 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/`. `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`). - 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. - 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`. - 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 */ }`. - 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. - 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>`. - 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 { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog' import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog'
import { Drawer, DrawerPopup, DrawerTrigger } from '@langgenius/dify-ui/drawer' 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 { Form } from '@langgenius/dify-ui/form'
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd' import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' 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. 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 ## Primitives
| Category | Subpath | Notes | | 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 `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. 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 ```tsx
<FieldRoot name="allowedNetworkProtocols"> <Field name="allowedNetworkProtocols">
<FieldsetRoot render={<CheckboxGroup />}> <Fieldset render={<CheckboxGroup />}>
<FieldsetLegend>Allowed network protocols</FieldsetLegend> <FieldsetLegend>Allowed network protocols</FieldsetLegend>
<FieldItem> <FieldItem>
<FieldLabel className="flex items-center gap-2"> <FieldLabel className="flex items-center gap-2">
@ -95,11 +97,11 @@ Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group
HTTPS HTTPS
</FieldLabel> </FieldLabel>
</FieldItem> </FieldItem>
</FieldsetRoot> </Fieldset>
</FieldRoot> </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 ## Typed value contracts

View File

@ -17,26 +17,26 @@ import { parsePlacement } from '../placement'
export type { Placement } export type { Placement }
export type AutocompleteRootProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue> export type AutocompleteProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
export type AutocompleteRootGroupedProps< export type AutocompleteGroupedProps<
Items extends readonly { items: readonly unknown[] }[], Items extends readonly { items: readonly unknown[] }[],
> = Omit<AutocompleteRootProps<Items[number]['items'][number]>, 'items'> & { > = Omit<AutocompleteProps<Items[number]['items'][number]>, 'items'> & {
items: Items items: Items
} }
export type AutocompleteRootFlatProps<ItemValue> export type AutocompleteFlatProps<ItemValue>
= Omit<AutocompleteRootProps<ItemValue>, 'items'> = Omit<AutocompleteProps<ItemValue>, 'items'>
& { & {
items?: readonly ItemValue[] items?: readonly ItemValue[]
} }
export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>( export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
props: AutocompleteRootGroupedProps<Items>, props: AutocompleteGroupedProps<Items>,
): React.JSX.Element ): React.JSX.Element
export function Autocomplete<ItemValue>( export function Autocomplete<ItemValue>(
props: AutocompleteRootFlatProps<ItemValue>, props: AutocompleteFlatProps<ItemValue>,
): React.JSX.Element ): React.JSX.Element
export function Autocomplete( export function Autocomplete(
props: AutocompleteRootProps<unknown>, props: AutocompleteProps<unknown>,
): React.JSX.Element { ): React.JSX.Element {
return <BaseAutocomplete.Root {...props} /> return <BaseAutocomplete.Root {...props} />
} }
@ -48,8 +48,8 @@ export const AutocompleteRow = BaseAutocomplete.Row
export const useAutocompleteFilter = BaseAutocomplete.useFilter export const useAutocompleteFilter = BaseAutocomplete.useFilter
export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
export type AutocompleteRootChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails export type AutocompleteChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
export type AutocompleteRootHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails export type AutocompleteHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
const autocompletePopupClassName = [ 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', '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 * as React from 'react'
import { render } from 'vitest-browser-react' import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox' import { Checkbox } from '../../checkbox'
import { FieldItem, FieldLabel, FieldRoot } from '../../field' import { Field, FieldItem, FieldLabel } from '../../field'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset' import { Fieldset, FieldsetLegend } from '../../fieldset'
import { CheckboxGroup } from '../index' import { CheckboxGroup } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement 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 () => { it('should compose with Dify UI Field and Fieldset without losing labels', async () => {
const onValueChange = vi.fn() const onValueChange = vi.fn()
const screen = await render( const screen = await render(
<FieldRoot name="features"> <Field name="features">
<FieldsetRoot render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}> <Fieldset render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}>
<FieldsetLegend>Features</FieldsetLegend> <FieldsetLegend>Features</FieldsetLegend>
<FieldItem> <FieldItem>
<FieldLabel> <FieldLabel>
@ -61,8 +61,8 @@ describe('CheckboxGroup', () => {
Analytics Analytics
</FieldLabel> </FieldLabel>
</FieldItem> </FieldItem>
</FieldsetRoot> </Fieldset>
</FieldRoot>, </Field>,
) )
const analytics = screen.getByRole('checkbox', { name: 'Analytics' }) const analytics = screen.getByRole('checkbox', { name: 'Analytics' })

View File

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

View File

@ -1,7 +1,7 @@
import { render } from 'vitest-browser-react' import { render } from 'vitest-browser-react'
import { import {
Collapsible,
CollapsiblePanel, CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger, CollapsibleTrigger,
} from '../index' } from '../index'
@ -10,10 +10,10 @@ const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElem
describe('Collapsible wrappers', () => { describe('Collapsible wrappers', () => {
it('renders the Base UI anatomy with an accessible trigger', async () => { it('renders the Base UI anatomy with an accessible trigger', async () => {
const screen = await render( const screen = await render(
<CollapsibleRoot defaultOpen data-testid="collapsible-root"> <Collapsible defaultOpen data-testid="collapsible-root">
<CollapsibleTrigger>Recovery keys</CollapsibleTrigger> <CollapsibleTrigger>Recovery keys</CollapsibleTrigger>
<CollapsiblePanel>Panel content</CollapsiblePanel> <CollapsiblePanel>Panel content</CollapsiblePanel>
</CollapsibleRoot>, </Collapsible>,
) )
await expect.element(screen.getByTestId('collapsible-root')).toBeInTheDocument() 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 () => { it('toggles open state through the trigger without caller-owned state', async () => {
const screen = await render( const screen = await render(
<CollapsibleRoot> <Collapsible>
<CollapsibleTrigger>Toggle section</CollapsibleTrigger> <CollapsibleTrigger>Toggle section</CollapsibleTrigger>
<CollapsiblePanel>Hidden content</CollapsiblePanel> <CollapsiblePanel>Hidden content</CollapsiblePanel>
</CollapsibleRoot>, </Collapsible>,
) )
const trigger = screen.getByRole('button', { name: 'Toggle section' }) const trigger = screen.getByRole('button', { name: 'Toggle section' })
@ -40,10 +40,10 @@ describe('Collapsible wrappers', () => {
it('forwards className to every compound part', async () => { it('forwards className to every compound part', async () => {
const screen = await render( const screen = await render(
<CollapsibleRoot defaultOpen className="custom-root"> <Collapsible defaultOpen className="custom-root">
<CollapsibleTrigger className="custom-trigger">Custom</CollapsibleTrigger> <CollapsibleTrigger className="custom-trigger">Custom</CollapsibleTrigger>
<CollapsiblePanel className="custom-panel">Custom panel</CollapsiblePanel> <CollapsiblePanel className="custom-panel">Custom panel</CollapsiblePanel>
</CollapsibleRoot>, </Collapsible>,
) )
await expect.element(screen.getByRole('button', { name: 'Custom' })).toHaveClass('custom-trigger') 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 () => { it('passes Base UI panel props through to the panel', async () => {
const screen = await render( const screen = await render(
<CollapsibleRoot defaultOpen> <Collapsible defaultOpen>
<CollapsibleTrigger>Styled trigger</CollapsibleTrigger> <CollapsibleTrigger>Styled trigger</CollapsibleTrigger>
<CollapsiblePanel keepMounted>Styled panel</CollapsiblePanel> <CollapsiblePanel keepMounted>Styled panel</CollapsiblePanel>
</CollapsibleRoot>, </Collapsible>,
) )
await expect.element(screen.getByRole('button', { name: 'Styled trigger' })).toHaveAttribute('data-panel-open', '') 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 type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react' import * as React from 'react'
import { import {
Collapsible,
CollapsiblePanel, CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger, CollapsibleTrigger,
} from '.' } from '.'
import { cn } from '../cn' import { cn } from '../cn'
const meta = { const meta = {
title: 'Base/UI/Collapsible', title: 'Base/UI/Collapsible',
component: CollapsibleRoot, component: Collapsible,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
docs: { docs: {
@ -19,7 +19,7 @@ const meta = {
}, },
}, },
tags: ['autodocs'], tags: ['autodocs'],
} satisfies Meta<typeof CollapsibleRoot> } satisfies Meta<typeof Collapsible>
export default meta export default meta
type Story = StoryObj<typeof meta> type Story = StoryObj<typeof meta>
@ -67,25 +67,25 @@ export const Anatomy: Story = {
defaultOpen: true, defaultOpen: true,
}, },
render: args => ( render: args => (
<CollapsibleRoot {...args} className={rootClassName}> <Collapsible {...args} className={rootClassName}>
<RecoveryKeys /> <RecoveryKeys />
</CollapsibleRoot> </Collapsible>
), ),
} }
export const DefaultClosed: Story = { export const DefaultClosed: Story = {
render: () => ( render: () => (
<CollapsibleRoot className={rootClassName}> <Collapsible className={rootClassName}>
<RecoveryKeys /> <RecoveryKeys />
</CollapsibleRoot> </Collapsible>
), ),
} }
export const DefaultOpen: Story = { export const DefaultOpen: Story = {
render: () => ( render: () => (
<CollapsibleRoot defaultOpen className={rootClassName}> <Collapsible defaultOpen className={rootClassName}>
<RecoveryKeys /> <RecoveryKeys />
</CollapsibleRoot> </Collapsible>
), ),
} }
@ -102,9 +102,9 @@ export const Controlled: Story = {
> >
{open ? 'Close panel' : 'Open panel'} {open ? 'Close panel' : 'Open panel'}
</button> </button>
<CollapsibleRoot open={open} onOpenChange={setOpen} className={rootClassName}> <Collapsible open={open} onOpenChange={setOpen} className={rootClassName}>
<RecoveryKeys /> <RecoveryKeys />
</CollapsibleRoot> </Collapsible>
</div> </div>
) )
}, },
@ -112,25 +112,25 @@ export const Controlled: Story = {
export const Disabled: Story = { export const Disabled: Story = {
render: () => ( render: () => (
<CollapsibleRoot disabled className={rootClassName}> <Collapsible disabled className={rootClassName}>
<RecoveryKeys /> <RecoveryKeys />
</CollapsibleRoot> </Collapsible>
), ),
} }
export const KeepMounted: Story = { export const KeepMounted: Story = {
render: () => ( render: () => (
<CollapsibleRoot className={rootClassName}> <Collapsible className={rootClassName}>
<RecoveryKeys panelProps={{ keepMounted: true }} /> <RecoveryKeys panelProps={{ keepMounted: true }} />
</CollapsibleRoot> </Collapsible>
), ),
} }
export const HiddenUntilFound: Story = { export const HiddenUntilFound: Story = {
render: () => ( render: () => (
<CollapsibleRoot className={rootClassName}> <Collapsible className={rootClassName}>
<RecoveryKeys panelProps={{ hiddenUntilFound: true }} /> <RecoveryKeys panelProps={{ hiddenUntilFound: true }} />
</CollapsibleRoot> </Collapsible>
), ),
} }
@ -159,7 +159,7 @@ export const SettingsSections: Story = {
render: () => ( render: () => (
<div className={sectionRootClassName}> <div className={sectionRootClassName}>
{settingSections.map((section, index) => ( {settingSections.map((section, index) => (
<CollapsibleRoot <Collapsible
key={section.title} key={section.title}
defaultOpen={section.defaultOpen} defaultOpen={section.defaultOpen}
className={cn(index > 0 && 'mt-px')} className={cn(index > 0 && 'mt-px')}
@ -176,7 +176,7 @@ export const SettingsSections: Story = {
{section.description} {section.description}
</div> </div>
</CollapsiblePanel> </CollapsiblePanel>
</CollapsibleRoot> </Collapsible>
))} ))}
</div> </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 { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
import { cn } from '../cn' import { cn } from '../cn'
export type CollapsibleRootProps export type CollapsibleProps
= Omit<BaseCollapsibleNS.Root.Props, 'className'> = Omit<BaseCollapsibleNS.Root.Props, 'className'>
& { & {
className?: string className?: string
} }
export function CollapsibleRoot({ export function Collapsible({
className, className,
...props ...props
}: CollapsibleRootProps) { }: CollapsibleProps) {
return ( return (
<BaseCollapsible.Root <BaseCollapsible.Root
className={cn('flex min-w-0 flex-col', className)} className={cn('flex min-w-0 flex-col', className)}

View File

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

View File

@ -17,11 +17,11 @@ import { parsePlacement } from '../placement'
export type { 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> = BaseCombobox.Root.Props<Value, Multiple>
export function Combobox<Value, Multiple extends boolean | undefined = false>( export function Combobox<Value, Multiple extends boolean | undefined = false>(
props: ComboboxRootProps<Value, Multiple>, props: ComboboxProps<Value, Multiple>,
): React.JSX.Element { ): React.JSX.Element {
return <BaseCombobox.Root {...props} /> return <BaseCombobox.Root {...props} />
} }
@ -33,8 +33,8 @@ export const ComboboxRow = BaseCombobox.Row
export const useComboboxFilter = BaseCombobox.useFilter export const useComboboxFilter = BaseCombobox.useFilter
export const useComboboxFilteredItems = BaseCombobox.useFilteredItems export const useComboboxFilteredItems = BaseCombobox.useFilteredItems
export type ComboboxRootChangeEventDetails = BaseCombobox.Root.ChangeEventDetails export type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
export type ComboboxRootHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails export type ComboboxHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
const comboboxPopupClassName = [ 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', '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, DialogViewport,
} from '.' } from '.'
import { Button } from '../button' 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 { Form } from '../form'
import { Input } from '../input' import { Input } from '../input'
import { import {
@ -251,12 +251,12 @@ const FormDialogDemo = () => {
className="grid gap-4 pt-5" className="grid gap-4 pt-5"
onFormSubmit={() => setOpen(false)} onFormSubmit={() => setOpen(false)}
> >
<FieldRoot name="name"> <Field name="name">
<FieldLabel>Name</FieldLabel> <FieldLabel>Name</FieldLabel>
<FieldControl required placeholder="Production API" /> <FieldControl required placeholder="Production API" />
<FieldError match="valueMissing">Name is required.</FieldError> <FieldError match="valueMissing">Name is required.</FieldError>
</FieldRoot> </Field>
<FieldRoot name="endpoint"> <Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel> <FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" /> <FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription> <FieldDescription>
@ -271,8 +271,8 @@ const FormDialogDemo = () => {
</FieldDescription> </FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError> <FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError> <FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot> </Field>
<FieldRoot <Field
name="apiKey" name="apiKey"
validate={(value) => { validate={(value) => {
if (typeof value === 'string' && value.length > 0 && value.length < 5) if (typeof value === 'string' && value.length > 0 && value.length < 5)
@ -285,7 +285,7 @@ const FormDialogDemo = () => {
<FieldControl required placeholder="sk-..." /> <FieldControl required placeholder="sk-..." />
<FieldError match="valueMissing">API key is required.</FieldError> <FieldError match="valueMissing">API key is required.</FieldError>
<FieldError match="customError" /> <FieldError match="customError" />
</FieldRoot> </Field>
<div className="mt-2 flex items-center justify-end gap-2"> <div className="mt-2 flex items-center justify-end gap-2">
<Button type="button" onClick={() => setOpen(false)}> <Button type="button" onClick={() => setOpen(false)}>
Cancel Cancel

View File

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

View File

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

View File

@ -1,15 +1,15 @@
import { render } from 'vitest-browser-react' import { render } from 'vitest-browser-react'
import { Checkbox } from '../../checkbox' import { Checkbox } from '../../checkbox'
import { CheckboxGroup } from '../../checkbox-group' import { CheckboxGroup } from '../../checkbox-group'
import { FieldsetLegend, FieldsetRoot } from '../../fieldset' import { Fieldset, FieldsetLegend } from '../../fieldset'
import { Form } from '../../form' import { Form } from '../../form'
import { import {
Field,
FieldControl, FieldControl,
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldItem, FieldItem,
FieldLabel, FieldLabel,
FieldRoot,
} from '../index' } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -19,12 +19,12 @@ describe('Field primitives', () => {
const onFormSubmit = vi.fn() const onFormSubmit = vi.fn()
const screen = await render( const screen = await render(
<Form aria-label="profile form" onFormSubmit={onFormSubmit}> <Form aria-label="profile form" onFormSubmit={onFormSubmit}>
<FieldRoot name="email"> <Field name="email">
<FieldLabel>Email</FieldLabel> <FieldLabel>Email</FieldLabel>
<FieldControl type="email" required /> <FieldControl type="email" required />
<FieldDescription>Used for account notifications.</FieldDescription> <FieldDescription>Used for account notifications.</FieldDescription>
<FieldError match="valueMissing">Email is required.</FieldError> <FieldError match="valueMissing">Email is required.</FieldError>
</FieldRoot> </Field>
<button type="submit">Save</button> <button type="submit">Save</button>
</Form>, </Form>,
) )
@ -55,10 +55,10 @@ describe('Field primitives', () => {
const onFormSubmit = vi.fn() const onFormSubmit = vi.fn()
const screen = await render( const screen = await render(
<Form aria-label="settings form" onFormSubmit={onFormSubmit}> <Form aria-label="settings form" onFormSubmit={onFormSubmit}>
<FieldRoot name="apiKey"> <Field name="apiKey">
<FieldLabel>API key</FieldLabel> <FieldLabel>API key</FieldLabel>
<FieldControl defaultValue="sk-test" required /> <FieldControl defaultValue="sk-test" required />
</FieldRoot> </Field>
<button type="submit">Save</button> <button type="submit">Save</button>
</Form>, </Form>,
) )
@ -71,8 +71,8 @@ describe('Field primitives', () => {
it('should support external invalid state without requiring FieldControl', async () => { it('should support external invalid state without requiring FieldControl', async () => {
const screen = await render( const screen = await render(
<FieldRoot name="features" invalid> <Field name="features" invalid>
<FieldsetRoot render={<CheckboxGroup value={['search']} />}> <Fieldset render={<CheckboxGroup value={['search']} />}>
<FieldsetLegend>Features</FieldsetLegend> <FieldsetLegend>Features</FieldsetLegend>
<FieldItem> <FieldItem>
<FieldLabel className="flex items-center gap-2"> <FieldLabel className="flex items-center gap-2">
@ -81,8 +81,8 @@ describe('Field primitives', () => {
</FieldLabel> </FieldLabel>
</FieldItem> </FieldItem>
<FieldError match>Choose at least one feature.</FieldError> <FieldError match>Choose at least one feature.</FieldError>
</FieldsetRoot> </Fieldset>
</FieldRoot>, </Field>,
) )
await expect.element(screen.getByRole('group', { name: 'Features' })).toBeInTheDocument() await expect.element(screen.getByRole('group', { name: 'Features' })).toBeInTheDocument()
@ -91,10 +91,10 @@ describe('Field primitives', () => {
it('should expose the read-only state', async () => { it('should expose the read-only state', async () => {
const screen = await render( const screen = await render(
<FieldRoot name="token"> <Field name="token">
<FieldLabel>Token</FieldLabel> <FieldLabel>Token</FieldLabel>
<FieldControl readOnly defaultValue="readonly-token" /> <FieldControl readOnly defaultValue="readonly-token" />
</FieldRoot>, </Field>,
) )
await expect.element(screen.getByRole('textbox', { name: 'Token' })).toHaveAttribute('readonly') 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 type { Meta, StoryObj } from '@storybook/react-vite'
import { Button } from '../button' import { Button } from '../button'
import { import {
Field,
FieldControl, FieldControl,
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldLabel, FieldLabel,
FieldRoot,
} from './index' } from './index'
const meta = { const meta = {
title: 'Base/Form/Field', title: 'Base/Form/Field',
component: FieldRoot, component: Field,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
docs: { docs: {
description: { 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'], tags: ['autodocs'],
} satisfies Meta<typeof FieldRoot> } satisfies Meta<typeof Field>
export default meta export default meta
@ -29,13 +29,13 @@ type Story = StoryObj<typeof meta>
export const TextField: Story = { export const TextField: Story = {
render: () => ( render: () => (
<form className="grid w-96 gap-4"> <form className="grid w-96 gap-4">
<FieldRoot name="endpoint"> <Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel> <FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" /> <FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription>Used as the base URL for extension requests.</FieldDescription> <FieldDescription>Used as the base URL for extension requests.</FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError> <FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError> <FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot> </Field>
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button> <Button type="submit" variant="primary">Save</Button>
</div> </div>
@ -46,24 +46,24 @@ export const TextField: Story = {
export const MultipleFields: Story = { export const MultipleFields: Story = {
render: () => ( render: () => (
<form className="grid w-96 gap-4"> <form className="grid w-96 gap-4">
<FieldRoot name="name"> <Field name="name">
<FieldLabel>Name</FieldLabel> <FieldLabel>Name</FieldLabel>
<FieldControl required placeholder="Production API" /> <FieldControl required placeholder="Production API" />
<FieldError match="valueMissing">Name is required.</FieldError> <FieldError match="valueMissing">Name is required.</FieldError>
</FieldRoot> </Field>
<FieldRoot name="endpoint"> <Field name="endpoint">
<FieldLabel>Endpoint</FieldLabel> <FieldLabel>Endpoint</FieldLabel>
<FieldControl type="url" required placeholder="https://api.example.com" /> <FieldControl type="url" required placeholder="https://api.example.com" />
<FieldDescription>Used as the base URL for extension requests.</FieldDescription> <FieldDescription>Used as the base URL for extension requests.</FieldDescription>
<FieldError match="valueMissing">Endpoint is required.</FieldError> <FieldError match="valueMissing">Endpoint is required.</FieldError>
<FieldError match="typeMismatch">Enter a valid URL.</FieldError> <FieldError match="typeMismatch">Enter a valid URL.</FieldError>
</FieldRoot> </Field>
<FieldRoot name="apiKey"> <Field name="apiKey">
<FieldLabel>API key</FieldLabel> <FieldLabel>API key</FieldLabel>
<FieldControl required placeholder="sk-..." /> <FieldControl required placeholder="sk-..." />
<FieldDescription>Stored with the extension configuration.</FieldDescription> <FieldDescription>Stored with the extension configuration.</FieldDescription>
<FieldError match="valueMissing">API key is required.</FieldError> <FieldError match="valueMissing">API key is required.</FieldError>
</FieldRoot> </Field>
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" variant="primary">Save</Button> <Button type="submit" variant="primary">Save</Button>
</div> </div>
@ -73,39 +73,39 @@ export const MultipleFields: Story = {
export const ExternalInvalidState: Story = { export const ExternalInvalidState: Story = {
render: () => ( render: () => (
<FieldRoot name="apiKey" invalid className="w-96"> <Field name="apiKey" invalid className="w-96">
<FieldLabel>API key</FieldLabel> <FieldLabel>API key</FieldLabel>
<FieldControl defaultValue="expired-key" /> <FieldControl defaultValue="expired-key" />
<FieldError match>API key has expired.</FieldError> <FieldError match>API key has expired.</FieldError>
</FieldRoot> </Field>
), ),
} }
export const Sizes: Story = { export const Sizes: Story = {
render: () => ( render: () => (
<div className="grid w-96 gap-4"> <div className="grid w-96 gap-4">
<FieldRoot name="smallEndpoint"> <Field name="smallEndpoint">
<FieldLabel>Small</FieldLabel> <FieldLabel>Small</FieldLabel>
<FieldControl size="small" placeholder="Small input" /> <FieldControl size="small" placeholder="Small input" />
</FieldRoot> </Field>
<FieldRoot name="regularEndpoint"> <Field name="regularEndpoint">
<FieldLabel>Regular</FieldLabel> <FieldLabel>Regular</FieldLabel>
<FieldControl placeholder="Regular input" /> <FieldControl placeholder="Regular input" />
</FieldRoot> </Field>
<FieldRoot name="largeEndpoint"> <Field name="largeEndpoint">
<FieldLabel>Large</FieldLabel> <FieldLabel>Large</FieldLabel>
<FieldControl size="large" placeholder="Large input" /> <FieldControl size="large" placeholder="Large input" />
</FieldRoot> </Field>
</div> </div>
), ),
} }
export const ReadOnly: Story = { export const ReadOnly: Story = {
render: () => ( render: () => (
<FieldRoot name="readonlyEndpoint" className="w-96"> <Field name="readonlyEndpoint" className="w-96">
<FieldLabel>Endpoint</FieldLabel> <FieldLabel>Endpoint</FieldLabel>
<FieldControl readOnly defaultValue="https://api.example.com" /> <FieldControl readOnly defaultValue="https://api.example.com" />
<FieldDescription>This value is managed by the workspace owner.</FieldDescription> <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 { cn } from '../cn'
import { formLabelClassName, textControlVariants } from '../form-control-shared' import { formLabelClassName, textControlVariants } from '../form-control-shared'
export type FieldRootProps export type FieldProps
= Omit<BaseFieldNS.Root.Props, 'className'> = Omit<BaseFieldNS.Root.Props, 'className'>
& { & {
className?: string className?: string
} }
export type FieldRootActions = BaseFieldNS.Root.Actions export type FieldActions = BaseFieldNS.Root.Actions
export function FieldRoot({ export function Field({
className, className,
...props ...props
}: FieldRootProps) { }: FieldProps) {
return ( return (
<BaseField.Root <BaseField.Root
className={cn('group/field grid min-w-0 gap-1', className)} className={cn('group/field grid min-w-0 gap-1', className)}

View File

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

View File

@ -1,25 +1,25 @@
import type { Meta, StoryObj } from '@storybook/react-vite' import type { Meta, StoryObj } from '@storybook/react-vite'
import { Checkbox } from '../checkbox' import { Checkbox } from '../checkbox'
import { CheckboxGroup } from '../checkbox-group' import { CheckboxGroup } from '../checkbox-group'
import { FieldItem, FieldLabel, FieldRoot } from '../field' import { Field, FieldItem, FieldLabel } from '../field'
import { import {
Fieldset,
FieldsetLegend, FieldsetLegend,
FieldsetRoot,
} from './index' } from './index'
const meta = { const meta = {
title: 'Base/Form/Fieldset', title: 'Base/Form/Fieldset',
component: FieldsetRoot, component: Fieldset,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
docs: { docs: {
description: { 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'], tags: ['autodocs'],
} satisfies Meta<typeof FieldsetRoot> } satisfies Meta<typeof Fieldset>
export default meta export default meta
@ -27,8 +27,8 @@ type Story = StoryObj<typeof meta>
export const CheckboxGroupField: Story = { export const CheckboxGroupField: Story = {
render: () => ( render: () => (
<FieldRoot name="scopes" className="w-80"> <Field name="scopes" className="w-80">
<FieldsetRoot render={<CheckboxGroup defaultValue={['read']} />}> <Fieldset render={<CheckboxGroup defaultValue={['read']} />}>
<FieldsetLegend>Scopes</FieldsetLegend> <FieldsetLegend>Scopes</FieldsetLegend>
<div className="grid gap-2"> <div className="grid gap-2">
<FieldItem> <FieldItem>
@ -50,7 +50,7 @@ export const CheckboxGroupField: Story = {
</FieldLabel> </FieldLabel>
</FieldItem> </FieldItem>
</div> </div>
</FieldsetRoot> </Fieldset>
</FieldRoot> </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 { Fieldset as BaseFieldset } from '@base-ui/react/fieldset'
import { cn } from '../cn' import { cn } from '../cn'
export type FieldsetRootProps export type FieldsetProps
= Omit<BaseFieldsetNS.Root.Props, 'className'> = Omit<BaseFieldsetNS.Root.Props, 'className'>
& { & {
className?: string className?: string
} }
export function FieldsetRoot({ export function Fieldset({
className, className,
...props ...props
}: FieldsetRootProps) { }: FieldsetProps) {
return ( return (
<BaseFieldset.Root <BaseFieldset.Root
className={cn('m-0 min-w-0 border-0 p-0', className)} 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 { render } from 'vitest-browser-react'
import { import {
FileTree,
FileTreeFile, FileTreeFile,
FileTreeFolder, FileTreeFolder,
FileTreeFolderPanel, FileTreeFolderPanel,
@ -7,7 +8,6 @@ import {
FileTreeIcon, FileTreeIcon,
FileTreeLabel, FileTreeLabel,
FileTreeList, FileTreeList,
FileTreeRoot,
} from '../index' } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
@ -18,7 +18,7 @@ function TestFileTree({
onPreview?: (itemId: string) => void onPreview?: (itemId: string) => void
}) { }) {
return ( return (
<FileTreeRoot aria-label="Project files"> <FileTree aria-label="Project files">
<FileTreeList> <FileTreeList>
<FileTreeFolder defaultOpen> <FileTreeFolder defaultOpen>
<FileTreeFolderTrigger> <FileTreeFolderTrigger>
@ -53,7 +53,7 @@ function TestFileTree({
<FileTreeLabel>package.json</FileTreeLabel> <FileTreeLabel>package.json</FileTreeLabel>
</FileTreeFile> </FileTreeFile>
</FileTreeList> </FileTreeList>
</FileTreeRoot> </FileTree>
) )
} }
@ -101,14 +101,14 @@ describe('FileTree', () => {
it('does not activate disabled file buttons', async () => { it('does not activate disabled file buttons', async () => {
const onPreview = vi.fn() const onPreview = vi.fn()
const screen = await render( const screen = await render(
<FileTreeRoot aria-label="Disabled files"> <FileTree aria-label="Disabled files">
<FileTreeList> <FileTreeList>
<FileTreeFile disabled onClick={() => onPreview('disabled')}> <FileTreeFile disabled onClick={() => onPreview('disabled')}>
<FileTreeIcon type="file" /> <FileTreeIcon type="file" />
<FileTreeLabel>disabled.txt</FileTreeLabel> <FileTreeLabel>disabled.txt</FileTreeLabel>
</FileTreeFile> </FileTreeFile>
</FileTreeList> </FileTreeList>
</FileTreeRoot>, </FileTree>,
) )
asHTMLElement(screen.getByRole('button', { name: 'disabled.txt' }).element()).click() 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 () => { it('resolves disabled folder triggers through the collapsible state', async () => {
const onOpenChange = vi.fn() const onOpenChange = vi.fn()
const screen = await render( const screen = await render(
<FileTreeRoot aria-label="Disabled folders"> <FileTree aria-label="Disabled folders">
<FileTreeList> <FileTreeList>
<FileTreeFolder disabled defaultOpen onOpenChange={onOpenChange}> <FileTreeFolder disabled defaultOpen onOpenChange={onOpenChange}>
<FileTreeFolderTrigger> <FileTreeFolderTrigger>
@ -136,7 +136,7 @@ describe('FileTree', () => {
</FileTreeFolderPanel> </FileTreeFolderPanel>
</FileTreeFolder> </FileTreeFolder>
</FileTreeList> </FileTreeList>
</FileTreeRoot>, </FileTree>,
) )
const trigger = screen.getByRole('button', { name: 'locked' }) const trigger = screen.getByRole('button', { name: 'locked' })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,8 +15,8 @@ import { Meter as BaseMeter } from '@base-ui/react/meter'
import { cva } from 'class-variance-authority' import { cva } from 'class-variance-authority'
import { cn } from '../cn' import { cn } from '../cn'
export const MeterRoot = BaseMeter.Root export const Meter = BaseMeter.Root
export type MeterRootProps = BaseMeter.Root.Props export type MeterProps = BaseMeter.Root.Props
const meterTrackClassName const meterTrackClassName
= 'relative block h-1 w-full overflow-hidden rounded-md bg-components-progress-bar-bg' = '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 * as React from 'react'
import { render } from 'vitest-browser-react' import { render } from 'vitest-browser-react'
import { import {
Field,
FieldLabel, FieldLabel,
FieldRoot,
} from '../../field' } from '../../field'
import { import {
NumberField, NumberField,
@ -79,14 +79,14 @@ describe('NumberField wrapper', () => {
it('should surface field invalid state on the visual group', async () => { it('should surface field invalid state on the visual group', async () => {
const screen = await render( const screen = await render(
<FieldRoot name="amount" invalid> <Field name="amount" invalid>
<FieldLabel>Amount</FieldLabel> <FieldLabel>Amount</FieldLabel>
<NumberField defaultValue={8}> <NumberField defaultValue={8}>
<NumberFieldGroup data-testid="group"> <NumberFieldGroup data-testid="group">
<NumberFieldInput /> <NumberFieldInput />
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot>, </Field>,
) )
await expect.element(screen.getByTestId('group')).toHaveAttribute('data-invalid') 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 * as React from 'react'
import { Button } from '../button' import { Button } from '../button'
import { import {
Field,
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldLabel, FieldLabel,
FieldRoot,
} from '../field' } from '../field'
import { Form } from '../form' import { Form } from '../form'
import { import {
@ -25,7 +25,7 @@ const meta = {
layout: 'centered', layout: 'centered',
docs: { docs: {
description: { 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 = { export const States: Story = {
render: () => ( render: () => (
<div className="grid w-80 gap-3"> <div className="grid w-80 gap-3">
<FieldRoot name="placeholderState"> <Field name="placeholderState">
<FieldLabel>Placeholder</FieldLabel> <FieldLabel>Placeholder</FieldLabel>
<NumberField min={0} max={100}> <NumberField min={0} max={100}>
<NumberFieldGroup> <NumberFieldGroup>
@ -152,8 +152,8 @@ export const States: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
<FieldRoot name="filledState"> <Field name="filledState">
<FieldLabel>Filled</FieldLabel> <FieldLabel>Filled</FieldLabel>
<NumberField defaultValue={85} min={0} max={100}> <NumberField defaultValue={85} min={0} max={100}>
<NumberFieldGroup> <NumberFieldGroup>
@ -165,8 +165,8 @@ export const States: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
<FieldRoot name="invalidState" invalid> <Field name="invalidState" invalid>
<FieldLabel>Invalid</FieldLabel> <FieldLabel>Invalid</FieldLabel>
<NumberField defaultValue={120} min={0} max={100}> <NumberField defaultValue={120} min={0} max={100}>
<NumberFieldGroup> <NumberFieldGroup>
@ -179,8 +179,8 @@ export const States: Story = {
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
<FieldError match>Use a value from 0 to 100.</FieldError> <FieldError match>Use a value from 0 to 100.</FieldError>
</FieldRoot> </Field>
<FieldRoot name="disabledState"> <Field name="disabledState">
<FieldLabel>Disabled</FieldLabel> <FieldLabel>Disabled</FieldLabel>
<NumberField defaultValue={5} min={0} max={10} disabled> <NumberField defaultValue={5} min={0} max={10} disabled>
<NumberFieldGroup> <NumberFieldGroup>
@ -191,8 +191,8 @@ export const States: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
<FieldRoot name="readonlyState"> <Field name="readonlyState">
<FieldLabel>Read-only</FieldLabel> <FieldLabel>Read-only</FieldLabel>
<NumberField defaultValue={92} min={0} max={100} readOnly> <NumberField defaultValue={92} min={0} max={100} readOnly>
<NumberFieldGroup> <NumberFieldGroup>
@ -204,7 +204,7 @@ export const States: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
</div> </div>
), ),
} }
@ -213,7 +213,7 @@ function ControlledDemo() {
const [value, setValue] = React.useState<number | null>(0.82) const [value, setValue] = React.useState<number | null>(0.82)
return ( return (
<FieldRoot name="controlledThreshold"> <Field name="controlledThreshold">
<FieldLabel>Score threshold</FieldLabel> <FieldLabel>Score threshold</FieldLabel>
<NumberField <NumberField
value={value} value={value}
@ -238,7 +238,7 @@ function ControlledDemo() {
{' '} {' '}
{value === null ? 'Empty' : value.toFixed(2)} {value === null ? 'Empty' : value.toFixed(2)}
</FieldDescription> </FieldDescription>
</FieldRoot> </Field>
) )
} }
@ -261,7 +261,7 @@ function FormDemo() {
setSavedValue(String(values.topK ?? '')) setSavedValue(String(values.topK ?? ''))
}} }}
> >
<FieldRoot name="topK"> <Field name="topK">
<FieldLabel>Top K</FieldLabel> <FieldLabel>Top K</FieldLabel>
<NumberField required defaultValue={3} min={1} max={10} step={1}> <NumberField required defaultValue={3} min={1} max={10} step={1}>
<NumberFieldGroup> <NumberFieldGroup>
@ -276,7 +276,7 @@ function FormDemo() {
<FieldError match="valueMissing">Top K is required.</FieldError> <FieldError match="valueMissing">Top K is required.</FieldError>
<FieldError match="rangeUnderflow">Use at least 1.</FieldError> <FieldError match="rangeUnderflow">Use at least 1.</FieldError>
<FieldError match="rangeOverflow">Use 10 or fewer.</FieldError> <FieldError match="rangeOverflow">Use 10 or fewer.</FieldError>
</FieldRoot> </Field>
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button> <Button type="submit" variant="primary">Save Settings</Button>
</div> </div>
@ -298,7 +298,7 @@ export const WithField: Story = {
export const Formatting: Story = { export const Formatting: Story = {
render: () => ( render: () => (
<div className="grid w-80 gap-3"> <div className="grid w-80 gap-3">
<FieldRoot name="currencyBudget"> <Field name="currencyBudget">
<FieldLabel>Budget</FieldLabel> <FieldLabel>Budget</FieldLabel>
<NumberField <NumberField
defaultValue={1200} defaultValue={1200}
@ -318,8 +318,8 @@ export const Formatting: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
<FieldRoot name="temperature"> <Field name="temperature">
<FieldLabel>Temperature</FieldLabel> <FieldLabel>Temperature</FieldLabel>
<NumberField <NumberField
defaultValue={0.7} defaultValue={0.7}
@ -338,7 +338,7 @@ export const Formatting: Story = {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldRoot> </Field>
</div> </div>
), ),
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react' import * as React from 'react'
import { Button } from '../button' import { Button } from '../button'
import { import {
Field,
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldLabel, FieldLabel,
FieldRoot,
} from '../field' } from '../field'
import { Form } from '../form' import { Form } from '../form'
import { Textarea } from './index' import { Textarea } from './index'
@ -17,7 +17,7 @@ const meta = {
layout: 'centered', layout: 'centered',
docs: { docs: {
description: { 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 = { export const States: Story = {
render: () => ( render: () => (
<div className="grid w-80 gap-3"> <div className="grid w-80 gap-3">
<FieldRoot name="placeholderState"> <Field name="placeholderState">
<FieldLabel>Placeholder</FieldLabel> <FieldLabel>Placeholder</FieldLabel>
<Textarea placeholder="Add a description..." rows={3} /> <Textarea placeholder="Add a description..." rows={3} />
</FieldRoot> </Field>
<FieldRoot name="filledState"> <Field name="filledState">
<FieldLabel>Filled</FieldLabel> <FieldLabel>Filled</FieldLabel>
<Textarea defaultValue="Use this dataset for support articles and product FAQs." rows={3} /> <Textarea defaultValue="Use this dataset for support articles and product FAQs." rows={3} />
</FieldRoot> </Field>
<FieldRoot name="invalidState" invalid> <Field name="invalidState" invalid>
<FieldLabel>Invalid</FieldLabel> <FieldLabel>Invalid</FieldLabel>
<Textarea defaultValue="Too short" rows={3} /> <Textarea defaultValue="Too short" rows={3} />
<FieldError match>Use at least 20 characters.</FieldError> <FieldError match>Use at least 20 characters.</FieldError>
</FieldRoot> </Field>
<FieldRoot name="disabledState"> <Field name="disabledState">
<FieldLabel>Disabled</FieldLabel> <FieldLabel>Disabled</FieldLabel>
<Textarea disabled placeholder="Editing is unavailable..." rows={3} /> <Textarea disabled placeholder="Editing is unavailable..." rows={3} />
</FieldRoot> </Field>
<FieldRoot name="readonlyState"> <Field name="readonlyState">
<FieldLabel>Read-only</FieldLabel> <FieldLabel>Read-only</FieldLabel>
<Textarea readOnly defaultValue="Generated from the published workflow configuration." rows={3} /> <Textarea readOnly defaultValue="Generated from the published workflow configuration." rows={3} />
</FieldRoot> </Field>
</div> </div>
), ),
} }
@ -101,7 +101,7 @@ const FormDemo = () => {
setSavedDescription(String(values.description ?? '')) setSavedDescription(String(values.description ?? ''))
}} }}
> >
<FieldRoot name="description"> <Field name="description">
<FieldLabel>Description</FieldLabel> <FieldLabel>Description</FieldLabel>
<Textarea <Textarea
required required
@ -114,7 +114,7 @@ const FormDemo = () => {
<FieldDescription>Shown to teammates when they choose a knowledge source.</FieldDescription> <FieldDescription>Shown to teammates when they choose a knowledge source.</FieldDescription>
<FieldError match="valueMissing">Description is required.</FieldError> <FieldError match="valueMissing">Description is required.</FieldError>
<FieldError match="tooShort">Use at least 20 characters.</FieldError> <FieldError match="tooShort">Use at least 20 characters.</FieldError>
</FieldRoot> </Field>
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" variant="primary">Save Settings</Button> <Button type="submit" variant="primary">Save Settings</Button>
</div> </div>
@ -137,7 +137,7 @@ const ControlledDemo = () => {
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.') const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return ( return (
<FieldRoot name="prompt"> <Field name="prompt">
<FieldLabel>Prompt</FieldLabel> <FieldLabel>Prompt</FieldLabel>
<Textarea <Textarea
value={value} value={value}
@ -146,7 +146,7 @@ const ControlledDemo = () => {
className="resize-y" className="resize-y"
/> />
<FieldDescription>The saved value is updated from the controlled state.</FieldDescription> <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.') const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
return ( return (
<FieldRoot name="limitedPrompt"> <Field name="limitedPrompt">
<FieldLabel>Prompt</FieldLabel> <FieldLabel>Prompt</FieldLabel>
<div className="relative"> <div className="relative">
<Textarea <Textarea
@ -180,7 +180,7 @@ const CharacterCounterDemo = () => {
</div> </div>
</div> </div>
<FieldDescription>Character counters are composed at the usage site when the workflow needs one.</FieldDescription> <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' '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 type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
import { Avatar } from '@langgenius/dify-ui/avatar' import { Avatar } from '@langgenius/dify-ui/avatar'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
@ -71,7 +71,7 @@ export default function AddMemberOrGroupDialog() {
setOpen(nextOpen) setOpen(nextOpen)
} }
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => { const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press') if (details.reason !== 'item-press')
setKeyword(inputValue) setKeyword(inputValue)
} }

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu' } 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 { toast } from '@langgenius/dify-ui/toast'
import { import {
Tooltip, 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"> <AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{t($ => $.deleteAppConfirmContent, { ns: 'app' })} {t($ => $.deleteAppConfirmContent, { ns: 'app' })}
</AlertDialogDescription> </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"> <FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
<Trans <Trans
i18nKey={$ => $.deleteAppConfirmInputLabel} i18nKey={$ => $.deleteAppConfirmInputLabel}
@ -712,7 +712,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
onValueChange={setConfirmDeleteInput} 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" 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> </div>
<AlertDialogActions> <AlertDialogActions>
<AlertDialogCancelButton type="button" disabled={isDeleting}> <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"> <AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{t($ => $.deleteAppConfirmContent, { ns: 'app' })} {t($ => $.deleteAppConfirmContent, { ns: 'app' })}
</AlertDialogDescription> </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"> <FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
<Trans <Trans
i18nKey={$ => $.deleteAppConfirmInputLabel} i18nKey={$ => $.deleteAppConfirmInputLabel}
@ -1315,7 +1315,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
{t($ => $['operation.fill'], { ns: 'common' })} {t($ => $['operation.fill'], { ns: 'common' })}
</button> </button>
</div> </div>
</FieldRoot> </Field>
</div> </div>
<AlertDialogActions> <AlertDialogActions>
<AlertDialogCancelButton type="button" disabled={isDeleting}> <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 { Checkbox } from '@langgenius/dify-ui/checkbox'
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group' import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { FieldDescription, FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' import { Field, FieldDescription, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset' import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge' import Badge from '@/app/components/base/badge'
@ -69,8 +69,8 @@ export const CheckboxList = ({
) )
return ( return (
<FieldRoot name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}> <Field name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}>
<FieldsetRoot <Fieldset
render={( render={(
<CheckboxGroup <CheckboxGroup
aria-label={!label && title ? title : undefined} aria-label={!label && title ? title : undefined}
@ -191,7 +191,7 @@ export const CheckboxList = ({
)} )}
</div> </div>
</div> </div>
</FieldsetRoot> </Fieldset>
</FieldRoot> </Field>
) )
} }

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import type { MeterTone } from '@langgenius/dify-ui/meter'
import type { FC } from 'react' import type { FC } from 'react'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn' 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 { useAtomValue } from 'jotai'
import * as React from 'react' import * as React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -72,11 +72,11 @@ const AppsFull: FC<{ loc: string, className?: string }> = ({
{total} {total}
</div> </div>
</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> <MeterTrack>
<MeterIndicator tone={tone} /> <MeterIndicator tone={tone} />
</MeterTrack> </MeterTrack>
</MeterRoot> </Meter>
</div> </div>
</div> </div>
) )

View File

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

View File

@ -1,5 +1,5 @@
'use client' '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 type { ParentMode, SimpleDocumentDetail } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { import {
@ -119,7 +119,7 @@ export function DocumentPicker({
}) })
const documentsList = data?.data ?? [] const documentsList = data?.data ?? []
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => { const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press') if (details.reason !== 'item-press')
setSearchValue(inputValue) 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 { FC, PropsWithChildren, ReactNode } from 'react'
import type { InputProps } from '@/app/components/base/input' import type { InputProps } from '@/app/components/base/input'
import { 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 label: string
unit?: ReactNode unit?: ReactNode
size?: NumberFieldSize size?: NumberFieldSize

View File

@ -1,6 +1,6 @@
'use client' '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 type { BuiltInMetadataItem, MetadataItem } from '../types'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { import {
@ -83,7 +83,7 @@ export function DatasetMetadataPicker({
resetPicker() resetPicker()
} }
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => { const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press') if (details.reason !== 'item-press')
setQuery(inputValue) 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 { import {
NumberField, NumberField,
NumberFieldControls, NumberFieldControls,
@ -33,7 +33,7 @@ const KeyWordNumber = ({
}, [onKeywordNumberChange]) }, [onKeywordNumberChange])
return ( return (
<FieldsetRoot className="flex items-center gap-x-1"> <Fieldset className="flex items-center gap-x-1">
<FieldsetLegend className="sr-only">{label}</FieldsetLegend> <FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<div className="flex grow items-center gap-x-0.5"> <div className="flex grow items-center gap-x-0.5">
<div className="truncate system-xs-medium text-text-secondary"> <div className="truncate system-xs-medium text-text-secondary">
@ -69,7 +69,7 @@ const KeyWordNumber = ({
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldsetRoot> </Fieldset>
) )
} }

View File

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

View File

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

View File

@ -4,8 +4,8 @@ import type {
NodeOutPutVar, NodeOutPutVar,
} from '@/app/components/workflow/types' } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset' import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio' import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select' import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select'
import { Slider } from '@langgenius/dify-ui/slider' import { Slider } from '@langgenius/dify-ui/slider'
@ -181,7 +181,7 @@ function ParameterItem({
} }
return ( return (
<FieldsetRoot className="flex items-center"> <Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend> <FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
<Slider <Slider
className="w-[120px]" className="w-[120px]"
@ -203,7 +203,7 @@ function ParameterItem({
onChange={handleNumberInputChange} onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur} onBlur={handleNumberInputBlur}
/> />
</FieldsetRoot> </Fieldset>
) )
} }
@ -225,7 +225,7 @@ function ParameterItem({
} }
return ( return (
<FieldsetRoot className="flex items-center"> <Fieldset className="flex items-center">
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend> <FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
<Slider <Slider
className="w-[120px]" className="w-[120px]"
@ -247,7 +247,7 @@ function ParameterItem({
onChange={handleNumberInputChange} onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur} onBlur={handleNumberInputBlur}
/> />
</FieldsetRoot> </Fieldset>
) )
} }
@ -256,8 +256,8 @@ function ParameterItem({
const translatedLabel = parameterRule.label[language] || parameterRule.label.en_US const translatedLabel = parameterRule.label[language] || parameterRule.label.en_US
return ( return (
<FieldRoot name={parameterRule.name} className="contents"> <Field name={parameterRule.name} className="contents">
<FieldsetRoot <Fieldset
render={( render={(
<RadioGroup<boolean> <RadioGroup<boolean>
className="w-[150px] gap-3" className="w-[150px] gap-3"
@ -279,8 +279,8 @@ function ParameterItem({
False False
</FieldLabel> </FieldLabel>
</FieldItem> </FieldItem>
</FieldsetRoot> </Fieldset>
</FieldRoot> </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 { DefaultModel, Model, ModelFeatureEnum, ModelItem } from '../declarations'
import type { ModelSelectorModelPredicate, ModelSelectorValue } from './types' import type { ModelSelectorModelPredicate, ModelSelectorValue } from './types'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
@ -115,7 +115,7 @@ function ModelSelector({
handleSelect(provider.provider, model) handleSelect(provider.provider, model)
}, [handleSelect, modelList]) }, [handleSelect, modelList])
const handleInputValueChange = useCallback((inputValue: string, details: ComboboxRootChangeEventDetails) => { const handleInputValueChange = useCallback((inputValue: string, details: ComboboxChangeEventDetails) => {
if (details.reason !== 'item-press') if (details.reason !== 'item-press')
setInputValue(inputValue) 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 { Trans, useTranslation } from 'react-i18next'
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
import { IS_CLOUD_EDITION } from '@/config' import { IS_CLOUD_EDITION } from '@/config'
@ -55,7 +55,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
/> />
</div> </div>
</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"> <div className="flex items-center justify-between">
<MeterLabel className="system-xs-medium text-text-tertiary"> <MeterLabel className="system-xs-medium text-text-tertiary">
{t($ => $['modelProvider.card.usageLabel'], { ns: 'common' })} {t($ => $['modelProvider.card.usageLabel'], { ns: 'common' })}
@ -73,7 +73,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
<MeterTrack className="bg-components-progress-error-bg"> <MeterTrack className="bg-components-progress-error-bg">
<MeterIndicator tone="error" /> <MeterIndicator tone="error" />
</MeterTrack> </MeterTrack>
</MeterRoot> </Meter>
</div> </div>
) )
} }

View File

@ -5,7 +5,7 @@ import type { InstallState } from '@/app/components/plugins/types'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog' 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 { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast' import { toast } from '@langgenius/dify-ui/toast'
import * as React from 'react' import * as React from 'react'
@ -204,7 +204,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, in
onFormSubmit={handleUrlSubmit} onFormSubmit={handleUrlSubmit}
className="flex flex-col items-start gap-4 self-stretch" 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"> <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> <span className="system-sm-semibold">{t($ => $['installFromGitHub.gitHubRepo'], { ns: 'plugin' })}</span>
</FieldLabel> </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" 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" placeholder="Please enter GitHub repo URL"
/> />
</FieldRoot> </Field>
<div className="mt-4 flex items-center justify-end gap-2 self-stretch"> <div className="mt-4 flex items-center justify-end gap-2 self-stretch">
<Button <Button
variant="secondary" variant="secondary"

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import type { NodeOutPutVar } from '../../../types'
import type { ToolVarInputs } from '../../tool/types' import type { ToolVarInputs } from '../../tool/types'
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { PluginMeta } from '@/app/components/plugins/types' 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 { import {
NumberField, NumberField,
NumberFieldControls, NumberFieldControls,
@ -146,7 +146,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
tooltip={def.tooltip && renderI18nObject(def.tooltip)} tooltip={def.tooltip && renderI18nObject(def.tooltip)}
inline 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> <FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<Slider <Slider
value={value} value={value}
@ -170,7 +170,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
</NumberFieldControls> </NumberFieldControls>
</NumberFieldGroup> </NumberFieldGroup>
</NumberField> </NumberField>
</FieldsetRoot> </Fieldset>
</Field> </Field>
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { import {
Collapsible,
CollapsiblePanel, CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger, CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible' } from '@langgenius/dify-ui/collapsible'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -9,7 +9,7 @@ export function AgentAdvancedSettings() {
const { t } = useTranslation() const { t } = useTranslation()
return ( 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"> <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"> <span className="min-w-0 truncate system-sm-semibold-uppercase text-text-secondary">
{t($ => $['nodes.agent.advancedSetting'], { ns: 'workflow' })} {t($ => $['nodes.agent.advancedSetting'], { ns: 'workflow' })}
@ -22,6 +22,6 @@ export function AgentAdvancedSettings() {
<CollapsiblePanel> <CollapsiblePanel>
<div className="px-4" /> <div className="px-4" />
</CollapsiblePanel> </CollapsiblePanel>
</CollapsibleRoot> </Collapsible>
) )
} }

View File

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

View File

@ -19,7 +19,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu' } from '@langgenius/dify-ui/dropdown-menu'
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
@ -369,7 +369,7 @@ export function AgentRosterField({
) )
return ( 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"> <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"> <FieldLabel className="min-w-0 flex-1 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $['nodes.agent.roster.label'], { ns: 'workflow' })} {t($ => $['nodes.agent.roster.label'], { ns: 'workflow' })}
@ -476,6 +476,6 @@ export function AgentRosterField({
</span> </span>
</div> </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 { AgentOutputTypeOptionValue } from '@/app/components/base/prompt-editor/plugins/agent-output-block/utils'
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types' import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
import { cn } from '@langgenius/dify-ui/cn' 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 { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { $insertNodes } from 'lexical' import { $insertNodes } from 'lexical'
@ -101,7 +101,7 @@ export function AgentTaskField({
}, {}) }, {})
return ( 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"> <div className="flex h-6 items-center gap-1">
<FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary"> <FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $[`${i18nPrefix}.task.label`], { ns: 'workflow' })} {t($ => $[`${i18nPrefix}.task.label`], { ns: 'workflow' })}
@ -156,6 +156,6 @@ export function AgentTaskField({
</PromptEditor> </PromptEditor>
</div> </div>
</div> </div>
</FieldRoot> </Field>
) )
} }

View File

@ -1,7 +1,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import type { IterationNodeType } from './types' import type { IterationNodeType } from './types'
import type { NodePanelProps } from '@/app/components/workflow/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 { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
import { Slider } from '@langgenius/dify-ui/slider' import { Slider } from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch' import { Switch } from '@langgenius/dify-ui/switch'
@ -103,7 +103,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
inputs.is_parallel && ( inputs.is_parallel && (
<div className="px-4 pb-2"> <div className="px-4 pb-2">
<Field title={maxParallelismLabel} isSubTitle tooltip={<div className="w-[230px]">{t($ => $[`${i18nPrefix}.MaxParallelismDesc`], { ns: 'workflow' })}</div>}> <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> <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)) }} /> <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 <Slider
@ -114,7 +114,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
className="mt-4 flex-1 shrink-0" className="mt-4 flex-1 shrink-0"
aria-label={maxParallelismLabel} aria-label={maxParallelismLabel}
/> />
</FieldsetRoot> </Fieldset>
</Field> </Field>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,8 +4,8 @@ import type { ReactNode } from 'react'
import type { AgentBuildDraftChangeSection } from '../build-draft-changes-context' import type { AgentBuildDraftChangeSection } from '../build-draft-changes-context'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { import {
Collapsible,
CollapsiblePanel, CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger, CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible' } from '@langgenius/dify-ui/collapsible'
import { Infotip } from '@/app/components/base/infotip' import { Infotip } from '@/app/components/base/infotip'
@ -62,7 +62,7 @@ export function ConfigureSection({
const isBuildDraftChanged = useIsAgentBuildDraftSectionChanged(buildDraftChangeSection) const isBuildDraftChanged = useIsAgentBuildDraftSectionChanged(buildDraftChangeSection)
return ( return (
<CollapsibleRoot <Collapsible
render={<section />} render={<section />}
defaultOpen={defaultOpen} defaultOpen={defaultOpen}
className={rootClassName} className={rootClassName}
@ -109,6 +109,6 @@ export function ConfigureSection({
{children} {children}
</div> </div>
</CollapsiblePanel> </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 type { AgentFileNode } from '@/features/agent-v2/agent-composer/form-state'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { import {
FileTree,
FileTreeFile, FileTreeFile,
FileTreeFolder, FileTreeFolder,
FileTreeFolderPanel, FileTreeFolderPanel,
@ -11,7 +12,6 @@ import {
FileTreeIcon, FileTreeIcon,
FileTreeLabel, FileTreeLabel,
FileTreeList, FileTreeList,
FileTreeRoot,
} from '@langgenius/dify-ui/file-tree' } from '@langgenius/dify-ui/file-tree'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area' import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
import { Fragment } from 'react' import { Fragment } from 'react'
@ -187,7 +187,7 @@ export function AgentFileTree({
scrollbar: 'hidden', scrollbar: 'hidden',
}} }}
> >
<FileTreeRoot <FileTree
id={id} id={id}
aria-label={treeLabel} aria-label={treeLabel}
className={cn('w-full max-w-full min-w-0 p-0', rootClassName)} className={cn('w-full max-w-full min-w-0 p-0', rootClassName)}
@ -207,7 +207,7 @@ export function AgentFileTree({
renderFolderPanel={renderFolderPanel} renderFolderPanel={renderFolderPanel}
/> />
</FileTreeList> </FileTreeList>
</FileTreeRoot> </FileTree>
</ScrollArea> </ScrollArea>
</div> </div>
) )

View File

@ -2,7 +2,7 @@
import type { FormValue, Model } from '@/app/components/header/account-setting/model-provider-page/declarations' 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 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 { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' 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 const canConfigureModelSettings = !readOnly && !!currentModel?.provider && !!currentModel.model
return ( 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"> <FieldLabel className="py-0 system-sm-semibold-uppercase! text-text-secondary">
{t($ => $['agentDetail.configure.model.label'])} {t($ => $['agentDetail.configure.model.label'])}
</FieldLabel> </FieldLabel>
@ -97,6 +97,6 @@ export function AgentModelField({
</div> </div>
)} )}
</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 { AgentConfigSnapshotSummaryResponse, AgentReferencingWorkflowResponse, AgentReferencingWorkflowsResponse } from '@dify/contracts/api/console/agent/types.gen'
import type { RegisterableHotkey } from '@tanstack/react-hotkeys' import type { RegisterableHotkey } from '@tanstack/react-hotkeys'
import { Button } from '@langgenius/dify-ui/button' 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 { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
import { StatusDot } from '@langgenius/dify-ui/status-dot' import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { toast } from '@langgenius/dify-ui/toast' import { toast } from '@langgenius/dify-ui/toast'
@ -282,7 +282,7 @@ export function AgentConfigurePublishBar({
const impactReferences = publishBarMode.status === 'confirmingImpact' ? publishBarMode.references : [] const impactReferences = publishBarMode.status === 'confirmingImpact' ? publishBarMode.references : []
return ( return (
<CollapsibleRoot <Collapsible
open={isConfirmingImpact} 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]" 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?.()} onOpenVersions={() => onOpenVersions?.()}
onPublishRequest={handlePublishRequest} onPublishRequest={handlePublishRequest}
/> />
</CollapsibleRoot> </Collapsible>
) )
} }

View File

@ -3,7 +3,7 @@
import type { AgentCliTool, EnvScope, EnvVariable } from '@/features/agent-v2/agent-composer/form-state' import type { AgentCliTool, EnvScope, EnvVariable } from '@/features/agent-v2/agent-composer/form-state'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog' 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 { Form } from '@langgenius/dify-ui/form'
import { toast } from '@langgenius/dify-ui/toast' import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
@ -143,7 +143,7 @@ export function CliToolDialog({
onFormSubmit={handleSubmit} onFormSubmit={handleSubmit}
> >
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<FieldRoot name="installCommand"> <Field name="installCommand">
<FieldLabel> <FieldLabel>
{t($ => $['agentDetail.configure.tools.cliDialog.installCommand.label'])} {t($ => $['agentDetail.configure.tools.cliDialog.installCommand.label'])}
</FieldLabel> </FieldLabel>
@ -156,8 +156,8 @@ export function CliToolDialog({
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.installCommand.placeholder'])} placeholder={t($ => $['agentDetail.configure.tools.cliDialog.installCommand.placeholder'])}
value={installCommand} value={installCommand}
/> />
</FieldRoot> </Field>
<FieldRoot name="name"> <Field name="name">
<FieldLabel> <FieldLabel>
{t($ => $['agentDetail.configure.tools.cliDialog.name.label'])} {t($ => $['agentDetail.configure.tools.cliDialog.name.label'])}
</FieldLabel> </FieldLabel>
@ -167,7 +167,7 @@ export function CliToolDialog({
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.name.placeholder'])} placeholder={t($ => $['agentDetail.configure.tools.cliDialog.name.placeholder'])}
value={toolName} value={toolName}
/> />
</FieldRoot> </Field>
<div className="pt-1"> <div className="pt-1">
<div className="mb-3 h-px bg-divider-subtle" /> <div className="mb-3 h-px bg-divider-subtle" />
<div className="mb-1 flex min-h-6 items-center gap-1"> <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 { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { import {
Collapsible,
CollapsiblePanel, CollapsiblePanel,
CollapsibleRoot,
CollapsibleTrigger, CollapsibleTrigger,
} from '@langgenius/dify-ui/collapsible' } from '@langgenius/dify-ui/collapsible'
import { import {
@ -248,7 +248,7 @@ export const AgentProviderToolItem = memo(({
const displayName = tool.displayName ?? tool.name const displayName = tool.displayName ?? tool.name
return ( return (
<CollapsibleRoot <Collapsible
open={isExpanded} open={isExpanded}
onOpenChange={onOpenChange} 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" 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> </div>
)} )}
</CollapsiblePanel> </CollapsiblePanel>
</CollapsibleRoot> </Collapsible>
) )
}) })

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import {
DialogDescription, DialogDescription,
DialogTitle, DialogTitle,
} from '@langgenius/dify-ui/dialog' } 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 { Form } from '@langgenius/dify-ui/form'
import { Textarea } from '@langgenius/dify-ui/textarea' import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast' import { toast } from '@langgenius/dify-ui/toast'
@ -73,7 +73,7 @@ function EditReleaseForm({
return ( return (
<Form<EditReleaseFormValues> className="flex flex-col gap-4" onFormSubmit={handleSubmit}> <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"> <FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{nameLabel} {nameLabel}
</FieldLabel> </FieldLabel>
@ -86,8 +86,8 @@ function EditReleaseForm({
className="h-8" className="h-8"
/> />
<FieldError match="valueMissing">{t($ => $['versions.releaseNameRequired'])}</FieldError> <FieldError match="valueMissing">{t($ => $['versions.releaseNameRequired'])}</FieldError>
</FieldRoot> </Field>
<FieldRoot name="description" className="gap-2"> <Field name="description" className="gap-2">
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary"> <FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
{t($ => $['versions.releaseDescriptionLabel'])} {t($ => $['versions.releaseDescriptionLabel'])}
<span className="ml-1.5 system-xs-regular text-text-quaternary">{t($ => $['versions.optional'])}</span> <span className="ml-1.5 system-xs-regular text-text-quaternary">{t($ => $['versions.optional'])}</span>
@ -98,7 +98,7 @@ function EditReleaseForm({
autoComplete="off" autoComplete="off"
className="min-h-24" className="min-h-24"
/> />
</FieldRoot> </Field>
<div className="flex justify-end gap-2 pt-2"> <div className="flex justify-end gap-2 pt-2">
<Button <Button
type="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 { 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 { cn } from '@langgenius/dify-ui/cn'
import { Combobox, ComboboxContent, ComboboxTrigger } from '@langgenius/dify-ui/combobox' import { Combobox, ComboboxContent, ComboboxTrigger } from '@langgenius/dify-ui/combobox'
import { useQuery } from '@tanstack/react-query' 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 { consoleQuery } from '@/service/client'
import { TagSearchContent } from './tag-search-content' 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 tagToString = (tag: Tag) => tag.name
const isSameTag = (item: Tag, value: Tag) => item.id === value.id 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 { 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 { ComponentProps } from 'react'
import type { TagComboboxItem } from './tag-combobox-item' import type { TagComboboxItem } from './tag-combobox-item'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
@ -18,12 +18,12 @@ import { isCreateTagOption } from './tag-combobox-item'
import { TagSearchContent } from './tag-search-content' import { TagSearchContent } from './tag-search-content'
import { TagTrigger } from './tag-trigger' 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 tagToString = (tag: TagComboboxItem) => tag.name
const isSameTag = (item: TagComboboxItem, value: TagComboboxItem) => item.id === value.id const isSameTag = (item: TagComboboxItem, value: TagComboboxItem) => item.id === value.id
type TagSelectorRootProps = Omit< type TagSelectorRootProps = Omit<
ComboboxRootProps<TagComboboxItem, true>, ComboboxProps<TagComboboxItem, true>,
| 'items' | 'items'
| 'multiple' | 'multiple'
| 'value' | 'value'