@stll/ui 0.59.0 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +120 -4
  2. package/dist/calendar/day-calendar.d.ts +14 -4
  3. package/dist/calendar/day-calendar.js +52 -20
  4. package/dist/calendar/day-calendar.logic.d.ts +3 -0
  5. package/dist/calendar/day-calendar.logic.js +4 -1
  6. package/dist/components/accordion.d.ts +17 -3
  7. package/dist/components/accordion.js +17 -3
  8. package/dist/components/button.d.ts +56 -5
  9. package/dist/components/button.js +97 -15
  10. package/dist/components/button.native-classes.d.ts +17 -0
  11. package/dist/components/button.native-classes.js +176 -0
  12. package/dist/components/button.native.d.ts +5 -2
  13. package/dist/components/button.native.js +17 -134
  14. package/dist/components/button.shared.d.ts +24 -2
  15. package/dist/components/button.shared.js +15 -1
  16. package/dist/components/combobox.d.ts +3 -1
  17. package/dist/components/combobox.js +3 -2
  18. package/dist/components/date-picker-popover.d.ts +2 -0
  19. package/dist/components/date-picker-popover.js +5 -3
  20. package/dist/components/details-grid.d.ts +21 -2
  21. package/dist/components/details-grid.js +75 -20
  22. package/dist/components/form.d.ts +9 -3
  23. package/dist/components/form.js +24 -3
  24. package/dist/components/input-control.d.ts +9 -0
  25. package/dist/components/input-control.js +10 -1
  26. package/dist/components/input.d.ts +7 -1
  27. package/dist/components/input.js +4 -4
  28. package/dist/components/label.d.ts +24 -2
  29. package/dist/components/label.js +21 -2
  30. package/dist/components/number-input.d.ts +9 -2
  31. package/dist/components/number-input.js +18 -6
  32. package/dist/components/textarea.d.ts +14 -1
  33. package/dist/components/textarea.js +11 -5
  34. package/dist/index.d.ts +10 -6
  35. package/dist/index.js +6 -3
  36. package/dist/kanban/index.d.ts +5 -2
  37. package/dist/kanban/index.js +4 -1
  38. package/dist/kanban/sortable-edge.d.ts +39 -0
  39. package/dist/kanban/sortable-edge.js +27 -0
  40. package/dist/kanban/sortable-interactions.d.ts +145 -0
  41. package/dist/kanban/sortable-interactions.js +503 -0
  42. package/dist/kanban/sortable-interactions.logic.d.ts +79 -0
  43. package/dist/kanban/sortable-interactions.logic.js +210 -0
  44. package/dist/kanban/subgroup-board.d.ts +27 -2
  45. package/dist/kanban/subgroup-board.js +26 -1
  46. package/dist/kanban/virtual-cell.d.ts +18 -1
  47. package/dist/kanban/virtual-cell.js +70 -17
  48. package/package.json +5 -1
@@ -5,6 +5,7 @@ import { OVERLAY_LAYER_CLASS_NAMES } from "../lib/overlay-layer.js";
5
5
  import { ScrollArea } from "./scroll-area.js";
6
6
  import { containedHandler } from "../hooks/use-contained-handler.js";
7
7
  import { CONTROL_SIZE } from "../lib/control-size.js";
8
+ import "../lib/field-trigger.js";
8
9
  import { Input } from "./input.js";
9
10
  import { jsx, jsxs } from "react/jsx-runtime";
10
11
  import * as React$1 from "react";
@@ -94,8 +95,8 @@ const COMBOBOX_ACTION_SIZE_CLASS_NAMES = {
94
95
  [CONTROL_SIZE.default]: "end-0.5",
95
96
  [CONTROL_SIZE.lg]: "end-0.5"
96
97
  };
