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 `
, + ) + + const saveButton = asHTMLElement(screen.getByRole('button', { name: 'Save' }).element()) + saveButton.click() + + await vi.waitFor(async () => { + await expect.element(screen.getByText('Summary is required.')).toBeInTheDocument() + await expect.element(screen.getByRole('textbox', { name: 'Summary' })).toHaveAttribute('aria-invalid', 'true') + }) + expect(onFormSubmit).not.toHaveBeenCalled() + + await screen.rerender( + , + ) + + asHTMLElement(screen.getByRole('button', { name: 'Save' }).element()).click() + expect(onFormSubmit).toHaveBeenCalledTimes(1) + expect(onFormSubmit.mock.calls[0]?.[0]).toMatchObject({ summary: 'Long enough summary' }) + }) + + it('should render character count when maxLength is set', async () => { + const screen = await render( + , + ) + + await expect.element(screen.getByText('5/20')).toBeInTheDocument() + await expect.element(screen.getByRole('textbox', { name: 'Release notes' })).toHaveClass('pb-7') + }) +}) diff --git a/packages/dify-ui/src/textarea/index.stories.tsx b/packages/dify-ui/src/textarea/index.stories.tsx new file mode 100644 index 0000000000..11824370e9 --- /dev/null +++ b/packages/dify-ui/src/textarea/index.stories.tsx @@ -0,0 +1,141 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { useState } from 'react' +import { Button } from '../button' +import { + FieldDescription, + FieldError, + FieldLabel, + FieldRoot, +} from '../field' +import { Form } from '../form' +import { Textarea } from './index' + +const meta = { + title: 'Base/Form/Textarea', + component: Textarea, + parameters: { + 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.', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta{t(`${prefixSettings}.webDescTip`, { ns: 'appOverview' })}
@@ -466,7 +466,7 @@ const SettingsModal: FCHelp us improve our product
-{bio || 'Your bio will appear here...'}
-