mirror of
https://github.com/langgenius/dify.git
synced 2026-07-31 17:29:37 +08:00
refactor(dify-ui): govern public component APIs (#39806)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
3aa937def4
commit
563cf2602f
@ -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
|
||||
|
||||
@ -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/<primitive>/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<Value, Multiple>`, `RadioGroup<Value>`, `Radio<Value>`, and `RadioItem<Value>` 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
|
||||
<RadioGroup<PromptMode> 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: `<Combobox<Subject, true> 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
|
||||
<Combobox<Subject, true> multiple value={subjects} onValueChange={setSubjects}>
|
||||
<ComboboxValue<Subject, true>>
|
||||
{(selectedSubjects) => selectedSubjects?.map((subject) => subject.name).join(', ') ?? 'Anyone'}
|
||||
</ComboboxValue>
|
||||
<ComboboxList<Subject>>
|
||||
{(subject) => <ComboboxItem value={subject}>{subject.name}</ComboboxItem>}
|
||||
</ComboboxList>
|
||||
</Combobox>
|
||||
```
|
||||
|
||||
`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 `<Value>` 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.
|
||||
|
||||
|
||||
@ -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<Payload = unknown> = BaseAlertDialog.Root.Props<Payload>
|
||||
type AlertDialogTriggerProps<Payload = unknown> = BaseAlertDialog.Trigger.Props<Payload>
|
||||
type AlertDialogTitleProps = BaseAlertDialog.Title.Props
|
||||
type AlertDialogDescriptionProps = BaseAlertDialog.Description.Props
|
||||
|
||||
type AlertDialogContentProps = {
|
||||
children: React.ReactNode
|
||||
@ -19,7 +24,7 @@ type AlertDialogContentProps = {
|
||||
backdropProps?: Omit<BaseAlertDialog.Backdrop.Props, 'className'>
|
||||
}
|
||||
|
||||
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 (
|
||||
<div
|
||||
className={cn('flex items-start justify-end gap-2 self-stretch p-6', className)}
|
||||
@ -60,7 +65,7 @@ type AlertDialogCancelButtonProps = Omit<ButtonProps, 'children'> & {
|
||||
closeProps?: Omit<BaseAlertDialog.Close.Props, 'children' | 'render'>
|
||||
}
|
||||
|
||||
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 <Button variant={variant} tone={tone} {...props} />
|
||||
}
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
}
|
||||
|
||||
export type {
|
||||
AlertDialogActionsProps,
|
||||
AlertDialogCancelButtonProps,
|
||||
AlertDialogConfirmButtonProps,
|
||||
AlertDialogContentProps,
|
||||
AlertDialogDescriptionProps,
|
||||
AlertDialogProps,
|
||||
AlertDialogTitleProps,
|
||||
AlertDialogTriggerProps,
|
||||
}
|
||||
|
||||
@ -245,8 +245,8 @@ describe('Autocomplete wrappers', () => {
|
||||
<AutocompleteInput aria-label="Search resources" />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: string) => (
|
||||
<AutocompleteList<string>>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
<AutocompleteItemText>{item}</AutocompleteItemText>
|
||||
</AutocompleteItem>
|
||||
|
||||
@ -370,8 +370,8 @@ const BasicTagAutocomplete = ({ size = 'medium' }: { size?: 'small' | 'medium' |
|
||||
<AutocompleteTrigger size={size} />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
<AutocompleteEmpty>No tag suggestion. Keep the typed value.</AutocompleteEmpty>
|
||||
</AutocompleteContent>
|
||||
@ -387,8 +387,8 @@ const GroupedSuggestionList = () => {
|
||||
<AutocompleteGroup key={group.label} items={group.items}>
|
||||
{groupIndex > 0 && <AutocompleteSeparator />}
|
||||
<AutocompleteGroupLabel>{group.label}</AutocompleteGroupLabel>
|
||||
<AutocompleteCollection>
|
||||
{(item: Suggestion) => <SuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteCollection<Suggestion>>
|
||||
{(item) => <SuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteCollection>
|
||||
</AutocompleteGroup>
|
||||
))}
|
||||
@ -408,8 +408,8 @@ const CommandPaletteList = () => {
|
||||
>
|
||||
{group.label}
|
||||
</AutocompleteGroupLabel>
|
||||
<AutocompleteCollection>
|
||||
{(item: Suggestion) => (
|
||||
<AutocompleteCollection<Suggestion>>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item} className="grid grid-cols-[1fr_auto]">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
{item.icon && (
|
||||
@ -516,8 +516,8 @@ const AsyncSearchDemo = () => {
|
||||
popupProps={{ 'aria-busy': isPending || undefined }}
|
||||
>
|
||||
<AutocompleteStatus>{status}</AutocompleteStatus>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <SuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <SuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
@ -643,8 +643,8 @@ const FuzzyMatchingDemo = () => {
|
||||
<AutocompleteTrigger />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => (
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item}>
|
||||
{item.icon && (
|
||||
<span
|
||||
@ -730,8 +730,8 @@ export const InlineAutocomplete: Story = {
|
||||
<AutocompleteTrigger />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <SuggestionItem key={item.value} item={item} dense />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <SuggestionItem key={item.value} item={item} dense />}
|
||||
</AutocompleteList>
|
||||
<AutocompleteEmpty>No inline completion. Continue typing freely.</AutocompleteEmpty>
|
||||
</AutocompleteContent>
|
||||
@ -800,8 +800,8 @@ export const LimitResults: Story = {
|
||||
<AutocompleteStatus className="border-b border-divider-subtle">
|
||||
<LimitedStatus total={workflowSuggestions.length} />
|
||||
</AutocompleteStatus>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <SuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <SuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
<AutocompleteEmpty>No suggestion. Submit the typed text instead.</AutocompleteEmpty>
|
||||
</AutocompleteContent>
|
||||
@ -908,8 +908,8 @@ export const Empty: Story = {
|
||||
<AutocompleteTrigger />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
<AutocompleteEmpty>No tag suggestion. The custom text remains valid.</AutocompleteEmpty>
|
||||
</AutocompleteContent>
|
||||
@ -934,8 +934,8 @@ export const DisabledAndReadOnly: Story = {
|
||||
<AutocompleteTrigger />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <TagSuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
@ -952,8 +952,8 @@ export const DisabledAndReadOnly: Story = {
|
||||
<AutocompleteTrigger />
|
||||
</AutocompleteInputGroup>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteList>
|
||||
{(item: Suggestion) => <SuggestionItem key={item.value} item={item} />}
|
||||
<AutocompleteList<Suggestion>>
|
||||
{(item) => <SuggestionItem key={item.value} item={item} />}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
|
||||
@ -15,36 +15,52 @@ import {
|
||||
} from '../overlay-shared'
|
||||
import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export type AutocompleteProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
export type AutocompleteGroupedProps<Items extends readonly { items: readonly unknown[] }[]> = Omit<
|
||||
type AutocompleteProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
type AutocompleteChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
|
||||
type AutocompleteGroupedProps<Items extends readonly { items: readonly unknown[] }[]> = Omit<
|
||||
AutocompleteProps<Items[number]['items'][number]>,
|
||||
'items'
|
||||
> & {
|
||||
items: Items
|
||||
}
|
||||
export type AutocompleteFlatProps<ItemValue> = Omit<AutocompleteProps<ItemValue>, 'items'> & {
|
||||
type AutocompleteFlatProps<ItemValue> = Omit<AutocompleteProps<ItemValue>, 'items'> & {
|
||||
items?: readonly ItemValue[]
|
||||
}
|
||||
|
||||
export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
|
||||
function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
|
||||
props: AutocompleteGroupedProps<Items>,
|
||||
): React.JSX.Element
|
||||
export function Autocomplete<ItemValue>(props: AutocompleteFlatProps<ItemValue>): React.JSX.Element
|
||||
export function Autocomplete(props: AutocompleteProps<unknown>): React.JSX.Element {
|
||||
function Autocomplete<ItemValue>(props: AutocompleteFlatProps<ItemValue>): React.JSX.Element
|
||||
function Autocomplete(props: AutocompleteProps<unknown>): React.JSX.Element {
|
||||
return <BaseAutocomplete.Root {...props} />
|
||||
}
|
||||
|
||||
export const AutocompleteValue = BaseAutocomplete.Value
|
||||
export const AutocompleteGroup = BaseAutocomplete.Group
|
||||
export const AutocompleteCollection = BaseAutocomplete.Collection
|
||||
export const AutocompleteRow = BaseAutocomplete.Row
|
||||
export const useAutocompleteFilter = BaseAutocomplete.useFilter
|
||||
export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
|
||||
const AutocompleteValue = BaseAutocomplete.Value
|
||||
const AutocompleteRow = BaseAutocomplete.Row
|
||||
const useAutocompleteFilter = BaseAutocomplete.useFilter
|
||||
const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
|
||||
|
||||
export type AutocompleteChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
|
||||
export type AutocompleteHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
|
||||
type AutocompleteValueProps = BaseAutocomplete.Value.Props
|
||||
type AutocompleteRowProps = BaseAutocomplete.Row.Props
|
||||
|
||||
type AutocompleteGroupProps<Value = unknown> = Omit<BaseAutocomplete.Group.Props, 'items'> & {
|
||||
items?: readonly Value[]
|
||||
}
|
||||
|
||||
function AutocompleteGroup<Value = unknown>(props: AutocompleteGroupProps<Value>) {
|
||||
return <BaseAutocomplete.Group {...props} />
|
||||
}
|
||||
|
||||
type AutocompleteCollectionProps<Value = unknown> = Omit<
|
||||
BaseAutocomplete.Collection.Props,
|
||||
'children'
|
||||
> & {
|
||||
children: (item: Value, index: number) => React.ReactNode
|
||||
}
|
||||
|
||||
function AutocompleteCollection<Value = unknown>(props: AutocompleteCollectionProps<Value>) {
|
||||
return <BaseAutocomplete.Collection {...props} />
|
||||
}
|
||||
|
||||
const autocompletePopupClassName = [
|
||||
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
@ -88,14 +104,10 @@ const autocompleteInputGroupVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type AutocompleteSize = NonNullable<
|
||||
VariantProps<typeof autocompleteInputGroupVariants>['size']
|
||||
>
|
||||
type AutocompleteInputGroupProps = Omit<BaseAutocomplete.InputGroup.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteInputGroupVariants> & { className?: string }
|
||||
|
||||
export type AutocompleteInputGroupProps = BaseAutocomplete.InputGroup.Props &
|
||||
VariantProps<typeof autocompleteInputGroupVariants>
|
||||
|
||||
export function AutocompleteInputGroup({
|
||||
function AutocompleteInputGroup({
|
||||
className,
|
||||
size = 'medium',
|
||||
...props
|
||||
@ -129,10 +141,10 @@ const autocompleteInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type AutocompleteInputProps = Omit<BaseAutocomplete.Input.Props, 'size'> &
|
||||
VariantProps<typeof autocompleteInputVariants>
|
||||
type AutocompleteInputProps = Omit<BaseAutocomplete.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof autocompleteInputVariants> & { className?: string }
|
||||
|
||||
export function AutocompleteInput({
|
||||
function AutocompleteInput({
|
||||
className,
|
||||
size = 'medium',
|
||||
type = 'text',
|
||||
@ -173,16 +185,16 @@ const autocompleteControlVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type AutocompleteControlProps = Omit<BaseAutocomplete.Trigger.Props, 'className'> &
|
||||
type AutocompleteTriggerProps = Omit<BaseAutocomplete.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteControlVariants> & { className?: string }
|
||||
|
||||
export function AutocompleteTrigger({
|
||||
function AutocompleteTrigger({
|
||||
className,
|
||||
children,
|
||||
size = 'medium',
|
||||
type = 'button',
|
||||
...props
|
||||
}: AutocompleteControlProps) {
|
||||
}: AutocompleteTriggerProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Trigger
|
||||
type={type}
|
||||
@ -198,10 +210,10 @@ export function AutocompleteTrigger({
|
||||
)
|
||||
}
|
||||
|
||||
export type AutocompleteClearProps = Omit<BaseAutocomplete.Clear.Props, 'className'> &
|
||||
type AutocompleteClearProps = Omit<BaseAutocomplete.Clear.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteControlVariants> & { className?: string }
|
||||
|
||||
export function AutocompleteClear({
|
||||
function AutocompleteClear({
|
||||
className,
|
||||
children,
|
||||
size = 'medium',
|
||||
@ -226,7 +238,11 @@ export function AutocompleteClear({
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteIcon({ className, children, ...props }: BaseAutocomplete.Icon.Props) {
|
||||
type AutocompleteIconProps = Omit<BaseAutocomplete.Icon.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteIcon({ className, children, ...props }: AutocompleteIconProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Icon
|
||||
className={cn('flex shrink-0 items-center text-text-tertiary', className)}
|
||||
@ -252,7 +268,7 @@ type AutocompleteContentProps = {
|
||||
popupProps?: Omit<BaseAutocomplete.Popup.Props, 'children' | 'className'>
|
||||
}
|
||||
|
||||
export function AutocompleteContent({
|
||||
function AutocompleteContent({
|
||||
children,
|
||||
placement = 'bottom-start',
|
||||
sideOffset = 4,
|
||||
@ -290,23 +306,43 @@ export function AutocompleteContent({
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteList({ className, ...props }: BaseAutocomplete.List.Props) {
|
||||
type AutocompleteListProps<Value = unknown> = Omit<
|
||||
BaseAutocomplete.List.Props,
|
||||
'children' | 'className'
|
||||
> & {
|
||||
children?: React.ReactNode | ((item: Value, index: number) => React.ReactNode)
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteList<Value = unknown>({ className, ...props }: AutocompleteListProps<Value>) {
|
||||
return <BaseAutocomplete.List className={cn(autocompleteListClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function AutocompleteItem({ className, ...props }: BaseAutocomplete.Item.Props) {
|
||||
type AutocompleteItemProps<Value = unknown> = Omit<
|
||||
BaseAutocomplete.Item.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function AutocompleteItem<Value = unknown>({ className, ...props }: AutocompleteItemProps<Value>) {
|
||||
return <BaseAutocomplete.Item className={cn(autocompleteItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export type AutocompleteItemTextProps = React.ComponentProps<'span'>
|
||||
type AutocompleteItemTextProps = React.ComponentProps<'span'>
|
||||
|
||||
export function AutocompleteItemText({ className, ...props }: AutocompleteItemTextProps) {
|
||||
function AutocompleteItemText({ className, ...props }: AutocompleteItemTextProps) {
|
||||
return (
|
||||
<span className={cn('min-w-0 grow truncate px-1 system-sm-medium', className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteGroupLabel({ className, ...props }: BaseAutocomplete.GroupLabel.Props) {
|
||||
type AutocompleteGroupLabelProps = Omit<BaseAutocomplete.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteGroupLabel({ className, ...props }: AutocompleteGroupLabelProps) {
|
||||
return (
|
||||
<BaseAutocomplete.GroupLabel
|
||||
className={cn(floatingGroupLabelClassName, className)}
|
||||
@ -315,13 +351,21 @@ export function AutocompleteGroupLabel({ className, ...props }: BaseAutocomplete
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteSeparator({ className, ...props }: BaseAutocomplete.Separator.Props) {
|
||||
type AutocompleteSeparatorProps = Omit<BaseAutocomplete.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteSeparator({ className, ...props }: AutocompleteSeparatorProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteEmpty({ className, ...props }: BaseAutocomplete.Empty.Props) {
|
||||
type AutocompleteEmptyProps = Omit<BaseAutocomplete.Empty.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteEmpty({ className, ...props }: AutocompleteEmptyProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Empty
|
||||
className={cn(
|
||||
@ -333,7 +377,11 @@ export function AutocompleteEmpty({ className, ...props }: BaseAutocomplete.Empt
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteStatus({ className, ...props }: BaseAutocomplete.Status.Props) {
|
||||
type AutocompleteStatusProps = Omit<BaseAutocomplete.Status.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function AutocompleteStatus({ className, ...props }: AutocompleteStatusProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Status
|
||||
className={cn('px-3 py-2 system-sm-regular text-text-tertiary', className)}
|
||||
@ -342,14 +390,65 @@ export function AutocompleteStatus({ className, ...props }: BaseAutocomplete.Sta
|
||||
)
|
||||
}
|
||||
|
||||
export function AutocompleteItemIndicator({
|
||||
function AutocompleteItemIndicator({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<'span'>) {
|
||||
}: AutocompleteItemIndicatorProps) {
|
||||
return (
|
||||
<span className={cn(floatingItemIndicatorClassName, className)} {...props}>
|
||||
{children ?? <span className="i-ri-arrow-right-line size-4" aria-hidden="true" />}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteItemIndicatorProps = React.ComponentProps<'span'>
|
||||
|
||||
export {
|
||||
Autocomplete,
|
||||
AutocompleteClear,
|
||||
AutocompleteCollection,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteGroup,
|
||||
AutocompleteGroupLabel,
|
||||
AutocompleteIcon,
|
||||
AutocompleteInput,
|
||||
AutocompleteInputGroup,
|
||||
AutocompleteItem,
|
||||
AutocompleteItemIndicator,
|
||||
AutocompleteItemText,
|
||||
AutocompleteList,
|
||||
AutocompleteRow,
|
||||
AutocompleteSeparator,
|
||||
AutocompleteStatus,
|
||||
AutocompleteTrigger,
|
||||
AutocompleteValue,
|
||||
useAutocompleteFilter,
|
||||
useAutocompleteFilteredItems,
|
||||
}
|
||||
|
||||
export type {
|
||||
AutocompleteChangeEventDetails,
|
||||
AutocompleteClearProps,
|
||||
AutocompleteCollectionProps,
|
||||
AutocompleteContentProps,
|
||||
AutocompleteEmptyProps,
|
||||
AutocompleteFlatProps,
|
||||
AutocompleteGroupedProps,
|
||||
AutocompleteGroupLabelProps,
|
||||
AutocompleteGroupProps,
|
||||
AutocompleteIconProps,
|
||||
AutocompleteInputGroupProps,
|
||||
AutocompleteInputProps,
|
||||
AutocompleteItemIndicatorProps,
|
||||
AutocompleteItemProps,
|
||||
AutocompleteItemTextProps,
|
||||
AutocompleteListProps,
|
||||
AutocompleteProps,
|
||||
AutocompleteRowProps,
|
||||
AutocompleteSeparatorProps,
|
||||
AutocompleteStatusProps,
|
||||
AutocompleteTriggerProps,
|
||||
AutocompleteValueProps,
|
||||
}
|
||||
|
||||
@ -15,9 +15,9 @@ const avatarSizeClasses = {
|
||||
'3xl': { root: 'size-16', text: 'text-2xl' },
|
||||
} as const
|
||||
|
||||
export type AvatarSize = keyof typeof avatarSizeClasses
|
||||
type AvatarSize = keyof typeof avatarSizeClasses
|
||||
|
||||
export type AvatarProps = {
|
||||
type AvatarProps = {
|
||||
name: string
|
||||
avatar: string | null
|
||||
size?: AvatarSize
|
||||
@ -25,11 +25,11 @@ export type AvatarProps = {
|
||||
onLoadingStatusChange?: (status: ImageLoadingStatus) => void
|
||||
}
|
||||
|
||||
type AvatarRootProps = BaseAvatar.Root.Props & {
|
||||
type AvatarRootProps = Omit<BaseAvatar.Root.Props, 'className'> & {
|
||||
size?: AvatarSize
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AvatarRoot({ size = 'md', className, ...props }: AvatarRootProps) {
|
||||
function AvatarRoot({ size = 'md', className, ...props }: AvatarRootProps) {
|
||||
return (
|
||||
<BaseAvatar.Root
|
||||
className={cn(
|
||||
@ -42,11 +42,11 @@ export function AvatarRoot({ size = 'md', className, ...props }: AvatarRootProps
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarFallbackProps = BaseAvatar.Fallback.Props & {
|
||||
type AvatarFallbackProps = Omit<BaseAvatar.Fallback.Props, 'className'> & {
|
||||
size?: AvatarSize
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AvatarFallback({ size = 'md', className, ...props }: AvatarFallbackProps) {
|
||||
function AvatarFallback({ size = 'md', className, ...props }: AvatarFallbackProps) {
|
||||
return (
|
||||
<BaseAvatar.Fallback
|
||||
className={cn(
|
||||
@ -59,9 +59,10 @@ export function AvatarFallback({ size = 'md', className, ...props }: AvatarFallb
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarImageProps = BaseAvatar.Image.Props
|
||||
|
||||
export function AvatarImage({ className, ...props }: AvatarImageProps) {
|
||||
type AvatarImageProps = Omit<BaseAvatar.Image.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
function AvatarImage({ className, ...props }: AvatarImageProps) {
|
||||
return (
|
||||
<BaseAvatar.Image
|
||||
className={cn('absolute inset-0 size-full object-cover', className)}
|
||||
@ -70,13 +71,7 @@ export function AvatarImage({ className, ...props }: AvatarImageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export const Avatar = ({
|
||||
name,
|
||||
avatar,
|
||||
size = 'md',
|
||||
className,
|
||||
onLoadingStatusChange,
|
||||
}: AvatarProps) => {
|
||||
const Avatar = ({ name, avatar, size = 'md', className, onLoadingStatusChange }: AvatarProps) => {
|
||||
return (
|
||||
<AvatarRoot size={size} className={className}>
|
||||
{avatar && (
|
||||
@ -86,3 +81,7 @@ export const Avatar = ({
|
||||
</AvatarRoot>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarFallback, AvatarImage, AvatarRoot }
|
||||
|
||||
export type { AvatarFallbackProps, AvatarImageProps, AvatarProps, AvatarRootProps, AvatarSize }
|
||||
|
||||
@ -6,7 +6,7 @@ import { Button as BaseButton } from '@base-ui/react/button'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
const buttonVariants = cva(
|
||||
'inline-flex cursor-pointer items-center justify-center whitespace-nowrap outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid data-disabled:cursor-not-allowed',
|
||||
{
|
||||
variants: {
|
||||
@ -100,13 +100,13 @@ export const buttonVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type ButtonProps = Omit<BaseButtonNS.Props, 'className'> &
|
||||
type ButtonProps = Omit<BaseButtonNS.Props, 'className'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
loading?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Button({
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
@ -136,3 +136,7 @@ export function Button({
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
||||
export type { ButtonProps }
|
||||
|
||||
@ -3,8 +3,12 @@
|
||||
import type { CheckboxGroup as BaseCheckboxGroupNS } from '@base-ui/react/checkbox-group'
|
||||
import { CheckboxGroup as BaseCheckboxGroup } from '@base-ui/react/checkbox-group'
|
||||
|
||||
export type CheckboxGroupProps = BaseCheckboxGroupNS.Props
|
||||
type CheckboxGroupProps = BaseCheckboxGroupNS.Props
|
||||
|
||||
export function CheckboxGroup(props: CheckboxGroupProps) {
|
||||
function CheckboxGroup(props: CheckboxGroupProps) {
|
||||
return <BaseCheckboxGroup {...props} />
|
||||
}
|
||||
|
||||
export { CheckboxGroup }
|
||||
|
||||
export type { CheckboxGroupProps }
|
||||
|
||||
@ -25,22 +25,17 @@ const checkboxIndicatorClassName =
|
||||
|
||||
const checkboxSkeletonClassName = 'size-4 shrink-0 rounded-sm bg-text-quaternary opacity-20'
|
||||
|
||||
export type CheckboxRootProps = Omit<BaseCheckboxNS.Root.Props, 'className'> & {
|
||||
type CheckboxRootProps = Omit<BaseCheckboxNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CheckboxRoot({ className, ...props }: CheckboxRootProps) {
|
||||
function CheckboxRoot({ className, ...props }: CheckboxRootProps) {
|
||||
return <BaseCheckbox.Root className={cn(checkboxRootClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export type CheckboxIndicatorProps = Omit<
|
||||
BaseCheckboxNS.Indicator.Props,
|
||||
'className' | 'children'
|
||||
> & {
|
||||
type CheckboxIndicatorProps = Omit<BaseCheckboxNS.Indicator.Props, 'className' | 'children'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CheckboxIndicator({ className, render, ...props }: CheckboxIndicatorProps) {
|
||||
function CheckboxIndicator({ className, render, ...props }: CheckboxIndicatorProps) {
|
||||
return (
|
||||
<BaseCheckbox.Indicator
|
||||
className={cn(checkboxIndicatorClassName, className)}
|
||||
@ -61,9 +56,9 @@ export function CheckboxIndicator({ className, render, ...props }: CheckboxIndic
|
||||
)
|
||||
}
|
||||
|
||||
export type CheckboxProps = Omit<CheckboxRootProps, 'children'>
|
||||
type CheckboxProps = Omit<CheckboxRootProps, 'children'>
|
||||
|
||||
export function Checkbox({ ...props }: CheckboxProps) {
|
||||
function Checkbox({ ...props }: CheckboxProps) {
|
||||
return (
|
||||
<CheckboxRoot {...props}>
|
||||
<CheckboxIndicator />
|
||||
@ -71,10 +66,14 @@ export function Checkbox({ ...props }: CheckboxProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type CheckboxSkeletonProps = Omit<React.ComponentProps<'div'>, 'className'> & {
|
||||
type CheckboxSkeletonProps = Omit<React.ComponentProps<'div'>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CheckboxSkeleton({ className, ...props }: CheckboxSkeletonProps) {
|
||||
function CheckboxSkeleton({ className, ...props }: CheckboxSkeletonProps) {
|
||||
return <div className={cn(checkboxSkeletonClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export { Checkbox, CheckboxIndicator, CheckboxRoot, CheckboxSkeleton }
|
||||
|
||||
export type { CheckboxIndicatorProps, CheckboxProps, CheckboxRootProps, CheckboxSkeletonProps }
|
||||
|
||||
@ -2,6 +2,8 @@ import type { ClassValue } from 'clsx'
|
||||
import { clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export { cn }
|
||||
|
||||
@ -4,19 +4,17 @@ import type { Collapsible as BaseCollapsibleNS } from '@base-ui/react/collapsibl
|
||||
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export type CollapsibleProps = Omit<BaseCollapsibleNS.Root.Props, 'className'> & {
|
||||
type CollapsibleProps = Omit<BaseCollapsibleNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Collapsible({ className, ...props }: CollapsibleProps) {
|
||||
function Collapsible({ className, ...props }: CollapsibleProps) {
|
||||
return <BaseCollapsible.Root className={cn('flex min-w-0 flex-col', className)} {...props} />
|
||||
}
|
||||
|
||||
export type CollapsibleTriggerProps = Omit<BaseCollapsibleNS.Trigger.Props, 'className'> & {
|
||||
type CollapsibleTriggerProps = Omit<BaseCollapsibleNS.Trigger.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CollapsibleTrigger({ className, ...props }: CollapsibleTriggerProps) {
|
||||
function CollapsibleTrigger({ className, ...props }: CollapsibleTriggerProps) {
|
||||
return (
|
||||
<BaseCollapsible.Trigger
|
||||
className={cn(
|
||||
@ -32,11 +30,10 @@ export function CollapsibleTrigger({ className, ...props }: CollapsibleTriggerPr
|
||||
)
|
||||
}
|
||||
|
||||
export type CollapsiblePanelProps = Omit<BaseCollapsibleNS.Panel.Props, 'className'> & {
|
||||
type CollapsiblePanelProps = Omit<BaseCollapsibleNS.Panel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CollapsiblePanel({ className, ...props }: CollapsiblePanelProps) {
|
||||
function CollapsiblePanel({ className, ...props }: CollapsiblePanelProps) {
|
||||
return (
|
||||
<BaseCollapsible.Panel
|
||||
className={cn(
|
||||
@ -49,3 +46,7 @@ export function CollapsiblePanel({ className, ...props }: CollapsiblePanelProps)
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Collapsible, CollapsiblePanel, CollapsibleTrigger }
|
||||
|
||||
export type { CollapsiblePanelProps, CollapsibleProps, CollapsibleTriggerProps }
|
||||
|
||||
@ -265,8 +265,8 @@ describe('Combobox wrappers', () => {
|
||||
<ComboboxInput aria-label="Search resources" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>
|
||||
{(item: string) => (
|
||||
<ComboboxList<string>>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item} value={item}>
|
||||
<ComboboxItemText>{item}</ComboboxItemText>
|
||||
<ComboboxItemIndicator />
|
||||
@ -300,15 +300,32 @@ describe('Combobox wrappers', () => {
|
||||
})
|
||||
|
||||
describe('Multiple selection chips', () => {
|
||||
it('should expose a controlled null value to a typed multiple value renderer', async () => {
|
||||
const screen = await renderWithSafeViewport(
|
||||
<Combobox<string, true> multiple value={null}>
|
||||
<ComboboxInputGroup>
|
||||
<ComboboxValue<string, true>>
|
||||
{(selectedValue) =>
|
||||
selectedValue === null ? 'No reviewers selected' : selectedValue.join(', ')
|
||||
}
|
||||
</ComboboxValue>
|
||||
<ComboboxInput aria-label="Reviewers" />
|
||||
</ComboboxInputGroup>
|
||||
</Combobox>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByText('No reviewers selected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render chip wrappers and default remove button label', async () => {
|
||||
const screen = await renderWithSafeViewport(
|
||||
<Combobox multiple defaultValue={['maya']} items={['maya', 'nora']}>
|
||||
<ComboboxInputGroup>
|
||||
<ComboboxChips className="custom-chips" data-testid="chips">
|
||||
<ComboboxValue>
|
||||
{(selectedValue: string[]) => (
|
||||
<ComboboxValue<string, true>>
|
||||
{(selectedValue) => (
|
||||
<React.Fragment>
|
||||
{selectedValue.map((item) => (
|
||||
{selectedValue?.map((item) => (
|
||||
<ComboboxChip key={item} className="custom-chip">
|
||||
<span>{item}</span>
|
||||
<ComboboxChipRemove data-testid="remove-chip" />
|
||||
@ -337,10 +354,10 @@ describe('Combobox wrappers', () => {
|
||||
<Combobox multiple defaultValue={['maya']} items={['maya']}>
|
||||
<ComboboxInputGroup>
|
||||
<ComboboxChips>
|
||||
<ComboboxValue>
|
||||
{(selectedValue: string[]) => (
|
||||
<ComboboxValue<string, true>>
|
||||
{(selectedValue) => (
|
||||
<React.Fragment>
|
||||
{selectedValue.map((item) => (
|
||||
{selectedValue?.map((item) => (
|
||||
<ComboboxChip key={item}>
|
||||
<span id="remove-maya">Remove Maya</span>
|
||||
<ComboboxChipRemove aria-labelledby="remove-maya" />
|
||||
|
||||
@ -362,7 +362,7 @@ const GroupedToolList = () => {
|
||||
<ComboboxGroup key={group.label} items={group.items}>
|
||||
{groupIndex > 0 && <ComboboxSeparator />}
|
||||
<ComboboxGroupLabel>{group.label}</ComboboxGroupLabel>
|
||||
<ComboboxCollection>{(option: Option) => renderOptionItem(option)}</ComboboxCollection>
|
||||
<ComboboxCollection<Option>>{(option) => renderOptionItem(option)}</ComboboxCollection>
|
||||
</ComboboxGroup>
|
||||
))}
|
||||
</ComboboxList>
|
||||
@ -560,7 +560,7 @@ const AsyncDirectoryDemo = () => {
|
||||
popupProps={{ 'aria-busy': isPending || undefined }}
|
||||
>
|
||||
<ComboboxStatus className="border-b border-divider-subtle">{status}</ComboboxStatus>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxEmpty>{emptyMessage}</ComboboxEmpty>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
@ -671,17 +671,17 @@ const AsyncReviewerDemo = () => {
|
||||
>
|
||||
<ComboboxInputGroup className="h-auto min-h-8 items-start py-1">
|
||||
<ComboboxChips>
|
||||
<ComboboxValue>
|
||||
{(selectedValue: Option[]) => (
|
||||
<ComboboxValue<Option, true>>
|
||||
{(selectedValue) => (
|
||||
<React.Fragment>
|
||||
{selectedValue.map((item) => (
|
||||
{selectedValue?.map((item) => (
|
||||
<ComboboxChip key={item.value} aria-label={item.label}>
|
||||
<span className="max-w-32 truncate">{item.label}</span>
|
||||
<ComboboxChipRemove aria-label={`Remove ${item.label}`} />
|
||||
</ComboboxChip>
|
||||
))}
|
||||
<ComboboxInput
|
||||
placeholder={selectedValue.length ? '' : 'Search reviewers…'}
|
||||
placeholder={selectedValue?.length ? '' : 'Search reviewers…'}
|
||||
className="min-w-24 px-1 py-0.5"
|
||||
/>
|
||||
</React.Fragment>
|
||||
@ -694,7 +694,7 @@ const AsyncReviewerDemo = () => {
|
||||
popupProps={{ 'aria-busy': isPending || undefined }}
|
||||
>
|
||||
<ComboboxStatus className="border-b border-divider-subtle">{status}</ComboboxStatus>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxEmpty>{emptyMessage}</ComboboxEmpty>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
@ -741,7 +741,7 @@ export const Default: Story = {
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</Field>
|
||||
@ -766,7 +766,7 @@ export const FormField: Story = {
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>Type to filter, then choose a remembered data source.</FieldDescription>
|
||||
@ -784,7 +784,7 @@ export const CompactTriggerWithPopupSearch: Story = {
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search data sources" placeholder="Search sources" />
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
@ -816,7 +816,7 @@ export const Sizes: Story = {
|
||||
<ComboboxInputTrigger size={size} className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</Field>
|
||||
@ -851,17 +851,17 @@ const MultipleChipsDemo = () => {
|
||||
<Combobox items={reviewerOptions} multiple value={value} onValueChange={setValue}>
|
||||
<ComboboxInputGroup className="h-auto min-h-8 items-start py-1">
|
||||
<ComboboxChips>
|
||||
<ComboboxValue>
|
||||
{(selectedValue: Option[]) => (
|
||||
<ComboboxValue<Option, true>>
|
||||
{(selectedValue) => (
|
||||
<React.Fragment>
|
||||
{selectedValue.map((item) => (
|
||||
{selectedValue?.map((item) => (
|
||||
<ComboboxChip key={item.value}>
|
||||
<span className="max-w-32 truncate">{item.label}</span>
|
||||
<ComboboxChipRemove aria-label={`Remove ${item.label}`} />
|
||||
</ComboboxChip>
|
||||
))}
|
||||
<ComboboxInput
|
||||
placeholder={selectedValue.length ? '' : 'Assign reviewers…'}
|
||||
placeholder={selectedValue?.length ? '' : 'Assign reviewers…'}
|
||||
className="min-w-24 px-1 py-0.5"
|
||||
/>
|
||||
</React.Fragment>
|
||||
@ -870,7 +870,7 @@ const MultipleChipsDemo = () => {
|
||||
</ComboboxChips>
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>
|
||||
@ -917,7 +917,7 @@ export const EmptyAndStatus: Story = {
|
||||
<ComboboxContent>
|
||||
<ComboboxStatus>Search workspace connectors</ComboboxStatus>
|
||||
<ComboboxEmpty>No connectors found</ComboboxEmpty>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</Field>
|
||||
@ -935,7 +935,7 @@ export const DisabledAndReadOnly: Story = {
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search disabled providers" placeholder="Search providers" />
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</Field>
|
||||
@ -951,7 +951,7 @@ export const DisabledAndReadOnly: Story = {
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</Field>
|
||||
@ -972,7 +972,7 @@ const ControlledDemo = () => {
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search app tags" placeholder="Search tags" />
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
<ComboboxList<Option>>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
|
||||
@ -15,28 +15,59 @@ import {
|
||||
} from '../overlay-shared'
|
||||
import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export type ComboboxProps<
|
||||
type ComboboxProps<Value, Multiple extends boolean | undefined = false> = BaseCombobox.Root.Props<
|
||||
Value,
|
||||
Multiple extends boolean | undefined = false,
|
||||
> = BaseCombobox.Root.Props<Value, Multiple>
|
||||
Multiple
|
||||
> &
|
||||
([Multiple] extends [true] ? { multiple: true } : unknown)
|
||||
type ComboboxChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
|
||||
|
||||
export function Combobox<Value, Multiple extends boolean | undefined = false>(
|
||||
function Combobox<Value, Multiple extends boolean | undefined = false>(
|
||||
props: ComboboxProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseCombobox.Root {...props} />
|
||||
}
|
||||
|
||||
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<Value, Multiple extends boolean | undefined = false> =
|
||||
| (Multiple extends true ? Value[] : Value)
|
||||
| null
|
||||
|
||||
type ComboboxValueProps<Value = unknown, Multiple extends boolean | undefined = false> = Omit<
|
||||
BaseCombobox.Value.Props,
|
||||
'children'
|
||||
> & {
|
||||
children?:
|
||||
| React.ReactNode
|
||||
| ((selectedValue: ComboboxSelectedValue<Value, Multiple>) => React.ReactNode)
|
||||
}
|
||||
function ComboboxValue<Value = unknown, Multiple extends boolean | undefined = false>(
|
||||
props: ComboboxValueProps<Value, Multiple>,
|
||||
): React.JSX.Element
|
||||
function ComboboxValue(props: BaseCombobox.Value.Props): React.JSX.Element {
|
||||
return <BaseCombobox.Value {...props} />
|
||||
}
|
||||
|
||||
type ComboboxGroupProps<Value = unknown> = Omit<BaseCombobox.Group.Props, 'items'> & {
|
||||
items?: readonly Value[]
|
||||
}
|
||||
|
||||
function ComboboxGroup<Value = unknown>(props: ComboboxGroupProps<Value>) {
|
||||
return <BaseCombobox.Group {...props} />
|
||||
}
|
||||
|
||||
type ComboboxCollectionProps<Value = unknown> = Omit<BaseCombobox.Collection.Props, 'children'> & {
|
||||
children: (item: Value, index: number) => React.ReactNode
|
||||
}
|
||||
|
||||
function ComboboxCollection<Value = unknown>(props: ComboboxCollectionProps<Value>) {
|
||||
return <BaseCombobox.Collection {...props} />
|
||||
}
|
||||
|
||||
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<VariantProps<typeof comboboxTriggerVariants>['size']>
|
||||
|
||||
type ComboboxTriggerProps = Omit<BaseCombobox.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof comboboxTriggerVariants> & {
|
||||
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<typeof comboboxInputGroupVariants>
|
||||
type ComboboxInputGroupProps = Omit<BaseCombobox.InputGroup.Props, 'className'> &
|
||||
VariantProps<typeof comboboxInputGroupVariants> & { className?: string }
|
||||
|
||||
export function ComboboxInputGroup({
|
||||
className,
|
||||
size = 'medium',
|
||||
...props
|
||||
}: ComboboxInputGroupProps) {
|
||||
function ComboboxInputGroup({ className, size = 'medium', ...props }: ComboboxInputGroupProps) {
|
||||
return (
|
||||
<BaseCombobox.InputGroup
|
||||
className={cn(comboboxInputGroupVariants({ size }), className)}
|
||||
@ -176,10 +201,10 @@ const comboboxInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type ComboboxInputProps = Omit<BaseCombobox.Input.Props, 'size'> &
|
||||
VariantProps<typeof comboboxInputVariants>
|
||||
type ComboboxInputProps = Omit<BaseCombobox.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof comboboxInputVariants> & { className?: string }
|
||||
|
||||
export function ComboboxInput({
|
||||
function ComboboxInput({
|
||||
className,
|
||||
size = 'medium',
|
||||
type = 'text',
|
||||
@ -220,10 +245,10 @@ const comboboxControlVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type ComboboxClearProps = Omit<BaseCombobox.Clear.Props, 'className'> &
|
||||
type ComboboxClearProps = Omit<BaseCombobox.Clear.Props, 'className'> &
|
||||
VariantProps<typeof comboboxControlVariants> & { className?: string }
|
||||
|
||||
export function ComboboxClear({
|
||||
function ComboboxClear({
|
||||
className,
|
||||
children,
|
||||
size = 'medium',
|
||||
@ -246,10 +271,10 @@ export function ComboboxClear({
|
||||
)
|
||||
}
|
||||
|
||||
export type ComboboxInputTriggerProps = Omit<BaseCombobox.Trigger.Props, 'className'> &
|
||||
type ComboboxInputTriggerProps = Omit<BaseCombobox.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof comboboxControlVariants> & { 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<BaseCombobox.Icon.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxIcon({ className, children, ...props }: ComboboxIconProps) {
|
||||
return (
|
||||
<BaseCombobox.Icon
|
||||
className={cn('flex shrink-0 items-center text-text-tertiary', className)}
|
||||
@ -296,7 +325,7 @@ type ComboboxContentProps = {
|
||||
popupProps?: Omit<BaseCombobox.Popup.Props, 'children' | 'className'>
|
||||
}
|
||||
|
||||
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<Value = unknown> = Omit<
|
||||
BaseCombobox.List.Props,
|
||||
'children' | 'className'
|
||||
> & {
|
||||
className?: string
|
||||
children?: React.ReactNode | ((item: Value, index: number) => React.ReactNode)
|
||||
}
|
||||
|
||||
function ComboboxList<Value = unknown>({ className, ...props }: ComboboxListProps<Value>) {
|
||||
return <BaseCombobox.List className={cn(comboboxListClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ComboboxItem({ className, ...props }: BaseCombobox.Item.Props) {
|
||||
type ComboboxItemProps<Value = unknown> = Omit<BaseCombobox.Item.Props, 'className' | 'value'> & {
|
||||
className?: string
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function ComboboxItem<Value = unknown>({ className, ...props }: ComboboxItemProps<Value>) {
|
||||
return <BaseCombobox.Item className={cn(comboboxItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export type ComboboxItemTextProps = React.ComponentProps<'span'>
|
||||
type ComboboxItemTextProps = React.ComponentProps<'span'>
|
||||
|
||||
export function ComboboxItemText({ className, ...props }: ComboboxItemTextProps) {
|
||||
function ComboboxItemText({ className, ...props }: ComboboxItemTextProps) {
|
||||
return (
|
||||
<span className={cn('min-w-0 grow truncate px-1 system-sm-medium', className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxItemIndicator({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: Omit<BaseCombobox.ItemIndicator.Props, 'children'> & { children?: React.ReactNode }) {
|
||||
function ComboboxItemIndicator({ className, children, ...props }: ComboboxItemIndicatorProps) {
|
||||
return (
|
||||
<BaseCombobox.ItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
@ -361,21 +399,45 @@ export function ComboboxItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxLabel({ className, ...props }: BaseCombobox.Label.Props) {
|
||||
type ComboboxItemIndicatorProps = Omit<
|
||||
BaseCombobox.ItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
type ComboboxLabelProps = Omit<BaseCombobox.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxLabel({ className, ...props }: ComboboxLabelProps) {
|
||||
return <BaseCombobox.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ComboboxGroupLabel({ className, ...props }: BaseCombobox.GroupLabel.Props) {
|
||||
type ComboboxGroupLabelProps = Omit<BaseCombobox.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxGroupLabel({ className, ...props }: ComboboxGroupLabelProps) {
|
||||
return (
|
||||
<BaseCombobox.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxSeparator({ className, ...props }: BaseCombobox.Separator.Props) {
|
||||
type ComboboxSeparatorProps = Omit<BaseCombobox.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxSeparator({ className, ...props }: ComboboxSeparatorProps) {
|
||||
return <BaseCombobox.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ComboboxEmpty({ className, ...props }: BaseCombobox.Empty.Props) {
|
||||
type ComboboxEmptyProps = Omit<BaseCombobox.Empty.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxEmpty({ className, ...props }: ComboboxEmptyProps) {
|
||||
return (
|
||||
<BaseCombobox.Empty
|
||||
className={cn(
|
||||
@ -387,7 +449,11 @@ export function ComboboxEmpty({ className, ...props }: BaseCombobox.Empty.Props)
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxStatus({ className, ...props }: BaseCombobox.Status.Props) {
|
||||
type ComboboxStatusProps = Omit<BaseCombobox.Status.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxStatus({ className, ...props }: ComboboxStatusProps) {
|
||||
return (
|
||||
<BaseCombobox.Status
|
||||
className={cn('px-3 py-2 system-sm-regular text-text-tertiary', className)}
|
||||
@ -396,7 +462,11 @@ export function ComboboxStatus({ className, ...props }: BaseCombobox.Status.Prop
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxChips({ className, ...props }: BaseCombobox.Chips.Props) {
|
||||
type ComboboxChipsProps = Omit<BaseCombobox.Chips.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxChips({ className, ...props }: ComboboxChipsProps) {
|
||||
return (
|
||||
<BaseCombobox.Chips
|
||||
className={cn('flex w-full min-w-0 flex-wrap items-center gap-1 px-1', className)}
|
||||
@ -405,7 +475,11 @@ export function ComboboxChips({ className, ...props }: BaseCombobox.Chips.Props)
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxChip({ className, ...props }: BaseCombobox.Chip.Props) {
|
||||
type ComboboxChipProps = Omit<BaseCombobox.Chip.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxChip({ className, ...props }: ComboboxChipProps) {
|
||||
return (
|
||||
<BaseCombobox.Chip
|
||||
className={cn(
|
||||
@ -417,12 +491,12 @@ export function ComboboxChip({ className, ...props }: BaseCombobox.Chip.Props) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxChipRemove({
|
||||
function ComboboxChipRemove({
|
||||
className,
|
||||
children,
|
||||
type = 'button',
|
||||
...props
|
||||
}: BaseCombobox.ChipRemove.Props) {
|
||||
}: ComboboxChipRemoveProps) {
|
||||
return (
|
||||
<BaseCombobox.ChipRemove
|
||||
type={type}
|
||||
@ -439,3 +513,65 @@ export function ComboboxChipRemove({
|
||||
</BaseCombobox.ChipRemove>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxChipRemoveProps = Omit<BaseCombobox.ChipRemove.Props, 'className'> & {
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<Density>('comfortable')
|
||||
|
||||
return (
|
||||
<ContextMenu>
|
||||
<TriggerArea label={`Right-click to set density: ${value}`} />
|
||||
<TriggerArea label={`Right-click to set density: ${density}`} />
|
||||
<ContextMenuContent popupClassName="w-44">
|
||||
<ContextMenuRadioGroup value={value} onValueChange={setValue}>
|
||||
<ContextMenuRadioItem value="compact">
|
||||
<ContextMenuRadioGroup<Density> value={density} onValueChange={setDensity}>
|
||||
<ContextMenuRadioItem<Density> value="compact">
|
||||
Compact
|
||||
<ContextMenuRadioItemIndicator />
|
||||
</ContextMenuRadioItem>
|
||||
<ContextMenuRadioItem value="comfortable">
|
||||
<ContextMenuRadioItem<Density> value="comfortable">
|
||||
Comfortable
|
||||
<ContextMenuRadioItemIndicator />
|
||||
</ContextMenuRadioItem>
|
||||
<ContextMenuRadioItem value="spacious">
|
||||
<ContextMenuRadioItem<Density> value="spacious">
|
||||
Spacious
|
||||
<ContextMenuRadioItemIndicator />
|
||||
</ContextMenuRadioItem>
|
||||
|
||||
@ -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<Value = unknown> = 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<Value = unknown>(
|
||||
props: ContextMenuRadioGroupProps<Value>,
|
||||
): React.JSX.Element {
|
||||
return <BaseContextMenu.RadioGroup {...props} />
|
||||
}
|
||||
|
||||
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<BaseContextMenu.Item.Props, 'className'> & {
|
||||
variant?: ContextMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ContextMenuItem({
|
||||
className,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: ContextMenuItemProps) {
|
||||
function ContextMenuItem({ className, variant = 'default', ...props }: ContextMenuItemProps) {
|
||||
return (
|
||||
<BaseContextMenu.Item
|
||||
data-variant={variant}
|
||||
@ -123,11 +139,12 @@ export function ContextMenuItem({
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuLinkItemProps = BaseContextMenu.LinkItem.Props & {
|
||||
variant?: MenuItemVariant
|
||||
type ContextMenuLinkItemProps = Omit<BaseContextMenu.LinkItem.Props, 'className'> & {
|
||||
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<Value = unknown> = Omit<
|
||||
BaseContextMenu.RadioItem.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
value: Value
|
||||
}
|
||||
|
||||
function ContextMenuRadioItem<Value = unknown>({
|
||||
className,
|
||||
...props
|
||||
}: ContextMenuRadioItemProps<Value>) {
|
||||
return <BaseContextMenu.RadioItem className={cn(menuItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ContextMenuCheckboxItem({
|
||||
className,
|
||||
...props
|
||||
}: BaseContextMenu.CheckboxItem.Props) {
|
||||
function ContextMenuCheckboxItem({ className, ...props }: ContextMenuCheckboxItemProps) {
|
||||
return <BaseContextMenu.CheckboxItem className={cn(menuItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ContextMenuCheckboxItemIndicator({
|
||||
type ContextMenuCheckboxItemProps = Omit<BaseContextMenu.CheckboxItem.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItemIndicator({
|
||||
className,
|
||||
...props
|
||||
}: Omit<BaseContextMenu.CheckboxItemIndicator.Props, 'children'>) {
|
||||
}: ContextMenuCheckboxItemIndicatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.CheckboxItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
@ -168,10 +197,15 @@ export function ContextMenuCheckboxItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
export function ContextMenuRadioItemIndicator({
|
||||
type ContextMenuCheckboxItemIndicatorProps = Omit<
|
||||
BaseContextMenu.CheckboxItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
|
||||
function ContextMenuRadioItemIndicator({
|
||||
className,
|
||||
...props
|
||||
}: Omit<BaseContextMenu.RadioItemIndicator.Props, 'children'>) {
|
||||
}: ContextMenuRadioItemIndicatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.RadioItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
@ -182,11 +216,17 @@ export function ContextMenuRadioItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuSubTriggerProps = BaseContextMenu.SubmenuTrigger.Props & {
|
||||
variant?: MenuItemVariant
|
||||
type ContextMenuRadioItemIndicatorProps = Omit<
|
||||
BaseContextMenu.RadioItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
|
||||
type ContextMenuSubTriggerProps = Omit<BaseContextMenu.SubmenuTrigger.Props, 'className'> & {
|
||||
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<BaseContextMenu.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuLabel({ className, ...props }: ContextMenuLabelProps) {
|
||||
return (
|
||||
<BaseContextMenu.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function ContextMenuSeparator({ className, ...props }: BaseContextMenu.Separator.Props) {
|
||||
type ContextMenuSeparatorProps = Omit<BaseContextMenu.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({ className, ...props }: ContextMenuSeparatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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: () => <ControlledDemo />,
|
||||
}
|
||||
|
||||
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<WorkspaceDialogPayload>())
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="flex gap-2">
|
||||
{workspaceDialogPayloads.map((payload) => (
|
||||
<DialogTrigger
|
||||
key={payload.name}
|
||||
handle={dialogHandle}
|
||||
payload={payload}
|
||||
render={<Button variant="secondary" />}
|
||||
>
|
||||
Edit {payload.name}
|
||||
</DialogTrigger>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Dialog handle={dialogHandle}>
|
||||
{({ payload }) => (
|
||||
<DialogContent>
|
||||
<DialogCloseButton />
|
||||
<div className="grid gap-2 pr-8">
|
||||
<DialogTitle className="text-lg leading-7 font-semibold text-text-primary">
|
||||
{payload ? `Edit ${payload.name}` : 'Edit workspace'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-5 text-text-secondary">
|
||||
{payload
|
||||
? `${payload.memberCount} members currently have access to this workspace.`
|
||||
: 'Choose a workspace to edit.'}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</DialogContent>
|
||||
)}
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const DetachedTriggers: Story = {
|
||||
render: () => <DetachedTriggersDemo />,
|
||||
}
|
||||
|
||||
type ApiExtensionFormValues = {
|
||||
name: string
|
||||
endpoint: string
|
||||
|
||||
@ -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<Payload = unknown> = BaseDialog.Root.Props<Payload>
|
||||
type DialogHandle<Payload = unknown> = BaseDialog.Handle<Payload>
|
||||
type DialogTriggerProps<Payload = unknown> = BaseDialog.Trigger.Props<Payload>
|
||||
type DialogTitleProps = BaseDialog.Title.Props
|
||||
type DialogDescriptionProps = BaseDialog.Description.Props
|
||||
type DialogPortalProps = BaseDialog.Portal.Props
|
||||
|
||||
type DialogBackdropProps = Omit<BaseDialog.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogBackdrop({ className, ...props }: DialogBackdropProps) {
|
||||
function DialogBackdrop({ className, ...props }: DialogBackdropProps) {
|
||||
return <BaseDialog.Backdrop {...props} className={cn(modalBackdropClassName, className)} />
|
||||
}
|
||||
|
||||
@ -24,7 +31,7 @@ type DialogViewportProps = Omit<BaseDialog.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogViewport({ className, ...props }: DialogViewportProps) {
|
||||
function DialogViewport({ className, ...props }: DialogViewportProps) {
|
||||
return <BaseDialog.Viewport className={cn('fixed inset-0 z-50', className)} {...props} />
|
||||
}
|
||||
|
||||
@ -32,7 +39,7 @@ type DialogPopupProps = Omit<BaseDialog.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogPopup({ className, ...props }: DialogPopupProps) {
|
||||
function DialogPopup({ className, ...props }: DialogPopupProps) {
|
||||
return (
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
@ -45,9 +52,11 @@ export function DialogPopup({ className, ...props }: DialogPopupProps) {
|
||||
)
|
||||
}
|
||||
|
||||
type DialogCloseButtonProps = Omit<BaseDialog.Close.Props, 'children'>
|
||||
type DialogCloseButtonProps = Omit<BaseDialog.Close.Props, 'children' | 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogCloseButton({
|
||||
function DialogCloseButton({
|
||||
className,
|
||||
'aria-label': ariaLabel = 'Close',
|
||||
...props
|
||||
@ -73,7 +82,7 @@ type DialogContentProps = {
|
||||
backdropProps?: Omit<BaseDialog.Backdrop.Props, 'className'>
|
||||
}
|
||||
|
||||
export function DialogContent({
|
||||
function DialogContent({
|
||||
children,
|
||||
className,
|
||||
backdropClassName,
|
||||
@ -93,3 +102,31 @@ export function DialogContent({
|
||||
</DialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<DrawerProps['snapPoints']>[number]
|
||||
const initialSnapPoint: DrawerSnapPoint = `${visibleSnapPointRem + snapTopMarginRem}rem`
|
||||
const snapPoints = [initialSnapPoint, 1] satisfies DrawerSnapPoint[]
|
||||
|
||||
|
||||
@ -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<Payload = unknown> = BaseDrawer.Root.Props<Payload>
|
||||
export type DrawerActions = BaseDrawer.Root.Actions
|
||||
export type DrawerChangeEventDetails = BaseDrawer.Root.ChangeEventDetails
|
||||
export type DrawerChangeEventReason = BaseDrawer.Root.ChangeEventReason
|
||||
export type DrawerSnapPoint = BaseDrawer.Root.SnapPoint
|
||||
export type DrawerSnapPointChangeEventDetails = BaseDrawer.Root.SnapPointChangeEventDetails
|
||||
export type DrawerSnapPointChangeEventReason = BaseDrawer.Root.SnapPointChangeEventReason
|
||||
export type DrawerTriggerProps<Payload = unknown> = BaseDrawer.Trigger.Props<Payload>
|
||||
type DrawerProps<Payload = unknown> = BaseDrawer.Root.Props<Payload>
|
||||
type DrawerHandle<Payload = unknown> = BaseDrawer.Handle<Payload>
|
||||
type DrawerProviderProps = BaseDrawer.Provider.Props
|
||||
type DrawerIndentProps = BaseDrawer.Indent.Props
|
||||
type DrawerIndentBackgroundProps = BaseDrawer.IndentBackground.Props
|
||||
type DrawerTriggerProps<Payload = unknown> = BaseDrawer.Trigger.Props<Payload>
|
||||
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<BaseDrawer.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DrawerBackdrop({ className, ...props }: DrawerBackdropProps) {
|
||||
return (
|
||||
<BaseDrawer.Backdrop
|
||||
className={cn(
|
||||
@ -38,7 +45,11 @@ export function DrawerBackdrop({ className, ...props }: BaseDrawer.Backdrop.Prop
|
||||
)
|
||||
}
|
||||
|
||||
export function DrawerViewport({ className, ...props }: BaseDrawer.Viewport.Props) {
|
||||
type DrawerViewportProps = Omit<BaseDrawer.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DrawerViewport({ className, ...props }: DrawerViewportProps) {
|
||||
return (
|
||||
<BaseDrawer.Viewport
|
||||
className={cn(
|
||||
@ -50,7 +61,11 @@ export function DrawerViewport({ className, ...props }: BaseDrawer.Viewport.Prop
|
||||
)
|
||||
}
|
||||
|
||||
export function DrawerPopup({ className, ...props }: BaseDrawer.Popup.Props) {
|
||||
type DrawerPopupProps = Omit<BaseDrawer.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DrawerPopup({ className, ...props }: DrawerPopupProps) {
|
||||
return (
|
||||
<BaseDrawer.Popup
|
||||
className={cn(
|
||||
@ -71,7 +86,11 @@ export function DrawerPopup({ className, ...props }: BaseDrawer.Popup.Props) {
|
||||
)
|
||||
}
|
||||
|
||||
export function DrawerContent({ className, ...props }: BaseDrawer.Content.Props) {
|
||||
type DrawerContentProps = Omit<BaseDrawer.Content.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DrawerContent({ className, ...props }: DrawerContentProps) {
|
||||
return (
|
||||
<BaseDrawer.Content
|
||||
className={cn(
|
||||
@ -83,11 +102,12 @@ export function DrawerContent({ className, ...props }: BaseDrawer.Content.Props)
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerCloseButtonProps = Omit<BaseDrawer.Close.Props, 'children'> & {
|
||||
type DrawerCloseButtonProps = Omit<BaseDrawer.Close.Props, 'children' | 'className'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DrawerCloseButton({
|
||||
function DrawerCloseButton({
|
||||
className,
|
||||
children,
|
||||
type = 'button',
|
||||
@ -108,3 +128,41 @@ export function DrawerCloseButton({
|
||||
</BaseDrawer.Close>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<Density>('comfortable')
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<TriggerButton label={`Density: ${value}`} />
|
||||
<TriggerButton label={`Density: ${density}`} />
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup value={value} onValueChange={setValue}>
|
||||
<DropdownMenuRadioItem value="compact">
|
||||
<DropdownMenuRadioGroup<Density> value={density} onValueChange={setDensity}>
|
||||
<DropdownMenuRadioItem<Density> value="compact">
|
||||
Compact
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="comfortable">
|
||||
<DropdownMenuRadioItem<Density> value="comfortable">
|
||||
Comfortable
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="spacious">
|
||||
<DropdownMenuRadioItem<Density> value="spacious">
|
||||
Spacious
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
@ -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<SortOrder>('newest')
|
||||
const [showArchived, setShowArchived] = React.useState(false)
|
||||
|
||||
return (
|
||||
@ -308,16 +312,16 @@ const ComplexDemo = () => {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Sort by</DropdownMenuLabel>
|
||||
<DropdownMenuRadioGroup value={sortOrder} onValueChange={setSortOrder}>
|
||||
<DropdownMenuRadioItem value="newest">
|
||||
<DropdownMenuRadioGroup<SortOrder> value={sortOrder} onValueChange={setSortOrder}>
|
||||
<DropdownMenuRadioItem<SortOrder> value="newest">
|
||||
Newest first
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="oldest">
|
||||
<DropdownMenuRadioItem<SortOrder> value="oldest">
|
||||
Oldest first
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="name">
|
||||
<DropdownMenuRadioItem<SortOrder> value="name">
|
||||
Name
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
|
||||
@ -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<Payload = unknown> = Menu.Root.Props<Payload>
|
||||
type DropdownMenuTriggerProps<Payload = unknown> = Menu.Trigger.Props<Payload>
|
||||
type DropdownMenuSubProps = Menu.SubmenuRoot.Props
|
||||
type DropdownMenuGroupProps = Menu.Group.Props
|
||||
type DropdownMenuRadioGroupProps<Value = unknown> = 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<Value = unknown>(
|
||||
props: DropdownMenuRadioGroupProps<Value>,
|
||||
): React.JSX.Element {
|
||||
return <Menu.RadioGroup {...props} />
|
||||
}
|
||||
|
||||
type DropdownMenuRadioItemProps<Value = unknown> = Omit<
|
||||
Menu.RadioItem.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
value: Value
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem<Value = unknown>({
|
||||
className,
|
||||
...props
|
||||
}: DropdownMenuRadioItemProps<Value>) {
|
||||
return <Menu.RadioItem className={cn(menuItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function DropdownMenuRadioItemIndicator({
|
||||
function DropdownMenuRadioItemIndicator({
|
||||
className,
|
||||
...props
|
||||
}: Omit<Menu.RadioItemIndicator.Props, 'children'>) {
|
||||
}: DropdownMenuRadioItemIndicatorProps) {
|
||||
return (
|
||||
<Menu.RadioItemIndicator className={cn(floatingItemIndicatorClassName, className)} {...props}>
|
||||
<span aria-hidden className="i-ri-check-line h-4 w-4" />
|
||||
@ -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<Menu.CheckboxItem.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({ className, ...props }: DropdownMenuCheckboxItemProps) {
|
||||
return <Menu.CheckboxItem className={cn(menuItemClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function DropdownMenuCheckboxItemIndicator({
|
||||
function DropdownMenuCheckboxItemIndicator({
|
||||
className,
|
||||
...props
|
||||
}: Omit<Menu.CheckboxItemIndicator.Props, 'children'>) {
|
||||
}: DropdownMenuCheckboxItemIndicatorProps) {
|
||||
return (
|
||||
<Menu.CheckboxItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
@ -57,7 +94,16 @@ export function DropdownMenuCheckboxItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
export function DropdownMenuLabel({ className, ...props }: Menu.GroupLabel.Props) {
|
||||
type DropdownMenuCheckboxItemIndicatorProps = Omit<
|
||||
Menu.CheckboxItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
|
||||
type DropdownMenuLabelProps = Omit<Menu.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({ className, ...props }: DropdownMenuLabelProps) {
|
||||
return <Menu.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
@ -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<Menu.SubmenuTrigger.Props, 'className'> & {
|
||||
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<Menu.Item.Props, 'className'> & {
|
||||
variant?: DropdownMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DropdownMenuItem({
|
||||
className,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: DropdownMenuItemProps) {
|
||||
function DropdownMenuItem({ className, variant = 'default', ...props }: DropdownMenuItemProps) {
|
||||
return (
|
||||
<Menu.Item
|
||||
data-variant={variant}
|
||||
@ -216,11 +260,12 @@ export function DropdownMenuItem({
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuLinkItemProps = Menu.LinkItem.Props & {
|
||||
variant?: MenuItemVariant
|
||||
type DropdownMenuLinkItemProps = Omit<Menu.LinkItem.Props, 'className'> & {
|
||||
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<Menu.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({ className, ...props }: DropdownMenuSeparatorProps) {
|
||||
return <Menu.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<BaseFieldNS.Root.Props, 'className'> & {
|
||||
type FieldProps = Omit<BaseFieldNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type FieldActions = BaseFieldNS.Root.Actions
|
||||
|
||||
export function Field({ className, ...props }: FieldProps) {
|
||||
function Field({ className, ...props }: FieldProps) {
|
||||
return <BaseField.Root className={cn('group/field grid min-w-0 gap-1', className)} {...props} />
|
||||
}
|
||||
|
||||
export type FieldItemProps = Omit<BaseFieldNS.Item.Props, 'className'> & {
|
||||
type FieldItemProps = Omit<BaseFieldNS.Item.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldItem({ className, ...props }: FieldItemProps) {
|
||||
function FieldItem({ className, ...props }: FieldItemProps) {
|
||||
return <BaseField.Item className={cn('grid min-w-0 gap-1', className)} {...props} />
|
||||
}
|
||||
|
||||
export type FieldLabelProps = Omit<BaseFieldNS.Label.Props, 'className'> & {
|
||||
type FieldLabelProps = Omit<BaseFieldNS.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldLabel({ className, ...props }: FieldLabelProps) {
|
||||
function FieldLabel({ className, ...props }: FieldLabelProps) {
|
||||
return <BaseField.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export type FieldControlSize = NonNullable<VariantProps<typeof textControlVariants>['size']>
|
||||
|
||||
export type FieldControlProps = Omit<BaseFieldNS.Control.Props, 'className' | 'size'> &
|
||||
type FieldControlProps = Omit<BaseFieldNS.Control.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof textControlVariants> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type FieldControlChangeEventDetails = BaseFieldNS.Control.ChangeEventDetails
|
||||
|
||||
export function FieldControl({ className, size = 'medium', ...props }: FieldControlProps) {
|
||||
function FieldControl({ className, size = 'medium', ...props }: FieldControlProps) {
|
||||
return <BaseField.Control className={cn(textControlVariants({ size }), className)} {...props} />
|
||||
}
|
||||
|
||||
export type FieldDescriptionProps = Omit<BaseFieldNS.Description.Props, 'className'> & {
|
||||
type FieldDescriptionProps = Omit<BaseFieldNS.Description.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldDescription({ className, ...props }: FieldDescriptionProps) {
|
||||
function FieldDescription({ className, ...props }: FieldDescriptionProps) {
|
||||
return (
|
||||
<BaseField.Description
|
||||
className={cn('py-0.5 body-xs-regular text-text-tertiary', className)}
|
||||
@ -58,11 +52,11 @@ export function FieldDescription({ className, ...props }: FieldDescriptionProps)
|
||||
)
|
||||
}
|
||||
|
||||
export type FieldErrorProps = Omit<BaseFieldNS.Error.Props, 'className'> & {
|
||||
type FieldErrorProps = Omit<BaseFieldNS.Error.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldError({ className, ...props }: FieldErrorProps) {
|
||||
function FieldError({ className, ...props }: FieldErrorProps) {
|
||||
return (
|
||||
<BaseField.Error
|
||||
className={cn('py-0.5 body-xs-regular text-text-destructive', className)}
|
||||
@ -71,7 +65,18 @@ export function FieldError({ className, ...props }: FieldErrorProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type FieldValidityProps = BaseFieldNS.Validity.Props
|
||||
export type FieldValidityState = BaseFieldNS.Validity.State
|
||||
type FieldValidityProps = BaseFieldNS.Validity.Props
|
||||
|
||||
export const FieldValidity = BaseField.Validity
|
||||
const FieldValidity = BaseField.Validity
|
||||
|
||||
export { Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldValidity }
|
||||
|
||||
export type {
|
||||
FieldControlProps,
|
||||
FieldDescriptionProps,
|
||||
FieldErrorProps,
|
||||
FieldItemProps,
|
||||
FieldLabelProps,
|
||||
FieldProps,
|
||||
FieldValidityProps,
|
||||
}
|
||||
|
||||
@ -4,19 +4,19 @@ import type { Fieldset as BaseFieldsetNS } from '@base-ui/react/fieldset'
|
||||
import { Fieldset as BaseFieldset } from '@base-ui/react/fieldset'
|
||||
import { cn } from '../cn'
|
||||
|
||||
export type FieldsetProps = Omit<BaseFieldsetNS.Root.Props, 'className'> & {
|
||||
type FieldsetProps = Omit<BaseFieldsetNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Fieldset({ className, ...props }: FieldsetProps) {
|
||||
function Fieldset({ className, ...props }: FieldsetProps) {
|
||||
return <BaseFieldset.Root className={cn('m-0 min-w-0 border-0 p-0', className)} {...props} />
|
||||
}
|
||||
|
||||
export type FieldsetLegendProps = Omit<BaseFieldsetNS.Legend.Props, 'className'> & {
|
||||
type FieldsetLegendProps = Omit<BaseFieldsetNS.Legend.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FieldsetLegend({ className, ...props }: FieldsetLegendProps) {
|
||||
function FieldsetLegend({ className, ...props }: FieldsetLegendProps) {
|
||||
return (
|
||||
<BaseFieldset.Legend
|
||||
className={cn('mb-1 py-1 system-sm-medium text-text-secondary', className)}
|
||||
@ -24,3 +24,7 @@ export function FieldsetLegend({ className, ...props }: FieldsetLegendProps) {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Fieldset, FieldsetLegend }
|
||||
|
||||
export type { FieldsetLegendProps, FieldsetProps }
|
||||
|
||||
@ -20,7 +20,7 @@ function renderGuides(level: number) {
|
||||
return Array.from({ length: Math.max(level - 1, 0) }, (_, index) => <FileTreeGuide key={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: <FileTreeLevelContext.Provider value={1}>{children}</FileTreeLevelContext.Provider>,
|
||||
@ -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<BaseCollapsible.Root.Props, 'render'> & {
|
||||
type FileTreeFolderProps = Omit<BaseCollapsible.Root.Props, 'className' | 'render'> & {
|
||||
className?: string
|
||||
render?: BaseCollapsible.Root.Props['render']
|
||||
}
|
||||
|
||||
export function FileTreeFolder({ render = <li />, className, ...props }: FileTreeFolderProps) {
|
||||
function FileTreeFolder({ render = <li />, className, ...props }: FileTreeFolderProps) {
|
||||
return <BaseCollapsible.Root render={render} className={cn('min-w-0', className)} {...props} />
|
||||
}
|
||||
|
||||
export type FileTreeFolderTriggerProps = Omit<BaseCollapsible.Trigger.Props, 'className'> & {
|
||||
type FileTreeFolderTriggerProps = Omit<BaseCollapsible.Trigger.Props, 'className'> & {
|
||||
className?: string
|
||||
level?: number
|
||||
}
|
||||
|
||||
export function FileTreeFolderTrigger({
|
||||
function FileTreeFolderTrigger({
|
||||
className,
|
||||
children,
|
||||
disabled,
|
||||
@ -102,11 +103,12 @@ export function FileTreeFolderTrigger({
|
||||
)
|
||||
}
|
||||
|
||||
export type FileTreeFolderPanelProps = Omit<BaseCollapsible.Panel.Props, 'render'> & {
|
||||
type FileTreeFolderPanelProps = Omit<BaseCollapsible.Panel.Props, 'className' | 'render'> & {
|
||||
className?: string
|
||||
render?: BaseCollapsible.Panel.Props['render']
|
||||
}
|
||||
|
||||
export function FileTreeFolderPanel({
|
||||
function FileTreeFolderPanel({
|
||||
render = <ul />,
|
||||
className,
|
||||
children,
|
||||
@ -125,15 +127,12 @@ export function FileTreeFolderPanel({
|
||||
)
|
||||
}
|
||||
|
||||
export type FileTreeFileProps = Omit<
|
||||
useRender.ComponentProps<'button', FileTreeRowState>,
|
||||
'type'
|
||||
> & {
|
||||
type FileTreeFileProps = Omit<useRender.ComponentProps<'button', FileTreeFileState>, '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 <li className="min-w-0">{file}</li>
|
||||
}
|
||||
|
||||
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<Exclude<FileTreeIconType, 'folder'>, string
|
||||
archive: 'i-ri-file-zip-fill text-[#A4AABF]',
|
||||
}
|
||||
|
||||
export type FileTreeIconProps = Omit<useRender.ComponentProps<'span'>, 'children'> & {
|
||||
type FileTreeIconProps = Omit<useRender.ComponentProps<'span'>, '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,
|
||||
}
|
||||
|
||||
@ -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<typeof meta>
|
||||
|
||||
export const Basic: Story = {
|
||||
render: () => (
|
||||
<Form className="grid w-96 gap-4" onFormSubmit={() => undefined}>
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl required placeholder="Enter a name" />
|
||||
<FieldError match="valueMissing">Name is required.</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<FieldControl type="email" required placeholder="name@example.com" />
|
||||
<FieldDescription>Used for account notifications.</FieldDescription>
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field name="features">
|
||||
<Fieldset render={<CheckboxGroup defaultValue={['search']} />}>
|
||||
<FieldsetLegend>Features</FieldsetLegend>
|
||||
<div className="grid gap-2">
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
<Checkbox value="search" />
|
||||
Search
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
<Checkbox value="analytics" />
|
||||
Analytics
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
),
|
||||
type WorkspaceFormValues = {
|
||||
name: string
|
||||
email: string
|
||||
features: string[]
|
||||
}
|
||||
|
||||
function BasicFormDemo() {
|
||||
const [submittedValues, setSubmittedValues] = React.useState<WorkspaceFormValues>()
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Form<WorkspaceFormValues>
|
||||
className="grid w-96 gap-4"
|
||||
onFormSubmit={(formValues) => setSubmittedValues(formValues)}
|
||||
>
|
||||
<Field name="name">
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldControl required placeholder="Enter a name" />
|
||||
<FieldError match="valueMissing">Name is required.</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field name="email">
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<FieldControl type="email" required placeholder="name@example.com" />
|
||||
<FieldDescription>Used for account notifications.</FieldDescription>
|
||||
<FieldError match="valueMissing">Email is required.</FieldError>
|
||||
<FieldError match="typeMismatch">Enter a valid email address.</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field name="features">
|
||||
<Fieldset render={<CheckboxGroup defaultValue={['search']} />}>
|
||||
<FieldsetLegend>Features</FieldsetLegend>
|
||||
<div className="grid gap-2">
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
<Checkbox value="search" />
|
||||
Search
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
<FieldItem>
|
||||
<FieldLabel className="flex items-center gap-2">
|
||||
<Checkbox value="analytics" />
|
||||
Analytics
|
||||
</FieldLabel>
|
||||
</FieldItem>
|
||||
</div>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
{submittedValues && (
|
||||
<output className="w-96 rounded-lg bg-background-default-subtle p-3 font-mono text-xs text-text-secondary">
|
||||
{JSON.stringify(submittedValues, null, 2)}
|
||||
</output>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Basic: Story = {
|
||||
render: () => <BasicFormDemo />,
|
||||
}
|
||||
|
||||
@ -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<FormValues extends BaseFormNS.Values = BaseFormNS.Values> =
|
||||
BaseFormNS.Props<FormValues>
|
||||
|
||||
export { Form }
|
||||
export type { FormProps }
|
||||
|
||||
@ -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<VariantProps<typeof textControlVariants>['size']>
|
||||
|
||||
export type InputProps = Omit<BaseInputNS.Props, 'className' | 'size'> &
|
||||
type InputProps = Omit<BaseInputNS.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof textControlVariants> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type InputChangeEventDetails = BaseInputNS.ChangeEventDetails
|
||||
|
||||
export function Input({ className, size = 'medium', ...props }: InputProps) {
|
||||
function Input({ className, size = 'medium', ...props }: InputProps) {
|
||||
return <BaseInput className={cn(textControlVariants({ size }), className)} {...props} />
|
||||
}
|
||||
|
||||
export { Input }
|
||||
export type { InputProps }
|
||||
|
||||
@ -25,11 +25,11 @@ const kbdVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type KbdColor = NonNullable<VariantProps<typeof kbdVariants>['color']>
|
||||
type KbdColor = NonNullable<VariantProps<typeof kbdVariants>['color']>
|
||||
|
||||
export type KbdProps = Omit<React.ComponentProps<'kbd'>, 'color'> & VariantProps<typeof kbdVariants>
|
||||
type KbdProps = Omit<React.ComponentProps<'kbd'>, 'color'> & VariantProps<typeof kbdVariants>
|
||||
|
||||
export function Kbd({ className, color, disabled, ...props }: KbdProps) {
|
||||
function Kbd({ className, color, disabled, ...props }: KbdProps) {
|
||||
return (
|
||||
<kbd
|
||||
data-disabled={disabled ? '' : undefined}
|
||||
@ -39,10 +39,13 @@ export function Kbd({ className, color, disabled, ...props }: KbdProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type KbdGroupProps = React.ComponentProps<'span'>
|
||||
type KbdGroupProps = React.ComponentProps<'span'>
|
||||
|
||||
export function KbdGroup({ className, ...props }: KbdGroupProps) {
|
||||
function KbdGroup({ className, ...props }: KbdGroupProps) {
|
||||
return (
|
||||
<span className={cn('inline-flex items-center gap-0.5 align-middle', className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Kbd, KbdGroup }
|
||||
export type { KbdColor, KbdGroupProps, KbdProps }
|
||||
|
||||
@ -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<BaseMeter.Track.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function MeterTrack({ className, ...props }: MeterTrackProps) {
|
||||
function MeterTrack({ className, ...props }: MeterTrackProps) {
|
||||
return <BaseMeter.Track className={cn(meterTrackClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
@ -43,28 +45,43 @@ const meterIndicatorVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type MeterTone = NonNullable<VariantProps<typeof meterIndicatorVariants>['tone']>
|
||||
type MeterTone = NonNullable<VariantProps<typeof meterIndicatorVariants>['tone']>
|
||||
|
||||
export type MeterIndicatorProps = BaseMeter.Indicator.Props & {
|
||||
type MeterIndicatorProps = Omit<BaseMeter.Indicator.Props, 'className'> & {
|
||||
className?: string
|
||||
tone?: MeterTone
|
||||
}
|
||||
|
||||
export function MeterIndicator({ className, tone, ...props }: MeterIndicatorProps) {
|
||||
function MeterIndicator({ className, tone, ...props }: MeterIndicatorProps) {
|
||||
return (
|
||||
<BaseMeter.Indicator className={cn(meterIndicatorVariants({ tone }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
const meterValueClassName = 'system-xs-regular text-text-tertiary tabular-nums'
|
||||
export type MeterValueProps = BaseMeter.Value.Props
|
||||
type MeterValueProps = Omit<BaseMeter.Value.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function MeterValue({ className, ...props }: MeterValueProps) {
|
||||
function MeterValue({ className, ...props }: MeterValueProps) {
|
||||
return <BaseMeter.Value className={cn(meterValueClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
const meterLabelClassName = 'system-xs-medium text-text-tertiary'
|
||||
export type MeterLabelProps = BaseMeter.Label.Props
|
||||
type MeterLabelProps = Omit<BaseMeter.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function MeterLabel({ className, ...props }: MeterLabelProps) {
|
||||
function MeterLabel({ className, ...props }: MeterLabelProps) {
|
||||
return <BaseMeter.Label className={cn(meterLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue }
|
||||
export type {
|
||||
MeterIndicatorProps,
|
||||
MeterLabelProps,
|
||||
MeterProps,
|
||||
MeterTone,
|
||||
MeterTrackProps,
|
||||
MeterValueProps,
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import type {
|
||||
NumberFieldButtonProps,
|
||||
NumberFieldControlsProps,
|
||||
NumberFieldDecrementProps,
|
||||
NumberFieldGroupProps,
|
||||
NumberFieldIncrementProps,
|
||||
NumberFieldInputProps,
|
||||
NumberFieldUnitProps,
|
||||
} from '../index'
|
||||
@ -24,8 +25,8 @@ type RenderNumberFieldOptions = {
|
||||
inputProps?: Partial<NumberFieldInputProps>
|
||||
unitProps?: Partial<NumberFieldUnitProps> & { children?: React.ReactNode }
|
||||
controlsProps?: Partial<NumberFieldControlsProps>
|
||||
incrementProps?: Partial<NumberFieldButtonProps>
|
||||
decrementProps?: Partial<NumberFieldButtonProps>
|
||||
incrementProps?: Partial<NumberFieldIncrementProps>
|
||||
decrementProps?: Partial<NumberFieldDecrementProps>
|
||||
}
|
||||
|
||||
const renderNumberField = ({
|
||||
|
||||
@ -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<VariantProps<typeof numberFieldGroupVariants>['size']>
|
||||
type NumberFieldSize = NonNullable<VariantProps<typeof numberFieldGroupVariants>['size']>
|
||||
|
||||
export type NumberFieldGroupProps = Omit<BaseNumberField.Group.Props, 'className'> &
|
||||
type NumberFieldGroupProps = Omit<BaseNumberField.Group.Props, 'className'> &
|
||||
VariantProps<typeof numberFieldGroupVariants> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function NumberFieldGroup({ className, size = 'medium', ...props }: NumberFieldGroupProps) {
|
||||
function NumberFieldGroup({ className, size = 'medium', ...props }: NumberFieldGroupProps) {
|
||||
return (
|
||||
<BaseNumberField.Group
|
||||
className={cn(numberFieldGroupVariants({ size }), className)}
|
||||
@ -49,7 +49,7 @@ export function NumberFieldGroup({ className, size = 'medium', ...props }: Numbe
|
||||
)
|
||||
}
|
||||
|
||||
export const numberFieldInputVariants = cva(
|
||||
const numberFieldInputVariants = cva(
|
||||
[
|
||||
'w-0 min-w-0 flex-1 appearance-none border-0 bg-transparent text-components-input-text-filled caret-primary-600 outline-hidden',
|
||||
'placeholder:text-components-input-text-placeholder',
|
||||
@ -69,12 +69,12 @@ export const numberFieldInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type NumberFieldInputProps = Omit<BaseNumberField.Input.Props, 'className' | 'size'> &
|
||||
type NumberFieldInputProps = Omit<BaseNumberField.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof numberFieldInputVariants> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function NumberFieldInput({ className, size = 'medium', ...props }: NumberFieldInputProps) {
|
||||
function NumberFieldInput({ className, size = 'medium', ...props }: NumberFieldInputProps) {
|
||||
return (
|
||||
<BaseNumberField.Input
|
||||
className={cn(numberFieldInputVariants({ size }), className)}
|
||||
@ -83,7 +83,7 @@ export function NumberFieldInput({ className, size = 'medium', ...props }: Numbe
|
||||
)
|
||||
}
|
||||
|
||||
export const numberFieldUnitVariants = cva(
|
||||
const numberFieldUnitVariants = cva(
|
||||
'flex shrink-0 items-center self-stretch system-sm-regular text-text-tertiary',
|
||||
{
|
||||
variants: {
|
||||
@ -98,10 +98,10 @@ export const numberFieldUnitVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type NumberFieldUnitProps = React.ComponentProps<'span'> &
|
||||
type NumberFieldUnitProps = React.ComponentProps<'span'> &
|
||||
VariantProps<typeof numberFieldUnitVariants>
|
||||
|
||||
export function NumberFieldUnit({ className, size = 'medium', ...props }: NumberFieldUnitProps) {
|
||||
function NumberFieldUnit({ className, size = 'medium', ...props }: NumberFieldUnitProps) {
|
||||
return <span className={cn(numberFieldUnitVariants({ size }), className)} {...props} />
|
||||
}
|
||||
|
||||
@ -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 <div className={cn(numberFieldControlsVariants(), className)} {...props} />
|
||||
}
|
||||
|
||||
@ -170,7 +170,11 @@ type NumberFieldButtonVariantProps = Omit<
|
||||
'direction'
|
||||
>
|
||||
|
||||
export type NumberFieldButtonProps = Omit<BaseNumberField.Increment.Props, 'className'> &
|
||||
type NumberFieldIncrementProps = Omit<BaseNumberField.Increment.Props, 'className'> &
|
||||
NumberFieldButtonVariantProps & {
|
||||
className?: string
|
||||
}
|
||||
type NumberFieldDecrementProps = Omit<BaseNumberField.Decrement.Props, 'className'> &
|
||||
NumberFieldButtonVariantProps & {
|
||||
className?: string
|
||||
}
|
||||
@ -178,12 +182,12 @@ export type NumberFieldButtonProps = Omit<BaseNumberField.Increment.Props, 'clas
|
||||
const incrementAriaLabel = 'Increment value'
|
||||
const decrementAriaLabel = 'Decrement value'
|
||||
|
||||
export function NumberFieldIncrement({
|
||||
function NumberFieldIncrement({
|
||||
className,
|
||||
children,
|
||||
size = 'medium',
|
||||
...props
|
||||
}: NumberFieldButtonProps) {
|
||||
}: NumberFieldIncrementProps) {
|
||||
return (
|
||||
<BaseNumberField.Increment
|
||||
{...props}
|
||||
@ -197,12 +201,12 @@ export function NumberFieldIncrement({
|
||||
)
|
||||
}
|
||||
|
||||
export function NumberFieldDecrement({
|
||||
function NumberFieldDecrement({
|
||||
className,
|
||||
children,
|
||||
size = 'medium',
|
||||
...props
|
||||
}: NumberFieldButtonProps) {
|
||||
}: NumberFieldDecrementProps) {
|
||||
return (
|
||||
<BaseNumberField.Decrement
|
||||
{...props}
|
||||
@ -215,3 +219,23 @@ export function NumberFieldDecrement({
|
||||
</BaseNumberField.Decrement>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
NumberField,
|
||||
NumberFieldControls,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldGroup,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
NumberFieldUnit,
|
||||
}
|
||||
export type {
|
||||
NumberFieldControlsProps,
|
||||
NumberFieldDecrementProps,
|
||||
NumberFieldGroupProps,
|
||||
NumberFieldIncrementProps,
|
||||
NumberFieldInputProps,
|
||||
NumberFieldProps,
|
||||
NumberFieldSize,
|
||||
NumberFieldUnitProps,
|
||||
}
|
||||
|
||||
@ -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<BaseButtonNS.Props, 'children'> & {
|
||||
type PaginationPreviousProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
type PaginationNextProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
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<BaseButtonNS.Props, 'children'> & {
|
||||
type PaginationPageJumpProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
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<BaseButtonNS.Props, 'children'> & {
|
||||
type PaginationPageProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
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<Value extends number = number> = {
|
||||
type PaginationPageSizeProps<Value extends number = number> = {
|
||||
value: Value
|
||||
options: readonly Value[]
|
||||
onValueChange: (value: Value) => void
|
||||
@ -493,7 +501,7 @@ export type PaginationPageSizeProps<Value extends number = number> = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function PaginationPageSize<Value extends number = number>({
|
||||
function PaginationPageSize<Value extends number = number>({
|
||||
value,
|
||||
options,
|
||||
onValueChange,
|
||||
@ -538,14 +546,14 @@ export function PaginationPageSize<Value extends number = number>({
|
||||
)
|
||||
}
|
||||
|
||||
export type PaginationLabels = {
|
||||
type PaginationLabels = {
|
||||
previous?: string
|
||||
next?: string
|
||||
editPageNumber?: (page: number, totalPages: number) => string
|
||||
pageNumberInput?: string
|
||||
}
|
||||
|
||||
export type PaginationPageSizeConfig<Value extends number = number> = {
|
||||
type PaginationPageSizeConfig<Value extends number = number> = {
|
||||
value: Value
|
||||
options: readonly Value[]
|
||||
onValueChange: (value: Value) => void
|
||||
@ -553,15 +561,12 @@ export type PaginationPageSizeConfig<Value extends number = number> = {
|
||||
ariaLabel?: string
|
||||
}
|
||||
|
||||
export type PaginationProps<Value extends number = number> = Omit<
|
||||
PaginationRootProps,
|
||||
'children'
|
||||
> & {
|
||||
type PaginationProps<Value extends number = number> = Omit<PaginationRootProps, 'children'> & {
|
||||
labels?: PaginationLabels
|
||||
pageSize?: PaginationPageSizeConfig<Value>
|
||||
}
|
||||
|
||||
export function Pagination<Value extends number = number>({
|
||||
function Pagination<Value extends number = number>({
|
||||
labels,
|
||||
pageSize,
|
||||
page,
|
||||
@ -598,9 +603,9 @@ export function Pagination<Value extends number = number>({
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<ResourcePopoverPayload>())
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{resourcePopoverPayloads.map((payload) => (
|
||||
<PopoverTrigger
|
||||
key={payload.label}
|
||||
handle={popoverHandle}
|
||||
payload={payload}
|
||||
render={<button type="button" className={triggerButtonClassName} />}
|
||||
>
|
||||
{payload.label}
|
||||
</PopoverTrigger>
|
||||
))}
|
||||
|
||||
<Popover handle={popoverHandle}>
|
||||
{({ payload }) => (
|
||||
<PopoverContent>
|
||||
<div className="flex w-72 flex-col gap-1 p-4">
|
||||
<PopoverTitle className="text-sm font-semibold text-text-primary">
|
||||
{payload?.label ?? 'Resource'}
|
||||
</PopoverTitle>
|
||||
<PopoverDescription className="text-xs text-text-secondary">
|
||||
{payload?.description ?? 'Choose a resource to learn more.'}
|
||||
</PopoverDescription>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const DetachedTriggers: Story = {
|
||||
render: () => <DetachedTriggersDemo />,
|
||||
}
|
||||
|
||||
export const Infotip: Story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
|
||||
@ -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<Payload = unknown> = BasePopover.Root.Props<Payload>
|
||||
type PopoverHandle<Payload = unknown> = BasePopover.Handle<Payload>
|
||||
type PopoverTriggerProps<Payload = unknown> = BasePopover.Trigger.Props<Payload>
|
||||
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<BasePopover.Popup.Props, 'children' | 'className'>
|
||||
}
|
||||
|
||||
export function PopoverContent({
|
||||
function PopoverContent({
|
||||
children,
|
||||
placement = 'bottom',
|
||||
sideOffset = 8,
|
||||
@ -67,3 +72,23 @@ export function PopoverContent({
|
||||
</BasePopover.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
createPopoverHandle,
|
||||
Popover,
|
||||
PopoverClose,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
}
|
||||
export type {
|
||||
Placement,
|
||||
PopoverCloseProps,
|
||||
PopoverContentProps,
|
||||
PopoverDescriptionProps,
|
||||
PopoverHandle,
|
||||
PopoverProps,
|
||||
PopoverTitleProps,
|
||||
PopoverTriggerProps,
|
||||
}
|
||||
|
||||
@ -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<typeof meta>
|
||||
// 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<TypographyPreviewPayload>(),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="max-w-md p-6 text-sm leading-6 text-text-secondary">
|
||||
<p>
|
||||
The principles of good{' '}
|
||||
<PreviewCardTrigger
|
||||
handle={previewCardHandle}
|
||||
payload={typographyPreviewPayload}
|
||||
href="https://en.wikipedia.org/wiki/Typography"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={inlineLinkClassName}
|
||||
>
|
||||
typography
|
||||
</PreviewCardTrigger>{' '}
|
||||
remain in the digital age.
|
||||
</p>
|
||||
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload = typographyPreviewPayload }) => (
|
||||
<PreviewCardContent popupClassName="w-[240px] p-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<img
|
||||
width="224"
|
||||
height="150"
|
||||
className="block max-w-none rounded-md"
|
||||
src={payload.image.src}
|
||||
alt={payload.image.alt}
|
||||
/>
|
||||
<p className="m-0 text-xs leading-5 text-text-secondary">
|
||||
<strong className="text-text-primary">{payload.title}</strong> {payload.description}
|
||||
</p>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const LinkPreview: Story = {
|
||||
name: 'Link preview (canonical)',
|
||||
@ -45,44 +106,12 @@ export const LinkPreview: Story = {
|
||||
},
|
||||
},
|
||||
},
|
||||
render: () => (
|
||||
<div className="max-w-md p-6 text-sm leading-6 text-text-secondary">
|
||||
<p>
|
||||
The principles of good{' '}
|
||||
<PreviewCardTrigger
|
||||
handle={typographyPreview}
|
||||
href="https://en.wikipedia.org/wiki/Typography"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={inlineLinkClassName}
|
||||
>
|
||||
typography
|
||||
</PreviewCardTrigger>{' '}
|
||||
remain in the digital age.
|
||||
</p>
|
||||
|
||||
<PreviewCard handle={typographyPreview}>
|
||||
<PreviewCardContent popupClassName="w-[240px] p-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<img
|
||||
width="224"
|
||||
height="150"
|
||||
className="block max-w-none rounded-md"
|
||||
src="https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300"
|
||||
alt="Station Hofplein signage in Rotterdam, Netherlands"
|
||||
/>
|
||||
<p className="m-0 text-xs leading-5 text-text-secondary">
|
||||
<strong className="text-text-primary">Typography</strong> is the art and science of
|
||||
arranging type to make written language legible, readable, and visually appealing.
|
||||
</p>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
</div>
|
||||
),
|
||||
render: () => <LinkPreviewDemo />,
|
||||
}
|
||||
|
||||
const PLACEMENTS: Placement[] = [
|
||||
type PreviewCardPlacement = NonNullable<PreviewCardContentProps['placement']>
|
||||
|
||||
const PLACEMENTS: PreviewCardPlacement[] = [
|
||||
'top-start',
|
||||
'top',
|
||||
'top-end',
|
||||
@ -98,7 +127,7 @@ const PLACEMENTS: Placement[] = [
|
||||
]
|
||||
|
||||
const PlacementsDemo = () => {
|
||||
const [placement, setPlacement] = React.useState<Placement>('bottom')
|
||||
const [placement, setPlacement] = React.useState<PreviewCardPlacement>('bottom')
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 p-20">
|
||||
|
||||
@ -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<Payload = unknown> = BasePreviewCard.Root.Props<Payload>
|
||||
type PreviewCardHandle<Payload = unknown> = BasePreviewCard.Handle<Payload>
|
||||
type PreviewCardTriggerProps<Payload = unknown> = BasePreviewCard.Trigger.Props<Payload>
|
||||
type PreviewCardViewportProps = BasePreviewCard.Viewport.Props
|
||||
|
||||
type PreviewCardContentProps = {
|
||||
children: React.ReactNode
|
||||
@ -40,7 +43,7 @@ type PreviewCardContentProps = {
|
||||
popupProps?: Omit<BasePreviewCard.Popup.Props, 'children' | 'className'>
|
||||
}
|
||||
|
||||
export function PreviewCardContent({
|
||||
function PreviewCardContent({
|
||||
children,
|
||||
placement = 'bottom',
|
||||
sideOffset = 8,
|
||||
@ -76,3 +79,18 @@ export function PreviewCardContent({
|
||||
</BasePreviewCard.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
createPreviewCardHandle,
|
||||
PreviewCard,
|
||||
PreviewCardContent,
|
||||
PreviewCardTrigger,
|
||||
PreviewCardViewport,
|
||||
}
|
||||
export type {
|
||||
PreviewCardContentProps,
|
||||
PreviewCardHandle,
|
||||
PreviewCardProps,
|
||||
PreviewCardTriggerProps,
|
||||
PreviewCardViewportProps,
|
||||
}
|
||||
|
||||
@ -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<ProgressCircleProps['color']>
|
||||
type ProgressCircleSize = NonNullable<ProgressCircleProps['size']>
|
||||
|
||||
const colors: ProgressCircleColor[] = ['gray', 'white', 'blue', 'warning', 'error']
|
||||
const sizes: ProgressCircleSize[] = ['small', 'medium', 'large']
|
||||
|
||||
|
||||
@ -46,10 +46,8 @@ const progressCircleColorClasses = {
|
||||
},
|
||||
} as const
|
||||
|
||||
export type ProgressCircleSize = NonNullable<
|
||||
VariantProps<typeof progressCircleRootVariants>['size']
|
||||
>
|
||||
export type ProgressCircleColor = keyof typeof progressCircleColorClasses
|
||||
type ProgressCircleSize = NonNullable<VariantProps<typeof progressCircleRootVariants>['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({
|
||||
</BaseProgress.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { ProgressCircle }
|
||||
export type { ProgressCircleProps }
|
||||
|
||||
188
packages/dify-ui/src/public-types.test-d.ts
Normal file
188
packages/dify-ui/src/public-types.test-d.ts
Normal file
@ -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<Left, Right> =
|
||||
(<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2
|
||||
? true
|
||||
: false
|
||||
type Expect<Condition extends true> = Condition
|
||||
type Arguments<Callback> = Callback extends (...args: infer Values) => unknown ? Values : never
|
||||
type FirstArgument<Callback> = Arguments<Callback>[0]
|
||||
type SecondArgument<Callback> = Arguments<Callback>[1]
|
||||
type IsRequired<Props, Key extends keyof Props> =
|
||||
Record<never, never> extends Pick<Props, Key> ? false : true
|
||||
type Callable<Member> = 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<typeof SelectValue<DomainValue>>[0]
|
||||
type SelectMultipleValueProps = Parameters<typeof SelectValue<DomainValue, true>>[0]
|
||||
type SelectDynamicValueProps = Parameters<typeof SelectValue<DomainValue, boolean>>[0]
|
||||
type ComboboxSingleValueProps = Parameters<typeof ComboboxValue<DomainValue>>[0]
|
||||
type ComboboxMultipleValueProps = Parameters<typeof ComboboxValue<DomainValue, true>>[0]
|
||||
type ComboboxDynamicValueProps = Parameters<typeof ComboboxValue<DomainValue, boolean>>[0]
|
||||
type AutocompleteListRuntimeProps = Parameters<typeof AutocompleteList<DomainValue>>[0]
|
||||
type AutocompleteCollectionRuntimeProps = Parameters<typeof AutocompleteCollection<DomainValue>>[0]
|
||||
type AutocompleteGroupRuntimeProps = Parameters<typeof AutocompleteGroup<DomainValue>>[0]
|
||||
type ComboboxListRuntimeProps = Parameters<typeof ComboboxList<DomainValue>>[0]
|
||||
type ComboboxCollectionRuntimeProps = Parameters<typeof ComboboxCollection<DomainValue>>[0]
|
||||
type ComboboxGroupRuntimeProps = Parameters<typeof ComboboxGroup<DomainValue>>[0]
|
||||
|
||||
type PublicTypeAssertions = [
|
||||
Expect<Equal<FirstArgument<NonNullable<FormProps<FormValues>['onFormSubmit']>>, FormValues>>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<SelectProps<DomainValue, false>['onValueChange']>>,
|
||||
DomainValue | null
|
||||
>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<SelectProps<DomainValue, true>['onValueChange']>>,
|
||||
DomainValue[]
|
||||
>
|
||||
>,
|
||||
Expect<Equal<IsRequired<SelectProps<DomainValue, true>, 'multiple'>, true>>,
|
||||
Expect<Equal<IsRequired<SelectProps<DomainValue, boolean>, 'multiple'>, false>>,
|
||||
Expect<Equal<SelectItemProps<DomainValue>['value'], DomainValue | undefined>>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<Callable<SelectValueProps<DomainValue, true>['children']>>,
|
||||
DomainValue[] | null
|
||||
>
|
||||
>,
|
||||
Expect<Equal<FirstArgument<Callable<SelectSingleValueProps['children']>>, DomainValue | null>>,
|
||||
Expect<
|
||||
Equal<FirstArgument<Callable<SelectMultipleValueProps['children']>>, DomainValue[] | null>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<Callable<SelectDynamicValueProps['children']>>,
|
||||
DomainValue | DomainValue[] | null
|
||||
>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<ComboboxProps<DomainValue, true>['onValueChange']>>,
|
||||
DomainValue[]
|
||||
>
|
||||
>,
|
||||
Expect<Equal<IsRequired<ComboboxProps<DomainValue, true>, 'multiple'>, true>>,
|
||||
Expect<Equal<IsRequired<ComboboxProps<DomainValue, boolean>, 'multiple'>, false>>,
|
||||
Expect<Equal<ComboboxItemProps<DomainValue>['value'], DomainValue | undefined>>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<Callable<ComboboxValueProps<DomainValue, true>['children']>>,
|
||||
DomainValue[] | null
|
||||
>
|
||||
>,
|
||||
Expect<Equal<FirstArgument<Callable<ComboboxSingleValueProps['children']>>, DomainValue | null>>,
|
||||
Expect<
|
||||
Equal<FirstArgument<Callable<ComboboxMultipleValueProps['children']>>, DomainValue[] | null>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<Callable<ComboboxDynamicValueProps['children']>>,
|
||||
DomainValue | DomainValue[] | null
|
||||
>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<AutocompleteProps<DomainValue>['onItemHighlighted']>>,
|
||||
DomainValue | undefined
|
||||
>
|
||||
>,
|
||||
Expect<Equal<AutocompleteItemProps<DomainValue>['value'], DomainValue | undefined>>,
|
||||
Expect<
|
||||
Equal<FirstArgument<Callable<AutocompleteListProps<DomainValue>['children']>>, DomainValue>
|
||||
>,
|
||||
Expect<Equal<SecondArgument<Callable<AutocompleteListProps<DomainValue>['children']>>, number>>,
|
||||
Expect<Equal<FirstArgument<Callable<AutocompleteListRuntimeProps['children']>>, DomainValue>>,
|
||||
Expect<Equal<FirstArgument<AutocompleteCollectionProps<DomainValue>['children']>, DomainValue>>,
|
||||
Expect<Equal<SecondArgument<AutocompleteCollectionProps<DomainValue>['children']>, number>>,
|
||||
Expect<Equal<FirstArgument<AutocompleteCollectionRuntimeProps['children']>, DomainValue>>,
|
||||
Expect<Equal<AutocompleteGroupProps<DomainValue>['items'], readonly DomainValue[] | undefined>>,
|
||||
Expect<Equal<AutocompleteGroupRuntimeProps['items'], readonly DomainValue[] | undefined>>,
|
||||
Expect<Equal<FirstArgument<Callable<ComboboxListProps<DomainValue>['children']>>, DomainValue>>,
|
||||
Expect<Equal<SecondArgument<Callable<ComboboxListProps<DomainValue>['children']>>, number>>,
|
||||
Expect<Equal<FirstArgument<Callable<ComboboxListRuntimeProps['children']>>, DomainValue>>,
|
||||
Expect<Equal<FirstArgument<ComboboxCollectionProps<DomainValue>['children']>, DomainValue>>,
|
||||
Expect<Equal<SecondArgument<ComboboxCollectionProps<DomainValue>['children']>, number>>,
|
||||
Expect<Equal<FirstArgument<ComboboxCollectionRuntimeProps['children']>, DomainValue>>,
|
||||
Expect<Equal<ComboboxGroupProps<DomainValue>['items'], readonly DomainValue[] | undefined>>,
|
||||
Expect<Equal<ComboboxGroupRuntimeProps['items'], readonly DomainValue[] | undefined>>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<ContextMenuRadioGroupProps<DomainValue>['onValueChange']>>,
|
||||
DomainValue
|
||||
>
|
||||
>,
|
||||
Expect<Equal<ContextMenuRadioItemProps<DomainValue>['value'], DomainValue>>,
|
||||
Expect<
|
||||
Equal<
|
||||
FirstArgument<NonNullable<DropdownMenuRadioGroupProps<DomainValue>['onValueChange']>>,
|
||||
DomainValue
|
||||
>
|
||||
>,
|
||||
Expect<Equal<DropdownMenuRadioItemProps<DomainValue>['value'], DomainValue>>,
|
||||
Expect<
|
||||
Equal<FirstArgument<NonNullable<RadioGroupProps<DomainValue>['onValueChange']>>, DomainValue>
|
||||
>,
|
||||
Expect<Equal<RadioItemProps<DomainValue>['value'], DomainValue>>,
|
||||
Expect<Equal<SliderRootProps<RangeValue>['value'], RangeValue | undefined>>,
|
||||
Expect<
|
||||
Equal<FirstArgument<NonNullable<SliderRootProps<RangeValue>['onValueChange']>>, RangeValue>
|
||||
>,
|
||||
Expect<
|
||||
Equal<NonNullable<DialogTriggerProps<OverlayPayload>['handle']>, DialogHandle<OverlayPayload>>
|
||||
>,
|
||||
Expect<
|
||||
Equal<NonNullable<DrawerTriggerProps<OverlayPayload>['handle']>, DrawerHandle<OverlayPayload>>
|
||||
>,
|
||||
Expect<
|
||||
Equal<NonNullable<PopoverTriggerProps<OverlayPayload>['handle']>, PopoverHandle<OverlayPayload>>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
NonNullable<PreviewCardTriggerProps<OverlayPayload>['handle']>,
|
||||
PreviewCardHandle<OverlayPayload>
|
||||
>
|
||||
>,
|
||||
]
|
||||
|
||||
export type { PublicTypeAssertions }
|
||||
@ -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<Value = string> = Omit<BaseRadioGroupNS.Props<Value>, 'className'> & {
|
||||
type RadioGroupProps<Value = string> = Omit<BaseRadioGroupNS.Props<Value>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function RadioGroup<Value = string>({ className, ...props }: RadioGroupProps<Value>) {
|
||||
function RadioGroup<Value = string>({ className, ...props }: RadioGroupProps<Value>) {
|
||||
return <BaseRadioGroup<Value> className={cn('flex items-center gap-2', className)} {...props} />
|
||||
}
|
||||
|
||||
export type RadioItemProps<Value = string> = Omit<BaseRadioNS.Root.Props<Value>, 'className'> & {
|
||||
type RadioItemProps<Value = string> = Omit<BaseRadioNS.Root.Props<Value>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function RadioItem<Value = string>({ className, ...props }: RadioItemProps<Value>) {
|
||||
function RadioItem<Value = string>({ className, ...props }: RadioItemProps<Value>) {
|
||||
return <BaseRadio.Root<Value> 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 (
|
||||
<BaseRadio.Indicator
|
||||
{...props}
|
||||
@ -51,9 +51,9 @@ export function RadioControl({ className, ...props }: RadioControlProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type RadioProps<Value = string> = Omit<RadioItemProps<Value>, 'children'>
|
||||
type RadioProps<Value = string> = Omit<RadioItemProps<Value>, 'children'>
|
||||
|
||||
export function Radio<Value = string>({ className, ...props }: RadioProps<Value>) {
|
||||
function Radio<Value = string>({ className, ...props }: RadioProps<Value>) {
|
||||
return (
|
||||
<BaseRadio.Root<Value>
|
||||
className={cn(
|
||||
@ -73,9 +73,9 @@ export function Radio<Value = string>({ className, ...props }: RadioProps<Value>
|
||||
)
|
||||
}
|
||||
|
||||
export type RadioSkeletonProps = React.ComponentProps<'div'>
|
||||
type RadioSkeletonProps = React.ComponentProps<'div'>
|
||||
|
||||
export function RadioSkeleton({ className, ...props }: RadioSkeletonProps) {
|
||||
function RadioSkeleton({ className, ...props }: RadioSkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('size-4 shrink-0 rounded-full bg-text-quaternary opacity-20', className)}
|
||||
@ -83,3 +83,7 @@ export function RadioSkeleton({ className, ...props }: RadioSkeletonProps) {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Radio, RadioControl, RadioGroup, RadioItem, RadioSkeleton }
|
||||
|
||||
export type { RadioControlProps, RadioGroupProps, RadioItemProps, RadioProps, RadioSkeletonProps }
|
||||
|
||||
@ -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<BaseScrollArea.Scrollbar.Props['orientation']>
|
||||
|
||||
type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
|
||||
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<BaseScrollArea.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) {
|
||||
function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) {
|
||||
return (
|
||||
<BaseScrollArea.Viewport className={cn(scrollAreaViewportClassName, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
type ScrollAreaScrollbarProps = BaseScrollArea.Scrollbar.Props
|
||||
type ScrollAreaScrollbarProps = Omit<BaseScrollArea.Scrollbar.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) {
|
||||
function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) {
|
||||
return (
|
||||
<BaseScrollArea.Scrollbar
|
||||
data-dify-scrollbar=""
|
||||
@ -67,19 +73,23 @@ export function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbar
|
||||
)
|
||||
}
|
||||
|
||||
type ScrollAreaThumbProps = BaseScrollArea.Thumb.Props
|
||||
type ScrollAreaThumbProps = Omit<BaseScrollArea.Thumb.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ScrollAreaThumb({ className, ...props }: ScrollAreaThumbProps) {
|
||||
function ScrollAreaThumb({ className, ...props }: ScrollAreaThumbProps) {
|
||||
return <BaseScrollArea.Thumb className={cn(scrollAreaThumbClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
type ScrollAreaCornerProps = BaseScrollArea.Corner.Props
|
||||
type ScrollAreaCornerProps = Omit<BaseScrollArea.Corner.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) {
|
||||
function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) {
|
||||
return <BaseScrollArea.Corner className={cn(scrollAreaCornerClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function ScrollArea({
|
||||
function ScrollArea({
|
||||
children,
|
||||
className,
|
||||
orientation = 'vertical',
|
||||
@ -104,3 +114,23 @@ export function ScrollArea({
|
||||
</ScrollAreaRoot>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaCorner,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
}
|
||||
|
||||
export type {
|
||||
ScrollAreaContentProps,
|
||||
ScrollAreaCornerProps,
|
||||
ScrollAreaProps,
|
||||
ScrollAreaRootProps,
|
||||
ScrollAreaScrollbarProps,
|
||||
ScrollAreaThumbProps,
|
||||
ScrollAreaViewportProps,
|
||||
}
|
||||
|
||||
@ -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<Value extends string = string> = Omit<
|
||||
type SegmentedControlProps<Value extends string = string> = Omit<
|
||||
BaseToggleGroupNS.Props<Value>,
|
||||
'className'
|
||||
> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SegmentedControl<Value extends string = string>({
|
||||
function SegmentedControl<Value extends string = string>({
|
||||
className,
|
||||
...props
|
||||
}: SegmentedControlProps<Value>) {
|
||||
@ -29,14 +29,14 @@ export function SegmentedControl<Value extends string = string>({
|
||||
)
|
||||
}
|
||||
|
||||
export type SegmentedControlItemProps<Value extends string = string> = Omit<
|
||||
type SegmentedControlItemProps<Value extends string = string> = Omit<
|
||||
BaseToggleNS.Props<Value>,
|
||||
'className'
|
||||
> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SegmentedControlItem<Value extends string = string>({
|
||||
function SegmentedControlItem<Value extends string = string>({
|
||||
className,
|
||||
...props
|
||||
}: SegmentedControlItemProps<Value>) {
|
||||
@ -51,11 +51,11 @@ export function SegmentedControlItem<Value extends string = string>({
|
||||
)
|
||||
}
|
||||
|
||||
export type SegmentedControlDividerProps = Omit<React.ComponentProps<'span'>, 'className'> & {
|
||||
type SegmentedControlDividerProps = Omit<React.ComponentProps<'span'>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SegmentedControlDivider({ className, ...props }: SegmentedControlDividerProps) {
|
||||
function SegmentedControlDivider({ className, ...props }: SegmentedControlDividerProps) {
|
||||
return (
|
||||
<span
|
||||
role="presentation"
|
||||
@ -65,3 +65,7 @@ export function SegmentedControlDivider({ className, ...props }: SegmentedContro
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { SegmentedControl, SegmentedControlDivider, SegmentedControlItem }
|
||||
|
||||
export type { SegmentedControlDividerProps, SegmentedControlItemProps, SegmentedControlProps }
|
||||
|
||||
@ -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(
|
||||
<Select<string, true> multiple value={null}>
|
||||
<SelectTrigger aria-label="city select">
|
||||
<SelectValue<string, true>>
|
||||
{(selectedValue) =>
|
||||
selectedValue === null ? 'No cities selected' : selectedValue.join(', ')
|
||||
}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem<string> value="seattle">
|
||||
<SelectItemText>Seattle</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>,
|
||||
)
|
||||
|
||||
await expect.element(screen.getByText('No cities selected')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('SelectTrigger', () => {
|
||||
|
||||
@ -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: () => <ControlledDemo />,
|
||||
}
|
||||
|
||||
const MultipleControlledDemo = () => {
|
||||
const [value, setValue] = React.useState<DeploymentRegion[]>(['us-east', 'eu-west'])
|
||||
|
||||
return (
|
||||
<div className={triggerWidth}>
|
||||
<Select<DeploymentRegion, true>
|
||||
items={deploymentRegionItems}
|
||||
multiple
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
>
|
||||
<SelectLabel>Deployment regions</SelectLabel>
|
||||
<SelectTrigger>
|
||||
<SelectValue<DeploymentRegion, true>>
|
||||
{(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
|
||||
}}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent listProps={{ 'aria-label': 'Deployment region options' }}>
|
||||
{deploymentRegionItems.map((item) => (
|
||||
<SelectItem<DeploymentRegion> key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.label}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const MultipleControlled: Story = {
|
||||
render: () => <MultipleControlledDemo />,
|
||||
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: () => (
|
||||
<Form aria-label="Timezone form" className="grid w-72 gap-3" onFormSubmit={() => undefined}>
|
||||
|
||||
@ -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<Value, Multiple extends boolean | undefined = false> = BaseSelect.Root.Props<
|
||||
Value,
|
||||
Multiple extends boolean | undefined = false,
|
||||
> = BaseSelect.Root.Props<Value, Multiple>
|
||||
Multiple
|
||||
> &
|
||||
([Multiple] extends [true] ? { multiple: true } : unknown)
|
||||
|
||||
export function Select<Value, Multiple extends boolean | undefined = false>(
|
||||
function Select<Value, Multiple extends boolean | undefined = false>(
|
||||
props: SelectProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseSelect.Root {...props} />
|
||||
}
|
||||
|
||||
export const SelectValue = BaseSelect.Value
|
||||
export const SelectGroup = BaseSelect.Group
|
||||
const SelectGroup = BaseSelect.Group
|
||||
|
||||
type SelectSelectedValue<Value, Multiple extends boolean | undefined = false> =
|
||||
| (Multiple extends true ? Value[] : Value)
|
||||
| null
|
||||
type SelectValueState<Value = unknown, Multiple extends boolean | undefined = false> = Omit<
|
||||
BaseSelect.Value.State,
|
||||
'value'
|
||||
> & {
|
||||
value: SelectSelectedValue<Value, Multiple>
|
||||
}
|
||||
type SelectValueProps<Value = unknown, Multiple extends boolean | undefined = false> = Omit<
|
||||
BaseSelect.Value.Props,
|
||||
'children' | 'className' | 'render' | 'style'
|
||||
> & {
|
||||
children?: React.ReactNode | ((value: SelectSelectedValue<Value, Multiple>) => React.ReactNode)
|
||||
className?: string | ((state: SelectValueState<Value, Multiple>) => string | undefined)
|
||||
render?:
|
||||
| React.ReactElement
|
||||
| ComponentRenderFn<HTMLProps<HTMLSpanElement>, SelectValueState<Value, Multiple>>
|
||||
style?:
|
||||
| React.CSSProperties
|
||||
| ((state: SelectValueState<Value, Multiple>) => React.CSSProperties | undefined)
|
||||
}
|
||||
function SelectValue<Value = unknown, Multiple extends boolean | undefined = false>(
|
||||
props: SelectValueProps<Value, Multiple>,
|
||||
): React.JSX.Element
|
||||
function SelectValue(props: BaseSelect.Value.Props): React.JSX.Element {
|
||||
return <BaseSelect.Value {...props} />
|
||||
}
|
||||
type SelectGroupProps = BaseSelect.Group.Props
|
||||
|
||||
const selectTriggerVariants = cva(
|
||||
[
|
||||
@ -55,10 +84,13 @@ const selectTriggerVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type SelectTriggerProps = Omit<BaseSelect.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof selectTriggerVariants> & { className?: string }
|
||||
type SelectSize = NonNullable<VariantProps<typeof selectTriggerVariants>['size']>
|
||||
type SelectTriggerProps = Omit<BaseSelect.Trigger.Props, 'className'> & {
|
||||
className?: string
|
||||
size?: SelectSize
|
||||
}
|
||||
|
||||
export function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) {
|
||||
function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) {
|
||||
return (
|
||||
<BaseSelect.Trigger className={cn(selectTriggerVariants({ size, className }))} {...props}>
|
||||
<span className="min-w-0 grow truncate">{children}</span>
|
||||
@ -69,18 +101,33 @@ export function SelectTrigger({ className, children, size, ...props }: SelectTri
|
||||
)
|
||||
}
|
||||
|
||||
export function SelectLabel({ className, ...props }: BaseSelect.Label.Props) {
|
||||
type SelectLabelProps = Omit<BaseSelect.Label.Props, 'className'> & { className?: string }
|
||||
|
||||
function SelectLabel({ className, ...props }: SelectLabelProps) {
|
||||
return <BaseSelect.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function SelectGroupLabel({ className, ...props }: BaseSelect.GroupLabel.Props) {
|
||||
type SelectGroupLabelProps = Omit<BaseSelect.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function SelectGroupLabel({ className, ...props }: SelectGroupLabelProps) {
|
||||
return <BaseSelect.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
export function SelectSeparator({ className, ...props }: BaseSelect.Separator.Props) {
|
||||
type SelectSeparatorProps = Omit<BaseSelect.Separator.Props, 'className'> & { className?: string }
|
||||
|
||||
function SelectSeparator({ className, ...props }: SelectSeparatorProps) {
|
||||
return <BaseSelect.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
type SelectContentPositionerProps = Omit<
|
||||
BaseSelect.Positioner.Props,
|
||||
'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset'
|
||||
>
|
||||
type SelectContentPopupProps = Omit<BaseSelect.Popup.Props, 'children' | 'className'>
|
||||
type SelectContentListProps = Omit<BaseSelect.List.Props, 'children' | 'className'>
|
||||
|
||||
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<BaseSelect.Popup.Props, 'children' | 'className'>
|
||||
listProps?: Omit<BaseSelect.List.Props, 'children' | 'className'>
|
||||
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<Value = unknown> = Omit<BaseSelect.Item.Props, 'className' | 'value'> & {
|
||||
className?: string
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function SelectItem<Value = unknown>({ className, ...props }: SelectItemProps<Value>) {
|
||||
return (
|
||||
<BaseSelect.Item
|
||||
className={cn(
|
||||
@ -155,19 +204,50 @@ export function SelectItem({ className, ...props }: BaseSelect.Item.Props) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SelectItemText({ className, ...props }: BaseSelect.ItemText.Props) {
|
||||
type SelectItemTextProps = Omit<BaseSelect.ItemText.Props, 'className'> & { className?: string }
|
||||
|
||||
function SelectItemText({ className, ...props }: SelectItemTextProps) {
|
||||
return (
|
||||
<BaseSelect.ItemText className={cn('me-1 min-w-0 grow truncate px-1', className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function SelectItemIndicator({
|
||||
className,
|
||||
...props
|
||||
}: Omit<BaseSelect.ItemIndicator.Props, 'children'>) {
|
||||
type SelectItemIndicatorProps = Omit<BaseSelect.ItemIndicator.Props, 'children' | 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function SelectItemIndicator({ className, ...props }: SelectItemIndicatorProps) {
|
||||
return (
|
||||
<BaseSelect.ItemIndicator className={cn(floatingItemIndicatorClassName, className)} {...props}>
|
||||
<span className="i-ri-check-line h-4 w-4" aria-hidden />
|
||||
</BaseSelect.ItemIndicator>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@ -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<typeof Slider>) {
|
||||
}: SliderProps) {
|
||||
const [value, setValue] = React.useState(initialValue)
|
||||
|
||||
return (
|
||||
@ -107,3 +108,37 @@ export const ComposedWithLabel: Story = {
|
||||
</SliderRoot>
|
||||
),
|
||||
}
|
||||
|
||||
type PriceRange = readonly [number, number]
|
||||
|
||||
function RangeSliderDemo() {
|
||||
const [range, setRange] = React.useState<PriceRange>([25, 75])
|
||||
|
||||
return (
|
||||
<div className="w-[320px] space-y-3">
|
||||
<SliderRoot<PriceRange>
|
||||
value={range}
|
||||
onValueChange={setRange}
|
||||
min={0}
|
||||
max={100}
|
||||
className="group/slider relative inline-flex w-full flex-col gap-1 data-disabled:opacity-30"
|
||||
>
|
||||
<SliderLabel>Price range</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderIndicator />
|
||||
</SliderTrack>
|
||||
<SliderThumb aria-label="Minimum price" />
|
||||
<SliderThumb aria-label="Maximum price" />
|
||||
</SliderControl>
|
||||
</SliderRoot>
|
||||
<div className="text-center system-sm-medium text-text-secondary">
|
||||
{range[0]} – {range[1]}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Range: Story = {
|
||||
render: () => <RangeSliderDemo />,
|
||||
}
|
||||
|
||||
@ -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<Value extends SliderRootValue = SliderRootValue> = BaseSlider.Root.Props<Value>
|
||||
|
||||
type SliderLabelProps = Omit<BaseSlider.Label.Props, 'className'> & { className?: string }
|
||||
|
||||
function SliderLabel({ className, ...props }: SliderLabelProps) {
|
||||
return <BaseSlider.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
type SliderRootProps = BaseSlider.Root.Props<number>
|
||||
|
||||
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<BaseSlider.Control.Props, 'className'> & { className?: string }
|
||||
|
||||
export function SliderControl({ className, ...props }: SliderControlProps) {
|
||||
function SliderControl({ className, ...props }: SliderControlProps) {
|
||||
return <BaseSlider.Control className={cn(sliderControlClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
@ -28,17 +33,19 @@ const sliderTrackClassName = cn(
|
||||
'bg-components-slider-track',
|
||||
)
|
||||
|
||||
type SliderTrackProps = BaseSlider.Track.Props
|
||||
type SliderTrackProps = Omit<BaseSlider.Track.Props, 'className'> & { className?: string }
|
||||
|
||||
export function SliderTrack({ className, ...props }: SliderTrackProps) {
|
||||
function SliderTrack({ className, ...props }: SliderTrackProps) {
|
||||
return <BaseSlider.Track className={cn(sliderTrackClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
const sliderIndicatorClassName = cn('h-full rounded-full', 'bg-components-slider-range')
|
||||
|
||||
type SliderIndicatorProps = BaseSlider.Indicator.Props
|
||||
type SliderIndicatorProps = Omit<BaseSlider.Indicator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function SliderIndicator({ className, ...props }: SliderIndicatorProps) {
|
||||
function SliderIndicator({ className, ...props }: SliderIndicatorProps) {
|
||||
return <BaseSlider.Indicator className={cn(sliderIndicatorClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
@ -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<BaseSlider.Thumb.Props, 'className'> & { className?: string }
|
||||
|
||||
export function SliderThumb({ className, ...props }: SliderThumbProps) {
|
||||
function SliderThumb({ className, ...props }: SliderThumbProps) {
|
||||
return <BaseSlider.Thumb className={cn(sliderThumbClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
@ -66,7 +73,7 @@ type SliderSlotClassNames = {
|
||||
}
|
||||
|
||||
type SliderBaseProps = Pick<
|
||||
SliderRootProps,
|
||||
SliderRootProps<SliderValue>,
|
||||
'onValueChange' | 'min' | 'max' | 'step' | 'disabled' | 'name'
|
||||
> &
|
||||
Pick<SliderThumbProps, 'aria-label' | 'aria-labelledby'> & {
|
||||
@ -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({
|
||||
</SliderRoot>
|
||||
)
|
||||
}
|
||||
|
||||
export { Slider, SliderControl, SliderIndicator, SliderLabel, SliderRoot, SliderThumb, SliderTrack }
|
||||
|
||||
export type {
|
||||
SliderControlProps,
|
||||
SliderIndicatorProps,
|
||||
SliderLabelProps,
|
||||
SliderProps,
|
||||
SliderRootProps,
|
||||
SliderThumbProps,
|
||||
SliderTrackProps,
|
||||
}
|
||||
|
||||
@ -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<StatusDotProps['size']>
|
||||
|
||||
const statuses: StatusDotStatus[] = ['success', 'warning', 'error', 'normal', 'disabled']
|
||||
const sizes: StatusDotSize[] = ['small', 'medium']
|
||||
|
||||
|
||||
@ -47,19 +47,19 @@ const statusDotSkeletonVariants = cva(
|
||||
|
||||
type StatusDotVariants = VariantProps<typeof statusDotVariants>
|
||||
|
||||
export type StatusDotStatus = NonNullable<StatusDotVariants['status']>
|
||||
export type StatusDotSize = NonNullable<StatusDotVariants['size']>
|
||||
type StatusDotStatus = NonNullable<StatusDotVariants['status']>
|
||||
type StatusDotSize = NonNullable<StatusDotVariants['size']>
|
||||
|
||||
export type StatusDotProps = Omit<React.ComponentProps<'span'>, 'children'> & {
|
||||
type StatusDotProps = Omit<React.ComponentProps<'span'>, 'children'> & {
|
||||
status?: StatusDotStatus
|
||||
size?: StatusDotSize
|
||||
}
|
||||
|
||||
export type StatusDotSkeletonProps = Omit<React.ComponentProps<'span'>, 'children'> & {
|
||||
type StatusDotSkeletonProps = Omit<React.ComponentProps<'span'>, '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 }
|
||||
|
||||
@ -44,8 +44,6 @@ const switchThumbVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
export type SwitchSize = NonNullable<VariantProps<typeof switchRootVariants>['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<typeof switchSkeletonVariants>
|
||||
type SwitchSkeletonProps = React.ComponentProps<'div'> & VariantProps<typeof switchSkeletonVariants>
|
||||
|
||||
export function SwitchSkeleton({ size = 'md', className, ...props }: SwitchSkeletonProps) {
|
||||
function SwitchSkeleton({ size = 'md', className, ...props }: SwitchSkeletonProps) {
|
||||
return <div className={cn(switchSkeletonVariants({ size }), className)} {...props} />
|
||||
}
|
||||
|
||||
export { Switch, SwitchSkeleton }
|
||||
|
||||
export type { SwitchProps, SwitchSkeletonProps }
|
||||
|
||||
@ -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<BaseTabsNS.List.Props, 'className'> & {
|
||||
type TabsListProps = Omit<BaseTabsNS.List.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function TabsList({ className, ...props }: TabsListProps) {
|
||||
function TabsList({ className, ...props }: TabsListProps) {
|
||||
return <BaseTabs.List className={cn('flex gap-4', className)} {...props} />
|
||||
}
|
||||
|
||||
export type TabsTabProps = Omit<BaseTabsNS.Tab.Props, 'className'> & {
|
||||
type TabsTabProps = Omit<BaseTabsNS.Tab.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function TabsTab({ className, ...props }: TabsTabProps) {
|
||||
function TabsTab({ className, ...props }: TabsTabProps) {
|
||||
return (
|
||||
<BaseTabs.Tab
|
||||
className={cn(
|
||||
@ -32,12 +31,12 @@ export function TabsTab({ className, ...props }: TabsTabProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type TabsPanelProps = Omit<BaseTabsNS.Panel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type TabsPanelProps = BaseTabsNS.Panel.Props
|
||||
const TabsPanel = BaseTabs.Panel
|
||||
|
||||
export function TabsPanel({ className, ...props }: TabsPanelProps) {
|
||||
return <BaseTabs.Panel className={className} {...props} />
|
||||
}
|
||||
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 }
|
||||
|
||||
@ -35,8 +35,7 @@ const textareaVariants = cva(
|
||||
)
|
||||
|
||||
type TextareaValue = string | number
|
||||
export type TextareaSize = NonNullable<VariantProps<typeof textareaVariants>['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 }
|
||||
|
||||
@ -141,7 +141,7 @@ function promiseToast<Value>(promiseValue: Promise<Value>, 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 (
|
||||
<BaseToast.Provider toastManager={toastManager} timeout={timeout} limit={limit}>
|
||||
<BaseToast.Portal>
|
||||
@ -260,3 +260,7 @@ export function ToastHost({ timeout, limit }: ToastHostProps) {
|
||||
</BaseToast.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { toast, ToastHost }
|
||||
|
||||
export type { ToastHostProps }
|
||||
|
||||
@ -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<TooltipContentProps['placement']>
|
||||
|
||||
const PLACEMENTS: TooltipPlacement[] = [
|
||||
'top-start',
|
||||
'top',
|
||||
'top-end',
|
||||
@ -107,7 +109,7 @@ const PLACEMENTS: Placement[] = [
|
||||
]
|
||||
|
||||
const PlacementsDemo = () => {
|
||||
const [placement, setPlacement] = React.useState<Placement>('top')
|
||||
const [placement, setPlacement] = React.useState<TooltipPlacement>('top')
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 p-24">
|
||||
|
||||
@ -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<Payload = unknown> = BaseTooltip.Root.Props<Payload>
|
||||
type TooltipTriggerProps<Payload = unknown> = BaseTooltip.Trigger.Props<Payload>
|
||||
|
||||
type TooltipContentProps = {
|
||||
children: React.ReactNode
|
||||
@ -40,7 +42,7 @@ type TooltipContentProps = {
|
||||
className?: string
|
||||
} & Omit<BaseTooltip.Popup.Props, 'children' | 'className'>
|
||||
|
||||
export function TooltipContent({
|
||||
function TooltipContent({
|
||||
children,
|
||||
placement = 'top',
|
||||
sideOffset = 8,
|
||||
@ -74,3 +76,7 @@ export function TooltipContent({
|
||||
</BaseTooltip.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|
||||
|
||||
export type { TooltipContentProps, TooltipProps, TooltipProviderProps, TooltipTriggerProps }
|
||||
|
||||
@ -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 = ({
|
||||
<Button
|
||||
key={action.id}
|
||||
disabled={isActionDisabled}
|
||||
variant={getButtonStyle(action.button_style) as ButtonProps['variant']}
|
||||
variant={getButtonStyle(action.button_style)}
|
||||
onClick={() => submit(action.id)}
|
||||
>
|
||||
{action.title}
|
||||
|
||||
@ -163,10 +163,8 @@ export default function AddMemberOrGroupDialog() {
|
||||
)}
|
||||
{hasResults ? (
|
||||
<>
|
||||
<ComboboxList className="max-h-none p-1">
|
||||
{(subject: Subject) => (
|
||||
<SubjectItem key={getSubjectValue(subject)} subject={subject} />
|
||||
)}
|
||||
<ComboboxList<Subject> className="max-h-none p-1">
|
||||
{(subject) => <SubjectItem key={getSubjectValue(subject)} subject={subject} />}
|
||||
</ComboboxList>
|
||||
{isFetchingNextPage && <Loading />}
|
||||
<div ref={anchorRef} className="h-0" />
|
||||
|
||||
@ -20,16 +20,6 @@ type AppSortFilterProps = {
|
||||
onChange: (value: AppListSortBy) => void
|
||||
}
|
||||
|
||||
const appListSortByValues: AppListSortBy[] = [
|
||||
'last_modified',
|
||||
'recently_created',
|
||||
'earliest_created',
|
||||
]
|
||||
|
||||
function isAppListSortBy(value: string): value is AppListSortBy {
|
||||
return appListSortByValues.includes(value as AppListSortBy)
|
||||
}
|
||||
|
||||
export function AppSortFilter({ value, onChange }: AppSortFilterProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -65,12 +55,16 @@ export function AppSortFilter({ value, onChange }: AppSortFilterProps) {
|
||||
<span aria-hidden className="i-ri-arrow-down-s-line size-4 shrink-0 text-text-tertiary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-start" sideOffset={4} popupClassName="w-[220px]">
|
||||
<DropdownMenuRadioGroup
|
||||
<DropdownMenuRadioGroup<AppListSortBy>
|
||||
value={value}
|
||||
onValueChange={(nextValue) => isAppListSortBy(nextValue) && onChange(nextValue)}
|
||||
onValueChange={(nextValue) => onChange(nextValue)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<DropdownMenuRadioItem key={option.value} value={option.value} closeOnClick>
|
||||
<DropdownMenuRadioItem<AppListSortBy>
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
closeOnClick
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{option.text}</span>
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
|
||||
@ -2,14 +2,7 @@ import { parseAsStringLiteral } from 'nuqs'
|
||||
import { AppModes } from '@/types/app'
|
||||
|
||||
const APP_LIST_CATEGORY_VALUES = ['all', ...AppModes] as const
|
||||
type AppListCategory = (typeof APP_LIST_CATEGORY_VALUES)[number]
|
||||
export type { AppListCategory }
|
||||
|
||||
const appListCategorySet = new Set<string>(APP_LIST_CATEGORY_VALUES)
|
||||
|
||||
export const isAppListCategory = (value: string): value is AppListCategory => {
|
||||
return appListCategorySet.has(value)
|
||||
}
|
||||
export type AppListCategory = (typeof APP_LIST_CATEGORY_VALUES)[number]
|
||||
|
||||
export const parseAsAppListCategory = parseAsStringLiteral(APP_LIST_CATEGORY_VALUES)
|
||||
.withDefault('all')
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { isAppListCategory } from './app-type-filter-shared'
|
||||
|
||||
const chipClassName =
|
||||
'flex h-8 items-center whitespace-nowrap rounded-lg border-[0.5px] px-2 text-[13px] leading-4 outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
@ -27,38 +26,39 @@ export function AppTypeFilter({ value, onChange }: AppTypeFilterProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const options = useMemo(
|
||||
() => [
|
||||
{
|
||||
value: 'all',
|
||||
text: t(($) => $['types.all'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-apps-2-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.WORKFLOW,
|
||||
text: t(($) => $['types.workflow'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-exchange-2-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.ADVANCED_CHAT,
|
||||
text: t(($) => $['types.advanced'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-message-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.CHAT,
|
||||
text: t(($) => $['types.chatbot'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-message-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.AGENT_CHAT,
|
||||
text: t(($) => $['types.agent'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-robot-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.COMPLETION,
|
||||
text: t(($) => $['newApp.completeApp'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-file-4-line',
|
||||
},
|
||||
],
|
||||
() =>
|
||||
[
|
||||
{
|
||||
value: 'all',
|
||||
text: t(($) => $['types.all'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-apps-2-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.WORKFLOW,
|
||||
text: t(($) => $['types.workflow'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-exchange-2-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.ADVANCED_CHAT,
|
||||
text: t(($) => $['types.advanced'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-message-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.CHAT,
|
||||
text: t(($) => $['types.chatbot'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-message-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.AGENT_CHAT,
|
||||
text: t(($) => $['types.agent'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-robot-3-line',
|
||||
},
|
||||
{
|
||||
value: AppModeEnum.COMPLETION,
|
||||
text: t(($) => $['newApp.completeApp'], { ns: 'app' }),
|
||||
iconClassName: 'i-ri-file-4-line',
|
||||
},
|
||||
] satisfies Array<{ value: AppListCategory; text: string; iconClassName: string }>,
|
||||
[t],
|
||||
)
|
||||
|
||||
@ -87,12 +87,12 @@ export function AppTypeFilter({ value, onChange }: AppTypeFilterProps) {
|
||||
<span aria-hidden className="i-ri-arrow-down-s-line h-4 w-4 shrink-0 text-text-tertiary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-start" popupClassName="w-[220px]">
|
||||
<DropdownMenuRadioGroup
|
||||
<DropdownMenuRadioGroup<AppListCategory>
|
||||
value={value}
|
||||
onValueChange={(nextValue) => isAppListCategory(nextValue) && onChange(nextValue)}
|
||||
onValueChange={(nextValue) => onChange(nextValue)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
||||
<DropdownMenuRadioItem<AppListCategory> key={option.value} value={option.value}>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn('h-4 w-4 shrink-0 text-text-tertiary', option.iconClassName)}
|
||||
|
||||
@ -103,7 +103,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
/>
|
||||
)}
|
||||
{form.type === InputVarType.select && (
|
||||
<Select
|
||||
<Select<string>
|
||||
value={(inputsFormValue?.[form.variable] ?? form.default ?? '') || null}
|
||||
onValueChange={(value) => value && handleFormChange(form.variable, value)}
|
||||
>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { HumanInputFieldValue } from './field-renderer'
|
||||
import type { HumanInputFormProps } from './type'
|
||||
import type { UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
@ -63,7 +62,7 @@ const HumanInputForm = ({ formData, onSubmit }: HumanInputFormProps) => {
|
||||
<Button
|
||||
key={action.id}
|
||||
disabled={isActionDisabled}
|
||||
variant={getButtonStyle(action.button_style) as ButtonProps['variant']}
|
||||
variant={getButtonStyle(action.button_style)}
|
||||
onClick={() => formToken && submit(formToken, action.id, inputs)}
|
||||
>
|
||||
{action.title}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { HumanInputFieldValue } from './field-renderer'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
@ -24,7 +25,7 @@ dayjs.extend(utc)
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(isSameOrAfter)
|
||||
|
||||
export const getButtonStyle = (style: UserActionButtonType) => {
|
||||
export const getButtonStyle = (style: UserActionButtonType): ButtonProps['variant'] => {
|
||||
if (style === UserActionButtonType.Primary) return 'primary'
|
||||
if (style === UserActionButtonType.Default) return 'secondary'
|
||||
if (style === UserActionButtonType.Accent) return 'secondary-accent'
|
||||
|
||||
@ -107,7 +107,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
/>
|
||||
)}
|
||||
{form.type === InputVarType.select && (
|
||||
<Select
|
||||
<Select<string>
|
||||
value={(inputsFormValue?.[form.variable] ?? form.default ?? '') || null}
|
||||
onValueChange={(value) => value && handleFormChange(form.variable, value)}
|
||||
>
|
||||
|
||||
@ -13,7 +13,7 @@ import { useStore } from '../store'
|
||||
type FileFromLinkOrLocalProps = {
|
||||
showFromLink?: boolean
|
||||
showFromLocal?: boolean
|
||||
trigger: (open: boolean) => React.ReactNode
|
||||
trigger: (open: boolean) => React.ReactElement
|
||||
fileConfig: FileUpload
|
||||
}
|
||||
const FileFromLinkOrLocal = ({
|
||||
@ -49,7 +49,7 @@ const FileFromLinkOrLocal = ({
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger render={trigger(open) as React.ReactElement} />
|
||||
<PopoverTrigger render={trigger(open)} />
|
||||
<PopoverContent
|
||||
placement="top"
|
||||
sideOffset={4}
|
||||
|
||||
@ -277,7 +277,7 @@ const BaseField = ({
|
||||
)}
|
||||
{formItemType === FormTypeEnum.select &&
|
||||
(multiple ? (
|
||||
<Select
|
||||
<Select<string, true>
|
||||
multiple
|
||||
items={memorizedOptions}
|
||||
value={Array.isArray(value) ? value : []}
|
||||
@ -289,9 +289,9 @@ const BaseField = ({
|
||||
aria-label={translatedLabel || field.name}
|
||||
className="px-2"
|
||||
>
|
||||
<SelectValue placeholder={translatedPlaceholder}>
|
||||
{(selectedValue: string[]) =>
|
||||
selectedValue.length
|
||||
<SelectValue<string, true> placeholder={translatedPlaceholder}>
|
||||
{(selectedValue) =>
|
||||
selectedValue?.length
|
||||
? t(($) => $['dynamicSelect.selected'], {
|
||||
ns: 'common',
|
||||
count: selectedValue.length,
|
||||
@ -310,7 +310,7 @@ const BaseField = ({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Select
|
||||
<Select<string>
|
||||
items={memorizedOptions}
|
||||
value={getSingleSelectValue(value, memorizedOptions)}
|
||||
disabled={disabled}
|
||||
@ -324,7 +324,7 @@ const BaseField = ({
|
||||
aria-label={translatedLabel || field.name}
|
||||
className="px-2"
|
||||
>
|
||||
<SelectValue placeholder={translatedPlaceholder}>
|
||||
<SelectValue<string> placeholder={translatedPlaceholder}>
|
||||
{(nextValue) =>
|
||||
getSingleSelectLabel(nextValue, memorizedOptions, translatedPlaceholder)
|
||||
}
|
||||
@ -352,7 +352,7 @@ const BaseField = ({
|
||||
)}
|
||||
{formItemType === FormTypeEnum.dynamicSelect &&
|
||||
(multiple ? (
|
||||
<Select
|
||||
<Select<string, true>
|
||||
multiple
|
||||
items={dynamicOptions}
|
||||
value={Array.isArray(value) ? value : []}
|
||||
@ -364,9 +364,9 @@ const BaseField = ({
|
||||
aria-label={translatedLabel || field.name}
|
||||
className="px-2"
|
||||
>
|
||||
<SelectValue placeholder={dynamicPlaceholder}>
|
||||
{(selectedValue: string[]) =>
|
||||
selectedValue.length
|
||||
<SelectValue<string, true> placeholder={dynamicPlaceholder}>
|
||||
{(selectedValue) =>
|
||||
selectedValue?.length
|
||||
? t(($) => $['dynamicSelect.selected'], {
|
||||
ns: 'common',
|
||||
count: selectedValue.length,
|
||||
@ -395,7 +395,7 @@ const BaseField = ({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Select
|
||||
<Select<string>
|
||||
items={dynamicOptions}
|
||||
value={getSingleSelectValue(value, dynamicOptions)}
|
||||
disabled={disabled || isDynamicOptionsLoading}
|
||||
@ -409,7 +409,7 @@ const BaseField = ({
|
||||
aria-label={translatedLabel || field.name}
|
||||
className="px-2"
|
||||
>
|
||||
<SelectValue placeholder={dynamicPlaceholder}>
|
||||
<SelectValue<string> placeholder={dynamicPlaceholder}>
|
||||
{(nextValue) =>
|
||||
getSingleSelectLabel(nextValue, dynamicOptions, dynamicPlaceholder)
|
||||
}
|
||||
|
||||
@ -71,8 +71,8 @@ const SelectField = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id={field.name} className="px-2">
|
||||
<SelectValue placeholder={placeholderText}>
|
||||
{(nextValue: string | null) => getDisplayLabel(nextValue, options, placeholderText)}
|
||||
<SelectValue<string> placeholder={placeholderText}>
|
||||
{(nextValue) => getDisplayLabel(nextValue, options, placeholderText)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName={cn('bg-components-panel-bg-blur', popupProps?.className)}>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ExtraProps } from 'streamdown'
|
||||
import type { ChatContextValue } from '@/app/components/base/chat/chat/context'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
@ -13,14 +14,15 @@ vi.mock('../utils', () => ({
|
||||
isValidUrl: (u: string) => isValidUrlSpy(u),
|
||||
})) // test subject
|
||||
|
||||
type TestNode = {
|
||||
properties?: {
|
||||
dataVariant?: string
|
||||
dataMessage?: string
|
||||
dataLink?: string
|
||||
dataSize?: string
|
||||
type TestNode = NonNullable<ExtraProps['node']>
|
||||
|
||||
function createButtonNode(properties: TestNode['properties'], label?: string): TestNode {
|
||||
return {
|
||||
type: 'element',
|
||||
tagName: 'button',
|
||||
properties,
|
||||
children: label === undefined ? [] : [{ type: 'text', value: label }],
|
||||
}
|
||||
children?: Array<{ value?: string }>
|
||||
}
|
||||
|
||||
describe('MarkdownButton (integration)', () => {
|
||||
@ -39,13 +41,13 @@ describe('MarkdownButton (integration)', () => {
|
||||
|
||||
return render(
|
||||
<ChatContextProvider {...ctx}>
|
||||
<MarkdownButton node={node as unknown as Record<string, unknown>} />
|
||||
<MarkdownButton node={node} />
|
||||
</ChatContextProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
it('renders button text from node children', () => {
|
||||
const node: TestNode = { children: [{ value: 'Click me' }], properties: {} }
|
||||
const node = createButtonNode({}, 'Click me')
|
||||
renderWithCtx(node)
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Click me')
|
||||
})
|
||||
@ -55,10 +57,7 @@ describe('MarkdownButton (integration)', () => {
|
||||
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
|
||||
const user = userEvent.setup()
|
||||
|
||||
const node: TestNode = {
|
||||
properties: { dataLink: 'https://example.com' },
|
||||
children: [{ value: 'Go' }],
|
||||
}
|
||||
const node = createButtonNode({ dataLink: 'https://example.com' }, 'Go')
|
||||
|
||||
renderWithCtx(node)
|
||||
await user.click(screen.getByRole('button'))
|
||||
@ -74,10 +73,7 @@ describe('MarkdownButton (integration)', () => {
|
||||
isValidUrlSpy.mockReturnValue(false)
|
||||
const user = userEvent.setup()
|
||||
|
||||
const node: TestNode = {
|
||||
properties: { dataLink: 'not-a-url', dataMessage: 'hello!' },
|
||||
children: [{ value: 'Send' }],
|
||||
}
|
||||
const node = createButtonNode({ dataLink: 'not-a-url', dataMessage: 'hello!' }, 'Send')
|
||||
|
||||
renderWithCtx(node)
|
||||
await user.click(screen.getByRole('button'))
|
||||
@ -91,7 +87,7 @@ describe('MarkdownButton (integration)', () => {
|
||||
isValidUrlSpy.mockReturnValue(false)
|
||||
const user = userEvent.setup()
|
||||
|
||||
const node: TestNode = { properties: {}, children: [{ value: 'Empty' }] }
|
||||
const node = createButtonNode({}, 'Empty')
|
||||
renderWithCtx(node)
|
||||
await user.click(screen.getByRole('button'))
|
||||
|
||||
@ -101,10 +97,7 @@ describe('MarkdownButton (integration)', () => {
|
||||
|
||||
it('calls onSend when message present and no link', async () => {
|
||||
const user = userEvent.setup()
|
||||
const node: TestNode = {
|
||||
properties: { dataMessage: 'msg-only' },
|
||||
children: [{ value: 'Msg' }],
|
||||
}
|
||||
const node = createButtonNode({ dataMessage: 'msg-only' }, 'Msg')
|
||||
|
||||
renderWithCtx(node)
|
||||
await user.click(screen.getByRole('button'))
|
||||
@ -113,9 +106,17 @@ describe('MarkdownButton (integration)', () => {
|
||||
})
|
||||
|
||||
it('falls back to empty label when first child value is missing', () => {
|
||||
const node: TestNode = { properties: {}, children: [{}] }
|
||||
const node = createButtonNode({})
|
||||
renderWithCtx(node)
|
||||
|
||||
expect(screen.getByRole('button')).toHaveTextContent('')
|
||||
})
|
||||
|
||||
it('maps the legacy warning variant to a destructive primary button', () => {
|
||||
renderWithCtx(createButtonNode({ dataVariant: 'warning' }, 'Delete'))
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Delete' })).toHaveClass(
|
||||
'bg-components-button-destructive-primary-bg',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -704,6 +704,18 @@ describe('MarkdownForm', () => {
|
||||
expect(button)!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should map the legacy warning variant to a destructive primary button', () => {
|
||||
const node = createRootNode([
|
||||
createElementNode('button', { dataVariant: 'warning' }, [createTextNode('Delete')]),
|
||||
])
|
||||
|
||||
render(<MarkdownForm node={node} />)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Delete' })).toHaveClass(
|
||||
'bg-components-button-destructive-primary-bg',
|
||||
)
|
||||
})
|
||||
|
||||
it('should ignore invalid variant and size values', () => {
|
||||
const node = createRootNode([
|
||||
createElementNode('button', { dataVariant: 'danger', dataSize: 'xl' }, [
|
||||
|
||||
46
web/app/components/base/markdown-blocks/button-appearance.ts
Normal file
46
web/app/components/base/markdown-blocks/button-appearance.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
|
||||
type MarkdownButtonAppearance = Pick<ButtonProps, 'size' | 'tone' | 'variant'>
|
||||
|
||||
const MARKDOWN_BUTTON_APPEARANCES = {
|
||||
primary: { variant: 'primary' },
|
||||
warning: { tone: 'destructive', variant: 'primary' },
|
||||
secondary: { variant: 'secondary' },
|
||||
'secondary-accent': { variant: 'secondary-accent' },
|
||||
ghost: { variant: 'ghost' },
|
||||
'ghost-accent': { variant: 'ghost-accent' },
|
||||
tertiary: { variant: 'tertiary' },
|
||||
} satisfies Record<string, MarkdownButtonAppearance>
|
||||
|
||||
const VALID_BUTTON_SIZES: ReadonlySet<string> = new Set([
|
||||
'small',
|
||||
'medium',
|
||||
'large',
|
||||
] satisfies Array<NonNullable<ButtonProps['size']>>)
|
||||
|
||||
function isMarkdownButtonVariant(value: string): value is keyof typeof MARKDOWN_BUTTON_APPEARANCES {
|
||||
return Object.hasOwn(MARKDOWN_BUTTON_APPEARANCES, value)
|
||||
}
|
||||
|
||||
function isButtonSize(value: string): value is NonNullable<ButtonProps['size']> {
|
||||
return VALID_BUTTON_SIZES.has(value)
|
||||
}
|
||||
|
||||
function normalizeAttribute(value: unknown) {
|
||||
return value == null ? '' : String(value)
|
||||
}
|
||||
|
||||
function getMarkdownButtonAppearance(
|
||||
dataVariant: unknown,
|
||||
dataSize: unknown,
|
||||
): MarkdownButtonAppearance {
|
||||
const variant = normalizeAttribute(dataVariant)
|
||||
const size = normalizeAttribute(dataSize)
|
||||
|
||||
return {
|
||||
...(isMarkdownButtonVariant(variant) ? MARKDOWN_BUTTON_APPEARANCES[variant] : undefined),
|
||||
size: isButtonSize(size) ? size : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export { getMarkdownButtonAppearance }
|
||||
@ -1,19 +1,31 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { ExtraProps } from 'streamdown'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useChatContext } from '@/app/components/base/chat/chat/context'
|
||||
import { getMarkdownButtonAppearance } from './button-appearance'
|
||||
import { isValidUrl } from './utils'
|
||||
|
||||
const MarkdownButton = ({ node }: any) => {
|
||||
type MarkdownButtonProps = ComponentProps<'button'> & ExtraProps
|
||||
|
||||
function getStringProperty(value: unknown) {
|
||||
return typeof value === 'string' ? value : undefined
|
||||
}
|
||||
|
||||
const MarkdownButton = ({ node }: MarkdownButtonProps) => {
|
||||
const { onSend } = useChatContext()
|
||||
const variant = node.properties.dataVariant
|
||||
const message = node.properties.dataMessage
|
||||
const link = node.properties.dataLink
|
||||
const size = node.properties.dataSize
|
||||
const appearance = getMarkdownButtonAppearance(
|
||||
node?.properties.dataVariant,
|
||||
node?.properties.dataSize,
|
||||
)
|
||||
const message = getStringProperty(node?.properties.dataMessage)
|
||||
const link = getStringProperty(node?.properties.dataLink)
|
||||
const firstChild = node?.children[0]
|
||||
const label = firstChild?.type === 'text' ? firstChild.value : ''
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
size={size}
|
||||
{...appearance}
|
||||
className={cn('h-auto! min-h-8 px-3! whitespace-normal select-none')}
|
||||
onClick={() => {
|
||||
if (link && isValidUrl(link)) {
|
||||
@ -24,7 +36,7 @@ const MarkdownButton = ({ node }: any) => {
|
||||
onSend?.(message)
|
||||
}}
|
||||
>
|
||||
<span className="text-[13px]">{node.children[0]?.value || ''}</span>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
@ -22,6 +21,7 @@ import {
|
||||
toDayjs,
|
||||
} from '@/app/components/base/date-and-time-picker/utils/dayjs'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { getMarkdownButtonAppearance } from './button-appearance'
|
||||
|
||||
const DATA_FORMAT = {
|
||||
TEXT: 'text',
|
||||
@ -72,17 +72,6 @@ function isSafeName(name: unknown): name is string {
|
||||
)
|
||||
}
|
||||
|
||||
const VALID_BUTTON_VARIANTS = new Set<string>([
|
||||
'primary',
|
||||
'warning',
|
||||
'secondary',
|
||||
'secondary-accent',
|
||||
'ghost',
|
||||
'ghost-accent',
|
||||
'tertiary',
|
||||
])
|
||||
const VALID_BUTTON_SIZES = new Set<string>(['small', 'medium', 'large'])
|
||||
|
||||
type HastText = {
|
||||
type: 'text'
|
||||
value: string
|
||||
@ -391,21 +380,16 @@ const MarkdownForm = ({ node }: { node: HastElement }) => {
|
||||
}
|
||||
|
||||
if (child.tagName === SUPPORTED_TAGS.BUTTON) {
|
||||
const rawVariant = str(child.properties.dataVariant)
|
||||
const rawSize = str(child.properties.dataSize)
|
||||
const variant = VALID_BUTTON_VARIANTS.has(rawVariant)
|
||||
? (rawVariant as ButtonProps['variant'])
|
||||
: undefined
|
||||
const size = VALID_BUTTON_SIZES.has(rawSize)
|
||||
? (rawSize as ButtonProps['size'])
|
||||
: undefined
|
||||
const appearance = getMarkdownButtonAppearance(
|
||||
child.properties.dataVariant,
|
||||
child.properties.dataSize,
|
||||
)
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
size={size}
|
||||
className="mt-4"
|
||||
key={key}
|
||||
{...appearance}
|
||||
className="mt-4"
|
||||
disabled={isSubmitting}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
|
||||
@ -22,14 +22,7 @@ const isTheme = (value: string): value is Theme => {
|
||||
export default function ThemeSelector() {
|
||||
const { t } = useTranslation()
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
const handleThemeChange = (newTheme: Theme) => {
|
||||
setTheme(newTheme)
|
||||
}
|
||||
|
||||
const handleThemeValueChange = (value: string) => {
|
||||
if (isTheme(value)) handleThemeChange(value)
|
||||
}
|
||||
const currentTheme: Theme = theme && isTheme(theme) ? theme : 'system'
|
||||
|
||||
const getCurrentIcon = () => {
|
||||
switch (theme) {
|
||||
@ -55,22 +48,25 @@ export default function ThemeSelector() {
|
||||
{getCurrentIcon()}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={6} popupClassName="w-[144px]">
|
||||
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={handleThemeValueChange}>
|
||||
<DropdownMenuRadioItem value="light" closeOnClick>
|
||||
<DropdownMenuRadioGroup<Theme>
|
||||
value={currentTheme}
|
||||
onValueChange={(nextTheme) => setTheme(nextTheme)}
|
||||
>
|
||||
<DropdownMenuRadioItem<Theme> value="light" closeOnClick>
|
||||
<span className="i-ri-sun-line size-4 text-text-tertiary" />
|
||||
<span className="grow px-1 system-md-regular">
|
||||
{t(($) => $['theme.light'], { ns: 'common' })}
|
||||
</span>
|
||||
<DropdownMenuRadioItemIndicator data-testid="light-icon" />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="dark" closeOnClick>
|
||||
<DropdownMenuRadioItem<Theme> value="dark" closeOnClick>
|
||||
<span className="i-ri-moon-line size-4 text-text-tertiary" />
|
||||
<span className="grow px-1 system-md-regular">
|
||||
{t(($) => $['theme.dark'], { ns: 'common' })}
|
||||
</span>
|
||||
<DropdownMenuRadioItemIndicator data-testid="dark-icon" />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="system" closeOnClick>
|
||||
<DropdownMenuRadioItem<Theme> value="system" closeOnClick>
|
||||
<span className="i-ri-computer-line size-4 text-text-tertiary" />
|
||||
<span className="grow px-1 system-md-regular">
|
||||
{t(($) => $['theme.auto'], { ns: 'common' })}
|
||||
|
||||
@ -20,8 +20,8 @@ function getDocumentExtension(document: SimpleDocumentDetail) {
|
||||
|
||||
export default function DocumentList({ className }: Props) {
|
||||
return (
|
||||
<ComboboxList className={cn('max-h-[calc(100vh-120px)] p-0', className)}>
|
||||
{(item: SimpleDocumentDetail) => {
|
||||
<ComboboxList<SimpleDocumentDetail> className={cn('max-h-[calc(100vh-120px)] p-0', className)}>
|
||||
{(item) => {
|
||||
const extension = getDocumentExtension(item)
|
||||
return (
|
||||
<ComboboxItem
|
||||
|
||||
@ -148,10 +148,8 @@ export function DocumentPicker({ datasetId, value, parentMode, onChange }: Props
|
||||
'ml-1 flex size-auto rounded-lg border-0 bg-transparent px-2 py-1 hover:bg-state-base-hover focus-visible:bg-state-base-hover data-popup-open:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<ComboboxValue>
|
||||
{(document: SimpleDocumentDetail | null) => (
|
||||
<DocumentPickerTriggerValue document={document} parentMode={parentMode} />
|
||||
)}
|
||||
<ComboboxValue<SimpleDocumentDetail>>
|
||||
{(document) => <DocumentPickerTriggerValue document={document} parentMode={parentMode} />}
|
||||
</ComboboxValue>
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent
|
||||
|
||||
@ -27,7 +27,7 @@ export function CreateMetadataModal({
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger render={triggerElement as React.ReactElement} />
|
||||
<PopoverTrigger render={triggerElement} />
|
||||
<PopoverContent
|
||||
placement="left-start"
|
||||
sideOffset={popupLeft}
|
||||
|
||||
@ -202,8 +202,8 @@ function MetadataPickerSelectPanel({
|
||||
{query && <ComboboxClear aria-label={t(($) => $['operation.clear'], { ns: 'common' })} />}
|
||||
</ComboboxInputGroup>
|
||||
</div>
|
||||
<ComboboxList>
|
||||
{(metadata: MetadataItem) => <MetadataOption key={metadata.id} metadata={metadata} />}
|
||||
<ComboboxList<MetadataItem>>
|
||||
{(metadata) => <MetadataOption key={metadata.id} metadata={metadata} />}
|
||||
</ComboboxList>
|
||||
<ComboboxEmpty>{t(($) => $.noData, { ns: 'common' })}</ComboboxEmpty>
|
||||
<ComboboxSeparator />
|
||||
|
||||
@ -477,8 +477,8 @@ function GotoAnythingDialog() {
|
||||
? t(($) => $['gotoAnything.groups.commands'], { ns: 'app' })
|
||||
: t(($) => $['gotoAnything.selectSearchType'], { ns: 'app' })}
|
||||
</AutocompleteGroupLabel>
|
||||
<AutocompleteCollection>
|
||||
{(option: CommandOption) => (
|
||||
<AutocompleteCollection<CommandOption>>
|
||||
{(option) => (
|
||||
<AutocompleteItem
|
||||
key={option.shortcut}
|
||||
value={option}
|
||||
@ -542,8 +542,8 @@ function GotoAnythingDialog() {
|
||||
{ ns: 'app' },
|
||||
)}
|
||||
</AutocompleteGroupLabel>
|
||||
<AutocompleteCollection>
|
||||
{(result: SearchResult) => (
|
||||
<AutocompleteCollection<SearchResult>>
|
||||
{(result) => (
|
||||
<AutocompleteItem
|
||||
key={`${result.type}-${result.id}`}
|
||||
value={result}
|
||||
|
||||
@ -57,6 +57,8 @@ function MainNavRadioItemContent({ iconClassName, label }: MainNavRadioItemConte
|
||||
function AppearanceSubmenu() {
|
||||
const { t } = useTranslation()
|
||||
const { theme, setTheme } = useTheme()
|
||||
const currentTheme: Theme =
|
||||
theme === 'light' || theme === 'dark' || theme === 'system' ? theme : 'system'
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
@ -71,23 +73,35 @@ function AppearanceSubmenu() {
|
||||
sideOffset={6}
|
||||
popupClassName="w-[139px] max-h-[360px] bg-components-panel-bg-blur p-1 backdrop-blur-[5px]"
|
||||
>
|
||||
<DropdownMenuRadioGroup
|
||||
value={theme || 'system'}
|
||||
onValueChange={(value) => setTheme(value as Theme)}
|
||||
<DropdownMenuRadioGroup<Theme>
|
||||
value={currentTheme}
|
||||
onValueChange={(nextTheme) => setTheme(nextTheme)}
|
||||
>
|
||||
<DropdownMenuRadioItem value="light" closeOnClick className="mx-0 h-8 gap-1 px-2 py-1">
|
||||
<DropdownMenuRadioItem<Theme>
|
||||
value="light"
|
||||
closeOnClick
|
||||
className="mx-0 h-8 gap-1 px-2 py-1"
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-sun-line"
|
||||
label={t(($) => $['account.appearanceLight'], { ns: 'common' })}
|
||||
/>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="dark" closeOnClick className="mx-0 h-8 gap-1 px-2 py-1">
|
||||
<DropdownMenuRadioItem<Theme>
|
||||
value="dark"
|
||||
closeOnClick
|
||||
className="mx-0 h-8 gap-1 px-2 py-1"
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-moon-line"
|
||||
label={t(($) => $['account.appearanceDark'], { ns: 'common' })}
|
||||
/>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="system" closeOnClick className="mx-0 h-8 gap-1 px-2 py-1">
|
||||
<DropdownMenuRadioItem<Theme>
|
||||
value="system"
|
||||
closeOnClick
|
||||
className="mx-0 h-8 gap-1 px-2 py-1"
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-computer-line"
|
||||
label={t(($) => $['account.appearanceSystem'], { ns: 'common' })}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
import type { DefaultModel, Model, ModelItem } from '../../declarations'
|
||||
import type { ModelSelectorPreviewPayload } from '../popup-item'
|
||||
import { Combobox } from '@langgenius/dify-ui/combobox'
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
@ -138,7 +139,7 @@ const makeProvider = (overrides: Record<string, unknown> = {}) => ({
|
||||
})
|
||||
|
||||
const previewCardProps = () => ({
|
||||
previewCardHandle: createPreviewCardHandle(),
|
||||
previewCardHandle: createPreviewCardHandle<ModelSelectorPreviewPayload>(),
|
||||
onPreviewCardClose: vi.fn(),
|
||||
})
|
||||
|
||||
@ -206,7 +207,7 @@ describe('PopupItem', () => {
|
||||
const onPreviewCardClose = vi.fn()
|
||||
renderWithCombobox(
|
||||
<PopupItem
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
previewCardHandle={createPreviewCardHandle<ModelSelectorPreviewPayload>()}
|
||||
onPreviewCardClose={onPreviewCardClose}
|
||||
model={makeModel()}
|
||||
onHide={vi.fn()}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { PreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import type { DefaultModel, Model, ModelItem } from '../declarations'
|
||||
import type { ModelSelectorModelPredicate } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -26,14 +26,12 @@ export type ModelSelectorPreviewPayload = {
|
||||
modelItem: ModelItem
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
|
||||
type PopupItemProps = {
|
||||
defaultModel?: DefaultModel
|
||||
model: Model
|
||||
modelPredicate?: ModelSelectorModelPredicate
|
||||
modelSuggestionPredicate?: ModelSelectorModelPredicate
|
||||
previewCardHandle: PreviewCardHandle
|
||||
previewCardHandle: PreviewCardHandle<ModelSelectorPreviewPayload>
|
||||
onPreviewCardClose: () => void
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ function Popup({
|
||||
<ModelSelectorPreviewCard
|
||||
capabilitiesLabel={t(($) => $['model.capabilities'], { ns: 'common' })}
|
||||
language={language}
|
||||
payload={payload as ModelSelectorPreviewPayload | undefined}
|
||||
payload={payload}
|
||||
/>
|
||||
)}
|
||||
</PreviewCard>
|
||||
|
||||
@ -89,15 +89,15 @@ function WorkspaceSwitchControls({
|
||||
sideOffset={4}
|
||||
popupClassName="w-40 bg-components-panel-bg-blur! p-1! backdrop-blur-[5px]"
|
||||
>
|
||||
<DropdownMenuRadioGroup
|
||||
<DropdownMenuRadioGroup<WorkspaceSort>
|
||||
value={sort}
|
||||
onValueChange={(value) => {
|
||||
onSortChange(value as WorkspaceSort)
|
||||
onSortChange(value)
|
||||
setSortMenuOpen(false)
|
||||
}}
|
||||
>
|
||||
{sortOptions.map((option) => (
|
||||
<DropdownMenuRadioItem
|
||||
<DropdownMenuRadioItem<WorkspaceSort>
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className="mx-0 h-8 gap-1 px-2 py-1"
|
||||
|
||||
@ -168,8 +168,8 @@ export function AppPicker({
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-1">
|
||||
{isLoading && <ComboboxStatus>{t(($) => $.loading, { ns: 'common' })}</ComboboxStatus>}
|
||||
<ComboboxList className="max-h-none p-0">
|
||||
{(app: App) => <AppPickerOption key={app.id} app={app} />}
|
||||
<ComboboxList<App> className="max-h-none p-0">
|
||||
{(app) => <AppPickerOption key={app.id} app={app} />}
|
||||
</ComboboxList>
|
||||
<ComboboxEmpty>{t(($) => $.noData, { ns: 'common' })}</ComboboxEmpty>
|
||||
{hasMore && (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { Placement } from '@langgenius/dify-ui/popover'
|
||||
import type { ComponentProps, ReactNode } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
@ -16,7 +17,7 @@ type DebugInfoProps = {
|
||||
popupPlacement?: Placement
|
||||
triggerClassName?: string
|
||||
triggerContent?: ReactNode
|
||||
triggerVariant?: ComponentProps<typeof Button>['variant']
|
||||
triggerVariant?: ButtonProps['variant']
|
||||
}
|
||||
|
||||
function DebugInfo({
|
||||
|
||||
@ -21,11 +21,6 @@ type SnippetPublishStatusFilterProps = {
|
||||
|
||||
const chipClassName =
|
||||
'flex h-8 items-center rounded-lg border-[0.5px] px-2 text-[13px] leading-4 outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
const snippetPublishStatusValues: SnippetPublishStatus[] = ['all', 'published', 'draft']
|
||||
|
||||
const isSnippetPublishStatus = (value: string): value is SnippetPublishStatus => {
|
||||
return snippetPublishStatusValues.includes(value as SnippetPublishStatus)
|
||||
}
|
||||
|
||||
const SnippetPublishStatusFilter = ({ value, onChange }: SnippetPublishStatusFilterProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -64,12 +59,16 @@ const SnippetPublishStatusFilter = ({ value, onChange }: SnippetPublishStatusFil
|
||||
<span aria-hidden className="i-ri-arrow-down-s-line h-4 w-4 shrink-0 text-text-tertiary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-start" popupClassName="w-[220px]">
|
||||
<DropdownMenuRadioGroup
|
||||
<DropdownMenuRadioGroup<SnippetPublishStatus>
|
||||
value={value}
|
||||
onValueChange={(nextValue) => isSnippetPublishStatus(nextValue) && onChange(nextValue)}
|
||||
onValueChange={(nextValue) => onChange(nextValue)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<DropdownMenuRadioItem key={option.value} value={option.value} closeOnClick>
|
||||
<DropdownMenuRadioItem<SnippetPublishStatus>
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
closeOnClick
|
||||
>
|
||||
<span>{option.text}</span>
|
||||
<DropdownMenuRadioItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { DropdownMenuContent } from '@langgenius/dify-ui/dropdown-menu'
|
||||
import type { ComponentProps, SyntheticEvent } from 'react'
|
||||
import type { DropdownMenuContentProps } from '@langgenius/dify-ui/dropdown-menu'
|
||||
import type { SyntheticEvent } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
@ -8,7 +8,6 @@ const STEP_BY_STEP_TOUR_MENU_POPUP_NO_MOTION_CLASS_NAME =
|
||||
'transition-none data-starting-style:scale-100 data-starting-style:opacity-100 data-ending-style:scale-100 data-ending-style:opacity-100'
|
||||
const STEP_BY_STEP_TOUR_MENU_PRESENTATION_CLASS_NAME = 'pointer-events-none cursor-default'
|
||||
|
||||
type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuContent>
|
||||
type DropdownMenuPositionerProps = DropdownMenuContentProps['positionerProps']
|
||||
type DropdownMenuPopupProps = DropdownMenuContentProps['popupProps']
|
||||
|
||||
|
||||
@ -194,7 +194,7 @@ const Blocks = ({
|
||||
)}
|
||||
{!isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup)}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => <BlockPreviewCard payload={payload as BlockPreviewPayload | undefined} />}
|
||||
{({ payload }) => <BlockPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { PreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { ToolWithProvider } from '../types'
|
||||
import type { ToolDefaultValue, ToolValue } from './types'
|
||||
@ -227,9 +228,7 @@ const FeaturedTools = ({
|
||||
)}
|
||||
</CollapsiblePanel>
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<FeaturedToolPreviewCard payload={payload as FeaturedToolPreviewPayload | undefined} />
|
||||
)}
|
||||
{({ payload }) => <FeaturedToolPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
</Collapsible>
|
||||
)
|
||||
@ -238,7 +237,7 @@ const FeaturedTools = ({
|
||||
type FeaturedToolUninstalledItemProps = {
|
||||
plugin: Plugin
|
||||
language: Locale
|
||||
previewCardHandle: ReturnType<typeof createPreviewCardHandle<FeaturedToolPreviewPayload>>
|
||||
previewCardHandle: PreviewCardHandle<FeaturedToolPreviewPayload>
|
||||
onInstallSuccess?: () => Promise<void> | void
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { PreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TriggerPluginActionPreviewPayload } from './trigger-plugin/action-item'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
|
||||
@ -227,18 +228,10 @@ const FeaturedTriggers = ({
|
||||
)}
|
||||
</CollapsiblePanel>
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<FeaturedTriggerPreviewCard
|
||||
payload={payload as FeaturedTriggerPreviewPayload | undefined}
|
||||
/>
|
||||
)}
|
||||
{({ payload }) => <FeaturedTriggerPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
<PreviewCard handle={triggerActionPreviewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<TriggerPluginActionPreviewCard
|
||||
payload={payload as TriggerPluginActionPreviewPayload | undefined}
|
||||
/>
|
||||
)}
|
||||
{({ payload }) => <TriggerPluginActionPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
</Collapsible>
|
||||
)
|
||||
@ -247,7 +240,7 @@ const FeaturedTriggers = ({
|
||||
type FeaturedTriggerUninstalledItemProps = {
|
||||
plugin: Plugin
|
||||
language: Locale
|
||||
previewCardHandle: ReturnType<typeof createPreviewCardHandle<FeaturedTriggerPreviewPayload>>
|
||||
previewCardHandle: PreviewCardHandle<FeaturedTriggerPreviewPayload>
|
||||
onInstallSuccess?: () => Promise<void> | void
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
@ -66,9 +66,7 @@ const List = ({ onSelect, tools, viewType, unInstalledPlugins, className }: List
|
||||
/>
|
||||
))}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<ToolActionPreviewCard payload={payload as ToolActionPreviewPayload | undefined} />
|
||||
)}
|
||||
{({ payload }) => <ToolActionPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
{unInstalledPlugins.map((item) => {
|
||||
return <UninstalledItem key={item.plugin_id} payload={item} />
|
||||
|
||||
@ -196,7 +196,7 @@ const Snippets = ({ searchText, onSearchTextChange, insertPayload, onInserted }:
|
||||
|
||||
return (
|
||||
<BlockSelectorPreviewCardContent>
|
||||
<SnippetDetailCard snippet={payload as SnippetListItemData} />
|
||||
<SnippetDetailCard snippet={payload} />
|
||||
</BlockSelectorPreviewCardContent>
|
||||
)
|
||||
}}
|
||||
|
||||
@ -214,9 +214,7 @@ const StartBlocks = ({
|
||||
))}
|
||||
</div>
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<StartBlockPreviewCard payload={payload as StartBlockPreviewPayload | undefined} t={t} />
|
||||
)}
|
||||
{({ payload }) => <StartBlockPreviewCard payload={payload} t={t} />}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import type { PreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import type { FC } from 'react'
|
||||
import type { ToolWithProvider } from '../../types'
|
||||
import type { ToolDefaultValue } from '../types'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
@ -30,23 +31,22 @@ const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
|
||||
return icon
|
||||
}
|
||||
|
||||
type Props = Readonly<{
|
||||
provider: ToolWithProvider
|
||||
payload: Tool
|
||||
previewCardHandle: PreviewCardHandle
|
||||
disabled?: boolean
|
||||
isAdded?: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
}>
|
||||
|
||||
export type ToolActionPreviewPayload = {
|
||||
providerIcon: ToolWithProvider['icon']
|
||||
payload: Tool
|
||||
language: ReturnType<typeof useGetLanguage>
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
export type ToolActionPreviewCardHandle = PreviewCardHandle
|
||||
export type ToolActionPreviewCardHandle = PreviewCardHandle<ToolActionPreviewPayload>
|
||||
|
||||
type Props = Readonly<{
|
||||
provider: ToolWithProvider
|
||||
payload: Tool
|
||||
previewCardHandle: ToolActionPreviewCardHandle
|
||||
disabled?: boolean
|
||||
isAdded?: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
}>
|
||||
|
||||
const ToolItem: FC<Props> = ({
|
||||
provider,
|
||||
|
||||
@ -91,9 +91,7 @@ const Tools = ({
|
||||
/>
|
||||
))}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<ToolActionPreviewCard payload={payload as ToolActionPreviewPayload | undefined} />
|
||||
)}
|
||||
{({ payload }) => <ToolActionPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import type { PreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import type { FC } from 'react'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
|
||||
import type { Event } from '@/app/components/tools/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -26,8 +27,8 @@ export type TriggerPluginActionPreviewPayload = {
|
||||
language: ReturnType<typeof useGetLanguage>
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
export type TriggerPluginActionPreviewCardHandle = PreviewCardHandle
|
||||
export type TriggerPluginActionPreviewCardHandle =
|
||||
PreviewCardHandle<TriggerPluginActionPreviewPayload>
|
||||
|
||||
const TriggerPluginActionItem: FC<Props> = ({
|
||||
provider,
|
||||
|
||||
@ -102,11 +102,7 @@ const TriggerPluginList = ({
|
||||
/>
|
||||
))}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<TriggerPluginActionPreviewCard
|
||||
payload={payload as TriggerPluginActionPreviewPayload | undefined}
|
||||
/>
|
||||
)}
|
||||
{({ payload }) => <TriggerPluginActionPreviewCard payload={payload} />}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -190,7 +190,7 @@ const FormItem: FC<Props> = ({
|
||||
)}
|
||||
|
||||
{type === InputVarType.select && (
|
||||
<Select
|
||||
<Select<string>
|
||||
value={value || payload.default || null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue) return
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user