@stll/ui 0.42.1 → 0.44.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 (35) hide show
  1. package/dist/components/button.d.ts +1 -1
  2. package/dist/components/button.js +8 -5
  3. package/dist/components/date-picker-popover.js +6 -1
  4. package/dist/components/details-grid.d.ts +10 -0
  5. package/dist/components/details-grid.js +26 -0
  6. package/dist/components/dialog-form-state.d.ts +16 -0
  7. package/dist/components/dialog-form-state.js +28 -0
  8. package/dist/components/dialog.d.ts +12 -2
  9. package/dist/components/dialog.js +89 -23
  10. package/dist/components/form.d.ts +4 -1
  11. package/dist/components/form.js +8 -3
  12. package/dist/components/inline-drop-indicator.d.ts +10 -0
  13. package/dist/components/inline-drop-indicator.js +13 -0
  14. package/dist/components/inline-rename.d.ts +12 -0
  15. package/dist/components/inline-rename.js +75 -0
  16. package/dist/components/input-group.d.ts +1 -1
  17. package/dist/components/loader.d.ts +10 -4
  18. package/dist/components/loader.js +12 -8
  19. package/dist/components/sidebar.d.ts +1 -1
  20. package/dist/components/split-button.d.ts +35 -0
  21. package/dist/components/split-button.js +64 -0
  22. package/dist/components/toast.d.ts +5 -5
  23. package/dist/components/toast.js +59 -55
  24. package/dist/components/toolbar-icon-action.d.ts +17 -0
  25. package/dist/components/toolbar-icon-action.js +23 -0
  26. package/dist/css.d.ts +3 -0
  27. package/dist/index.d.ts +5 -2
  28. package/dist/index.js +6 -3
  29. package/dist/kanban/drag-interactions.d.ts +1 -1
  30. package/dist/kanban/drag-interactions.js +4 -4
  31. package/dist/lib/inline-reorder.d.ts +19 -0
  32. package/dist/lib/inline-reorder.js +15 -0
  33. package/dist/styles/inline-rename.css +69 -0
  34. package/dist/styles/theme.css +2 -0
  35. package/package.json +30 -6
@@ -14,7 +14,7 @@ import { VariantProps } from "class-variance-authority";
14
14
  declare const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
15
15
  declare const buttonVariants: (props?: ({
16
16
  size?: "default" | "row" | "chip" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "sm" | "xl" | "xs" | null | undefined;
17
- variant?: "link" | "destructive" | "muted" | "default" | "destructive-outline" | "destructive-ghost" | "ghost" | "outline" | "secondary" | null | undefined;
17
+ variant?: "link" | "default" | "destructive" | "destructive-outline" | "destructive-ghost" | "ghost" | "muted" | "outline" | "secondary" | null | undefined;
18
18
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
19
19
  type ButtonProps = {
20
20
  variant?: VariantProps<typeof buttonVariants>["variant"];
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { LoaderIcon } from "../icons.js";
3
2
  import { cn } from "../lib/utils.js";
4
3
  import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
5
4
  import { BUTTON_DISPOSITION, blockDisabledActivation, blockDisabledKeyActivation, resolveButtonDisposition } from "../lib/button-disposition.js";
6
5
  import { MENU_ROW_CLASS_NAME } from "../lib/menu-row.js";
6
+ import { Loader } from "./loader.js";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { mergeProps } from "@base-ui/react/merge-props";
9
9
  import { useRender } from "@base-ui/react/use-render";
@@ -62,16 +62,19 @@ function Button({ className, variant, size, render, loading, children, disabled,
62
62
  });
63
63
  const isAccessibleDisabled = disposition === BUTTON_DISPOSITION.accessibleDisabled;
64
64
  const defaultProps = {
65
- children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderIcon, {
66
- className: "animate-spin [transition:opacity_150ms_ease-out,scale_320ms_var(--ease-spring)] motion-reduce:transition-none starting:scale-50 starting:opacity-0",
67
- "data-slot": "button-loader"
65
+ children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader, {
66
+ className: "[transition:opacity_150ms_ease-out,scale_320ms_var(--ease-spring)] motion-reduce:transition-none starting:scale-50 starting:opacity-0",
67
+ "data-slot": "button-loader",
68
+ size: "sm",
69
+ variant: "decorative"
68
70
  }), children] }) : children,
69
71
  className: cn(buttonVariants({
70
72
  className,
71
73
  size,
72
74
  variant
73
- }), loading && "[&_svg:not([data-slot=button-loader])]:hidden", isAccessibleDisabled && "cursor-not-allowed opacity-64"),
75
+ }), loading && "[&_svg:not([data-slot=button-loader]_svg)]:hidden", isAccessibleDisabled && "cursor-not-allowed opacity-64"),
74
76
  "data-slot": "button",
