@stll/ui 0.0.1-placeholder.0 → 0.3.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 (149) hide show
  1. package/README.md +84 -0
  2. package/dist/components/accordion.d.ts +8 -0
  3. package/dist/components/accordion.js +38 -0
  4. package/dist/components/alert-dialog.d.ts +32 -0
  5. package/dist/components/alert-dialog.js +75 -0
  6. package/dist/components/avatar.d.ts +7 -0
  7. package/dist/components/avatar.js +22 -0
  8. package/dist/components/bidi-text.d.ts +24 -0
  9. package/dist/components/bidi-text.js +38 -0
  10. package/dist/components/brand-icons.d.ts +7 -0
  11. package/dist/components/brand-icons.js +18 -0
  12. package/dist/components/breadcrumb.d.ts +12 -0
  13. package/dist/components/breadcrumb.js +61 -0
  14. package/dist/components/button-variants.d.ts +17 -0
  15. package/dist/components/button-variants.js +43 -0
  16. package/dist/components/button.d.ts +20 -0
  17. package/dist/components/button.js +53 -0
  18. package/dist/components/checkbox.d.ts +5 -0
  19. package/dist/components/checkbox.js +44 -0
  20. package/dist/components/color-picker.d.ts +47 -0
  21. package/dist/components/color-picker.js +249 -0
  22. package/dist/components/combobox.d.ts +40 -0
  23. package/dist/components/combobox.js +208 -0
  24. package/dist/components/command.d.ts +34 -0
  25. package/dist/components/command.js +100 -0
  26. package/dist/components/date-picker-popover.d.ts +29 -0
  27. package/dist/components/date-picker-popover.js +548 -0
  28. package/dist/components/date-picker-popover.logic.d.ts +14 -0
  29. package/dist/components/date-picker-popover.logic.js +26 -0
  30. package/dist/components/destructive-action-confirmation.d.ts +21 -0
  31. package/dist/components/destructive-action-confirmation.js +60 -0
  32. package/dist/components/destructive-confirm-dialog.d.ts +17 -0
  33. package/dist/components/destructive-confirm-dialog.js +49 -0
  34. package/dist/components/dialog.d.ts +37 -0
  35. package/dist/components/dialog.js +92 -0
  36. package/dist/components/directional-icon.d.ts +30 -0
  37. package/dist/components/directional-icon.js +12 -0
  38. package/dist/components/field.d.ts +11 -0
  39. package/dist/components/field.js +34 -0
  40. package/dist/components/form.d.ts +5 -0
  41. package/dist/components/form.js +12 -0
  42. package/dist/components/frame.d.ts +10 -0
  43. package/dist/components/frame.js +35 -0
  44. package/dist/components/hex-color-picker.d.ts +11 -0
  45. package/dist/components/hex-color-picker.js +366 -0
  46. package/dist/components/input-group.d.ts +15 -0
  47. package/dist/components/input-group.js +53 -0
  48. package/dist/components/input-otp.d.ts +13 -0
  49. package/dist/components/input-otp.js +39 -0
  50. package/dist/components/input.d.ts +12 -0
  51. package/dist/components/input.js +46 -0
  52. package/dist/components/label.d.ts +5 -0
  53. package/dist/components/label.js +18 -0
  54. package/dist/components/menu.d.ts +44 -0
  55. package/dist/components/menu.js +152 -0
  56. package/dist/components/outline-rail.d.ts +43 -0
  57. package/dist/components/outline-rail.js +416 -0
  58. package/dist/components/pagination.d.ts +17 -0
  59. package/dist/components/pagination.js +78 -0
  60. package/dist/components/popover.d.ts +34 -0
  61. package/dist/components/popover.js +67 -0
  62. package/dist/components/preview-card.d.ts +12 -0
  63. package/dist/components/preview-card.js +25 -0
  64. package/dist/components/preview-pane.d.ts +23 -0
  65. package/dist/components/preview-pane.js +33 -0
  66. package/dist/components/scroll-area.d.ts +19 -0
  67. package/dist/components/scroll-area.js +38 -0
  68. package/dist/components/scroll-to-top.d.ts +12 -0
  69. package/dist/components/scroll-to-top.js +43 -0
  70. package/dist/components/secret-input.d.ts +9 -0
  71. package/dist/components/secret-input.js +38 -0
  72. package/dist/components/segmented-icon-toggle.d.ts +22 -0
  73. package/dist/components/segmented-icon-toggle.js +30 -0
  74. package/dist/components/select.d.ts +25 -0
  75. package/dist/components/select.js +196 -0
  76. package/dist/components/separator.d.ts +16 -0
  77. package/dist/components/separator.js +26 -0
  78. package/dist/components/sheet.d.ts +29 -0
  79. package/dist/components/sheet.js +87 -0
  80. package/dist/components/skeleton.d.ts +4 -0
  81. package/dist/components/skeleton.js +10 -0
  82. package/dist/components/stella-mark.d.ts +5 -0
  83. package/dist/components/stella-mark.js +12 -0
  84. package/dist/components/stella-wordmark.d.ts +5 -0
  85. package/dist/components/stella-wordmark.js +21 -0
  86. package/dist/components/table.d.ts +20 -0
  87. package/dist/components/table.js +80 -0
  88. package/dist/components/tabs.d.ts +11 -0
  89. package/dist/components/tabs.js +46 -0
  90. package/dist/components/textarea.d.ts +9 -0
  91. package/dist/components/textarea.js +31 -0
  92. package/dist/components/toast.d.ts +60 -0
  93. package/dist/components/toast.js +261 -0
  94. package/dist/components/tooltip-trigger-helper.d.ts +11 -0
  95. package/dist/components/tooltip-trigger-helper.js +14 -0
  96. package/dist/components/tooltip.d.ts +15 -0
  97. package/dist/components/tooltip.js +32 -0
  98. package/dist/data-table/index.d.ts +2 -0
  99. package/dist/data-table/index.js +2 -0
  100. package/dist/data-table/schema.d.ts +70 -0
  101. package/dist/data-table/schema.js +40 -0
  102. package/dist/hooks/use-contained-handler.d.ts +55 -0
  103. package/dist/hooks/use-contained-handler.js +54 -0
  104. package/dist/hooks/use-content-dir.d.ts +48 -0
  105. package/dist/hooks/use-content-dir.js +45 -0
  106. package/dist/hooks/use-mobile.d.ts +4 -0
  107. package/dist/hooks/use-mobile.js +16 -0
  108. package/dist/hooks/use-viewport-width.d.ts +5 -0
  109. package/dist/hooks/use-viewport-width.js +14 -0
  110. package/dist/index.d.ts +67 -0
  111. package/dist/index.js +65 -0
  112. package/dist/inspector/chrome.d.ts +72 -0
  113. package/dist/inspector/chrome.js +153 -0
  114. package/dist/inspector/dock.d.ts +48 -0
  115. package/dist/inspector/dock.js +36 -0
  116. package/dist/inspector/index.d.ts +6 -0
  117. package/dist/inspector/index.js +6 -0
  118. package/dist/inspector/layout-tokens.d.ts +28 -0
  119. package/dist/inspector/layout-tokens.js +28 -0
  120. package/dist/inspector/pane-width.d.ts +64 -0
  121. package/dist/inspector/pane-width.js +53 -0
  122. package/dist/inspector/use-pane-width.d.ts +99 -0
  123. package/dist/inspector/use-pane-width.js +149 -0
  124. package/dist/kanban/card-properties.d.ts +27 -0
  125. package/dist/kanban/card-properties.js +19 -0
  126. package/dist/kanban/card-shell.d.ts +32 -0
  127. package/dist/kanban/card-shell.js +46 -0
  128. package/dist/kanban/column-header.d.ts +23 -0
  129. package/dist/kanban/column-header.js +24 -0
  130. package/dist/kanban/grouping.d.ts +84 -0
  131. package/dist/kanban/grouping.js +76 -0
  132. package/dist/kanban/index.d.ts +5 -0
  133. package/dist/kanban/index.js +5 -0
  134. package/dist/lib/button-disposition.d.ts +63 -0
  135. package/dist/lib/button-disposition.js +52 -0
  136. package/dist/lib/option-color.d.ts +28 -0
  137. package/dist/lib/option-color.js +59 -0
  138. package/dist/lib/overlay-layer.d.ts +22 -0
  139. package/dist/lib/overlay-layer.js +21 -0
  140. package/dist/lib/tab-order.d.ts +15 -0
  141. package/dist/lib/tab-order.js +15 -0
  142. package/dist/lib/tooltip-content.d.ts +13 -0
  143. package/dist/lib/tooltip-content.js +12 -0
  144. package/dist/lib/utils.d.ts +20 -0
  145. package/dist/lib/utils.js +41 -0
  146. package/dist/lib/week.d.ts +25 -0
  147. package/dist/lib/week.js +40 -0
  148. package/dist/styles/theme.css +1209 -0
  149. package/package.json +518 -2
