mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 02:28:30 +08:00
refactor(dify-ui): standardize primitive boundary names (#38669)
This commit is contained in:
parent
e3a08b40f2
commit
6e5ba18d65
@ -28,9 +28,12 @@ Flag:
|
||||
- Missing `package.json#exports` entry for a new primitive.
|
||||
- Internal package imports using workspace subpaths instead of relative paths.
|
||||
- Exported props using internal-only types that consumers cannot import from the component subpath.
|
||||
- Canonical primitive boundaries or their associated public types using a redundant `Root` suffix when no higher-level convenience component exists in the same subpath.
|
||||
|
||||
Consumers use subpath exports such as `@langgenius/dify-ui/button`.
|
||||
|
||||
Canonical boundaries use the primitive name and matching public types (`Select` / `SelectProps`). Keep `Root` only to distinguish a low-level anatomy root from a higher-level convenience component (`CheckboxRoot` / `Checkbox`); implementation aliases should still show their Base UI source (`BaseSelect.Root.Props`).
|
||||
|
||||
## Props And State
|
||||
|
||||
Flag:
|
||||
@ -45,17 +48,17 @@ Flag:
|
||||
|
||||
Prefer Base UI/Dify UI data attributes and CSS variables for visual state: `data-open`, `data-checked`, `data-disabled`, `data-highlighted`, `data-popup-open`, `group-data-*`, `peer-data-*`, `has-[:focus-visible]`, and primitive CSS variables such as anchor width or transform origin. Use JS conditional classes for product/business state that the primitive does not expose.
|
||||
|
||||
For non-string `Select` and `RadioGroup` values, prefer explicit domain generics at the root and at child value carriers. JSX children do not inherit the parent generic, so `RadioGroup<PromptMode>` should compose with `Radio<PromptMode>`, `RadioRoot<PromptMode>`, or option values from a typed collection. For `Select`, prefer the Base UI `items` collection pattern for typed value-to-label rendering, and flag string coercion helpers used only to recover display labels.
|
||||
For non-string `Select` and `RadioGroup` values, prefer explicit domain generics at the root and at child value carriers. JSX children do not inherit the parent generic, so `RadioGroup<PromptMode>` should compose with `Radio<PromptMode>`, `RadioItem<PromptMode>`, or option values from a typed collection. For `Select`, prefer the Base UI `items` collection pattern for typed value-to-label rendering, and flag string coercion helpers used only to recover display labels.
|
||||
|
||||
## Forms
|
||||
|
||||
Flag:
|
||||
|
||||
- Form-like UI using unrelated `Input` and `Button` pieces without a submit boundary.
|
||||
- Text-like fields not composed through `FieldRoot`, `FieldLabel`, and `FieldControl` when using Dify UI form semantics.
|
||||
- Text-like fields not composed through `Field`, `FieldLabel`, and `FieldControl` when using Dify UI form semantics.
|
||||
- Select fields using `FieldLabel` instead of `SelectLabel`.
|
||||
- Slider fields using a generic label instead of `SliderLabel`.
|
||||
- Checkbox/radio groups missing `FieldsetRoot` and `FieldsetLegend`.
|
||||
- Checkbox/radio groups missing `Fieldset` and `FieldsetLegend`.
|
||||
- Field errors or descriptions rendered without `FieldDescription` / `FieldError` relationships.
|
||||
|
||||
`Form` is the submit boundary. Dify UI form primitives are not a form state-management framework; business validation and schema-driven behavior belong in `web/`.
|
||||
|
||||
@ -8,6 +8,7 @@ Shared design tokens, the `cn()` utility, CSS-first Tailwind styles, and headles
|
||||
- Inside dify-ui, cross-component imports use relative paths (`../button`). External consumers use subpath exports (`@langgenius/dify-ui/button`).
|
||||
- No imports from `web/`. No dependencies on next / i18next / ky / jotai / zustand.
|
||||
- One component per folder: `src/<name>/index.tsx`, optional `index.stories.tsx` and `__tests__/index.spec.tsx`. Add a matching `./<name>` subpath to `package.json#exports`.
|
||||
- Name the canonical public boundary and its associated public types after the primitive without a `Root` suffix (`Select` / `SelectProps`). Keep `Root` only when the subpath also exports a higher-level convenience component that must be distinguished from the low-level anatomy root (`CheckboxRoot` / `Checkbox`). Preserve the upstream anatomy in implementation type sources such as `BaseSelect.Root.Props`.
|
||||
- Props pattern: `Omit<BaseXxx.Root.Props, 'className' | ...> & VariantProps<typeof xxxVariants> & { /* custom */ }`.
|
||||
- Use plain `Omit<...>` only for non-union Base UI props. When a prop changes the valid shape of related props (for example `value` / `defaultValue`, `multiple` / `value`, or `clearable` / `onChange`), model that relationship with an explicit discriminated union or a distributive helper instead of flattening the props.
|
||||
- Preserve Base UI generic value contracts in wrappers. If the upstream primitive is generic, expose the same generic parameters and pass them through to the Base UI part, such as `Select.Root<Value, Multiple>`, `RadioGroup<Value>`, or `Radio.Root<Value>`.
|
||||
|
||||
@ -30,7 +30,7 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog'
|
||||
import { Drawer, DrawerPopup, DrawerTrigger } from '@langgenius/dify-ui/drawer'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
@ -41,6 +41,8 @@ import '@langgenius/dify-ui/styles.css' // once, in the app root
|
||||
|
||||
Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive.
|
||||
|
||||
The canonical boundary exported from a primitive subpath uses the primitive name without a `Root` suffix, and its public types follow the same name (`Select` / `SelectProps`, `Drawer` / `DrawerProps`). Keep `Root` only when the subpath exposes both a low-level anatomy root and a higher-level convenience component, such as `CheckboxRoot` / `Checkbox` or `PaginationRoot` / `Pagination`. Implementation code should continue to reference the upstream Base UI anatomy explicitly through names such as `BaseSelect.Root.Props`.
|
||||
|
||||
## Primitives
|
||||
|
||||
| Category | Subpath | Notes |
|
||||
@ -79,15 +81,15 @@ Dify UI's form primitives are a Base UI composition layer for native form semant
|
||||
|
||||
Use `Form` for the submit boundary. It renders a native `<form>`, preserves Enter-to-submit and submit-button behavior, and adds Base UI's `onFormSubmit`, `errors`, `actionsRef`, and `validationMode` APIs for structured values and consolidated field validation. Prefer it over a bare `<form>` when the form is composed with Dify UI fields.
|
||||
|
||||
Use `FieldRoot` for each standalone named field. A field must have a stable `name`, a label relationship, and either a `FieldControl` or another control that participates in the same Base UI field context. Prefer a visible label for normal form rows; when the surrounding UI already supplies the visible text, use the matching label primitive visually hidden or put `aria-label` on the actual interactive control. `FieldDescription` and `FieldError` provide the message relationships that screen readers need, while the Dify wrapper adds the default Form Input Set styling from the design system.
|
||||
Use `Field` for each standalone named field. A field must have a stable `name`, a label relationship, and either a `FieldControl` or another control that participates in the same Base UI field context. Prefer a visible label for normal form rows; when the surrounding UI already supplies the visible text, use the matching label primitive visually hidden or put `aria-label` on the actual interactive control. `FieldDescription` and `FieldError` provide the message relationships that screen readers need, while the Dify wrapper adds the default Form Input Set styling from the design system.
|
||||
|
||||
Choose the label primitive by the control semantics. Text-like inputs, `Textarea`, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels.
|
||||
|
||||
Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label:
|
||||
Use `Fieldset` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label:
|
||||
|
||||
```tsx
|
||||
<FieldRoot name="allowedNetworkProtocols">
|
||||
<FieldsetRoot render={<CheckboxGroup />}>
|
||||
<Field name="allowedNetworkProtocols">
|
||||
<Fieldset render={<CheckboxGroup />}>
|
||||
<FieldsetLegend>Allowed network protocols</FieldsetLegend>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
@ -95,11 +97,11 @@ Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group
|
||||
HTTPS
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
```
|
||||
|
||||
`FieldsetRoot` provides the group semantics and legend relationship. It does not own the interactive state of the grouped control. Pass `disabled`, `value`, `defaultValue`, and change handlers to the actual group primitive (`CheckboxGroup`, radio group, slider root, etc.) instead of relying on the fieldset wrapper to manage them.
|
||||
`Fieldset` provides the group semantics and legend relationship. It does not own the interactive state of the grouped control. Pass `disabled`, `value`, `defaultValue`, and change handlers to the actual group primitive (`CheckboxGroup`, radio group, slider root, etc.) instead of relying on the fieldset wrapper to manage them.
|
||||
|
||||
## Typed value contracts
|
||||
|
||||
|
||||
@ -17,26 +17,26 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export type AutocompleteRootProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
export type AutocompleteRootGroupedProps<
|
||||
export type AutocompleteProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
export type AutocompleteGroupedProps<
|
||||
Items extends readonly { items: readonly unknown[] }[],
|
||||
> = Omit<AutocompleteRootProps<Items[number]['items'][number]>, 'items'> & {
|
||||
> = Omit<AutocompleteProps<Items[number]['items'][number]>, 'items'> & {
|
||||
items: Items
|
||||
}
|
||||
export type AutocompleteRootFlatProps<ItemValue>
|
||||
= Omit<AutocompleteRootProps<ItemValue>, 'items'>
|
||||
export type AutocompleteFlatProps<ItemValue>
|
||||
= Omit<AutocompleteProps<ItemValue>, 'items'>
|
||||
& {
|
||||
items?: readonly ItemValue[]
|
||||
}
|
||||
|
||||
export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
|
||||
props: AutocompleteRootGroupedProps<Items>,
|
||||
props: AutocompleteGroupedProps<Items>,
|
||||
): React.JSX.Element
|
||||
export function Autocomplete<ItemValue>(
|
||||
props: AutocompleteRootFlatProps<ItemValue>,
|
||||
props: AutocompleteFlatProps<ItemValue>,
|
||||
): React.JSX.Element
|
||||
export function Autocomplete(
|
||||
props: AutocompleteRootProps<unknown>,
|
||||
props: AutocompleteProps<unknown>,
|
||||
): React.JSX.Element {
|
||||
return <BaseAutocomplete.Root {...props} />
|
||||
}
|
||||
@ -48,8 +48,8 @@ export const AutocompleteRow = BaseAutocomplete.Row
|
||||
export const useAutocompleteFilter = BaseAutocomplete.useFilter
|
||||
export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
|
||||
|
||||
export type AutocompleteRootChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
|
||||
export type AutocompleteRootHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
|
||||
export type AutocompleteChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
|
||||
export type AutocompleteHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
|
||||
|
||||
const autocompletePopupClassName = [
|
||||
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { Checkbox } from '../../checkbox'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '../../field'
|
||||
import { Fieldset, FieldsetLegend } from '../../fieldset'
|
||||
import { CheckboxGroup } from '../index'
|
||||
|
||||
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
|
||||
@ -46,8 +46,8 @@ describe('CheckboxGroup', () => {
|
||||
it('should compose with Dify UI Field and Fieldset without losing labels', async () => {
|
||||
const onValueChange = vi.fn()
|
||||
const screen = await render(
|
||||
<FieldRoot name="features">
|
||||
<FieldsetRoot render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}>
|
||||
<Field name="features">
|
||||
<Fieldset render={<CheckboxGroup value={['search']} onValueChange={onValueChange} />}>
|
||||
<FieldsetLegend>Features</FieldsetLegend>
|
||||
<FieldItem>
|
||||
<FieldLabel>
|
||||
@ -61,8 +61,8 @@ describe('CheckboxGroup', () => {
|
||||
Analytics
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>,
|
||||
</Fieldset>
|
||||
</Field>,
|
||||
)
|
||||
|
||||
const analytics = screen.getByRole('checkbox', { name: 'Analytics' })
|
||||
|
||||
@ -3,12 +3,12 @@ import * as React from 'react'
|
||||
import { CheckboxGroup } from '.'
|
||||
import { Checkbox } from '../checkbox'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldItem,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '../fieldset'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Form/CheckboxGroup',
|
||||
@ -80,11 +80,11 @@ function DynamicFormFieldDemo() {
|
||||
const [selected, setSelected] = React.useState<string[]>(['markdown'])
|
||||
|
||||
return (
|
||||
<FieldRoot name="allowed_file_types" className="flex w-80 flex-col gap-2">
|
||||
<Field name="allowed_file_types" className="flex w-80 flex-col gap-2">
|
||||
<FieldDescription className="body-xs-regular text-text-tertiary">
|
||||
This mirrors Dify dynamic form fields where checkbox options are controlled by schema and persisted as a string array.
|
||||
</FieldDescription>
|
||||
<FieldsetRoot
|
||||
<Fieldset
|
||||
render={(
|
||||
<CheckboxGroup
|
||||
value={selected}
|
||||
@ -104,8 +104,8 @@ function DynamicFormFieldDemo() {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '../index'
|
||||
|
||||
@ -10,10 +10,10 @@ const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElem
|
||||
describe('Collapsible wrappers', () => {
|
||||
it('renders the Base UI anatomy with an accessible trigger', async () => {
|
||||
const screen = await render(
|
||||
<CollapsibleRoot defaultOpen data-testid="collapsible-root">
|
||||
<Collapsible defaultOpen data-testid="collapsible-root">
|
||||
<CollapsibleTrigger>Recovery keys</CollapsibleTrigger>
|
||||
<CollapsiblePanel>Panel content</CollapsiblePanel>
|
||||
</CollapsibleRoot>,
|
||||
</Collapsible>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByTestId('collapsible-root')).toBeInTheDocument()
|
||||
@ -23,10 +23,10 @@ describe('Collapsible wrappers', () => {
|
||||
|
||||
it('toggles open state through the trigger without caller-owned state', async () => {
|
||||
const screen = await render(
|
||||
<CollapsibleRoot>
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger>Toggle section</CollapsibleTrigger>
|
||||
<CollapsiblePanel>Hidden content</CollapsiblePanel>
|
||||
</CollapsibleRoot>,
|
||||
</Collapsible>,
|
||||
)
|
||||
const trigger = screen.getByRole('button', { name: 'Toggle section' })
|
||||
|
||||
@ -40,10 +40,10 @@ describe('Collapsible wrappers', () => {
|
||||
|
||||
it('forwards className to every compound part', async () => {
|
||||
const screen = await render(
|
||||
<CollapsibleRoot defaultOpen className="custom-root">
|
||||
<Collapsible defaultOpen className="custom-root">
|
||||
<CollapsibleTrigger className="custom-trigger">Custom</CollapsibleTrigger>
|
||||
<CollapsiblePanel className="custom-panel">Custom panel</CollapsiblePanel>
|
||||
</CollapsibleRoot>,
|
||||
</Collapsible>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByRole('button', { name: 'Custom' })).toHaveClass('custom-trigger')
|
||||
@ -53,10 +53,10 @@ describe('Collapsible wrappers', () => {
|
||||
|
||||
it('passes Base UI panel props through to the panel', async () => {
|
||||
const screen = await render(
|
||||
<CollapsibleRoot defaultOpen>
|
||||
<Collapsible defaultOpen>
|
||||
<CollapsibleTrigger>Styled trigger</CollapsibleTrigger>
|
||||
<CollapsiblePanel keepMounted>Styled panel</CollapsiblePanel>
|
||||
</CollapsibleRoot>,
|
||||
</Collapsible>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByRole('button', { name: 'Styled trigger' })).toHaveAttribute('data-panel-open', '')
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '.'
|
||||
import { cn } from '../cn'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Collapsible',
|
||||
component: CollapsibleRoot,
|
||||
component: Collapsible,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
@ -19,7 +19,7 @@ const meta = {
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof CollapsibleRoot>
|
||||
} satisfies Meta<typeof Collapsible>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
@ -67,25 +67,25 @@ export const Anatomy: Story = {
|
||||
defaultOpen: true,
|
||||
},
|
||||
render: args => (
|
||||
<CollapsibleRoot {...args} className={rootClassName}>
|
||||
<Collapsible {...args} className={rootClassName}>
|
||||
<RecoveryKeys />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
export const DefaultClosed: Story = {
|
||||
render: () => (
|
||||
<CollapsibleRoot className={rootClassName}>
|
||||
<Collapsible className={rootClassName}>
|
||||
<RecoveryKeys />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
export const DefaultOpen: Story = {
|
||||
render: () => (
|
||||
<CollapsibleRoot defaultOpen className={rootClassName}>
|
||||
<Collapsible defaultOpen className={rootClassName}>
|
||||
<RecoveryKeys />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
@ -102,9 +102,9 @@ export const Controlled: Story = {
|
||||
>
|
||||
{open ? 'Close panel' : 'Open panel'}
|
||||
</button>
|
||||
<CollapsibleRoot open={open} onOpenChange={setOpen} className={rootClassName}>
|
||||
<Collapsible open={open} onOpenChange={setOpen} className={rootClassName}>
|
||||
<RecoveryKeys />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@ -112,25 +112,25 @@ export const Controlled: Story = {
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: () => (
|
||||
<CollapsibleRoot disabled className={rootClassName}>
|
||||
<Collapsible disabled className={rootClassName}>
|
||||
<RecoveryKeys />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
export const KeepMounted: Story = {
|
||||
render: () => (
|
||||
<CollapsibleRoot className={rootClassName}>
|
||||
<Collapsible className={rootClassName}>
|
||||
<RecoveryKeys panelProps={{ keepMounted: true }} />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
export const HiddenUntilFound: Story = {
|
||||
render: () => (
|
||||
<CollapsibleRoot className={rootClassName}>
|
||||
<Collapsible className={rootClassName}>
|
||||
<RecoveryKeys panelProps={{ hiddenUntilFound: true }} />
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
),
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ export const SettingsSections: Story = {
|
||||
render: () => (
|
||||
<div className={sectionRootClassName}>
|
||||
{settingSections.map((section, index) => (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
key={section.title}
|
||||
defaultOpen={section.defaultOpen}
|
||||
className={cn(index > 0 && 'mt-px')}
|
||||
@ -176,7 +176,7 @@ export const SettingsSections: Story = {
|
||||
{section.description}
|
||||
</div>
|
||||
</CollapsiblePanel>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
|
||||
@ -4,16 +4,16 @@ import type { Collapsible as BaseCollapsibleNS } from '@base-ui/react/collapsibl
|
||||
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export type CollapsibleRootProps
|
||||
export type CollapsibleProps
|
||||
= Omit<BaseCollapsibleNS.Root.Props, 'className'>
|
||||
& {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CollapsibleRoot({
|
||||
export function Collapsible({
|
||||
className,
|
||||
...props
|
||||
}: CollapsibleRootProps) {
|
||||
}: CollapsibleProps) {
|
||||
return (
|
||||
<BaseCollapsible.Root
|
||||
className={cn('flex min-w-0 flex-col', className)}
|
||||
|
||||
@ -31,9 +31,9 @@ import {
|
||||
} from '.'
|
||||
import { cn } from '../cn'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
|
||||
type Option = {
|
||||
@ -411,7 +411,7 @@ const AsyncDirectoryDemo = () => {
|
||||
: 'Try a different owner search.'
|
||||
|
||||
return (
|
||||
<FieldRoot name="owner" className={fieldWidth}>
|
||||
<Field name="owner" className={fieldWidth}>
|
||||
<FieldLabel>Owner</FieldLabel>
|
||||
<Combobox
|
||||
items={items}
|
||||
@ -472,7 +472,7 @@ const AsyncDirectoryDemo = () => {
|
||||
<ComboboxEmpty>{emptyMessage}</ComboboxEmpty>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -523,7 +523,7 @@ const AsyncReviewerDemo = () => {
|
||||
: 'Try a different reviewer search.'
|
||||
|
||||
return (
|
||||
<FieldRoot name="asyncReviewers" className={fieldWidth}>
|
||||
<Field name="asyncReviewers" className={fieldWidth}>
|
||||
<FieldLabel>Async reviewers</FieldLabel>
|
||||
<Combobox
|
||||
items={items}
|
||||
@ -609,7 +609,7 @@ const AsyncReviewerDemo = () => {
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>Selected reviewers stay available while async matches change.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -632,7 +632,7 @@ type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="dataSource" className={fieldWidth}>
|
||||
<Field name="dataSource" className={fieldWidth}>
|
||||
<FieldLabel>Connect source</FieldLabel>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource}>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
@ -645,13 +645,13 @@ export const Default: Story = {
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
export const FormField: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="sourceConnector" className={fieldWidth}>
|
||||
<Field name="sourceConnector" className={fieldWidth}>
|
||||
<FieldLabel>Connect source</FieldLabel>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource}>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
@ -665,7 +665,7 @@ export const FormField: Story = {
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>Type to filter, then choose a remembered data source.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
@ -698,7 +698,7 @@ export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex w-80 flex-col gap-3">
|
||||
{(['small', 'medium', 'large'] as const).map(size => (
|
||||
<FieldRoot key={size} name={`provider-${size}`}>
|
||||
<Field key={size} name={`provider-${size}`}>
|
||||
<FieldLabel>{`${size[0]!.toUpperCase()}${size.slice(1)}`}</FieldLabel>
|
||||
<Combobox items={sizeOptions} defaultValue={defaultProvider}>
|
||||
<ComboboxInputGroup size={size} className="px-2">
|
||||
@ -711,7 +711,7 @@ export const Sizes: Story = {
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
@ -738,7 +738,7 @@ const MultipleChipsDemo = () => {
|
||||
const [value, setValue] = React.useState<Option[]>(defaultReviewers)
|
||||
|
||||
return (
|
||||
<FieldRoot name="reviewers" className={fieldWidth}>
|
||||
<Field name="reviewers" className={fieldWidth}>
|
||||
<FieldLabel>Reviewers</FieldLabel>
|
||||
<Combobox items={reviewerOptions} multiple value={value} onValueChange={setValue}>
|
||||
<ComboboxInputGroup className="h-auto min-h-8 items-start py-1">
|
||||
@ -763,7 +763,7 @@ const MultipleChipsDemo = () => {
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>Selected reviewers wrap inside the input instead of scrolling horizontally.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -786,7 +786,7 @@ export const VirtualizedLongList: Story = {
|
||||
|
||||
export const EmptyAndStatus: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="connector" className={fieldWidth}>
|
||||
<Field name="connector" className={fieldWidth}>
|
||||
<FieldLabel>Connector</FieldLabel>
|
||||
<Combobox items={emptyOptions} defaultInputValue="salesforce">
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
@ -801,14 +801,14 @@ export const EmptyAndStatus: Story = {
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
export const DisabledAndReadOnly: Story = {
|
||||
render: () => (
|
||||
<div className="flex w-80 flex-col gap-3">
|
||||
<FieldRoot name="disabledProvider" disabled>
|
||||
<Field name="disabledProvider" disabled>
|
||||
<Combobox items={providerOptions} defaultValue={disabledProvider} disabled>
|
||||
<ComboboxLabel>Disabled provider</ComboboxLabel>
|
||||
<ComboboxTrigger aria-label="Disabled model provider">
|
||||
@ -819,8 +819,8 @@ export const DisabledAndReadOnly: Story = {
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="readOnlySource">
|
||||
</Field>
|
||||
<Field name="readOnlySource">
|
||||
<FieldLabel>Read-only source</FieldLabel>
|
||||
<Combobox items={dataSourceOptions} defaultValue={readOnlyDataSource} readOnly>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
@ -832,7 +832,7 @@ export const DisabledAndReadOnly: Story = {
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
@ -17,11 +17,11 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export type ComboboxRootProps<Value, Multiple extends boolean | undefined = false>
|
||||
export type ComboboxProps<Value, Multiple extends boolean | undefined = false>
|
||||
= BaseCombobox.Root.Props<Value, Multiple>
|
||||
|
||||
export function Combobox<Value, Multiple extends boolean | undefined = false>(
|
||||
props: ComboboxRootProps<Value, Multiple>,
|
||||
props: ComboboxProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseCombobox.Root {...props} />
|
||||
}
|
||||
@ -33,8 +33,8 @@ export const ComboboxRow = BaseCombobox.Row
|
||||
export const useComboboxFilter = BaseCombobox.useFilter
|
||||
export const useComboboxFilteredItems = BaseCombobox.useFilteredItems
|
||||
|
||||
export type ComboboxRootChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
|
||||
export type ComboboxRootHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
|
||||
export type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
|
||||
export type ComboboxHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
|
||||
|
||||
const comboboxPopupClassName = [
|
||||
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
DialogViewport,
|
||||
} from '.'
|
||||
import { Button } from '../button'
|
||||
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from '../field'
|
||||
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from '../field'
|
||||
import { Form } from '../form'
|
||||
import { Input } from '../input'
|
||||
import {
|
||||
@ -251,12 +251,12 @@ const FormDialogDemo = () => {
|
||||
className="grid gap-4 pt-5"
|
||||
onFormSubmit={() => setOpen(false)}
|
||||
>
|
||||
<FieldRoot name="name">
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl required placeholder="Production API" />
|
||||
<FieldError match="valueMissing">Name is required.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="endpoint">
|
||||
</Field>
|
||||
<Field name="endpoint">
|
||||
<FieldLabel>Endpoint</FieldLabel>
|
||||
<FieldControl type="url" required placeholder="https://api.example.com" />
|
||||
<FieldDescription>
|
||||
@ -271,8 +271,8 @@ const FormDialogDemo = () => {
|
||||
</FieldDescription>
|
||||
<FieldError match="valueMissing">Endpoint is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot
|
||||
</Field>
|
||||
<Field
|
||||
name="apiKey"
|
||||
validate={(value) => {
|
||||
if (typeof value === 'string' && value.length > 0 && value.length < 5)
|
||||
@ -285,7 +285,7 @@ const FormDialogDemo = () => {
|
||||
<FieldControl required placeholder="sk-..." />
|
||||
<FieldError match="valueMissing">API key is required.</FieldError>
|
||||
<FieldError match="customError" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="mt-2 flex items-center justify-end gap-2">
|
||||
<Button type="button" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import type { DrawerRootSnapPoint } from '.'
|
||||
import type { DrawerSnapPoint } from '.'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
createDrawerHandle,
|
||||
@ -293,11 +293,11 @@ export const Positions: Story = {
|
||||
|
||||
const snapTopMarginRem = 1
|
||||
const visibleSnapPointRem = 30
|
||||
const initialSnapPoint: DrawerRootSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem`
|
||||
const snapPoints = [initialSnapPoint, 1] satisfies DrawerRootSnapPoint[]
|
||||
const initialSnapPoint: DrawerSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem`
|
||||
const snapPoints = [initialSnapPoint, 1] satisfies DrawerSnapPoint[]
|
||||
|
||||
function SnapPointsDemo() {
|
||||
const [snapPoint, setSnapPoint] = React.useState<DrawerRootSnapPoint | null>(initialSnapPoint)
|
||||
const [snapPoint, setSnapPoint] = React.useState<DrawerSnapPoint | null>(initialSnapPoint)
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
||||
@ -16,13 +16,13 @@ export const DrawerDescription = BaseDrawer.Description
|
||||
export const DrawerClose = BaseDrawer.Close
|
||||
export const createDrawerHandle = BaseDrawer.createHandle
|
||||
|
||||
export type DrawerRootProps<Payload = unknown> = BaseDrawer.Root.Props<Payload>
|
||||
export type DrawerRootActions = BaseDrawer.Root.Actions
|
||||
export type DrawerRootChangeEventDetails = BaseDrawer.Root.ChangeEventDetails
|
||||
export type DrawerRootChangeEventReason = BaseDrawer.Root.ChangeEventReason
|
||||
export type DrawerRootSnapPoint = BaseDrawer.Root.SnapPoint
|
||||
export type DrawerRootSnapPointChangeEventDetails = BaseDrawer.Root.SnapPointChangeEventDetails
|
||||
export type DrawerRootSnapPointChangeEventReason = BaseDrawer.Root.SnapPointChangeEventReason
|
||||
export type DrawerProps<Payload = unknown> = BaseDrawer.Root.Props<Payload>
|
||||
export type DrawerActions = BaseDrawer.Root.Actions
|
||||
export type DrawerChangeEventDetails = BaseDrawer.Root.ChangeEventDetails
|
||||
export type DrawerChangeEventReason = BaseDrawer.Root.ChangeEventReason
|
||||
export type DrawerSnapPoint = BaseDrawer.Root.SnapPoint
|
||||
export type DrawerSnapPointChangeEventDetails = BaseDrawer.Root.SnapPointChangeEventDetails
|
||||
export type DrawerSnapPointChangeEventReason = BaseDrawer.Root.SnapPointChangeEventReason
|
||||
export type DrawerTriggerProps<Payload = unknown> = BaseDrawer.Trigger.Props<Payload>
|
||||
|
||||
export function DrawerBackdrop({
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { Checkbox } from '../../checkbox'
|
||||
import { CheckboxGroup } from '../../checkbox-group'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '../../fieldset'
|
||||
import { Form } from '../../form'
|
||||
import {
|
||||
Field,
|
||||
FieldControl,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldItem,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../index'
|
||||
|
||||
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
|
||||
@ -19,12 +19,12 @@ describe('Field primitives', () => {
|
||||
const onFormSubmit = vi.fn()
|
||||
const screen = await render(
|
||||
<Form aria-label="profile form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="email">
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<FieldControl type="email" required />
|
||||
<FieldDescription>Used for account notifications.</FieldDescription>
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
@ -55,10 +55,10 @@ describe('Field primitives', () => {
|
||||
const onFormSubmit = vi.fn()
|
||||
const screen = await render(
|
||||
<Form aria-label="settings form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="apiKey">
|
||||
<Field name="apiKey">
|
||||
<FieldLabel>API key</FieldLabel>
|
||||
<FieldControl defaultValue="sk-test" required />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
@ -71,8 +71,8 @@ describe('Field primitives', () => {
|
||||
|
||||
it('should support external invalid state without requiring FieldControl', async () => {
|
||||
const screen = await render(
|
||||
<FieldRoot name="features" invalid>
|
||||
<FieldsetRoot render={<CheckboxGroup value={['search']} />}>
|
||||
<Field name="features" invalid>
|
||||
<Fieldset render={<CheckboxGroup value={['search']} />}>
|
||||
<FieldsetLegend>Features</FieldsetLegend>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
@ -81,8 +81,8 @@ describe('Field primitives', () => {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
<FieldError match>Choose at least one feature.</FieldError>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>,
|
||||
</Fieldset>
|
||||
</Field>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByRole('group', { name: 'Features' })).toBeInTheDocument()
|
||||
@ -91,10 +91,10 @@ describe('Field primitives', () => {
|
||||
|
||||
it('should expose the read-only state', async () => {
|
||||
const screen = await render(
|
||||
<FieldRoot name="token">
|
||||
<Field name="token">
|
||||
<FieldLabel>Token</FieldLabel>
|
||||
<FieldControl readOnly defaultValue="readonly-token" />
|
||||
</FieldRoot>,
|
||||
</Field>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByRole('textbox', { name: 'Token' })).toHaveAttribute('readonly')
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { Button } from '../button'
|
||||
import {
|
||||
Field,
|
||||
FieldControl,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from './index'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Form/Field',
|
||||
component: FieldRoot,
|
||||
component: Field,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Field primitives built on Base UI Field. Use FieldRoot with FieldLabel, FieldControl, FieldDescription, and FieldError for one named form field. External form libraries can control invalid, dirty, and touched on FieldRoot.',
|
||||
component: 'Field primitives built on Base UI Field. Use Field with FieldLabel, FieldControl, FieldDescription, and FieldError for one named form field. External form libraries can control invalid, dirty, and touched on Field.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof FieldRoot>
|
||||
} satisfies Meta<typeof Field>
|
||||
|
||||
export default meta
|
||||
|
||||
@ -29,13 +29,13 @@ type Story = StoryObj<typeof meta>
|
||||
export const TextField: Story = {
|
||||
render: () => (
|
||||
<form className="grid w-96 gap-4">
|
||||
<FieldRoot name="endpoint">
|
||||
<Field name="endpoint">
|
||||
<FieldLabel>Endpoint</FieldLabel>
|
||||
<FieldControl type="url" required placeholder="https://api.example.com" />
|
||||
<FieldDescription>Used as the base URL for extension requests.</FieldDescription>
|
||||
<FieldError match="valueMissing">Endpoint is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
</div>
|
||||
@ -46,24 +46,24 @@ export const TextField: Story = {
|
||||
export const MultipleFields: Story = {
|
||||
render: () => (
|
||||
<form className="grid w-96 gap-4">
|
||||
<FieldRoot name="name">
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl required placeholder="Production API" />
|
||||
<FieldError match="valueMissing">Name is required.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="endpoint">
|
||||
</Field>
|
||||
<Field name="endpoint">
|
||||
<FieldLabel>Endpoint</FieldLabel>
|
||||
<FieldControl type="url" required placeholder="https://api.example.com" />
|
||||
<FieldDescription>Used as the base URL for extension requests.</FieldDescription>
|
||||
<FieldError match="valueMissing">Endpoint is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="apiKey">
|
||||
</Field>
|
||||
<Field name="apiKey">
|
||||
<FieldLabel>API key</FieldLabel>
|
||||
<FieldControl required placeholder="sk-..." />
|
||||
<FieldDescription>Stored with the extension configuration.</FieldDescription>
|
||||
<FieldError match="valueMissing">API key is required.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
</div>
|
||||
@ -73,39 +73,39 @@ export const MultipleFields: Story = {
|
||||
|
||||
export const ExternalInvalidState: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="apiKey" invalid className="w-96">
|
||||
<Field name="apiKey" invalid className="w-96">
|
||||
<FieldLabel>API key</FieldLabel>
|
||||
<FieldControl defaultValue="expired-key" />
|
||||
<FieldError match>API key has expired.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-96 gap-4">
|
||||
<FieldRoot name="smallEndpoint">
|
||||
<Field name="smallEndpoint">
|
||||
<FieldLabel>Small</FieldLabel>
|
||||
<FieldControl size="small" placeholder="Small input" />
|
||||
</FieldRoot>
|
||||
<FieldRoot name="regularEndpoint">
|
||||
</Field>
|
||||
<Field name="regularEndpoint">
|
||||
<FieldLabel>Regular</FieldLabel>
|
||||
<FieldControl placeholder="Regular input" />
|
||||
</FieldRoot>
|
||||
<FieldRoot name="largeEndpoint">
|
||||
</Field>
|
||||
<Field name="largeEndpoint">
|
||||
<FieldLabel>Large</FieldLabel>
|
||||
<FieldControl size="large" placeholder="Large input" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const ReadOnly: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="readonlyEndpoint" className="w-96">
|
||||
<Field name="readonlyEndpoint" className="w-96">
|
||||
<FieldLabel>Endpoint</FieldLabel>
|
||||
<FieldControl readOnly defaultValue="https://api.example.com" />
|
||||
<FieldDescription>This value is managed by the workspace owner.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
@ -6,18 +6,18 @@ import { Field as BaseField } from '@base-ui/react/field'
|
||||
import { cn } from '../cn'
|
||||
import { formLabelClassName, textControlVariants } from '../form-control-shared'
|
||||
|
||||
export type FieldRootProps
|
||||
export type FieldProps
|
||||
= Omit<BaseFieldNS.Root.Props, 'className'>
|
||||
& {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type FieldRootActions = BaseFieldNS.Root.Actions
|
||||
export type FieldActions = BaseFieldNS.Root.Actions
|
||||
|
||||
export function FieldRoot({
|
||||
export function Field({
|
||||
className,
|
||||
...props
|
||||
}: FieldRootProps) {
|
||||
}: FieldProps) {
|
||||
return (
|
||||
<BaseField.Root
|
||||
className={cn('group/field grid min-w-0 gap-1', className)}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
Fieldset,
|
||||
FieldsetLegend,
|
||||
FieldsetRoot,
|
||||
} from '../index'
|
||||
|
||||
describe('Fieldset primitives', () => {
|
||||
it('should forward className to the fieldset and legend', async () => {
|
||||
const screen = await render(
|
||||
<FieldsetRoot className="custom-root">
|
||||
<Fieldset className="custom-root">
|
||||
<FieldsetLegend className="custom-legend">Permissions</FieldsetLegend>
|
||||
</FieldsetRoot>,
|
||||
</Fieldset>,
|
||||
)
|
||||
|
||||
const legend = screen.getByText('Permissions').element() as HTMLElement
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { Checkbox } from '../checkbox'
|
||||
import { CheckboxGroup } from '../checkbox-group'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '../field'
|
||||
import { Field, FieldItem, FieldLabel } from '../field'
|
||||
import {
|
||||
Fieldset,
|
||||
FieldsetLegend,
|
||||
FieldsetRoot,
|
||||
} from './index'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Form/Fieldset',
|
||||
component: FieldsetRoot,
|
||||
component: Fieldset,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Fieldset primitives built on Base UI Fieldset. Use FieldsetRoot and FieldsetLegend when one field is represented by a group of related controls such as checkbox groups, radio groups, or multi-thumb sliders. Fieldset provides group semantics and labeling; pass interactive state such as disabled and value to the actual group primitive.',
|
||||
component: 'Fieldset primitives built on Base UI Fieldset. Use Fieldset and FieldsetLegend when one field is represented by a group of related controls such as checkbox groups, radio groups, or multi-thumb sliders. Fieldset provides group semantics and labeling; pass interactive state such as disabled and value to the actual group primitive.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof FieldsetRoot>
|
||||
} satisfies Meta<typeof Fieldset>
|
||||
|
||||
export default meta
|
||||
|
||||
@ -27,8 +27,8 @@ type Story = StoryObj<typeof meta>
|
||||
|
||||
export const CheckboxGroupField: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="scopes" className="w-80">
|
||||
<FieldsetRoot render={<CheckboxGroup defaultValue={['read']} />}>
|
||||
<Field name="scopes" className="w-80">
|
||||
<Fieldset render={<CheckboxGroup defaultValue={['read']} />}>
|
||||
<FieldsetLegend>Scopes</FieldsetLegend>
|
||||
<div className="grid gap-2">
|
||||
<FieldItem>
|
||||
@ -50,7 +50,7 @@ export const CheckboxGroupField: Story = {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
),
|
||||
}
|
||||
|
||||
@ -4,16 +4,16 @@ import type { Fieldset as BaseFieldsetNS } from '@base-ui/react/fieldset'
|
||||
import { Fieldset as BaseFieldset } from '@base-ui/react/fieldset'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export type FieldsetRootProps
|
||||
export type FieldsetProps
|
||||
= Omit<BaseFieldsetNS.Root.Props, 'className'>
|
||||
& {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldsetRoot({
|
||||
export function Fieldset({
|
||||
className,
|
||||
...props
|
||||
}: FieldsetRootProps) {
|
||||
}: FieldsetProps) {
|
||||
return (
|
||||
<BaseFieldset.Root
|
||||
className={cn('m-0 min-w-0 border-0 p-0', className)}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
FileTree,
|
||||
FileTreeFile,
|
||||
FileTreeFolder,
|
||||
FileTreeFolderPanel,
|
||||
@ -7,7 +8,6 @@ import {
|
||||
FileTreeIcon,
|
||||
FileTreeLabel,
|
||||
FileTreeList,
|
||||
FileTreeRoot,
|
||||
} from '../index'
|
||||
|
||||
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
|
||||
@ -18,7 +18,7 @@ function TestFileTree({
|
||||
onPreview?: (itemId: string) => void
|
||||
}) {
|
||||
return (
|
||||
<FileTreeRoot aria-label="Project files">
|
||||
<FileTree aria-label="Project files">
|
||||
<FileTreeList>
|
||||
<FileTreeFolder defaultOpen>
|
||||
<FileTreeFolderTrigger>
|
||||
@ -53,7 +53,7 @@ function TestFileTree({
|
||||
<FileTreeLabel>package.json</FileTreeLabel>
|
||||
</FileTreeFile>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
)
|
||||
}
|
||||
|
||||
@ -101,14 +101,14 @@ describe('FileTree', () => {
|
||||
it('does not activate disabled file buttons', async () => {
|
||||
const onPreview = vi.fn()
|
||||
const screen = await render(
|
||||
<FileTreeRoot aria-label="Disabled files">
|
||||
<FileTree aria-label="Disabled files">
|
||||
<FileTreeList>
|
||||
<FileTreeFile disabled onClick={() => onPreview('disabled')}>
|
||||
<FileTreeIcon type="file" />
|
||||
<FileTreeLabel>disabled.txt</FileTreeLabel>
|
||||
</FileTreeFile>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>,
|
||||
</FileTree>,
|
||||
)
|
||||
|
||||
asHTMLElement(screen.getByRole('button', { name: 'disabled.txt' }).element()).click()
|
||||
@ -121,7 +121,7 @@ describe('FileTree', () => {
|
||||
it('resolves disabled folder triggers through the collapsible state', async () => {
|
||||
const onOpenChange = vi.fn()
|
||||
const screen = await render(
|
||||
<FileTreeRoot aria-label="Disabled folders">
|
||||
<FileTree aria-label="Disabled folders">
|
||||
<FileTreeList>
|
||||
<FileTreeFolder disabled defaultOpen onOpenChange={onOpenChange}>
|
||||
<FileTreeFolderTrigger>
|
||||
@ -136,7 +136,7 @@ describe('FileTree', () => {
|
||||
</FileTreeFolderPanel>
|
||||
</FileTreeFolder>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>,
|
||||
</FileTree>,
|
||||
)
|
||||
const trigger = screen.getByRole('button', { name: 'locked' })
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import type { FileTreeIconType } from '.'
|
||||
import * as React from 'react'
|
||||
import { expect } from 'storybook/test'
|
||||
import {
|
||||
FileTree,
|
||||
FileTreeBadge,
|
||||
FileTreeFile,
|
||||
FileTreeFolder,
|
||||
@ -12,12 +13,11 @@ import {
|
||||
FileTreeLabel,
|
||||
FileTreeList,
|
||||
FileTreeMeta,
|
||||
FileTreeRoot,
|
||||
} from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/FileTree',
|
||||
component: FileTreeRoot,
|
||||
component: FileTree,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
@ -28,7 +28,7 @@ const meta = {
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof FileTreeRoot>
|
||||
} satisfies Meta<typeof FileTree>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
@ -121,7 +121,7 @@ function ComposedFileTree() {
|
||||
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('button')
|
||||
|
||||
return (
|
||||
<FileTreeRoot
|
||||
<FileTree
|
||||
aria-label="Project files"
|
||||
className="w-80 rounded-lg border border-divider-subtle bg-background-default-subtle"
|
||||
>
|
||||
@ -172,7 +172,7 @@ function ComposedFileTree() {
|
||||
<FileTreeMeta>root</FileTreeMeta>
|
||||
</FileTreeFile>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
)
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ function DataDrivenFileTree() {
|
||||
const [selectedItemId, setSelectedItemId] = React.useState<string | null>('app-components-file-tree')
|
||||
|
||||
return (
|
||||
<FileTreeRoot
|
||||
<FileTree
|
||||
aria-label="Data-driven project files"
|
||||
className="w-80 rounded-lg border border-divider-subtle bg-background-default-subtle"
|
||||
>
|
||||
@ -191,7 +191,7 @@ function DataDrivenFileTree() {
|
||||
onPreview={setSelectedItemId}
|
||||
/>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
)
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ function IconGallery() {
|
||||
] as const
|
||||
|
||||
return (
|
||||
<FileTreeRoot aria-label="File icon examples" className="w-64 rounded-lg border border-divider-subtle bg-background-default-subtle">
|
||||
<FileTree aria-label="File icon examples" className="w-64 rounded-lg border border-divider-subtle bg-background-default-subtle">
|
||||
<FileTreeList>
|
||||
{iconTypes.map(type => (
|
||||
type === 'folder'
|
||||
@ -232,7 +232,7 @@ function IconGallery() {
|
||||
)
|
||||
))}
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
)
|
||||
}
|
||||
|
||||
@ -246,11 +246,11 @@ function StateFrame({
|
||||
return (
|
||||
<div className="w-80 min-w-0 space-y-1">
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{label}</div>
|
||||
<FileTreeRoot aria-label={label} className="rounded-lg border border-divider-subtle bg-background-default-subtle">
|
||||
<FileTree aria-label={label} className="rounded-lg border border-divider-subtle bg-background-default-subtle">
|
||||
<FileTreeList>
|
||||
{children}
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,14 +45,14 @@ function fileTreeRowClassName({
|
||||
)
|
||||
}
|
||||
|
||||
export type FileTreeRootProps = useRender.ComponentProps<'section'>
|
||||
export type FileTreeProps = useRender.ComponentProps<'section'>
|
||||
|
||||
export function FileTreeRoot({
|
||||
export function FileTree({
|
||||
render,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: FileTreeRootProps) {
|
||||
}: FileTreeProps) {
|
||||
const defaultProps: useRender.ElementProps<'section'> = {
|
||||
className: cn('flex min-w-0 flex-col gap-px p-1', className),
|
||||
children: (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '../../field'
|
||||
import { Field, FieldControl, FieldLabel } from '../../field'
|
||||
import { Form } from '../index'
|
||||
|
||||
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
|
||||
@ -8,10 +8,10 @@ describe('Form primitive', () => {
|
||||
it('should render a native named form and merge custom class names', async () => {
|
||||
const screen = await render(
|
||||
<Form aria-label="profile form" className="custom-form">
|
||||
<FieldRoot name="name">
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl defaultValue="Ada" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</Form>,
|
||||
)
|
||||
|
||||
@ -22,10 +22,10 @@ describe('Form primitive', () => {
|
||||
const onFormSubmit = vi.fn()
|
||||
const screen = await render(
|
||||
<Form aria-label="api form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="endpoint">
|
||||
<Field name="endpoint">
|
||||
<FieldLabel>Endpoint</FieldLabel>
|
||||
<FieldControl defaultValue="https://api.example.com" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
@ -41,10 +41,10 @@ describe('Form primitive', () => {
|
||||
it('should expose externally supplied errors through FieldError consumers', async () => {
|
||||
const screen = await render(
|
||||
<Form aria-label="server form" errors={{ token: 'Token has expired.' }}>
|
||||
<FieldRoot name="token">
|
||||
<Field name="token">
|
||||
<FieldLabel>Token</FieldLabel>
|
||||
<FieldControl defaultValue="expired" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</Form>,
|
||||
)
|
||||
|
||||
|
||||
@ -3,14 +3,14 @@ import { Button } from '../button'
|
||||
import { Checkbox } from '../checkbox'
|
||||
import { CheckboxGroup } from '../checkbox-group'
|
||||
import {
|
||||
Field,
|
||||
FieldControl,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldItem,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '../fieldset'
|
||||
import { Form } from './index'
|
||||
|
||||
const meta = {
|
||||
@ -28,22 +28,22 @@ type Story = StoryObj<typeof meta>
|
||||
export const Basic: Story = {
|
||||
render: () => (
|
||||
<Form className="grid w-96 gap-4" onFormSubmit={() => undefined}>
|
||||
<FieldRoot name="name">
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl required placeholder="Enter a name" />
|
||||
<FieldError match="valueMissing">Name is required.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<FieldRoot name="email">
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<FieldControl type="email" required placeholder="name@example.com" />
|
||||
<FieldDescription>Used for account notifications.</FieldDescription>
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<FieldRoot name="features">
|
||||
<FieldsetRoot render={<CheckboxGroup defaultValue={['search']} />}>
|
||||
<Field name="features">
|
||||
<Fieldset render={<CheckboxGroup defaultValue={['search']} />}>
|
||||
<FieldsetLegend>Features</FieldsetLegend>
|
||||
<div className="grid gap-2">
|
||||
<FieldItem>
|
||||
@ -59,8 +59,8 @@ export const Basic: Story = {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { FieldError, FieldLabel, FieldRoot } from '../../field'
|
||||
import { Field, FieldError, FieldLabel } from '../../field'
|
||||
import { Form } from '../../form'
|
||||
import { Input } from '../index'
|
||||
|
||||
@ -19,12 +19,12 @@ describe('Input', () => {
|
||||
await expect.element(input).toHaveValue('Dify')
|
||||
})
|
||||
|
||||
it('should use FieldRoot invalid state', async () => {
|
||||
it('should use Field invalid state', async () => {
|
||||
const screen = await render(
|
||||
<FieldRoot name="repositoryUrl" invalid>
|
||||
<Field name="repositoryUrl" invalid>
|
||||
<FieldLabel>Repository URL</FieldLabel>
|
||||
<Input defaultValue="github.com/langgenius" />
|
||||
</FieldRoot>,
|
||||
</Field>,
|
||||
)
|
||||
|
||||
const input = screen.getByRole('textbox', { name: 'Repository URL' })
|
||||
@ -33,15 +33,15 @@ describe('Input', () => {
|
||||
await expect.element(input).toHaveAttribute('data-invalid')
|
||||
})
|
||||
|
||||
it('should integrate with FieldRoot and Base UI Form validation', async () => {
|
||||
it('should integrate with Field and Base UI Form validation', async () => {
|
||||
const onFormSubmit = vi.fn()
|
||||
const screen = await render(
|
||||
<Form aria-label="account form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="email">
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<Input type="email" required />
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { Button } from '../button'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { Form } from '../form'
|
||||
import { Input } from './index'
|
||||
@ -16,7 +16,7 @@ const meta = {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'A standalone text input primitive built on Base UI Input. Use it for labelled text boxes outside FieldControl, and keep FieldControl for full FieldRoot form composition.',
|
||||
component: 'A standalone text input primitive built on Base UI Input. Use it for labelled text boxes outside FieldControl, and keep FieldControl for full Field form composition.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -74,7 +74,7 @@ export const States: Story = {
|
||||
<Input id="filled-state" name="filledState" defaultValue="Customer knowledge base" autoComplete="off" />
|
||||
</div>
|
||||
<div className="grid gap-1">
|
||||
<FieldRoot name="repositoryUrl" invalid>
|
||||
<Field name="repositoryUrl" invalid>
|
||||
<FieldLabel>Invalid</FieldLabel>
|
||||
<Input
|
||||
id="invalid-state"
|
||||
@ -85,7 +85,7 @@ export const States: Story = {
|
||||
spellCheck={false}
|
||||
/>
|
||||
<FieldError match>Enter a full URL including https://.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="grid gap-1">
|
||||
<label className="text-text-secondary system-sm-medium" htmlFor="disabled-state">Disabled</label>
|
||||
@ -102,20 +102,20 @@ export const States: Story = {
|
||||
export const WithField: Story = {
|
||||
render: () => (
|
||||
<Form aria-label="Account form" className="grid w-80 gap-4" onFormSubmit={() => undefined}>
|
||||
<FieldRoot name="email">
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<Input type="email" inputMode="email" required autoComplete="email" placeholder="name@example.com…" spellCheck={false} />
|
||||
<FieldDescription>Used for account notifications.</FieldDescription>
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="repositoryUrl">
|
||||
</Field>
|
||||
<Field name="repositoryUrl">
|
||||
<FieldLabel>Repository URL</FieldLabel>
|
||||
<Input type="url" inputMode="url" required autoComplete="off" placeholder="https://github.com/langgenius/dify…" spellCheck={false} />
|
||||
<FieldDescription>Use the full GitHub repository URL.</FieldDescription>
|
||||
<FieldError match="valueMissing">Repository URL is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid URL.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save Settings</Button>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
Meter,
|
||||
MeterIndicator,
|
||||
MeterLabel,
|
||||
MeterRoot,
|
||||
MeterTrack,
|
||||
MeterValue,
|
||||
} from '../index'
|
||||
@ -10,11 +10,11 @@ import {
|
||||
describe('Meter compound primitives', () => {
|
||||
it('exposes role="meter" with ARIA value metadata', async () => {
|
||||
const screen = await render(
|
||||
<MeterRoot value={40} aria-label="Quota">
|
||||
<Meter value={40} aria-label="Quota">
|
||||
<MeterTrack>
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
</MeterRoot>,
|
||||
</Meter>,
|
||||
)
|
||||
|
||||
const meter = screen.getByLabelText('Quota')
|
||||
@ -26,11 +26,11 @@ describe('Meter compound primitives', () => {
|
||||
|
||||
it('respects custom min and max', async () => {
|
||||
const screen = await render(
|
||||
<MeterRoot value={3} min={1} max={5} aria-label="Quota">
|
||||
<Meter value={3} min={1} max={5} aria-label="Quota">
|
||||
<MeterTrack>
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
</MeterRoot>,
|
||||
</Meter>,
|
||||
)
|
||||
|
||||
const meter = screen.getByLabelText('Quota')
|
||||
@ -41,11 +41,11 @@ describe('Meter compound primitives', () => {
|
||||
|
||||
it('sets indicator width from value/min/max', async () => {
|
||||
const screen = await render(
|
||||
<MeterRoot value={42} aria-label="Quota">
|
||||
<Meter value={42} aria-label="Quota">
|
||||
<MeterTrack>
|
||||
<MeterIndicator data-testid="indicator" />
|
||||
</MeterTrack>
|
||||
</MeterRoot>,
|
||||
</Meter>,
|
||||
)
|
||||
|
||||
const indicator = screen.getByTestId('indicator').element() as HTMLElement
|
||||
@ -54,11 +54,11 @@ describe('Meter compound primitives', () => {
|
||||
|
||||
it('forwards className to MeterTrack', async () => {
|
||||
const screen = await render(
|
||||
<MeterRoot value={10} aria-label="Quota">
|
||||
<Meter value={10} aria-label="Quota">
|
||||
<MeterTrack className="custom-track" data-testid="track">
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
</MeterRoot>,
|
||||
</Meter>,
|
||||
)
|
||||
|
||||
const track = screen.getByTestId('track').element() as HTMLElement
|
||||
@ -67,7 +67,7 @@ describe('Meter compound primitives', () => {
|
||||
|
||||
it('renders MeterLabel and MeterValue inside a compound layout', async () => {
|
||||
const screen = await render(
|
||||
<MeterRoot
|
||||
<Meter
|
||||
value={0.42}
|
||||
min={0}
|
||||
max={1}
|
||||
@ -79,7 +79,7 @@ describe('Meter compound primitives', () => {
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
<MeterValue />
|
||||
</MeterRoot>,
|
||||
</Meter>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByText('Score')).toBeInTheDocument()
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { MeterIndicator, MeterLabel, MeterRoot, MeterTrack, MeterValue } from '.'
|
||||
import { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue } from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Meter',
|
||||
component: MeterRoot,
|
||||
component: Meter,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'A graphical display of a numeric value within a known range. '
|
||||
+ 'Use the compound primitives (`MeterRoot / MeterTrack / MeterIndicator / '
|
||||
+ 'Use the compound primitives (`Meter / MeterTrack / MeterIndicator / '
|
||||
+ 'MeterValue / MeterLabel`) for quota, capacity, or score indicators; do '
|
||||
+ 'not use for task-completion progress.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof MeterRoot>
|
||||
} satisfies Meta<typeof Meter>
|
||||
|
||||
export default meta
|
||||
|
||||
@ -30,11 +30,11 @@ export const Default: Story = {
|
||||
},
|
||||
render: args => (
|
||||
<div className="w-[320px]">
|
||||
<MeterRoot {...args}>
|
||||
<Meter {...args}>
|
||||
<MeterTrack>
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -46,11 +46,11 @@ export const Warning: Story = {
|
||||
},
|
||||
render: args => (
|
||||
<div className="w-[320px]">
|
||||
<MeterRoot {...args}>
|
||||
<Meter {...args}>
|
||||
<MeterTrack>
|
||||
<MeterIndicator tone="warning" />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -62,11 +62,11 @@ export const Error: Story = {
|
||||
},
|
||||
render: args => (
|
||||
<div className="w-[320px]">
|
||||
<MeterRoot {...args}>
|
||||
<Meter {...args}>
|
||||
<MeterTrack>
|
||||
<MeterIndicator tone="error" />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -78,7 +78,7 @@ export const ComposedWithLabelAndValue: Story = {
|
||||
},
|
||||
render: args => (
|
||||
<div className="w-[320px] space-y-2 rounded-xl bg-components-panel-bg p-4">
|
||||
<MeterRoot {...args}>
|
||||
<Meter {...args}>
|
||||
<div className="flex items-center justify-between">
|
||||
<MeterLabel>Storage</MeterLabel>
|
||||
<MeterValue />
|
||||
@ -86,7 +86,7 @@ export const ComposedWithLabelAndValue: Story = {
|
||||
<MeterTrack className="mt-2">
|
||||
<MeterIndicator tone="warning" />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -101,7 +101,7 @@ export const PercentFormatted: Story = {
|
||||
},
|
||||
render: args => (
|
||||
<div className="w-[320px] space-y-2">
|
||||
<MeterRoot {...args}>
|
||||
<Meter {...args}>
|
||||
<div className="flex items-center justify-between">
|
||||
<MeterLabel>Score</MeterLabel>
|
||||
<MeterValue />
|
||||
@ -109,7 +109,7 @@ export const PercentFormatted: Story = {
|
||||
<MeterTrack className="mt-2">
|
||||
<MeterIndicator />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ import { Meter as BaseMeter } from '@base-ui/react/meter'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export const MeterRoot = BaseMeter.Root
|
||||
export type MeterRootProps = BaseMeter.Root.Props
|
||||
export const Meter = BaseMeter.Root
|
||||
export type MeterProps = BaseMeter.Root.Props
|
||||
|
||||
const meterTrackClassName
|
||||
= 'relative block h-1 w-full overflow-hidden rounded-md bg-components-progress-bar-bg'
|
||||
|
||||
@ -8,8 +8,8 @@ import type {
|
||||
import * as React from 'react'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../../field'
|
||||
import {
|
||||
NumberField,
|
||||
@ -79,14 +79,14 @@ describe('NumberField wrapper', () => {
|
||||
|
||||
it('should surface field invalid state on the visual group', async () => {
|
||||
const screen = await render(
|
||||
<FieldRoot name="amount" invalid>
|
||||
<Field name="amount" invalid>
|
||||
<FieldLabel>Amount</FieldLabel>
|
||||
<NumberField defaultValue={8}>
|
||||
<NumberFieldGroup data-testid="group">
|
||||
<NumberFieldInput />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>,
|
||||
</Field>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByTestId('group')).toHaveAttribute('data-invalid')
|
||||
|
||||
@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import * as React from 'react'
|
||||
import { Button } from '../button'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { Form } from '../form'
|
||||
import {
|
||||
@ -25,7 +25,7 @@ const meta = {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Compound numeric input built on Base UI NumberField. Use it with FieldRoot for labelled, described, and validated form fields.',
|
||||
component: 'Compound numeric input built on Base UI NumberField. Use it with Field for labelled, described, and validated form fields.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -140,7 +140,7 @@ export const Sizes: Story = {
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-80 gap-3">
|
||||
<FieldRoot name="placeholderState">
|
||||
<Field name="placeholderState">
|
||||
<FieldLabel>Placeholder</FieldLabel>
|
||||
<NumberField min={0} max={100}>
|
||||
<NumberFieldGroup>
|
||||
@ -152,8 +152,8 @@ export const States: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="filledState">
|
||||
</Field>
|
||||
<Field name="filledState">
|
||||
<FieldLabel>Filled</FieldLabel>
|
||||
<NumberField defaultValue={85} min={0} max={100}>
|
||||
<NumberFieldGroup>
|
||||
@ -165,8 +165,8 @@ export const States: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="invalidState" invalid>
|
||||
</Field>
|
||||
<Field name="invalidState" invalid>
|
||||
<FieldLabel>Invalid</FieldLabel>
|
||||
<NumberField defaultValue={120} min={0} max={100}>
|
||||
<NumberFieldGroup>
|
||||
@ -179,8 +179,8 @@ export const States: Story = {
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
<FieldError match>Use a value from 0 to 100.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="disabledState">
|
||||
</Field>
|
||||
<Field name="disabledState">
|
||||
<FieldLabel>Disabled</FieldLabel>
|
||||
<NumberField defaultValue={5} min={0} max={10} disabled>
|
||||
<NumberFieldGroup>
|
||||
@ -191,8 +191,8 @@ export const States: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="readonlyState">
|
||||
</Field>
|
||||
<Field name="readonlyState">
|
||||
<FieldLabel>Read-only</FieldLabel>
|
||||
<NumberField defaultValue={92} min={0} max={100} readOnly>
|
||||
<NumberFieldGroup>
|
||||
@ -204,7 +204,7 @@ export const States: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -213,7 +213,7 @@ function ControlledDemo() {
|
||||
const [value, setValue] = React.useState<number | null>(0.82)
|
||||
|
||||
return (
|
||||
<FieldRoot name="controlledThreshold">
|
||||
<Field name="controlledThreshold">
|
||||
<FieldLabel>Score threshold</FieldLabel>
|
||||
<NumberField
|
||||
value={value}
|
||||
@ -238,7 +238,7 @@ function ControlledDemo() {
|
||||
{' '}
|
||||
{value === null ? 'Empty' : value.toFixed(2)}
|
||||
</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ function FormDemo() {
|
||||
setSavedValue(String(values.topK ?? ''))
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="topK">
|
||||
<Field name="topK">
|
||||
<FieldLabel>Top K</FieldLabel>
|
||||
<NumberField required defaultValue={3} min={1} max={10} step={1}>
|
||||
<NumberFieldGroup>
|
||||
@ -276,7 +276,7 @@ function FormDemo() {
|
||||
<FieldError match="valueMissing">Top K is required.</FieldError>
|
||||
<FieldError match="rangeUnderflow">Use at least 1.</FieldError>
|
||||
<FieldError match="rangeOverflow">Use 10 or fewer.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save Settings</Button>
|
||||
</div>
|
||||
@ -298,7 +298,7 @@ export const WithField: Story = {
|
||||
export const Formatting: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-80 gap-3">
|
||||
<FieldRoot name="currencyBudget">
|
||||
<Field name="currencyBudget">
|
||||
<FieldLabel>Budget</FieldLabel>
|
||||
<NumberField
|
||||
defaultValue={1200}
|
||||
@ -318,8 +318,8 @@ export const Formatting: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="temperature">
|
||||
</Field>
|
||||
<Field name="temperature">
|
||||
<FieldLabel>Temperature</FieldLabel>
|
||||
<NumberField
|
||||
defaultValue={0.7}
|
||||
@ -338,7 +338,7 @@ export const Formatting: Story = {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { cn } from '../cn'
|
||||
import { textControlCompoundFocusClassName } from '../form-control-shared'
|
||||
|
||||
export const NumberField = BaseNumberField.Root
|
||||
export type NumberFieldRootProps = BaseNumberField.Root.Props
|
||||
export type NumberFieldProps = BaseNumberField.Root.Props
|
||||
|
||||
export const numberFieldGroupVariants = cva(
|
||||
[
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { RadioGroupProps } from '../index'
|
||||
import * as React from 'react'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '../../field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../../fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '../../field'
|
||||
import { Fieldset, FieldsetLegend } from '../../fieldset'
|
||||
import { Radio, RadioControl, RadioGroup, RadioItem, RadioSkeleton } from '../index'
|
||||
|
||||
const clickElement = (element: HTMLElement | SVGElement) => {
|
||||
@ -22,12 +22,12 @@ function TestRadioGroup<Value = string>({
|
||||
...props
|
||||
}: TestRadioGroupProps<Value>) {
|
||||
return (
|
||||
<FieldRoot name={name}>
|
||||
<FieldsetRoot render={<RadioGroup<Value> {...props} />}>
|
||||
<Field name={name}>
|
||||
<Fieldset render={<RadioGroup<Value> {...props} />}>
|
||||
<FieldsetLegend>{label}</FieldsetLegend>
|
||||
{children}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -8,12 +8,12 @@ import {
|
||||
RadioSkeleton,
|
||||
} from '.'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldItem,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '../fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '../fieldset'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Form/Radio',
|
||||
@ -36,8 +36,8 @@ function StandardFormRowsDemo() {
|
||||
const [value, setValue] = React.useState('vector')
|
||||
|
||||
return (
|
||||
<FieldRoot name="retrievalIndex" className="w-80">
|
||||
<FieldsetRoot
|
||||
<Field name="retrievalIndex" className="w-80">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup value={value} onValueChange={setValue} className="flex-col items-start gap-3" />
|
||||
)}
|
||||
@ -55,8 +55,8 @@ function StandardFormRowsDemo() {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -75,8 +75,8 @@ function BooleanInlineDemo() {
|
||||
const [value, setValue] = React.useState(true)
|
||||
|
||||
return (
|
||||
<FieldRoot name="streaming" className="w-80">
|
||||
<FieldsetRoot
|
||||
<Field name="streaming" className="w-80">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<boolean> value={value} onValueChange={setValue} className="gap-3" />
|
||||
)}
|
||||
@ -96,8 +96,8 @@ function BooleanInlineDemo() {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -135,8 +135,8 @@ function OptionCardsDemo() {
|
||||
}>
|
||||
|
||||
return (
|
||||
<FieldRoot name="promptMode" className="w-100">
|
||||
<FieldsetRoot
|
||||
<Field name="promptMode" className="w-100">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<PromptMode> value={value} onValueChange={setValue} className="flex-col items-stretch gap-3" />
|
||||
)}
|
||||
@ -164,8 +164,8 @@ function OptionCardsDemo() {
|
||||
</RadioItem>
|
||||
</FieldItem>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -189,11 +189,11 @@ function DynamicFormFieldDemo() {
|
||||
const [selected, setSelected] = React.useState('automatic')
|
||||
|
||||
return (
|
||||
<FieldRoot name="generation_mode" className="flex w-80 flex-col gap-2">
|
||||
<Field name="generation_mode" className="flex w-80 flex-col gap-2">
|
||||
<FieldDescription className="body-xs-regular text-text-tertiary">
|
||||
This mirrors Dify dynamic form fields where radio options are controlled by schema and persisted as a single value.
|
||||
</FieldDescription>
|
||||
<FieldsetRoot
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup
|
||||
value={selected}
|
||||
@ -213,8 +213,8 @@ function DynamicFormFieldDemo() {
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -232,8 +232,8 @@ export const DynamicFormField: Story = {
|
||||
export const StateMatrix: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-3">
|
||||
<FieldRoot name="radioStates">
|
||||
<FieldsetRoot render={<RadioGroup defaultValue="checked" className="flex-col items-start gap-3" />}>
|
||||
<Field name="radioStates">
|
||||
<Fieldset render={<RadioGroup defaultValue="checked" className="flex-col items-start gap-3" />}>
|
||||
<FieldsetLegend>Interactive radio states</FieldsetLegend>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2 system-sm-medium text-text-secondary">
|
||||
@ -247,10 +247,10 @@ export const StateMatrix: Story = {
|
||||
Checked
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="disabledRadioStates">
|
||||
<FieldsetRoot render={<RadioGroup defaultValue="disabled-checked" disabled className="flex-col items-start gap-3" />}>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
<Field name="disabledRadioStates">
|
||||
<Fieldset render={<RadioGroup defaultValue="disabled-checked" disabled className="flex-col items-start gap-3" />}>
|
||||
<FieldsetLegend>Disabled radio states</FieldsetLegend>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2 system-sm-medium text-text-secondary">
|
||||
@ -264,8 +264,8 @@ export const StateMatrix: Story = {
|
||||
Disabled checked
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
<div className="flex items-center gap-2 system-sm-medium text-text-secondary">
|
||||
<RadioSkeleton aria-hidden="true" />
|
||||
Skeleton
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
SelectValue,
|
||||
} from '.'
|
||||
import { Button } from '../button'
|
||||
import { FieldDescription, FieldRoot } from '../field'
|
||||
import { Field, FieldDescription } from '../field'
|
||||
import { Form } from '../form'
|
||||
|
||||
const triggerWidth = 'w-64'
|
||||
@ -330,7 +330,7 @@ export const Controlled: Story = {
|
||||
export const InForm: Story = {
|
||||
render: () => (
|
||||
<Form aria-label="Timezone form" className="grid w-72 gap-3" onFormSubmit={() => undefined}>
|
||||
<FieldRoot name="timezone">
|
||||
<Field name="timezone">
|
||||
<Select name="timezone" defaultValue="utc">
|
||||
<SelectLabel>Timezone</SelectLabel>
|
||||
<SelectTrigger>
|
||||
@ -352,7 +352,7 @@ export const InForm: Story = {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldDescription>Used to schedule workflow runs.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
</div>
|
||||
|
||||
@ -16,13 +16,13 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export type SelectRootProps<
|
||||
export type SelectProps<
|
||||
Value,
|
||||
Multiple extends boolean | undefined = false,
|
||||
> = BaseSelect.Root.Props<Value, Multiple>
|
||||
|
||||
export function Select<Value, Multiple extends boolean | undefined = false>(
|
||||
props: SelectRootProps<Value, Multiple>,
|
||||
props: SelectProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseSelect.Root {...props} />
|
||||
}
|
||||
|
||||
@ -3,9 +3,9 @@ import * as React from 'react'
|
||||
import { expect } from 'storybook/test'
|
||||
import { Switch, SwitchSkeleton } from '.'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
|
||||
const meta = {
|
||||
@ -55,7 +55,7 @@ const SwitchDemo = (args: SwitchDemoProps) => {
|
||||
const [enabled, setEnabled] = React.useState(args.checked ?? false)
|
||||
|
||||
return (
|
||||
<FieldRoot name="autoRetry" className="w-72">
|
||||
<Field name="autoRetry" className="w-72">
|
||||
<FieldLabel className="flex items-center justify-between gap-3">
|
||||
<span>Enable auto retry</span>
|
||||
<Switch
|
||||
@ -67,7 +67,7 @@ const SwitchDemo = (args: SwitchDemoProps) => {
|
||||
<FieldDescription>
|
||||
{enabled ? 'Failures will retry automatically.' : 'Failures require manual retry.'}
|
||||
</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -187,30 +187,30 @@ const SizeComparisonDemo = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<FieldRoot name="extraSmallSwitch">
|
||||
<Field name="extraSmallSwitch">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="xs" checked={states.xs} onCheckedChange={v => setStates({ ...states, xs: v })} />
|
||||
Extra Small (xs) - 14x10
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="smallSwitch">
|
||||
</Field>
|
||||
<Field name="smallSwitch">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="sm" checked={states.sm} onCheckedChange={v => setStates({ ...states, sm: v })} />
|
||||
Small (sm) - 20x12
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="regularSwitch">
|
||||
</Field>
|
||||
<Field name="regularSwitch">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="md" checked={states.md} onCheckedChange={v => setStates({ ...states, md: v })} />
|
||||
Regular (md) - 28x16
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="largeSwitch">
|
||||
</Field>
|
||||
<Field name="largeSwitch">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="lg" checked={states.lg} onCheckedChange={v => setStates({ ...states, lg: v })} />
|
||||
Large (lg) - 36x20
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -231,42 +231,42 @@ const LoadingDemo = () => {
|
||||
{loading ? 'Stop Loading' : 'Start Loading'}
|
||||
</button>
|
||||
<div className="space-y-3">
|
||||
<FieldRoot name="largeUncheckedLoading">
|
||||
<Field name="largeUncheckedLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="lg" checked={false} loading={loading} />
|
||||
Large unchecked
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="largeCheckedLoading">
|
||||
</Field>
|
||||
<Field name="largeCheckedLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="lg" checked={true} loading={loading} />
|
||||
Large checked
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="regularUncheckedLoading">
|
||||
</Field>
|
||||
<Field name="regularUncheckedLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="md" checked={false} loading={loading} />
|
||||
Regular unchecked
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="regularCheckedLoading">
|
||||
</Field>
|
||||
<Field name="regularCheckedLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="md" checked={true} loading={loading} />
|
||||
Regular checked
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="smallLoading">
|
||||
</Field>
|
||||
<Field name="smallLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="sm" checked={false} loading={loading} />
|
||||
Small
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="extraSmallLoading">
|
||||
</Field>
|
||||
<Field name="extraSmallLoading">
|
||||
<FieldLabel className="flex items-center gap-3">
|
||||
<Switch size="xs" checked={false} loading={loading} />
|
||||
Extra Small
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -335,7 +335,7 @@ const MutationLoadingDemo = () => {
|
||||
|
||||
return (
|
||||
<div className="grid w-90 gap-3 rounded-lg border border-components-panel-border bg-components-panel-bg p-4 shadow-sm">
|
||||
<FieldRoot name="autoRetry">
|
||||
<Field name="autoRetry">
|
||||
<FieldLabel className="flex items-center justify-between gap-4">
|
||||
<span className="system-sm-medium text-text-secondary">Enable auto retry</span>
|
||||
<Switch
|
||||
@ -346,7 +346,7 @@ const MutationLoadingDemo = () => {
|
||||
/>
|
||||
</FieldLabel>
|
||||
<FieldDescription>Retry failed workflow runs without manual intervention.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<span className="text-xs text-text-tertiary" aria-live="polite">
|
||||
{statusText}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type * as React from 'react'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../../field'
|
||||
import { Form } from '../../form'
|
||||
import { Textarea } from '../index'
|
||||
@ -20,11 +20,11 @@ const setTextareaValue = (element: HTMLElement | SVGElement, value: string) => {
|
||||
describe('Textarea', () => {
|
||||
it('should render a labelled textarea through Base UI Field.Control', async () => {
|
||||
const screen = await render(
|
||||
<FieldRoot name="description">
|
||||
<Field name="description">
|
||||
<FieldLabel>Description</FieldLabel>
|
||||
<Textarea defaultValue="A workspace for support automation." />
|
||||
<FieldDescription>Shown to workspace members.</FieldDescription>
|
||||
</FieldRoot>,
|
||||
</Field>,
|
||||
)
|
||||
|
||||
const textarea = screen.getByRole('textbox', { name: 'Description' })
|
||||
@ -73,12 +73,12 @@ describe('Textarea', () => {
|
||||
const onFormSubmit = vi.fn()
|
||||
const screen = await render(
|
||||
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="summary">
|
||||
<Field name="summary">
|
||||
<FieldLabel>Summary</FieldLabel>
|
||||
<Textarea required minLength={10} />
|
||||
<FieldError match="valueMissing">Summary is required.</FieldError>
|
||||
<FieldError match="tooShort">Summary is too short.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
@ -94,12 +94,12 @@ describe('Textarea', () => {
|
||||
|
||||
await screen.rerender(
|
||||
<Form aria-label="dataset form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="summary">
|
||||
<Field name="summary">
|
||||
<FieldLabel>Summary</FieldLabel>
|
||||
<Textarea key="valid-summary" required minLength={10} defaultValue="Long enough summary" />
|
||||
<FieldError match="valueMissing">Summary is required.</FieldError>
|
||||
<FieldError match="tooShort">Summary is too short.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
@ -129,7 +129,7 @@ describe('Textarea', () => {
|
||||
})
|
||||
const screen = await render(
|
||||
<Form aria-label="profile form" onFormSubmit={onFormSubmit}>
|
||||
<FieldRoot name="profileSummary">
|
||||
<Field name="profileSummary">
|
||||
<FieldLabel>Profile summary</FieldLabel>
|
||||
<Textarea
|
||||
id="profile-summary"
|
||||
@ -141,11 +141,11 @@ describe('Textarea', () => {
|
||||
maxLength={80}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</FieldRoot>
|
||||
<FieldRoot disabled>
|
||||
</Field>
|
||||
<Field disabled>
|
||||
<FieldLabel>Disabled note</FieldLabel>
|
||||
<Textarea name="disabledNote" defaultValue="Disabled value" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<button type="submit">Save</button>
|
||||
</Form>,
|
||||
)
|
||||
|
||||
@ -2,10 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import * as React from 'react'
|
||||
import { Button } from '../button'
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
import { Form } from '../form'
|
||||
import { Textarea } from './index'
|
||||
@ -17,7 +17,7 @@ const meta = {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Multiline text control built on Base UI Field.Control. Use it with FieldRoot for labelled, described, and validated form fields.',
|
||||
component: 'Multiline text control built on Base UI Field.Control. Use it with Field for labelled, described, and validated form fields.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -65,27 +65,27 @@ export const Sizes: Story = {
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-80 gap-3">
|
||||
<FieldRoot name="placeholderState">
|
||||
<Field name="placeholderState">
|
||||
<FieldLabel>Placeholder</FieldLabel>
|
||||
<Textarea placeholder="Add a description..." rows={3} />
|
||||
</FieldRoot>
|
||||
<FieldRoot name="filledState">
|
||||
</Field>
|
||||
<Field name="filledState">
|
||||
<FieldLabel>Filled</FieldLabel>
|
||||
<Textarea defaultValue="Use this dataset for support articles and product FAQs." rows={3} />
|
||||
</FieldRoot>
|
||||
<FieldRoot name="invalidState" invalid>
|
||||
</Field>
|
||||
<Field name="invalidState" invalid>
|
||||
<FieldLabel>Invalid</FieldLabel>
|
||||
<Textarea defaultValue="Too short" rows={3} />
|
||||
<FieldError match>Use at least 20 characters.</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="disabledState">
|
||||
</Field>
|
||||
<Field name="disabledState">
|
||||
<FieldLabel>Disabled</FieldLabel>
|
||||
<Textarea disabled placeholder="Editing is unavailable..." rows={3} />
|
||||
</FieldRoot>
|
||||
<FieldRoot name="readonlyState">
|
||||
</Field>
|
||||
<Field name="readonlyState">
|
||||
<FieldLabel>Read-only</FieldLabel>
|
||||
<Textarea readOnly defaultValue="Generated from the published workflow configuration." rows={3} />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@ -101,7 +101,7 @@ const FormDemo = () => {
|
||||
setSavedDescription(String(values.description ?? ''))
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="description">
|
||||
<Field name="description">
|
||||
<FieldLabel>Description</FieldLabel>
|
||||
<Textarea
|
||||
required
|
||||
@ -114,7 +114,7 @@ const FormDemo = () => {
|
||||
<FieldDescription>Shown to teammates when they choose a knowledge source.</FieldDescription>
|
||||
<FieldError match="valueMissing">Description is required.</FieldError>
|
||||
<FieldError match="tooShort">Use at least 20 characters.</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">Save Settings</Button>
|
||||
</div>
|
||||
@ -137,7 +137,7 @@ const ControlledDemo = () => {
|
||||
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
|
||||
|
||||
return (
|
||||
<FieldRoot name="prompt">
|
||||
<Field name="prompt">
|
||||
<FieldLabel>Prompt</FieldLabel>
|
||||
<Textarea
|
||||
value={value}
|
||||
@ -146,7 +146,7 @@ const ControlledDemo = () => {
|
||||
className="resize-y"
|
||||
/>
|
||||
<FieldDescription>The saved value is updated from the controlled state.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ const CharacterCounterDemo = () => {
|
||||
const [value, setValue] = React.useState('Summarize customer feedback into actionable product themes.')
|
||||
|
||||
return (
|
||||
<FieldRoot name="limitedPrompt">
|
||||
<Field name="limitedPrompt">
|
||||
<FieldLabel>Prompt</FieldLabel>
|
||||
<div className="relative">
|
||||
<Textarea
|
||||
@ -180,7 +180,7 @@ const CharacterCounterDemo = () => {
|
||||
</div>
|
||||
</div>
|
||||
<FieldDescription>Character counters are composed at the usage site when the workflow needs one.</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
|
||||
import { Avatar } from '@langgenius/dify-ui/avatar'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
@ -71,7 +71,7 @@ export default function AddMemberOrGroupDialog() {
|
||||
setOpen(nextOpen)
|
||||
}
|
||||
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
|
||||
if (details.reason !== 'item-press')
|
||||
setKeyword(inputValue)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
||||
import type { VersionHistory } from '@/types/workflow'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
@ -85,7 +85,7 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4 px-6 py-3">
|
||||
<FieldRoot name="title" invalid={titleError} className="gap-y-1">
|
||||
<Field name="title" invalid={titleError} className="gap-y-1">
|
||||
<FieldLabel className="flex h-6 items-center py-0 system-sm-semibold text-text-secondary">
|
||||
{t($ => $['versionHistory.editField.title'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
@ -94,8 +94,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
|
||||
placeholder={`${t($ => $['versionHistory.nameThisVersion'], { ns: 'workflow' })}${t($ => $['panel.optional'], { ns: 'workflow' })}`}
|
||||
onValueChange={setTitle}
|
||||
/>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="releaseNotes" invalid={releaseNotesError} className="gap-y-1">
|
||||
</Field>
|
||||
<Field name="releaseNotes" invalid={releaseNotesError} className="gap-y-1">
|
||||
<FieldLabel className="flex h-6 items-center py-0 system-sm-semibold text-text-secondary">
|
||||
{t($ => $['versionHistory.editField.releaseNotes'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
@ -104,7 +104,7 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
|
||||
placeholder={`${t($ => $['versionHistory.releaseNotesPlaceholder'], { ns: 'workflow' })}${t($ => $['panel.optional'], { ns: 'workflow' })}`}
|
||||
onValueChange={handleDescriptionChange}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="flex justify-end p-6 pt-5">
|
||||
<div className="flex items-center gap-x-3">
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { AgentConfig } from '@/models/debug'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useClickAway } from 'ahooks'
|
||||
@ -101,7 +101,7 @@ const AgentSetting: FC<Props> = ({
|
||||
name={maximumIterationsLabel}
|
||||
description={t($ => $['agent.setting.maximumIterations.description'], { ns: 'appDebug' })}
|
||||
>
|
||||
<FieldsetRoot className="flex items-center">
|
||||
<Fieldset className="flex items-center">
|
||||
<FieldsetLegend className="sr-only">{maximumIterationsLabel}</FieldsetLegend>
|
||||
<Slider
|
||||
className="mr-3 w-[156px]"
|
||||
@ -138,7 +138,7 @@ const AgentSetting: FC<Props> = ({
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
</ItemPanel>
|
||||
|
||||
{!isFunctionCall && (
|
||||
|
||||
@ -5,7 +5,7 @@ import type { AppIconType, Language, SiteConfig } from '@/types/app'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldDescription, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
@ -360,14 +360,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
>
|
||||
{/* name & icon */}
|
||||
<div className="flex gap-4">
|
||||
<FieldRoot name="title" className="grow">
|
||||
<Field name="title" className="grow">
|
||||
<FieldLabel>{t($ => $[`${prefixSettings}.webName`], { ns: 'appOverview' })}</FieldLabel>
|
||||
<FieldControl
|
||||
value={inputInfo.title}
|
||||
onValueChange={value => setInputInfo(item => ({ ...item, title: value }))}
|
||||
placeholder={t($ => $.appNamePlaceholder, { ns: 'app' }) || ''}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
onClick={() => { setShowAppIconPicker(true) }}
|
||||
@ -379,7 +379,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
{/* description */}
|
||||
<FieldRoot name="description">
|
||||
<Field name="description">
|
||||
<FieldLabel>{t($ => $[`${prefixSettings}.webDesc`], { ns: 'appOverview' })}</FieldLabel>
|
||||
<Textarea
|
||||
value={inputInfo.desc}
|
||||
@ -387,11 +387,11 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
placeholder={t($ => $[`${prefixSettings}.webDescPlaceholder`], { ns: 'appOverview' }) as string}
|
||||
/>
|
||||
<FieldDescription>{t($ => $[`${prefixSettings}.webDescTip`], { ns: 'appOverview' })}</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
{/* answer icon */}
|
||||
{isChat && (
|
||||
<FieldRoot name="use_icon_as_answer_icon" className="w-full">
|
||||
<Field name="use_icon_as_answer_icon" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t($ => $['answerIcon.title'], { ns: 'app' })}</FieldLabel>
|
||||
<Switch
|
||||
@ -400,7 +400,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>{t($ => $['answerIcon.description'], { ns: 'app' })}</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)}
|
||||
{/* language */}
|
||||
<div className="flex items-center">
|
||||
@ -433,7 +433,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>{t($ => $[`${prefixSettings}.chatColorTheme`], { ns: 'appOverview' })}</div>
|
||||
<div className="pb-0.5 body-xs-regular text-text-tertiary">{t($ => $[`${prefixSettings}.chatColorThemeDesc`], { ns: 'appOverview' })}</div>
|
||||
</div>
|
||||
<FieldRoot name="chat_color_theme" className="w-[200px] shrink-0">
|
||||
<Field name="chat_color_theme" className="w-[200px] shrink-0">
|
||||
<FieldControl
|
||||
className="mb-1"
|
||||
value={inputInfo.chatColorTheme ?? ''}
|
||||
@ -444,11 +444,11 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
<span>{t($ => $[`${prefixSettings}.chatColorThemeInverted`], { ns: 'appOverview' })}</span>
|
||||
<Switch checked={inputInfo.chatColorThemeInverted} onCheckedChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
{/* workflow detail */}
|
||||
<FieldRoot name="show_workflow_steps" className="w-full">
|
||||
<Field name="show_workflow_steps" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t($ => $[`${prefixSettings}.workflow.subTitle`], { ns: 'appOverview' })}</FieldLabel>
|
||||
<Switch
|
||||
@ -458,7 +458,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>{t($ => $[`${prefixSettings}.workflow.showDesc`], { ns: 'appOverview' })}</FieldDescription>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
<div className="space-y-5">
|
||||
{INPUT_PLACEHOLDER_SUPPORTED_MODES.includes(appInfo.mode) && (
|
||||
|
||||
@ -23,7 +23,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
Tooltip,
|
||||
@ -692,7 +692,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t($ => $.deleteAppConfirmContent, { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
<FieldRoot name="confirm-app-name" className="mt-2">
|
||||
<Field name="confirm-app-name" className="mt-2">
|
||||
<FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={$ => $.deleteAppConfirmInputLabel}
|
||||
@ -712,7 +712,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
|
||||
onValueChange={setConfirmDeleteInput}
|
||||
className="border-components-input-border-hover bg-components-input-bg-normal focus:border-components-input-border-active focus:bg-components-input-bg-active"
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton type="button" disabled={isDeleting}>
|
||||
@ -1286,7 +1286,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t($ => $.deleteAppConfirmContent, { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
<FieldRoot name="confirm-app-name" className="mt-2">
|
||||
<Field name="confirm-app-name" className="mt-2">
|
||||
<FieldLabel className="mb-1 block py-0 system-sm-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={$ => $.deleteAppConfirmInputLabel}
|
||||
@ -1315,7 +1315,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
|
||||
{t($ => $['operation.fill'], { ns: 'common' })}
|
||||
</button>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton type="button" disabled={isDeleting}>
|
||||
|
||||
@ -3,8 +3,8 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldDescription, FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldDescription, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
@ -69,8 +69,8 @@ export const CheckboxList = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<FieldRoot name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}>
|
||||
<FieldsetRoot
|
||||
<Field name={name} className={cn('flex w-full flex-col gap-1', containerClassName)}>
|
||||
<Fieldset
|
||||
render={(
|
||||
<CheckboxGroup
|
||||
aria-label={!label && title ? title : undefined}
|
||||
@ -191,7 +191,7 @@ export const CheckboxList = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ import type {
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldItem, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { produce } from 'immer'
|
||||
@ -147,8 +147,8 @@ const FollowUpSettingModal = ({
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
<FieldRoot name="follow_up_prompt_mode" className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name="follow_up_prompt_mode" className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<PromptMode>
|
||||
className="flex-col items-stretch gap-3"
|
||||
@ -227,8 +227,8 @@ const FollowUpSettingModal = ({
|
||||
)}
|
||||
</RadioItem>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mt-6 flex items-center justify-end gap-2">
|
||||
<Button onClick={onCancel}>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { AnyFieldApi } from '@tanstack/react-form'
|
||||
import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base/form/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import {
|
||||
Select,
|
||||
@ -391,8 +391,8 @@ const BaseField = ({
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.radio && (
|
||||
<FieldRoot name={name} className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name={name} className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup
|
||||
value={stringValue}
|
||||
@ -436,14 +436,14 @@ const BaseField = ({
|
||||
</FieldItem>
|
||||
))
|
||||
}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.boolean && (
|
||||
<FieldRoot name={name} className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name={name} className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<boolean>
|
||||
className="w-fit gap-3"
|
||||
@ -465,8 +465,8 @@ const BaseField = ({
|
||||
False
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { NumberFieldInputProps, NumberFieldRootProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
|
||||
import type { NumberFieldInputProps, NumberFieldProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { LabelProps } from '../label'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -22,7 +22,7 @@ type NumberInputFieldProps = {
|
||||
inputClassName?: string
|
||||
unit?: ReactNode
|
||||
size?: NumberFieldSize
|
||||
} & Omit<NumberFieldRootProps, 'children' | 'className' | 'id' | 'value' | 'defaultValue' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onBlur' | 'className' | 'onChange'>
|
||||
} & Omit<NumberFieldProps, 'children' | 'className' | 'id' | 'value' | 'defaultValue' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onBlur' | 'className' | 'onChange'>
|
||||
|
||||
const NumberInputField = ({
|
||||
label,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldControls,
|
||||
@ -32,7 +32,7 @@ type Props = Readonly<{
|
||||
|
||||
const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1, min = 0, max, value, enable, onChange, disabled = false, hasSwitch, onSwitchChange }) => {
|
||||
return (
|
||||
<FieldsetRoot className={className}>
|
||||
<Fieldset className={className}>
|
||||
<FieldsetLegend className="sr-only">{name}</FieldsetLegend>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-6 items-center">
|
||||
@ -86,7 +86,7 @@ const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1,
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
export default ParamItem
|
||||
|
||||
@ -3,7 +3,7 @@ import type { MeterTone } from '@langgenius/dify-ui/meter'
|
||||
import type { FC } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { MeterIndicator, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -72,11 +72,11 @@ const AppsFull: FC<{ loc: string, className?: string }> = ({
|
||||
{total}
|
||||
</div>
|
||||
</div>
|
||||
<MeterRoot value={Math.min(percent, 100)} max={100} aria-label={buildAppsLabel}>
|
||||
<Meter value={Math.min(percent, 100)} max={100} aria-label={buildAppsLabel}>
|
||||
<MeterTrack>
|
||||
<MeterIndicator tone={tone} />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { MeterTone } from '@langgenius/dify-ui/meter'
|
||||
import type { ComponentType, FC, ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { MeterIndicator, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -150,11 +150,11 @@ const UsageInfo: FC<Props> = ({
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<MeterRoot value={effectivePercent} max={100} aria-label={name}>
|
||||
<Meter value={effectivePercent} max={100} aria-label={name}>
|
||||
<MeterTrack>
|
||||
<MeterIndicator tone={tone} />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
)
|
||||
|
||||
const wrapWithStorageTooltip = (children: ReactNode) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ParentMode, SimpleDocumentDetail } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
@ -119,7 +119,7 @@ export function DocumentPicker({
|
||||
})
|
||||
const documentsList = data?.data ?? []
|
||||
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
|
||||
if (details.reason !== 'item-press')
|
||||
setSearchValue(inputValue)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { NumberFieldInputProps, NumberFieldRootProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
|
||||
import type { NumberFieldInputProps, NumberFieldProps, NumberFieldSize } from '@langgenius/dify-ui/number-field'
|
||||
import type { FC, PropsWithChildren, ReactNode } from 'react'
|
||||
import type { InputProps } from '@/app/components/base/input'
|
||||
import {
|
||||
@ -71,7 +71,7 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = ({ tooltip,
|
||||
)
|
||||
}
|
||||
|
||||
type CompoundNumberInputProps = Omit<NumberFieldRootProps, 'children' | 'className' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onChange'> & {
|
||||
type CompoundNumberInputProps = Omit<NumberFieldProps, 'children' | 'className' | 'onValueChange'> & Omit<NumberFieldInputProps, 'children' | 'size' | 'onChange'> & {
|
||||
label: string
|
||||
unit?: ReactNode
|
||||
size?: NumberFieldSize
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ComboboxRootChangeEventDetails, Placement } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails, Placement } from '@langgenius/dify-ui/combobox'
|
||||
import type { BuiltInMetadataItem, MetadataItem } from '../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
@ -83,7 +83,7 @@ export function DatasetMetadataPicker({
|
||||
resetPicker()
|
||||
}
|
||||
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
|
||||
if (details.reason !== 'item-press')
|
||||
setQuery(inputValue)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldControls,
|
||||
@ -33,7 +33,7 @@ const KeyWordNumber = ({
|
||||
}, [onKeywordNumberChange])
|
||||
|
||||
return (
|
||||
<FieldsetRoot className="flex items-center gap-x-1">
|
||||
<Fieldset className="flex items-center gap-x-1">
|
||||
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
|
||||
<div className="flex grow items-center gap-x-0.5">
|
||||
<div className="truncate system-xs-medium text-text-secondary">
|
||||
@ -69,7 +69,7 @@ const KeyWordNumber = ({
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import type {
|
||||
} from '@dify/contracts/api/console/api-based-extension/types.gen'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
@ -81,7 +81,7 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
|
||||
: t($ => $['apiBasedExtension.modal.title'], { ns: 'common' })}
|
||||
</DialogTitle>
|
||||
<Form<ApiBasedExtensionPayload> className="grid gap-4 pt-2" onFormSubmit={handleSubmit}>
|
||||
<FieldRoot name="name">
|
||||
<Field name="name">
|
||||
<FieldLabel>{nameLabel}</FieldLabel>
|
||||
<FieldControl
|
||||
required
|
||||
@ -89,9 +89,9 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
|
||||
placeholder={t($ => $['apiBasedExtension.modal.name.placeholder'], { ns: 'common' }) || ''}
|
||||
/>
|
||||
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: nameLabel })}</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<FieldRoot name="api_endpoint">
|
||||
<Field name="api_endpoint">
|
||||
<FieldLabel>{apiEndpointLabel}</FieldLabel>
|
||||
<FieldControl
|
||||
required
|
||||
@ -110,9 +110,9 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
|
||||
</a>
|
||||
</FieldDescription>
|
||||
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: apiEndpointLabel })}</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<FieldRoot
|
||||
<Field
|
||||
name="api_key"
|
||||
validate={(value) => {
|
||||
if (typeof value === 'string' && value.length > 0 && value.length < 5)
|
||||
@ -129,7 +129,7 @@ export function ApiBasedExtensionModal(props: ApiBasedExtensionModalProps) {
|
||||
/>
|
||||
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: apiKeyLabel })}</FieldError>
|
||||
<FieldError match="customError" />
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<div className="mt-2 flex items-center justify-end gap-2">
|
||||
<Button type="button" onClick={() => onOpenChange(false)}>
|
||||
|
||||
@ -13,8 +13,8 @@ import type {
|
||||
NodeOutPutVar,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { useCallback, useState } from 'react'
|
||||
@ -223,8 +223,8 @@ function Form<
|
||||
const translatedLabel = label[language] || label.en_US
|
||||
|
||||
return (
|
||||
<FieldRoot key={variable} name={variable} className="contents">
|
||||
<FieldsetRoot
|
||||
<Field key={variable} name={variable} className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup
|
||||
value={selectedValue}
|
||||
@ -264,8 +264,8 @@ function Form<
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
{validating && changeKey === variable && <ValidatingTip />}
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -347,8 +347,8 @@ function Form<
|
||||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<FieldRoot name={variable} className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name={variable} className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<boolean>
|
||||
className="flex items-center justify-between gap-3 py-2"
|
||||
@ -378,8 +378,8 @@ function Form<
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -4,8 +4,8 @@ import type {
|
||||
NodeOutPutVar,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
@ -181,7 +181,7 @@ function ParameterItem({
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldsetRoot className="flex items-center">
|
||||
<Fieldset className="flex items-center">
|
||||
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
|
||||
<Slider
|
||||
className="w-[120px]"
|
||||
@ -203,7 +203,7 @@ function ParameterItem({
|
||||
onChange={handleNumberInputChange}
|
||||
onBlur={handleNumberInputBlur}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
@ -225,7 +225,7 @@ function ParameterItem({
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldsetRoot className="flex items-center">
|
||||
<Fieldset className="flex items-center">
|
||||
<FieldsetLegend className="sr-only">{sliderLabel}</FieldsetLegend>
|
||||
<Slider
|
||||
className="w-[120px]"
|
||||
@ -247,7 +247,7 @@ function ParameterItem({
|
||||
onChange={handleNumberInputChange}
|
||||
onBlur={handleNumberInputBlur}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
@ -256,8 +256,8 @@ function ParameterItem({
|
||||
const translatedLabel = parameterRule.label[language] || parameterRule.label.en_US
|
||||
|
||||
return (
|
||||
<FieldRoot name={parameterRule.name} className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name={parameterRule.name} className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<boolean>
|
||||
className="w-[150px] gap-3"
|
||||
@ -279,8 +279,8 @@ function ParameterItem({
|
||||
False
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { DefaultModel, Model, ModelFeatureEnum, ModelItem } from '../declarations'
|
||||
import type { ModelSelectorModelPredicate, ModelSelectorValue } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -115,7 +115,7 @@ function ModelSelector({
|
||||
handleSelect(provider.provider, model)
|
||||
}, [handleSelect, modelList])
|
||||
|
||||
const handleInputValueChange = useCallback((inputValue: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = useCallback((inputValue: string, details: ComboboxChangeEventDetails) => {
|
||||
if (details.reason !== 'item-press')
|
||||
setInputValue(inputValue)
|
||||
}, [])
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MeterIndicator, MeterLabel, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { Meter, MeterIndicator, MeterLabel, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
@ -55,7 +55,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MeterRoot value={meterValue} max={meterMax} className="mt-3 flex flex-col gap-1">
|
||||
<Meter value={meterValue} max={meterMax} className="mt-3 flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<MeterLabel className="system-xs-medium text-text-tertiary">
|
||||
{t($ => $['modelProvider.card.usageLabel'], { ns: 'common' })}
|
||||
@ -73,7 +73,7 @@ export default function CreditsExhaustedAlert({ hasApiKeyFallback, credits: cred
|
||||
<MeterTrack className="bg-components-progress-error-bg">
|
||||
<MeterIndicator tone="error" />
|
||||
</MeterTrack>
|
||||
</MeterRoot>
|
||||
</Meter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import type { InstallState } from '@/app/components/plugins/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
@ -204,7 +204,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, in
|
||||
onFormSubmit={handleUrlSubmit}
|
||||
className="flex flex-col items-start gap-4 self-stretch"
|
||||
>
|
||||
<FieldRoot name="repoUrl" className="gap-4 self-stretch">
|
||||
<Field name="repoUrl" className="gap-4 self-stretch">
|
||||
<FieldLabel className="flex w-full flex-col items-start justify-center p-0 text-text-secondary">
|
||||
<span className="system-sm-semibold">{t($ => $['installFromGitHub.gitHubRepo'], { ns: 'plugin' })}</span>
|
||||
</FieldLabel>
|
||||
@ -217,7 +217,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, in
|
||||
className="flex grow items-center gap-0.5 self-stretch overflow-hidden rounded-lg border-components-input-border-active bg-components-input-bg-active p-2 text-ellipsis"
|
||||
placeholder="Please enter GitHub repo URL"
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field } from '@langgenius/dify-ui/field'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -78,7 +78,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<FieldRoot name="version" className="gap-4 self-stretch">
|
||||
<Field name="version" className="gap-4 self-stretch">
|
||||
<Select
|
||||
value={selectedVersionOption?.value ?? null}
|
||||
onValueChange={(value) => {
|
||||
@ -120,8 +120,8 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="package" className="gap-4 self-stretch">
|
||||
</Field>
|
||||
<Field name="package" className="gap-4 self-stretch">
|
||||
<Select
|
||||
value={selectedPackageOption?.value ?? null}
|
||||
readOnly={!selectedVersion}
|
||||
@ -148,7 +148,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
|
||||
{!isEdit
|
||||
&& (
|
||||
|
||||
@ -12,8 +12,8 @@ import {
|
||||
DrawerTitle,
|
||||
DrawerViewport,
|
||||
} from '@langgenius/dify-ui/drawer'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
@ -123,8 +123,8 @@ export default function ConfigCredential({
|
||||
content: 'space-y-4 pt-2 pr-8 pl-6',
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="auth_type" className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name="auth_type" className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<AuthType>
|
||||
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
|
||||
@ -151,12 +151,12 @@ export default function ConfigCredential({
|
||||
value={AuthType.apiKeyQuery}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
{tempCredential.auth_type === AuthType.apiKeyHeader && (
|
||||
<>
|
||||
<FieldRoot name="api_key_header_prefix" className="contents">
|
||||
<FieldsetRoot
|
||||
<Field name="api_key_header_prefix" className="contents">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<AuthHeaderPrefix>
|
||||
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
|
||||
@ -183,8 +183,8 @@ export default function ConfigCredential({
|
||||
value={AuthHeaderPrefix.custom}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
<div>
|
||||
<div className="flex items-center py-2 system-sm-medium text-text-primary">
|
||||
{t($ => $['createTool.authMethod.key'], { ns: 'tools' })}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { DrawerRootProps } from '@langgenius/dify-ui/drawer'
|
||||
import type { DrawerProps } from '@langgenius/dify-ui/drawer'
|
||||
import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderOutputSchema, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -83,7 +83,7 @@ const InfoTooltip = ({ children }: { children: string }) => {
|
||||
}
|
||||
|
||||
const WorkflowToolDrawerFrame = ({ title, closeLabel, onHide, children }: WorkflowToolDrawerFrameProps) => {
|
||||
const handleOpenChange = React.useCallback<NonNullable<DrawerRootProps['onOpenChange']>>((open) => {
|
||||
const handleOpenChange = React.useCallback<NonNullable<DrawerProps['onOpenChange']>>((open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}, [onHide])
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AgentInviteOptionResponse } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { NodeDefault } from '../types'
|
||||
import type { AgentRosterNodeData } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -83,7 +83,7 @@ export function AgentSelectorContent({
|
||||
|
||||
return option.name
|
||||
}
|
||||
const handleInputValueChange = (nextSearchText: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = (nextSearchText: string, details: ComboboxChangeEventDetails) => {
|
||||
if (details.reason !== 'item-press')
|
||||
setSearchText(nextSearchText)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import type { NodeOutPutVar } from '../../../types'
|
||||
import type { ToolVarInputs } from '../../tool/types'
|
||||
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { PluginMeta } from '@/app/components/plugins/types'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldControls,
|
||||
@ -146,7 +146,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
||||
tooltip={def.tooltip && renderI18nObject(def.tooltip)}
|
||||
inline
|
||||
>
|
||||
<FieldsetRoot className="flex w-[200px] items-center gap-3">
|
||||
<Fieldset className="flex w-[200px] items-center gap-3">
|
||||
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
|
||||
<Slider
|
||||
value={value}
|
||||
@ -170,7 +170,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,12 +4,12 @@ import type {
|
||||
} from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '@langgenius/dify-ui/collapsible'
|
||||
|
||||
type CollapseProps = Omit<ComponentProps<typeof CollapsibleRoot>, 'open' | 'onOpenChange'> & {
|
||||
type CollapseProps = Omit<ComponentProps<typeof Collapsible>, 'open' | 'onOpenChange'> & {
|
||||
collapsed?: boolean
|
||||
onCollapse?: (collapsed: boolean) => void
|
||||
}
|
||||
@ -20,7 +20,7 @@ export function Collapse({
|
||||
...props
|
||||
}: CollapseProps) {
|
||||
return (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
open={collapsed === undefined ? undefined : !collapsed}
|
||||
onOpenChange={open => onCollapse?.(!open)}
|
||||
{...props}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
@ -41,7 +41,7 @@ function InputNumberWithSlider({
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
<FieldsetRoot>
|
||||
<Fieldset>
|
||||
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
|
||||
<div className="flex h-8 items-center justify-between space-x-2">
|
||||
<input
|
||||
@ -67,7 +67,7 @@ function InputNumberWithSlider({
|
||||
aria-label={label}
|
||||
/>
|
||||
</div>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
export default React.memo(InputNumberWithSlider)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Memory } from '../../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { produce } from 'immer'
|
||||
@ -160,7 +160,7 @@ const MemoryConfig: FC<Props> = ({
|
||||
/>
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{windowSizeLabel}</div>
|
||||
</div>
|
||||
<FieldsetRoot className="flex h-8 items-center space-x-2">
|
||||
<Fieldset className="flex h-8 items-center space-x-2">
|
||||
<FieldsetLegend className="sr-only">{windowSizeLabel}</FieldsetLegend>
|
||||
<Slider
|
||||
className="w-[144px]"
|
||||
@ -185,7 +185,7 @@ const MemoryConfig: FC<Props> = ({
|
||||
onBlur={handleBlur}
|
||||
disabled={readonly || !payload.window?.enabled}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
</div>
|
||||
{canSetRoleName && (
|
||||
<div className="mt-4">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -68,7 +68,7 @@ const RetryOnPanel = ({
|
||||
{
|
||||
retry_config?.retry_enabled && (
|
||||
<div className="px-4 pb-2">
|
||||
<FieldsetRoot className="mb-1 flex w-full items-center">
|
||||
<Fieldset className="mb-1 flex w-full items-center">
|
||||
<FieldsetLegend className="sr-only">{maxRetriesLabel}</FieldsetLegend>
|
||||
<div className="mr-2 grow system-xs-medium-uppercase text-text-secondary">{maxRetriesLabel}</div>
|
||||
<Slider
|
||||
@ -91,8 +91,8 @@ const RetryOnPanel = ({
|
||||
unit={t($ => $['nodes.common.retry.times'], { ns: 'workflow' }) || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
<FieldsetRoot className="flex items-center">
|
||||
</Fieldset>
|
||||
<Fieldset className="flex items-center">
|
||||
<FieldsetLegend className="sr-only">{retryIntervalLabel}</FieldsetLegend>
|
||||
<div className="mr-2 grow system-xs-medium-uppercase text-text-secondary">{retryIntervalLabel}</div>
|
||||
<Slider
|
||||
@ -115,7 +115,7 @@ const RetryOnPanel = ({
|
||||
unit={t($ => $['nodes.common.retry.ms'], { ns: 'workflow' }) || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '@langgenius/dify-ui/collapsible'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -9,7 +9,7 @@ export function AgentAdvancedSettings() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<CollapsibleRoot className="border-b border-divider-subtle py-2">
|
||||
<Collapsible className="border-b border-divider-subtle py-2">
|
||||
<CollapsibleTrigger className="group h-8 min-h-0 justify-start gap-0 rounded-none px-4 py-0 hover:not-data-disabled:bg-transparent hover:not-data-disabled:text-text-secondary data-panel-open:text-text-secondary">
|
||||
<span className="min-w-0 truncate system-sm-semibold-uppercase text-text-secondary">
|
||||
{t($ => $['nodes.agent.advancedSetting'], { ns: 'workflow' })}
|
||||
@ -22,6 +22,6 @@ export function AgentAdvancedSettings() {
|
||||
<CollapsiblePanel>
|
||||
<div className="px-4" />
|
||||
</CollapsiblePanel>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { DeclaredOutputConfig } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { EditableOutputConfig, EditingState, OutputDraft } from './utils'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
|
||||
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
|
||||
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
@ -83,7 +83,7 @@ export function OutputEditCard({
|
||||
>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="flex h-6 items-center gap-x-2">
|
||||
<FieldRoot name="name" invalid={hasNameError} className="contents">
|
||||
<Field name="name" invalid={hasNameError} className="contents">
|
||||
<FieldLabel className="sr-only">
|
||||
{t($ => $['nodes.agent.outputVars.nameLabel'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
@ -99,12 +99,12 @@ export function OutputEditCard({
|
||||
className="h-6 w-24 px-1.5 py-0 code-sm-semibold"
|
||||
onChange={event => updateDraft({ name: event.currentTarget.value })}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<OutputTypeSelect
|
||||
value={draft.type}
|
||||
onChange={value => updateDraft({ type: value })}
|
||||
/>
|
||||
<FieldRoot name="required" className="contents">
|
||||
<Field name="required" className="contents">
|
||||
<FieldLabel className="flex h-6 items-center gap-x-1 system-xs-regular text-text-tertiary">
|
||||
<Switch
|
||||
aria-label={t($ => $['nodes.agent.outputVars.requiredLabel'], { ns: 'workflow' })}
|
||||
@ -114,18 +114,18 @@ export function OutputEditCard({
|
||||
/>
|
||||
{t($ => $['nodes.agent.outputVars.requiredLabel'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
{hasNameError && (
|
||||
<FieldRoot name="nameError" invalid className="contents">
|
||||
<Field name="nameError" invalid className="contents">
|
||||
<FieldError id={nameErrorId} match className="mt-1 px-1 py-0 system-xs-regular text-text-destructive">
|
||||
{duplicateName
|
||||
? t($ => $['nodes.agent.outputVars.nameDuplicate'], { ns: 'workflow' })
|
||||
: t($ => $['nodes.agent.outputVars.nameInvalid'], { ns: 'workflow' })}
|
||||
</FieldError>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)}
|
||||
<FieldRoot name="description" className="contents">
|
||||
<Field name="description" className="contents">
|
||||
<FieldLabel className="sr-only">
|
||||
{t($ => $['nodes.agent.outputVars.descriptionLabel'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
@ -136,10 +136,10 @@ export function OutputEditCard({
|
||||
className="mt-2 h-5 border-transparent bg-transparent px-1 py-0 system-xs-regular shadow-none hover:border-transparent hover:bg-transparent focus:bg-transparent"
|
||||
onChange={event => updateDraft({ description: event.currentTarget.value })}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
{allowDefaultValue && (
|
||||
<CollapsibleRoot>
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="h-8 min-h-8 justify-start gap-x-0.5 rounded-none border-y border-divider-subtle pr-2 pl-2.5 system-xs-regular text-text-tertiary hover:not-data-disabled:bg-state-base-hover hover:not-data-disabled:text-text-tertiary focus-visible:bg-state-base-hover focus-visible:ring-inset data-panel-open:text-text-tertiary">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -149,7 +149,7 @@ export function OutputEditCard({
|
||||
</CollapsibleTrigger>
|
||||
<CollapsiblePanel className="border-t border-divider-subtle">
|
||||
<div className="px-3 py-2">
|
||||
<FieldRoot name="defaultValue" className="gap-1">
|
||||
<Field name="defaultValue" className="gap-1">
|
||||
<FieldLabel className="py-0 system-xs-medium text-text-secondary">
|
||||
{t($ => $['nodes.agent.outputVars.defaultValueLabel'], { ns: 'workflow' })}
|
||||
</FieldLabel>
|
||||
@ -165,10 +165,10 @@ export function OutputEditCard({
|
||||
{t($ => $[defaultValueErrorKey], { ns: 'workflow' })}
|
||||
</FieldError>
|
||||
)}
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
</CollapsiblePanel>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)}
|
||||
<div className="flex h-12 items-center justify-end gap-x-2 px-3">
|
||||
<Button type="button" size="small" variant="secondary" onClick={onCancel}>
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@ -369,7 +369,7 @@ export function AgentRosterField({
|
||||
)
|
||||
|
||||
return (
|
||||
<FieldRoot name="agent_binding" className="gap-1 px-4 py-2">
|
||||
<Field name="agent_binding" className="gap-1 px-4 py-2">
|
||||
<div className="flex h-6 items-center gap-2">
|
||||
<FieldLabel className="min-w-0 flex-1 py-1 system-sm-semibold-uppercase! text-text-secondary">
|
||||
{t($ => $['nodes.agent.roster.label'], { ns: 'workflow' })}
|
||||
@ -476,6 +476,6 @@ export function AgentRosterField({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import type { AgentV2NodeType } from '../types'
|
||||
import type { AgentOutputTypeOptionValue } from '@/app/components/base/prompt-editor/plugins/agent-output-block/utils'
|
||||
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { $insertNodes } from 'lexical'
|
||||
@ -101,7 +101,7 @@ export function AgentTaskField({
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<FieldRoot name="agent_task" className="gap-1 px-4 py-2">
|
||||
<Field name="agent_task" className="gap-1 px-4 py-2">
|
||||
<div className="flex h-6 items-center gap-1">
|
||||
<FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary">
|
||||
{t($ => $[`${i18nPrefix}.task.label`], { ns: 'workflow' })}
|
||||
@ -156,6 +156,6 @@ export function AgentTaskField({
|
||||
</PromptEditor>
|
||||
</div>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { IterationNodeType } from './types'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
@ -103,7 +103,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
inputs.is_parallel && (
|
||||
<div className="px-4 pb-2">
|
||||
<Field title={maxParallelismLabel} isSubTitle tooltip={<div className="w-[230px]">{t($ => $[`${i18nPrefix}.MaxParallelismDesc`], { ns: 'workflow' })}</div>}>
|
||||
<FieldsetRoot className="row flex">
|
||||
<Fieldset className="row flex">
|
||||
<FieldsetLegend className="sr-only">{maxParallelismLabel}</FieldsetLegend>
|
||||
<Input aria-label={maxParallelismLabel} type="number" wrapperClassName="w-18 mr-4" max={MAX_PARALLEL_LIMIT} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} />
|
||||
<Slider
|
||||
@ -114,7 +114,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
className="mt-4 flex-1 shrink-0"
|
||||
aria-label={maxParallelismLabel}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import {
|
||||
memo,
|
||||
@ -93,7 +93,7 @@ const IndexMethod = ({
|
||||
onClick={handleIndexMethodChange}
|
||||
effectColor="blue"
|
||||
>
|
||||
<FieldsetRoot className="flex items-center">
|
||||
<Fieldset className="flex items-center">
|
||||
<FieldsetLegend className="sr-only">{keywordNumberLabel}</FieldsetLegend>
|
||||
<div className="flex grow items-center">
|
||||
<div className="truncate system-xs-medium text-text-secondary">
|
||||
@ -122,7 +122,7 @@ const IndexMethod = ({
|
||||
value={keywordNumber}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FieldsetRoot>
|
||||
</Fieldset>
|
||||
</OptionCard>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ComponentType, SVGProps } from 'react'
|
||||
import { FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import {
|
||||
fireEvent,
|
||||
@ -111,8 +111,8 @@ function renderSearchMethodOption(props: ReturnType<typeof createProps>) {
|
||||
} = props
|
||||
|
||||
render(
|
||||
<FieldRoot name="retrieval_search_method">
|
||||
<FieldsetRoot
|
||||
<Field name="retrieval_search_method">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup
|
||||
value={props.searchMethod}
|
||||
@ -122,8 +122,8 @@ function renderSearchMethodOption(props: ReturnType<typeof createProps>) {
|
||||
>
|
||||
<FieldsetLegend>Retrieval search method</FieldsetLegend>
|
||||
<SearchMethodOption {...optionProps} />
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>,
|
||||
</Fieldset>
|
||||
</Field>,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -9,14 +9,14 @@ import type {
|
||||
TopKFieldProps,
|
||||
VisibleScoreThresholdFieldProps,
|
||||
} from './top-k-and-score-threshold'
|
||||
import { FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import {
|
||||
memo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import { Field as WorkflowField } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useRetrievalSetting } from './hooks'
|
||||
import { SearchMethodOption } from './search-method-option'
|
||||
@ -71,7 +71,7 @@ const RetrievalSetting = ({
|
||||
} = useRetrievalSetting(indexMethod)
|
||||
|
||||
return (
|
||||
<Field
|
||||
<WorkflowField
|
||||
fieldTitleProps={{
|
||||
title: t($ => $['form.retrievalSetting.title'], { ns: 'datasetSettings' }),
|
||||
subTitle: (
|
||||
@ -83,8 +83,8 @@ const RetrievalSetting = ({
|
||||
),
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="retrieval_search_method" className="gap-0">
|
||||
<FieldsetRoot
|
||||
<Field name="retrieval_search_method" className="gap-0">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<RetrievalSearchMethodEnum>
|
||||
value={searchMethod}
|
||||
@ -131,9 +131,9 @@ const RetrievalSetting = ({
|
||||
readonly={readonly}
|
||||
/>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
</WorkflowField>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ import type {
|
||||
Option,
|
||||
} from './type'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -252,8 +252,8 @@ export function SearchMethodOption({
|
||||
<div className="space-y-3">
|
||||
{isHybridSearch
|
||||
? (
|
||||
<FieldRoot name="hybrid_search_mode" className="gap-0">
|
||||
<FieldsetRoot
|
||||
<Field name="hybrid_search_mode" className="gap-0">
|
||||
<Fieldset
|
||||
render={(
|
||||
<RadioGroup<HybridSearchModeEnum>
|
||||
value={hybridSearch.mode}
|
||||
@ -271,8 +271,8 @@ export function SearchMethodOption({
|
||||
readonly={readonly}
|
||||
/>
|
||||
))}
|
||||
</FieldsetRoot>
|
||||
</FieldRoot>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
)
|
||||
: null}
|
||||
{isHybridSearch && isHybridSearchWeightedScoreMode
|
||||
@ -289,7 +289,7 @@ export function SearchMethodOption({
|
||||
<div>
|
||||
{showRerankModelSelectorSwitch
|
||||
? (
|
||||
<FieldRoot name="reranking_model_enabled" className="mb-1 gap-0">
|
||||
<Field name="reranking_model_enabled" className="mb-1 gap-0">
|
||||
<div className="flex items-center">
|
||||
<FieldLabel className="flex min-w-0 items-center py-0 system-sm-semibold text-text-secondary">
|
||||
<Switch
|
||||
@ -307,7 +307,7 @@ export function SearchMethodOption({
|
||||
{rerankModelTip}
|
||||
</Infotip>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
: null}
|
||||
<RerankingModelSelector
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset'
|
||||
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldControls,
|
||||
@ -64,7 +64,7 @@ export function TopKAndScoreThreshold({
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FieldRoot name="top_k" className="gap-0">
|
||||
<Field name="top_k" className="gap-0">
|
||||
<div className="mb-0.5 flex h-6 items-center">
|
||||
<FieldLabel className="py-0 system-xs-medium text-text-secondary">
|
||||
{topKLabel}
|
||||
@ -92,13 +92,13 @@ export function TopKAndScoreThreshold({
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
{scoreThresholdHidden
|
||||
? null
|
||||
: (
|
||||
<FieldsetRoot className="min-w-0">
|
||||
<Fieldset className="min-w-0">
|
||||
<FieldsetLegend className="sr-only">{scoreThresholdLabel}</FieldsetLegend>
|
||||
<FieldRoot name="score_threshold_enabled" className="mb-0.5 gap-0">
|
||||
<Field name="score_threshold_enabled" className="mb-0.5 gap-0">
|
||||
<div className="flex h-6 items-center">
|
||||
<FieldLabel className="flex w-full min-w-0 grow items-center py-0 system-sm-medium text-text-secondary">
|
||||
<Switch
|
||||
@ -118,8 +118,8 @@ export function TopKAndScoreThreshold({
|
||||
{scoreThresholdTip}
|
||||
</Infotip>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="score_threshold" className="gap-0">
|
||||
</Field>
|
||||
<Field name="score_threshold" className="gap-0">
|
||||
<FieldLabel className="sr-only">{scoreThresholdLabel}</FieldLabel>
|
||||
<NumberField
|
||||
disabled={readonly || !scoreThresholdEnabled}
|
||||
@ -137,8 +137,8 @@ export function TopKAndScoreThreshold({
|
||||
</NumberFieldControls>
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</FieldRoot>
|
||||
</FieldsetRoot>
|
||||
</Field>
|
||||
</Fieldset>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useState } from 'react'
|
||||
@ -59,7 +59,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
||||
void handleGetEMailVerificationCode()
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="email" disabled={isInvite} className="mb-2">
|
||||
<Field name="email" disabled={isInvite} className="mb-2">
|
||||
<FieldLabel className="my-2 py-0 system-md-semibold text-text-secondary">{t($ => $.email, { ns: 'login' })}</FieldLabel>
|
||||
<FieldControl
|
||||
type="email"
|
||||
@ -73,7 +73,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
||||
<div className="mt-3">
|
||||
<Button type="submit" loading={loading} disabled={loading || !email} variant="primary" className="w-full">{t($ => $['signup.verifyMail'], { ns: 'login' })}</Button>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
@ -116,7 +116,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
|
||||
void handleEmailPasswordLogin()
|
||||
}}
|
||||
>
|
||||
<FieldRoot name="email" disabled={isInvite} className="mb-3">
|
||||
<Field name="email" disabled={isInvite} className="mb-3">
|
||||
<FieldLabel className="my-2 py-0 system-md-semibold text-text-secondary">
|
||||
{t($ => $.email, { ns: 'login' })}
|
||||
</FieldLabel>
|
||||
@ -129,9 +129,9 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
|
||||
spellCheck={false}
|
||||
placeholder={t($ => $.emailPlaceholder, { ns: 'login' }) || ''}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<FieldRoot name="password" className="mb-3">
|
||||
<Field name="password" className="mb-3">
|
||||
<div className="my-2 flex items-center justify-between">
|
||||
<FieldLabel className="py-0 system-md-semibold text-text-secondary">{t($ => $.password, { ns: 'login' })}</FieldLabel>
|
||||
<Link
|
||||
@ -168,7 +168,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
|
||||
<div className="mb-2">
|
||||
<Button
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import type { AgentBuildDraftChangeSummary } from './build-draft-changes-context'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { CollapsiblePanel, CollapsibleRoot } from '@langgenius/dify-ui/collapsible'
|
||||
import { Collapsible, CollapsiblePanel } from '@langgenius/dify-ui/collapsible'
|
||||
import { useId, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AgentBuildGridTexture } from '../build-grid-texture'
|
||||
@ -53,7 +53,7 @@ export function AgentBuildDraftBar({
|
||||
}
|
||||
|
||||
return (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
role="group"
|
||||
@ -115,6 +115,6 @@ export function AgentBuildDraftBar({
|
||||
{tCustom($ => $.apply)}
|
||||
</Button>
|
||||
</div>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,8 +4,8 @@ import type { ReactNode } from 'react'
|
||||
import type { AgentBuildDraftChangeSection } from '../build-draft-changes-context'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '@langgenius/dify-ui/collapsible'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
@ -62,7 +62,7 @@ export function ConfigureSection({
|
||||
const isBuildDraftChanged = useIsAgentBuildDraftSectionChanged(buildDraftChangeSection)
|
||||
|
||||
return (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
render={<section />}
|
||||
defaultOpen={defaultOpen}
|
||||
className={rootClassName}
|
||||
@ -109,6 +109,6 @@ export function ConfigureSection({
|
||||
{children}
|
||||
</div>
|
||||
</CollapsiblePanel>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import type { ReactNode } from 'react'
|
||||
import type { AgentFileNode } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
FileTree,
|
||||
FileTreeFile,
|
||||
FileTreeFolder,
|
||||
FileTreeFolderPanel,
|
||||
@ -11,7 +12,6 @@ import {
|
||||
FileTreeIcon,
|
||||
FileTreeLabel,
|
||||
FileTreeList,
|
||||
FileTreeRoot,
|
||||
} from '@langgenius/dify-ui/file-tree'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import { Fragment } from 'react'
|
||||
@ -187,7 +187,7 @@ export function AgentFileTree({
|
||||
scrollbar: 'hidden',
|
||||
}}
|
||||
>
|
||||
<FileTreeRoot
|
||||
<FileTree
|
||||
id={id}
|
||||
aria-label={treeLabel}
|
||||
className={cn('w-full max-w-full min-w-0 p-0', rootClassName)}
|
||||
@ -207,7 +207,7 @@ export function AgentFileTree({
|
||||
renderFolderPanel={renderFolderPanel}
|
||||
/>
|
||||
</FileTreeList>
|
||||
</FileTreeRoot>
|
||||
</FileTree>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import type { FormValue, Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
@ -27,7 +27,7 @@ export function AgentModelField({
|
||||
const canConfigureModelSettings = !readOnly && !!currentModel?.provider && !!currentModel.model
|
||||
|
||||
return (
|
||||
<FieldRoot name="model" className="gap-1 pb-4">
|
||||
<Field name="model" className="gap-1 pb-4">
|
||||
<FieldLabel className="py-0 system-sm-semibold-uppercase! text-text-secondary">
|
||||
{t($ => $['agentDetail.configure.model.label'])}
|
||||
</FieldLabel>
|
||||
@ -97,6 +97,6 @@ export function AgentModelField({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import type { AgentConfigSnapshotSummaryResponse, AgentReferencingWorkflowResponse, AgentReferencingWorkflowsResponse } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { RegisterableHotkey } from '@tanstack/react-hotkeys'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { CollapsiblePanel, CollapsibleRoot } from '@langgenius/dify-ui/collapsible'
|
||||
import { Collapsible, CollapsiblePanel } from '@langgenius/dify-ui/collapsible'
|
||||
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@ -282,7 +282,7 @@ export function AgentConfigurePublishBar({
|
||||
const impactReferences = publishBarMode.status === 'confirmingImpact' ? publishBarMode.references : []
|
||||
|
||||
return (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
open={isConfirmingImpact}
|
||||
className="group/publish-bar pointer-events-auto w-full overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]"
|
||||
>
|
||||
@ -306,7 +306,7 @@ export function AgentConfigurePublishBar({
|
||||
onOpenVersions={() => onOpenVersions?.()}
|
||||
onPublishRequest={handlePublishRequest}
|
||||
/>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import type { AgentCliTool, EnvScope, EnvVariable } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldDescription, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useCallback, useState } from 'react'
|
||||
@ -143,7 +143,7 @@ export function CliToolDialog({
|
||||
onFormSubmit={handleSubmit}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<FieldRoot name="installCommand">
|
||||
<Field name="installCommand">
|
||||
<FieldLabel>
|
||||
{t($ => $['agentDetail.configure.tools.cliDialog.installCommand.label'])}
|
||||
</FieldLabel>
|
||||
@ -156,8 +156,8 @@ export function CliToolDialog({
|
||||
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.installCommand.placeholder'])}
|
||||
value={installCommand}
|
||||
/>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="name">
|
||||
</Field>
|
||||
<Field name="name">
|
||||
<FieldLabel>
|
||||
{t($ => $['agentDetail.configure.tools.cliDialog.name.label'])}
|
||||
</FieldLabel>
|
||||
@ -167,7 +167,7 @@ export function CliToolDialog({
|
||||
placeholder={t($ => $['agentDetail.configure.tools.cliDialog.name.placeholder'])}
|
||||
value={toolName}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="pt-1">
|
||||
<div className="mb-3 h-px bg-divider-subtle" />
|
||||
<div className="mb-1 flex min-h-6 items-center gap-1">
|
||||
|
||||
@ -5,8 +5,8 @@ import type { AgentProviderTool, AgentToolAction } from '@/features/agent-v2/age
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsiblePanel,
|
||||
CollapsibleRoot,
|
||||
CollapsibleTrigger,
|
||||
} from '@langgenius/dify-ui/collapsible'
|
||||
import {
|
||||
@ -248,7 +248,7 @@ export const AgentProviderToolItem = memo(({
|
||||
const displayName = tool.displayName ?? tool.name
|
||||
|
||||
return (
|
||||
<CollapsibleRoot
|
||||
<Collapsible
|
||||
open={isExpanded}
|
||||
onOpenChange={onOpenChange}
|
||||
className="overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-1 shadow-xs shadow-shadow-shadow-3"
|
||||
@ -311,6 +311,6 @@ export const AgentProviderToolItem = memo(({
|
||||
</div>
|
||||
)}
|
||||
</CollapsiblePanel>
|
||||
</CollapsibleRoot>
|
||||
</Collapsible>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AgentIconSelection } from './agent-form'
|
||||
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
@ -50,7 +50,7 @@ export function AgentFormFields({
|
||||
/>
|
||||
</button>
|
||||
<div className="flex min-w-0 flex-1 gap-3 pb-1">
|
||||
<FieldRoot
|
||||
<Field
|
||||
name="name"
|
||||
className="relative min-w-0 flex-1"
|
||||
validate={(value) => {
|
||||
@ -77,8 +77,8 @@ export function AgentFormFields({
|
||||
<FieldError match="valueMissing">{t($ => $['roster.createForm.nameRequired'])}</FieldError>
|
||||
<FieldError match="customError" />
|
||||
</div>
|
||||
</FieldRoot>
|
||||
<FieldRoot
|
||||
</Field>
|
||||
<Field
|
||||
name="role"
|
||||
className="relative min-w-0 flex-1"
|
||||
>
|
||||
@ -95,10 +95,10 @@ export function AgentFormFields({
|
||||
placeholder={t($ => $['roster.createForm.rolePlaceholder'])}
|
||||
value={role}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<FieldRoot name="description">
|
||||
<Field name="description">
|
||||
<FieldLabel>
|
||||
{t($ => $['roster.createForm.descriptionLabel'])}
|
||||
<span className="ml-1 system-xs-regular text-text-tertiary">
|
||||
@ -112,7 +112,7 @@ export function AgentFormFields({
|
||||
placeholder={t($ => $['roster.createForm.descriptionPlaceholder'])}
|
||||
value={description}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import type { AgentAppCopyPayload, AgentAppPartial } from '@dify/contracts/api/c
|
||||
import type { AgentFormValues, AgentIconSelection } from './agent-form'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@ -147,7 +147,7 @@ export function DuplicateAgentDialog({
|
||||
/>
|
||||
</button>
|
||||
<div className="flex min-w-0 flex-1 gap-3 pb-1">
|
||||
<FieldRoot name="name" className="relative min-w-0 flex-1">
|
||||
<Field name="name" className="relative min-w-0 flex-1">
|
||||
<FieldLabel>
|
||||
{t($ => $['roster.createForm.nameLabel'])}
|
||||
<span className="ml-1 system-xs-regular text-text-tertiary">
|
||||
@ -163,8 +163,8 @@ export function DuplicateAgentDialog({
|
||||
placeholder={defaultCopyName}
|
||||
value={name}
|
||||
/>
|
||||
</FieldRoot>
|
||||
<FieldRoot
|
||||
</Field>
|
||||
<Field
|
||||
name="role"
|
||||
className="relative min-w-0 flex-1"
|
||||
>
|
||||
@ -181,10 +181,10 @@ export function DuplicateAgentDialog({
|
||||
placeholder={t($ => $['roster.createForm.rolePlaceholder'])}
|
||||
value={role}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<FieldRoot name="description">
|
||||
<Field name="description">
|
||||
<FieldLabel>
|
||||
{t($ => $['roster.createForm.descriptionLabel'])}
|
||||
<span className="ml-1 system-xs-regular text-text-tertiary">
|
||||
@ -198,7 +198,7 @@ export function DuplicateAgentDialog({
|
||||
placeholder={t($ => $['roster.createForm.descriptionPlaceholder'])}
|
||||
value={description}
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-end gap-2 px-6 pt-5 pb-6">
|
||||
<Button type="button" className="min-w-18" onClick={() => handleOpenChange(false)} disabled={duplicateAgentMutation.isPending}>
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@ -96,7 +96,7 @@ function EditDeploymentForm() {
|
||||
<>
|
||||
<DialogCloseButton disabled={updateInstance.isPending} />
|
||||
<Form<EditDeploymentFormValues> className="flex flex-col gap-4" onFormSubmit={handleSubmit}>
|
||||
<FieldRoot name="name" className="gap-2">
|
||||
<Field name="name" className="gap-2">
|
||||
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
|
||||
{nameLabel}
|
||||
</FieldLabel>
|
||||
@ -107,8 +107,8 @@ function EditDeploymentForm() {
|
||||
className="h-8"
|
||||
/>
|
||||
<FieldError match="valueMissing">{t($ => $['errorMsg.fieldRequired'], { ns: 'common', field: nameLabel })}</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="description" className="gap-2">
|
||||
</Field>
|
||||
<Field name="description" className="gap-2">
|
||||
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
|
||||
{t($ => $['settings.description'])}
|
||||
</FieldLabel>
|
||||
@ -116,7 +116,7 @@ function EditDeploymentForm() {
|
||||
defaultValue={initialValues.description}
|
||||
className="min-h-24"
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ComboboxRootChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
|
||||
import type { AccessSubjectSelectionProps } from './types'
|
||||
import type { AccessControlGroup, Subject } from '@/models/access-control'
|
||||
import {
|
||||
@ -91,7 +91,7 @@ export function AccessSubjectAddButton({
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxRootChangeEventDetails) => {
|
||||
const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
|
||||
if (!disabled && details.reason !== 'item-press')
|
||||
setKeyword(inputValue)
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
DialogDescription,
|
||||
DialogTitle,
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@ -73,7 +73,7 @@ function EditReleaseForm({
|
||||
|
||||
return (
|
||||
<Form<EditReleaseFormValues> className="flex flex-col gap-4" onFormSubmit={handleSubmit}>
|
||||
<FieldRoot name="name" className="gap-2">
|
||||
<Field name="name" className="gap-2">
|
||||
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
|
||||
{nameLabel}
|
||||
</FieldLabel>
|
||||
@ -86,8 +86,8 @@ function EditReleaseForm({
|
||||
className="h-8"
|
||||
/>
|
||||
<FieldError match="valueMissing">{t($ => $['versions.releaseNameRequired'])}</FieldError>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="description" className="gap-2">
|
||||
</Field>
|
||||
<Field name="description" className="gap-2">
|
||||
<FieldLabel className="system-xs-medium-uppercase text-text-tertiary">
|
||||
{t($ => $['versions.releaseDescriptionLabel'])}
|
||||
<span className="ml-1.5 system-xs-regular text-text-quaternary">{t($ => $['versions.optional'])}</span>
|
||||
@ -98,7 +98,7 @@ function EditReleaseForm({
|
||||
autoComplete="off"
|
||||
className="min-h-24"
|
||||
/>
|
||||
</FieldRoot>
|
||||
</Field>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import type { ComboboxRootProps } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxProps } from '@langgenius/dify-ui/combobox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Combobox, ComboboxContent, ComboboxTrigger } from '@langgenius/dify-ui/combobox'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
@ -9,7 +9,7 @@ import XCircleIcon from '@/app/components/base/icons/src/vender/solid/general/XC
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { TagSearchContent } from './tag-search-content'
|
||||
|
||||
const tagFilterComboboxFilter: NonNullable<ComboboxRootProps<Tag, true>['filter']> = (tag, query) => tag.name.includes(query)
|
||||
const tagFilterComboboxFilter: NonNullable<ComboboxProps<Tag, true>['filter']> = (tag, query) => tag.name.includes(query)
|
||||
const tagToString = (tag: Tag) => tag.name
|
||||
const isSameTag = (item: Tag, value: Tag) => item.id === value.id
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import type { ComboboxRootProps } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComboboxProps } from '@langgenius/dify-ui/combobox'
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { TagComboboxItem } from './tag-combobox-item'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -18,12 +18,12 @@ import { isCreateTagOption } from './tag-combobox-item'
|
||||
import { TagSearchContent } from './tag-search-content'
|
||||
import { TagTrigger } from './tag-trigger'
|
||||
|
||||
const TAG_COMBOBOX_FILTER: NonNullable<ComboboxRootProps<TagComboboxItem, true>['filter']> = (tag, query) => tag.name.includes(query)
|
||||
const TAG_COMBOBOX_FILTER: NonNullable<ComboboxProps<TagComboboxItem, true>['filter']> = (tag, query) => tag.name.includes(query)
|
||||
const tagToString = (tag: TagComboboxItem) => tag.name
|
||||
const isSameTag = (item: TagComboboxItem, value: TagComboboxItem) => item.id === value.id
|
||||
|
||||
type TagSelectorRootProps = Omit<
|
||||
ComboboxRootProps<TagComboboxItem, true>,
|
||||
ComboboxProps<TagComboboxItem, true>,
|
||||
| 'items'
|
||||
| 'multiple'
|
||||
| 'value'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user