77
+ "aria-busy": loading || void 0,
75
78
  ...isAccessibleDisabled ? {
76
79
  "aria-disabled": true,
77
80
  "data-disabled": "",
@@ -427,6 +427,10 @@ const DatePickerPopoverContent = (props) => {
427
427
  const selectedMonth = value ? Temporal.PlainDate.from(value).month - 1 : null;
428
428
  const handleOpenChange = (open) => {
429
429
  onOpenChange?.(open);
430
+ if (open) {
431
+ setViewMonthOverride(null);
432
+ setFocusedDate(value || today);
433
+ }
430
434
  if (!open) {
431
435
  setView("days");
432
436
  setDecadeBaseOverride(decadeStart(viewYear));
@@ -452,7 +456,7 @@ const DatePickerPopoverContent = (props) => {
452
456
  children: [
453
457
  showIcon && /* @__PURE__ */ jsx(CalendarIcon, { className: "size-3.5 shrink-0" }),
454
458
  /* @__PURE__ */ jsx("span", {
455
- className: "min-w-0 flex-1 overflow-hidden text-start wrap-break-word text-ellipsis",
459
+ className: "min-w-0 flex-1 truncate text-start",
456
460
  id: labelledBy ? displayValueId : void 0,
457
461
  children: displayLabel
458
462
  }),
@@ -462,6 +466,7 @@ const DatePickerPopoverContent = (props) => {
462
466
  })
463
467
  ]
464
468
  }), /* @__PURE__ */ jsx(PopoverPopup, {
469
+ initialFocus: () => gridRef.current?.querySelector(`[data-date="${value || today}"]`) ?? gridRef.current,
465
470
  className: "data-starting-style:scale-100",
466
471
  layer,
467
472
  padding: "sm",
@@ -0,0 +1,10 @@
1
+ import * as React$1 from "react";
2
+ //#region src/components/details-grid.d.ts
3
+ declare const DetailsGrid: ({ className, ...props }: React$1.ComponentProps<"dl">) => React$1.JSX.Element;
4
+ type DetailsItemProps = React$1.ComponentProps<"div"> & {
5
+ label: React$1.ReactNode;
6
+ span?: "wide";
7
+ };
8
+ declare const DetailsItem: ({ label, children, span, className, ...props }: DetailsItemProps) => React$1.JSX.Element;
9
+ //#endregion
10
+ export { DetailsGrid, DetailsItem };
@@ -0,0 +1,26 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/components/details-grid.tsx
4
+ const DetailsGrid = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
5
+ className: "@container min-w-0",
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
+ })]
24
+ });
25
+ //#endregion
26
+ export { DetailsGrid, DetailsItem };
@@ -0,0 +1,16 @@
1
+ //#region src/components/dialog-form-state.d.ts
2
+ export type DialogFormSource = {
3
+ dirty: boolean;
4
+ onDiscard?: (() => void) | undefined;
5
+ };
6
+ export type DialogFormRegistry = {
7
+ sources: Map<string, DialogFormSource>;
8
+ clearConfirmation: () => void;
9
+ };
10
+ export declare const DialogFormContext: import("react").Context<DialogFormRegistry | null>;
11
+ /** Forms report semantic changes, including edits made by custom controls. */
12
+ export declare const DialogFormState: ({ dirty, onDiscard }: {
13
+ dirty: boolean;
14
+ onDiscard?: (() => void) | undefined;
15
+ }) => null;
16
+ //#endregion
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { createContext, useContext, useId, useLayoutEffect } from "react";
3
+ //#region src/components/dialog-form-state.tsx
4
+ const DialogFormContext = createContext(null);
5
+ /** Forms report semantic changes, including edits made by custom controls. */
6
+ const DialogFormState = ({ dirty, onDiscard }) => {
7
+ const registry = useContext(DialogFormContext);
8
+ const id = useId();
9
+ useLayoutEffect(() => {
10
+ if (registry === null) return;
11
+ if (registry.sources.get(id)?.dirty !== dirty) registry.clearConfirmation();
12
+ registry.sources.set(id, {
13
+ dirty,
14
+ onDiscard
15
+ });
16
+ }, [
17
+ dirty,
18
+ id,
19
+ onDiscard,
20
+ registry
21
+ ]);
22
+ useLayoutEffect(() => () => {
23
+ registry?.sources.delete(id);
24
+ }, [id, registry]);
25
+ return null;
26
+ };
27
+ //#endregion
28
+ export { DialogFormContext, DialogFormState };
@@ -1,9 +1,19 @@
1
1
  import { OverlayLayer } from "../lib/overlay-layer.js";
