fix(dify-ui): preserve state-aware styling callbacks (#42631)
This commit is contained in:
@@ -26,15 +26,17 @@ Prefer:
|
||||
|
||||
## Styling
|
||||
|
||||
For Dify UI/Base UI state styling and callback composition, apply the package's [Styling] contract,
|
||||
including its documented reasons for direct state access.
|
||||
|
||||
Flag:
|
||||
|
||||
- New CSS modules or ad hoc CSS when Tailwind utilities and Dify tokens cover the need.
|
||||
- Component-level plain `.css` files or component CSS imported through `globals.css`; use scoped `*.module.css` only when Tailwind and component variants cannot express the style.
|
||||
- Generic color utilities where Dify semantic tokens exist.
|
||||
- Hardcoded magic class values for colors, spacing, radius, shadow, z-index, or typography when Dify tokens, component variants, or documented radius mappings exist.
|
||||
- Hardcoded magic class values for colors, spacing, radius, shadow, z-index, or typography when Dify tokens, component variants, or standard Tailwind radius classes match the required design value. Apply [Styling] when choosing radius classes.
|
||||
- `!` important modifiers or important CSS overrides without a narrow, documented reason.
|
||||
- Manual class-list assembly through string concatenation, template strings, array `.join(' ')`, or a custom conditional combiner instead of `cn(...)`. Conditional values passed to `cn(...)` remain valid.
|
||||
- JS conditional class branches for primitive visual states already exposed by Dify UI/Base UI `data-*` selectors.
|
||||
- Incoming `className` placed before default classes in `cn(...)`, preventing call-site overrides.
|
||||
- Arbitrary z-index or one-off layering fixes on overlays.
|
||||
|
||||
@@ -43,7 +45,6 @@ Use:
|
||||
- `cn(...)` from the local package or utility already used by the file.
|
||||
- Dify semantic tokens and Tailwind v4 utilities.
|
||||
- Existing component variants before one-off class forks.
|
||||
- Primitive selectors such as `data-disabled:*`, `data-checked:*`, `data-highlighted:*`, `group-data-*`, `peer-data-*`, and `has-[:focus-visible]` before adding React state or boolean props solely for styling.
|
||||
- Component-level variants, semantic tokens, and normal cascade/order before `!` overrides. Use `!` only for a contained compatibility override that cannot be expressed through the component API or local selector structure.
|
||||
|
||||
## Imports
|
||||
@@ -66,3 +67,5 @@ Flag:
|
||||
- Error messages that state only the failure and not the next step.
|
||||
|
||||
Use feature-local translation keys by default. Alias only when crossing namespaces. `pnpm i18n:check --file <name>` should pass for any touched translation namespace.
|
||||
|
||||
[Styling]: ../../../../packages/dify-ui/docs/styling.md
|
||||
|
||||
@@ -16,7 +16,7 @@ owner:
|
||||
| Forms and field semantics | `packages/dify-ui/docs/forms.md` |
|
||||
| Selection and typed values | `packages/dify-ui/docs/selection.md` |
|
||||
| Portals, layers, and floating surfaces | `packages/dify-ui/docs/overlays.md` |
|
||||
| Tailwind and radius tokens | `packages/dify-ui/docs/styling.md` |
|
||||
| State styling, callbacks, and Tailwind | `packages/dify-ui/docs/styling.md` |
|
||||
| Package tests and Storybook | `packages/dify-ui/docs/testing.md` |
|
||||
|
||||
For consumer code under `web/`, also read `web/AGENTS.md` for application-owned reuse policy and
|
||||
|
||||
@@ -10,6 +10,7 @@ Use this skill to route component architecture decisions to its bundled referenc
|
||||
## Topic Routing
|
||||
|
||||
- Component moves, module boundaries, props, types, or owner placement: read [`references/ownership.md`][ownership].
|
||||
- Dify UI wrappers or primitive state styling: read the package's [Public API authoring] and [Styling] contracts.
|
||||
- Jotai, form drafts, route identity, URL state, or persistence: read [`references/state.md`][state].
|
||||
- Generated contracts, nullable API data, Query, mutations, SSR, auth, or workspace state: read [`references/data.md`][data].
|
||||
- Hotkeys, focus, dialogs, menus, popovers, or other secondary surfaces: read [`references/interactions.md`][interactions] and the overlay guide it references when applicable. Also read [`references/state.md`][state] when the surface owns a draft or other local session state.
|
||||
@@ -24,6 +25,8 @@ Identify the behavior owner, state lifetime, and public contract from the nearby
|
||||
- Write canonical Tailwind v4 classes; prefer canonical utilities over equivalent arbitrary values.
|
||||
- Common forms include `w-105` instead of `w-[420px]`, `px-2.25` instead of `px-[9px]`, `bg-linear-to-b` instead of `bg-gradient-to-b`, `wrap-break-word` instead of `break-words`, and `field-sizing-content` instead of `[field-sizing:content]`.
|
||||
|
||||
[Public API authoring]: ../../../packages/dify-ui/docs/authoring.md
|
||||
[Styling]: ../../../packages/dify-ui/docs/styling.md
|
||||
[data]: references/data.md
|
||||
[interactions]: references/interactions.md
|
||||
[ownership]: references/ownership.md
|
||||
|
||||
@@ -12,8 +12,8 @@ This file owns the package boundary. Read the matching contract guide below dire
|
||||
- Prefer `@base-ui/react` when it owns the required headless behavior. Style primitives with `cva`,
|
||||
`cn`, and Dify design tokens. Keep one primitive per `src/<name>/` folder with optional colocated
|
||||
stories and tests.
|
||||
- Prefer Base UI data attributes and CSS variables for visual states. Do not mirror primitive state
|
||||
in React solely to add classes.
|
||||
- Prefer Base UI data attributes for state styling and CSS variables for exposed dynamic values.
|
||||
Follow [Styling] for state callbacks; do not mirror primitive state in React solely to add classes.
|
||||
- When an upstream API or selector contract is unclear, read the current official Base UI
|
||||
documentation and installed `@base-ui/react` declarations before coding.
|
||||
|
||||
@@ -26,7 +26,7 @@ This file owns the package boundary. Read the matching contract guide below dire
|
||||
- Form structure, labels, and value ownership: [Forms]
|
||||
- Picker choice and typed values: [Selection]
|
||||
- Portals, presence, layering, and floating-surface semantics: [Overlays]
|
||||
- Tailwind integration and radius mapping: [Styling]
|
||||
- State styling, callbacks, composition, Tailwind integration, and border radius: [Styling]
|
||||
- Package test ownership and setup: [Testing and development]
|
||||
|
||||
A component needs a local README only when it owns a substantial Dify-specific contract that its
|
||||
|
||||
@@ -68,15 +68,15 @@ Upstream behavior remains owned by the [Base UI documentation].
|
||||
|
||||
### Cross-component guides
|
||||
|
||||
| Guide | Scope |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| [Accessible names and descriptions] | Naming sources, descriptions, overrides, and safe label removal. |
|
||||
| [Forms] | Native submit boundaries, value ownership, fields, labels, and errors. |
|
||||
| [Selection] | Typed values and choosing among segmented controls, pickers, and radio groups. |
|
||||
| [Overlays] | Portals, presence lifecycles, layering, trigger composition, and semantics. |
|
||||
| [Styling] | Tailwind CSS integration and the Figma radius mapping. |
|
||||
| [Public API authoring] | Subpath exports, naming, public types, generics, and private helpers. |
|
||||
| [Testing and development] | Package commands, test ownership, accessibility, and animation setup. |
|
||||
| Guide | Scope |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| [Accessible names and descriptions] | Naming sources, descriptions, overrides, and safe label removal. |
|
||||
| [Forms] | Native submit boundaries, value ownership, fields, labels, and errors. |
|
||||
| [Selection] | Typed values and choosing among segmented controls, pickers, and radio groups. |
|
||||
| [Overlays] | Portals, presence lifecycles, layering, trigger composition, and semantics. |
|
||||
| [Styling] | State styling, callbacks, composition, Tailwind integration, and border radius. |
|
||||
| [Public API authoring] | Subpath exports, naming, public types, generics, and private helpers. |
|
||||
| [Testing and development] | Package commands, test ownership, accessibility, and animation setup. |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -57,11 +57,15 @@ State, event details and reasons, actions, controlled-state helpers, context val
|
||||
helpers, styling helpers, and upstream passthrough aliases are private by default. Public props
|
||||
already provide contextual typing for inline render and event callbacks.
|
||||
|
||||
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.
|
||||
Preserve upstream `className` and `style` callbacks. Resolve `className` with the owning Base UI
|
||||
part's state before merging default classes with `cn()`, including through composite wrappers.
|
||||
Forward `style` unchanged unless the wrapper needs to merge styles; then resolve its callback first.
|
||||
Do not add state callbacks to native DOM props or unrelated custom APIs. See [Styling].
|
||||
|
||||
## Evidence
|
||||
|
||||
Use local public-subpath type tests to protect generic inference, required relationships, and
|
||||
intentional errors. Read current official Base UI documentation and installed type declarations
|
||||
before changing an upstream-derived contract.
|
||||
|
||||
[Styling]: ./styling.md
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Styling
|
||||
|
||||
## Component state
|
||||
|
||||
Prefer the primitive's data attributes for state styling, such as `data-checked:` or
|
||||
`data-disabled:`. Use its CSS variables for exposed dynamic values, such as popup anchor width
|
||||
and available height. Do not mirror primitive state in React solely to style it.
|
||||
|
||||
Where supported by the upstream part, `className(state)` and `style(state)` read that part's state.
|
||||
At call sites, before using a callback or React state to calculate styles, check whether data attributes or CSS
|
||||
variables already express the requirement. When direct state access is needed, add a short comment
|
||||
explaining why; for example, Tooltip and Popover can share `data-popup-open` on one trigger while
|
||||
the style needs only Popover's open state. This does not restrict conditional rendering of content.
|
||||
|
||||
Composite content forwards these callbacks to its styled part: for example, `PopoverContent`
|
||||
receives Popup state, not Positioner state. Native DOM props and custom convenience APIs do not
|
||||
automatically support state callbacks.
|
||||
|
||||
Use `render` for element composition, not merely to change styles. A callback on `PopoverTrigger`
|
||||
reads Popover state; one on a standalone `Button` reads Button state. Composition does not change
|
||||
which state a part owns.
|
||||
|
||||
With `render={<Button />}`, Base UI automatically merges the target's props without resolving
|
||||
its callbacks. Put callbacks on the outer primitive and use class strings and style objects on
|
||||
that target. With `render={(props, state) => ...}`, forward the received props and ref, and explicitly
|
||||
merge any custom classes, styles, or handlers. Do not switch to this form merely for state styling.
|
||||
See Base UI's [styling] and [composition] guides for the upstream contracts.
|
||||
|
||||
## Tailwind CSS v4
|
||||
|
||||
Import Tailwind from the consumer's root stylesheet as described in the [Tailwind CSS v4 upgrade
|
||||
@@ -21,26 +47,12 @@ stylesheet:
|
||||
@source not '../../../packages/dify-ui/src/**/*.stories.{ts,tsx}';
|
||||
```
|
||||
|
||||
## Figma radius mapping
|
||||
## Border radius
|
||||
|
||||
Figma radius tokens are offset by one step from Tailwind CSS v4 defaults. Use this mapping instead
|
||||
of adding custom theme values or `radius-*` utilities:
|
||||
|
||||
| Figma token | Tailwind class |
|
||||
| --------------- | ---------------- |
|
||||
| `--radius/2xs` | `rounded-xs` |
|
||||
| `--radius/xs` | `rounded-sm` |
|
||||
| `--radius/sm` | `rounded-md` |
|
||||
| `--radius/md` | `rounded-lg` |
|
||||
| `--radius/lg` | `rounded-[10px]` |
|
||||
| `--radius/xl` | `rounded-xl` |
|
||||
| `--radius/2xl` | `rounded-2xl` |
|
||||
| `--radius/3xl` | `rounded-[20px]` |
|
||||
| `--radius/6xl` | `rounded-[28px]` |
|
||||
| `--radius/full` | `rounded-full` |
|
||||
|
||||
Convert Figma output such as `rounded-[var(--radius/sm, 6px)]` to the mapped Tailwind class. Use an
|
||||
arbitrary value only when no standard class matches.
|
||||
Check the actual radius value in Figma before choosing a `rounded-*` class; matching token names
|
||||
do not imply matching values. For example, `radius/sm = 6px` requires `rounded-md`, not
|
||||
`rounded-sm` (4px). Prefer a standard Tailwind class with the matching value; use an arbitrary
|
||||
value only when none matches, such as `rounded-[10px]` for 10px.
|
||||
|
||||
Use semantic Dify tokens and existing component variants before hard-coded values or repeated
|
||||
primitive classes. Use an important modifier only for a tightly scoped compatibility override
|
||||
@@ -52,3 +64,5 @@ contains the native focus target, select that descendant state from the wrapper;
|
||||
|
||||
[Tailwind CSS functions and directives]: https://tailwindcss.com/docs/functions-and-directives
|
||||
[Tailwind CSS v4 upgrade guide]: https://tailwindcss.com/docs/upgrade-guide
|
||||
[composition]: https://base-ui.com/react/handbook/composition
|
||||
[styling]: https://base-ui.com/react/handbook/styling
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ButtonProps } from '../button'
|
||||
import { AlertDialog as BaseAlertDialog } from '@base-ui/react/alert-dialog'
|
||||
import { Button } from '../button'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { modalBackdropClassName, modalPopupAnimationClassName } from '../overlay-shared'
|
||||
|
||||
const AlertDialog = BaseAlertDialog.Root
|
||||
@@ -17,17 +18,19 @@ type AlertDialogTriggerProps<Payload = unknown> = BaseAlertDialog.Trigger.Props<
|
||||
type AlertDialogTitleProps = BaseAlertDialog.Title.Props
|
||||
type AlertDialogDescriptionProps = BaseAlertDialog.Description.Props
|
||||
|
||||
type AlertDialogBackdropProps = Omit<BaseAlertDialog.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AlertDialogBackdropProps = BaseAlertDialog.Backdrop.Props
|
||||
|
||||
function AlertDialogBackdrop({ className, ...props }: AlertDialogBackdropProps) {
|
||||
return <BaseAlertDialog.Backdrop {...props} className={cn(modalBackdropClassName, className)} />
|
||||
return (
|
||||
<BaseAlertDialog.Backdrop
|
||||
{...props}
|
||||
className={(state) => cn(modalBackdropClassName, resolveClassName(className, state))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AlertDialogContentProps = Omit<BaseAlertDialog.Popup.Props, 'children' | 'className'> & {
|
||||
type AlertDialogContentProps = Omit<BaseAlertDialog.Popup.Props, 'children'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
backdropProps?: AlertDialogBackdropProps
|
||||
}
|
||||
|
||||
@@ -41,11 +44,13 @@ function AlertDialogContent({
|
||||
<BaseAlertDialog.Portal>
|
||||
<AlertDialogBackdrop {...backdropProps} />
|
||||
<BaseAlertDialog.Popup
|
||||
className={cn(
|
||||
'fixed top-1/2 left-1/2 z-50 max-h-[calc(100vh-2rem)] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
modalPopupAnimationClassName,
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'fixed top-1/2 left-1/2 z-50 max-h-[calc(100vh-2rem)] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
modalPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Autocomplete as BaseAutocomplete } from '@base-ui/react/autocomplete'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { textControlCompoundInputFocusClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import {
|
||||
floatingGroupLabelClassName,
|
||||
floatingItemIndicatorClassName,
|
||||
@@ -104,8 +105,8 @@ const autocompleteInputGroupVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type AutocompleteInputGroupProps = Omit<BaseAutocomplete.InputGroup.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteInputGroupVariants> & { className?: string }
|
||||
type AutocompleteInputGroupProps = BaseAutocomplete.InputGroup.Props &
|
||||
VariantProps<typeof autocompleteInputGroupVariants>
|
||||
|
||||
function AutocompleteInputGroup({
|
||||
className,
|
||||
@@ -114,7 +115,9 @@ function AutocompleteInputGroup({
|
||||
}: AutocompleteInputGroupProps) {
|
||||
return (
|
||||
<BaseAutocomplete.InputGroup
|
||||
className={cn(autocompleteInputGroupVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(autocompleteInputGroupVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -141,8 +144,8 @@ const autocompleteInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type AutocompleteInputProps = Omit<BaseAutocomplete.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof autocompleteInputVariants> & { className?: string }
|
||||
type AutocompleteInputProps = Omit<BaseAutocomplete.Input.Props, 'size'> &
|
||||
VariantProps<typeof autocompleteInputVariants>
|
||||
|
||||
function AutocompleteInput({
|
||||
className,
|
||||
@@ -153,7 +156,9 @@ function AutocompleteInput({
|
||||
return (
|
||||
<BaseAutocomplete.Input
|
||||
autoComplete={autoComplete}
|
||||
className={cn(autocompleteInputVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(autocompleteInputVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -183,8 +188,8 @@ const autocompleteControlVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type AutocompleteTriggerProps = Omit<BaseAutocomplete.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteControlVariants> & { className?: string }
|
||||
type AutocompleteTriggerProps = BaseAutocomplete.Trigger.Props &
|
||||
VariantProps<typeof autocompleteControlVariants>
|
||||
|
||||
function AutocompleteTrigger({
|
||||
className,
|
||||
@@ -200,7 +205,9 @@ function AutocompleteTrigger({
|
||||
props['aria-label'] ??
|
||||
(props['aria-labelledby'] ? undefined : 'Open autocomplete suggestions')
|
||||
}
|
||||
className={cn(autocompleteControlVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(autocompleteControlVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-arrow-down-s-line size-4" aria-hidden="true" />}
|
||||
@@ -208,8 +215,8 @@ function AutocompleteTrigger({
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteClearProps = Omit<BaseAutocomplete.Clear.Props, 'className'> &
|
||||
VariantProps<typeof autocompleteControlVariants> & { className?: string }
|
||||
type AutocompleteClearProps = BaseAutocomplete.Clear.Props &
|
||||
VariantProps<typeof autocompleteControlVariants>
|
||||
|
||||
function AutocompleteClear({
|
||||
className,
|
||||
@@ -224,11 +231,13 @@ function AutocompleteClear({
|
||||
aria-label={
|
||||
props['aria-label'] ?? (props['aria-labelledby'] ? undefined : 'Clear autocomplete')
|
||||
}
|
||||
className={cn(
|
||||
autocompleteControlVariants({ size }),
|
||||
'data-ending-style:opacity-0 data-starting-style:opacity-0',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
autocompleteControlVariants({ size }),
|
||||
'data-ending-style:opacity-0 data-starting-style:opacity-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-close-line size-4" aria-hidden="true" />}
|
||||
@@ -236,14 +245,14 @@ function AutocompleteClear({
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteIconProps = Omit<BaseAutocomplete.Icon.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompleteIconProps = BaseAutocomplete.Icon.Props
|
||||
|
||||
function AutocompleteIcon({ className, children, ...props }: AutocompleteIconProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Icon
|
||||
className={cn('flex shrink-0 items-center text-text-tertiary', className)}
|
||||
className={(state) =>
|
||||
cn('flex shrink-0 items-center text-text-tertiary', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-arrow-down-s-line size-4" aria-hidden="true" />}
|
||||
@@ -254,11 +263,7 @@ function AutocompleteIcon({ className, children, ...props }: AutocompleteIconPro
|
||||
const AutocompletePortal = BaseAutocomplete.Portal
|
||||
type AutocompletePortalProps = BaseAutocomplete.Portal.Props
|
||||
|
||||
type AutocompletePositionerProps = Omit<
|
||||
BaseAutocomplete.Positioner.Props,
|
||||
'className' | 'side' | 'align'
|
||||
> & {
|
||||
className?: string
|
||||
type AutocompletePositionerProps = Omit<BaseAutocomplete.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -275,47 +280,53 @@ function AutocompletePositioner({
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompletePopupProps = Omit<BaseAutocomplete.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompletePopupProps = BaseAutocomplete.Popup.Props
|
||||
|
||||
function AutocompletePopup({ className, ...props }: AutocompletePopupProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Popup
|
||||
className={cn(autocompletePopupClassName, floatingPopupAnimationClassName, className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
autocompletePopupClassName,
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteListProps<Value = unknown> = Omit<
|
||||
BaseAutocomplete.List.Props,
|
||||
'children' | 'className'
|
||||
> & {
|
||||
type AutocompleteListProps<Value = unknown> = Omit<BaseAutocomplete.List.Props, 'children'> & {
|
||||
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} />
|
||||
return (
|
||||
<BaseAutocomplete.List
|
||||
className={(state) => cn(autocompleteListClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteItemProps<Value = unknown> = Omit<
|
||||
BaseAutocomplete.Item.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
type AutocompleteItemProps<Value = unknown> = Omit<BaseAutocomplete.Item.Props, 'value'> & {
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function AutocompleteItem<Value = unknown>({ className, ...props }: AutocompleteItemProps<Value>) {
|
||||
return <BaseAutocomplete.Item className={cn(autocompleteItemClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseAutocomplete.Item
|
||||
className={(state) => cn(autocompleteItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteItemTextProps = React.ComponentProps<'span'>
|
||||
@@ -326,53 +337,52 @@ function AutocompleteItemText({ className, ...props }: AutocompleteItemTextProps
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteGroupLabelProps = Omit<BaseAutocomplete.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompleteGroupLabelProps = BaseAutocomplete.GroupLabel.Props
|
||||
|
||||
function AutocompleteGroupLabel({ className, ...props }: AutocompleteGroupLabelProps) {
|
||||
return (
|
||||
<BaseAutocomplete.GroupLabel
|
||||
className={cn(floatingGroupLabelClassName, className)}
|
||||
className={(state) => cn(floatingGroupLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteSeparatorProps = Omit<BaseAutocomplete.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompleteSeparatorProps = BaseAutocomplete.Separator.Props
|
||||
|
||||
function AutocompleteSeparator({ className, ...props }: AutocompleteSeparatorProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
<BaseAutocomplete.Separator
|
||||
className={(state) => cn(floatingSeparatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteEmptyProps = Omit<BaseAutocomplete.Empty.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompleteEmptyProps = BaseAutocomplete.Empty.Props
|
||||
|
||||
function AutocompleteEmpty({ className, ...props }: AutocompleteEmptyProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Empty
|
||||
className={cn(
|
||||
'px-3 py-2 system-sm-regular text-text-tertiary empty:h-0 empty:p-0',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'px-3 py-2 system-sm-regular text-text-tertiary empty:h-0 empty:p-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AutocompleteStatusProps = Omit<BaseAutocomplete.Status.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AutocompleteStatusProps = BaseAutocomplete.Status.Props
|
||||
|
||||
function AutocompleteStatus({ className, ...props }: AutocompleteStatusProps) {
|
||||
return (
|
||||
<BaseAutocomplete.Status
|
||||
className={cn('px-3 py-2 system-sm-regular text-text-tertiary', className)}
|
||||
className={(state) =>
|
||||
cn('px-3 py-2 system-sm-regular text-text-tertiary', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ImageLoadingStatus } from '@base-ui/react/avatar'
|
||||
import { Avatar as BaseAvatar } from '@base-ui/react/avatar'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const avatarSizeClasses = {
|
||||
xxs: { root: 'size-4', text: 'text-[7px]' },
|
||||
@@ -25,47 +26,49 @@ type AvatarProps = {
|
||||
onLoadingStatusChange?: (status: ImageLoadingStatus) => void
|
||||
}
|
||||
|
||||
type AvatarRootProps = Omit<BaseAvatar.Root.Props, 'className'> & {
|
||||
type AvatarRootProps = BaseAvatar.Root.Props & {
|
||||
size?: AvatarSize
|
||||
className?: string
|
||||
}
|
||||
function AvatarRoot({ size = 'md', className, ...props }: AvatarRootProps) {
|
||||
return (
|
||||
<BaseAvatar.Root
|
||||
className={cn(
|
||||
'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary-600 select-none',
|
||||
avatarSizeClasses[size].root,
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary-600 select-none',
|
||||
avatarSizeClasses[size].root,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarFallbackProps = Omit<BaseAvatar.Fallback.Props, 'className'> & {
|
||||
type AvatarFallbackProps = BaseAvatar.Fallback.Props & {
|
||||
size?: AvatarSize
|
||||
className?: string
|
||||
}
|
||||
function AvatarFallback({ size = 'md', className, ...props }: AvatarFallbackProps) {
|
||||
return (
|
||||
<BaseAvatar.Fallback
|
||||
className={cn(
|
||||
'flex size-full items-center justify-center font-medium text-white',
|
||||
avatarSizeClasses[size].text,
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'flex size-full items-center justify-center font-medium text-white',
|
||||
avatarSizeClasses[size].text,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarImageProps = Omit<BaseAvatar.Image.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type AvatarImageProps = BaseAvatar.Image.Props
|
||||
function AvatarImage({ className, ...props }: AvatarImageProps) {
|
||||
return (
|
||||
<BaseAvatar.Image
|
||||
className={cn('absolute inset-0 size-full object-cover', className)}
|
||||
className={(state) =>
|
||||
cn('absolute inset-0 size-full object-cover', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -91,15 +91,17 @@ at call sites:
|
||||
```
|
||||
|
||||
Regular (`medium`) and `large` sizes use 4px and 6px gaps. `small` uses 3px for `primary` and 4px
|
||||
for the other variants. Use a `className` override only for a documented layout exception.
|
||||
for the other variants. Override these gaps through `className` only for a documented layout exception.
|
||||
|
||||
## Related guides
|
||||
|
||||
- Read [`IconButton`] for icon-only actions.
|
||||
- Read [Accessible names and descriptions] when choosing or changing a naming source.
|
||||
- Read [Styling] for state styles, callbacks, and their composition boundary.
|
||||
- Read [Base UI Button] for the upstream interaction and composition contract.
|
||||
|
||||
[Accessible names and descriptions]: ../../docs/accessible-names-and-descriptions.md
|
||||
[Base UI Button]: https://base-ui.com/react/components/button
|
||||
[Styling]: ../../docs/styling.md
|
||||
[WAI-ARIA `aria-busy`]: https://www.w3.org/TR/wai-aria#aria-busy
|
||||
[`IconButton`]: ../icon-button/README.md
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { VariantProps } from 'class-variance-authority'
|
||||
import { Button as BaseButton } from '@base-ui/react/button'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-disabled:cursor-not-allowed',
|
||||
@@ -103,7 +104,7 @@ const buttonVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type ButtonProps = Omit<BaseButtonNS.Props, 'className'> &
|
||||
type ButtonProps = BaseButtonNS.Props &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
/**
|
||||
* Marks an action as pending, blocks activation, and keeps the button focusable by default.
|
||||
@@ -111,7 +112,6 @@ type ButtonProps = Omit<BaseButtonNS.Props, 'className'> &
|
||||
* reference it with `aria-labelledby` so it remains the explicit accessible name.
|
||||
*/
|
||||
loading?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Button({
|
||||
@@ -129,7 +129,9 @@ function Button({
|
||||
return (
|
||||
<BaseButton
|
||||
type={type}
|
||||
className={cn(buttonVariants({ variant, size, tone, className }))}
|
||||
className={(state) =>
|
||||
cn(buttonVariants({ variant, size, tone, className: resolveClassName(className, state) }))
|
||||
}
|
||||
disabled={disabled || loading}
|
||||
focusableWhenDisabled={focusableWhenDisabled ?? loading}
|
||||
{...props}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Checkbox as BaseCheckboxNS } from '@base-ui/react/checkbox'
|
||||
import type * as React from 'react'
|
||||
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const checkboxRootClassName = cn(
|
||||
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-sm shadow-xs shadow-shadow-shadow-3 transition-colors motion-reduce:transition-none',
|
||||
@@ -25,20 +26,21 @@ const checkboxIndicatorClassName =
|
||||
|
||||
const checkboxSkeletonClassName = 'size-4 shrink-0 rounded-sm bg-text-quaternary opacity-20'
|
||||
|
||||
type CheckboxRootProps = Omit<BaseCheckboxNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type CheckboxRootProps = BaseCheckboxNS.Root.Props
|
||||
function CheckboxRoot({ className, ...props }: CheckboxRootProps) {
|
||||
return <BaseCheckbox.Root className={cn(checkboxRootClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseCheckbox.Root
|
||||
className={(state) => cn(checkboxRootClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type CheckboxIndicatorProps = Omit<BaseCheckboxNS.Indicator.Props, 'className' | 'children'> & {
|
||||
className?: string
|
||||
}
|
||||
type CheckboxIndicatorProps = Omit<BaseCheckboxNS.Indicator.Props, 'children'>
|
||||
function CheckboxIndicator({ className, render, ...props }: CheckboxIndicatorProps) {
|
||||
return (
|
||||
<BaseCheckbox.Indicator
|
||||
className={cn(checkboxIndicatorClassName, className)}
|
||||
className={(state) => cn(checkboxIndicatorClassName, resolveClassName(className, state))}
|
||||
render={
|
||||
render ??
|
||||
((indicatorProps, state) => (
|
||||
|
||||
@@ -3,29 +3,33 @@
|
||||
import type { Collapsible as BaseCollapsibleNS } from '@base-ui/react/collapsible'
|
||||
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type CollapsibleProps = Omit<BaseCollapsibleNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type CollapsibleProps = BaseCollapsibleNS.Root.Props
|
||||
function Collapsible({ className, ...props }: CollapsibleProps) {
|
||||
return <BaseCollapsible.Root className={cn('flex min-w-0 flex-col', className)} {...props} />
|
||||
return (
|
||||
<BaseCollapsible.Root
|
||||
className={(state) => cn('flex min-w-0 flex-col', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const CollapsibleTrigger = BaseCollapsible.Trigger
|
||||
type CollapsibleTriggerProps = BaseCollapsibleNS.Trigger.Props
|
||||
|
||||
type CollapsiblePanelProps = Omit<BaseCollapsibleNS.Panel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type CollapsiblePanelProps = BaseCollapsibleNS.Panel.Props
|
||||
function CollapsiblePanel({ className, ...props }: CollapsiblePanelProps) {
|
||||
return (
|
||||
<BaseCollapsible.Panel
|
||||
className={cn(
|
||||
'h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none',
|
||||
"[&[hidden]:not([hidden='until-found'])]:hidden",
|
||||
'data-ending-style:h-0 data-starting-style:h-0',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none',
|
||||
"[&[hidden]:not([hidden='until-found'])]:hidden",
|
||||
'data-ending-style:h-0 data-starting-style:h-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Combobox as BaseCombobox } from '@base-ui/react/combobox'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { formLabelClassName, textControlCompoundInputFocusClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import {
|
||||
floatingGroupLabelClassName,
|
||||
floatingItemIndicatorClassName,
|
||||
@@ -113,9 +114,8 @@ const comboboxTriggerVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type ComboboxTriggerProps = Omit<BaseCombobox.Trigger.Props, 'className'> &
|
||||
type ComboboxTriggerProps = BaseCombobox.Trigger.Props &
|
||||
VariantProps<typeof comboboxTriggerVariants> & {
|
||||
className?: string
|
||||
icon?: React.ReactNode | false
|
||||
}
|
||||
|
||||
@@ -130,7 +130,9 @@ function ComboboxTrigger({
|
||||
return (
|
||||
<BaseCombobox.Trigger
|
||||
type={type}
|
||||
className={cn(comboboxTriggerVariants({ size, className }))}
|
||||
className={(state) =>
|
||||
cn(comboboxTriggerVariants({ size, className: resolveClassName(className, state) }))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<span className="min-w-0 grow truncate">{children}</span>
|
||||
@@ -169,13 +171,15 @@ const comboboxInputGroupVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type ComboboxInputGroupProps = Omit<BaseCombobox.InputGroup.Props, 'className'> &
|
||||
VariantProps<typeof comboboxInputGroupVariants> & { className?: string }
|
||||
type ComboboxInputGroupProps = BaseCombobox.InputGroup.Props &
|
||||
VariantProps<typeof comboboxInputGroupVariants>
|
||||
|
||||
function ComboboxInputGroup({ className, size = 'medium', ...props }: ComboboxInputGroupProps) {
|
||||
return (
|
||||
<BaseCombobox.InputGroup
|
||||
className={cn(comboboxInputGroupVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(comboboxInputGroupVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -202,8 +206,8 @@ const comboboxInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type ComboboxInputProps = Omit<BaseCombobox.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof comboboxInputVariants> & { className?: string }
|
||||
type ComboboxInputProps = Omit<BaseCombobox.Input.Props, 'size'> &
|
||||
VariantProps<typeof comboboxInputVariants>
|
||||
|
||||
function ComboboxInput({
|
||||
className,
|
||||
@@ -214,7 +218,7 @@ function ComboboxInput({
|
||||
return (
|
||||
<BaseCombobox.Input
|
||||
autoComplete={autoComplete}
|
||||
className={cn(comboboxInputVariants({ size }), className)}
|
||||
className={(state) => cn(comboboxInputVariants({ size }), resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -244,8 +248,7 @@ const comboboxControlVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type ComboboxClearProps = Omit<BaseCombobox.Clear.Props, 'className'> &
|
||||
VariantProps<typeof comboboxControlVariants> & { className?: string }
|
||||
type ComboboxClearProps = BaseCombobox.Clear.Props & VariantProps<typeof comboboxControlVariants>
|
||||
|
||||
function ComboboxClear({
|
||||
className,
|
||||
@@ -258,11 +261,13 @@ function ComboboxClear({
|
||||
<BaseCombobox.Clear
|
||||
type={type}
|
||||
aria-label={props['aria-label'] ?? (props['aria-labelledby'] ? undefined : 'Clear combobox')}
|
||||
className={cn(
|
||||
comboboxControlVariants({ size }),
|
||||
'data-ending-style:opacity-0 data-starting-style:opacity-0',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
comboboxControlVariants({ size }),
|
||||
'data-ending-style:opacity-0 data-starting-style:opacity-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-close-line size-4" aria-hidden="true" />}
|
||||
@@ -270,8 +275,8 @@ function ComboboxClear({
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxInputTriggerProps = Omit<BaseCombobox.Trigger.Props, 'className'> &
|
||||
VariantProps<typeof comboboxControlVariants> & { className?: string }
|
||||
type ComboboxInputTriggerProps = BaseCombobox.Trigger.Props &
|
||||
VariantProps<typeof comboboxControlVariants>
|
||||
|
||||
function ComboboxInputTrigger({
|
||||
className,
|
||||
@@ -286,7 +291,9 @@ function ComboboxInputTrigger({
|
||||
aria-label={
|
||||
props['aria-label'] ?? (props['aria-labelledby'] ? undefined : 'Open combobox options')
|
||||
}
|
||||
className={cn(comboboxControlVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(comboboxControlVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-arrow-down-s-line size-4" aria-hidden="true" />}
|
||||
@@ -294,14 +301,14 @@ function ComboboxInputTrigger({
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxIconProps = Omit<BaseCombobox.Icon.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxIconProps = BaseCombobox.Icon.Props
|
||||
|
||||
function ComboboxIcon({ className, children, ...props }: ComboboxIconProps) {
|
||||
return (
|
||||
<BaseCombobox.Icon
|
||||
className={cn('flex shrink-0 items-center text-text-tertiary', className)}
|
||||
className={(state) =>
|
||||
cn('flex shrink-0 items-center text-text-tertiary', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-arrow-down-s-line size-4" aria-hidden="true" />}
|
||||
@@ -312,11 +319,7 @@ function ComboboxIcon({ className, children, ...props }: ComboboxIconProps) {
|
||||
const ComboboxPortal = BaseCombobox.Portal
|
||||
type ComboboxPortalProps = BaseCombobox.Portal.Props
|
||||
|
||||
type ComboboxPositionerProps = Omit<
|
||||
BaseCombobox.Positioner.Props,
|
||||
'className' | 'side' | 'align'
|
||||
> & {
|
||||
className?: string
|
||||
type ComboboxPositionerProps = Omit<BaseCombobox.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -333,41 +336,53 @@ function ComboboxPositioner({
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxPopupProps = Omit<BaseCombobox.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxPopupProps = BaseCombobox.Popup.Props
|
||||
|
||||
function ComboboxPopup({ className, ...props }: ComboboxPopupProps) {
|
||||
return (
|
||||
<BaseCombobox.Popup
|
||||
className={cn(comboboxPopupClassName, floatingPopupAnimationClassName, className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
comboboxPopupClassName,
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxListProps<Item = unknown> = Omit<BaseCombobox.List.Props, 'children' | 'className'> & {
|
||||
className?: string
|
||||
type ComboboxListProps<Item = unknown> = Omit<BaseCombobox.List.Props, 'children'> & {
|
||||
children?: React.ReactNode | ((item: Item, index: number) => React.ReactNode)
|
||||
}
|
||||
|
||||
function ComboboxList<Item = unknown>({ className, ...props }: ComboboxListProps<Item>) {
|
||||
return <BaseCombobox.List className={cn(comboboxListClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseCombobox.List
|
||||
className={(state) => cn(comboboxListClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxItemProps<Value = unknown> = Omit<BaseCombobox.Item.Props, 'className' | 'value'> & {
|
||||
className?: string
|
||||
type ComboboxItemProps<Value = unknown> = Omit<BaseCombobox.Item.Props, 'value'> & {
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function ComboboxItem<Value = unknown>({ className, ...props }: ComboboxItemProps<Value>) {
|
||||
return <BaseCombobox.Item className={cn(comboboxItemClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseCombobox.Item
|
||||
className={(state) => cn(comboboxItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxItemTextProps = React.ComponentProps<'span'>
|
||||
@@ -381,7 +396,7 @@ function ComboboxItemText({ className, ...props }: ComboboxItemTextProps) {
|
||||
function ComboboxItemIndicator({ className, children, ...props }: ComboboxItemIndicatorProps) {
|
||||
return (
|
||||
<BaseCombobox.ItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-check-line h-4 w-4" aria-hidden="true" />}
|
||||
@@ -389,93 +404,102 @@ function ComboboxItemIndicator({ className, children, ...props }: ComboboxItemIn
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxItemIndicatorProps = Omit<
|
||||
BaseCombobox.ItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & {
|
||||
type ComboboxItemIndicatorProps = Omit<BaseCombobox.ItemIndicator.Props, 'children'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
type ComboboxLabelProps = Omit<BaseCombobox.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxLabelProps = BaseCombobox.Label.Props
|
||||
|
||||
function ComboboxLabel({ className, ...props }: ComboboxLabelProps) {
|
||||
return <BaseCombobox.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
type ComboboxGroupLabelProps = Omit<BaseCombobox.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ComboboxGroupLabel({ className, ...props }: ComboboxGroupLabelProps) {
|
||||
return (
|
||||
<BaseCombobox.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
<BaseCombobox.Label
|
||||
className={(state) => cn(formLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxSeparatorProps = Omit<BaseCombobox.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
type ComboboxGroupLabelProps = BaseCombobox.GroupLabel.Props
|
||||
|
||||
function ComboboxGroupLabel({ className, ...props }: ComboboxGroupLabelProps) {
|
||||
return (
|
||||
<BaseCombobox.GroupLabel
|
||||
className={(state) => cn(floatingGroupLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxSeparatorProps = BaseCombobox.Separator.Props
|
||||
|
||||
function ComboboxSeparator({ className, ...props }: ComboboxSeparatorProps) {
|
||||
return <BaseCombobox.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseCombobox.Separator
|
||||
className={(state) => cn(floatingSeparatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxEmptyProps = Omit<BaseCombobox.Empty.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxEmptyProps = BaseCombobox.Empty.Props
|
||||
|
||||
function ComboboxEmpty({ className, ...props }: ComboboxEmptyProps) {
|
||||
return (
|
||||
<BaseCombobox.Empty
|
||||
className={cn(
|
||||
'px-3 py-2 system-sm-regular text-text-tertiary empty:h-0 empty:p-0',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'px-3 py-2 system-sm-regular text-text-tertiary empty:h-0 empty:p-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxStatusProps = Omit<BaseCombobox.Status.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxStatusProps = BaseCombobox.Status.Props
|
||||
|
||||
function ComboboxStatus({ className, ...props }: ComboboxStatusProps) {
|
||||
return (
|
||||
<BaseCombobox.Status
|
||||
className={cn('px-3 py-2 system-sm-regular text-text-tertiary empty:p-0', className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'px-3 py-2 system-sm-regular text-text-tertiary empty:p-0',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxChipsProps = Omit<BaseCombobox.Chips.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxChipsProps = BaseCombobox.Chips.Props
|
||||
|
||||
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)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'flex w-full min-w-0 flex-wrap items-center gap-1 px-1',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxChipProps = Omit<BaseCombobox.Chip.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxChipProps = BaseCombobox.Chip.Props
|
||||
|
||||
function ComboboxChip({ className, ...props }: ComboboxChipProps) {
|
||||
return (
|
||||
<BaseCombobox.Chip
|
||||
className={cn(
|
||||
'inline-flex max-w-full min-w-0 items-center gap-1 rounded-md bg-state-base-hover px-1.5 py-0.5 system-xs-medium text-text-secondary',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'inline-flex max-w-full min-w-0 items-center gap-1 rounded-md bg-state-base-hover px-1.5 py-0.5 system-xs-medium text-text-secondary',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -493,10 +517,12 @@ function ComboboxChipRemove({
|
||||
aria-label={
|
||||
props['aria-label'] ?? (props['aria-labelledby'] ? undefined : 'Remove selected item')
|
||||
}
|
||||
className={cn(
|
||||
'flex size-3.5 shrink-0 items-center justify-center rounded-sm text-text-tertiary outline-hidden hover:bg-state-base-hover-alt hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'flex size-3.5 shrink-0 items-center justify-center rounded-sm text-text-tertiary outline-hidden hover:bg-state-base-hover-alt hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span className="i-ri-close-line size-3" aria-hidden="true" />}
|
||||
@@ -504,9 +530,7 @@ function ComboboxChipRemove({
|
||||
)
|
||||
}
|
||||
|
||||
type ComboboxChipRemoveProps = Omit<BaseCombobox.ChipRemove.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ComboboxChipRemoveProps = BaseCombobox.ChipRemove.Props
|
||||
|
||||
export {
|
||||
Combobox,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { MenuItemVariant } from '../overlay-shared'
|
||||
import type { Placement } from '../placement'
|
||||
import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import {
|
||||
floatingGroupLabelClassName,
|
||||
floatingItemIndicatorClassName,
|
||||
@@ -45,11 +46,10 @@ function ContextMenuRadioGroup<Value = unknown>(
|
||||
return <BaseContextMenu.RadioGroup {...props} />
|
||||
}
|
||||
|
||||
type ContextMenuContentProps = Omit<BaseContextMenu.Popup.Props, 'children' | 'className'> &
|
||||
type ContextMenuContentProps = Omit<BaseContextMenu.Popup.Props, 'children'> &
|
||||
Pick<BaseContextMenu.Positioner.Props, 'sideOffset' | 'alignOffset'> & {
|
||||
children: React.ReactNode
|
||||
placement?: Placement
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuContent({
|
||||
@@ -72,7 +72,13 @@ function ContextMenuContent({
|
||||
className="z-50 outline-hidden"
|
||||
>
|
||||
<BaseContextMenu.Popup
|
||||
className={cn(menuPopupClassName, floatingPopupAnimationClassName, className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
menuPopupClassName,
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -82,24 +88,24 @@ function ContextMenuContent({
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuItemProps = Omit<BaseContextMenu.Item.Props, 'className'> & {
|
||||
type ContextMenuItemProps = BaseContextMenu.Item.Props & {
|
||||
variant?: ContextMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuItem({ className, variant = 'default', ...props }: ContextMenuItemProps) {
|
||||
return (
|
||||
<BaseContextMenu.Item
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuLinkItemProps = Omit<BaseContextMenu.LinkItem.Props, 'className'> & {
|
||||
type ContextMenuLinkItemProps = BaseContextMenu.LinkItem.Props & {
|
||||
variant?: ContextMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuLinkItem({
|
||||
@@ -111,18 +117,16 @@ function ContextMenuLinkItem({
|
||||
return (
|
||||
<BaseContextMenu.LinkItem
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
closeOnClick={closeOnClick}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuRadioItemProps<Value = unknown> = Omit<
|
||||
BaseContextMenu.RadioItem.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
type ContextMenuRadioItemProps<Value = unknown> = Omit<BaseContextMenu.RadioItem.Props, 'value'> & {
|
||||
value: Value
|
||||
}
|
||||
|
||||
@@ -130,16 +134,24 @@ function ContextMenuRadioItem<Value = unknown>({
|
||||
className,
|
||||
...props
|
||||
}: ContextMenuRadioItemProps<Value>) {
|
||||
return <BaseContextMenu.RadioItem className={cn(menuItemClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseContextMenu.RadioItem
|
||||
className={(state) => cn(menuItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItem({ className, ...props }: ContextMenuCheckboxItemProps) {
|
||||
return <BaseContextMenu.CheckboxItem className={cn(menuItemClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseContextMenu.CheckboxItem
|
||||
className={(state) => cn(menuItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuCheckboxItemProps = Omit<BaseContextMenu.CheckboxItem.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ContextMenuCheckboxItemProps = BaseContextMenu.CheckboxItem.Props
|
||||
|
||||
function ContextMenuCheckboxItemIndicator({
|
||||
className,
|
||||
@@ -147,7 +159,7 @@ function ContextMenuCheckboxItemIndicator({
|
||||
}: ContextMenuCheckboxItemIndicatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.CheckboxItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
<span aria-hidden className="i-ri-check-line h-4 w-4" />
|
||||
@@ -157,8 +169,8 @@ function ContextMenuCheckboxItemIndicator({
|
||||
|
||||
type ContextMenuCheckboxItemIndicatorProps = Omit<
|
||||
BaseContextMenu.CheckboxItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
'children'
|
||||
>
|
||||
|
||||
function ContextMenuRadioItemIndicator({
|
||||
className,
|
||||
@@ -166,7 +178,7 @@ function ContextMenuRadioItemIndicator({
|
||||
}: ContextMenuRadioItemIndicatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.RadioItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
<span aria-hidden className="i-ri-check-line h-4 w-4" />
|
||||
@@ -174,14 +186,10 @@ function ContextMenuRadioItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuRadioItemIndicatorProps = Omit<
|
||||
BaseContextMenu.RadioItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
type ContextMenuRadioItemIndicatorProps = Omit<BaseContextMenu.RadioItemIndicator.Props, 'children'>
|
||||
|
||||
type ContextMenuSubTriggerProps = Omit<BaseContextMenu.SubmenuTrigger.Props, 'className'> & {
|
||||
type ContextMenuSubTriggerProps = BaseContextMenu.SubmenuTrigger.Props & {
|
||||
variant?: ContextMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
@@ -193,7 +201,9 @@ function ContextMenuSubTrigger({
|
||||
return (
|
||||
<BaseContextMenu.SubmenuTrigger
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -237,23 +247,25 @@ function ContextMenuSubContent({
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuLabelProps = Omit<BaseContextMenu.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ContextMenuLabelProps = BaseContextMenu.GroupLabel.Props
|
||||
|
||||
function ContextMenuLabel({ className, ...props }: ContextMenuLabelProps) {
|
||||
return (
|
||||
<BaseContextMenu.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
<BaseContextMenu.GroupLabel
|
||||
className={(state) => cn(floatingGroupLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuSeparatorProps = Omit<BaseContextMenu.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ContextMenuSeparatorProps = BaseContextMenu.Separator.Props
|
||||
|
||||
function ContextMenuSeparator({ className, ...props }: ContextMenuSeparatorProps) {
|
||||
return (
|
||||
<BaseContextMenu.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
<BaseContextMenu.Separator
|
||||
className={(state) => cn(floatingSeparatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type * as React from 'react'
|
||||
import { Dialog as BaseDialog } from '@base-ui/react/dialog'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { modalBackdropClassName, modalPopupAnimationClassName } from '../overlay-shared'
|
||||
|
||||
const Dialog = BaseDialog.Root
|
||||
@@ -21,42 +22,47 @@ type DialogDescriptionProps = BaseDialog.Description.Props
|
||||
type DialogPortalProps = BaseDialog.Portal.Props
|
||||
type DialogCloseProps = BaseDialog.Close.Props
|
||||
|
||||
type DialogBackdropProps = Omit<BaseDialog.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DialogBackdropProps = BaseDialog.Backdrop.Props
|
||||
|
||||
function DialogBackdrop({ className, ...props }: DialogBackdropProps) {
|
||||
return <BaseDialog.Backdrop {...props} className={cn(modalBackdropClassName, className)} />
|
||||
return (
|
||||
<BaseDialog.Backdrop
|
||||
{...props}
|
||||
className={(state) => cn(modalBackdropClassName, resolveClassName(className, state))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogViewportProps = Omit<BaseDialog.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DialogViewportProps = BaseDialog.Viewport.Props
|
||||
|
||||
function DialogViewport({ className, ...props }: DialogViewportProps) {
|
||||
return <BaseDialog.Viewport className={cn('fixed inset-0 z-50', className)} {...props} />
|
||||
}
|
||||
|
||||
type DialogPopupProps = Omit<BaseDialog.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DialogPopup({ className, ...props }: DialogPopupProps) {
|
||||
return (
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
'z-50 rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl',
|
||||
modalPopupAnimationClassName,
|
||||
className,
|
||||
)}
|
||||
<BaseDialog.Viewport
|
||||
className={(state) => cn('fixed inset-0 z-50', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogContentProps = Omit<DialogPopupProps, 'children' | 'className'> & {
|
||||
type DialogPopupProps = BaseDialog.Popup.Props
|
||||
|
||||
function DialogPopup({ className, ...props }: DialogPopupProps) {
|
||||
return (
|
||||
<BaseDialog.Popup
|
||||
className={(state) =>
|
||||
cn(
|
||||
'z-50 rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl',
|
||||
modalPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogContentProps = Omit<DialogPopupProps, 'children'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
backdropProps?: DialogBackdropProps
|
||||
}
|
||||
|
||||
@@ -65,10 +71,12 @@ function DialogContent({ children, className, backdropProps, ...props }: DialogC
|
||||
<DialogPortal>
|
||||
<DialogBackdrop {...backdropProps} />
|
||||
<DialogPopup
|
||||
className={cn(
|
||||
'fixed top-1/2 left-1/2 max-h-[80dvh] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain p-6',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'fixed top-1/2 left-1/2 max-h-[80dvh] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain p-6',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as React from 'react'
|
||||
import { Drawer as BaseDrawer } from '@base-ui/react/drawer'
|
||||
import { cn } from '../cn'
|
||||
import { iconButtonVariants } from '../icon-button/variants'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const Drawer = BaseDrawer.Root
|
||||
const DrawerProvider = BaseDrawer.Provider
|
||||
@@ -29,83 +30,82 @@ type DrawerTitleProps = BaseDrawer.Title.Props
|
||||
type DrawerDescriptionProps = BaseDrawer.Description.Props
|
||||
type DrawerCloseProps = BaseDrawer.Close.Props
|
||||
|
||||
type DrawerBackdropProps = Omit<BaseDrawer.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DrawerBackdropProps = BaseDrawer.Backdrop.Props
|
||||
|
||||
function DrawerBackdrop({ className, ...props }: DrawerBackdropProps) {
|
||||
return (
|
||||
<BaseDrawer.Backdrop
|
||||
className={cn(
|
||||
'absolute inset-0 z-50 bg-background-overlay opacity-[calc(1-var(--drawer-swipe-progress,0))]',
|
||||
'transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0 data-swiping:duration-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'absolute inset-0 z-50 bg-background-overlay opacity-[calc(1-var(--drawer-swipe-progress,0))]',
|
||||
'transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0 data-swiping:duration-0 motion-reduce:transition-none',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerViewportProps = Omit<BaseDrawer.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DrawerViewportProps = BaseDrawer.Viewport.Props
|
||||
|
||||
function DrawerViewport({ className, ...props }: DrawerViewportProps) {
|
||||
return (
|
||||
<BaseDrawer.Viewport
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 touch-none overflow-hidden overscroll-contain outline-hidden',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'fixed inset-0 z-50 touch-none overflow-hidden overscroll-contain outline-hidden',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerPopupProps = Omit<BaseDrawer.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DrawerPopupProps = BaseDrawer.Popup.Props
|
||||
|
||||
function DrawerPopup({ className, ...props }: DrawerPopupProps) {
|
||||
return (
|
||||
<BaseDrawer.Popup
|
||||
className={cn(
|
||||
'fixed z-50 flex min-h-0 touch-none flex-col overflow-hidden border-[0.5px] border-components-panel-border bg-components-panel-bg text-text-primary shadow-xl outline-hidden',
|
||||
'transition-[transform,opacity,box-shadow] duration-200 data-swiping:duration-0 data-swiping:select-none motion-reduce:transition-none',
|
||||
'data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:h-dvh data-[swipe-direction=right]:w-120 data-[swipe-direction=right]:max-w-[calc(100vw-2rem)] data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=right]:rounded-l-2xl data-[swipe-direction=right]:border-r-0',
|
||||
'data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))]',
|
||||
'data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:h-dvh data-[swipe-direction=left]:w-120 data-[swipe-direction=left]:max-w-[calc(100vw-2rem)] data-[swipe-direction=left]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=left]:rounded-r-2xl data-[swipe-direction=left]:border-l-0',
|
||||
'data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))] data-starting-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))]',
|
||||
'data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=down]:w-full data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px)))] data-[swipe-direction=down]:rounded-t-2xl data-[swipe-direction=down]:border-b-0',
|
||||
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))]',
|
||||
'data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=up]:w-full data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-[swipe-direction=up]:rounded-b-2xl data-[swipe-direction=up]:border-t-0',
|
||||
'data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))] data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))]',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'fixed z-50 flex min-h-0 touch-none flex-col overflow-hidden border-[0.5px] border-components-panel-border bg-components-panel-bg text-text-primary shadow-xl outline-hidden',
|
||||
'transition-[transform,opacity,box-shadow] duration-200 data-swiping:duration-0 data-swiping:select-none motion-reduce:transition-none',
|
||||
'data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:h-dvh data-[swipe-direction=right]:w-120 data-[swipe-direction=right]:max-w-[calc(100vw-2rem)] data-[swipe-direction=right]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=right]:rounded-l-2xl data-[swipe-direction=right]:border-r-0',
|
||||
'data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))] data-starting-style:data-[swipe-direction=right]:transform-[translateX(calc(100%+2px))]',
|
||||
'data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:h-dvh data-[swipe-direction=left]:w-120 data-[swipe-direction=left]:max-w-[calc(100vw-2rem)] data-[swipe-direction=left]:transform-[translateX(var(--drawer-swipe-movement-x,0px))] data-[swipe-direction=left]:rounded-r-2xl data-[swipe-direction=left]:border-l-0',
|
||||
'data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))] data-starting-style:data-[swipe-direction=left]:transform-[translateX(calc(-100%-2px))]',
|
||||
'data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=down]:w-full data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px)))] data-[swipe-direction=down]:rounded-t-2xl data-[swipe-direction=down]:border-b-0',
|
||||
'data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))] data-starting-style:data-[swipe-direction=down]:transform-[translateY(calc(100%+2px))]',
|
||||
'data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:max-h-[calc(100dvh-2rem)] data-[swipe-direction=up]:w-full data-[swipe-direction=up]:transform-[translateY(var(--drawer-swipe-movement-y,0px))] data-[swipe-direction=up]:rounded-b-2xl data-[swipe-direction=up]:border-t-0',
|
||||
'data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))] data-starting-style:data-[swipe-direction=up]:transform-[translateY(calc(-100%-2px))]',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerContentProps = Omit<BaseDrawer.Content.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DrawerContentProps = BaseDrawer.Content.Props
|
||||
|
||||
function DrawerContent({ className, ...props }: DrawerContentProps) {
|
||||
return (
|
||||
<BaseDrawer.Content
|
||||
className={cn(
|
||||
'min-h-0 flex-1 overflow-y-auto overscroll-contain p-6 pb-[calc(1.5rem+env(safe-area-inset-bottom,0))]',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'min-h-0 flex-1 overflow-y-auto overscroll-contain p-6 pb-[calc(1.5rem+env(safe-area-inset-bottom,0))]',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerCloseButtonProps = Omit<BaseDrawer.Close.Props, 'children' | 'className'> & {
|
||||
type DrawerCloseButtonProps = Omit<BaseDrawer.Close.Props, 'children'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DrawerCloseButton({
|
||||
@@ -119,11 +119,13 @@ function DrawerCloseButton({
|
||||
<BaseDrawer.Close
|
||||
type={type}
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
iconButtonVariants({ size: 'lg' }),
|
||||
'focus-visible:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50 data-disabled:text-text-tertiary',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
iconButtonVariants({ size: 'lg' }),
|
||||
'focus-visible:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50 data-disabled:text-text-tertiary',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <span aria-hidden="true" className="i-ri-close-line size-4" />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { MenuItemVariant } from '../overlay-shared'
|
||||
import type { Placement } from '../placement'
|
||||
import { Menu } from '@base-ui/react/menu'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import {
|
||||
floatingGroupLabelClassName,
|
||||
floatingItemIndicatorClassName,
|
||||
@@ -44,11 +45,7 @@ function DropdownMenuRadioGroup<Value = unknown>(
|
||||
return <Menu.RadioGroup {...props} />
|
||||
}
|
||||
|
||||
type DropdownMenuRadioItemProps<Value = unknown> = Omit<
|
||||
Menu.RadioItem.Props,
|
||||
'className' | 'value'
|
||||
> & {
|
||||
className?: string
|
||||
type DropdownMenuRadioItemProps<Value = unknown> = Omit<Menu.RadioItem.Props, 'value'> & {
|
||||
value: Value
|
||||
}
|
||||
|
||||
@@ -56,7 +53,12 @@ function DropdownMenuRadioItem<Value = unknown>({
|
||||
className,
|
||||
...props
|
||||
}: DropdownMenuRadioItemProps<Value>) {
|
||||
return <Menu.RadioItem className={cn(menuItemClassName, className)} {...props} />
|
||||
return (
|
||||
<Menu.RadioItem
|
||||
className={(state) => cn(menuItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItemIndicator({
|
||||
@@ -64,23 +66,26 @@ function DropdownMenuRadioItemIndicator({
|
||||
...props
|
||||
}: DropdownMenuRadioItemIndicatorProps) {
|
||||
return (
|
||||
<Menu.RadioItemIndicator className={cn(floatingItemIndicatorClassName, className)} {...props}>
|
||||
<Menu.RadioItemIndicator
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
<span aria-hidden className="i-ri-check-line h-4 w-4" />
|
||||
</Menu.RadioItemIndicator>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuRadioItemIndicatorProps = Omit<
|
||||
Menu.RadioItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
type DropdownMenuRadioItemIndicatorProps = Omit<Menu.RadioItemIndicator.Props, 'children'>
|
||||
|
||||
type DropdownMenuCheckboxItemProps = Omit<Menu.CheckboxItem.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DropdownMenuCheckboxItemProps = Menu.CheckboxItem.Props
|
||||
|
||||
function DropdownMenuCheckboxItem({ className, ...props }: DropdownMenuCheckboxItemProps) {
|
||||
return <Menu.CheckboxItem className={cn(menuItemClassName, className)} {...props} />
|
||||
return (
|
||||
<Menu.CheckboxItem
|
||||
className={(state) => cn(menuItemClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItemIndicator({
|
||||
@@ -89,7 +94,7 @@ function DropdownMenuCheckboxItemIndicator({
|
||||
}: DropdownMenuCheckboxItemIndicatorProps) {
|
||||
return (
|
||||
<Menu.CheckboxItemIndicator
|
||||
className={cn(floatingItemIndicatorClassName, className)}
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
<span aria-hidden className="i-ri-check-line h-4 w-4" />
|
||||
@@ -97,21 +102,20 @@ function DropdownMenuCheckboxItemIndicator({
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuCheckboxItemIndicatorProps = Omit<
|
||||
Menu.CheckboxItemIndicator.Props,
|
||||
'children' | 'className'
|
||||
> & { className?: string }
|
||||
type DropdownMenuCheckboxItemIndicatorProps = Omit<Menu.CheckboxItemIndicator.Props, 'children'>
|
||||
|
||||
type DropdownMenuLabelProps = Omit<Menu.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DropdownMenuLabelProps = Menu.GroupLabel.Props
|
||||
|
||||
function DropdownMenuLabel({ className, ...props }: DropdownMenuLabelProps) {
|
||||
return <Menu.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
return (
|
||||
<Menu.GroupLabel
|
||||
className={(state) => cn(floatingGroupLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuPositionerProps = Omit<Menu.Positioner.Props, 'className' | 'side' | 'align'> & {
|
||||
className?: string
|
||||
type DropdownMenuPositionerProps = Omit<Menu.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -130,29 +134,32 @@ function DropdownMenuPositioner({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuPopupProps = Omit<Menu.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DropdownMenuPopupProps = Menu.Popup.Props
|
||||
|
||||
function DropdownMenuPopup({ className, ...props }: DropdownMenuPopupProps) {
|
||||
return (
|
||||
<Menu.Popup
|
||||
className={cn(menuPopupBaseClassName, floatingPopupAnimationClassName, className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
menuPopupBaseClassName,
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuContentProps = Omit<DropdownMenuPopupProps, 'children' | 'className'> &
|
||||
type DropdownMenuContentProps = Omit<DropdownMenuPopupProps, 'children'> &
|
||||
Pick<DropdownMenuPositionerProps, 'alignOffset' | 'placement' | 'sideOffset'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
@@ -170,7 +177,10 @@ function DropdownMenuContent({
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
>
|
||||
<DropdownMenuPopup className={cn(menuPopupSurfaceClassName, className)} {...props}>
|
||||
<DropdownMenuPopup
|
||||
className={(state) => cn(menuPopupSurfaceClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPopup>
|
||||
</DropdownMenuPositioner>
|
||||
@@ -178,9 +188,8 @@ function DropdownMenuContent({
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuSubTriggerProps = Omit<Menu.SubmenuTrigger.Props, 'className'> & {
|
||||
type DropdownMenuSubTriggerProps = Menu.SubmenuTrigger.Props & {
|
||||
variant?: DropdownMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
@@ -192,7 +201,9 @@ function DropdownMenuSubTrigger({
|
||||
return (
|
||||
<Menu.SubmenuTrigger
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -229,24 +240,24 @@ function DropdownMenuSubContent({
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuItemProps = Omit<Menu.Item.Props, 'className'> & {
|
||||
type DropdownMenuItemProps = Menu.Item.Props & {
|
||||
variant?: DropdownMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuItem({ className, variant = 'default', ...props }: DropdownMenuItemProps) {
|
||||
return (
|
||||
<Menu.Item
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuLinkItemProps = Omit<Menu.LinkItem.Props, 'className'> & {
|
||||
type DropdownMenuLinkItemProps = Menu.LinkItem.Props & {
|
||||
variant?: DropdownMenuItemVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
function DropdownMenuLinkItem({
|
||||
@@ -258,19 +269,24 @@ function DropdownMenuLinkItem({
|
||||
return (
|
||||
<Menu.LinkItem
|
||||
data-variant={variant}
|
||||
className={cn(menuItemClassName, menuItemDestructiveClassName, className)}
|
||||
className={(state) =>
|
||||
cn(menuItemClassName, menuItemDestructiveClassName, resolveClassName(className, state))
|
||||
}
|
||||
closeOnClick={closeOnClick}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuSeparatorProps = Omit<Menu.Separator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type DropdownMenuSeparatorProps = Menu.Separator.Props
|
||||
|
||||
function DropdownMenuSeparator({ className, ...props }: DropdownMenuSeparatorProps) {
|
||||
return <Menu.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
return (
|
||||
<Menu.Separator
|
||||
className={(state) => cn(floatingSeparatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -4,52 +4,64 @@ import type { Field as BaseFieldNS } from '@base-ui/react/field'
|
||||
import { Field as BaseField } from '@base-ui/react/field'
|
||||
import { cn } from '../cn'
|
||||
import { formLabelClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type FieldProps = Omit<BaseFieldNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type FieldProps = BaseFieldNS.Root.Props
|
||||
|
||||
function Field({ className, ...props }: FieldProps) {
|
||||
return <BaseField.Root className={cn('group/field grid min-w-0 gap-1', className)} {...props} />
|
||||
}
|
||||
|
||||
type FieldItemProps = Omit<BaseFieldNS.Item.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function FieldItem({ className, ...props }: FieldItemProps) {
|
||||
return <BaseField.Item className={cn('grid min-w-0 gap-1', className)} {...props} />
|
||||
}
|
||||
|
||||
type FieldLabelProps = Omit<BaseFieldNS.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function FieldLabel({ className, ...props }: FieldLabelProps) {
|
||||
return <BaseField.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
}
|
||||
|
||||
type FieldDescriptionProps = Omit<BaseFieldNS.Description.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function FieldDescription({ className, ...props }: FieldDescriptionProps) {
|
||||
return (
|
||||
<BaseField.Description
|
||||
className={cn('py-0.5 body-xs-regular text-text-tertiary', className)}
|
||||
<BaseField.Root
|
||||
className={(state) =>
|
||||
cn('group/field grid min-w-0 gap-1', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldErrorProps = Omit<BaseFieldNS.Error.Props, 'className'> & {
|
||||
className?: string
|
||||
type FieldItemProps = BaseFieldNS.Item.Props
|
||||
|
||||
function FieldItem({ className, ...props }: FieldItemProps) {
|
||||
return (
|
||||
<BaseField.Item
|
||||
className={(state) => cn('grid min-w-0 gap-1', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldLabelProps = BaseFieldNS.Label.Props
|
||||
|
||||
function FieldLabel({ className, ...props }: FieldLabelProps) {
|
||||
return (
|
||||
<BaseField.Label
|
||||
className={(state) => cn(formLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldDescriptionProps = BaseFieldNS.Description.Props
|
||||
|
||||
function FieldDescription({ className, ...props }: FieldDescriptionProps) {
|
||||
return (
|
||||
<BaseField.Description
|
||||
className={(state) =>
|
||||
cn('py-0.5 body-xs-regular text-text-tertiary', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldErrorProps = BaseFieldNS.Error.Props
|
||||
|
||||
function FieldError({ className, ...props }: FieldErrorProps) {
|
||||
return (
|
||||
<BaseField.Error
|
||||
className={cn('py-0.5 body-xs-regular text-text-destructive', className)}
|
||||
className={(state) =>
|
||||
cn('py-0.5 body-xs-regular text-text-destructive', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -3,23 +3,27 @@
|
||||
import type { Fieldset as BaseFieldsetNS } from '@base-ui/react/fieldset'
|
||||
import { Fieldset as BaseFieldset } from '@base-ui/react/fieldset'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type FieldsetProps = Omit<BaseFieldsetNS.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type FieldsetProps = BaseFieldsetNS.Root.Props
|
||||
|
||||
function Fieldset({ className, ...props }: FieldsetProps) {
|
||||
return <BaseFieldset.Root className={cn('m-0 min-w-0 border-0 p-0', className)} {...props} />
|
||||
return (
|
||||
<BaseFieldset.Root
|
||||
className={(state) => cn('m-0 min-w-0 border-0 p-0', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FieldsetLegendProps = Omit<BaseFieldsetNS.Legend.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type FieldsetLegendProps = BaseFieldsetNS.Legend.Props
|
||||
|
||||
function FieldsetLegend({ className, ...props }: FieldsetLegendProps) {
|
||||
return (
|
||||
<BaseFieldset.Legend
|
||||
className={cn('mb-1 py-1 system-sm-medium text-text-secondary', className)}
|
||||
className={(state) =>
|
||||
cn('mb-1 py-1 system-sm-medium text-text-secondary', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { mergeProps } from '@base-ui/react/merge-props'
|
||||
import { useRender } from '@base-ui/react/use-render'
|
||||
import * as React from 'react'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const FileTreeLevelContext = React.createContext(1)
|
||||
|
||||
@@ -66,17 +67,21 @@ function FileTreeList({ render, className, ...props }: FileTreeListProps) {
|
||||
})
|
||||
}
|
||||
|
||||
type FileTreeFolderProps = Omit<BaseCollapsible.Root.Props, 'className' | 'render'> & {
|
||||
className?: string
|
||||
type FileTreeFolderProps = Omit<BaseCollapsible.Root.Props, 'render'> & {
|
||||
render?: BaseCollapsible.Root.Props['render']
|
||||
}
|
||||
|
||||
function FileTreeFolder({ render = <li />, className, ...props }: FileTreeFolderProps) {
|
||||
return <BaseCollapsible.Root render={render} className={cn('min-w-0', className)} {...props} />
|
||||
return (
|
||||
<BaseCollapsible.Root
|
||||
render={render}
|
||||
className={(state) => cn('min-w-0', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type FileTreeFolderTriggerProps = Omit<BaseCollapsible.Trigger.Props, 'className'> & {
|
||||
className?: string
|
||||
type FileTreeFolderTriggerProps = BaseCollapsible.Trigger.Props & {
|
||||
level?: number
|
||||
}
|
||||
|
||||
@@ -92,7 +97,7 @@ function FileTreeFolderTrigger({
|
||||
|
||||
return (
|
||||
<BaseCollapsible.Trigger
|
||||
className={fileTreeRowClassName({ className })}
|
||||
className={(state) => fileTreeRowClassName({ className: resolveClassName(className, state) })}
|
||||
disabled={disabled}
|
||||
data-disabled={disabled || undefined}
|
||||
{...props}
|
||||
@@ -103,8 +108,7 @@ function FileTreeFolderTrigger({
|
||||
)
|
||||
}
|
||||
|
||||
type FileTreeFolderPanelProps = Omit<BaseCollapsible.Panel.Props, 'className' | 'render'> & {
|
||||
className?: string
|
||||
type FileTreeFolderPanelProps = Omit<BaseCollapsible.Panel.Props, 'render'> & {
|
||||
render?: BaseCollapsible.Panel.Props['render']
|
||||
}
|
||||
|
||||
@@ -119,7 +123,9 @@ function FileTreeFolderPanel({
|
||||
return (
|
||||
<BaseCollapsible.Panel
|
||||
render={render}
|
||||
className={cn('m-0 flex min-w-0 list-none flex-col gap-px p-0', className)}
|
||||
className={(state) =>
|
||||
cn('m-0 flex min-w-0 list-none flex-col gap-px p-0', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<FileTreeLevelContext.Provider value={level + 1}>{children}</FileTreeLevelContext.Provider>
|
||||
|
||||
@@ -53,9 +53,11 @@ the icon button.
|
||||
|
||||
- Read [`Button`] for visible-label actions, submit semantics, and loading state.
|
||||
- Read [Accessible names and descriptions] for the cross-component naming and description contract.
|
||||
- Read [Styling] for state styles, callbacks, and their composition boundary.
|
||||
- Read [Base UI Button] for the upstream interaction and composition contract.
|
||||
|
||||
[Accessible names and descriptions]: ../../docs/accessible-names-and-descriptions.md
|
||||
[Base UI Button]: https://base-ui.com/react/components/button
|
||||
[Styling]: ../../docs/styling.md
|
||||
[`Button`]: ../button/README.md
|
||||
[name, role, and value]: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { VariantProps } from 'class-variance-authority'
|
||||
import type * as React from 'react'
|
||||
import { Button as BaseButton } from '@base-ui/react/button'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { iconButtonVariants } from './variants'
|
||||
|
||||
type AccessibleName =
|
||||
@@ -17,14 +18,10 @@ type AccessibleName =
|
||||
'aria-labelledby': string
|
||||
}
|
||||
|
||||
type IconButtonProps = Omit<
|
||||
BaseButtonNS.Props,
|
||||
'aria-label' | 'aria-labelledby' | 'children' | 'className'
|
||||
> &
|
||||
type IconButtonProps = Omit<BaseButtonNS.Props, 'aria-label' | 'aria-labelledby' | 'children'> &
|
||||
AccessibleName &
|
||||
VariantProps<typeof iconButtonVariants> & {
|
||||
children: React.ReactElement
|
||||
className?: string
|
||||
}
|
||||
|
||||
function IconButton({
|
||||
@@ -39,7 +36,9 @@ function IconButton({
|
||||
return (
|
||||
<BaseButton
|
||||
type={type}
|
||||
className={cn(iconButtonVariants({ variant, tone, size }), className)}
|
||||
className={(state) =>
|
||||
cn(iconButtonVariants({ variant, tone, size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type * as React from 'react'
|
||||
import { Input as BaseInput } from '@base-ui/react/input'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const interactiveElementSelector =
|
||||
'button,a[href],[role="button"],[role="link"],select,[tabindex]:not([tabindex="-1"]),input:not([type="hidden"]):not([disabled]),[contenteditable]:not([contenteditable="false"]),textarea:not([disabled])'
|
||||
@@ -55,23 +56,23 @@ function InputGroup({ className, onMouseDown, ...props }: InputGroupProps) {
|
||||
)
|
||||
}
|
||||
|
||||
type InputGroupInputProps = Omit<BaseInputNS.Props, 'className' | 'render' | 'size'> & {
|
||||
className?: string
|
||||
}
|
||||
type InputGroupInputProps = Omit<BaseInputNS.Props, 'render' | 'size'>
|
||||
|
||||
function InputGroupInput({ className, ...props }: InputGroupInputProps) {
|
||||
return (
|
||||
<BaseInput
|
||||
{...props}
|
||||
className={cn(
|
||||
[
|
||||
'w-0 min-w-0 flex-1 appearance-none rounded-none border-0 bg-transparent px-3 py-1.75 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden',
|
||||
'placeholder:text-components-input-text-placeholder',
|
||||
'read-only:cursor-default',
|
||||
'disabled:cursor-not-allowed disabled:text-components-input-text-filled-disabled',
|
||||
],
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
[
|
||||
'w-0 min-w-0 flex-1 appearance-none rounded-none border-0 bg-transparent px-3 py-1.75 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden',
|
||||
'placeholder:text-components-input-text-placeholder',
|
||||
'read-only:cursor-default',
|
||||
'disabled:cursor-not-allowed disabled:text-components-input-text-filled-disabled',
|
||||
],
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,28 +4,29 @@ import type { Input as BaseInputNS } from '@base-ui/react/input'
|
||||
import { Input as BaseInput } from '@base-ui/react/input'
|
||||
import { cn } from '../cn'
|
||||
import { textControlFocusClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type InputProps = Omit<BaseInputNS.Props, 'className' | 'size'> & {
|
||||
className?: string
|
||||
}
|
||||
type InputProps = Omit<BaseInputNS.Props, 'size'>
|
||||
|
||||
function Input({ className, ...props }: InputProps) {
|
||||
return (
|
||||
<BaseInput
|
||||
className={cn(
|
||||
[
|
||||
'w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 py-1.75 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden transition-[background-color,border-color]',
|
||||
'placeholder:text-components-input-text-placeholder',
|
||||
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
|
||||
textControlFocusClassName,
|
||||
'data-invalid:border-components-input-border-destructive data-invalid:bg-components-input-bg-destructive',
|
||||
'read-only:cursor-default read-only:shadow-none read-only:hover:border-transparent read-only:hover:bg-components-input-bg-normal read-only:focus:border-transparent read-only:focus:bg-components-input-bg-normal read-only:focus:shadow-none read-only:focus-visible:ring-2 read-only:focus-visible:ring-state-accent-solid',
|
||||
'disabled:cursor-not-allowed disabled:border-transparent disabled:bg-components-input-bg-disabled disabled:text-components-input-text-filled-disabled',
|
||||
'disabled:hover:border-transparent disabled:hover:bg-components-input-bg-disabled',
|
||||
'motion-reduce:transition-none',
|
||||
],
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
[
|
||||
'w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 py-1.75 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden transition-[background-color,border-color]',
|
||||
'placeholder:text-components-input-text-placeholder',
|
||||
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
|
||||
textControlFocusClassName,
|
||||
'data-invalid:border-components-input-border-destructive data-invalid:bg-components-input-bg-destructive',
|
||||
'read-only:cursor-default read-only:shadow-none read-only:hover:border-transparent read-only:hover:bg-components-input-bg-normal read-only:focus:border-transparent read-only:focus:bg-components-input-bg-normal read-only:focus:shadow-none read-only:focus-visible:ring-2 read-only:focus-visible:ring-state-accent-solid',
|
||||
'disabled:cursor-not-allowed disabled:border-transparent disabled:bg-components-input-bg-disabled disabled:text-components-input-text-filled-disabled',
|
||||
'disabled:hover:border-transparent disabled:hover:bg-components-input-bg-disabled',
|
||||
'motion-reduce:transition-none',
|
||||
],
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
6
packages/dify-ui/src/internals/resolve-class-name.ts
Normal file
6
packages/dify-ui/src/internals/resolve-class-name.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function resolveClassName<State>(
|
||||
className: string | ((state: State) => string | undefined) | undefined,
|
||||
state: State,
|
||||
) {
|
||||
return typeof className === 'function' ? className(state) : className
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import type { VariantProps } from 'class-variance-authority'
|
||||
import { Meter as BaseMeter } from '@base-ui/react/meter'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const Meter = BaseMeter.Root
|
||||
type MeterProps = BaseMeter.Root.Props
|
||||
@@ -21,12 +22,15 @@ type MeterProps = BaseMeter.Root.Props
|
||||
const meterTrackClassName =
|
||||
'relative block h-1 w-full overflow-hidden rounded-md bg-components-progress-bar-bg'
|
||||
|
||||
type MeterTrackProps = Omit<BaseMeter.Track.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type MeterTrackProps = BaseMeter.Track.Props
|
||||
|
||||
function MeterTrack({ className, ...props }: MeterTrackProps) {
|
||||
return <BaseMeter.Track className={cn(meterTrackClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseMeter.Track
|
||||
className={(state) => cn(meterTrackClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const meterIndicatorVariants = cva(
|
||||
@@ -47,33 +51,43 @@ const meterIndicatorVariants = cva(
|
||||
|
||||
type MeterTone = NonNullable<VariantProps<typeof meterIndicatorVariants>['tone']>
|
||||
|
||||
type MeterIndicatorProps = Omit<BaseMeter.Indicator.Props, 'className'> & {
|
||||
className?: string
|
||||
type MeterIndicatorProps = BaseMeter.Indicator.Props & {
|
||||
tone?: MeterTone
|
||||
}
|
||||
|
||||
function MeterIndicator({ className, tone, ...props }: MeterIndicatorProps) {
|
||||
return (
|
||||
<BaseMeter.Indicator className={cn(meterIndicatorVariants({ tone }), className)} {...props} />
|
||||
<BaseMeter.Indicator
|
||||
className={(state) =>
|
||||
cn(meterIndicatorVariants({ tone }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const meterValueClassName = 'system-xs-regular text-text-tertiary tabular-nums'
|
||||
type MeterValueProps = Omit<BaseMeter.Value.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type MeterValueProps = BaseMeter.Value.Props
|
||||
|
||||
function MeterValue({ className, ...props }: MeterValueProps) {
|
||||
return <BaseMeter.Value className={cn(meterValueClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseMeter.Value
|
||||
className={(state) => cn(meterValueClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const meterLabelClassName = 'system-xs-medium text-text-tertiary'
|
||||
type MeterLabelProps = Omit<BaseMeter.Label.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type MeterLabelProps = BaseMeter.Label.Props
|
||||
|
||||
function MeterLabel({ className, ...props }: MeterLabelProps) {
|
||||
return <BaseMeter.Label className={cn(meterLabelClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseMeter.Label
|
||||
className={(state) => cn(meterLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue }
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NumberField as BaseNumberField } from '@base-ui/react/number-field'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { textControlCompoundInputFocusClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const NumberField = BaseNumberField.Root
|
||||
type NumberFieldProps = BaseNumberField.Root.Props
|
||||
@@ -36,15 +37,15 @@ const numberFieldGroupVariants = cva(
|
||||
)
|
||||
type NumberFieldSize = NonNullable<VariantProps<typeof numberFieldGroupVariants>['size']>
|
||||
|
||||
type NumberFieldGroupProps = Omit<BaseNumberField.Group.Props, 'className'> &
|
||||
VariantProps<typeof numberFieldGroupVariants> & {
|
||||
className?: string
|
||||
}
|
||||
type NumberFieldGroupProps = BaseNumberField.Group.Props &
|
||||
VariantProps<typeof numberFieldGroupVariants>
|
||||
|
||||
function NumberFieldGroup({ className, size = 'medium', ...props }: NumberFieldGroupProps) {
|
||||
return (
|
||||
<BaseNumberField.Group
|
||||
className={cn(numberFieldGroupVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(numberFieldGroupVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -70,15 +71,15 @@ const numberFieldInputVariants = cva(
|
||||
},
|
||||
)
|
||||
|
||||
type NumberFieldInputProps = Omit<BaseNumberField.Input.Props, 'className' | 'size'> &
|
||||
VariantProps<typeof numberFieldInputVariants> & {
|
||||
className?: string
|
||||
}
|
||||
type NumberFieldInputProps = Omit<BaseNumberField.Input.Props, 'size'> &
|
||||
VariantProps<typeof numberFieldInputVariants>
|
||||
|
||||
function NumberFieldInput({ className, size = 'medium', ...props }: NumberFieldInputProps) {
|
||||
return (
|
||||
<BaseNumberField.Input
|
||||
className={cn(numberFieldInputVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(numberFieldInputVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -171,14 +172,8 @@ type NumberFieldButtonVariantProps = Omit<
|
||||
'direction'
|
||||
>
|
||||
|
||||
type NumberFieldIncrementProps = Omit<BaseNumberField.Increment.Props, 'className'> &
|
||||
NumberFieldButtonVariantProps & {
|
||||
className?: string
|
||||
}
|
||||
type NumberFieldDecrementProps = Omit<BaseNumberField.Decrement.Props, 'className'> &
|
||||
NumberFieldButtonVariantProps & {
|
||||
className?: string
|
||||
}
|
||||
type NumberFieldIncrementProps = BaseNumberField.Increment.Props & NumberFieldButtonVariantProps
|
||||
type NumberFieldDecrementProps = BaseNumberField.Decrement.Props & NumberFieldButtonVariantProps
|
||||
|
||||
const incrementAriaLabel = 'Increment value'
|
||||
const decrementAriaLabel = 'Decrement value'
|
||||
@@ -195,7 +190,12 @@ function NumberFieldIncrement({
|
||||
aria-label={
|
||||
props['aria-label'] ?? (props['aria-labelledby'] ? undefined : incrementAriaLabel)
|
||||
}
|
||||
className={cn(numberFieldControlButtonVariants({ size, direction: 'increment' }), className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
numberFieldControlButtonVariants({ size, direction: 'increment' }),
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
>
|
||||
{children ?? <span aria-hidden="true" className="i-ri-arrow-up-s-line size-3" />}
|
||||
</BaseNumberField.Increment>
|
||||
@@ -214,7 +214,12 @@ function NumberFieldDecrement({
|
||||
aria-label={
|
||||
props['aria-label'] ?? (props['aria-labelledby'] ? undefined : decrementAriaLabel)
|
||||
}
|
||||
className={cn(numberFieldControlButtonVariants({ size, direction: 'decrement' }), className)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
numberFieldControlButtonVariants({ size, direction: 'decrement' }),
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
>
|
||||
{children ?? <span aria-hidden="true" className="i-ri-arrow-down-s-line size-3" />}
|
||||
</BaseNumberField.Decrement>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { mergeProps } from '@base-ui/react/merge-props'
|
||||
import { useRender } from '@base-ui/react/use-render'
|
||||
import * as React from 'react'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { useIsoLayoutEffect } from '../internals/use-iso-layout-effect'
|
||||
import { NumberField, NumberFieldGroup, NumberFieldInput } from '../number-field'
|
||||
import { SegmentedControl, SegmentedControlItem } from '../segmented-control'
|
||||
@@ -208,14 +209,12 @@ function PaginationNavigation({ render, className, ...props }: PaginationNavigat
|
||||
})
|
||||
}
|
||||
|
||||
type PaginationPreviousProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
type PaginationPreviousProps = Omit<BaseButtonNS.Props, 'children'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
type PaginationNextProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
type PaginationNextProps = Omit<BaseButtonNS.Props, 'children'> & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const paginationArrowButtonClassName = [
|
||||
@@ -243,7 +242,7 @@ function PaginationPrevious({
|
||||
{...props}
|
||||
type="button"
|
||||
aria-label={ariaLabel ?? 'Previous page'}
|
||||
className={cn(paginationArrowButtonClassName, className)}
|
||||
className={(state) => cn(paginationArrowButtonClassName, resolveClassName(className, state))}
|
||||
disabled={disabled}
|
||||
onClick={(event) => {
|
||||
props.onClick?.(event)
|
||||
@@ -273,7 +272,7 @@ function PaginationNext({
|
||||
{...props}
|
||||
type="button"
|
||||
aria-label={ariaLabel ?? 'Next page'}
|
||||
className={cn(paginationArrowButtonClassName, className)}
|
||||
className={(state) => cn(paginationArrowButtonClassName, resolveClassName(className, state))}
|
||||
disabled={disabled}
|
||||
onClick={(event) => {
|
||||
props.onClick?.(event)
|
||||
@@ -286,10 +285,9 @@ function PaginationNext({
|
||||
)
|
||||
}
|
||||
|
||||
type PaginationPageJumpProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
type PaginationPageJumpProps = Omit<BaseButtonNS.Props, 'children'> & {
|
||||
inputLabel?: string
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function PaginationPageJump({
|
||||
@@ -378,10 +376,12 @@ function PaginationPageJump({
|
||||
aria-label={
|
||||
ariaLabel ?? `Edit page number, current page ${pagination.page} of ${pagination.totalPages}`
|
||||
}
|
||||
className={cn(
|
||||
'inline-flex h-7 touch-manipulation items-center justify-center gap-0.5 rounded-lg px-2 py-1.5 system-xs-medium text-text-secondary tabular-nums outline-hidden transition-colors hover:cursor-text hover:bg-state-base-hover-alt focus-visible:ring-2 focus-visible:ring-state-accent-solid motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'inline-flex h-7 touch-manipulation items-center justify-center gap-0.5 rounded-lg px-2 py-1.5 system-xs-medium text-text-secondary tabular-nums outline-hidden transition-colors hover:cursor-text hover:bg-state-base-hover-alt focus-visible:ring-2 focus-visible:ring-state-accent-solid motion-reduce:transition-none',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
onClick={(event) => {
|
||||
props.onClick?.(event)
|
||||
|
||||
@@ -424,10 +424,9 @@ function PaginationPageList({ render, className, ...props }: PaginationPageListP
|
||||
})
|
||||
}
|
||||
|
||||
type PaginationPageProps = Omit<BaseButtonNS.Props, 'children' | 'className'> & {
|
||||
type PaginationPageProps = Omit<BaseButtonNS.Props, 'children'> & {
|
||||
page: number
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function PaginationPage({
|
||||
@@ -446,12 +445,14 @@ function PaginationPage({
|
||||
type="button"
|
||||
aria-current={current ? 'page' : undefined}
|
||||
aria-label={ariaLabel ?? (current ? `Page ${page}, current page` : `Go to page ${page}`)}
|
||||
className={cn(
|
||||
'inline-flex h-8 min-w-8 touch-manipulation items-center justify-center rounded-lg px-1 py-2 system-sm-medium text-text-tertiary tabular-nums outline-hidden hover:bg-components-button-ghost-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
current &&
|
||||
'bg-components-button-tertiary-bg text-components-button-tertiary-text hover:bg-components-button-ghost-bg-hover',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'inline-flex h-8 min-w-8 touch-manipulation items-center justify-center rounded-lg px-1 py-2 system-sm-medium text-text-tertiary tabular-nums outline-hidden hover:bg-components-button-ghost-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
current &&
|
||||
'bg-components-button-tertiary-bg text-components-button-tertiary-text hover:bg-components-button-ghost-bg-hover',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
onClick={(event) => {
|
||||
props.onClick?.(event)
|
||||
|
||||
|
||||
@@ -84,7 +84,11 @@ describe('PopoverContent', () => {
|
||||
const screen = await renderWithSafeViewport(
|
||||
<Popover open>
|
||||
<PopoverTrigger>Open</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverContent
|
||||
// Verify both Dify wrapper layers resolve the popup callback before merging classes.
|
||||
className={(state) => (state.open ? 'w-48' : undefined)}
|
||||
render={<section className="p-2" />}
|
||||
>
|
||||
<PopoverTitle>Default popover</PopoverTitle>
|
||||
<span>Default content</span>
|
||||
</PopoverContent>
|
||||
@@ -98,6 +102,8 @@ describe('PopoverContent', () => {
|
||||
expect(popupStyle.borderTopLeftRadius).not.toBe('0px')
|
||||
expect(popupStyle.backgroundColor).not.toBe('rgba(0, 0, 0, 0)')
|
||||
expect(popupStyle.boxShadow).not.toBe('none')
|
||||
expect(popupStyle.width).toBe('192px')
|
||||
expect(popupStyle.paddingTop).toBe('8px')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as React from 'react'
|
||||
import type { Placement } from '../placement'
|
||||
import { Popover as BasePopover } from '@base-ui/react/popover'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { floatingPopupAnimationClassName } from '../overlay-shared'
|
||||
import { parsePlacement } from '../placement'
|
||||
|
||||
@@ -25,16 +26,18 @@ type PopoverCloseProps = BasePopover.Close.Props
|
||||
type PopoverTitleProps = BasePopover.Title.Props
|
||||
type PopoverDescriptionProps = BasePopover.Description.Props
|
||||
|
||||
type PopoverBackdropProps = Omit<BasePopover.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type PopoverBackdropProps = BasePopover.Backdrop.Props
|
||||
|
||||
function PopoverBackdrop({ className, ...props }: PopoverBackdropProps) {
|
||||
return <BasePopover.Backdrop className={cn('fixed inset-0 z-50', className)} {...props} />
|
||||
return (
|
||||
<BasePopover.Backdrop
|
||||
className={(state) => cn('fixed inset-0 z-50', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PopoverPositionerProps = Omit<BasePopover.Positioner.Props, 'className' | 'side' | 'align'> & {
|
||||
className?: string
|
||||
type PopoverPositionerProps = Omit<BasePopover.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -53,33 +56,32 @@ function PopoverPositioner({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PopoverPopupProps = Omit<BasePopover.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type PopoverPopupProps = BasePopover.Popup.Props
|
||||
|
||||
function PopoverPopup({ className, ...props }: PopoverPopupProps) {
|
||||
return (
|
||||
<BasePopover.Popup
|
||||
className={cn(
|
||||
'outline-hidden focus:outline-hidden focus-visible:outline-hidden',
|
||||
floatingPopupAnimationClassName,
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'outline-hidden focus:outline-hidden focus-visible:outline-hidden',
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PopoverContentProps = Omit<PopoverPopupProps, 'children' | 'className'> &
|
||||
type PopoverContentProps = Omit<PopoverPopupProps, 'children'> &
|
||||
Pick<PopoverPositionerProps, 'alignOffset' | 'placement' | 'sideOffset'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
@@ -94,10 +96,12 @@ function PopoverContent({
|
||||
<PopoverPortal>
|
||||
<PopoverPositioner placement={placement} sideOffset={sideOffset} alignOffset={alignOffset}>
|
||||
<PopoverPopup
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as React from 'react'
|
||||
import type { Placement } from '../placement'
|
||||
import { PreviewCard as BasePreviewCard } from '@base-ui/react/preview-card'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { floatingPopupAnimationClassName } from '../overlay-shared'
|
||||
import { parsePlacement } from '../placement'
|
||||
|
||||
@@ -31,11 +32,7 @@ type PreviewCardTriggerProps<Payload = unknown> = BasePreviewCard.Trigger.Props<
|
||||
type PreviewCardPortalProps = BasePreviewCard.Portal.Props
|
||||
type PreviewCardViewportProps = BasePreviewCard.Viewport.Props
|
||||
|
||||
type PreviewCardPositionerProps = Omit<
|
||||
BasePreviewCard.Positioner.Props,
|
||||
'className' | 'side' | 'align'
|
||||
> & {
|
||||
className?: string
|
||||
type PreviewCardPositionerProps = Omit<BasePreviewCard.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -54,26 +51,26 @@ function PreviewCardPositioner({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PreviewCardPopupProps = Omit<BasePreviewCard.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type PreviewCardPopupProps = BasePreviewCard.Popup.Props
|
||||
|
||||
function PreviewCardPopup({ className, ...props }: PreviewCardPopupProps) {
|
||||
return (
|
||||
<BasePreviewCard.Popup className={cn(floatingPopupAnimationClassName, className)} {...props} />
|
||||
<BasePreviewCard.Popup
|
||||
className={(state) => cn(floatingPopupAnimationClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PreviewCardContentProps = Omit<PreviewCardPopupProps, 'children' | 'className'> &
|
||||
type PreviewCardContentProps = Omit<PreviewCardPopupProps, 'children'> &
|
||||
Pick<PreviewCardPositionerProps, 'alignOffset' | 'placement' | 'sideOffset'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function PreviewCardContent({
|
||||
@@ -92,10 +89,12 @@ function PreviewCardContent({
|
||||
alignOffset={alignOffset}
|
||||
>
|
||||
<PreviewCardPopup
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { VariantProps } from 'class-variance-authority'
|
||||
import { Progress as BaseProgress } from '@base-ui/react/progress'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const progressCircleRootVariants = cva('inline-flex shrink-0 items-center justify-center', {
|
||||
variants: {
|
||||
@@ -67,10 +68,9 @@ type ProgressCircleAccessibleNameProps =
|
||||
|
||||
type ProgressCircleProps = Omit<
|
||||
BaseProgress.Root.Props,
|
||||
'children' | 'className' | 'aria-label' | 'aria-labelledby'
|
||||
'children' | 'aria-label' | 'aria-labelledby'
|
||||
> &
|
||||
ProgressCircleAccessibleNameProps & {
|
||||
className?: string
|
||||
color?: ProgressCircleColor
|
||||
size?: ProgressCircleSize
|
||||
circleStrokeWidth?: number
|
||||
@@ -130,7 +130,9 @@ function ProgressCircle({
|
||||
|
||||
return (
|
||||
<BaseProgress.Root
|
||||
className={cn(progressCircleRootVariants({ size }), className)}
|
||||
className={(state) =>
|
||||
cn(progressCircleRootVariants({ size }), resolveClassName(className, state))
|
||||
}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
|
||||
@@ -6,47 +6,46 @@ import type * as React from 'react'
|
||||
import { Radio as BaseRadio } from '@base-ui/react/radio'
|
||||
import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type RadioGroupProps<Value = string> = Omit<BaseRadioGroupNS.Props<Value>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type RadioGroupProps<Value = string> = BaseRadioGroupNS.Props<Value>
|
||||
|
||||
function RadioGroup<Value = string>({ className, ...props }: RadioGroupProps<Value>) {
|
||||
return <BaseRadioGroup<Value> className={cn('flex items-center gap-2', className)} {...props} />
|
||||
return (
|
||||
<BaseRadioGroup<Value>
|
||||
className={(state) => cn('flex items-center gap-2', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type RadioItemProps<Value = string> = Omit<BaseRadioNS.Root.Props<Value>, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type RadioItemProps<Value = string> = BaseRadioNS.Root.Props<Value>
|
||||
|
||||
function RadioItem<Value = string>({ className, ...props }: RadioItemProps<Value>) {
|
||||
return <BaseRadio.Root<Value> className={className} {...props} />
|
||||
}
|
||||
|
||||
type RadioControlProps = Omit<
|
||||
BaseRadioNS.Indicator.Props,
|
||||
'className' | 'children' | 'keepMounted'
|
||||
> & {
|
||||
className?: string
|
||||
}
|
||||
type RadioControlProps = Omit<BaseRadioNS.Indicator.Props, 'children' | 'keepMounted'>
|
||||
|
||||
function RadioControl({ className, ...props }: RadioControlProps) {
|
||||
return (
|
||||
<BaseRadio.Indicator
|
||||
{...props}
|
||||
keepMounted
|
||||
className={cn(
|
||||
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-full p-0 transition-colors motion-reduce:transition-none',
|
||||
'border border-components-radio-border bg-components-radio-bg shadow-xs shadow-shadow-shadow-3',
|
||||
'hover:border-components-radio-border-hover hover:bg-components-radio-bg-hover',
|
||||
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0 focus-visible:outline-hidden',
|
||||
'data-checked:border-[5px] data-checked:border-components-radio-border-checked data-checked:hover:border-components-radio-border-checked-hover',
|
||||
'data-disabled:cursor-not-allowed data-disabled:border-components-radio-border-disabled data-disabled:bg-components-radio-bg-disabled',
|
||||
'data-disabled:hover:border-components-radio-border-disabled data-disabled:hover:bg-components-radio-bg-disabled',
|
||||
'data-disabled:data-checked:border-[5px] data-disabled:data-checked:border-components-radio-border-checked-disabled',
|
||||
'data-disabled:data-checked:hover:border-components-radio-border-checked-disabled',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-full p-0 transition-colors motion-reduce:transition-none',
|
||||
'border border-components-radio-border bg-components-radio-bg shadow-xs shadow-shadow-shadow-3',
|
||||
'hover:border-components-radio-border-hover hover:bg-components-radio-bg-hover',
|
||||
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-offset-0 focus-visible:outline-hidden',
|
||||
'data-checked:border-[5px] data-checked:border-components-radio-border-checked data-checked:hover:border-components-radio-border-checked-hover',
|
||||
'data-disabled:cursor-not-allowed data-disabled:border-components-radio-border-disabled data-disabled:bg-components-radio-bg-disabled',
|
||||
'data-disabled:hover:border-components-radio-border-disabled data-disabled:hover:bg-components-radio-bg-disabled',
|
||||
'data-disabled:data-checked:border-[5px] data-disabled:data-checked:border-components-radio-border-checked-disabled',
|
||||
'data-disabled:data-checked:hover:border-components-radio-border-checked-disabled',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
|
||||
import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type ScrollAreaProps = Omit<BaseScrollArea.Root.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ScrollAreaProps = BaseScrollArea.Root.Props
|
||||
|
||||
function ScrollArea({ className, ...props }: ScrollAreaProps) {
|
||||
return (
|
||||
<BaseScrollArea.Root {...props} data-dify-scroll-area="" className={cn('isolate', className)} />
|
||||
<BaseScrollArea.Root
|
||||
{...props}
|
||||
data-dify-scroll-area=""
|
||||
className={(state) => cn('isolate', resolveClassName(className, state))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,48 +36,52 @@ const scrollAreaThumbClassName = cn(
|
||||
'active:bg-state-base-handle-hover',
|
||||
)
|
||||
|
||||
type ScrollAreaViewportProps = Omit<BaseScrollArea.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ScrollAreaViewportProps = BaseScrollArea.Viewport.Props
|
||||
|
||||
function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) {
|
||||
return (
|
||||
<BaseScrollArea.Viewport
|
||||
{...props}
|
||||
data-dify-scroll-area-viewport=""
|
||||
className={cn('isolate size-full rounded-[inherit] outline-none', className)}
|
||||
className={(state) =>
|
||||
cn('isolate size-full rounded-[inherit] outline-none', resolveClassName(className, state))
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ScrollAreaScrollbarProps = Omit<BaseScrollArea.Scrollbar.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ScrollAreaScrollbarProps = BaseScrollArea.Scrollbar.Props
|
||||
|
||||
function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) {
|
||||
return (
|
||||
<BaseScrollArea.Scrollbar
|
||||
{...props}
|
||||
data-dify-scroll-area-scrollbar=""
|
||||
className={cn(scrollAreaScrollbarClassName, className)}
|
||||
className={(state) => cn(scrollAreaScrollbarClassName, resolveClassName(className, state))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ScrollAreaThumbProps = Omit<BaseScrollArea.Thumb.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ScrollAreaThumbProps = BaseScrollArea.Thumb.Props
|
||||
|
||||
function ScrollAreaThumb({ className, ...props }: ScrollAreaThumbProps) {
|
||||
return <BaseScrollArea.Thumb className={cn(scrollAreaThumbClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseScrollArea.Thumb
|
||||
className={(state) => cn(scrollAreaThumbClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ScrollAreaCornerProps = Omit<BaseScrollArea.Corner.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type ScrollAreaCornerProps = BaseScrollArea.Corner.Props
|
||||
|
||||
function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) {
|
||||
return <BaseScrollArea.Corner className={cn('bg-transparent', className)} {...props} />
|
||||
return (
|
||||
<BaseScrollArea.Corner
|
||||
className={(state) => cn('bg-transparent', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type * as React from 'react'
|
||||
import { Radio as BaseRadio } from '@base-ui/react/radio'
|
||||
import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type SegmentedControlSelectionProps<Value> =
|
||||
| {
|
||||
@@ -19,30 +20,25 @@ type SegmentedControlSelectionProps<Value> =
|
||||
|
||||
type SegmentedControlProps<Value = string> = Omit<
|
||||
BaseRadioGroupNS.Props<Value>,
|
||||
'className' | 'defaultValue' | 'value'
|
||||
'defaultValue' | 'value'
|
||||
> &
|
||||
SegmentedControlSelectionProps<Value> & {
|
||||
className?: string
|
||||
}
|
||||
SegmentedControlSelectionProps<Value>
|
||||
|
||||
function SegmentedControl<Value = string>({ className, ...props }: SegmentedControlProps<Value>) {
|
||||
return (
|
||||
<BaseRadioGroup<Value>
|
||||
className={cn(
|
||||
'inline-flex items-center gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'inline-flex items-center gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SegmentedControlItemProps<Value = string> = Omit<
|
||||
BaseRadioNS.Root.Props<Value>,
|
||||
'className'
|
||||
> & {
|
||||
className?: string
|
||||
}
|
||||
type SegmentedControlItemProps<Value = string> = BaseRadioNS.Root.Props<Value>
|
||||
|
||||
function SegmentedControlItem<Value = string>({
|
||||
className,
|
||||
@@ -54,10 +50,12 @@ function SegmentedControlItem<Value = string>({
|
||||
<BaseRadio.Root<Value>
|
||||
nativeButton={nativeButton}
|
||||
render={render}
|
||||
className={cn(
|
||||
'relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium whitespace-nowrap text-text-secondary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-checked:border-components-segmented-control-item-active-border data-checked:bg-components-segmented-control-item-active-bg data-checked:text-text-accent-light-mode-only data-checked:shadow-xs data-checked:shadow-shadow-shadow-3 data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium whitespace-nowrap text-text-secondary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-checked:border-components-segmented-control-item-active-border data-checked:bg-components-segmented-control-item-active-bg data-checked:text-text-accent-light-mode-only data-checked:shadow-xs data-checked:shadow-shadow-shadow-3 data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Select as BaseSelect } from '@base-ui/react/select'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { formLabelClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import {
|
||||
floatingGroupLabelClassName,
|
||||
floatingItemIndicatorClassName,
|
||||
@@ -85,14 +86,18 @@ const selectTriggerVariants = cva(
|
||||
)
|
||||
|
||||
type SelectSize = NonNullable<VariantProps<typeof selectTriggerVariants>['size']>
|
||||
type SelectTriggerProps = Omit<BaseSelect.Trigger.Props, 'className'> & {
|
||||
className?: string
|
||||
type SelectTriggerProps = BaseSelect.Trigger.Props & {
|
||||
size?: SelectSize
|
||||
}
|
||||
|
||||
function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) {
|
||||
return (
|
||||
<BaseSelect.Trigger className={cn(selectTriggerVariants({ size, className }))} {...props}>
|
||||
<BaseSelect.Trigger
|
||||
className={(state) =>
|
||||
cn(selectTriggerVariants({ size, className: resolveClassName(className, state) }))
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<span className="min-w-0 grow truncate">{children}</span>
|
||||
<BaseSelect.Icon className="shrink-0 text-text-quaternary transition-colors group-data-readonly/select-trigger:hidden group-[:hover:not([data-disabled])]/select-trigger:text-text-secondary data-popup-open:text-text-secondary">
|
||||
<span className="i-ri-arrow-down-s-line h-4 w-4" aria-hidden="true" />
|
||||
@@ -101,31 +106,43 @@ function SelectTrigger({ className, children, size, ...props }: SelectTriggerPro
|
||||
)
|
||||
}
|
||||
|
||||
type SelectLabelProps = Omit<BaseSelect.Label.Props, 'className'> & { className?: string }
|
||||
type SelectLabelProps = BaseSelect.Label.Props
|
||||
|
||||
function SelectLabel({ className, ...props }: SelectLabelProps) {
|
||||
return <BaseSelect.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseSelect.Label
|
||||
className={(state) => cn(formLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectGroupLabelProps = Omit<BaseSelect.GroupLabel.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type SelectGroupLabelProps = BaseSelect.GroupLabel.Props
|
||||
|
||||
function SelectGroupLabel({ className, ...props }: SelectGroupLabelProps) {
|
||||
return <BaseSelect.GroupLabel className={cn(floatingGroupLabelClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseSelect.GroupLabel
|
||||
className={(state) => cn(floatingGroupLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectSeparatorProps = Omit<BaseSelect.Separator.Props, 'className'> & { className?: string }
|
||||
type SelectSeparatorProps = BaseSelect.Separator.Props
|
||||
|
||||
function SelectSeparator({ className, ...props }: SelectSeparatorProps) {
|
||||
return <BaseSelect.Separator className={cn(floatingSeparatorClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseSelect.Separator
|
||||
className={(state) => cn(floatingSeparatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const SelectPortal = BaseSelect.Portal
|
||||
type SelectPortalProps = BaseSelect.Portal.Props
|
||||
|
||||
type SelectPositionerProps = Omit<BaseSelect.Positioner.Props, 'className' | 'side' | 'align'> & {
|
||||
className?: string
|
||||
type SelectPositionerProps = Omit<BaseSelect.Positioner.Props, 'side' | 'align'> & {
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
@@ -146,46 +163,45 @@ function SelectPositioner({
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
alignItemWithTrigger={alignItemWithTrigger}
|
||||
className={cn('z-50 outline-hidden', className)}
|
||||
className={(state) => cn('z-50 outline-hidden', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectPopupProps = Omit<BaseSelect.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type SelectPopupProps = BaseSelect.Popup.Props
|
||||
|
||||
function SelectPopup({ className, ...props }: SelectPopupProps) {
|
||||
return (
|
||||
<BaseSelect.Popup
|
||||
className={cn(
|
||||
'max-w-(--available-width) min-w-[min(var(--anchor-width),var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
floatingPopupAnimationClassName,
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'max-w-(--available-width) min-w-[min(var(--anchor-width),var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
floatingPopupAnimationClassName,
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectListProps = Omit<BaseSelect.List.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type SelectListProps = BaseSelect.List.Props
|
||||
|
||||
function SelectList({ className, ...props }: SelectListProps) {
|
||||
return (
|
||||
<BaseSelect.List
|
||||
className={cn('max-h-80 overflow-auto p-1 outline-hidden', className)}
|
||||
className={(state) =>
|
||||
cn('max-h-80 overflow-auto p-1 outline-hidden', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectContentProps = Omit<SelectPopupProps, 'children' | 'className'> &
|
||||
type SelectContentProps = Omit<SelectPopupProps, 'children'> &
|
||||
Pick<SelectPositionerProps, 'alignOffset' | 'placement' | 'sideOffset'> & {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
@@ -207,39 +223,46 @@ function SelectContent({
|
||||
)
|
||||
}
|
||||
|
||||
type SelectItemProps<Value = unknown> = Omit<BaseSelect.Item.Props, 'className' | 'value'> & {
|
||||
className?: string
|
||||
type SelectItemProps<Value = unknown> = Omit<BaseSelect.Item.Props, 'value'> & {
|
||||
value?: Value
|
||||
}
|
||||
|
||||
function SelectItem<Value = unknown>({ className, ...props }: SelectItemProps<Value>) {
|
||||
return (
|
||||
<BaseSelect.Item
|
||||
className={cn(
|
||||
'flex h-8 cursor-pointer items-center rounded-lg px-2 system-sm-medium text-text-secondary outline-hidden',
|
||||
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:bg-state-base-hover',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'flex h-8 cursor-pointer items-center rounded-lg px-2 system-sm-medium text-text-secondary outline-hidden',
|
||||
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:bg-state-base-hover',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectItemTextProps = Omit<BaseSelect.ItemText.Props, 'className'> & { className?: string }
|
||||
type SelectItemTextProps = BaseSelect.ItemText.Props
|
||||
|
||||
function SelectItemText({ className, ...props }: SelectItemTextProps) {
|
||||
return (
|
||||
<BaseSelect.ItemText className={cn('me-1 min-w-0 grow truncate px-1', className)} {...props} />
|
||||
<BaseSelect.ItemText
|
||||
className={(state) =>
|
||||
cn('me-1 min-w-0 grow truncate px-1', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectItemIndicatorProps = Omit<BaseSelect.ItemIndicator.Props, 'children' | 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type SelectItemIndicatorProps = Omit<BaseSelect.ItemIndicator.Props, 'children'>
|
||||
|
||||
function SelectItemIndicator({ className, ...props }: SelectItemIndicatorProps) {
|
||||
return (
|
||||
<BaseSelect.ItemIndicator className={cn(floatingItemIndicatorClassName, className)} {...props}>
|
||||
<BaseSelect.ItemIndicator
|
||||
className={(state) => cn(floatingItemIndicatorClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
>
|
||||
<span className="i-ri-check-line h-4 w-4" aria-hidden />
|
||||
</BaseSelect.ItemIndicator>
|
||||
)
|
||||
|
||||
@@ -18,8 +18,7 @@ does not implement the keyboard interaction or value semantics of a resizable se
|
||||
Keep headings and labels beside the separator, since descendants of `role="separator"` have
|
||||
presentational semantics. Use the owning menu family's separator inside menus.
|
||||
|
||||
Base UI owns `orientation`, `render`, refs, and callback-valued `style`; render callbacks receive
|
||||
its orientation state. `className` accepts a string for Dify's class merging. Custom render
|
||||
Follow the shared [styling contract] for state callbacks. Custom render
|
||||
components must forward the received props and ref. Explicit DOM props and render overrides can
|
||||
change the default semantics; callers are responsible for keeping those overrides consistent
|
||||
with the intended role and `decorative` setting.
|
||||
@@ -31,3 +30,4 @@ Use `variant` for the gradient rather than repeating its styles. To customize co
|
||||
background token for solid lines or a gradient-start token for gradient lines.
|
||||
|
||||
[Base UI Separator]: https://base-ui.com/react/components/separator
|
||||
[styling contract]: ../../docs/styling.md
|
||||
|
||||
@@ -10,8 +10,9 @@ export function separatorTypeContracts(ref: React.Ref<HTMLDivElement>) {
|
||||
style={(state) => ({ height: state.orientation === 'vertical' ? 24 : 1 })}
|
||||
/>
|
||||
)
|
||||
// @ts-expect-error Dify merges string class names, not state callbacks.
|
||||
const className = <Separator className={() => 'custom'} />
|
||||
const className = (
|
||||
<Separator className={(state) => (state.orientation === 'vertical' ? 'custom' : undefined)} />
|
||||
)
|
||||
// @ts-expect-error Orientation is inherited from Base UI.
|
||||
const orientation = <Separator orientation="diagonal" />
|
||||
return { decorative, semantic, render, className, orientation }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Separator as BaseSeparator } from '@base-ui/react/separator'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const separatorVariants = cva('shrink-0', {
|
||||
variants: {
|
||||
@@ -17,8 +18,7 @@ const separatorVariants = cva('shrink-0', {
|
||||
},
|
||||
})
|
||||
|
||||
type SeparatorProps = Omit<BaseSeparator.Props, 'className'> & {
|
||||
className?: string
|
||||
type SeparatorProps = BaseSeparator.Props & {
|
||||
/** Set true for visual lines that do not separate content groups. */
|
||||
decorative?: boolean
|
||||
variant?: 'solid' | 'gradient'
|
||||
@@ -36,7 +36,9 @@ function Separator({
|
||||
orientation={orientation}
|
||||
role={decorative ? 'none' : 'separator'}
|
||||
aria-orientation={decorative ? undefined : orientation}
|
||||
className={cn(separatorVariants({ orientation, variant }), className)}
|
||||
className={(state) =>
|
||||
cn(separatorVariants({ orientation, variant }), resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
import { Slider as BaseSlider } from '@base-ui/react/slider'
|
||||
import { cn } from '../cn'
|
||||
import { formLabelClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type SliderValue = number | readonly number[]
|
||||
type SliderProps<Value extends SliderValue = SliderValue> = Omit<
|
||||
BaseSlider.Root.Props<Value>,
|
||||
'className'
|
||||
> & { className?: string }
|
||||
type SliderProps<Value extends SliderValue = SliderValue> = BaseSlider.Root.Props<Value>
|
||||
|
||||
function Slider<Value extends SliderValue = SliderValue>({
|
||||
className,
|
||||
@@ -16,83 +14,96 @@ function Slider<Value extends SliderValue = SliderValue>({
|
||||
}: SliderProps<Value>) {
|
||||
return (
|
||||
<BaseSlider.Root
|
||||
className={cn(
|
||||
'group/slider relative inline-flex w-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
|
||||
'data-disabled:opacity-30',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'group/slider relative inline-flex w-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
|
||||
'data-disabled:opacity-30',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderLabelProps = Omit<BaseSlider.Label.Props, 'className'> & { className?: string }
|
||||
type SliderLabelProps = BaseSlider.Label.Props
|
||||
|
||||
function SliderLabel({ className, ...props }: SliderLabelProps) {
|
||||
return <BaseSlider.Label className={cn(formLabelClassName, className)} {...props} />
|
||||
return (
|
||||
<BaseSlider.Label
|
||||
className={(state) => cn(formLabelClassName, resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderControlProps = Omit<BaseSlider.Control.Props, 'className'> & { className?: string }
|
||||
type SliderControlProps = BaseSlider.Control.Props
|
||||
|
||||
function SliderControl({ className, ...props }: SliderControlProps) {
|
||||
return (
|
||||
<BaseSlider.Control
|
||||
className={cn(
|
||||
'relative flex h-5 w-full touch-none items-center select-none',
|
||||
'data-[orientation=vertical]:h-32 data-[orientation=vertical]:w-5 data-[orientation=vertical]:justify-center',
|
||||
'data-disabled:cursor-not-allowed',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative flex h-5 w-full touch-none items-center select-none',
|
||||
'data-[orientation=vertical]:h-32 data-[orientation=vertical]:w-5 data-[orientation=vertical]:justify-center',
|
||||
'data-disabled:cursor-not-allowed',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderTrackProps = Omit<BaseSlider.Track.Props, 'className'> & { className?: string }
|
||||
type SliderTrackProps = BaseSlider.Track.Props
|
||||
|
||||
function SliderTrack({ className, ...props }: SliderTrackProps) {
|
||||
return (
|
||||
<BaseSlider.Track
|
||||
className={cn(
|
||||
'relative h-1 w-full rounded-full',
|
||||
'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1',
|
||||
'bg-components-slider-track',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative h-1 w-full rounded-full',
|
||||
'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1',
|
||||
'bg-components-slider-track',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderIndicatorProps = Omit<BaseSlider.Indicator.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type SliderIndicatorProps = BaseSlider.Indicator.Props
|
||||
|
||||
function SliderIndicator({ className, ...props }: SliderIndicatorProps) {
|
||||
return (
|
||||
<BaseSlider.Indicator
|
||||
className={cn('h-full rounded-full', 'bg-components-slider-range', className)}
|
||||
className={(state) =>
|
||||
cn('h-full rounded-full', 'bg-components-slider-range', resolveClassName(className, state))
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderThumbProps = Omit<BaseSlider.Thumb.Props, 'className'> & { className?: string }
|
||||
type SliderThumbProps = BaseSlider.Thumb.Props
|
||||
|
||||
function SliderThumb({ className, ...props }: SliderThumbProps) {
|
||||
return (
|
||||
<BaseSlider.Thumb
|
||||
className={cn(
|
||||
'block h-5 w-2 shrink-0 rounded-[3px] border-[0.5px]',
|
||||
'data-[orientation=vertical]:h-2 data-[orientation=vertical]:w-5',
|
||||
'border-components-slider-knob-border bg-components-slider-knob shadow-sm',
|
||||
'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none',
|
||||
'hover:bg-components-slider-knob-hover',
|
||||
'has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid has-focus-visible:ring-offset-0',
|
||||
'active:shadow-md group-data-dragging/slider:has-focus:shadow-md',
|
||||
'data-disabled:border-components-slider-knob-border data-disabled:bg-components-slider-knob-disabled data-disabled:shadow-none',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'block h-5 w-2 shrink-0 rounded-[3px] border-[0.5px]',
|
||||
'data-[orientation=vertical]:h-2 data-[orientation=vertical]:w-5',
|
||||
'border-components-slider-knob-border bg-components-slider-knob shadow-sm',
|
||||
'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none',
|
||||
'hover:bg-components-slider-knob-hover',
|
||||
'has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid has-focus-visible:ring-offset-0',
|
||||
'active:shadow-md group-data-dragging/slider:has-focus:shadow-md',
|
||||
'data-disabled:border-components-slider-knob-border data-disabled:bg-components-slider-knob-disabled data-disabled:shadow-none',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import type * as React from 'react'
|
||||
import { Switch as BaseSwitch } from '@base-ui/react/switch'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const switchRootStateClassName =
|
||||
'bg-components-toggle-bg-unchecked hover:bg-components-toggle-bg-unchecked-hover data-checked:bg-components-toggle-bg data-checked:hover:bg-components-toggle-bg-hover data-disabled:cursor-not-allowed data-disabled:bg-components-toggle-bg-unchecked-disabled data-disabled:hover:bg-components-toggle-bg-unchecked-disabled data-disabled:data-checked:bg-components-toggle-bg-disabled data-disabled:data-checked:hover:bg-components-toggle-bg-disabled'
|
||||
@@ -67,13 +68,12 @@ type SwitchControlProps = ControlledSwitchProps | UncontrolledSwitchProps
|
||||
|
||||
type SwitchProps = Omit<
|
||||
BaseSwitchNS.Root.Props,
|
||||
'checked' | 'defaultChecked' | 'className' | 'size' | 'onCheckedChange'
|
||||
'checked' | 'defaultChecked' | 'size' | 'onCheckedChange'
|
||||
> &
|
||||
VariantProps<typeof switchRootVariants> &
|
||||
SwitchControlProps & {
|
||||
onCheckedChange?: (checked: boolean) => void
|
||||
loading?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Switch({
|
||||
@@ -92,7 +92,7 @@ function Switch({
|
||||
checked={checked}
|
||||
disabled={isDisabled}
|
||||
aria-busy={loading || undefined}
|
||||
className={cn(switchRootVariants({ size }), className)}
|
||||
className={(state) => cn(switchRootVariants({ size }), resolveClassName(className, state))}
|
||||
onCheckedChange={(value) => onCheckedChange?.(value)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -3,45 +3,49 @@
|
||||
import type { Tabs as BaseTabsNS } from '@base-ui/react/tabs'
|
||||
import { Tabs as BaseTabs } from '@base-ui/react/tabs'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
type TabsProps = BaseTabsNS.Root.Props
|
||||
const Tabs = BaseTabs.Root
|
||||
|
||||
type TabsListProps = Omit<BaseTabsNS.List.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
type TabsListProps = BaseTabsNS.List.Props
|
||||
|
||||
function TabsList({ className, ...props }: TabsListProps) {
|
||||
return <BaseTabs.List className={cn('flex gap-4', className)} {...props} />
|
||||
}
|
||||
|
||||
type TabsTabProps = Omit<BaseTabsNS.Tab.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
function TabsTab({ className, ...props }: TabsTabProps) {
|
||||
return (
|
||||
<BaseTabs.Tab
|
||||
className={cn(
|
||||
'relative flex cursor-pointer touch-manipulation items-center border-b-2 border-transparent pt-2.5 pb-2 system-md-semibold text-text-tertiary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-active:border-components-tab-active data-active:text-text-primary data-disabled:cursor-not-allowed data-disabled:text-text-tertiary data-disabled:opacity-30 data-active:data-disabled:text-text-primary',
|
||||
className,
|
||||
)}
|
||||
<BaseTabs.List
|
||||
className={(state) => cn('flex gap-4', resolveClassName(className, state))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type TabsPanelProps = Omit<BaseTabsNS.Panel.Props, 'className'> & {
|
||||
className?: string
|
||||
type TabsTabProps = BaseTabsNS.Tab.Props
|
||||
|
||||
function TabsTab({ className, ...props }: TabsTabProps) {
|
||||
return (
|
||||
<BaseTabs.Tab
|
||||
className={(state) =>
|
||||
cn(
|
||||
'relative flex cursor-pointer touch-manipulation items-center border-b-2 border-transparent pt-2.5 pb-2 system-md-semibold text-text-tertiary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-active:border-components-tab-active data-active:text-text-primary data-disabled:cursor-not-allowed data-disabled:text-text-tertiary data-disabled:opacity-30 data-active:data-disabled:text-text-primary',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type TabsPanelProps = BaseTabsNS.Panel.Props
|
||||
|
||||
function TabsPanel({ className, ...props }: TabsPanelProps) {
|
||||
return (
|
||||
<BaseTabs.Panel
|
||||
className={cn(
|
||||
'outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -37,15 +37,30 @@ describe('Textarea', () => {
|
||||
expect(asHTMLElement(textarea.element()).tagName).toBe('TEXTAREA')
|
||||
})
|
||||
|
||||
it('should apply custom classes', async () => {
|
||||
it('should update callback styles from field state while preserving textarea variants', async () => {
|
||||
const screen = await render(
|
||||
<label>
|
||||
Prompt
|
||||
<Textarea size="large" className="resize-none" />
|
||||
</label>,
|
||||
<Field>
|
||||
<FieldLabel>Prompt</FieldLabel>
|
||||
<Textarea
|
||||
size="large"
|
||||
// Exercise Dify's callback merging across the Field.Control textarea adapter.
|
||||
className={(state) => (state.filled ? 'resize-none' : undefined)}
|
||||
style={(state) => ({ opacity: state.filled ? 0.5 : 1 })}
|
||||
/>
|
||||
</Field>,
|
||||
)
|
||||
const textarea = screen.getByRole('textbox', { name: 'Prompt' })
|
||||
const initialPadding = getComputedStyle(textarea.element()).padding
|
||||
|
||||
await expect.element(screen.getByRole('textbox', { name: 'Prompt' })).toHaveClass('resize-none')
|
||||
await expect.element(textarea).toHaveStyle({ opacity: '1' })
|
||||
await textarea.fill('Draft')
|
||||
await expect.poll(() => getComputedStyle(textarea.element()).resize).toBe('none')
|
||||
await expect.element(textarea).toHaveStyle({ opacity: '0.5' })
|
||||
expect(getComputedStyle(textarea.element()).padding).toBe(initialPadding)
|
||||
|
||||
await textarea.fill('')
|
||||
await expect.element(textarea).toHaveStyle({ opacity: '1' })
|
||||
await expect.poll(() => getComputedStyle(textarea.element()).resize).not.toBe('none')
|
||||
})
|
||||
|
||||
it('should call onValueChange and stay controlled until value changes', async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Field as BaseField } from '@base-ui/react/field'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '../cn'
|
||||
import { textControlFocusClassName } from '../form-control-shared'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
|
||||
const textareaVariants = cva(
|
||||
[
|
||||
@@ -61,6 +62,7 @@ type TextareaElementProps = Omit<
|
||||
| 'onChange'
|
||||
| 'rows'
|
||||
| 'size'
|
||||
| 'style'
|
||||
| 'value'
|
||||
| 'wrap'
|
||||
>
|
||||
@@ -73,11 +75,11 @@ type FieldControlTextareaProps = Omit<
|
||||
>
|
||||
|
||||
type TextareaProps = TextareaElementProps &
|
||||
Pick<BaseFieldNS.Control.Props, 'className' | 'style'> &
|
||||
TextareaOnlyProps &
|
||||
TextareaControlProps &
|
||||
TextareaVariantProps & {
|
||||
children?: never
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Textarea({
|
||||
@@ -98,7 +100,7 @@ function Textarea({
|
||||
return (
|
||||
<BaseField.Control
|
||||
{...fieldControlProps}
|
||||
className={cn(textareaVariants({ size }), className)}
|
||||
className={(state) => cn(textareaVariants({ size }), resolveClassName(className, state))}
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as React from 'react'
|
||||
import type { Placement } from '../placement'
|
||||
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'
|
||||
import { cn } from '../cn'
|
||||
import { resolveClassName } from '../internals/resolve-class-name'
|
||||
import { parsePlacement } from '../placement'
|
||||
|
||||
/**
|
||||
@@ -33,11 +34,10 @@ type TooltipProviderProps = BaseTooltip.Provider.Props
|
||||
type TooltipProps<Payload = unknown> = BaseTooltip.Root.Props<Payload>
|
||||
type TooltipTriggerProps<Payload = unknown> = BaseTooltip.Trigger.Props<Payload>
|
||||
|
||||
type TooltipContentProps = Omit<BaseTooltip.Popup.Props, 'children' | 'className'> &
|
||||
type TooltipContentProps = Omit<BaseTooltip.Popup.Props, 'children'> &
|
||||
Pick<BaseTooltip.Positioner.Props, 'sideOffset' | 'alignOffset'> & {
|
||||
children: React.ReactNode
|
||||
placement?: Placement
|
||||
className?: string
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
@@ -60,11 +60,13 @@ function TooltipContent({
|
||||
className="z-50 outline-hidden"
|
||||
>
|
||||
<BaseTooltip.Popup
|
||||
className={cn(
|
||||
'max-w-75 rounded-md bg-components-panel-bg px-3 py-2 text-start system-xs-regular wrap-break-word text-text-tertiary shadow-lg',
|
||||
'origin-(--transform-origin) transition-opacity data-ending-style:opacity-0 data-instant:transition-none data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
className={(state) =>
|
||||
cn(
|
||||
'max-w-75 rounded-md bg-components-panel-bg px-3 py-2 text-start system-xs-regular wrap-break-word text-text-tertiary shadow-lg',
|
||||
'origin-(--transform-origin) transition-opacity data-ending-style:opacity-0 data-instant:transition-none data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
resolveClassName(className, state),
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user