diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 643f838f8de..b52ee31d98e 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1686,11 +1686,6 @@ "count": 5 } }, - "web/app/components/base/markdown-blocks/button.tsx": { - "typescript/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/markdown-blocks/code-block.tsx": { "typescript/no-explicit-any": { "count": 9 @@ -5757,11 +5752,6 @@ "count": 1 } }, - "web/app/components/workflow/variable-inspect/display-content.tsx": { - "typescript/no-explicit-any": { - "count": 1 - } - }, "web/app/components/workflow/variable-inspect/group.tsx": { "typescript/no-explicit-any": { "count": 2 diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index a39f4144768..2b0ff94cb16 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -43,6 +43,22 @@ Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported 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`. +### Public type contracts + +Every runtime component exported from a primitive subpath must have an accurate, importable props type with the matching name (`DialogContent` / `DialogContentProps`). Define Dify-authored composite props at the Dify UI boundary; use direct aliases for unchanged Base UI parts instead of copying their shapes. + +Treat each `src//index.tsx` as an explicit public API boundary. Keep component, factory, hook, and type declarations module-local, then publish the complete surface through separate `export { ... }` and `export type { ... }` manifests at the bottom of the file. Do not mix scattered inline exports with the manifest or use a wildcard export; omission from the manifest keeps implementation helpers private. + +Preserve generic relationships end to end. Generic public components and their props must carry the same caller-owned type parameters, including picker `Value` / `Multiple`, form values, radio values, slider values, and overlay payloads and handles. Never erase those relationships with `any` or a hard-coded `string`; use `unknown` only as the safe default for an independently consumed anatomy part whose value cannot be inferred from its parent through JSX. + +Do not add a root-only generic when independently rendered JSX anatomy can produce values outside that root's inferred type. Preserve the upstream contract until the complete component family can enforce one value type; otherwise the generic gives callbacks a narrower type than the runtime can guarantee. `Tabs` intentionally follows Base UI's value contract for this reason. + +Keep the public type surface smaller than the upstream Base UI namespace. A type is not public merely because Base UI provides a name for it, because Dify gives an internal contract a descriptive alias, or because an earlier implementation happened to export it. In addition to matching component props, export a type only when it pairs with a public factory or has a concrete workspace consumer that cannot express the contract clearly through the matching props type. Remove legacy aliases that have no matching runtime API or real consumer; package-local tests and stories can derive narrow values from canonical props instead of preserving an otherwise unused export. Dify-authored options, states, and controlled/uncontrolled branches stay private when the matching props type already expresses them. + +State, event details and reasons, actions, and controlled/uncontrolled composition helpers are private by default. Public props already provide contextual typing for inline render and event callbacks. Export one of these narrower contracts only when Dify defines the state or event itself, a public factory requires the named type, or an external consumer needs to name it independently. Apply the same rule to Dify-authored components such as FileTree, Pagination, ProgressCircle, StatusDot, and Toast; being implemented locally does not justify a broader API. + +Keep implementation-only render helpers, context values, styling helpers, and upstream passthrough aliases private. When a wrapper consumes `className` through `cn()`, omit the upstream state-callback form and expose `className?: string`; public types must describe behavior the wrapper actually implements. + ## Primitives | Category | Subpath | Notes | @@ -107,7 +123,7 @@ Use `Fieldset` and `FieldsetLegend` when one field is represented by a group of Selection primitives should preserve the caller's domain value type instead of widening values to `string`. Use `Select`, `RadioGroup`, `Radio`, and `RadioItem` when the selected value is an enum, union, boolean, number, object, or nullable placeholder value. -Root-level generics type `value`, `defaultValue`, `onValueChange`, and collection props such as `items`, but JSX children do not automatically inherit the parent generic. For non-string radio groups, type the child item too: +Root-level generics type `value`, `defaultValue`, and value-dependent callbacks such as `onValueChange`. Upstream collection props such as `items` can still be broader than the root value type, and JSX children do not automatically inherit the parent generic. Type independently consumed anatomy parts when their value cannot be inferred locally; for non-string radio groups, type the child item too: ```tsx value={promptMode} onValueChange={setPromptMode}> @@ -121,7 +137,22 @@ Root-level generics type `value`, `defaultValue`, `onValueChange`, and collectio Use `Radio` for the default Dify control. Use `RadioItem` when custom UI should be the radio item; place `RadioControl` inside it for the standard visual dot. `RadioControl` is a Dify visual part, not a Base UI anatomy export. -For select labels and display values, prefer the Base UI `items` collection pattern so the root, value display, and item list share the same typed values. Avoid helpers that stringify values only to recover labels later; convert values to strings only at real boundaries such as form submission, URL/search params, or legacy APIs that require strings. +For `Select` and `Combobox`, a literal multiple-value contract must match the runtime mode: ` multiple>`. Their value display can still receive `null` before a value is selected, including in multiple mode. Because JSX does not pass the root's generic to its children, repeat the domain type on independently consumed render anatomy instead of annotating callback parameters: + +```tsx + multiple value={subjects} onValueChange={setSubjects}> + > + {(selectedSubjects) => selectedSubjects?.map((subject) => subject.name).join(', ') ?? 'Anyone'} + + > + {(subject) => {subject.name}} + + +``` + +`AutocompleteList` follows the same rule. A `ComboboxGroup` or `AutocompleteGroup` can infer its local value type from `items`; a nested `Collection` is a separate JSX boundary and should use `` when its render callback needs the domain type. Dynamic `multiple={condition}` remains supported and gives callbacks the corresponding single-or-multiple union. + +For select labels and display values, prefer the Base UI `items` collection pattern so the root, value display, and item list share one runtime source of truth for values and labels. Avoid helpers that stringify values only to recover labels later; convert values to strings only at real boundaries such as form submission, URL/search params, or legacy APIs that require strings. `CheckboxGroup` follows the Base UI contract and uses `string[]`. Do not add a generic checkbox-group wrapper unless the underlying primitive contract changes; if different business IDs need stronger separation, model that at the feature/domain type boundary. diff --git a/packages/dify-ui/src/alert-dialog/index.tsx b/packages/dify-ui/src/alert-dialog/index.tsx index d0a3cb1e729..451c360c7d3 100644 --- a/packages/dify-ui/src/alert-dialog/index.tsx +++ b/packages/dify-ui/src/alert-dialog/index.tsx @@ -7,10 +7,15 @@ import { Button } from '../button' import { cn } from '../cn' import { modalBackdropClassName, modalPopupAnimationClassName } from '../overlay-shared' -export const AlertDialog = BaseAlertDialog.Root -export const AlertDialogTrigger = BaseAlertDialog.Trigger -export const AlertDialogTitle = BaseAlertDialog.Title -export const AlertDialogDescription = BaseAlertDialog.Description +const AlertDialog = BaseAlertDialog.Root +const AlertDialogTrigger = BaseAlertDialog.Trigger +const AlertDialogTitle = BaseAlertDialog.Title +const AlertDialogDescription = BaseAlertDialog.Description + +type AlertDialogProps = BaseAlertDialog.Root.Props +type AlertDialogTriggerProps = BaseAlertDialog.Trigger.Props +type AlertDialogTitleProps = BaseAlertDialog.Title.Props +type AlertDialogDescriptionProps = BaseAlertDialog.Description.Props type AlertDialogContentProps = { children: React.ReactNode @@ -19,7 +24,7 @@ type AlertDialogContentProps = { backdropProps?: Omit } -export function AlertDialogContent({ +function AlertDialogContent({ children, className, backdropClassName, @@ -46,7 +51,7 @@ export function AlertDialogContent({ type AlertDialogActionsProps = React.ComponentProps<'div'> -export function AlertDialogActions({ className, ...props }: AlertDialogActionsProps) { +function AlertDialogActions({ className, ...props }: AlertDialogActionsProps) { return (
& { closeProps?: Omit } -export function AlertDialogCancelButton({ +function AlertDialogCancelButton({ children, closeProps, ...buttonProps @@ -74,10 +79,32 @@ export function AlertDialogCancelButton({ type AlertDialogConfirmButtonProps = ButtonProps -export function AlertDialogConfirmButton({ +function AlertDialogConfirmButton({ variant = 'primary', tone = 'destructive', ...props }: AlertDialogConfirmButtonProps) { return
diff --git a/packages/dify-ui/src/combobox/index.tsx b/packages/dify-ui/src/combobox/index.tsx index 40dc1dfb84c..64ea7470717 100644 --- a/packages/dify-ui/src/combobox/index.tsx +++ b/packages/dify-ui/src/combobox/index.tsx @@ -15,28 +15,59 @@ import { } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } - -export type ComboboxProps< +type ComboboxProps = BaseCombobox.Root.Props< Value, - Multiple extends boolean | undefined = false, -> = BaseCombobox.Root.Props + Multiple +> & + ([Multiple] extends [true] ? { multiple: true } : unknown) +type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails -export function Combobox( +function Combobox( props: ComboboxProps, ): React.JSX.Element { return } -export const ComboboxValue = BaseCombobox.Value -export const ComboboxGroup = BaseCombobox.Group -export const ComboboxCollection = BaseCombobox.Collection -export const ComboboxRow = BaseCombobox.Row -export const useComboboxFilter = BaseCombobox.useFilter -export const useComboboxFilteredItems = BaseCombobox.useFilteredItems +const ComboboxRow = BaseCombobox.Row +const useComboboxFilter = BaseCombobox.useFilter +const useComboboxFilteredItems = BaseCombobox.useFilteredItems -export type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails -export type ComboboxHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails +type ComboboxSelectedValue = + | (Multiple extends true ? Value[] : Value) + | null + +type ComboboxValueProps = Omit< + BaseCombobox.Value.Props, + 'children' +> & { + children?: + | React.ReactNode + | ((selectedValue: ComboboxSelectedValue) => React.ReactNode) +} +function ComboboxValue( + props: ComboboxValueProps, +): React.JSX.Element +function ComboboxValue(props: BaseCombobox.Value.Props): React.JSX.Element { + return +} + +type ComboboxGroupProps = Omit & { + items?: readonly Value[] +} + +function ComboboxGroup(props: ComboboxGroupProps) { + return +} + +type ComboboxCollectionProps = Omit & { + children: (item: Value, index: number) => React.ReactNode +} + +function ComboboxCollection(props: ComboboxCollectionProps) { + return +} + +type ComboboxRowProps = BaseCombobox.Row.Props 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', @@ -81,15 +112,13 @@ const comboboxTriggerVariants = cva( }, ) -export type ComboboxSize = NonNullable['size']> - type ComboboxTriggerProps = Omit & VariantProps & { className?: string icon?: React.ReactNode | false } -export function ComboboxTrigger({ +function ComboboxTrigger({ className, children, icon, @@ -139,14 +168,10 @@ const comboboxInputGroupVariants = cva( }, ) -export type ComboboxInputGroupProps = BaseCombobox.InputGroup.Props & - VariantProps +type ComboboxInputGroupProps = Omit & + VariantProps & { className?: string } -export function ComboboxInputGroup({ - className, - size = 'medium', - ...props -}: ComboboxInputGroupProps) { +function ComboboxInputGroup({ className, size = 'medium', ...props }: ComboboxInputGroupProps) { return ( & - VariantProps +type ComboboxInputProps = Omit & + VariantProps & { className?: string } -export function ComboboxInput({ +function ComboboxInput({ className, size = 'medium', type = 'text', @@ -220,10 +245,10 @@ const comboboxControlVariants = cva( }, ) -export type ComboboxClearProps = Omit & +type ComboboxClearProps = Omit & VariantProps & { className?: string } -export function ComboboxClear({ +function ComboboxClear({ className, children, size = 'medium', @@ -246,10 +271,10 @@ export function ComboboxClear({ ) } -export type ComboboxInputTriggerProps = Omit & +type ComboboxInputTriggerProps = Omit & VariantProps & { className?: string } -export function ComboboxInputTrigger({ +function ComboboxInputTrigger({ className, children, size = 'medium', @@ -270,7 +295,11 @@ export function ComboboxInputTrigger({ ) } -export function ComboboxIcon({ className, children, ...props }: BaseCombobox.Icon.Props) { +type ComboboxIconProps = Omit & { + className?: string +} + +function ComboboxIcon({ className, children, ...props }: ComboboxIconProps) { return ( } -export function ComboboxContent({ +function ComboboxContent({ children, placement = 'bottom-start', sideOffset = 4, @@ -330,27 +359,36 @@ export function ComboboxContent({ ) } -export function ComboboxList({ className, ...props }: BaseCombobox.List.Props) { +type ComboboxListProps = Omit< + BaseCombobox.List.Props, + 'children' | 'className' +> & { + className?: string + children?: React.ReactNode | ((item: Value, index: number) => React.ReactNode) +} + +function ComboboxList({ className, ...props }: ComboboxListProps) { return } -export function ComboboxItem({ className, ...props }: BaseCombobox.Item.Props) { +type ComboboxItemProps = Omit & { + className?: string + value?: Value +} + +function ComboboxItem({ className, ...props }: ComboboxItemProps) { return } -export type ComboboxItemTextProps = React.ComponentProps<'span'> +type ComboboxItemTextProps = React.ComponentProps<'span'> -export function ComboboxItemText({ className, ...props }: ComboboxItemTextProps) { +function ComboboxItemText({ className, ...props }: ComboboxItemTextProps) { return ( ) } -export function ComboboxItemIndicator({ - className, - children, - ...props -}: Omit & { children?: React.ReactNode }) { +function ComboboxItemIndicator({ className, children, ...props }: ComboboxItemIndicatorProps) { return ( & { + children?: React.ReactNode + className?: string +} + +type ComboboxLabelProps = Omit & { + className?: string +} + +function ComboboxLabel({ className, ...props }: ComboboxLabelProps) { return } -export function ComboboxGroupLabel({ className, ...props }: BaseCombobox.GroupLabel.Props) { +type ComboboxGroupLabelProps = Omit & { + className?: string +} + +function ComboboxGroupLabel({ className, ...props }: ComboboxGroupLabelProps) { return ( ) } -export function ComboboxSeparator({ className, ...props }: BaseCombobox.Separator.Props) { +type ComboboxSeparatorProps = Omit & { + className?: string +} + +function ComboboxSeparator({ className, ...props }: ComboboxSeparatorProps) { return } -export function ComboboxEmpty({ className, ...props }: BaseCombobox.Empty.Props) { +type ComboboxEmptyProps = Omit & { + className?: string +} + +function ComboboxEmpty({ className, ...props }: ComboboxEmptyProps) { return ( & { + className?: string +} + +function ComboboxStatus({ className, ...props }: ComboboxStatusProps) { return ( & { + className?: string +} + +function ComboboxChips({ className, ...props }: ComboboxChipsProps) { return ( & { + className?: string +} + +function ComboboxChip({ className, ...props }: ComboboxChipProps) { return ( ) } + +type ComboboxChipRemoveProps = Omit & { + className?: string +} + +export { + Combobox, + ComboboxChip, + ComboboxChipRemove, + ComboboxChips, + ComboboxClear, + ComboboxCollection, + ComboboxContent, + ComboboxEmpty, + ComboboxGroup, + ComboboxGroupLabel, + ComboboxIcon, + ComboboxInput, + ComboboxInputGroup, + ComboboxInputTrigger, + ComboboxItem, + ComboboxItemIndicator, + ComboboxItemText, + ComboboxLabel, + ComboboxList, + ComboboxRow, + ComboboxSeparator, + ComboboxStatus, + ComboboxTrigger, + ComboboxValue, + useComboboxFilter, + useComboboxFilteredItems, +} + +export type { + ComboboxChangeEventDetails, + ComboboxChipProps, + ComboboxChipRemoveProps, + ComboboxChipsProps, + ComboboxClearProps, + ComboboxCollectionProps, + ComboboxContentProps, + ComboboxEmptyProps, + ComboboxGroupLabelProps, + ComboboxGroupProps, + ComboboxIconProps, + ComboboxInputGroupProps, + ComboboxInputProps, + ComboboxInputTriggerProps, + ComboboxItemIndicatorProps, + ComboboxItemProps, + ComboboxItemTextProps, + ComboboxLabelProps, + ComboboxListProps, + ComboboxProps, + ComboboxRowProps, + ComboboxSeparatorProps, + ComboboxStatusProps, + ComboboxTriggerProps, + ComboboxValueProps, + Placement, +} diff --git a/packages/dify-ui/src/context-menu/index.stories.tsx b/packages/dify-ui/src/context-menu/index.stories.tsx index de1ff794a28..057e5b6fb28 100644 --- a/packages/dify-ui/src/context-menu/index.stories.tsx +++ b/packages/dify-ui/src/context-menu/index.stories.tsx @@ -100,23 +100,25 @@ export const WithGroupLabel: Story = { ), } +type Density = 'compact' | 'comfortable' | 'spacious' + const WithRadioItemsDemo = () => { - const [value, setValue] = React.useState('comfortable') + const [density, setDensity] = React.useState('comfortable') return ( - + - - + value={density} onValueChange={setDensity}> + value="compact"> Compact - + value="comfortable"> Comfortable - + value="spacious"> Spacious diff --git a/packages/dify-ui/src/context-menu/index.tsx b/packages/dify-ui/src/context-menu/index.tsx index b07a96b8fd2..e934ac788d7 100644 --- a/packages/dify-ui/src/context-menu/index.tsx +++ b/packages/dify-ui/src/context-menu/index.tsx @@ -16,16 +16,35 @@ import { } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } - -export const ContextMenu = BaseContextMenu.Root -export const ContextMenuTrigger = BaseContextMenu.Trigger -export const ContextMenuSub = BaseContextMenu.SubmenuRoot -export const ContextMenuGroup = BaseContextMenu.Group -export const ContextMenuRadioGroup = BaseContextMenu.RadioGroup -export type ContextMenuActions = BaseContextMenu.Root.Actions +const ContextMenu = BaseContextMenu.Root +const ContextMenuTrigger = BaseContextMenu.Trigger +const ContextMenuSub = BaseContextMenu.SubmenuRoot +const ContextMenuGroup = BaseContextMenu.Group +type ContextMenuProps = BaseContextMenu.Root.Props +type ContextMenuActions = BaseContextMenu.Root.Actions +type ContextMenuTriggerProps = BaseContextMenu.Trigger.Props +type ContextMenuSubProps = BaseContextMenu.SubmenuRoot.Props +type ContextMenuGroupProps = BaseContextMenu.Group.Props +type ContextMenuRadioGroupProps = Omit< + BaseContextMenu.RadioGroup.Props, + 'defaultValue' | 'onValueChange' | 'value' +> & { + defaultValue?: Value + onValueChange?: ( + value: Value, + eventDetails: BaseContextMenu.RadioGroup.ChangeEventDetails, + ) => void + value?: Value +} +type ContextMenuItemVariant = MenuItemVariant // Intentionally no public Backdrop export; Base UI handles context-menu modal dismissal internally. +function ContextMenuRadioGroup( + props: ContextMenuRadioGroupProps, +): React.JSX.Element { + return +} + type ContextMenuContentProps = { children: React.ReactNode placement?: Placement @@ -83,7 +102,7 @@ function renderContextMenuPopup({ ) } -export function ContextMenuContent({ +function ContextMenuContent({ children, placement = 'bottom-start', sideOffset = 0, @@ -105,15 +124,12 @@ export function ContextMenuContent({ }) } -type ContextMenuItemProps = BaseContextMenu.Item.Props & { - variant?: MenuItemVariant +type ContextMenuItemProps = Omit & { + variant?: ContextMenuItemVariant + className?: string } -export function ContextMenuItem({ - className, - variant = 'default', - ...props -}: ContextMenuItemProps) { +function ContextMenuItem({ className, variant = 'default', ...props }: ContextMenuItemProps) { return ( & { + variant?: ContextMenuItemVariant + className?: string } -export function ContextMenuLinkItem({ +function ContextMenuLinkItem({ className, variant = 'default', closeOnClick = true, @@ -143,21 +160,33 @@ export function ContextMenuLinkItem({ ) } -export function ContextMenuRadioItem({ className, ...props }: BaseContextMenu.RadioItem.Props) { +type ContextMenuRadioItemProps = Omit< + BaseContextMenu.RadioItem.Props, + 'className' | 'value' +> & { + className?: string + value: Value +} + +function ContextMenuRadioItem({ + className, + ...props +}: ContextMenuRadioItemProps) { return } -export function ContextMenuCheckboxItem({ - className, - ...props -}: BaseContextMenu.CheckboxItem.Props) { +function ContextMenuCheckboxItem({ className, ...props }: ContextMenuCheckboxItemProps) { return } -export function ContextMenuCheckboxItemIndicator({ +type ContextMenuCheckboxItemProps = Omit & { + className?: string +} + +function ContextMenuCheckboxItemIndicator({ className, ...props -}: Omit) { +}: ContextMenuCheckboxItemIndicatorProps) { return ( & { className?: string } + +function ContextMenuRadioItemIndicator({ className, ...props -}: Omit) { +}: ContextMenuRadioItemIndicatorProps) { return ( & { className?: string } + +type ContextMenuSubTriggerProps = Omit & { + variant?: ContextMenuItemVariant + className?: string } -export function ContextMenuSubTrigger({ +function ContextMenuSubTrigger({ className, variant = 'default', children, @@ -218,7 +258,7 @@ type ContextMenuSubContentProps = { popupProps?: ContextMenuContentProps['popupProps'] } -export function ContextMenuSubContent({ +function ContextMenuSubContent({ children, placement = 'right-start', sideOffset = 4, @@ -240,14 +280,61 @@ export function ContextMenuSubContent({ }) } -export function ContextMenuLabel({ className, ...props }: BaseContextMenu.GroupLabel.Props) { +type ContextMenuLabelProps = Omit & { + className?: string +} + +function ContextMenuLabel({ className, ...props }: ContextMenuLabelProps) { return ( ) } -export function ContextMenuSeparator({ className, ...props }: BaseContextMenu.Separator.Props) { +type ContextMenuSeparatorProps = Omit & { + className?: string +} + +function ContextMenuSeparator({ className, ...props }: ContextMenuSeparatorProps) { return ( ) } + +export { + ContextMenu, + ContextMenuCheckboxItem, + ContextMenuCheckboxItemIndicator, + ContextMenuContent, + ContextMenuGroup, + ContextMenuItem, + ContextMenuLabel, + ContextMenuLinkItem, + ContextMenuRadioGroup, + ContextMenuRadioItem, + ContextMenuRadioItemIndicator, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} + +export type { + ContextMenuActions, + ContextMenuCheckboxItemIndicatorProps, + ContextMenuCheckboxItemProps, + ContextMenuContentProps, + ContextMenuGroupProps, + ContextMenuItemProps, + ContextMenuLabelProps, + ContextMenuLinkItemProps, + ContextMenuProps, + ContextMenuRadioGroupProps, + ContextMenuRadioItemIndicatorProps, + ContextMenuRadioItemProps, + ContextMenuSeparatorProps, + ContextMenuSubContentProps, + ContextMenuSubProps, + ContextMenuSubTriggerProps, + ContextMenuTriggerProps, +} diff --git a/packages/dify-ui/src/dialog/index.stories.tsx b/packages/dify-ui/src/dialog/index.stories.tsx index 6ba8df0148e..d113a80293d 100644 --- a/packages/dify-ui/src/dialog/index.stories.tsx +++ b/packages/dify-ui/src/dialog/index.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite' import * as React from 'react' import { expect, waitFor, within } from 'storybook/test' import { + createDialogHandle, Dialog, DialogBackdrop, DialogCloseButton, @@ -191,6 +192,59 @@ export const Controlled: Story = { render: () => , } +type WorkspaceDialogPayload = { + name: string + memberCount: number +} + +const workspaceDialogPayloads = [ + { name: 'Design', memberCount: 8 }, + { name: 'Engineering', memberCount: 24 }, +] as const satisfies readonly WorkspaceDialogPayload[] + +function DetachedTriggersDemo() { + const [dialogHandle] = React.useState(() => createDialogHandle()) + + return ( +
+
+ {workspaceDialogPayloads.map((payload) => ( + } + > + Edit {payload.name} + + ))} +
+ + + {({ payload }) => ( + + +
+ + {payload ? `Edit ${payload.name}` : 'Edit workspace'} + + + {payload + ? `${payload.memberCount} members currently have access to this workspace.` + : 'Choose a workspace to edit.'} + +
+
+ )} +
+
+ ) +} + +export const DetachedTriggers: Story = { + render: () => , +} + type ApiExtensionFormValues = { name: string endpoint: string diff --git a/packages/dify-ui/src/dialog/index.tsx b/packages/dify-ui/src/dialog/index.tsx index 9f1413f0d0d..dd3f7f9bda9 100644 --- a/packages/dify-ui/src/dialog/index.tsx +++ b/packages/dify-ui/src/dialog/index.tsx @@ -5,18 +5,25 @@ import { Dialog as BaseDialog } from '@base-ui/react/dialog' import { cn } from '../cn' import { modalBackdropClassName, modalPopupAnimationClassName } from '../overlay-shared' -export const Dialog = BaseDialog.Root -export const DialogTrigger = BaseDialog.Trigger -export const DialogTitle = BaseDialog.Title -export const DialogDescription = BaseDialog.Description -export const DialogPortal = BaseDialog.Portal -export const createDialogHandle = BaseDialog.createHandle +const Dialog = BaseDialog.Root +const DialogTrigger = BaseDialog.Trigger +const DialogTitle = BaseDialog.Title +const DialogDescription = BaseDialog.Description +const DialogPortal = BaseDialog.Portal +const createDialogHandle = BaseDialog.createHandle + +type DialogProps = BaseDialog.Root.Props +type DialogHandle = BaseDialog.Handle +type DialogTriggerProps = BaseDialog.Trigger.Props +type DialogTitleProps = BaseDialog.Title.Props +type DialogDescriptionProps = BaseDialog.Description.Props +type DialogPortalProps = BaseDialog.Portal.Props type DialogBackdropProps = Omit & { className?: string } -export function DialogBackdrop({ className, ...props }: DialogBackdropProps) { +function DialogBackdrop({ className, ...props }: DialogBackdropProps) { return } @@ -24,7 +31,7 @@ type DialogViewportProps = Omit & { className?: string } -export function DialogViewport({ className, ...props }: DialogViewportProps) { +function DialogViewport({ className, ...props }: DialogViewportProps) { return } @@ -32,7 +39,7 @@ type DialogPopupProps = Omit & { className?: string } -export function DialogPopup({ className, ...props }: DialogPopupProps) { +function DialogPopup({ className, ...props }: DialogPopupProps) { return ( +type DialogCloseButtonProps = Omit & { + className?: string +} -export function DialogCloseButton({ +function DialogCloseButton({ className, 'aria-label': ariaLabel = 'Close', ...props @@ -73,7 +82,7 @@ type DialogContentProps = { backdropProps?: Omit } -export function DialogContent({ +function DialogContent({ children, className, backdropClassName, @@ -93,3 +102,31 @@ export function DialogContent({ ) } + +export { + createDialogHandle, + Dialog, + DialogBackdrop, + DialogCloseButton, + DialogContent, + DialogDescription, + DialogPopup, + DialogPortal, + DialogTitle, + DialogTrigger, + DialogViewport, +} + +export type { + DialogBackdropProps, + DialogCloseButtonProps, + DialogContentProps, + DialogDescriptionProps, + DialogHandle, + DialogPopupProps, + DialogPortalProps, + DialogProps, + DialogTitleProps, + DialogTriggerProps, + DialogViewportProps, +} diff --git a/packages/dify-ui/src/drawer/index.stories.tsx b/packages/dify-ui/src/drawer/index.stories.tsx index 093f348366f..1f7264f1719 100644 --- a/packages/dify-ui/src/drawer/index.stories.tsx +++ b/packages/dify-ui/src/drawer/index.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite' -import type { DrawerSnapPoint } from '.' +import type { DrawerProps } from '.' import * as React from 'react' import { createDrawerHandle, @@ -308,6 +308,7 @@ export const Positions: Story = { const snapTopMarginRem = 1 const visibleSnapPointRem = 30 +type DrawerSnapPoint = NonNullable[number] const initialSnapPoint: DrawerSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem` const snapPoints = [initialSnapPoint, 1] satisfies DrawerSnapPoint[] diff --git a/packages/dify-ui/src/drawer/index.tsx b/packages/dify-ui/src/drawer/index.tsx index 1f7efb64cfb..6921003b0bc 100644 --- a/packages/dify-ui/src/drawer/index.tsx +++ b/packages/dify-ui/src/drawer/index.tsx @@ -4,28 +4,35 @@ import type * as React from 'react' import { Drawer as BaseDrawer } from '@base-ui/react/drawer' import { cn } from '../cn' -export const Drawer = BaseDrawer.Root -export const DrawerProvider = BaseDrawer.Provider -export const DrawerIndent = BaseDrawer.Indent -export const DrawerIndentBackground = BaseDrawer.IndentBackground -export const DrawerTrigger = BaseDrawer.Trigger -export const DrawerSwipeArea = BaseDrawer.SwipeArea -export const DrawerPortal = BaseDrawer.Portal -export const DrawerTitle = BaseDrawer.Title -export const DrawerDescription = BaseDrawer.Description -export const DrawerClose = BaseDrawer.Close -export const createDrawerHandle = BaseDrawer.createHandle +const Drawer = BaseDrawer.Root +const DrawerProvider = BaseDrawer.Provider +const DrawerIndent = BaseDrawer.Indent +const DrawerIndentBackground = BaseDrawer.IndentBackground +const DrawerTrigger = BaseDrawer.Trigger +const DrawerSwipeArea = BaseDrawer.SwipeArea +const DrawerPortal = BaseDrawer.Portal +const DrawerTitle = BaseDrawer.Title +const DrawerDescription = BaseDrawer.Description +const DrawerClose = BaseDrawer.Close +const createDrawerHandle = BaseDrawer.createHandle -export type DrawerProps = BaseDrawer.Root.Props -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 = BaseDrawer.Trigger.Props +type DrawerProps = BaseDrawer.Root.Props +type DrawerHandle = BaseDrawer.Handle +type DrawerProviderProps = BaseDrawer.Provider.Props +type DrawerIndentProps = BaseDrawer.Indent.Props +type DrawerIndentBackgroundProps = BaseDrawer.IndentBackground.Props +type DrawerTriggerProps = BaseDrawer.Trigger.Props +type DrawerSwipeAreaProps = BaseDrawer.SwipeArea.Props +type DrawerPortalProps = BaseDrawer.Portal.Props +type DrawerTitleProps = BaseDrawer.Title.Props +type DrawerDescriptionProps = BaseDrawer.Description.Props +type DrawerCloseProps = BaseDrawer.Close.Props -export function DrawerBackdrop({ className, ...props }: BaseDrawer.Backdrop.Props) { +type DrawerBackdropProps = Omit & { + className?: string +} + +function DrawerBackdrop({ className, ...props }: DrawerBackdropProps) { return ( & { + className?: string +} + +function DrawerViewport({ className, ...props }: DrawerViewportProps) { return ( & { + className?: string +} + +function DrawerPopup({ className, ...props }: DrawerPopupProps) { return ( & { + className?: string +} + +function DrawerContent({ className, ...props }: DrawerContentProps) { return ( & { +type DrawerCloseButtonProps = Omit & { children?: React.ReactNode + className?: string } -export function DrawerCloseButton({ +function DrawerCloseButton({ className, children, type = 'button', @@ -108,3 +128,41 @@ export function DrawerCloseButton({ ) } + +export { + createDrawerHandle, + Drawer, + DrawerBackdrop, + DrawerClose, + DrawerCloseButton, + DrawerContent, + DrawerDescription, + DrawerIndent, + DrawerIndentBackground, + DrawerPopup, + DrawerPortal, + DrawerProvider, + DrawerSwipeArea, + DrawerTitle, + DrawerTrigger, + DrawerViewport, +} + +export type { + DrawerBackdropProps, + DrawerCloseButtonProps, + DrawerCloseProps, + DrawerContentProps, + DrawerDescriptionProps, + DrawerHandle, + DrawerIndentBackgroundProps, + DrawerIndentProps, + DrawerPopupProps, + DrawerPortalProps, + DrawerProps, + DrawerProviderProps, + DrawerSwipeAreaProps, + DrawerTitleProps, + DrawerTriggerProps, + DrawerViewportProps, +} diff --git a/packages/dify-ui/src/dropdown-menu/index.stories.tsx b/packages/dify-ui/src/dropdown-menu/index.stories.tsx index fe5c342daea..8ebc334db1c 100644 --- a/packages/dify-ui/src/dropdown-menu/index.stories.tsx +++ b/packages/dify-ui/src/dropdown-menu/index.stories.tsx @@ -138,23 +138,25 @@ export const WithSubmenu: Story = { ), } +type Density = 'compact' | 'comfortable' | 'spacious' + const WithRadioItemsDemo = () => { - const [value, setValue] = React.useState('comfortable') + const [density, setDensity] = React.useState('comfortable') return ( - + - - + value={density} onValueChange={setDensity}> + value="compact"> Compact - + value="comfortable"> Comfortable - + value="spacious"> Spacious @@ -261,8 +263,10 @@ export const WithLinkItems: Story = { ), } +type SortOrder = 'newest' | 'oldest' | 'name' + const ComplexDemo = () => { - const [sortOrder, setSortOrder] = React.useState('newest') + const [sortOrder, setSortOrder] = React.useState('newest') const [showArchived, setShowArchived] = React.useState(false) return ( @@ -308,16 +312,16 @@ const ComplexDemo = () => { Sort by - - + value={sortOrder} onValueChange={setSortOrder}> + value="newest"> Newest first - + value="oldest"> Oldest first - + value="name"> Name diff --git a/packages/dify-ui/src/dropdown-menu/index.tsx b/packages/dify-ui/src/dropdown-menu/index.tsx index 457ce8ff844..236892cab76 100644 --- a/packages/dify-ui/src/dropdown-menu/index.tsx +++ b/packages/dify-ui/src/dropdown-menu/index.tsx @@ -16,22 +16,50 @@ import { } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } +const DropdownMenu = Menu.Root +const DropdownMenuTrigger = Menu.Trigger +const DropdownMenuSub = Menu.SubmenuRoot +const DropdownMenuGroup = Menu.Group -export const DropdownMenu = Menu.Root -export const DropdownMenuTrigger = Menu.Trigger -export const DropdownMenuSub = Menu.SubmenuRoot -export const DropdownMenuGroup = Menu.Group -export const DropdownMenuRadioGroup = Menu.RadioGroup +type DropdownMenuProps = Menu.Root.Props +type DropdownMenuTriggerProps = Menu.Trigger.Props +type DropdownMenuSubProps = Menu.SubmenuRoot.Props +type DropdownMenuGroupProps = Menu.Group.Props +type DropdownMenuRadioGroupProps = Omit< + Menu.RadioGroup.Props, + 'defaultValue' | 'onValueChange' | 'value' +> & { + defaultValue?: Value + onValueChange?: (value: Value, eventDetails: Menu.RadioGroup.ChangeEventDetails) => void + value?: Value +} +type DropdownMenuItemVariant = MenuItemVariant -export function DropdownMenuRadioItem({ className, ...props }: Menu.RadioItem.Props) { +function DropdownMenuRadioGroup( + props: DropdownMenuRadioGroupProps, +): React.JSX.Element { + return +} + +type DropdownMenuRadioItemProps = Omit< + Menu.RadioItem.Props, + 'className' | 'value' +> & { + className?: string + value: Value +} + +function DropdownMenuRadioItem({ + className, + ...props +}: DropdownMenuRadioItemProps) { return } -export function DropdownMenuRadioItemIndicator({ +function DropdownMenuRadioItemIndicator({ className, ...props -}: Omit) { +}: DropdownMenuRadioItemIndicatorProps) { return ( @@ -39,14 +67,23 @@ export function DropdownMenuRadioItemIndicator({ ) } -export function DropdownMenuCheckboxItem({ className, ...props }: Menu.CheckboxItem.Props) { +type DropdownMenuRadioItemIndicatorProps = Omit< + Menu.RadioItemIndicator.Props, + 'children' | 'className' +> & { className?: string } + +type DropdownMenuCheckboxItemProps = Omit & { + className?: string +} + +function DropdownMenuCheckboxItem({ className, ...props }: DropdownMenuCheckboxItemProps) { return } -export function DropdownMenuCheckboxItemIndicator({ +function DropdownMenuCheckboxItemIndicator({ className, ...props -}: Omit) { +}: DropdownMenuCheckboxItemIndicatorProps) { return ( & { className?: string } + +type DropdownMenuLabelProps = Omit & { + className?: string +} + +function DropdownMenuLabel({ className, ...props }: DropdownMenuLabelProps) { return } @@ -118,7 +164,7 @@ function renderDropdownMenuPopup({ ) } -export function DropdownMenuContent({ +function DropdownMenuContent({ children, placement = 'bottom-end', sideOffset = 4, @@ -140,11 +186,12 @@ export function DropdownMenuContent({ }) } -type DropdownMenuSubTriggerProps = Menu.SubmenuTrigger.Props & { - variant?: MenuItemVariant +type DropdownMenuSubTriggerProps = Omit & { + variant?: DropdownMenuItemVariant + className?: string } -export function DropdownMenuSubTrigger({ +function DropdownMenuSubTrigger({ className, variant = 'default', children, @@ -176,7 +223,7 @@ type DropdownMenuSubContentProps = { popupProps?: DropdownMenuContentProps['popupProps'] } -export function DropdownMenuSubContent({ +function DropdownMenuSubContent({ children, placement = 'left-start', sideOffset = 4, @@ -198,15 +245,12 @@ export function DropdownMenuSubContent({ }) } -type DropdownMenuItemProps = Menu.Item.Props & { - variant?: MenuItemVariant +type DropdownMenuItemProps = Omit & { + variant?: DropdownMenuItemVariant + className?: string } -export function DropdownMenuItem({ - className, - variant = 'default', - ...props -}: DropdownMenuItemProps) { +function DropdownMenuItem({ className, variant = 'default', ...props }: DropdownMenuItemProps) { return ( & { + variant?: DropdownMenuItemVariant + className?: string } -export function DropdownMenuLinkItem({ +function DropdownMenuLinkItem({ className, variant = 'default', closeOnClick = true, @@ -236,6 +281,49 @@ export function DropdownMenuLinkItem({ ) } -export function DropdownMenuSeparator({ className, ...props }: Menu.Separator.Props) { +type DropdownMenuSeparatorProps = Omit & { + className?: string +} + +function DropdownMenuSeparator({ className, ...props }: DropdownMenuSeparatorProps) { return } + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuCheckboxItemIndicator, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuLinkItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuRadioItemIndicator, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} + +export type { + DropdownMenuCheckboxItemIndicatorProps, + DropdownMenuCheckboxItemProps, + DropdownMenuContentProps, + DropdownMenuGroupProps, + DropdownMenuItemProps, + DropdownMenuLabelProps, + DropdownMenuLinkItemProps, + DropdownMenuProps, + DropdownMenuRadioGroupProps, + DropdownMenuRadioItemIndicatorProps, + DropdownMenuRadioItemProps, + DropdownMenuSeparatorProps, + DropdownMenuSubContentProps, + DropdownMenuSubProps, + DropdownMenuSubTriggerProps, + DropdownMenuTriggerProps, + Placement, +} diff --git a/packages/dify-ui/src/field/index.tsx b/packages/dify-ui/src/field/index.tsx index 9a8540c6496..97256610eb2 100644 --- a/packages/dify-ui/src/field/index.tsx +++ b/packages/dify-ui/src/field/index.tsx @@ -6,50 +6,44 @@ import { Field as BaseField } from '@base-ui/react/field' import { cn } from '../cn' import { formLabelClassName, textControlVariants } from '../form-control-shared' -export type FieldProps = Omit & { +type FieldProps = Omit & { className?: string } -export type FieldActions = BaseFieldNS.Root.Actions - -export function Field({ className, ...props }: FieldProps) { +function Field({ className, ...props }: FieldProps) { return } -export type FieldItemProps = Omit & { +type FieldItemProps = Omit & { className?: string } -export function FieldItem({ className, ...props }: FieldItemProps) { +function FieldItem({ className, ...props }: FieldItemProps) { return } -export type FieldLabelProps = Omit & { +type FieldLabelProps = Omit & { className?: string } -export function FieldLabel({ className, ...props }: FieldLabelProps) { +function FieldLabel({ className, ...props }: FieldLabelProps) { return } -export type FieldControlSize = NonNullable['size']> - -export type FieldControlProps = Omit & +type FieldControlProps = Omit & VariantProps & { className?: string } -export type FieldControlChangeEventDetails = BaseFieldNS.Control.ChangeEventDetails - -export function FieldControl({ className, size = 'medium', ...props }: FieldControlProps) { +function FieldControl({ className, size = 'medium', ...props }: FieldControlProps) { return } -export type FieldDescriptionProps = Omit & { +type FieldDescriptionProps = Omit & { className?: string } -export function FieldDescription({ className, ...props }: FieldDescriptionProps) { +function FieldDescription({ className, ...props }: FieldDescriptionProps) { return ( & { +type FieldErrorProps = Omit & { className?: string } -export function FieldError({ className, ...props }: FieldErrorProps) { +function FieldError({ className, ...props }: FieldErrorProps) { return ( & { +type FieldsetProps = Omit & { className?: string } -export function Fieldset({ className, ...props }: FieldsetProps) { +function Fieldset({ className, ...props }: FieldsetProps) { return } -export type FieldsetLegendProps = Omit & { +type FieldsetLegendProps = Omit & { className?: string } -export function FieldsetLegend({ className, ...props }: FieldsetLegendProps) { +function FieldsetLegend({ className, ...props }: FieldsetLegendProps) { return ( ) } + +export { Fieldset, FieldsetLegend } + +export type { FieldsetLegendProps, FieldsetProps } diff --git a/packages/dify-ui/src/file-tree/index.tsx b/packages/dify-ui/src/file-tree/index.tsx index de592ec82d8..4ccb7145999 100644 --- a/packages/dify-ui/src/file-tree/index.tsx +++ b/packages/dify-ui/src/file-tree/index.tsx @@ -20,7 +20,7 @@ function renderGuides(level: number) { return Array.from({ length: Math.max(level - 1, 0) }, (_, index) => ) } -type FileTreeRowState = { +type FileTreeFileState = { selected: boolean disabled: boolean level: number @@ -37,9 +37,9 @@ function fileTreeRowClassName({ className }: { className?: string }) { ) } -export type FileTreeProps = useRender.ComponentProps<'section'> +type FileTreeProps = useRender.ComponentProps<'section'> -export function FileTree({ render, className, children, ...props }: FileTreeProps) { +function FileTree({ render, className, children, ...props }: FileTreeProps) { const defaultProps: useRender.ElementProps<'section'> = { className: cn('flex min-w-0 flex-col gap-px p-1', className), children: {children}, @@ -52,9 +52,9 @@ export function FileTree({ render, className, children, ...props }: FileTreeProp }) } -export type FileTreeListProps = useRender.ComponentProps<'ul'> +type FileTreeListProps = useRender.ComponentProps<'ul'> -export function FileTreeList({ render, className, ...props }: FileTreeListProps) { +function FileTreeList({ render, className, ...props }: FileTreeListProps) { const defaultProps: useRender.ElementProps<'ul'> = { className: cn('m-0 flex min-w-0 list-none flex-col gap-px p-0', className), } @@ -66,20 +66,21 @@ export function FileTreeList({ render, className, ...props }: FileTreeListProps) }) } -export type FileTreeFolderProps = Omit & { +type FileTreeFolderProps = Omit & { + className?: string render?: BaseCollapsible.Root.Props['render'] } -export function FileTreeFolder({ render =
  • , className, ...props }: FileTreeFolderProps) { +function FileTreeFolder({ render =
  • , className, ...props }: FileTreeFolderProps) { return } -export type FileTreeFolderTriggerProps = Omit & { +type FileTreeFolderTriggerProps = Omit & { className?: string level?: number } -export function FileTreeFolderTrigger({ +function FileTreeFolderTrigger({ className, children, disabled, @@ -102,11 +103,12 @@ export function FileTreeFolderTrigger({ ) } -export type FileTreeFolderPanelProps = Omit & { +type FileTreeFolderPanelProps = Omit & { + className?: string render?: BaseCollapsible.Panel.Props['render'] } -export function FileTreeFolderPanel({ +function FileTreeFolderPanel({ render =
      , className, children, @@ -125,15 +127,12 @@ export function FileTreeFolderPanel({ ) } -export type FileTreeFileProps = Omit< - useRender.ComponentProps<'button', FileTreeRowState>, - 'type' -> & { +type FileTreeFileProps = Omit, 'type'> & { level?: number selected?: boolean } -export function FileTreeFile({ +function FileTreeFile({ render, className, children, @@ -144,7 +143,7 @@ export function FileTreeFile({ }: FileTreeFileProps) { const contextLevel = useFileTreeLevel() const level = levelProp ?? contextLevel - const state: FileTreeRowState = { + const state: FileTreeFileState = { selected, disabled, level, @@ -174,9 +173,9 @@ export function FileTreeFile({ return
    • {file}
    • } -export type FileTreeGuideProps = useRender.ComponentProps<'span'> +type FileTreeGuideProps = useRender.ComponentProps<'span'> -export function FileTreeGuide({ render, className, ...props }: FileTreeGuideProps) { +function FileTreeGuide({ render, className, ...props }: FileTreeGuideProps) { const defaultProps: useRender.ElementProps<'span'> = { 'aria-hidden': true, className: cn( @@ -192,7 +191,7 @@ export function FileTreeGuide({ render, className, ...props }: FileTreeGuideProp }) } -export type FileTreeIconType = +type FileTreeIconType = | 'folder' | 'file' | 'markdown' @@ -218,18 +217,12 @@ const fileTreeIconClassNames: Record, string archive: 'i-ri-file-zip-fill text-[#A4AABF]', } -export type FileTreeIconProps = Omit, 'children'> & { +type FileTreeIconProps = Omit, 'children'> & { type?: FileTreeIconType children?: React.ReactNode } -export function FileTreeIcon({ - type = 'file', - render, - className, - children, - ...props -}: FileTreeIconProps) { +function FileTreeIcon({ type = 'file', render, className, children, ...props }: FileTreeIconProps) { const defaultProps: useRender.ElementProps<'span'> = { 'aria-hidden': true, className: cn( @@ -258,12 +251,12 @@ export function FileTreeIcon({ }) } -export type FileTreeLabelProps = useRender.ComponentProps<'span'> +type FileTreeLabelProps = useRender.ComponentProps<'span'> type FileTreeLabelElementProps = useRender.ElementProps<'span'> & { 'data-label'?: string } -export function FileTreeLabel({ render, className, children, ...props }: FileTreeLabelProps) { +function FileTreeLabel({ render, className, children, ...props }: FileTreeLabelProps) { const labelText = getLabelText(children) const defaultProps = { 'data-label': labelText, @@ -284,9 +277,9 @@ export function FileTreeLabel({ render, className, children, ...props }: FileTre }) } -export type FileTreeMetaProps = useRender.ComponentProps<'span'> +type FileTreeMetaProps = useRender.ComponentProps<'span'> -export function FileTreeMeta({ render, className, ...props }: FileTreeMetaProps) { +function FileTreeMeta({ render, className, ...props }: FileTreeMetaProps) { const defaultProps: useRender.ElementProps<'span'> = { className: cn('min-w-0 shrink truncate system-xs-regular text-text-tertiary', className), } @@ -298,9 +291,9 @@ export function FileTreeMeta({ render, className, ...props }: FileTreeMetaProps) }) } -export type FileTreeBadgeProps = useRender.ComponentProps<'span'> +type FileTreeBadgeProps = useRender.ComponentProps<'span'> -export function FileTreeBadge({ render, className, ...props }: FileTreeBadgeProps) { +function FileTreeBadge({ render, className, ...props }: FileTreeBadgeProps) { const defaultProps: useRender.ElementProps<'span'> = { className: cn( 'ms-1 inline-flex min-w-4 shrink-0 items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 system-2xs-medium-uppercase text-text-tertiary', @@ -314,3 +307,31 @@ export function FileTreeBadge({ render, className, ...props }: FileTreeBadgeProp props: mergeProps<'span'>(defaultProps, props), }) } + +export { + FileTree, + FileTreeBadge, + FileTreeFile, + FileTreeFolder, + FileTreeFolderPanel, + FileTreeFolderTrigger, + FileTreeGuide, + FileTreeIcon, + FileTreeLabel, + FileTreeList, + FileTreeMeta, +} +export type { + FileTreeBadgeProps, + FileTreeFileProps, + FileTreeFolderPanelProps, + FileTreeFolderProps, + FileTreeFolderTriggerProps, + FileTreeGuideProps, + FileTreeIconProps, + FileTreeIconType, + FileTreeLabelProps, + FileTreeListProps, + FileTreeMetaProps, + FileTreeProps, +} diff --git a/packages/dify-ui/src/form/index.stories.tsx b/packages/dify-ui/src/form/index.stories.tsx index 2ed973627ea..4874bf025ab 100644 --- a/packages/dify-ui/src/form/index.stories.tsx +++ b/packages/dify-ui/src/form/index.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite' +import * as React from 'react' import { Button } from '../button' import { Checkbox } from '../checkbox' import { CheckboxGroup } from '../checkbox-group' @@ -18,48 +19,71 @@ export default meta type Story = StoryObj -export const Basic: Story = { - render: () => ( -
      undefined}> - - Name - - Name is required. - - - - Email - - Used for account notifications. - Email is required. - Enter a valid email address. - - - -
      }> - Features -
      - - - - Search - - - - - - Analytics - - -
      -
      -
      - -
      - -
      -
      - ), +type WorkspaceFormValues = { + name: string + email: string + features: string[] +} + +function BasicFormDemo() { + const [submittedValues, setSubmittedValues] = React.useState() + + return ( +
      + + className="grid w-96 gap-4" + onFormSubmit={(formValues) => setSubmittedValues(formValues)} + > + + Name + + Name is required. + + + + Email + + Used for account notifications. + Email is required. + Enter a valid email address. + + + +
      }> + Features +
      + + + + Search + + + + + + Analytics + + +
      +
      +
      + +
      + +
      + + + {submittedValues && ( + + {JSON.stringify(submittedValues, null, 2)} + + )} +
      + ) +} + +export const Basic: Story = { + render: () => , } diff --git a/packages/dify-ui/src/form/index.tsx b/packages/dify-ui/src/form/index.tsx index 09cdc2a6239..411863561f5 100644 --- a/packages/dify-ui/src/form/index.tsx +++ b/packages/dify-ui/src/form/index.tsx @@ -3,9 +3,10 @@ import type { Form as BaseFormNS } from '@base-ui/react/form' import { Form as BaseForm } from '@base-ui/react/form' -export const Form = BaseForm +const Form = BaseForm -export type FormProps = BaseFormNS.Props -export type FormActions = BaseFormNS.Actions -export type FormValidationMode = BaseFormNS.ValidationMode -export type FormSubmitEventDetails = BaseFormNS.SubmitEventDetails +type FormProps = + BaseFormNS.Props + +export { Form } +export type { FormProps } diff --git a/packages/dify-ui/src/input/index.tsx b/packages/dify-ui/src/input/index.tsx index aa51fc4dd08..1cbb516ab68 100644 --- a/packages/dify-ui/src/input/index.tsx +++ b/packages/dify-ui/src/input/index.tsx @@ -6,15 +6,14 @@ import { Input as BaseInput } from '@base-ui/react/input' import { cn } from '../cn' import { textControlVariants } from '../form-control-shared' -export type InputSize = NonNullable['size']> - -export type InputProps = Omit & +type InputProps = Omit & VariantProps & { className?: string } -export type InputChangeEventDetails = BaseInputNS.ChangeEventDetails - -export function Input({ className, size = 'medium', ...props }: InputProps) { +function Input({ className, size = 'medium', ...props }: InputProps) { return } + +export { Input } +export type { InputProps } diff --git a/packages/dify-ui/src/kbd/index.tsx b/packages/dify-ui/src/kbd/index.tsx index 7959a071daa..d0761481f3e 100644 --- a/packages/dify-ui/src/kbd/index.tsx +++ b/packages/dify-ui/src/kbd/index.tsx @@ -25,11 +25,11 @@ const kbdVariants = cva( }, ) -export type KbdColor = NonNullable['color']> +type KbdColor = NonNullable['color']> -export type KbdProps = Omit, 'color'> & VariantProps +type KbdProps = Omit, 'color'> & VariantProps -export function Kbd({ className, color, disabled, ...props }: KbdProps) { +function Kbd({ className, color, disabled, ...props }: KbdProps) { return ( +type KbdGroupProps = React.ComponentProps<'span'> -export function KbdGroup({ className, ...props }: KbdGroupProps) { +function KbdGroup({ className, ...props }: KbdGroupProps) { return ( ) } + +export { Kbd, KbdGroup } +export type { KbdColor, KbdGroupProps, KbdProps } diff --git a/packages/dify-ui/src/meter/index.tsx b/packages/dify-ui/src/meter/index.tsx index 00f7f156a8d..d5f42ac26a2 100644 --- a/packages/dify-ui/src/meter/index.tsx +++ b/packages/dify-ui/src/meter/index.tsx @@ -15,15 +15,17 @@ import { Meter as BaseMeter } from '@base-ui/react/meter' import { cva } from 'class-variance-authority' import { cn } from '../cn' -export const Meter = BaseMeter.Root -export type MeterProps = BaseMeter.Root.Props +const Meter = BaseMeter.Root +type MeterProps = BaseMeter.Root.Props const meterTrackClassName = 'relative block h-1 w-full overflow-hidden rounded-md bg-components-progress-bar-bg' -export type MeterTrackProps = BaseMeter.Track.Props +type MeterTrackProps = Omit & { + className?: string +} -export function MeterTrack({ className, ...props }: MeterTrackProps) { +function MeterTrack({ className, ...props }: MeterTrackProps) { return } @@ -43,28 +45,43 @@ const meterIndicatorVariants = cva( }, ) -export type MeterTone = NonNullable['tone']> +type MeterTone = NonNullable['tone']> -export type MeterIndicatorProps = BaseMeter.Indicator.Props & { +type MeterIndicatorProps = Omit & { + className?: string tone?: MeterTone } -export function MeterIndicator({ className, tone, ...props }: MeterIndicatorProps) { +function MeterIndicator({ className, tone, ...props }: MeterIndicatorProps) { return ( ) } const meterValueClassName = 'system-xs-regular text-text-tertiary tabular-nums' -export type MeterValueProps = BaseMeter.Value.Props +type MeterValueProps = Omit & { + className?: string +} -export function MeterValue({ className, ...props }: MeterValueProps) { +function MeterValue({ className, ...props }: MeterValueProps) { return } const meterLabelClassName = 'system-xs-medium text-text-tertiary' -export type MeterLabelProps = BaseMeter.Label.Props +type MeterLabelProps = Omit & { + className?: string +} -export function MeterLabel({ className, ...props }: MeterLabelProps) { +function MeterLabel({ className, ...props }: MeterLabelProps) { return } + +export { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue } +export type { + MeterIndicatorProps, + MeterLabelProps, + MeterProps, + MeterTone, + MeterTrackProps, + MeterValueProps, +} diff --git a/packages/dify-ui/src/number-field/__tests__/index.spec.tsx b/packages/dify-ui/src/number-field/__tests__/index.spec.tsx index 5f83f07f52d..d81ea972be7 100644 --- a/packages/dify-ui/src/number-field/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/number-field/__tests__/index.spec.tsx @@ -1,7 +1,8 @@ import type { - NumberFieldButtonProps, NumberFieldControlsProps, + NumberFieldDecrementProps, NumberFieldGroupProps, + NumberFieldIncrementProps, NumberFieldInputProps, NumberFieldUnitProps, } from '../index' @@ -24,8 +25,8 @@ type RenderNumberFieldOptions = { inputProps?: Partial unitProps?: Partial & { children?: React.ReactNode } controlsProps?: Partial - incrementProps?: Partial - decrementProps?: Partial + incrementProps?: Partial + decrementProps?: Partial } const renderNumberField = ({ diff --git a/packages/dify-ui/src/number-field/index.tsx b/packages/dify-ui/src/number-field/index.tsx index e7b1980f85c..350a5bb24e3 100644 --- a/packages/dify-ui/src/number-field/index.tsx +++ b/packages/dify-ui/src/number-field/index.tsx @@ -7,10 +7,10 @@ import { cva } from 'class-variance-authority' import { cn } from '../cn' import { textControlCompoundFocusClassName } from '../form-control-shared' -export const NumberField = BaseNumberField.Root -export type NumberFieldProps = BaseNumberField.Root.Props +const NumberField = BaseNumberField.Root +type NumberFieldProps = BaseNumberField.Root.Props -export const numberFieldGroupVariants = cva( +const numberFieldGroupVariants = cva( [ 'group/number-field flex w-full min-w-0 items-stretch overflow-hidden border border-transparent bg-components-input-bg-normal text-components-input-text-filled shadow-none outline-hidden transition-[background-color,border-color,box-shadow]', 'hover:border-components-input-border-hover hover:bg-components-input-bg-hover', @@ -33,14 +33,14 @@ export const numberFieldGroupVariants = cva( }, }, ) -export type NumberFieldSize = NonNullable['size']> +type NumberFieldSize = NonNullable['size']> -export type NumberFieldGroupProps = Omit & +type NumberFieldGroupProps = Omit & VariantProps & { className?: string } -export function NumberFieldGroup({ className, size = 'medium', ...props }: NumberFieldGroupProps) { +function NumberFieldGroup({ className, size = 'medium', ...props }: NumberFieldGroupProps) { return ( & +type NumberFieldInputProps = Omit & VariantProps & { className?: string } -export function NumberFieldInput({ className, size = 'medium', ...props }: NumberFieldInputProps) { +function NumberFieldInput({ className, size = 'medium', ...props }: NumberFieldInputProps) { return ( & +type NumberFieldUnitProps = React.ComponentProps<'span'> & VariantProps -export function NumberFieldUnit({ className, size = 'medium', ...props }: NumberFieldUnitProps) { +function NumberFieldUnit({ className, size = 'medium', ...props }: NumberFieldUnitProps) { return } @@ -109,9 +109,9 @@ const numberFieldControlsVariants = cva( 'flex shrink-0 flex-col items-stretch border-l border-divider-subtle bg-transparent text-text-tertiary', ) -export type NumberFieldControlsProps = React.ComponentProps<'div'> +type NumberFieldControlsProps = React.ComponentProps<'div'> -export function NumberFieldControls({ className, ...props }: NumberFieldControlsProps) { +function NumberFieldControls({ className, ...props }: NumberFieldControlsProps) { return
      } @@ -170,7 +170,11 @@ type NumberFieldButtonVariantProps = Omit< 'direction' > -export type NumberFieldButtonProps = Omit & +type NumberFieldIncrementProps = Omit & + NumberFieldButtonVariantProps & { + className?: string + } +type NumberFieldDecrementProps = Omit & NumberFieldButtonVariantProps & { className?: string } @@ -178,12 +182,12 @@ export type NumberFieldButtonProps = Omit ) } + +export { + NumberField, + NumberFieldControls, + NumberFieldDecrement, + NumberFieldGroup, + NumberFieldIncrement, + NumberFieldInput, + NumberFieldUnit, +} +export type { + NumberFieldControlsProps, + NumberFieldDecrementProps, + NumberFieldGroupProps, + NumberFieldIncrementProps, + NumberFieldInputProps, + NumberFieldProps, + NumberFieldSize, + NumberFieldUnitProps, +} diff --git a/packages/dify-ui/src/pagination/index.tsx b/packages/dify-ui/src/pagination/index.tsx index 1113ba77632..3d80574e85b 100644 --- a/packages/dify-ui/src/pagination/index.tsx +++ b/packages/dify-ui/src/pagination/index.tsx @@ -110,7 +110,7 @@ type PaginationRootState = { disabled: boolean } -export type PaginationRootProps = Omit< +type PaginationRootProps = Omit< useRender.ComponentProps<'nav', PaginationRootState>, 'onChange' > & { @@ -122,7 +122,7 @@ export type PaginationRootProps = Omit< visiblePageCount?: number } -export function PaginationRoot({ +function PaginationRoot({ page, totalPages, onPageChange, @@ -184,11 +184,11 @@ export function PaginationRoot({ }) } -export type PaginationNavigationProps = useRender.ComponentProps<'div'> +type PaginationNavigationProps = useRender.ComponentProps<'div'> -export type PaginationContentProps = useRender.ComponentProps<'div'> +type PaginationContentProps = useRender.ComponentProps<'div'> -export function PaginationContent({ render, className, ...props }: PaginationContentProps) { +function PaginationContent({ render, className, ...props }: PaginationContentProps) { const defaultProps: useRender.ElementProps<'div'> = { className: cn( 'grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-2', @@ -203,7 +203,7 @@ export function PaginationContent({ render, className, ...props }: PaginationCon }) } -export function PaginationNavigation({ render, className, ...props }: PaginationNavigationProps) { +function PaginationNavigation({ render, className, ...props }: PaginationNavigationProps) { const defaultProps: useRender.ElementProps<'div'> = { className: cn( 'flex shrink-0 items-center gap-0.5 justify-self-start rounded-[10px] bg-background-section-burn p-0.5', @@ -218,8 +218,14 @@ export function PaginationNavigation({ render, className, ...props }: Pagination }) } -type PaginationButtonProps = Omit & { +type PaginationPreviousProps = Omit & { children?: React.ReactNode + className?: string +} + +type PaginationNextProps = Omit & { + children?: React.ReactNode + className?: string } const paginationArrowButtonClassName = [ @@ -230,12 +236,12 @@ const paginationArrowButtonClassName = [ 'motion-reduce:transition-none', ] -export function PaginationPrevious({ +function PaginationPrevious({ className, children, 'aria-label': ariaLabel, ...props -}: PaginationButtonProps) { +}: PaginationPreviousProps) { const pagination = usePaginationContext('PaginationPrevious') if (!pagination.hasPages) return null @@ -260,12 +266,12 @@ export function PaginationPrevious({ ) } -export function PaginationNext({ +function PaginationNext({ className, children, 'aria-label': ariaLabel, ...props -}: PaginationButtonProps) { +}: PaginationNextProps) { const pagination = usePaginationContext('PaginationNext') if (!pagination.hasPages) return null @@ -290,12 +296,13 @@ export function PaginationNext({ ) } -export type PaginationPageJumpProps = Omit & { +type PaginationPageJumpProps = Omit & { inputLabel?: string children?: React.ReactNode + className?: string } -export function PaginationPageJump({ +function PaginationPageJump({ className, inputLabel = 'Page number', children, @@ -402,9 +409,9 @@ export function PaginationPageJump({ ) } -export type PaginationPageListProps = useRender.ComponentProps<'ol'> +type PaginationPageListProps = useRender.ComponentProps<'ol'> -export function PaginationPageList({ render, className, ...props }: PaginationPageListProps) { +function PaginationPageList({ render, className, ...props }: PaginationPageListProps) { const pagination = usePaginationContext('PaginationPageList') const defaultProps: useRender.ElementProps<'ol'> = { @@ -427,12 +434,13 @@ export function PaginationPageList({ render, className, ...props }: PaginationPa }) } -export type PaginationPageProps = Omit & { +type PaginationPageProps = Omit & { page: number children?: React.ReactNode + className?: string } -export function PaginationPage({ +function PaginationPage({ page, className, children, @@ -465,9 +473,9 @@ export function PaginationPage({ ) } -export type PaginationEllipsisProps = useRender.ComponentProps<'span'> +type PaginationEllipsisProps = useRender.ComponentProps<'span'> -export function PaginationEllipsis({ render, className, ...props }: PaginationEllipsisProps) { +function PaginationEllipsis({ render, className, ...props }: PaginationEllipsisProps) { const defaultProps: useRender.ElementProps<'span'> = { 'aria-hidden': true, className: cn( @@ -484,7 +492,7 @@ export function PaginationEllipsis({ render, className, ...props }: PaginationEl }) } -export type PaginationPageSizeProps = { +type PaginationPageSizeProps = { value: Value options: readonly Value[] onValueChange: (value: Value) => void @@ -493,7 +501,7 @@ export type PaginationPageSizeProps = { className?: string } -export function PaginationPageSize({ +function PaginationPageSize({ value, options, onValueChange, @@ -538,14 +546,14 @@ export function PaginationPageSize({ ) } -export type PaginationLabels = { +type PaginationLabels = { previous?: string next?: string editPageNumber?: (page: number, totalPages: number) => string pageNumberInput?: string } -export type PaginationPageSizeConfig = { +type PaginationPageSizeConfig = { value: Value options: readonly Value[] onValueChange: (value: Value) => void @@ -553,15 +561,12 @@ export type PaginationPageSizeConfig = { ariaLabel?: string } -export type PaginationProps = Omit< - PaginationRootProps, - 'children' -> & { +type PaginationProps = Omit & { labels?: PaginationLabels pageSize?: PaginationPageSizeConfig } -export function Pagination({ +function Pagination({ labels, pageSize, page, @@ -598,9 +603,9 @@ export function Pagination({ ) } -export type PaginationSkeletonProps = useRender.ComponentProps<'div'> +type PaginationSkeletonProps = useRender.ComponentProps<'div'> -export function PaginationSkeleton({ render, className, ...props }: PaginationSkeletonProps) { +function PaginationSkeleton({ render, className, ...props }: PaginationSkeletonProps) { const defaultProps: useRender.ElementProps<'div'> = { 'aria-hidden': true, className: cn( @@ -635,3 +640,32 @@ export function PaginationSkeleton({ render, className, ...props }: PaginationSk props: mergeProps<'div'>(defaultProps, props), }) } + +export { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationNavigation, + PaginationNext, + PaginationPage, + PaginationPageJump, + PaginationPageList, + PaginationPageSize, + PaginationPrevious, + PaginationRoot, + PaginationSkeleton, +} +export type { + PaginationContentProps, + PaginationEllipsisProps, + PaginationNavigationProps, + PaginationNextProps, + PaginationPageJumpProps, + PaginationPageListProps, + PaginationPageProps, + PaginationPageSizeProps, + PaginationPreviousProps, + PaginationProps, + PaginationRootProps, + PaginationSkeletonProps, +} diff --git a/packages/dify-ui/src/popover/index.stories.tsx b/packages/dify-ui/src/popover/index.stories.tsx index 9af6dd2b573..dbda3464bec 100644 --- a/packages/dify-ui/src/popover/index.stories.tsx +++ b/packages/dify-ui/src/popover/index.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite' import type { Placement } from '.' import * as React from 'react' import { + createPopoverHandle, Popover, PopoverClose, PopoverContent, @@ -91,6 +92,60 @@ export const WithActions: Story = { ), } +type ResourcePopoverPayload = { + label: string + description: string +} + +const resourcePopoverPayloads = [ + { + label: 'Knowledge base', + description: 'Searches indexed documents before the model generates an answer.', + }, + { + label: 'HTTP request', + description: 'Calls an external endpoint with values from the current workflow.', + }, +] as const satisfies readonly ResourcePopoverPayload[] + +function DetachedTriggersDemo() { + const [popoverHandle] = React.useState(() => createPopoverHandle()) + + return ( +
      + {resourcePopoverPayloads.map((payload) => ( + } + > + {payload.label} + + ))} + + + {({ payload }) => ( + +
      + + {payload?.label ?? 'Resource'} + + + {payload?.description ?? 'Choose a resource to learn more.'} + +
      +
      + )} +
      +
      + ) +} + +export const DetachedTriggers: Story = { + render: () => , +} + export const Infotip: Story = { parameters: { docs: { diff --git a/packages/dify-ui/src/popover/index.tsx b/packages/dify-ui/src/popover/index.tsx index 514af9c6f55..f3eb3b73204 100644 --- a/packages/dify-ui/src/popover/index.tsx +++ b/packages/dify-ui/src/popover/index.tsx @@ -7,14 +7,19 @@ import { cn } from '../cn' import { floatingPopupAnimationClassName } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } +const Popover = BasePopover.Root +const PopoverTrigger = BasePopover.Trigger +const PopoverClose = BasePopover.Close +const PopoverTitle = BasePopover.Title +const PopoverDescription = BasePopover.Description +const createPopoverHandle = BasePopover.createHandle -export const Popover = BasePopover.Root -export const PopoverTrigger = BasePopover.Trigger -export const PopoverClose = BasePopover.Close -export const PopoverTitle = BasePopover.Title -export const PopoverDescription = BasePopover.Description -export const createPopoverHandle = BasePopover.createHandle +type PopoverProps = BasePopover.Root.Props +type PopoverHandle = BasePopover.Handle +type PopoverTriggerProps = BasePopover.Trigger.Props +type PopoverCloseProps = BasePopover.Close.Props +type PopoverTitleProps = BasePopover.Title.Props +type PopoverDescriptionProps = BasePopover.Description.Props type PopoverContentProps = { children: React.ReactNode @@ -30,7 +35,7 @@ type PopoverContentProps = { popupProps?: Omit } -export function PopoverContent({ +function PopoverContent({ children, placement = 'bottom', sideOffset = 8, @@ -67,3 +72,23 @@ export function PopoverContent({ ) } + +export { + createPopoverHandle, + Popover, + PopoverClose, + PopoverContent, + PopoverDescription, + PopoverTitle, + PopoverTrigger, +} +export type { + Placement, + PopoverCloseProps, + PopoverContentProps, + PopoverDescriptionProps, + PopoverHandle, + PopoverProps, + PopoverTitleProps, + PopoverTriggerProps, +} diff --git a/packages/dify-ui/src/preview-card/index.stories.tsx b/packages/dify-ui/src/preview-card/index.stories.tsx index 4188abe75bb..a055f50c2c8 100644 --- a/packages/dify-ui/src/preview-card/index.stories.tsx +++ b/packages/dify-ui/src/preview-card/index.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite' -import type { Placement } from '.' +import type { PreviewCardContentProps } from '.' import * as React from 'react' import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '.' @@ -33,7 +33,68 @@ type Story = StoryObj // destination. The Wikipedia URL and Unsplash image are the exact assets used // in base-ui.com's public docs so the story renders a real preview. // https://base-ui.com/react/components/preview-card -const typographyPreview = createPreviewCardHandle() +type TypographyPreviewPayload = { + title: string + description: string + image: { + src: string + alt: string + } +} + +const typographyPreviewPayload = { + title: 'Typography', + description: + 'Typography is the art and science of arranging type to make written language legible, readable, and visually appealing.', + image: { + src: 'https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300', + alt: 'Station Hofplein signage in Rotterdam, Netherlands', + }, +} satisfies TypographyPreviewPayload + +function LinkPreviewDemo() { + const [previewCardHandle] = React.useState(() => + createPreviewCardHandle(), + ) + + return ( +
      +

      + The principles of good{' '} + + typography + {' '} + remain in the digital age. +

      + + + {({ payload = typographyPreviewPayload }) => ( + +
      + {payload.image.alt} +

      + {payload.title} {payload.description} +

      +
      +
      + )} +
      +
      + ) +} export const LinkPreview: Story = { name: 'Link preview (canonical)', @@ -45,44 +106,12 @@ export const LinkPreview: Story = { }, }, }, - render: () => ( -
      -

      - The principles of good{' '} - - typography - {' '} - remain in the digital age. -

      - - - -
      - Station Hofplein signage in Rotterdam, Netherlands -

      - Typography is the art and science of - arranging type to make written language legible, readable, and visually appealing. -

      -
      -
      -
      -
      - ), + render: () => , } -const PLACEMENTS: Placement[] = [ +type PreviewCardPlacement = NonNullable + +const PLACEMENTS: PreviewCardPlacement[] = [ 'top-start', 'top', 'top-end', @@ -98,7 +127,7 @@ const PLACEMENTS: Placement[] = [ ] const PlacementsDemo = () => { - const [placement, setPlacement] = React.useState('bottom') + const [placement, setPlacement] = React.useState('bottom') return (
      diff --git a/packages/dify-ui/src/preview-card/index.tsx b/packages/dify-ui/src/preview-card/index.tsx index e52e49add1a..76f7a3cd5e7 100644 --- a/packages/dify-ui/src/preview-card/index.tsx +++ b/packages/dify-ui/src/preview-card/index.tsx @@ -7,8 +7,6 @@ import { cn } from '../cn' import { floatingPopupAnimationClassName } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } - /** * PreviewCard follows Base UI's canonical semantics: a hover/focus-triggered * visual enhancement for a link that previews its destination. @@ -21,10 +19,15 @@ export type { Placement } * opening the popup is itself the trigger's purpose or its content must be * accessible across input modes. */ -export const PreviewCard = BasePreviewCard.Root -export const PreviewCardTrigger = BasePreviewCard.Trigger -export const PreviewCardViewport = BasePreviewCard.Viewport -export const createPreviewCardHandle = BasePreviewCard.createHandle +const PreviewCard = BasePreviewCard.Root +const PreviewCardTrigger = BasePreviewCard.Trigger +const PreviewCardViewport = BasePreviewCard.Viewport +const createPreviewCardHandle = BasePreviewCard.createHandle + +type PreviewCardProps = BasePreviewCard.Root.Props +type PreviewCardHandle = BasePreviewCard.Handle +type PreviewCardTriggerProps = BasePreviewCard.Trigger.Props +type PreviewCardViewportProps = BasePreviewCard.Viewport.Props type PreviewCardContentProps = { children: React.ReactNode @@ -40,7 +43,7 @@ type PreviewCardContentProps = { popupProps?: Omit } -export function PreviewCardContent({ +function PreviewCardContent({ children, placement = 'bottom', sideOffset = 8, @@ -76,3 +79,18 @@ export function PreviewCardContent({ ) } + +export { + createPreviewCardHandle, + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, + PreviewCardViewport, +} +export type { + PreviewCardContentProps, + PreviewCardHandle, + PreviewCardProps, + PreviewCardTriggerProps, + PreviewCardViewportProps, +} diff --git a/packages/dify-ui/src/progress/index.stories.tsx b/packages/dify-ui/src/progress/index.stories.tsx index 4a6339abf07..2e44114063e 100644 --- a/packages/dify-ui/src/progress/index.stories.tsx +++ b/packages/dify-ui/src/progress/index.stories.tsx @@ -1,8 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react-vite' -import type { ProgressCircleColor, ProgressCircleSize } from '.' +import type { ProgressCircleProps } from '.' import * as React from 'react' import { ProgressCircle } from '.' +type ProgressCircleColor = NonNullable +type ProgressCircleSize = NonNullable + const colors: ProgressCircleColor[] = ['gray', 'white', 'blue', 'warning', 'error'] const sizes: ProgressCircleSize[] = ['small', 'medium', 'large'] diff --git a/packages/dify-ui/src/progress/index.tsx b/packages/dify-ui/src/progress/index.tsx index e4559b97f09..90226f0799e 100644 --- a/packages/dify-ui/src/progress/index.tsx +++ b/packages/dify-ui/src/progress/index.tsx @@ -46,10 +46,8 @@ const progressCircleColorClasses = { }, } as const -export type ProgressCircleSize = NonNullable< - VariantProps['size'] -> -export type ProgressCircleColor = keyof typeof progressCircleColorClasses +type ProgressCircleSize = NonNullable['size']> +type ProgressCircleColor = keyof typeof progressCircleColorClasses const progressCircleSizeValues = { small: 12, @@ -67,7 +65,7 @@ type ProgressCircleAccessibleNameProps = 'aria-labelledby': string } -export type ProgressCircleProps = Omit< +type ProgressCircleProps = Omit< BaseProgress.Root.Props, 'children' | 'className' | 'aria-label' | 'aria-labelledby' > & @@ -113,7 +111,7 @@ function getSectorPath(size: number, percentage: number | null) { ` } -export function ProgressCircle({ +function ProgressCircle({ className, color = 'blue', size = 'small', @@ -163,3 +161,6 @@ export function ProgressCircle({ ) } + +export { ProgressCircle } +export type { ProgressCircleProps } diff --git a/packages/dify-ui/src/public-types.test-d.ts b/packages/dify-ui/src/public-types.test-d.ts new file mode 100644 index 00000000000..3ff1e1a1fef --- /dev/null +++ b/packages/dify-ui/src/public-types.test-d.ts @@ -0,0 +1,188 @@ +// Compile-time public API contracts; included by package typecheck and excluded from package exports. +import type { + AutocompleteCollection, + AutocompleteCollectionProps, + AutocompleteGroup, + AutocompleteGroupProps, + AutocompleteItemProps, + AutocompleteList, + AutocompleteListProps, + AutocompleteProps, +} from './autocomplete' +import type { + ComboboxCollection, + ComboboxCollectionProps, + ComboboxGroup, + ComboboxGroupProps, + ComboboxItemProps, + ComboboxList, + ComboboxListProps, + ComboboxProps, + ComboboxValue, + ComboboxValueProps, +} from './combobox' +import type { ContextMenuRadioGroupProps, ContextMenuRadioItemProps } from './context-menu' +import type { DialogHandle, DialogTriggerProps } from './dialog' +import type { DrawerHandle, DrawerTriggerProps } from './drawer' +import type { DropdownMenuRadioGroupProps, DropdownMenuRadioItemProps } from './dropdown-menu' +import type { FormProps } from './form' +import type { PopoverHandle, PopoverTriggerProps } from './popover' +import type { PreviewCardHandle, PreviewCardTriggerProps } from './preview-card' +import type { RadioGroupProps, RadioItemProps } from './radio' +import type { SelectItemProps, SelectProps, SelectValue, SelectValueProps } from './select' +import type { SliderRootProps } from './slider' + +type Equal = + (() => Value extends Left ? 1 : 2) extends () => Value extends Right ? 1 : 2 + ? true + : false +type Expect = Condition +type Arguments = Callback extends (...args: infer Values) => unknown ? Values : never +type FirstArgument = Arguments[0] +type SecondArgument = Arguments[1] +type IsRequired = + Record extends Pick ? false : true +type Callable = Member extends (...args: infer Values) => infer Result + ? (...args: Values) => Result + : never + +type DomainValue = { id: string } +type FormValues = { enabled: boolean; name: string } +type RangeValue = readonly [number, number] +type OverlayPayload = { sourceId: string } +type SelectSingleValueProps = Parameters>[0] +type SelectMultipleValueProps = Parameters>[0] +type SelectDynamicValueProps = Parameters>[0] +type ComboboxSingleValueProps = Parameters>[0] +type ComboboxMultipleValueProps = Parameters>[0] +type ComboboxDynamicValueProps = Parameters>[0] +type AutocompleteListRuntimeProps = Parameters>[0] +type AutocompleteCollectionRuntimeProps = Parameters>[0] +type AutocompleteGroupRuntimeProps = Parameters>[0] +type ComboboxListRuntimeProps = Parameters>[0] +type ComboboxCollectionRuntimeProps = Parameters>[0] +type ComboboxGroupRuntimeProps = Parameters>[0] + +type PublicTypeAssertions = [ + Expect['onFormSubmit']>>, FormValues>>, + Expect< + Equal< + FirstArgument['onValueChange']>>, + DomainValue | null + > + >, + Expect< + Equal< + FirstArgument['onValueChange']>>, + DomainValue[] + > + >, + Expect, 'multiple'>, true>>, + Expect, 'multiple'>, false>>, + Expect['value'], DomainValue | undefined>>, + Expect< + Equal< + FirstArgument['children']>>, + DomainValue[] | null + > + >, + Expect>, DomainValue | null>>, + Expect< + Equal>, DomainValue[] | null> + >, + Expect< + Equal< + FirstArgument>, + DomainValue | DomainValue[] | null + > + >, + Expect< + Equal< + FirstArgument['onValueChange']>>, + DomainValue[] + > + >, + Expect, 'multiple'>, true>>, + Expect, 'multiple'>, false>>, + Expect['value'], DomainValue | undefined>>, + Expect< + Equal< + FirstArgument['children']>>, + DomainValue[] | null + > + >, + Expect>, DomainValue | null>>, + Expect< + Equal>, DomainValue[] | null> + >, + Expect< + Equal< + FirstArgument>, + DomainValue | DomainValue[] | null + > + >, + Expect< + Equal< + FirstArgument['onItemHighlighted']>>, + DomainValue | undefined + > + >, + Expect['value'], DomainValue | undefined>>, + Expect< + Equal['children']>>, DomainValue> + >, + Expect['children']>>, number>>, + Expect>, DomainValue>>, + Expect['children']>, DomainValue>>, + Expect['children']>, number>>, + Expect, DomainValue>>, + Expect['items'], readonly DomainValue[] | undefined>>, + Expect>, + Expect['children']>>, DomainValue>>, + Expect['children']>>, number>>, + Expect>, DomainValue>>, + Expect['children']>, DomainValue>>, + Expect['children']>, number>>, + Expect, DomainValue>>, + Expect['items'], readonly DomainValue[] | undefined>>, + Expect>, + Expect< + Equal< + FirstArgument['onValueChange']>>, + DomainValue + > + >, + Expect['value'], DomainValue>>, + Expect< + Equal< + FirstArgument['onValueChange']>>, + DomainValue + > + >, + Expect['value'], DomainValue>>, + Expect< + Equal['onValueChange']>>, DomainValue> + >, + Expect['value'], DomainValue>>, + Expect['value'], RangeValue | undefined>>, + Expect< + Equal['onValueChange']>>, RangeValue> + >, + Expect< + Equal['handle']>, DialogHandle> + >, + Expect< + Equal['handle']>, DrawerHandle> + >, + Expect< + Equal['handle']>, PopoverHandle> + >, + Expect< + Equal< + NonNullable['handle']>, + PreviewCardHandle + > + >, +] + +export type { PublicTypeAssertions } diff --git a/packages/dify-ui/src/radio/index.tsx b/packages/dify-ui/src/radio/index.tsx index dfcfc2882e9..379f8b35c0d 100644 --- a/packages/dify-ui/src/radio/index.tsx +++ b/packages/dify-ui/src/radio/index.tsx @@ -7,30 +7,30 @@ import { Radio as BaseRadio } from '@base-ui/react/radio' import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group' import { cn } from '../cn' -export type RadioGroupProps = Omit, 'className'> & { +type RadioGroupProps = Omit, 'className'> & { className?: string } -export function RadioGroup({ className, ...props }: RadioGroupProps) { +function RadioGroup({ className, ...props }: RadioGroupProps) { return className={cn('flex items-center gap-2', className)} {...props} /> } -export type RadioItemProps = Omit, 'className'> & { +type RadioItemProps = Omit, 'className'> & { className?: string } -export function RadioItem({ className, ...props }: RadioItemProps) { +function RadioItem({ className, ...props }: RadioItemProps) { return className={className} {...props} /> } -export type RadioControlProps = Omit< +type RadioControlProps = Omit< BaseRadioNS.Indicator.Props, 'className' | 'children' | 'keepMounted' > & { className?: string } -export function RadioControl({ className, ...props }: RadioControlProps) { +function RadioControl({ className, ...props }: RadioControlProps) { return ( = Omit, 'children'> +type RadioProps = Omit, 'children'> -export function Radio({ className, ...props }: RadioProps) { +function Radio({ className, ...props }: RadioProps) { return ( className={cn( @@ -73,9 +73,9 @@ export function Radio({ className, ...props }: RadioProps ) } -export type RadioSkeletonProps = React.ComponentProps<'div'> +type RadioSkeletonProps = React.ComponentProps<'div'> -export function RadioSkeleton({ className, ...props }: RadioSkeletonProps) { +function RadioSkeleton({ className, ...props }: RadioSkeletonProps) { return (
      ) } + +export { Radio, RadioControl, RadioGroup, RadioItem, RadioSkeleton } + +export type { RadioControlProps, RadioGroupProps, RadioItemProps, RadioProps, RadioSkeletonProps } diff --git a/packages/dify-ui/src/scroll-area/index.tsx b/packages/dify-ui/src/scroll-area/index.tsx index f6914d33002..acdd50e1a03 100644 --- a/packages/dify-ui/src/scroll-area/index.tsx +++ b/packages/dify-ui/src/scroll-area/index.tsx @@ -4,20 +4,22 @@ import type * as React from 'react' import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area' import { cn } from '../cn' -export const ScrollAreaRoot = BaseScrollArea.Root +const ScrollAreaRoot = BaseScrollArea.Root type ScrollAreaRootProps = BaseScrollArea.Root.Props -export const ScrollAreaContent = BaseScrollArea.Content +const ScrollAreaContent = BaseScrollArea.Content +type ScrollAreaContentProps = BaseScrollArea.Content.Props type ScrollAreaSlotClassNames = { viewport?: string content?: string scrollbar?: string } +type ScrollAreaOrientation = NonNullable type ScrollAreaProps = Omit & { children: React.ReactNode - orientation?: 'vertical' | 'horizontal' + orientation?: ScrollAreaOrientation slotClassNames?: ScrollAreaSlotClassNames label?: string labelledBy?: string @@ -47,17 +49,21 @@ const scrollAreaViewportClassName = cn( const scrollAreaCornerClassName = 'bg-transparent' -type ScrollAreaViewportProps = BaseScrollArea.Viewport.Props +type ScrollAreaViewportProps = Omit & { + className?: string +} -export function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) { +function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) { return ( ) } -type ScrollAreaScrollbarProps = BaseScrollArea.Scrollbar.Props +type ScrollAreaScrollbarProps = Omit & { + className?: string +} -export function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) { +function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) { return ( & { + className?: string +} -export function ScrollAreaThumb({ className, ...props }: ScrollAreaThumbProps) { +function ScrollAreaThumb({ className, ...props }: ScrollAreaThumbProps) { return } -type ScrollAreaCornerProps = BaseScrollArea.Corner.Props +type ScrollAreaCornerProps = Omit & { + className?: string +} -export function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) { +function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) { return } -export function ScrollArea({ +function ScrollArea({ children, className, orientation = 'vertical', @@ -104,3 +114,23 @@ export function ScrollArea({ ) } + +export { + ScrollArea, + ScrollAreaContent, + ScrollAreaCorner, + ScrollAreaRoot, + ScrollAreaScrollbar, + ScrollAreaThumb, + ScrollAreaViewport, +} + +export type { + ScrollAreaContentProps, + ScrollAreaCornerProps, + ScrollAreaProps, + ScrollAreaRootProps, + ScrollAreaScrollbarProps, + ScrollAreaThumbProps, + ScrollAreaViewportProps, +} diff --git a/packages/dify-ui/src/segmented-control/index.tsx b/packages/dify-ui/src/segmented-control/index.tsx index 3017feedd3c..342691bdf58 100644 --- a/packages/dify-ui/src/segmented-control/index.tsx +++ b/packages/dify-ui/src/segmented-control/index.tsx @@ -7,14 +7,14 @@ import { Toggle as BaseToggle } from '@base-ui/react/toggle' import { ToggleGroup as BaseToggleGroup } from '@base-ui/react/toggle-group' import { cn } from '../cn' -export type SegmentedControlProps = Omit< +type SegmentedControlProps = Omit< BaseToggleGroupNS.Props, 'className' > & { className?: string } -export function SegmentedControl({ +function SegmentedControl({ className, ...props }: SegmentedControlProps) { @@ -29,14 +29,14 @@ export function SegmentedControl({ ) } -export type SegmentedControlItemProps = Omit< +type SegmentedControlItemProps = Omit< BaseToggleNS.Props, 'className' > & { className?: string } -export function SegmentedControlItem({ +function SegmentedControlItem({ className, ...props }: SegmentedControlItemProps) { @@ -51,11 +51,11 @@ export function SegmentedControlItem({ ) } -export type SegmentedControlDividerProps = Omit, 'className'> & { +type SegmentedControlDividerProps = Omit, 'className'> & { className?: string } -export function SegmentedControlDivider({ className, ...props }: SegmentedControlDividerProps) { +function SegmentedControlDivider({ className, ...props }: SegmentedControlDividerProps) { return ( ) } + +export { SegmentedControl, SegmentedControlDivider, SegmentedControlItem } + +export type { SegmentedControlDividerProps, SegmentedControlItemProps, SegmentedControlProps } diff --git a/packages/dify-ui/src/select/__tests__/index.spec.tsx b/packages/dify-ui/src/select/__tests__/index.spec.tsx index 2802b145061..d1876b73de5 100644 --- a/packages/dify-ui/src/select/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/select/__tests__/index.spec.tsx @@ -89,6 +89,27 @@ describe('Select wrappers', () => { expect(hiddenInput).toHaveAttribute('autocomplete', 'address-level2') expect(new FormData(form).get('city')).toBe('seattle') }) + + it('should expose a controlled null value to a typed multiple value renderer', async () => { + const screen = await renderWithSafeViewport( + multiple value={null}> + + > + {(selectedValue) => + selectedValue === null ? 'No cities selected' : selectedValue.join(', ') + } + + + + value="seattle"> + Seattle + + + , + ) + + await expect.element(screen.getByText('No cities selected')).toBeInTheDocument() + }) }) describe('SelectTrigger', () => { diff --git a/packages/dify-ui/src/select/index.stories.tsx b/packages/dify-ui/src/select/index.stories.tsx index 4c386f81857..408e75bf4bf 100644 --- a/packages/dify-ui/src/select/index.stories.tsx +++ b/packages/dify-ui/src/select/index.stories.tsx @@ -27,6 +27,14 @@ const cityItems = [ { label: 'Paris', value: 'paris' }, ] +const deploymentRegionItems = [ + { label: 'US East', value: 'us-east' }, + { label: 'Europe West', value: 'eu-west' }, + { label: 'Asia Pacific', value: 'ap-southeast' }, +] as const + +type DeploymentRegion = (typeof deploymentRegionItems)[number]['value'] + const meta = { title: 'Base/Form/Select', component: Select, @@ -35,7 +43,7 @@ const meta = { docs: { description: { component: - 'Compound select built on Base UI Select. Compose `SelectTrigger`, `SelectContent`, and `SelectItem` to build accessible single-value pickers with groups, labels, separators, and keyboard selection.', + 'Compound select built on Base UI Select. Compose `SelectTrigger`, `SelectContent`, and `SelectItem` to build accessible single- or multiple-value pickers with groups, labels, separators, and keyboard selection.', }, }, }, @@ -328,6 +336,68 @@ export const Controlled: Story = { render: () => , } +const MultipleControlledDemo = () => { + const [value, setValue] = React.useState(['us-east', 'eu-west']) + + return ( +
      + + items={deploymentRegionItems} + multiple + value={value} + onValueChange={setValue} + > + Deployment regions + + > + {(selectedRegions) => { + if (!selectedRegions?.length) return 'Choose regions' + + const [firstSelectedRegion] = selectedRegions + if (!firstSelectedRegion) return 'Choose regions' + + const firstRegion = deploymentRegionItems.find( + (item) => item.value === firstSelectedRegion, + ) + const additionalRegionCount = selectedRegions.length - 1 + const firstRegionLabel = firstRegion?.label ?? firstSelectedRegion + + return additionalRegionCount > 0 + ? `${firstRegionLabel} (+${additionalRegionCount} more)` + : firstRegionLabel + }} + + + + {deploymentRegionItems.map((item) => ( + key={item.value} value={item.value}> + {item.label} + + + ))} + + +
      + ) +} + +export const MultipleControlled: Story = { + render: () => , + play: async ({ canvas, canvasElement, userEvent }) => { + const trigger = canvas.getByRole('combobox', { name: 'Deployment regions' }) + const body = within(canvasElement.ownerDocument.body) + + await expect(trigger).toHaveTextContent('US East (+1 more)') + await userEvent.click(trigger) + + const asiaPacificOption = await body.findByRole('option', { name: 'Asia Pacific' }) + await userEvent.click(asiaPacificOption) + + await expect(trigger).toHaveTextContent('US East (+2 more)') + await expect(asiaPacificOption).toHaveAttribute('aria-selected', 'true') + }, +} + export const InForm: Story = { render: () => (
      undefined}> diff --git a/packages/dify-ui/src/select/index.tsx b/packages/dify-ui/src/select/index.tsx index 060488e96f8..87c2fea6595 100644 --- a/packages/dify-ui/src/select/index.tsx +++ b/packages/dify-ui/src/select/index.tsx @@ -1,5 +1,6 @@ 'use client' +import type { ComponentRenderFn, HTMLProps } from '@base-ui/react/types' import type { VariantProps } from 'class-variance-authority' import type * as React from 'react' import type { Placement } from '../placement' @@ -15,21 +16,49 @@ import { } from '../overlay-shared' import { parsePlacement } from '../placement' -export type { Placement } - -export type SelectProps< +type SelectProps = BaseSelect.Root.Props< Value, - Multiple extends boolean | undefined = false, -> = BaseSelect.Root.Props + Multiple +> & + ([Multiple] extends [true] ? { multiple: true } : unknown) -export function Select( +function Select( props: SelectProps, ): React.JSX.Element { return } -export const SelectValue = BaseSelect.Value -export const SelectGroup = BaseSelect.Group +const SelectGroup = BaseSelect.Group + +type SelectSelectedValue = + | (Multiple extends true ? Value[] : Value) + | null +type SelectValueState = Omit< + BaseSelect.Value.State, + 'value' +> & { + value: SelectSelectedValue +} +type SelectValueProps = Omit< + BaseSelect.Value.Props, + 'children' | 'className' | 'render' | 'style' +> & { + children?: React.ReactNode | ((value: SelectSelectedValue) => React.ReactNode) + className?: string | ((state: SelectValueState) => string | undefined) + render?: + | React.ReactElement + | ComponentRenderFn, SelectValueState> + style?: + | React.CSSProperties + | ((state: SelectValueState) => React.CSSProperties | undefined) +} +function SelectValue( + props: SelectValueProps, +): React.JSX.Element +function SelectValue(props: BaseSelect.Value.Props): React.JSX.Element { + return +} +type SelectGroupProps = BaseSelect.Group.Props const selectTriggerVariants = cva( [ @@ -55,10 +84,13 @@ const selectTriggerVariants = cva( }, ) -type SelectTriggerProps = Omit & - VariantProps & { className?: string } +type SelectSize = NonNullable['size']> +type SelectTriggerProps = Omit & { + className?: string + size?: SelectSize +} -export function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) { +function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) { return ( {children} @@ -69,18 +101,33 @@ export function SelectTrigger({ className, children, size, ...props }: SelectTri ) } -export function SelectLabel({ className, ...props }: BaseSelect.Label.Props) { +type SelectLabelProps = Omit & { className?: string } + +function SelectLabel({ className, ...props }: SelectLabelProps) { return } -export function SelectGroupLabel({ className, ...props }: BaseSelect.GroupLabel.Props) { +type SelectGroupLabelProps = Omit & { + className?: string +} + +function SelectGroupLabel({ className, ...props }: SelectGroupLabelProps) { return } -export function SelectSeparator({ className, ...props }: BaseSelect.Separator.Props) { +type SelectSeparatorProps = Omit & { className?: string } + +function SelectSeparator({ className, ...props }: SelectSeparatorProps) { return } +type SelectContentPositionerProps = Omit< + BaseSelect.Positioner.Props, + 'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset' +> +type SelectContentPopupProps = Omit +type SelectContentListProps = Omit + type SelectContentProps = { children: React.ReactNode placement?: Placement @@ -89,15 +136,12 @@ type SelectContentProps = { className?: string popupClassName?: string listClassName?: string - positionerProps?: Omit< - BaseSelect.Positioner.Props, - 'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset' - > - popupProps?: Omit - listProps?: Omit + positionerProps?: SelectContentPositionerProps + popupProps?: SelectContentPopupProps + listProps?: SelectContentListProps } -export function SelectContent({ +function SelectContent({ children, placement = 'bottom-start', sideOffset = 4, @@ -142,7 +186,12 @@ export function SelectContent({ ) } -export function SelectItem({ className, ...props }: BaseSelect.Item.Props) { +type SelectItemProps = Omit & { + className?: string + value?: Value +} + +function SelectItem({ className, ...props }: SelectItemProps) { return ( & { className?: string } + +function SelectItemText({ className, ...props }: SelectItemTextProps) { return ( ) } -export function SelectItemIndicator({ - className, - ...props -}: Omit) { +type SelectItemIndicatorProps = Omit & { + className?: string +} + +function SelectItemIndicator({ className, ...props }: SelectItemIndicatorProps) { return ( ) } + +export { + Select, + SelectContent, + SelectGroup, + SelectGroupLabel, + SelectItem, + SelectItemIndicator, + SelectItemText, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +} + +export type { + SelectContentProps, + SelectGroupLabelProps, + SelectGroupProps, + SelectItemIndicatorProps, + SelectItemProps, + SelectItemTextProps, + SelectLabelProps, + SelectProps, + SelectSeparatorProps, + SelectTriggerProps, + SelectValueProps, +} diff --git a/packages/dify-ui/src/slider/index.stories.tsx b/packages/dify-ui/src/slider/index.stories.tsx index 2524278585a..7572a99090e 100644 --- a/packages/dify-ui/src/slider/index.stories.tsx +++ b/packages/dify-ui/src/slider/index.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite' +import type { SliderProps } from '.' import * as React from 'react' import { Slider, @@ -49,7 +50,7 @@ function SliderDemo({ value: initialValue = 50, defaultValue: _defaultValue, ...args -}: React.ComponentProps) { +}: SliderProps) { const [value, setValue] = React.useState(initialValue) return ( @@ -107,3 +108,37 @@ export const ComposedWithLabel: Story = { ), } + +type PriceRange = readonly [number, number] + +function RangeSliderDemo() { + const [range, setRange] = React.useState([25, 75]) + + return ( +
      + + value={range} + onValueChange={setRange} + min={0} + max={100} + className="group/slider relative inline-flex w-full flex-col gap-1 data-disabled:opacity-30" + > + Price range + + + + + + + + +
      + {range[0]} – {range[1]} +
      +
      + ) +} + +export const Range: Story = { + render: () => , +} diff --git a/packages/dify-ui/src/slider/index.tsx b/packages/dify-ui/src/slider/index.tsx index b806373caf7..4007f837f3c 100644 --- a/packages/dify-ui/src/slider/index.tsx +++ b/packages/dify-ui/src/slider/index.tsx @@ -4,22 +4,27 @@ import { Slider as BaseSlider } from '@base-ui/react/slider' import { cn } from '../cn' import { formLabelClassName } from '../form-control-shared' -export const SliderRoot = BaseSlider.Root +const SliderRoot = BaseSlider.Root -export function SliderLabel({ className, ...props }: BaseSlider.Label.Props) { +type SliderValue = number +type SliderRangeValue = readonly number[] +type SliderRootValue = SliderValue | SliderRangeValue +type SliderRootProps = BaseSlider.Root.Props + +type SliderLabelProps = Omit & { className?: string } + +function SliderLabel({ className, ...props }: SliderLabelProps) { return } -type SliderRootProps = BaseSlider.Root.Props - const sliderControlClassName = cn( 'relative flex h-5 w-full touch-none items-center select-none', 'data-disabled:cursor-not-allowed', ) -type SliderControlProps = BaseSlider.Control.Props +type SliderControlProps = Omit & { className?: string } -export function SliderControl({ className, ...props }: SliderControlProps) { +function SliderControl({ className, ...props }: SliderControlProps) { return } @@ -28,17 +33,19 @@ const sliderTrackClassName = cn( 'bg-components-slider-track', ) -type SliderTrackProps = BaseSlider.Track.Props +type SliderTrackProps = Omit & { className?: string } -export function SliderTrack({ className, ...props }: SliderTrackProps) { +function SliderTrack({ className, ...props }: SliderTrackProps) { return } const sliderIndicatorClassName = cn('h-full rounded-full', 'bg-components-slider-range') -type SliderIndicatorProps = BaseSlider.Indicator.Props +type SliderIndicatorProps = Omit & { + className?: string +} -export function SliderIndicator({ className, ...props }: SliderIndicatorProps) { +function SliderIndicator({ className, ...props }: SliderIndicatorProps) { return } @@ -52,9 +59,9 @@ const sliderThumbClassName = cn( 'group-data-disabled/slider:border-components-slider-knob-border group-data-disabled/slider:bg-components-slider-knob-disabled group-data-disabled/slider:shadow-none', ) -type SliderThumbProps = BaseSlider.Thumb.Props +type SliderThumbProps = Omit & { className?: string } -export function SliderThumb({ className, ...props }: SliderThumbProps) { +function SliderThumb({ className, ...props }: SliderThumbProps) { return } @@ -66,7 +73,7 @@ type SliderSlotClassNames = { } type SliderBaseProps = Pick< - SliderRootProps, + SliderRootProps, 'onValueChange' | 'min' | 'max' | 'step' | 'disabled' | 'name' > & Pick & { @@ -75,13 +82,13 @@ type SliderBaseProps = Pick< } type ControlledSliderProps = SliderBaseProps & { - value: number + value: SliderValue defaultValue?: never } type UncontrolledSliderProps = SliderBaseProps & { value?: never - defaultValue?: number + defaultValue?: SliderValue } type SliderProps = ControlledSliderProps | UncontrolledSliderProps @@ -94,7 +101,7 @@ const getSafeValue = (value: number | undefined, min: number) => { return Number.isFinite(value) ? value : min } -export function Slider({ +function Slider({ value, defaultValue, onValueChange, @@ -134,3 +141,15 @@ export function Slider({ ) } + +export { Slider, SliderControl, SliderIndicator, SliderLabel, SliderRoot, SliderThumb, SliderTrack } + +export type { + SliderControlProps, + SliderIndicatorProps, + SliderLabelProps, + SliderProps, + SliderRootProps, + SliderThumbProps, + SliderTrackProps, +} diff --git a/packages/dify-ui/src/status-dot/index.stories.tsx b/packages/dify-ui/src/status-dot/index.stories.tsx index 7a45821e055..ad4d27a76a9 100644 --- a/packages/dify-ui/src/status-dot/index.stories.tsx +++ b/packages/dify-ui/src/status-dot/index.stories.tsx @@ -1,8 +1,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite' -import type { StatusDotSize, StatusDotStatus } from '.' +import type { StatusDotProps, StatusDotStatus } from '.' import * as React from 'react' import { StatusDot, StatusDotSkeleton } from '.' +type StatusDotSize = NonNullable + const statuses: StatusDotStatus[] = ['success', 'warning', 'error', 'normal', 'disabled'] const sizes: StatusDotSize[] = ['small', 'medium'] diff --git a/packages/dify-ui/src/status-dot/index.tsx b/packages/dify-ui/src/status-dot/index.tsx index 0f6891b4f78..35c942d6bd4 100644 --- a/packages/dify-ui/src/status-dot/index.tsx +++ b/packages/dify-ui/src/status-dot/index.tsx @@ -47,19 +47,19 @@ const statusDotSkeletonVariants = cva( type StatusDotVariants = VariantProps -export type StatusDotStatus = NonNullable -export type StatusDotSize = NonNullable +type StatusDotStatus = NonNullable +type StatusDotSize = NonNullable -export type StatusDotProps = Omit, 'children'> & { +type StatusDotProps = Omit, 'children'> & { status?: StatusDotStatus size?: StatusDotSize } -export type StatusDotSkeletonProps = Omit, 'children'> & { +type StatusDotSkeletonProps = Omit, 'children'> & { size?: StatusDotSize } -export function StatusDot({ +function StatusDot({ className, status = 'success', size = 'medium', @@ -81,7 +81,7 @@ export function StatusDot({ ) } -export function StatusDotSkeleton({ +function StatusDotSkeleton({ className, size = 'medium', 'aria-hidden': ariaHidden, @@ -101,3 +101,7 @@ export function StatusDotSkeleton({ /> ) } + +export { StatusDot, StatusDotSkeleton } + +export type { StatusDotProps, StatusDotSkeletonProps, StatusDotStatus } diff --git a/packages/dify-ui/src/switch/index.tsx b/packages/dify-ui/src/switch/index.tsx index 0dce682c205..2e9d59ef749 100644 --- a/packages/dify-ui/src/switch/index.tsx +++ b/packages/dify-ui/src/switch/index.tsx @@ -44,8 +44,6 @@ const switchThumbVariants = cva( }, ) -export type SwitchSize = NonNullable['size']> - const switchSpinnerVariants = cva('absolute top-1/2 -translate-x-1/2 -translate-y-1/2', { variants: { size: { @@ -67,7 +65,7 @@ type UncontrolledSwitchProps = { type SwitchControlProps = ControlledSwitchProps | UncontrolledSwitchProps -export type SwitchProps = Omit< +type SwitchProps = Omit< BaseSwitchNS.Root.Props, 'checked' | 'defaultChecked' | 'className' | 'size' | 'onCheckedChange' > & @@ -78,7 +76,7 @@ export type SwitchProps = Omit< className?: string } -export function Switch({ +function Switch({ checked, size = 'md', disabled, @@ -122,9 +120,12 @@ const switchSkeletonVariants = cva('bg-text-quaternary opacity-20', { }, }) -export type SwitchSkeletonProps = React.ComponentProps<'div'> & - VariantProps +type SwitchSkeletonProps = React.ComponentProps<'div'> & VariantProps -export function SwitchSkeleton({ size = 'md', className, ...props }: SwitchSkeletonProps) { +function SwitchSkeleton({ size = 'md', className, ...props }: SwitchSkeletonProps) { return
      } + +export { Switch, SwitchSkeleton } + +export type { SwitchProps, SwitchSkeletonProps } diff --git a/packages/dify-ui/src/tabs/index.tsx b/packages/dify-ui/src/tabs/index.tsx index cc275adf51d..db8df75fa87 100644 --- a/packages/dify-ui/src/tabs/index.tsx +++ b/packages/dify-ui/src/tabs/index.tsx @@ -4,23 +4,22 @@ import type { Tabs as BaseTabsNS } from '@base-ui/react/tabs' import { Tabs as BaseTabs } from '@base-ui/react/tabs' import { cn } from '../cn' -export type TabsProps = BaseTabsNS.Root.Props +type TabsProps = BaseTabsNS.Root.Props +const Tabs = BaseTabs.Root -export const Tabs = BaseTabs.Root - -export type TabsListProps = Omit & { +type TabsListProps = Omit & { className?: string } -export function TabsList({ className, ...props }: TabsListProps) { +function TabsList({ className, ...props }: TabsListProps) { return } -export type TabsTabProps = Omit & { +type TabsTabProps = Omit & { className?: string } -export function TabsTab({ className, ...props }: TabsTabProps) { +function TabsTab({ className, ...props }: TabsTabProps) { return ( & { - className?: string -} +type TabsPanelProps = BaseTabsNS.Panel.Props +const TabsPanel = BaseTabs.Panel -export function TabsPanel({ className, ...props }: TabsPanelProps) { - return -} +const TabsIndicator = BaseTabs.Indicator +type TabsIndicatorProps = BaseTabsNS.Indicator.Props -export const TabsIndicator = BaseTabs.Indicator +export { Tabs, TabsIndicator, TabsList, TabsPanel, TabsTab } + +export type { TabsIndicatorProps, TabsListProps, TabsPanelProps, TabsProps, TabsTabProps } diff --git a/packages/dify-ui/src/textarea/index.tsx b/packages/dify-ui/src/textarea/index.tsx index 3c88495392e..09b9c636fef 100644 --- a/packages/dify-ui/src/textarea/index.tsx +++ b/packages/dify-ui/src/textarea/index.tsx @@ -35,8 +35,7 @@ const textareaVariants = cva( ) type TextareaValue = string | number -export type TextareaSize = NonNullable['size']> -export type TextareaChangeEventDetails = BaseFieldNS.Control.ChangeEventDetails +type TextareaChangeEventDetails = BaseFieldNS.Control.ChangeEventDetails type TextareaOnValueChange = (value: string, eventDetails: TextareaChangeEventDetails) => void type ControlledTextareaProps = { @@ -73,7 +72,7 @@ type FieldControlTextareaProps = Omit< 'className' | 'defaultValue' | 'onValueChange' | 'render' | 'value' > -export type TextareaProps = TextareaElementProps & +type TextareaProps = TextareaElementProps & TextareaOnlyProps & TextareaControlProps & TextareaVariantProps & { @@ -81,7 +80,7 @@ export type TextareaProps = TextareaElementProps & className?: string } -export function Textarea({ +function Textarea({ className, cols, defaultValue, @@ -108,3 +107,7 @@ export function Textarea({ /> ) } + +export { Textarea } + +export type { TextareaProps } diff --git a/packages/dify-ui/src/toast/index.tsx b/packages/dify-ui/src/toast/index.tsx index 3fa8d3b9f04..6e3637dbcf5 100644 --- a/packages/dify-ui/src/toast/index.tsx +++ b/packages/dify-ui/src/toast/index.tsx @@ -141,7 +141,7 @@ function promiseToast(promiseValue: Promise, options: ToastPromise return toastManager.promise(promiseValue, options) } -export const toast: ToastApi = Object.assign(showToast, { +const toast: ToastApi = Object.assign(showToast, { success: createTypedToast('success'), error: createTypedToast('error'), warning: createTypedToast('warning'), @@ -251,7 +251,7 @@ function ToastViewport() { ) } -export function ToastHost({ timeout, limit }: ToastHostProps) { +function ToastHost({ timeout, limit }: ToastHostProps) { return ( @@ -260,3 +260,7 @@ export function ToastHost({ timeout, limit }: ToastHostProps) { ) } + +export { toast, ToastHost } + +export type { ToastHostProps } diff --git a/packages/dify-ui/src/tooltip/index.stories.tsx b/packages/dify-ui/src/tooltip/index.stories.tsx index 379d6336935..67b41890e44 100644 --- a/packages/dify-ui/src/tooltip/index.stories.tsx +++ b/packages/dify-ui/src/tooltip/index.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite' -import type { Placement } from '.' +import type { TooltipContentProps } from '.' import * as React from 'react' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '.' @@ -91,7 +91,9 @@ export const KeyboardShortcut: Story = { ), } -const PLACEMENTS: Placement[] = [ +type TooltipPlacement = NonNullable + +const PLACEMENTS: TooltipPlacement[] = [ 'top-start', 'top', 'top-end', @@ -107,7 +109,7 @@ const PLACEMENTS: Placement[] = [ ] const PlacementsDemo = () => { - const [placement, setPlacement] = React.useState('top') + const [placement, setPlacement] = React.useState('top') return (
      diff --git a/packages/dify-ui/src/tooltip/index.tsx b/packages/dify-ui/src/tooltip/index.tsx index cd65da1727b..f0aa439a091 100644 --- a/packages/dify-ui/src/tooltip/index.tsx +++ b/packages/dify-ui/src/tooltip/index.tsx @@ -6,8 +6,6 @@ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip' import { cn } from '../cn' import { parsePlacement } from '../placement' -export type { Placement } - /** * Tooltip is an **ephemeral hint** tied to a trigger (typically an icon button, * badge, or short label). It follows Base UI's Tooltip semantics: @@ -27,9 +25,13 @@ export type { Placement } * * If you need interactive affordances (buttons, links, forms) use `Popover`. */ -export const TooltipProvider = BaseTooltip.Provider -export const Tooltip = BaseTooltip.Root -export const TooltipTrigger = BaseTooltip.Trigger +const TooltipProvider = BaseTooltip.Provider +const Tooltip = BaseTooltip.Root +const TooltipTrigger = BaseTooltip.Trigger + +type TooltipProviderProps = BaseTooltip.Provider.Props +type TooltipProps = BaseTooltip.Root.Props +type TooltipTriggerProps = BaseTooltip.Trigger.Props type TooltipContentProps = { children: React.ReactNode @@ -40,7 +42,7 @@ type TooltipContentProps = { className?: string } & Omit -export function TooltipContent({ +function TooltipContent({ children, placement = 'top', sideOffset = 8, @@ -74,3 +76,7 @@ export function TooltipContent({ ) } + +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } + +export type { TooltipContentProps, TooltipProps, TooltipProviderProps, TooltipTriggerProps } diff --git a/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx b/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx index 8d415f1eaf8..cd116c1a75d 100644 --- a/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx +++ b/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx @@ -1,4 +1,3 @@ -import type { ButtonProps } from '@langgenius/dify-ui/button' import type { FormData } from './form' import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer' import type { UserAction } from '@/app/components/workflow/nodes/human-input/types' @@ -99,7 +98,7 @@ const LoadedFormContent = ({ ))} diff --git a/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx b/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx index 7fc1e94049c..6579ec1df1f 100644 --- a/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx +++ b/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx @@ -1,5 +1,4 @@ 'use client' -import type { ButtonProps } from '@langgenius/dify-ui/button' import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer' import type { UserAction } from '@/app/components/workflow/nodes/human-input/types' import type { HumanInputFormData } from '@/types/workflow' @@ -85,7 +84,7 @@ const FormContent = ({ nodeName, data, showBackButton, handleBack, onSubmit }: P
      - + + value={value} + onValueChange={(nextValue) => onChange?.(nextValue)} + > {items.map((item) => ( - key={item.value} value={item.value} closeOnClick diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/advanced/env.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/advanced/env.tsx index eb67e95dc7a..76df4c1b7de 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/advanced/env.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/advanced/env.tsx @@ -101,12 +101,12 @@ function EnvEditorScope({ } return ( -