2
+ import { DialogFormState } from "./dialog-form-state.js";
2
3
  import * as React$1 from "react";
3
4
  import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
4
5
  //#region src/components/dialog.d.ts
5
6
  declare const DialogCreateHandle: typeof Dialog$1.createHandle;
6
- declare const Dialog: <Payload>(props: Dialog$1.Root.Props<Payload>) => React$1.JSX.Element;
7
+ type DialogLabels = {
8
+ close: string;
9
+ unsavedChanges: string;
10
+ };
11
+ declare const DialogProvider: ({ labels, children }: React$1.PropsWithChildren<{
12
+ labels: DialogLabels;
13
+ }>) => React$1.JSX.Element;
14
+ declare const Dialog: <Payload>({ dirty, onOpenChange, onOpenChangeComplete, ...props }: Dialog$1.Root.Props<Payload> & {
15
+ dirty?: boolean;
16
+ }) => React$1.JSX.Element;
7
17
  declare const DialogPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").AlertDialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
8
18
  declare function DialogTrigger({ tooltip, ...props }: Dialog$1.Trigger.Props & {
9
19
  tooltip?: React$1.ReactNode;
@@ -34,4 +44,4 @@ declare const DialogPanel: ({ className, scrollFade, ...props }: React$1.Compone
34
44
  scrollFade?: boolean;
35
45
  }) => React$1.JSX.Element;
36
46
  //#endregion
37
- export { Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPortal, DialogTitle, DialogTrigger, DialogViewport };
47
+ export { Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogFormState, DialogHeader, DialogPanel, DialogPortal, DialogProvider, DialogTitle, DialogTrigger, DialogViewport };
@@ -5,11 +5,62 @@ import { OVERLAY_LAYER_CLASS_NAMES } from "../lib/overlay-layer.js";
5
5
  import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
6
6
  import { Button } from "./button.js";
7
7
  import { ScrollArea } from "./scroll-area.js";
8
+ import { DialogFormContext, DialogFormState } from "./dialog-form-state.js";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { createContext, useContext, useRef, useState } from "react";
9
11
  import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
10
12
  //#region src/components/dialog.tsx
11
13
  const DialogCreateHandle = Dialog$1.createHandle;
12
- const Dialog = Dialog$1.Root;
14
+ const DialogLabelsContext = createContext({
15
+ close: "Close",
16
+ unsavedChanges: "Unsaved changes. Press Esc again to discard"
17
+ });
18
+ const DialogProvider = ({ labels, children }) => /* @__PURE__ */ jsx(DialogLabelsContext, {
19
+ value: labels,
20
+ children
21
+ });
22
+ const DialogDismissalContext = createContext("idle");
23
+ const Dialog = ({ dirty = false, onOpenChange, onOpenChangeComplete, ...props }) => {
24
+ const [dismissal, setDismissal] = useState("idle");
25
+ const pendingDiscard = useRef([]);
26
+ const [forms] = useState(() => ({
27
+ sources: /* @__PURE__ */ new Map(),
28
+ clearConfirmation: () => setDismissal("idle")
29
+ }));
30
+ return /* @__PURE__ */ jsx(DialogFormContext, {
31
+ value: forms,
32
+ children: /* @__PURE__ */ jsx(DialogDismissalContext, {
33
+ value: dismissal,
34
+ children: /* @__PURE__ */ jsx(Dialog$1.Root, {
35
+ ...props,
36
+ onOpenChange: (open, details) => {
37
+ const hasChanges = dirty || [...forms.sources.values()].some((source) => source.dirty);
38
+ if (!open && details.reason === "escape-key" && hasChanges && (dismissal === "idle" || details.event.repeat)) {
39
+ details.cancel();
40
+ setDismissal("confirm-discard");
41
+ return;
42
+ }
43
+ onOpenChange?.(open, details);
44
+ if (details.isCanceled) return;
45
+ pendingDiscard.current = [];
46
+ if (!open) {
47
+ for (const source of forms.sources.values()) if (source.dirty && source.onDiscard !== void 0) pendingDiscard.current.push(source.onDiscard);
48
+ }
49
+ setDismissal("idle");
50
+ },
51
+ onOpenChangeComplete: (open) => {
52
+ if (!open) {
53
+ const callbacks = pendingDiscard.current;
54
+ pendingDiscard.current = [];
55
+ for (const discard of callbacks) discard();
56
+ setDismissal("idle");
57
+ }
58
+ onOpenChangeComplete?.(open);
59
+ }
60
+ })
61
+ })
62
+ });
63
+ };
13
64
  const DialogPortal = Dialog$1.Portal;
14
65
  function DialogTrigger({ tooltip, ...props }) {
15
66
  return renderTooltipTrigger({
@@ -39,27 +90,42 @@ const DialogViewport = ({ className, layer = "default", ...props }) => /* @__PUR
39
90
  "data-slot": "dialog-viewport",
40
91
  ...props
41
92
  });
42
- const DialogPopup = ({ backdropClassName, className, children, showCloseButton = true, bottomStickOnMobile = true, layer = "default", viewportClassName, ...props }) => /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, {
43
- className: backdropClassName,
44
- layer
45
- }), /* @__PURE__ */ jsx(DialogViewport, {
46
- className: cn(bottomStickOnMobile && "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12", viewportClassName),
47
- layer,
48
- children: /* @__PURE__ */ jsxs(Dialog$1.Popup, {
49
- className: cn("bg-popover text-popover-foreground relative row-start-2 flex max-h-full min-h-0 w-full max-w-lg min-w-0 -translate-y-[calc(1.25rem*var(--nested-dialogs))] scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-ending-style:opacity-0 data-nested:data-ending-style:translate-y-8 data-nested-dialog-open:origin-top data-starting-style:scale-98 data-starting-style:opacity-0 data-nested:data-starting-style:translate-y-8 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", bottomStickOnMobile && "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:before:hidden max-sm:before:rounded-none max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4", className),
50
- "data-slot": "dialog-popup",
51
- ...props,
52
- children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, {
53
- "aria-label": "Close",
54
- className: "absolute end-2 top-2",
55
- render: /* @__PURE__ */ jsx(Button, {
56
- size: "icon",
57
- variant: "ghost"
58
- }),
59
- children: /* @__PURE__ */ jsx(XIcon, {})
60
- })]
61
- })
62
- })] });
93
+ const DialogPopup = ({ backdropClassName, className, children, showCloseButton = true, bottomStickOnMobile = true, layer = "default", viewportClassName, ...props }) => {
94
+ const dismissal = useContext(DialogDismissalContext);
95
+ const labels = useContext(DialogLabelsContext);
96
+ return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, {
97
+ className: backdropClassName,
98
+ layer
99
+ }), /* @__PURE__ */ jsx(DialogViewport, {
100
+ className: cn(bottomStickOnMobile && "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12", viewportClassName),
101
+ layer,
102
+ children: /* @__PURE__ */ jsxs(Dialog$1.Popup, {
103
+ className: cn("bg-popover text-popover-foreground relative row-start-2 flex max-h-full min-h-0 w-full max-w-lg min-w-0 -translate-y-[calc(1.25rem*var(--nested-dialogs))] scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-ending-style:opacity-0 data-nested:data-ending-style:translate-y-8 data-nested-dialog-open:origin-top data-starting-style:scale-98 data-starting-style:opacity-0 data-nested:data-starting-style:translate-y-8 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", bottomStickOnMobile && "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:before:hidden max-sm:before:rounded-none max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4", className),
104
+ "data-slot": "dialog-popup",
105
+ ...props,
106
+ children: [
107
+ dismissal === "confirm-discard" && /* @__PURE__ */ jsx("div", {
108
+ className: "flex shrink-0 justify-end ps-2 pe-12 pt-2",
109
+ children: /* @__PURE__ */ jsx("span", {
110
+ className: "text-muted-foreground max-w-64 px-2 py-1 text-xs",
111
+ role: "status",
112
+ children: labels.unsavedChanges
113
+ })
114
+ }),
115
+ children,
116
+ (showCloseButton || dismissal === "confirm-discard") && /* @__PURE__ */ jsx(Dialog$1.Close, {
117
+ "aria-label": labels.close,
118
+ className: "absolute end-2 top-2",
119
+ render: /* @__PURE__ */ jsx(Button, {
120
+ size: "icon",
121
+ variant: "ghost"
122
+ }),
123
+ children: /* @__PURE__ */ jsx(XIcon, {})
124
+ })
125
+ ]
126
+ })
127
+ })] });
128
+ };
63
129
  const DialogHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
