diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index 325454d466..fe742de068 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -33,6 +33,7 @@ import { Drawer, DrawerPopup, DrawerTrigger } from '@langgenius/dify-ui/drawer' import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' import { Form } from '@langgenius/dify-ui/form' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import { Textarea } from '@langgenius/dify-ui/textarea' import '@langgenius/dify-ui/styles.css' // once, in the app root ``` @@ -40,16 +41,16 @@ Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported ## Primitives -| Category | Subpath | Notes | -| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -| Actions | `./button` | Design-system CTA primitive with `cva` variants. | -| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. | -| Form | `./form`, `./field`, `./fieldset`, `./input`, `./checkbox`, `./checkbox-group`, `./radio`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. | -| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. | -| Media | `./avatar` | Avatar root, image, and fallback primitives. | -| Navigation | `./tabs`, `./toggle-group` | Tabs for panels; ToggleGroup for segmented modes. | -| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. | -| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. | +| Category | Subpath | Notes | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | +| Actions | `./button` | Design-system CTA primitive with `cva` variants. | +| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. | +| Form | `./form`, `./field`, `./fieldset`, `./input`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. | +| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. | +| Media | `./avatar` | Avatar root, image, and fallback primitives. | +| Navigation | `./tabs`, `./toggle-group` | Tabs for panels; ToggleGroup for segmented modes. | +| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. | +| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. | Utilities: @@ -64,7 +65,7 @@ Use `Form` for the submit boundary. It renders a native `
`, preserves Ente 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. -Choose the label primitive by the control semantics. Text-like inputs, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels. +Choose the label primitive by the control semantics. Text-like inputs, `Textarea`, input-based `Combobox` / `Autocomplete`, single `Checkbox` / `Radio`, `Switch`, and `NumberField` use `FieldLabel`. Trigger-based `Select` fields use `SelectLabel`; `Slider` fields use `SliderLabel`, with per-thumb `aria-label` only when the thumbs need distinct names. `SelectGroupLabel` and `AutocompleteGroupLabel` only label grouped options inside their popup content; they are not field labels. Use `FieldsetRoot` and `FieldsetLegend` when one field is represented by a group of related controls, such as checkbox groups, radio groups, multi-thumb sliders, or a section that combines several inputs. For checkbox and radio groups, wrap each option with `FieldItem` and give each option its own label: diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index b18b8f3462..24ba9d23fd 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -113,6 +113,10 @@ "types": "./src/tabs/index.tsx", "import": "./src/tabs/index.tsx" }, + "./textarea": { + "types": "./src/textarea/index.tsx", + "import": "./src/textarea/index.tsx" + }, "./toggle-group": { "types": "./src/toggle-group/index.tsx", "import": "./src/toggle-group/index.tsx" diff --git a/packages/dify-ui/src/switch/index.stories.tsx b/packages/dify-ui/src/switch/index.stories.tsx index 4d47ef688e..43e74f1e98 100644 --- a/packages/dify-ui/src/switch/index.stories.tsx +++ b/packages/dify-ui/src/switch/index.stories.tsx @@ -148,7 +148,7 @@ export const AllStates: Story = { parameters: { docs: { description: { - story: 'Complete variant matrix: all sizes × all states, matching Figma design spec (node 2144:1210).', + story: 'Complete variant matrix: all sizes and states.', }, }, }, diff --git a/packages/dify-ui/src/textarea/__tests__/index.spec.tsx b/packages/dify-ui/src/textarea/__tests__/index.spec.tsx new file mode 100644 index 0000000000..6dd3ddba77 --- /dev/null +++ b/packages/dify-ui/src/textarea/__tests__/index.spec.tsx @@ -0,0 +1,129 @@ +import { render } from 'vitest-browser-react' +import { + FieldDescription, + FieldError, + FieldLabel, + FieldRoot, +} from '../../field' +import { Form } from '../../form' +import { Textarea } from '../index' + +const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement +const setTextareaValue = (element: HTMLElement | SVGElement, value: string) => { + const textarea = asHTMLElement(element) as HTMLTextAreaElement + const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set + valueSetter?.call(textarea, value) + textarea.dispatchEvent(new Event('input', { bubbles: true })) +} + +describe('Textarea', () => { + it('should render a labelled textarea through Base UI Field.Control', async () => { + const screen = await render( + + Description + - ) - }, -) -Textarea.displayName = 'Textarea' - -export default Textarea diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx index 2cf2782185..b49699a28d 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx @@ -1,6 +1,7 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { PipelineTemplate } from '@/models/pipeline' import { Button } from '@langgenius/dify-ui/button' +import { Textarea } from '@langgenius/dify-ui/textarea' import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine } from '@remixicon/react' import * as React from 'react' @@ -9,7 +10,6 @@ import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import AppIconPicker from '@/app/components/base/app-icon-picker' import Input from '@/app/components/base/input' -import Textarea from '@/app/components/base/textarea' import { useInvalidCustomizedTemplateList, useUpdateTemplateInfo } from '@/service/use-pipeline' type EditPipelineInfoProps = { @@ -57,8 +57,7 @@ const EditPipelineInfo = ({ setShowAppIconPicker(false) }, []) - const handleDescriptionChange = useCallback((event: React.ChangeEvent) => { - const value = event.target.value + const handleDescriptionChange = useCallback((value: string) => { setDescription(value) }, []) @@ -133,7 +132,7 @@ const EditPipelineInfo = ({ {t('knowledgeDescription', { ns: 'datasetPipeline' })} + onValueChange={value => setDescription(value)} + /> {latestParams.length > 0 && ( diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx index 316bbca556..185c3413ca 100644 --- a/web/app/components/tools/mcp/mcp-server-param-item.tsx +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -1,7 +1,7 @@ 'use client' +import { Textarea } from '@langgenius/dify-ui/textarea' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Textarea from '@/app/components/base/textarea' type Props = { data?: any @@ -28,9 +28,8 @@ const MCPServerParamItem = ({ className="h-8 resize-none" value={value} placeholder={t('mcp.server.modal.parametersPlaceholder', { ns: 'tools' })} - onChange={e => onChange(e.target.value)} - > - + onValueChange={value => onChange(value)} + /> ) } diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx index 9da6793092..46a5cdf58a 100644 --- a/web/app/components/tools/workflow-tool/index.tsx +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -14,6 +14,7 @@ import { DrawerTitle, DrawerViewport, } from '@langgenius/dify-ui/drawer' +import { Textarea } from '@langgenius/dify-ui/textarea' import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { produce } from 'immer' @@ -25,7 +26,6 @@ import Divider from '@/app/components/base/divider' import EmojiPickerInner from '@/app/components/base/emoji-picker/Inner' import { Infotip } from '@/app/components/base/infotip' import Input from '@/app/components/base/input' -import Textarea from '@/app/components/base/textarea' import LabelSelector from '@/app/components/tools/labels/selector' import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal' import MethodSelector from '@/app/components/tools/workflow-tool/method-selector' @@ -305,7 +305,7 @@ export function WorkflowToolDrawer({