@@ -0,0 +1,23 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/kanban/column-header.d.ts
3
+ type KanbanColumnHeaderProps = {
4
+ /** Colour swatch, or the control that changes it. */
5
+ swatch?: ReactNode;
6
+ /** The column name, or the editor that has taken its place. */
7
+ title: ReactNode;
8
+ /** Short text after the name, such as the card count the caller formatted. */
9
+ meta?: ReactNode;
10
+ /** Column calculation, rendered after the count. */
11
+ calculation?: ReactNode;
12
+ /** Drag affordance, revealed on hover over the column. */
13
+ dragHandle?: ReactNode;
14
+ /** Column menu. */
15
+ actions?: ReactNode;
16
+ };
17
+ /**
18
+ * The column header row: one rhythm for the swatch, the name, the count, the
19
+ * calculation, and the column's controls, so every board's header lines up.
20
+ */
21
+ declare const KanbanColumnHeader: ({ swatch, title, meta, calculation, dragHandle, actions }: KanbanColumnHeaderProps) => import("react").JSX.Element;
22
+ //#endregion
23
+ export { KanbanColumnHeader, KanbanColumnHeaderProps };
@@ -0,0 +1,24 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/kanban/column-header.tsx
3
+ /**
4
+ * The column header row: one rhythm for the swatch, the name, the count, the
5
+ * calculation, and the column's controls, so every board's header lines up.
6
+ */
7
+ const KanbanColumnHeader = ({ swatch, title, meta, calculation, dragHandle, actions }) => /* @__PURE__ */ jsxs("div", {
8
+ className: "flex items-center gap-2 px-3 py-2",
9
+ children: [
10
+ swatch,
11
+ /* @__PURE__ */ jsxs("span", {
12
+ className: "flex min-w-0 flex-1 items-center gap-1.5 truncate",
13
+ children: [title, meta !== void 0 && /* @__PURE__ */ jsx("span", {
14
+ className: "text-muted-foreground text-xs",
15
+ children: meta
16
+ })]
17
+ }),
18
+ calculation,
19
+ dragHandle,
20
+ actions
21
+ ]
22
+ });
23
+ //#endregion
24
+ export { KanbanColumnHeader };
@@ -0,0 +1,84 @@
1
+ import { OptionColor } from "../lib/option-color.js";
2
+ //#region src/kanban/grouping.d.ts
3
+ /** One column, before the uncategorized bucket is appended. */
4
+ type KanbanGroupOption = {
5
+ value: string;
6
+ label: string;
7
+ color?: string | undefined;
8
+ colorBg?: string | undefined;
9
+ optionColor?: OptionColor | undefined;
10
+ };
11
+ /** A column, including the uncategorized bucket (`value: null`). */
12
+ type KanbanGroup = {
13
+ value: string | null;
14
+ label: string;
15
+ color?: string | undefined;
16
+ colorBg?: string | undefined;
17
+ optionColor?: OptionColor | undefined;
18
+ };
19
+ /**
20
+ * A column source that is not a schema property, addressed by a reserved id.
21
+ *
22
+ * The board's columns are the caller's `options`, in the caller's order, so a
23
+ * built-in grouping is a data declaration rather than a branch this module has
24
+ * to know about.
25
+ */
26
+ type KanbanBuiltInGroup<TRow> = {
27
+ /** Reserved group id, distinct from any property id. */
28
+ id: string;
29
+ /** Ordered columns, without the uncategorized bucket. */
30
+ options: readonly KanbanGroupOption[];
31
+ /**
32
+ * Narrows the board to the rows this grouping can place. Omit when every row
33
+ * belongs on the board.
34
+ */
35
+ selectRows?: ((rows: readonly TRow[]) => TRow[]) | undefined;
36
+ };
37
+ /**
38
+ * Everything the grouping needs to know about a board.
39
+ *
40
+ * `getPropertyOptions` returns `null` for a property that cannot carry columns,
41
+ * which keeps the decision — and the property model it depends on — with the
42
+ * caller.
43
+ */
44
+ type KanbanSchema<TRow, TProperty> = {
45
+ builtInGroups: readonly KanbanBuiltInGroup<TRow>[];
46
+ properties: readonly TProperty[];
47
+ getPropertyId: (property: TProperty) => string;
48
+ getPropertyOptions: (property: TProperty) => readonly KanbanGroupOption[] | null;
49
+ };
50
+ type KanbanGrouping<TRow, TProperty> = {
51
+ type: "none";
52
+ } | {
53
+ type: "built-in";
54
+ propertyId: string;
55
+ group: KanbanBuiltInGroup<TRow>;
56
+ } | {
57
+ type: "property";
58
+ propertyId: string;
59
+ property: TProperty;
60
+ options: readonly KanbanGroupOption[];
61
+ };
62
+ type ResolveKanbanGroupingParams<TRow, TProperty> = {
63
+ /** The group-by id the view carries; empty means no grouping. */
64
+ groupBy: string;
65
+ schema: KanbanSchema<TRow, TProperty>;
66
+ };
67
+ /** Resolve a stored group-by id against a schema. */
68
+ declare const resolveKanbanGrouping: <TRow, TProperty>({ groupBy, schema }: ResolveKanbanGroupingParams<TRow, TProperty>) => KanbanGrouping<TRow, TProperty>;
69
+ declare const getKanbanGroupingPropertyId: <TRow, TProperty>(grouping: KanbanGrouping<TRow, TProperty>) => string | null;
70
+ /**
71
+ * A grouping with no columns is not a board. A built-in grouping declares its
72
+ * columns up front, so an empty declaration is the signal that the board cannot
73
+ * be drawn; a property grouping always draws, even when the property has no
74
+ * options yet, because rows still land in the uncategorized bucket.
75
+ */
76
+ declare const isKanbanGroupingRenderable: <TRow, TProperty>(grouping: KanbanGrouping<TRow, TProperty>) => boolean;
77
+ /** The rows a grouping can place on the board, in their incoming order. */
78
+ declare const selectKanbanRows: <TRow, TProperty>(rows: readonly TRow[], grouping: KanbanGrouping<TRow, TProperty>) => TRow[];
79
+ /** The static column options for a grouping, excluding uncategorized. */
80
+ declare const resolveKanbanGroupOptions: <TRow, TProperty>(grouping: KanbanGrouping<TRow, TProperty>) => readonly KanbanGroupOption[];
81
+ /** Append the uncategorized bucket (null value) after the options. */
82
+ declare const getKanbanGroups: (options: readonly KanbanGroupOption[], uncategorizedLabel: string) => KanbanGroup[];
83
+ //#endregion
84
+ export { KanbanBuiltInGroup, KanbanGroup, KanbanGroupOption, KanbanGrouping, KanbanSchema, ResolveKanbanGroupingParams, getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanRows };
@@ -0,0 +1,76 @@
1
+ //#region src/kanban/grouping.ts
2
+ /** Resolve a stored group-by id against a schema. */
3
+ const resolveKanbanGrouping = ({ groupBy, schema }) => {
4
+ if (groupBy === "") return { type: "none" };
5
+ const builtIn = schema.builtInGroups.find((group) => group.id === groupBy);
6
+ if (builtIn !== void 0) return {
7
+ type: "built-in",
8
+ propertyId: groupBy,
9
+ group: builtIn
10
+ };
11
+ const property = schema.properties.find((candidate) => schema.getPropertyId(candidate) === groupBy);
12
+ if (property === void 0) return { type: "none" };
13
+ return {
14
+ type: "property",
15
+ propertyId: groupBy,
16
+ property,
17
+ options: schema.getPropertyOptions(property) ?? []
18
+ };
19
+ };
20
+ const getKanbanGroupingPropertyId = (grouping) => {
21
+ switch (grouping.type) {
22
+ case "none": return null;
23
+ case "built-in":
24
+ case "property": return grouping.propertyId;
25
+ default: return grouping;
26
+ }
27
+ };
28
+ /**
29
+ * A grouping with no columns is not a board. A built-in grouping declares its
30
+ * columns up front, so an empty declaration is the signal that the board cannot
31
+ * be drawn; a property grouping always draws, even when the property has no
32
+ * options yet, because rows still land in the uncategorized bucket.
33
+ */
34
+ const isKanbanGroupingRenderable = (grouping) => {
35
+ switch (grouping.type) {
36
+ case "none": return false;
37
+ case "built-in": return grouping.group.options.length > 0;
38
+ case "property": return true;
39
+ default: return grouping;
40
+ }
41
+ };
42
+ /** The rows a grouping can place on the board, in their incoming order. */
43
+ const selectKanbanRows = (rows, grouping) => {
44
+ switch (grouping.type) {
45
+ case "none": return [];
46
+ case "built-in": return grouping.group.selectRows ? grouping.group.selectRows(rows) : [...rows];
47
+ case "property": return [...rows];
48
+ default: return grouping;
49
+ }
50
+ };
51
+ /** The static column options for a grouping, excluding uncategorized. */
52
+ const resolveKanbanGroupOptions = (grouping) => {
53
+ switch (grouping.type) {
54
+ case "none": return [];
55
+ case "built-in": return grouping.group.options;
56
+ case "property": return grouping.options;
57
+ default: return grouping;
58
+ }
59
+ };
60
+ /** Append the uncategorized bucket (null value) after the options. */
61
+ const getKanbanGroups = (options, uncategorizedLabel) => {
62
+ const result = options.map((option) => ({
63
+ value: option.value,
64
+ label: option.label,
65
+ color: option.color,
66
+ colorBg: option.colorBg,
67
+ optionColor: option.optionColor
68
+ }));
69
+ result.push({
70
+ value: null,
71
+ label: uncategorizedLabel
72
+ });
73
+ return result;
74
+ };
75
+ //#endregion
76
+ export { getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanRows };
@@ -0,0 +1,5 @@
1
+ import { KanbanCardFieldSelection, selectKanbanCardFieldIds } from "./card-properties.js";
2
+ import { KanbanCardShell, KanbanCardShellProps } from "./card-shell.js";
3
+ import { KanbanColumnHeader, KanbanColumnHeaderProps } from "./column-header.js";
4
+ import { KanbanBuiltInGroup, KanbanGroup, KanbanGroupOption, KanbanGrouping, KanbanSchema, ResolveKanbanGroupingParams, getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanRows } from "./grouping.js";
5
+ export { type KanbanBuiltInGroup, type KanbanCardFieldSelection, KanbanCardShell, type KanbanCardShellProps, KanbanColumnHeader, type KanbanColumnHeaderProps, type KanbanGroup, type KanbanGroupOption, type KanbanGrouping, type KanbanSchema, type ResolveKanbanGroupingParams, getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows };
@@ -0,0 +1,5 @@
1
+ import { selectKanbanCardFieldIds } from "./card-properties.js";
2
+ import { KanbanCardShell } from "./card-shell.js";
3
+ import { KanbanColumnHeader } from "./column-header.js";
4
+ import { getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanRows } from "./grouping.js";
5
+ export { KanbanCardShell, KanbanColumnHeader, getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows };
@@ -0,0 +1,63 @@
1
+ import * as React$1 from "react";
2
+ //#region src/lib/button-disposition.d.ts
3
+ /**
4
+ * How a button expresses "unavailable".
5
+ *
6
+ * `buttonVariants` carries `disabled:pointer-events-none`, and the browser
7
+ * suppresses pointer and focus events on a natively disabled control anyway, so
8
+ * a disabled button never receives hover or focus. A tooltip attached to it can
9
+ * therefore never open, which is exactly when the user most needs to be told
10
+ * why the action is unavailable. When there is a tooltip to show, the button
11
+ * switches to the ARIA accessible-disabled pattern instead: it stays in the tab
12
+ * order and keeps receiving pointer events, while every activation path is
13
+ * blocked (see `blockDisabledActivation`).
14
+ *
15
+ * This mirrors Base UI's own `focusableWhenDisabled` disposition
16
+ * (`useFocusableWhenDisabled`): `aria-disabled` instead of `disabled`, focusable,
17
+ * and non-`Tab` key defaults suppressed.
18
+ */
19
+ declare const BUTTON_DISPOSITION: {
20
+ readonly enabled: "enabled";
21
+ /** Native `disabled`: out of the tab order, inert to pointer and keyboard. */
22
+ readonly nativeDisabled: "native-disabled";
23
+ /** `aria-disabled`: focusable and hoverable so its tooltip can still open. */
24
+ readonly accessibleDisabled: "accessible-disabled";
25
+ };
26
+ type ButtonDisposition = (typeof BUTTON_DISPOSITION)[keyof typeof BUTTON_DISPOSITION];
27
+ type ButtonDispositionInput = {
28
+ disabled: boolean | undefined;
29
+ loading: boolean | undefined;
30
+ /** Resolved tooltip content, i.e. what `renderTooltipTrigger` would mount. */
31
+ tooltip: React$1.ReactNode;
32
+ };
33
+ declare const resolveButtonDisposition: ({ disabled, loading, tooltip }: ButtonDispositionInput) => ButtonDisposition;
34
+ /**
35
+ * The parts of a React synthetic event these blockers touch.
36
+ *
37
+ * `preventBaseUIHandler` is added by Base UI's `mergeProps` to the rightmost
38
+ * handler in a merged chain; calling it stops the handlers merged to its left,
39
+ * which is how a caller's own `onClick` is suppressed.
40
+ */
41
+ type BlockableEvent = {
42
+ preventDefault: () => void;
43
+ preventBaseUIHandler?: (() => void) | undefined;
44
+ };
45
+ /**
46
+ * Swallow an activation on an `aria-disabled` control.
47
+ *
48
+ * `preventDefault()` is what keeps a `type="submit"` button from submitting its
49
+ * form: the browser's form-submission default runs off the click event, and
50
+ * implicit submission (Enter in a text field) dispatches a click at the form's
51
+ * default button, so both paths funnel through here.
52
+ */
53
+ declare const blockDisabledActivation: (event: BlockableEvent) => void;
54
+ /**
55
+ * The keyboard counterpart. `Tab` keeps its default so focus can leave the
56
+ * control; suppressing the default on `Enter`/`Space` stops the browser from
57
+ * synthesizing the click that would activate or submit.
58
+ */
59
+ declare const blockDisabledKeyActivation: (event: BlockableEvent & {
60
+ key: string;
61
+ }) => void;
62
+ //#endregion
63
+ export { BUTTON_DISPOSITION, ButtonDisposition, blockDisabledActivation, blockDisabledKeyActivation, resolveButtonDisposition };
@@ -0,0 +1,52 @@
1
+ import { hasTooltipContent } from "./tooltip-content.js";
2
+ //#region src/lib/button-disposition.ts
3
+ /**
4
+ * How a button expresses "unavailable".
5
+ *
6
+ * `buttonVariants` carries `disabled:pointer-events-none`, and the browser
7
+ * suppresses pointer and focus events on a natively disabled control anyway, so
8
+ * a disabled button never receives hover or focus. A tooltip attached to it can
9
+ * therefore never open, which is exactly when the user most needs to be told
10
+ * why the action is unavailable. When there is a tooltip to show, the button
11
+ * switches to the ARIA accessible-disabled pattern instead: it stays in the tab
12
+ * order and keeps receiving pointer events, while every activation path is
13
+ * blocked (see `blockDisabledActivation`).
14
+ *
15
+ * This mirrors Base UI's own `focusableWhenDisabled` disposition
16
+ * (`useFocusableWhenDisabled`): `aria-disabled` instead of `disabled`, focusable,
17
+ * and non-`Tab` key defaults suppressed.
18
+ */
19
+ const BUTTON_DISPOSITION = {
20
+ enabled: "enabled",
21
+ /** Native `disabled`: out of the tab order, inert to pointer and keyboard. */
22
+ nativeDisabled: "native-disabled",
23
+ /** `aria-disabled`: focusable and hoverable so its tooltip can still open. */
24
+ accessibleDisabled: "accessible-disabled"
25
+ };
26
+ const resolveButtonDisposition = ({ disabled, loading, tooltip }) => {
27
+ if (disabled !== true && loading !== true) return BUTTON_DISPOSITION.enabled;
28
+ return hasTooltipContent(tooltip) ? BUTTON_DISPOSITION.accessibleDisabled : BUTTON_DISPOSITION.nativeDisabled;
29
+ };
30
+ /**
31
+ * Swallow an activation on an `aria-disabled` control.
32
+ *
33
+ * `preventDefault()` is what keeps a `type="submit"` button from submitting its
34
+ * form: the browser's form-submission default runs off the click event, and
35
+ * implicit submission (Enter in a text field) dispatches a click at the form's
36
+ * default button, so both paths funnel through here.
37
+ */
38
+ const blockDisabledActivation = (event) => {
39
+ event.preventBaseUIHandler?.();
40
+ event.preventDefault();
41
+ };
42
+ /**
43
+ * The keyboard counterpart. `Tab` keeps its default so focus can leave the
44
+ * control; suppressing the default on `Enter`/`Space` stops the browser from
45
+ * synthesizing the click that would activate or submit.
46
+ */
47
+ const blockDisabledKeyActivation = (event) => {
48
+ event.preventBaseUIHandler?.();
49
+ if (event.key !== "Tab") event.preventDefault();
50
+ };
51
+ //#endregion
52
+ export { BUTTON_DISPOSITION, blockDisabledActivation, blockDisabledKeyActivation, resolveButtonDisposition };
@@ -0,0 +1,28 @@
1
+ //#region src/lib/option-color.d.ts
2
+ /**
3
+ * The kit's colour token for user-chosen option colours.
4
+ *
5
+ * A token is either one of the sixteen named presets, which resolve to the
6
+ * theme's `--option-*` custom properties, or a six-character hex string, which
7
+ * resolves to a `color-mix` against the current background and foreground. The
8
+ * indirection is what keeps a stored colour theme-aware: nothing persists a
9
+ * literal colour the palette cannot re-tint.
10
+ *
11
+ * This lives in the kit rather than beside a data model because the resolver is
12
+ * pure presentation: it maps a token to CSS, and knows nothing about what the
13
+ * token was chosen for.
14
+ */
15
+ /** Named preset or arbitrary 6-character hex colour (e.g. "FF0000"). */
16
+ type OptionColor = "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "gray" | (string & Record<never, never>);
17
+ type ColorVariants = {
18
+ background: string;
19
+ foreground: string;
20
+ color: string;
21
+ };
22
+ declare const emptyColor: ColorVariants;
23
+ /** Resolve any OptionColor (named or hex) to CSS color variants. */
24
+ declare const resolveOptionColor: (color: OptionColor) => ColorVariants;
25
+ /** The 16 named preset color keys. */
26
+ declare const optionColors: readonly OptionColor[];
27
+ //#endregion
28
+ export { ColorVariants, OptionColor, emptyColor, optionColors, resolveOptionColor };
@@ -0,0 +1,59 @@
1
+ //#region src/lib/option-color.ts
2
+ /** Named preset colors with CSS variable references. */
3
+ const NAMED_COLORS = Object.freeze([
4
+ "red",
5
+ "orange",
6
+ "amber",
7
+ "yellow",
8
+ "lime",
9
+ "green",
10
+ "emerald",
11
+ "teal",
12
+ "cyan",
13
+ "sky",
14
+ "blue",
15
+ "indigo",
16
+ "violet",
17
+ "purple",
18
+ "fuchsia",
19
+ "gray"
20
+ ]);
21
+ const optionVar = (name) => ({
22
+ background: `var(--option-${name}-bg)`,
23
+ foreground: `var(--option-${name}-fg)`,
24
+ color: `var(--option-${name})`
25
+ });
26
+ const hexVar = (hex) => ({
27
+ background: `color-mix(in srgb, #${hex} 12%, var(--background))`,
28
+ foreground: `color-mix(in srgb, #${hex} 50%, var(--foreground))`,
29
+ color: `#${hex}`
30
+ });
31
+ const namedColorsMap = {
32
+ red: optionVar("red"),
33
+ orange: optionVar("orange"),
34
+ amber: optionVar("amber"),
35
+ yellow: optionVar("yellow"),
36
+ lime: optionVar("lime"),
37
+ green: optionVar("green"),
38
+ emerald: optionVar("emerald"),
39
+ teal: optionVar("teal"),
40
+ cyan: optionVar("cyan"),
41
+ sky: optionVar("sky"),
42
+ blue: optionVar("blue"),
43
+ indigo: optionVar("indigo"),
44
+ violet: optionVar("violet"),
45
+ purple: optionVar("purple"),
46
+ fuchsia: optionVar("fuchsia"),
47
+ gray: optionVar("gray")
48
+ };
49
+ const emptyColor = optionVar("empty");
50
+ /** Resolve any OptionColor (named or hex) to CSS color variants. */
51
+ const resolveOptionColor = (color) => {
52
+ const namedColor = NAMED_COLORS.find((candidate) => candidate === color);
53
+ if (namedColor !== void 0) return namedColorsMap[namedColor];
54
+ return hexVar(color);
55
+ };
56
+ /** The 16 named preset color keys. */
57
+ const optionColors = NAMED_COLORS;
58
+ //#endregion
59
+ export { emptyColor, optionColors, resolveOptionColor };
@@ -0,0 +1,22 @@
1
+ //#region src/lib/overlay-layer.d.ts
2
+ declare const OVERLAY_LAYER_CLASS_NAMES: {
3
+ /** Docked composer, suggestion chips, and other in-pane floating chrome. */
4
+ readonly chrome: "z-[80]";
5
+ /** Dialogs, sheets, popovers, tooltips: above chrome, below toasts. */
6
+ readonly default: "z-[85]";
7
+ /** Search sits a hair above a plain dialog so its own popup wins. */
8
+ readonly search: "z-[86]";
9
+ /**
10
+ * Transient popups — menu, select, combobox, context menus. Above everything
11
+ * including the toast viewport (z-[90]), because any of them can be opened
12
+ * *from* a dialog, a popover or a toast action and must cover its opener.
13
+ * They sat at z-50 alongside dialogs, so which one won was decided by portal
14
+ * mount order: a select inside a filter popover rendered behind it.
15
+ */
16
+ readonly popup: "z-[100]";
17
+ /** Historical alias for `popup`; Search's own menus use the same band. */
18
+ readonly "search-child": "z-[100]";
19
+ };
20
+ type OverlayLayer = keyof typeof OVERLAY_LAYER_CLASS_NAMES;
21
+ //#endregion
22
+ export { OVERLAY_LAYER_CLASS_NAMES, OverlayLayer };
@@ -0,0 +1,21 @@
1
+ //#region src/lib/overlay-layer.ts
2
+ const OVERLAY_LAYER_CLASS_NAMES = {
3
+ /** Docked composer, suggestion chips, and other in-pane floating chrome. */
4
+ chrome: "z-[80]",
5
+ /** Dialogs, sheets, popovers, tooltips: above chrome, below toasts. */
6
+ default: "z-[85]",
7
+ /** Search sits a hair above a plain dialog so its own popup wins. */
8
+ search: "z-[86]",
9
+ /**
10
+ * Transient popups — menu, select, combobox, context menus. Above everything
11
+ * including the toast viewport (z-[90]), because any of them can be opened
12
+ * *from* a dialog, a popover or a toast action and must cover its opener.
13
+ * They sat at z-50 alongside dialogs, so which one won was decided by portal
14
+ * mount order: a select inside a filter popover rendered behind it.
15
+ */
16
+ popup: "z-[100]",
17
+ /** Historical alias for `popup`; Search's own menus use the same band. */
18
+ "search-child": "z-[100]"
19
+ };
20
+ //#endregion
21
+ export { OVERLAY_LAYER_CLASS_NAMES };
@@ -0,0 +1,15 @@
1
+ //#region src/lib/tab-order.d.ts
2
+ /**
3
+ * Whether a committed tab list holds different elements, or the same elements
4
+ * in a different order, than the previous commit.
5
+ *
6
+ * Base UI measures the tabs indicator during render, so it reads the DOM as it
7
+ * stood before the commit. Its correction is a `ResizeObserver`, which catches
8
+ * an insertion or a removal (the list's own width changes) but not a reorder:
9
+ * there the tabs swap places without anything changing size. Detecting that is
10
+ * the whole of the workaround, so compare identity rather than count, because
11
+ * React moves the same nodes when it reorders keyed children.
12
+ */
13
+ declare const hasTabOrderChanged: <T>(previous: readonly T[], next: readonly T[]) => boolean;
14
+ //#endregion
15
+ export { hasTabOrderChanged };
@@ -0,0 +1,15 @@
1
+ //#region src/lib/tab-order.ts
2
+ /**
3
+ * Whether a committed tab list holds different elements, or the same elements
4
+ * in a different order, than the previous commit.
5
+ *
6
+ * Base UI measures the tabs indicator during render, so it reads the DOM as it
7
+ * stood before the commit. Its correction is a `ResizeObserver`, which catches
8
+ * an insertion or a removal (the list's own width changes) but not a reorder:
9
+ * there the tabs swap places without anything changing size. Detecting that is
10
+ * the whole of the workaround, so compare identity rather than count, because
11
+ * React moves the same nodes when it reorders keyed children.
12
+ */
13
+ const hasTabOrderChanged = (previous, next) => previous.length !== next.length || next.some((tab, index) => tab !== previous[index]);
14
+ //#endregion
15
+ export { hasTabOrderChanged };
@@ -0,0 +1,13 @@
1
+ import * as React$1 from "react";
2
+ //#region src/lib/tooltip-content.d.ts
3
+ /**
4
+ * Whether a tooltip value would actually render a popup.
5
+ *
6
+ * Single source of truth for "there is a tooltip": `renderTooltipTrigger`
7
+ * decides whether to mount one, and `resolveButtonDisposition` decides whether
8
+ * a disabled button has to stay hoverable so that popup can open. Two copies of
9
+ * this predicate would drift into a button that suppresses its own tooltip.
10
+ */
11
+ declare const hasTooltipContent: (content: React$1.ReactNode) => boolean;
12
+ //#endregion
13
+ export { hasTooltipContent };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/tooltip-content.ts
2
+ /**
3
+ * Whether a tooltip value would actually render a popup.
4
+ *
5
+ * Single source of truth for "there is a tooltip": `renderTooltipTrigger`
6
+ * decides whether to mount one, and `resolveButtonDisposition` decides whether
7
+ * a disabled button has to stay hoverable so that popup can open. Two copies of
8
+ * this predicate would drift into a button that suppresses its own tooltip.
9
+ */
10
+ const hasTooltipContent = (content) => content !== void 0 && content !== null && content !== "" && content !== false;
11
+ //#endregion
12
+ export { hasTooltipContent };
@@ -0,0 +1,20 @@
1
+ import { ClassValue } from "clsx";
2
+ import * as React$1 from "react";
3
+ //#region src/lib/utils.d.ts
4
+ declare function cn(...inputs: ClassValue[]): string;
5
+ /**
6
+ * Compose multiple refs into a single ref callback.
7
+ * Adapted from `@radix-ui/react-compose-refs` (MIT).
8
+ *
9
+ * Collects cleanup functions returned by React 19 ref
10
+ * callbacks and returns a combined cleanup so React can
11
+ * invoke it on unmount instead of re-calling with `null`.
12
+ *
13
+ * Use this rather than merging refs inline: forwarding a
14
+ * caller's return value verbatim makes React read whatever
15
+ * their callback happened to return as a cleanup, and warn
16
+ * when it is neither a function nor `undefined`.
17
+ */
18
+ declare const composeRefs: <T>(...refs: (React$1.Ref<T> | undefined)[]) => ((node: T | null) => (() => void) | undefined);
19
+ //#endregion
20
+ export { cn, composeRefs };
@@ -0,0 +1,41 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ //#region src/lib/utils.ts
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+ /**
8
+ * Compose multiple refs into a single ref callback.
9
+ * Adapted from `@radix-ui/react-compose-refs` (MIT).
10
+ *
11
+ * Collects cleanup functions returned by React 19 ref
12
+ * callbacks and returns a combined cleanup so React can
13
+ * invoke it on unmount instead of re-calling with `null`.
14
+ *
15
+ * Use this rather than merging refs inline: forwarding a
16
+ * caller's return value verbatim makes React read whatever
17
+ * their callback happened to return as a cleanup, and warn
18
+ * when it is neither a function nor `undefined`.
19
+ */
20
+ const composeRefs = (...refs) => (node) => {
21
+ const cleanups = [];
22
+ for (const ref of refs) if (typeof ref === "function") {
23
+ const cleanup = ref(node);
24
+ if (typeof cleanup === "function") cleanups.push(() => {
25
+ cleanup();
26
+ });
27
+ else if (node !== null) cleanups.push(() => {
28
+ ref(null);
29
+ });
30
+ } else if (ref !== void 0 && ref !== null) {
31
+ ref.current = node;
32
+ if (node !== null) cleanups.push(() => {
33
+ ref.current = null;
34
+ });
35
+ }
36
+ if (cleanups.length > 0) return () => {
37
+ for (const cleanup of cleanups) cleanup();
38
+ };
39
+ };
40
+ //#endregion
41
+ export { cn, composeRefs };
@@ -0,0 +1,25 @@
1
+ //#region src/lib/week.d.ts
2
+ /**
3
+ * Locale week data (first weekday, weekend) derived from CLDR via Intl.
4
+ * Shared so the app shell and the date picker resolve week starts identically.
5
+ */
6
+ /**
7
+ * Read CLDR week info for a locale, tolerating the legacy `weekInfo` accessor
8
+ * on engines that predate `getWeekInfo()`. Returns undefined when unavailable
9
+ * so callers can apply their own fallback.
10
+ */
11
+ declare const getLocaleWeekInfo: (locale: string) => Intl.WeekInfo | undefined;
12
+ /**
13
+ * First weekday as a `Date.getDay()` value (0 = Sunday … 6 = Saturday): Monday
14
+ * across most of Europe, Sunday in the US, Saturday across much of the Gulf.
15
+ * Falls back to Monday when the runtime lacks week info.
16
+ */
17
+ declare const getFirstWeekday: (locale: string) => number;
18
+ /**
19
+ * Weekend weekdays as `Date.getDay()` values (0 = Sunday … 6 = Saturday):
20
+ * Saturday/Sunday across the West, Friday/Saturday across much of the Gulf.
21
+ * Falls back to Saturday/Sunday when the runtime lacks week info.
22
+ */
23
+ declare const getWeekendDays: (locale: string) => ReadonlySet<number>;
24
+ //#endregion
25
+ export { getFirstWeekday, getLocaleWeekInfo, getWeekendDays };