64
130
  className: cn("flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-3 max-sm:pb-4", className),
65
131
  "data-slot": "dialog-header",
@@ -89,4 +155,4 @@ const DialogPanel = ({ className, scrollFade = true, ...props }) => /* @__PURE__
89
155
  })
90
156
  });
91
157
  //#endregion
92
- export { Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPortal, DialogTitle, DialogTrigger, DialogViewport };
158
+ export { Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogFormState, DialogHeader, DialogPanel, DialogPortal, DialogProvider, DialogTitle, DialogTrigger, DialogViewport };
@@ -1,5 +1,8 @@
1
1
  import { Form as Form$1 } from "@base-ui/react/form";
2
2
  //#region src/components/form.d.ts
3
- declare const Form: ({ className, ...props }: Form$1.Props) => import("react").JSX.Element;
3
+ declare const Form: ({ className, dirty, onDiscard, children, ...props }: Form$1.Props & {
4
+ dirty?: boolean;
5
+ onDiscard?: (() => void) | undefined;
6
+ }) => import("react").JSX.Element;
4
7
  //#endregion
5
8
  export { Form };
@@ -1,12 +1,17 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/utils.js";
3
- import { jsx } from "react/jsx-runtime";
3
+ import { DialogFormState } from "./dialog-form-state.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { Form as Form$1 } from "@base-ui/react/form";
5
6
  //#region src/components/form.tsx
6
- const Form = ({ className, ...props }) => /* @__PURE__ */ jsx(Form$1, {
7
+ const Form = ({ className, dirty = false, onDiscard, children, ...props }) => /* @__PURE__ */ jsxs(Form$1, {
7
8
  className: cn("flex w-full flex-col gap-4", className),
8
9
  "data-slot": "form",
9
- ...props
10
+ ...props,
11
+ children: [/* @__PURE__ */ jsx(DialogFormState, {
12
+ dirty,
13
+ onDiscard
14
+ }), children]
10
15
  });
11
16
  //#endregion
12
17
  export { Form };
@@ -0,0 +1,10 @@
1
+ import { InlineDropPosition } from "../lib/inline-reorder.js";
2
+ //#region src/components/inline-drop-indicator.d.ts
3
+ /**
4
+ * The bar beside a dragged-over item in a horizontal row. Render it inside
5
+ * the item's positioned wrapper; `before`/`after` follow the text direction.
6
+ */
7
+ export declare const InlineDropIndicator: ({ position }: {
8
+ position: InlineDropPosition | null;
9
+ }) => import("react").JSX.Element | null;
10
+ //#endregion
@@ -0,0 +1,13 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/inline-drop-indicator.tsx
4
+ /**
5
+ * The bar beside a dragged-over item in a horizontal row. Render it inside
6
+ * the item's positioned wrapper; `before`/`after` follow the text direction.
7
+ */
8
+ const InlineDropIndicator = ({ position }) => position === null ? null : /* @__PURE__ */ jsx("span", {
9
+ "aria-hidden": "true",
10
+ className: cn("bg-primary pointer-events-none absolute inset-y-1 z-20 w-0.5 rounded-full", position === "before" ? "start-0" : "end-0")
11
+ });
12
+ //#endregion
13
+ export { InlineDropIndicator };
@@ -0,0 +1,12 @@
1
+ import { ComponentProps } from "react";
2
+ //#region src/components/inline-rename.d.ts
3
+ type InlineRenameInputProps = Omit<ComponentProps<"input">, "value" | "onChange" | "onKeyDown" | "size"> & {
4
+ value: string;
5
+ onValueChange: (value: string) => void;
6
+ onCommit: () => void;
7
+ onCancel: () => void;
8
+ /** Fill the parent's row instead of sizing to the text. */
9
+ fill?: boolean;
10
+ };
11
+ export declare const InlineRenameInput: ({ value, onValueChange, onCommit, onCancel, onBlur, className, ref, dir, fill, ...props }: InlineRenameInputProps) => import("react").JSX.Element;
12
+ //#endregion
@@ -0,0 +1,75 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { contentDir, isStructuredInputType } from "../hooks/use-content-dir.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useCallback, useImperativeHandle, useRef } from "react";
5
+ import { panic } from "better-result";
6
+ //#region src/components/inline-rename.tsx
7
+ /** Content-sized rename field; the title's parent owns its typography. */
8
+ const mirrorTextFor = (value, placeholder) => {
9
+ if (value !== "") return value;
10
+ if (placeholder !== void 0 && placeholder !== "") return placeholder;
11
+ return "​";
12
+ };
13
+ const InlineRenameInput = ({ value, onValueChange, onCommit, onCancel, onBlur, className, ref, dir, fill = false, ...props }) => {
14
+ const finishedValue = useRef(null);
15
+ const inputRef = useRef(null);
16
+ useImperativeHandle(ref, () => {
17
+ if (!inputRef.current) panic("Inline rename input must be mounted before exposing its ref");
18
+ return inputRef.current;
19
+ }, []);
20
+ const focusInput = useCallback((input) => {
21
+ inputRef.current = input;
22
+ if (!input) return;
23
+ input.focus();
24
+ input.select();
25
+ }, []);
26
+ const mirrorText = mirrorTextFor(value, props.placeholder);
27
+ return /* @__PURE__ */ jsxs("span", {
28
+ className: cn(className),
29
+ "data-fill": fill ? "" : void 0,
30
+ "data-slot": "inline-rename",
31
+ children: [/* @__PURE__ */ jsx("span", {
32
+ "aria-hidden": "true",
33
+ "data-slot": "inline-rename-mirror",
34
+ children: mirrorText
35
+ }), /* @__PURE__ */ jsx("input", {
36
+ ...props,
37
+ "data-slot": "inline-rename-input",
38
+ dir: dir ?? (isStructuredInputType(props.type) ? "ltr" : contentDir(value)),
39
+ size: void 0,
40
+ onBlur: (event) => {
41
+ if (finishedValue.current === value) {
42
+ event.stopPropagation();
43
+ return;
44
+ }
45
+ if (onBlur) {
46
+ onBlur(event);
47
+ return;
48
+ }
49
+ finishedValue.current = value;
50
+ onCommit();
51
+ },
52
+ onChange: (event) => {
53
+ finishedValue.current = null;
54
+ onValueChange(event.currentTarget.value);
55
+ },
56
+ onClick: (event) => event.stopPropagation(),
57
+ onKeyDown: (event) => {
58
+ event.stopPropagation();
59
+ if (event.nativeEvent.isComposing) return;
60
+ if (event.key !== "Enter" && event.key !== "Escape") return;
61
+ event.preventDefault();
62
+ finishedValue.current = value;
63
+ if (event.key === "Escape") {
64
+ onCancel();
65
+ return;
66
+ }
67
+ onCommit();
68
+ },
69
+ ref: focusInput,
70
+ value
71
+ })]
72
+ });
73
+ };
74
+ //#endregion
75
+ export { InlineRenameInput };
@@ -5,7 +5,7 @@ import { VariantProps } from "class-variance-authority";
5
5
  //#region src/components/input-group.d.ts
6
6
  declare const InputGroup: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
7
7
  declare const inputGroupAddonVariants: (props?: ({
8
- align?: "inline-end" | "inline-start" | "block-start" | "block-end" | null | undefined;
8
+ align?: "inline-end" | "inline-start" | "block-end" | "block-start" | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
10
  declare const InputGroupAddon: ({ className, align, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) => React$1.JSX.Element;
11
11
  declare const InputGroupText: ({ className, ...props }: React$1.ComponentProps<"span">) => React$1.JSX.Element;
@@ -5,7 +5,7 @@ import { ComponentProps } from "react";
5
5
  * (`sm`) next to a control that is busy, `md` in a row, `lg` for a region.
6
6
  * Content with a known shape gets a `Skeleton` instead, never a loader.
7
7
  */
8
- declare const Loader: ({ label, size, className, ...props }: LoaderProps) => import("react").JSX.Element;
8
+ declare const Loader: ({ label, variant, size, className, ...props }: LoaderProps) => import("react").JSX.Element;
9
9
  /**
10
10
  * A region whose content has no shape yet (a job running, a first fetch): the
11
11
  * mark, one line saying what is happening, and at most one line of detail.
@@ -18,11 +18,17 @@ declare const LOADER_SIZE: {
18
18
  readonly lg: "size-8";
19
19
  };
20
20
  type LoaderSize = keyof typeof LOADER_SIZE;
21
- type LoaderProps = Omit<ComponentProps<"span">, "children"> & {
21
+ type LoaderProps = Omit<ComponentProps<"span">, "children" | "role" | "aria-label" | "aria-hidden" | "aria-busy"> & {
22
+ size?: LoaderSize;
23
+ } & ({
24
+ variant?: "status";
22
25
  /** What is pending, for assistive technology ("Loading matters"). */
23
26
  label: string;
24
- size?: LoaderSize;
25
- };
27
+ } | {
28
+ /** The surrounding control or text announces the pending state. */
29
+ variant: "decorative";
30
+ label?: never;
31
+ });
26
32
  type LoaderStateProps = {
27
33
  /** What is happening, shown as the title and announced. */
28
34
  label: string;
@@ -7,14 +7,15 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  * (`sm`) next to a control that is busy, `md` in a row, `lg` for a region.
8
8
  * Content with a known shape gets a `Skeleton` instead, never a loader.
9
9
  */
10
- const Loader = ({ label, size = "md", className, ...props }) => /* @__PURE__ */ jsx("span", {
11
- "aria-busy": "true",
12
- "aria-label": label,
13
- className: cn("inline-flex shrink-0 items-center justify-center", className),
10
+ const Loader = ({ label, variant = "status", size = "md", className, ...props }) => /* @__PURE__ */ jsx("span", {
11
+ className: cn("inline-flex shrink-0 items-center justify-center", LOADER_SIZE[size], className),
14
12
  "data-slot": "loader",
15
- role: "status",
16
13
  ...props,
17
- children: /* @__PURE__ */ jsx(LoaderMark, { size })
14
+ "aria-busy": variant === "status" ? true : void 0,
15
+ "aria-hidden": variant === "decorative" ? true : void 0,
16
+ "aria-label": label,
17
+ role: variant === "status" ? "status" : void 0,
18
+ children: /* @__PURE__ */ jsx(LoaderMark, {})
18
19
  });
19
20
  /**
20
21
  * A region whose content has no shape yet (a job running, a first fetch): the
@@ -26,7 +27,10 @@ const LoaderState = ({ label, detail, hint, className }) => /* @__PURE__ */ jsxs
26
27
  className: cn("flex h-full flex-col items-center justify-center gap-3 px-6 text-center", className),
27
28
  "data-slot": "loader-state",
28
29
  role: "status",
29
- children: [/* @__PURE__ */ jsx(LoaderMark, { size: "lg" }), /* @__PURE__ */ jsxs("div", {
30
+ children: [/* @__PURE__ */ jsx("span", {
31
+ className: LOADER_SIZE.lg,
32
+ children: /* @__PURE__ */ jsx(LoaderMark, {})
33
+ }), /* @__PURE__ */ jsxs("div", {
30
34
  className: "space-y-1",
31
35
  children: [
32
36
  /* @__PURE__ */ jsx("p", {
@@ -49,6 +53,6 @@ const LOADER_SIZE = {
49
53
  md: "size-6",
50
54
  lg: "size-8"
51
55
  };
52
- const LoaderMark = ({ size }) => /* @__PURE__ */ jsx(StellaMark, { className: cn("animate-loader text-muted-foreground motion-reduce:animate-none motion-reduce:opacity-70", LOADER_SIZE[size]) });
56
+ const LoaderMark = () => /* @__PURE__ */ jsx(StellaMark, { className: "animate-loader text-muted-foreground size-full motion-reduce:animate-none motion-reduce:opacity-70" });
53
57
  //#endregion
54
58
  export { Loader, LoaderState };
@@ -33,7 +33,7 @@ declare function useSidebarLayout(): SidebarLayout;
33
33
  * left for the content column. Mobile reports 0: there the sidebar is an
34
34
  * overlay sheet and occupies no layout width.
35
35
  */
36
- declare function useSidebarInlineSize(): 0 | 256 | 48;
36
+ declare function useSidebarInlineSize(): 0 | 48 | 256;
37
37
  declare const SidebarProvider: ({ defaultOpen, forceCollapsed, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }: React.ComponentProps<"div"> & {
38
38
  defaultOpen?: boolean;
39
39
  forceCollapsed?: boolean;
@@ -0,0 +1,35 @@
1
+ import { Button } from "./button.js";
2
+ import { DropdownMenu as Menu } from "./menu.js";
3
+ import { Popover } from "./popover.js";
4
+ import { ComponentProps, ReactNode } from "react";
5
+ //#region src/components/split-button.d.ts
6
+ type SplitButtonSharedProps = {
7
+ menuLabel: string;
8
+ primaryDescriptionId?: string;
9
+ menuDescriptionId?: string;
10
+ onPrimaryClick: NonNullable<ComponentProps<typeof Button>["onClick"]>;
11
+ menu: ReactNode;
12
+ primaryDisabled?: boolean;
13
+ menuDisabled?: boolean;
14
+ size?: "sm" | "md";
15
+ className?: string;
16
+ };
17
+ type SplitButtonPrimary = {
18
+ children: ReactNode;
19
+ primaryLabel: string;
20
+ } | {
21
+ children: string;
22
+ primaryLabel?: never;
23
+ };
24
+ type SplitButtonProps = SplitButtonSharedProps & SplitButtonPrimary & ({
25
+ surface?: "menu";
26
+ open?: ComponentProps<typeof Menu>["open"];
27
+ onOpenChange?: ComponentProps<typeof Menu>["onOpenChange"];
28
+ } | {
29
+ surface: "popover";
30
+ open?: ComponentProps<typeof Popover>["open"];
31
+ onOpenChange?: ComponentProps<typeof Popover>["onOpenChange"];
32
+ });
33
+ /** Two independent actions share quiet chrome; only the chevron opens the secondary surface. */
34
+ export declare const SplitButton: (props: SplitButtonProps) => import("react").JSX.Element;
35
+ //#endregion