97
- const ComboboxTrigger = ({ className, ...props }) => /* @__PURE__ */ jsx(Combobox$1.Trigger, {
98
- className,
98
+ const ComboboxTrigger = ({ className, variant = "plain", ...props }) => /* @__PURE__ */ jsx(Combobox$1.Trigger, {
99
+ className: cn(variant === "field" && "border-input bg-background text-foreground ring-ring/24 focus-visible:border-ring aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 dark:bg-input/32 dark:aria-invalid:ring-destructive/24 relative inline-flex min-h-9 w-full min-w-36 items-center justify-center gap-2 rounded-lg border px-[calc(--spacing(3)-1px)] text-start text-base shadow-xs/5 outline-none select-none not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-[3px] data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none", className),
99
100
  "data-slot": "combobox-trigger",
100
101
  ...props
101
102
  });
@@ -39,6 +39,8 @@ type DatePickerPopoverProps = {
39
39
  * the control still has height; pass a call-to-action ("Select date…") to
40
40
  * make the empty state self-explanatory. */
41
41
  placeholderLabel?: string;
42
+ /** Replaces the formatted date in the trigger, as a week range does. */
43
+ triggerLabel?: string;
42
44
  clearLabel?: string;
43
45
  defaultOpen?: boolean;
44
46
  open?: boolean;
@@ -3,8 +3,8 @@ import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Button } from "./button.js";
5
5
  import { DirectionalIcon } from "./directional-icon.js";
6
- import { Input } from "./input.js";
7
6
  import { FIELD_TRIGGER_CLASS_NAME } from "../lib/field-trigger.js";
7
+ import { Input } from "./input.js";
8
8
  import { getLocaleWeekInfo, getWeekendDays } from "../lib/week.js";
9
9
  import { DATE_PICKER_MODE, formatDateTimeValue, getHourOptions, getMinuteOptions, getTimeFieldNames, joinPickerTime, localDateFromTimestamp, millisecondsUntilNextLocalDate, parseDateTimeValue, resolveCalendarViewMonth, shiftCalendarDate, splitPickerTime } from "./date-picker-popover.logic.js";
10
10
  import { Popover, PopoverContent as PopoverPopup, PopoverTrigger } from "./popover.js";
@@ -252,8 +252,10 @@ const PICKER_SIZE_CLASS_NAMES = {
252
252
  cell: "pointer-coarse:min-h-11 pointer-coarse:text-sm"
253
253
  }
254
254
  };
255
+ /** The trigger keeps its height with an em dash when nothing has a label. */
256
+ const firstLabel = (...labels) => labels.find((label) => label !== null && label !== void 0) ?? "—";
255
257
  const DatePickerPopoverContent = (props) => {
256
- const { id, labelledBy, className, disabled, hideClear, size = "compact", variant = "inline", mode = DATE_PICKER_MODE.date, value: rawValue, onChange, locale, isOverdue = false, showIcon = true, placeholderLabel, clearLabel = "Clear date", defaultOpen = false, open: controlledOpen, triggerRef, focusTriggerOnOpen = false, dateInputLabel, outOfRangeLabel, onOpenChange, todayLabel: todayLabelProp, overdueLabel, minDate, maxDate, isDateDisabled, layer = "default", today } = props;
258
+ const { id, labelledBy, className, disabled, hideClear, size = "compact", variant = "inline", mode = DATE_PICKER_MODE.date, value: rawValue, onChange, locale, isOverdue = false, showIcon = true, placeholderLabel, triggerLabel, clearLabel = "Clear date", defaultOpen = false, open: controlledOpen, triggerRef, focusTriggerOnOpen = false, dateInputLabel, outOfRangeLabel, onOpenChange, todayLabel: todayLabelProp, overdueLabel, minDate, maxDate, isDateDisabled, layer = "default", today } = props;
257
259
  const selection = resolveSelection(mode, rawValue);
258
260
  const value = selection.date;
259
261
  const labels = resolvePopupLabels(props);
@@ -300,7 +302,7 @@ const DatePickerPopoverContent = (props) => {
300
302
  isDayDisabled,
301
303
  onChange
302
304
  });
303
- const displayLabel = formatSelection(locale, selection) ?? placeholderLabel ?? "—";
305
+ const displayLabel = firstLabel(triggerLabel, formatSelection(locale, selection), placeholderLabel);
304
306
  const selectDay = (date) => {
305
307
  switch (selection.mode) {
306
308
  case DATE_PICKER_MODE.date:
@@ -1,10 +1,29 @@
1
1
  import * as React$1 from "react";
2
2
  //#region src/components/details-grid.d.ts
3
- declare const DetailsGrid: ({ className, ...props }: React$1.ComponentProps<"dl">) => React$1.JSX.Element;
3
+ /**
4
+ * - `sheet`: a fact sheet; small uppercase labels, up to three pairs per row
5
+ * as the container widens.
6
+ * - `list`: one pair per row in body type with a muted label, for a short
7
+ * list inside a card or dialog (who is signed in, which organization).
8
+ */
9
+ type DetailsVariant = "sheet" | "list";
10
+ /**
11
+ * - `horizontal`: each label beside its value. In a `list`, every label
12
+ * shares one column sized to the longest.
13
+ * - `vertical`: each label above its value, for values too long to share a
14
+ * row (a server address, a list of origins).
15
+ */
16
+ type DetailsOrientation = "horizontal" | "vertical";
17
+ type DetailsGridProps = React$1.ComponentProps<"dl"> & {
18
+ orientation?: DetailsOrientation;
19
+ variant?: DetailsVariant;
20
+ };
21
+ declare const DetailsGrid: ({ className, orientation, variant, ...props }: DetailsGridProps) => React$1.JSX.Element;
4
22
  type DetailsItemProps = React$1.ComponentProps<"div"> & {
5
23
  label: React$1.ReactNode;
24
+ /** `wide` spans every pair column of a `sheet`. */
6
25
  span?: "wide";
7
26
  };
8
27
  declare const DetailsItem: ({ label, children, span, className, ...props }: DetailsItemProps) => React$1.JSX.Element;
9
28
  //#endregion
10
- export { DetailsGrid, DetailsItem };
29
+ export { DetailsGrid, DetailsItem, type DetailsOrientation, type DetailsVariant };
@@ -1,26 +1,81 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
3
4
  //#region src/components/details-grid.tsx
4
- const DetailsGrid = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
5
- className: "@container min-w-0 [contain-intrinsic-inline-size:20rem]",
6
- "data-slot": "details-grid-container",
7
- children: /* @__PURE__ */ jsx("dl", {
8
- className: cn("grid grid-cols-[repeat(auto-fit,minmax(min(100%,20rem),1fr))] gap-x-6 gap-y-2 font-sans @min-[64rem]:grid-cols-3", className),
9
- "data-slot": "details-grid",
10
- ...props
11
- })
12
- });
13
- const DetailsItem = ({ label, children, span, className, ...props }) => /* @__PURE__ */ jsxs("div", {
14
- className: cn("grid min-w-0 grid-cols-[minmax(0,7rem)_minmax(0,1fr)] items-start gap-x-4 text-xs", span === "wide" && "col-span-full", className),
15
- "data-slot": "details-item",
16
- ...props,
17
- children: [/* @__PURE__ */ jsx("dt", {
18
- className: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase",
19
- children: label
20
- }), /* @__PURE__ */ jsx("dd", {
21
- className: "text-foreground-strong-muted min-w-0 wrap-anywhere",
22
- children
23
- })]
5
+ const DetailsShapeContext = createContext({
6
+ orientation: "horizontal",
7
+ variant: "sheet"
24
8
  });
9
+ const LIST_CLASS_NAME = {
10
+ horizontal: "grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-4 gap-y-1 text-sm",
11
+ vertical: "flex min-w-0 flex-col gap-2 text-sm"
12
+ };
13
+ const ITEM_CLASS_NAME = {
14
+ list: {
15
+ horizontal: "col-span-full grid grid-cols-subgrid items-center",
16
+ vertical: "flex min-w-0 flex-col gap-2"
17
+ },
18
+ sheet: {
19
+ horizontal: "grid min-w-0 grid-cols-[minmax(0,7rem)_minmax(0,1fr)] items-start gap-x-4 text-xs",
20
+ vertical: "flex min-w-0 flex-col gap-1 text-xs"
21
+ }
22
+ };
23
+ const LABEL_CLASS_NAME = {
24
+ list: {
25
+ horizontal: "text-muted-foreground min-w-0",
26
+ vertical: "font-medium"
27
+ },
28
+ sheet: {
29
+ horizontal: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase",
30
+ vertical: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase"
31
+ }
32
+ };
33
+ const VALUE_CLASS_NAME = {
34
+ list: "min-w-0 wrap-anywhere",
35
+ sheet: "text-foreground-strong-muted min-w-0 wrap-anywhere"
36
+ };
37
+ const DetailsGrid = ({ className, orientation = "horizontal", variant = "sheet", ...props }) => {
38
+ const shape = {
39
+ orientation,
40
+ variant
41
+ };
42
+ if (variant === "list") return /* @__PURE__ */ jsx(DetailsShapeContext.Provider, {
43
+ value: shape,
44
+ children: /* @__PURE__ */ jsx("dl", {
45
+ className: cn(LIST_CLASS_NAME[orientation], className),
46
+ "data-orientation": orientation,
47
+ "data-slot": "details-grid",
48
+ "data-variant": variant,
49
+ ...props
50
+ })
51
+ });
52
+ return /* @__PURE__ */ jsx(DetailsShapeContext.Provider, {
53
+ value: shape,
54
+ children: /* @__PURE__ */ jsx("div", {
55
+ className: "@container min-w-0 [contain-intrinsic-inline-size:20rem]",
56
+ "data-slot": "details-grid-container",
57
+ children: /* @__PURE__ */ jsx("dl", {
58
+ className: cn("grid grid-cols-[repeat(auto-fit,minmax(min(100%,20rem),1fr))] gap-x-6 gap-y-2 font-sans @min-[64rem]:grid-cols-3", className),
59
+ "data-slot": "details-grid",
60
+ ...props
61
+ })
62
+ })
63
+ });
64
+ };
65
+ const DetailsItem = ({ label, children, span, className, ...props }) => {
66
+ const { orientation, variant } = useContext(DetailsShapeContext);
67
+ return /* @__PURE__ */ jsxs("div", {
68
+ className: cn(ITEM_CLASS_NAME[variant][orientation], variant === "sheet" && span === "wide" && "col-span-full", className),
69
+ "data-slot": "details-item",
70
+ ...props,
71
+ children: [/* @__PURE__ */ jsx("dt", {
72
+ className: LABEL_CLASS_NAME[variant][orientation],
73
+ children: label
74
+ }), /* @__PURE__ */ jsx("dd", {
75
+ className: VALUE_CLASS_NAME[variant],
76
+ children
77
+ })]
78
+ });
79
+ };
25
80
  //#endregion
26
81
  export { DetailsGrid, DetailsItem };
@@ -1,8 +1,14 @@
1
1
  import { Form as Form$1 } from "@base-ui/react/form";
2
+ import { VariantProps } from "class-variance-authority";
2
3
  //#region src/components/form.d.ts
3
- declare const Form: ({ className, dirty, onDiscard, children, ...props }: Form$1.Props & {
4
+ declare const formVariants: (props?: ({
5
+ density?: "compact" | "default" | "snug" | null | undefined;
6
+ layout?: "inline" | "stack" | null | undefined;
7
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
+ type FormProps = Form$1.Props & VariantProps<typeof formVariants> & {
4
9
  dirty?: boolean;
5
10
  onDiscard?: (() => void) | undefined;
6
- }) => import("react").JSX.Element;
11
+ };
12
+ declare const Form: ({ className, density, layout, dirty, onDiscard, children, ...props }: FormProps) => import("react").JSX.Element;
7
13
  //#endregion
8
- export { Form };
14
+ export { Form, type FormProps, formVariants };
@@ -2,10 +2,31 @@
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { DialogFormState } from "./dialog-form-state.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { cva } from "class-variance-authority";
5
6
  import { Form as Form$1 } from "@base-ui/react/form";
6
7
  //#region src/components/form.tsx
7
- const Form = ({ className, dirty = false, onDiscard, children, ...props }) => /* @__PURE__ */ jsxs(Form$1, {
8
- className: cn("flex w-full flex-col gap-4", className),
8
+ const formVariants = cva("flex w-full", {
9
+ defaultVariants: {
10
+ density: "default",
11
+ layout: "stack"
12
+ },
13
+ variants: {
14
+ density: {
15
+ default: "gap-4",
16
+ snug: "gap-3",
17
+ compact: "gap-2"
18
+ },
19
+ layout: {
20
+ stack: "flex-col",
21
+ inline: "flex-row items-center"
22
+ }
23
+ }
24
+ });
25
+ const Form = ({ className, density, layout, dirty = false, onDiscard, children, ...props }) => /* @__PURE__ */ jsxs(Form$1, {
26
+ className: cn(formVariants({
27
+ density,
28
+ layout
29
+ }), className),
9
30
  "data-slot": "form",
10
31
  ...props,
11
32
  children: [/* @__PURE__ */ jsx(DialogFormState, {
@@ -14,4 +35,4 @@ const Form = ({ className, dirty = false, onDiscard, children, ...props }) => /*
14
35
  }), children]
15
36
  });
16
37
  //#endregion
17
- export { Form };
38
+ export { Form, formVariants };
@@ -16,4 +16,13 @@ export declare const INPUT_SIZE_CLASS_NAMES: {
16
16
  readonly default: undefined;
17
17
  readonly lg: "min-h-9.5 leading-9.5 sm:min-h-8.5 sm:leading-8.5";
18
18
  };
19
+ /**
20
+ * A field edited where it is read: an option's name in a list, a page
21
+ * heading, the summary under it. No box until focused, the type, leading and
22
+ * colour of the text around it, so the row keeps the height of its text; a
23
+ * focus ring marks it while editing. A slim inline inset gives the ring and
24
+ * the caret room, and a matching negative margin keeps the text in line with
25
+ * its neighbours.
26
+ */
27
+ export declare const INLINE_FIELD_CLASS_NAME = "placeholder:text-foreground-placeholder focus-visible:ring-ring/30 -mx-1 min-h-0 w-full min-w-0 rounded-sm bg-transparent px-1 py-0 font-[inherit] text-[length:inherit] leading-[inherit] text-inherit outline-none focus-visible:ring-2";
19
28
  //#endregion
@@ -17,5 +17,14 @@ const INPUT_SIZE_CLASS_NAMES = {
17
17
  [CONTROL_SIZE.default]: void 0,
18
18
  [CONTROL_SIZE.lg]: "min-h-9.5 leading-9.5 sm:min-h-8.5 sm:leading-8.5"
19
19
  };
20
+ /**
21
+ * A field edited where it is read: an option's name in a list, a page
22
+ * heading, the summary under it. No box until focused, the type, leading and
23
+ * colour of the text around it, so the row keeps the height of its text; a
24
+ * focus ring marks it while editing. A slim inline inset gives the ring and
25
+ * the caret room, and a matching negative margin keeps the text in line with
26
+ * its neighbours.
27
+ */
28
+ const INLINE_FIELD_CLASS_NAME = "placeholder:text-foreground-placeholder focus-visible:ring-ring/30 -mx-1 min-h-0 w-full min-w-0 rounded-sm bg-transparent px-1 py-0 font-[inherit] text-[length:inherit] leading-[inherit] text-inherit outline-none focus-visible:ring-2";
20
29
  //#endregion
21
- export { INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES };
30
+ export { INLINE_FIELD_CLASS_NAME, INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES };
@@ -7,8 +7,14 @@ type InputProps = Omit<Input$1.Props & React$1.RefAttributes<HTMLInputElement>,
7
7
  size?: ControlSize | number;
8
8
  style?: React$1.CSSProperties;
9
9
  unstyled?: boolean;
10
+ /**
11
+ * A borderless field in a line of text: it edits a value where it is read,
12
+ * with no box until focused and the parent's type, leading and colour. See
13
+ * `INLINE_FIELD_CLASS_NAME`.
14
+ */
15
+ inline?: boolean;
10
16
  nativeInput?: boolean;
11
17
  };
12
- declare const Input: ({ className, size, unstyled, nativeInput, dir, onChange, ...props }: InputProps) => React$1.JSX.Element;
18
+ declare const Input: ({ className, size, unstyled, inline, nativeInput, dir, onChange, ...props }: InputProps) => React$1.JSX.Element;
13
19
  //#endregion
14
20
  export { Input, type InputProps };
@@ -3,11 +3,11 @@ import { SearchIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { CONTROL_SIZE } from "../lib/control-size.js";
5
5
  import { isStructuredInputType, useContentDir } from "../hooks/use-content-dir.js";
6
- import { INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
6
+ import { INLINE_FIELD_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Input as Input$1 } from "@base-ui/react/input";
9
9
  //#region src/components/input.tsx
10
- const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, nativeInput = false, dir, onChange, ...props }) => {
10
+ const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, inline = false, nativeInput = false, dir, onChange, ...props }) => {
11
11
  const controlSize = typeof size === "number" ? CONTROL_SIZE.default : size;
12
12
  const contentDir = useContentDir({
13
13
  dir: dir ?? (isStructuredInputType(props.type) ? "ltr" : void 0),
@@ -18,9 +18,9 @@ const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, nativ
18
18
  contentDir.trackValue(event.currentTarget.value);
19
19
  onChange?.(event);
20
20
  };
21
- const inputClassName = cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[controlSize], unstyled && "px-0", props.type === "search" && "ps-8 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none");
21
+ const inputClassName = cn(inline ? INLINE_FIELD_CLASS_NAME : INPUT_ELEMENT_CLASS_NAME, !inline && INPUT_SIZE_CLASS_NAMES[controlSize], unstyled && !inline && "px-0", props.type === "search" && "ps-8 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none");
22
22
  return /* @__PURE__ */ jsxs("span", {
23
- className: cn(!unstyled && "border-input bg-background text-foreground ring-ring/24 has-autofill:bg-foreground/4 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] pointer-coarse:min-h-11", className) || void 0,
23
+ className: cn(inline ? "relative inline-flex w-full" : !unstyled && "border-input bg-background text-foreground ring-ring/24 has-autofill:bg-foreground/4 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] pointer-coarse:min-h-11", className) || void 0,
24
24
  "data-size": size,
25
25
  "data-slot": "input-control",
26
26
  dir: contentDir.dir,
@@ -1,5 +1,27 @@
1
1
  import { useRender } from "@base-ui/react/use-render";
2
2
  //#region src/components/label.d.ts
3
- declare function Label({ className, render, ...props }: useRender.ComponentProps<"label">): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
3
+ /**
4
+ * - `default`: the caption above or beside a control.
5
+ * - `choice`: one row of a list of choices, wrapping its radio or checkbox so
6
+ * the whole row picks it; it highlights on hover and rings while the
7
+ * control inside has keyboard focus.
8
+ * - `choice-card`: a bordered card wrapping its radio, for choices that carry
9
+ * a description; the chosen card takes the primary border.
10
+ * - `muted`: a quiet caption in small type, for a filter bar or a dense side
11
+ * form where the controls, not their names, carry the eye.
12
+ *
13
+ * The choice variants dim and stop inviting a click while their control is
14
+ * disabled.
15
+ */
16
+ declare const LABEL_VARIANT_CLASS_NAME: {
17
+ readonly default: "text-foreground inline-flex items-center gap-2 text-base/4.5 font-medium sm:text-sm/4";
18
+ readonly choice: "hover:bg-muted/60 has-focus-visible:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm px-1 py-1.5 text-sm has-focus-visible:ring-2 has-disabled:cursor-not-allowed has-disabled:opacity-64";
19
+ readonly "choice-card": "has-checked:border-primary has-checked:bg-muted/50 flex min-h-11 cursor-pointer items-start gap-3 rounded-md border p-3 has-disabled:cursor-not-allowed has-disabled:opacity-50";
20
+ readonly muted: "text-muted-foreground inline-flex items-center gap-2 text-xs/4 font-medium";
21
+ };
22
+ type LabelVariant = keyof typeof LABEL_VARIANT_CLASS_NAME;
23
+ declare function Label({ className, render, variant, ...props }: useRender.ComponentProps<"label"> & {
24
+ variant?: LabelVariant;
25
+ }): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
4
26
  //#endregion
5
- export { Label };
27
+ export { Label, type LabelVariant };
@@ -3,9 +3,28 @@ import { cn } from "../lib/utils.js";
3
3
  import { mergeProps } from "@base-ui/react/merge-props";
4
4
  import { useRender } from "@base-ui/react/use-render";
5
5
  //#region src/components/label.tsx
6
- function Label({ className, render, ...props }) {
6
+ /**
7
+ * - `default`: the caption above or beside a control.
8
+ * - `choice`: one row of a list of choices, wrapping its radio or checkbox so
9
+ * the whole row picks it; it highlights on hover and rings while the
10
+ * control inside has keyboard focus.
11
+ * - `choice-card`: a bordered card wrapping its radio, for choices that carry
12
+ * a description; the chosen card takes the primary border.
13
+ * - `muted`: a quiet caption in small type, for a filter bar or a dense side
14
+ * form where the controls, not their names, carry the eye.
15
+ *
16
+ * The choice variants dim and stop inviting a click while their control is
17
+ * disabled.
18
+ */
19
+ const LABEL_VARIANT_CLASS_NAME = {
20
+ default: "text-foreground inline-flex items-center gap-2 text-base/4.5 font-medium sm:text-sm/4",
21
+ choice: "hover:bg-muted/60 has-focus-visible:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm px-1 py-1.5 text-sm has-focus-visible:ring-2 has-disabled:cursor-not-allowed has-disabled:opacity-64",
22
+ "choice-card": "has-checked:border-primary has-checked:bg-muted/50 flex min-h-11 cursor-pointer items-start gap-3 rounded-md border p-3 has-disabled:cursor-not-allowed has-disabled:opacity-50",
23
+ muted: "text-muted-foreground inline-flex items-center gap-2 text-xs/4 font-medium"
24
+ };
25
+ function Label({ className, render, variant = "default", ...props }) {
7
26
  const defaultProps = {
8
- className: cn("text-foreground inline-flex items-center gap-2 text-base/4.5 font-medium sm:text-sm/4", className),
27
+ className: cn(LABEL_VARIANT_CLASS_NAME[variant], className),
9
28
  "data-slot": "label"
10
29
  };
11
30
  return useRender({
@@ -1,17 +1,24 @@
1
1
  import { ControlSize } from "../lib/control-size.js";
2
2
  import { NumberField } from "@base-ui/react/number-field";
3
+ import { ReactNode } from "react";
3
4
  //#region src/components/number-input.d.ts
4
5
  type NumberInputProps = Omit<NumberField.Root.Props, "children" | "className" | "render"> & {
5
6
  className?: string;
6
7
  inputClassName?: string;
7
- inputProps?: Omit<NumberField.Input.Props, "className" | "defaultValue" | "id" | "onChange" | "type" | "value">;
8
+ inputProps?: Omit<NumberField.Input.Props, "className" | "defaultValue" | "id" | "onChange" | "render" | "type" | "value">;
8
9
  size?: ControlSize;
10
+ /**
11
+ * The unit the value is in ("pt", "%"), shown muted at the end of the
12
+ * control so the number reads with its unit while the typed text stays a
13
+ * bare number.
14
+ */
15
+ suffix?: ReactNode;
9
16
  };
10
17
  /**
11
18
  * A numeric control that keeps the user's editable text separate from its
12
19
  * canonical numeric value. Parsed changes are reported through `onValueChange`;
13
20
  * formatting and clamping happen only at Base UI's commit boundaries.
14
21
  */
15
- declare const NumberInput: ({ className, inputClassName, inputProps, dir, size, ...props }: NumberInputProps) => import("react").JSX.Element;
22
+ declare const NumberInput: ({ className, inputClassName, inputProps, dir, size, suffix, ...props }: NumberInputProps) => import("react").JSX.Element;
16
23
  //#endregion
17
24
  export { NumberInput, type NumberInputProps };
@@ -2,7 +2,7 @@
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { CONTROL_SIZE } from "../lib/control-size.js";
4
4
  import { INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
5
- import { jsx } from "react/jsx-runtime";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { NumberField } from "@base-ui/react/number-field";
7
7
  //#region src/components/number-input.tsx
8
8
  /**
@@ -10,18 +10,30 @@ import { NumberField } from "@base-ui/react/number-field";
10
10
  * canonical numeric value. Parsed changes are reported through `onValueChange`;
11
11
  * formatting and clamping happen only at Base UI's commit boundaries.
12
12
  */
13
- const NumberInput = ({ className, inputClassName, inputProps, dir = "ltr", size = CONTROL_SIZE.default, ...props }) => /* @__PURE__ */ jsx(NumberField.Root, {
13
+ const NumberInput = ({ className, inputClassName, inputProps, dir = "ltr", size = CONTROL_SIZE.default, suffix, ...props }) => /* @__PURE__ */ jsxs(NumberField.Root, {
14
14
  ...props,
15
15
  className: cn(INPUT_CONTROL_CLASS_NAME, className),
16
16
  "data-size": size,
17
17
  "data-slot": "number-input-control",
18
18
  dir,
19
19
  render: void 0,
20
- children: /* @__PURE__ */ jsx(NumberField.Input, {
20
+ children: [/* @__PURE__ */ jsx(NumberField.Input, {
21
21
  ...inputProps,
22
- className: cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[size], inputClassName),
23
- "data-slot": "number-input"
24
- })
22
+ className: cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[size], "tabular-nums", suffix !== void 0 && "pe-1", inputClassName),
23
+ "data-slot": "number-input",
24
+ render: (inputElementProps, state) => /* @__PURE__ */ jsx("input", {
25
+ ...inputElementProps,
26
+ "aria-valuemax": props.max,
27
+ "aria-valuemin": props.min,
28
+ "aria-valuenow": state.value ?? void 0,
29
+ dir,
30
+ role: "spinbutton"
31
+ })
32
+ }), suffix === void 0 ? null : /* @__PURE__ */ jsx("span", {
33
+ className: "text-muted-foreground pointer-events-none flex shrink-0 items-center pe-2.5 text-xs",
34
+ "data-slot": "number-input-suffix",
35
+ children: suffix
36
+ })]
25
37
  });
26
38
  //#endregion
27
39
  export { NumberInput };
@@ -4,7 +4,20 @@ import { ControlSize } from "../lib/control-size.js";
4
4
  type TextareaProps = React$1.ComponentProps<"textarea"> & {
5
5
  size?: ControlSize | number;
6
6
  unstyled?: boolean;
7
+ /**
8
+ * A borderless field in a line of text: it edits text where it is read
9
+ * (the summary under a heading), with no box until focused, the parent's
10
+ * type and colour, no floor height, and grows with its content. See
11
+ * `INLINE_FIELD_CLASS_NAME`.
12
+ */
13
+ inline?: boolean;
14
+ /**
15
+ * `content` (default) grows with what is typed. `fixed` keeps the height
16
+ * its `rows` give and scrolls, with no resize grip: an editor inside a
17
+ * table cell or a row must not push its neighbours around.
18
+ */
19
+ fieldSizing?: "content" | "fixed";
7
20
  };
8
- declare const Textarea: ({ className, size, unstyled, dir, onChange, ...props }: TextareaProps) => React$1.JSX.Element;
21
+ declare const Textarea: ({ className, size, unstyled, inline, fieldSizing, dir, onChange, ...props }: TextareaProps) => React$1.JSX.Element;
9
22
  //#endregion
10
23
  export { Textarea, type TextareaProps };
@@ -2,11 +2,12 @@
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { CONTROL_SIZE } from "../lib/control-size.js";
4
4
  import { useContentDir } from "../hooks/use-content-dir.js";
5
+ import { INLINE_FIELD_CLASS_NAME } from "./input-control.js";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  import { mergeProps } from "@base-ui/react/merge-props";
7
8
  import { Field } from "@base-ui/react/field";
8
9
  //#region src/components/textarea.tsx
9
- const Textarea = ({ className, size = CONTROL_SIZE.default, unstyled = false, dir, onChange, ...props }) => {
10
+ const Textarea = ({ className, size = CONTROL_SIZE.default, unstyled = false, inline = false, fieldSizing = "content", dir, onChange, ...props }) => {
10
11
  const controlSize = typeof size === "number" ? CONTROL_SIZE.default : size;
11
12
  const contentDir = useContentDir({
12
13
  dir,
@@ -18,11 +19,11 @@ const Textarea = ({ className, size = CONTROL_SIZE.default, unstyled = false, di
18
19
  onChange?.(event);
19
20
  };
20
21
  return /* @__PURE__ */ jsx("span", {
21
- className: cn(!unstyled && "border-input bg-background text-foreground ring-ring/24 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", className) || void 0,
22
+ className: cn(inline && "relative inline-flex w-full", !unstyled && !inline && "border-input bg-background text-foreground ring-ring/24 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", className) || void 0,
22
23
  "data-size": size,
23
24
  "data-slot": "textarea-control",
24
25
  children: /* @__PURE__ */ jsx(Field.Control, { render: (defaultProps) => /* @__PURE__ */ jsx("textarea", {
25
- className: cn("field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5", TEXTAREA_SIZE_CLASS_NAMES[controlSize]),
26
+ className: cn(inline ? cn(INLINE_FIELD_CLASS_NAME, "resize-none") : cn("w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none", TEXTAREA_SIZE_CLASS_NAMES[controlSize], fieldSizing === "content" && TEXTAREA_FLOOR_CLASS_NAMES[controlSize]), fieldSizing === "content" ? "field-sizing-content" : "field-sizing-fixed resize-none"),
26
27
  "data-slot": "textarea",
27
28
  ...mergeProps(defaultProps, props, { onChange: handleChange }),
28
29
  dir: contentDir.dir
@@ -30,9 +31,14 @@ const Textarea = ({ className, size = CONTROL_SIZE.default, unstyled = false, di
30
31
  });
31
32
  };
32
33
  const TEXTAREA_SIZE_CLASS_NAMES = {
33
- [CONTROL_SIZE.sm]: "min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",
34
+ [CONTROL_SIZE.sm]: "px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]",
34
35
  [CONTROL_SIZE.default]: void 0,
35
- [CONTROL_SIZE.lg]: "min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5"
36
+ [CONTROL_SIZE.lg]: "py-[calc(--spacing(2)-1px)]"
37
+ };
38
+ const TEXTAREA_FLOOR_CLASS_NAMES = {
39
+ [CONTROL_SIZE.sm]: "min-h-16.5 max-sm:min-h-19.5",
40
+ [CONTROL_SIZE.default]: "min-h-17.5 max-sm:min-h-20.5",
41
+ [CONTROL_SIZE.lg]: "min-h-18.5 max-sm:min-h-21.5"
36
42
  };
37
43
  //#endregion
38
44
  export { Textarea };