gridsmith-ui 0.14.0 → 0.16.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.
- package/dist/charts.d.ts +20 -1
- package/dist/charts.js +65 -29
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +5 -5
- package/dist/{chunk-JSQPVRHQ.js → chunk-2U2X6E2X.js} +39 -16
- package/dist/chunk-2U2X6E2X.js.map +1 -0
- package/dist/{chunk-X4CNZFWC.js → chunk-4LDYR7QP.js} +7 -7
- package/dist/chunk-4LDYR7QP.js.map +1 -0
- package/dist/chunk-5XOJZQIP.js +427 -0
- package/dist/chunk-5XOJZQIP.js.map +1 -0
- package/dist/{chunk-PKLVP5ZB.js → chunk-7Q46JXSK.js} +5 -4
- package/dist/chunk-7Q46JXSK.js.map +1 -0
- package/dist/{chunk-IYD6ZDM2.js → chunk-EMCSARQO.js} +7 -6
- package/dist/chunk-EMCSARQO.js.map +1 -0
- package/dist/{chunk-CR2KF73R.js → chunk-OENJCGTJ.js} +3 -3
- package/dist/{chunk-CR2KF73R.js.map → chunk-OENJCGTJ.js.map} +1 -1
- package/dist/{chunk-U2JT37GJ.js → chunk-PVTGMCVR.js} +4 -4
- package/dist/{chunk-U2JT37GJ.js.map → chunk-PVTGMCVR.js.map} +1 -1
- package/dist/{chunk-QJBCZ7BR.js → chunk-XGSQOMEY.js} +4 -4
- package/dist/{chunk-QJBCZ7BR.js.map → chunk-XGSQOMEY.js.map} +1 -1
- package/dist/{chunk-M3AARGLI.js → chunk-Z63OSN64.js} +9 -8
- package/dist/chunk-Z63OSN64.js.map +1 -0
- package/dist/data-grid.js +4 -4
- package/dist/editor.js +32 -8
- package/dist/editor.js.map +1 -1
- package/dist/{feather-B65FI6CY.js → feather-2JFSFLXN.js} +3 -2
- package/dist/feather-2JFSFLXN.js.map +1 -0
- package/dist/{heroicons-DIR7PXCR.js → heroicons-4AQ4YBVG.js} +3 -2
- package/dist/heroicons-4AQ4YBVG.js.map +1 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +75 -6
- package/dist/index.js +1371 -603
- package/dist/index.js.map +1 -1
- package/dist/kanban.d.ts +11 -1
- package/dist/kanban.js +227 -115
- package/dist/kanban.js.map +1 -1
- package/dist/{lucide-JH2R3TFV.js → lucide-RZC2FEMS.js} +3 -2
- package/dist/lucide-RZC2FEMS.js.map +1 -0
- package/dist/{phosphor-HCVUMVT5.js → phosphor-CEN25FLM.js} +3 -2
- package/dist/phosphor-CEN25FLM.js.map +1 -0
- package/dist/resizable.d.ts +11 -1
- package/dist/resizable.js +81 -23
- package/dist/resizable.js.map +1 -1
- package/dist/roots.css +11 -8
- package/dist/{tabler-HHIXSM73.js → tabler-YXXC7Y6B.js} +3 -2
- package/dist/tabler-YXXC7Y6B.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-IYD6ZDM2.js.map +0 -1
- package/dist/chunk-JSQPVRHQ.js.map +0 -1
- package/dist/chunk-M3AARGLI.js.map +0 -1
- package/dist/chunk-NHQCMOAN.js +0 -137
- package/dist/chunk-NHQCMOAN.js.map +0 -1
- package/dist/chunk-PKLVP5ZB.js.map +0 -1
- package/dist/chunk-X4CNZFWC.js.map +0 -1
- package/dist/feather-B65FI6CY.js.map +0 -1
- package/dist/heroicons-DIR7PXCR.js.map +0 -1
- package/dist/lucide-JH2R3TFV.js.map +0 -1
- package/dist/phosphor-HCVUMVT5.js.map +0 -1
- package/dist/tabler-HHIXSM73.js.map +0 -1
package/dist/resizable.d.ts
CHANGED
|
@@ -54,6 +54,16 @@ interface SplitViewProps {
|
|
|
54
54
|
onDetailClose?: () => void;
|
|
55
55
|
/** "stack" (default): the detail stacks over the list below the collapse width. "route": the app navigates to the detail's own route instead, and this renders the list alone there. */
|
|
56
56
|
detailMode?: "stack" | "route";
|
|
57
|
+
/**
|
|
58
|
+
* "shown" (default): above the collapse width the detail pane is always there — an inbox's reading pane may show a
|
|
59
|
+
* placeholder. "hidden" (Sprint 25.7, C3): `detailOpen` decides at every width. Closed, the list takes the whole
|
|
60
|
+
* width; open, the pane appears beside it at the width the user last gave it, with a close control that calls
|
|
61
|
+
* `onDetailClose`. For boards, timelines and lists of records. The list stays mounted, so a board or a timeline keeps
|
|
62
|
+
* its scroll and zoom.
|
|
63
|
+
*/
|
|
64
|
+
closedDetail?: "shown" | "hidden";
|
|
65
|
+
/** The open pane's close control, named for a screen reader (closedDetail="hidden"). Default "Close". */
|
|
66
|
+
closeLabel?: string;
|
|
57
67
|
backLabel?: string;
|
|
58
68
|
/** Percent of the width the list takes; the divider is draggable. */
|
|
59
69
|
listWidth?: number;
|
|
@@ -80,6 +90,6 @@ interface SplitViewProps {
|
|
|
80
90
|
* The page owns the selection and assembles the contents; keyboard movement
|
|
81
91
|
* through the list is the list's job (StackedList selectedId).
|
|
82
92
|
*/
|
|
83
|
-
declare function SplitView({ list, detail, aside, asideTitle, detailOpen, onDetailClose, detailMode, backLabel, listWidth, listMinWidth, asideWidth, collapseBelow, asideCollapseBelow, bordered, frame, "aria-label": ariaLabel, className, }: SplitViewProps): react_jsx_runtime.JSX.Element;
|
|
93
|
+
declare function SplitView({ list, detail, aside, asideTitle, detailOpen, onDetailClose, detailMode, closedDetail, closeLabel, backLabel, listWidth, listMinWidth, asideWidth, collapseBelow, asideCollapseBelow, bordered, frame, "aria-label": ariaLabel, className, }: SplitViewProps): react_jsx_runtime.JSX.Element;
|
|
84
94
|
|
|
85
95
|
export { ResizableHandle, ResizablePanel, ResizablePanelGroup, SplitView, type SplitViewFrame, type SplitViewProps };
|
package/dist/resizable.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { useCompact, useAppShell, Button, Drawer } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useCompact, useAppShell, Button, Drawer } from './chunk-2U2X6E2X.js';
|
|
2
|
+
import { isTabStop, tabStops, rememberFocus } from './chunk-5XOJZQIP.js';
|
|
3
3
|
import { SplitPaneCtx } from './chunk-TKVTZWBN.js';
|
|
4
4
|
import { useBreakpoint } from './chunk-CRQ6KCRD.js';
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
5
|
+
import './chunk-EMCSARQO.js';
|
|
6
|
+
import './chunk-7Q46JXSK.js';
|
|
7
7
|
import './chunk-WH2FUFAC.js';
|
|
8
|
-
import { Icon } from './chunk-
|
|
8
|
+
import { Icon } from './chunk-4LDYR7QP.js';
|
|
9
9
|
import { cn } from './chunk-4KVFH5KC.js';
|
|
10
10
|
import { Group, Panel, Separator } from 'react-resizable-panels';
|
|
11
|
-
import { createContext, useContext, useRef, useState } from 'react';
|
|
11
|
+
import { createContext, useContext, useRef, useState, useLayoutEffect } from 'react';
|
|
12
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
13
|
|
|
14
14
|
var DirectionCtx = createContext("horizontal");
|
|
@@ -97,6 +97,8 @@ function SplitView({
|
|
|
97
97
|
detailOpen = false,
|
|
98
98
|
onDetailClose,
|
|
99
99
|
detailMode = "stack",
|
|
100
|
+
closedDetail = "shown",
|
|
101
|
+
closeLabel = "Close",
|
|
100
102
|
backLabel = "Back",
|
|
101
103
|
listWidth = 35,
|
|
102
104
|
listMinWidth = 25,
|
|
@@ -112,6 +114,9 @@ function SplitView({
|
|
|
112
114
|
const compact = useCompact(rootRef, collapseBelow);
|
|
113
115
|
const asideCollapsed = useCompact(rootRef, asideCollapseBelow);
|
|
114
116
|
const [asideWantsOpen, setAsideWantsOpen] = useState(false);
|
|
117
|
+
const hiding = closedDetail === "hidden";
|
|
118
|
+
const showAside = !!aside && (!hiding || detailOpen);
|
|
119
|
+
if (hiding && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);
|
|
115
120
|
const asideOpen = asideCollapsed && asideWantsOpen;
|
|
116
121
|
const region = ariaLabel ? { role: "region", "aria-label": ariaLabel } : {};
|
|
117
122
|
const isFill = frame === "fill";
|
|
@@ -123,28 +128,81 @@ function SplitView({
|
|
|
123
128
|
bordered && !isFill && "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden",
|
|
124
129
|
className
|
|
125
130
|
);
|
|
131
|
+
const [listPct, setListPct] = useState(listWidth);
|
|
132
|
+
const paneRef = useRef(null);
|
|
133
|
+
const shown = useRef(null);
|
|
134
|
+
const lastInList = useRef(null);
|
|
135
|
+
const wasOpen = useRef(false);
|
|
136
|
+
useLayoutEffect(() => {
|
|
137
|
+
if (!hiding) return;
|
|
138
|
+
const listPane = rootRef.current?.querySelector('[data-pane="list"]') ?? null;
|
|
139
|
+
if (detailOpen) {
|
|
140
|
+
wasOpen.current = true;
|
|
141
|
+
const selected = listPane?.querySelector('[aria-current="true"], [aria-selected="true"]');
|
|
142
|
+
const last = lastInList.current;
|
|
143
|
+
const item = selected && (isTabStop(selected) ? selected : tabStops(selected)[0]) || (last?.isConnected && listPane?.contains(last) ? last : null);
|
|
144
|
+
if (item && item !== shown.current?.el) shown.current = rememberFocus(item, { ignoreRoute: true });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!wasOpen.current) return;
|
|
148
|
+
wasOpen.current = false;
|
|
149
|
+
const r = shown.current;
|
|
150
|
+
shown.current = null;
|
|
151
|
+
const active = document.activeElement;
|
|
152
|
+
if (active && active !== document.body && active.isConnected) return;
|
|
153
|
+
const first = listPane ? tabStops(listPane)[0] : void 0;
|
|
154
|
+
if (!r?.restore(first)) first?.focus();
|
|
155
|
+
});
|
|
156
|
+
const onPaneKeyDown = (e) => {
|
|
157
|
+
if (e.key !== "Escape" || e.defaultPrevented || !onDetailClose) return;
|
|
158
|
+
const t = e.target;
|
|
159
|
+
if (!paneRef.current?.contains(t) || t.closest("input, textarea, select, [contenteditable='true']")) return;
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
onDetailClose();
|
|
162
|
+
};
|
|
126
163
|
if (compact) {
|
|
127
|
-
const
|
|
128
|
-
return /* @__PURE__ */ jsx("div", { ref: rootRef, "data-split-view":
|
|
164
|
+
const showDetail2 = detailMode === "stack" && detailOpen;
|
|
165
|
+
return /* @__PURE__ */ jsx("div", { ref: rootRef, "data-split-view": showDetail2 ? "detail" : "list", "data-frame": frame, ...region, className: cn(surface, "flex flex-col"), style: fillStyle, children: showDetail2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
129
166
|
/* @__PURE__ */ jsx("div", { className: "shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "arrow-left", size: 14 }), onClick: onDetailClose, children: backLabel }) }),
|
|
130
167
|
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: detail }) })
|
|
131
168
|
] }) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: list }) }) });
|
|
132
169
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
const asideButton = showAside && asideCollapsed ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "info", size: 14 }), onClick: () => setAsideWantsOpen(true), "aria-expanded": asideOpen, children: asideTitle }) : null;
|
|
171
|
+
const closeButton = hiding && onDetailClose ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": closeLabel, onClick: onDetailClose, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) }) : null;
|
|
172
|
+
const paneBar = asideButton || closeButton ? /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-end gap-ds-1 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: [
|
|
173
|
+
asideButton,
|
|
174
|
+
closeButton
|
|
175
|
+
] }) : null;
|
|
176
|
+
const showDetail = !hiding || detailOpen;
|
|
177
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-split-view": hiding && !detailOpen ? "list" : "split", "data-frame": frame, ...region, className: surface, style: fillStyle, children: [
|
|
178
|
+
/* @__PURE__ */ jsxs(
|
|
179
|
+
ResizablePanelGroup,
|
|
180
|
+
{
|
|
181
|
+
direction: "horizontal",
|
|
182
|
+
stackOnMobile: false,
|
|
183
|
+
className: "h-full",
|
|
184
|
+
onLayoutChanged: hiding ? (layout) => {
|
|
185
|
+
if (layout.detail != null && layout.list != null) setListPct(layout.list);
|
|
186
|
+
} : void 0,
|
|
187
|
+
children: [
|
|
188
|
+
/* @__PURE__ */ jsx(ResizablePanel, { id: "list", defaultSize: `${hiding ? detailOpen ? listPct : 100 : listWidth}%`, minSize: `${listMinWidth}%`, children: /* @__PURE__ */ jsx("div", { "data-pane": "list", className: "h-full min-h-0 overflow-y-auto", onFocus: hiding ? (e) => {
|
|
189
|
+
lastInList.current = e.target;
|
|
190
|
+
} : void 0, children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: list }) }) }),
|
|
191
|
+
showDetail && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
192
|
+
/* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
|
|
193
|
+
/* @__PURE__ */ jsx(ResizablePanel, { id: "detail", minSize: "30%", children: /* @__PURE__ */ jsxs("div", { ref: paneRef, "data-pane": "detail", className: "h-full min-h-0 flex flex-col", onKeyDown: hiding ? onPaneKeyDown : void 0, children: [
|
|
194
|
+
paneBar,
|
|
195
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: detail }) })
|
|
196
|
+
] }) })
|
|
197
|
+
] }),
|
|
198
|
+
showAside && !asideCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
199
|
+
/* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
|
|
200
|
+
/* @__PURE__ */ jsx(ResizablePanel, { id: "aside", defaultSize: `${asideWidth}%`, minSize: "15%", children: /* @__PURE__ */ jsx("div", { "data-pane": "aside", className: "h-full min-h-0 overflow-y-auto p-ds-4", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: aside }) }) })
|
|
201
|
+
] })
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
showAside && asideCollapsed && /* @__PURE__ */ jsx(Drawer, { open: asideOpen, onClose: () => setAsideWantsOpen(false), title: asideTitle, position: "right", children: aside })
|
|
148
206
|
] });
|
|
149
207
|
}
|
|
150
208
|
|
package/dist/resizable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/components/ResizablePanels.tsx","../../../showcase/src/components/SplitView.tsx"],"names":["PanelGroup","PanelResizeHandle","jsx"],"mappings":"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","file":"resizable.js","sourcesContent":["import {\n Panel,\n Group as PanelGroup,\n Separator as PanelResizeHandle,\n} from \"react-resizable-panels\";\nimport { createContext, useContext, type ComponentProps } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { Icon } from \"../lib/icons\";\n\ntype PanelGroupProps = ComponentProps<typeof PanelGroup>;\n\ntype Direction = \"horizontal\" | \"vertical\";\nconst DirectionCtx = createContext<Direction>(\"horizontal\");\nconst MobileCtx = createContext(false);\n\nexport interface ResizablePanelGroupProps\n extends Omit<PanelGroupProps, \"className\" | \"orientation\"> {\n direction?: Direction;\n stackOnMobile?: boolean;\n className?: string;\n /** Map of panel id → percentage (0–100). Must be an object, never an array. Panels need matching id props. */\n defaultLayout?: { [panelId: string]: number };\n /** Called after resize ends. Receives { [panelId]: number }, not number[]. */\n onLayoutChanged?: (layout: { [panelId: string]: number }) => void;\n}\n\nexport function ResizablePanelGroup({\n className,\n direction = \"horizontal\",\n stackOnMobile = true,\n ...props\n}: ResizablePanelGroupProps) {\n const isMobile = useBreakpoint(\"sm\");\n const shouldStack = stackOnMobile && isMobile && direction === \"horizontal\";\n const effectiveDirection = shouldStack ? \"vertical\" : direction;\n\n return (\n <DirectionCtx.Provider value={effectiveDirection}>\n <MobileCtx.Provider value={isMobile}>\n <PanelGroup\n className={cn(\n \"flex h-full w-full\",\n effectiveDirection === \"vertical\" && \"flex-col\",\n className,\n )}\n orientation={effectiveDirection}\n {...props}\n />\n </MobileCtx.Provider>\n </DirectionCtx.Provider>\n );\n}\n\ntype PanelProps = ComponentProps<typeof Panel>;\n\nexport interface ResizablePanelProps extends Omit<PanelProps, \"className\"> {\n className?: string;\n id?: string;\n /** number = px. \"30%\" or \"30\" = percent. \"28rem\" / \"400px\" also valid. */\n defaultSize?: number | string;\n minSize?: number | string;\n maxSize?: number | string;\n collapsible?: boolean;\n}\n\nexport function ResizablePanel({ className, ...props }: ResizablePanelProps) {\n return <Panel className={cn(\"\", className)} {...props} />;\n}\n\ntype PanelResizeHandleProps = ComponentProps<typeof PanelResizeHandle>;\n\nexport interface ResizableHandleProps\n extends Omit<PanelResizeHandleProps, \"className\"> {\n withHandle?: boolean;\n className?: string;\n}\n\nexport function ResizableHandle({\n withHandle,\n className,\n ...props\n}: ResizableHandleProps) {\n const direction = useContext(DirectionCtx);\n const isMobile = useContext(MobileCtx);\n const isVertical = direction === \"vertical\";\n\n return (\n <PanelResizeHandle\n className={cn(\n \"relative flex items-center justify-center bg-[var(--ds-border-default)]\",\n isVertical\n ? cn(\n \"h-[var(--ds-border-width)] w-full after:absolute after:left-0 after:right-0 after:content-['']\",\n isMobile ? \"after:-top-3 after:-bottom-3\" : \"after:-top-1 after:-bottom-1\"\n )\n : cn(\n \"w-[var(--ds-border-width)] after:absolute after:inset-y-0 after:content-['']\",\n isMobile ? \"after:-left-3 after:-right-3\" : \"after:-left-1 after:-right-1\"\n ),\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"[&[data-resize-handle-state=drag]]:bg-[var(--ds-accent-primary)]\",\n \"[&[data-resize-handle-state=hover]]:bg-[var(--ds-accent-primary)]\",\n className,\n )}\n {...props}\n >\n {withHandle && (\n <div\n className={cn(\n \"z-10 flex items-center justify-center rounded-[var(--ds-radius-sm)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"bg-[var(--ds-bg-surface)]\",\n isVertical\n ? (isMobile ? \"h-4 w-8\" : \"h-3 w-4\")\n : (isMobile ? \"h-8 w-4\" : \"h-4 w-3\"),\n )}\n >\n <Icon\n name=\"grip-vertical\"\n size={isMobile ? 12 : 10}\n className={cn(\n \"text-[var(--ds-text-muted)]\",\n isVertical && \"rotate-90\",\n )}\n />\n </div>\n )}\n </PanelResizeHandle>\n );\n}\n","import { useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useCompact } from \"../lib/use-compact\";\nimport { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"./ResizablePanels\";\nimport { Button } from \"./Button\";\nimport { Drawer } from \"./Drawer\";\nimport { Icon } from \"../lib/icons\";\nimport { useAppShell } from \"./AppShell\";\nimport { SplitPaneCtx } from \"../lib/surface-context\";\n\nexport type SplitViewFrame = \"fill\" | \"page\";\n\nexport interface SplitViewProps {\n /** The list (an inbox, a queue): it scrolls on its own. */\n list: ReactNode;\n /** The selected item (a thread, a record): it fills the height and scrolls on its own. */\n detail: ReactNode;\n /** Context beside the detail (a contact, properties). The pane pads it (16 px, like the Drawer it collapses into), so pass content with no padding classes (round 8). Collapses to a toggle that opens it in a Drawer when the layout is narrower than `asideCollapseBelow`. */\n aside?: ReactNode;\n /** Names the aside's toggle and Drawer. */\n asideTitle?: string;\n /**\n * Below `collapseBelow` the list shows alone; with `detailOpen` the detail replaces it, with a Back button that calls\n * `onDetailClose` (detailMode \"stack\"). The page owns the selection, so it owns this flag.\n */\n detailOpen?: boolean;\n onDetailClose?: () => void;\n /** \"stack\" (default): the detail stacks over the list below the collapse width. \"route\": the app navigates to the detail's own route instead, and this renders the list alone there. */\n detailMode?: \"stack\" | \"route\";\n backLabel?: string;\n /** Percent of the width the list takes; the divider is draggable. */\n listWidth?: number;\n listMinWidth?: number;\n asideWidth?: number;\n /** Below this width of the layout itself (px) the list and detail stop sitting side by side. */\n collapseBelow?: number;\n /** Below this width of the layout itself (px) the aside becomes a toggle. */\n asideCollapseBelow?: number;\n /** A bordered, rounded surface (inside a page with padding) instead of edge to edge (filling <main>). */\n bordered?: boolean;\n /** \"fill\": the page root, flush to the shell, h-full against main, each pane owns its inset. \"page\" (default): inside a PageContainer under a PageHeader. */\n frame?: SplitViewFrame;\n \"aria-label\"?: string;\n className?: string;\n}\n\n/**\n * The reading-pane composition (Sprint 25.2, R3): one surface with dividers —\n * list, detail and an optional aside — that fills the height it is given (an\n * AppShell's <main> already gives it) and never three cards with gaps. The\n * panels compose ResizablePanelGroup, so the dividers drag. Below the collapse\n * width, measured on the layout itself, the list shows alone and the detail\n * stacks over it (or the app routes to it); the aside collapses to a toggle.\n * The page owns the selection and assembles the contents; keyboard movement\n * through the list is the list's job (StackedList selectedId).\n */\nexport function SplitView({\n list,\n detail,\n aside,\n asideTitle = \"Details\",\n detailOpen = false,\n onDetailClose,\n detailMode = \"stack\",\n backLabel = \"Back\",\n listWidth = 35,\n listMinWidth = 25,\n asideWidth = 25,\n collapseBelow = 768,\n asideCollapseBelow = 1024,\n bordered = false,\n frame = \"page\",\n \"aria-label\": ariaLabel,\n className,\n}: SplitViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompact(rootRef, collapseBelow);\n const asideCollapsed = useCompact(rootRef, asideCollapseBelow);\n const [asideWantsOpen, setAsideWantsOpen] = useState(false);\n const asideOpen = asideCollapsed && asideWantsOpen;\n const region = ariaLabel ? { role: \"region\" as const, \"aria-label\": ariaLabel } : {};\n // fill: the page root, flush to the shell on every side (no border, no radius, no padding of its own); h-full\n // resolves against <main> because the shell is a flex column down to it. page: inside a PageContainer under a\n // PageHeader, where `bordered` draws the surface. Each pane owns its own inset either way.\n const isFill = frame === \"fill\";\n // In a shell with an app footer the children wrapper has no definite height, so h-full cannot resolve; the shell\n // publishes main's height and fill takes it (the footer then sits below the fold, as it must under a full pane).\n const shell = useAppShell();\n const fillStyle = isFill && shell?.hasFooter ? { height: \"var(--ds-main-height, 100%)\" } : undefined;\n const surface = cn(\n \"h-full min-h-0 w-full bg-[var(--ds-bg-surface)]\",\n isFill && \"rounded-none border-0\",\n bordered && !isFill && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden\",\n className,\n );\n\n if (compact) {\n const showDetail = detailMode === \"stack\" && detailOpen;\n return (\n <div ref={rootRef} data-split-view={showDetail ? \"detail\" : \"list\"} data-frame={frame} {...region} className={cn(surface, \"flex flex-col\")} style={fillStyle}>\n {showDetail ? (\n <>\n <div className=\"shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"arrow-left\" size={14} />} onClick={onDetailClose}>{backLabel}</Button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </>\n ) : (\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{list}</SplitPaneCtx></div>\n )}\n </div>\n );\n }\n\n const asideToggle = aside && asideCollapsed ? (\n <div className=\"shrink-0 flex justify-end px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"info\" size={14} />} onClick={() => setAsideWantsOpen(true)} aria-expanded={asideOpen}>{asideTitle}</Button>\n </div>\n ) : null;\n\n return (\n <div ref={rootRef} data-split-view=\"split\" data-frame={frame} {...region} className={surface} style={fillStyle}>\n <ResizablePanelGroup direction=\"horizontal\" stackOnMobile={false} className=\"h-full\">\n <ResizablePanel id=\"list\" defaultSize={`${listWidth}%`} minSize={`${listMinWidth}%`}>\n <div data-pane=\"list\" className=\"h-full min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{list}</SplitPaneCtx></div>\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"detail\" minSize=\"30%\">\n <div data-pane=\"detail\" className=\"h-full min-h-0 flex flex-col\">\n {asideToggle}\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </div>\n </ResizablePanel>\n {aside && !asideCollapsed && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"aside\" defaultSize={`${asideWidth}%`} minSize=\"15%\">\n <div data-pane=\"aside\" className=\"h-full min-h-0 overflow-y-auto p-ds-4\"><SplitPaneCtx value={true}>{aside}</SplitPaneCtx></div>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n {aside && asideCollapsed && (\n <Drawer open={asideOpen} onClose={() => setAsideWantsOpen(false)} title={asideTitle} position=\"right\">\n {aside}\n </Drawer>\n )}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/ResizablePanels.tsx","../../../showcase/src/components/SplitView.tsx"],"names":["PanelGroup","PanelResizeHandle","showDetail","jsx"],"mappings":"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","file":"resizable.js","sourcesContent":["import {\n Panel,\n Group as PanelGroup,\n Separator as PanelResizeHandle,\n} from \"react-resizable-panels\";\nimport { createContext, useContext, type ComponentProps } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { Icon } from \"../lib/icons\";\n\ntype PanelGroupProps = ComponentProps<typeof PanelGroup>;\n\ntype Direction = \"horizontal\" | \"vertical\";\nconst DirectionCtx = createContext<Direction>(\"horizontal\");\nconst MobileCtx = createContext(false);\n\nexport interface ResizablePanelGroupProps\n extends Omit<PanelGroupProps, \"className\" | \"orientation\"> {\n direction?: Direction;\n stackOnMobile?: boolean;\n className?: string;\n /** Map of panel id → percentage (0–100). Must be an object, never an array. Panels need matching id props. */\n defaultLayout?: { [panelId: string]: number };\n /** Called after resize ends. Receives { [panelId]: number }, not number[]. */\n onLayoutChanged?: (layout: { [panelId: string]: number }) => void;\n}\n\nexport function ResizablePanelGroup({\n className,\n direction = \"horizontal\",\n stackOnMobile = true,\n ...props\n}: ResizablePanelGroupProps) {\n const isMobile = useBreakpoint(\"sm\");\n const shouldStack = stackOnMobile && isMobile && direction === \"horizontal\";\n const effectiveDirection = shouldStack ? \"vertical\" : direction;\n\n return (\n <DirectionCtx.Provider value={effectiveDirection}>\n <MobileCtx.Provider value={isMobile}>\n <PanelGroup\n className={cn(\n \"flex h-full w-full\",\n effectiveDirection === \"vertical\" && \"flex-col\",\n className,\n )}\n orientation={effectiveDirection}\n {...props}\n />\n </MobileCtx.Provider>\n </DirectionCtx.Provider>\n );\n}\n\ntype PanelProps = ComponentProps<typeof Panel>;\n\nexport interface ResizablePanelProps extends Omit<PanelProps, \"className\"> {\n className?: string;\n id?: string;\n /** number = px. \"30%\" or \"30\" = percent. \"28rem\" / \"400px\" also valid. */\n defaultSize?: number | string;\n minSize?: number | string;\n maxSize?: number | string;\n collapsible?: boolean;\n}\n\nexport function ResizablePanel({ className, ...props }: ResizablePanelProps) {\n return <Panel className={cn(\"\", className)} {...props} />;\n}\n\ntype PanelResizeHandleProps = ComponentProps<typeof PanelResizeHandle>;\n\nexport interface ResizableHandleProps\n extends Omit<PanelResizeHandleProps, \"className\"> {\n withHandle?: boolean;\n className?: string;\n}\n\nexport function ResizableHandle({\n withHandle,\n className,\n ...props\n}: ResizableHandleProps) {\n const direction = useContext(DirectionCtx);\n const isMobile = useContext(MobileCtx);\n const isVertical = direction === \"vertical\";\n\n return (\n <PanelResizeHandle\n className={cn(\n \"relative flex items-center justify-center bg-[var(--ds-border-default)]\",\n isVertical\n ? cn(\n \"h-[var(--ds-border-width)] w-full after:absolute after:left-0 after:right-0 after:content-['']\",\n isMobile ? \"after:-top-3 after:-bottom-3\" : \"after:-top-1 after:-bottom-1\"\n )\n : cn(\n \"w-[var(--ds-border-width)] after:absolute after:inset-y-0 after:content-['']\",\n isMobile ? \"after:-left-3 after:-right-3\" : \"after:-left-1 after:-right-1\"\n ),\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"[&[data-resize-handle-state=drag]]:bg-[var(--ds-accent-primary)]\",\n \"[&[data-resize-handle-state=hover]]:bg-[var(--ds-accent-primary)]\",\n className,\n )}\n {...props}\n >\n {withHandle && (\n <div\n className={cn(\n \"z-10 flex items-center justify-center rounded-[var(--ds-radius-sm)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"bg-[var(--ds-bg-surface)]\",\n isVertical\n ? (isMobile ? \"h-4 w-8\" : \"h-3 w-4\")\n : (isMobile ? \"h-8 w-4\" : \"h-4 w-3\"),\n )}\n >\n <Icon\n name=\"grip-vertical\"\n size={isMobile ? 12 : 10}\n className={cn(\n \"text-[var(--ds-text-muted)]\",\n isVertical && \"rotate-90\",\n )}\n />\n </div>\n )}\n </PanelResizeHandle>\n );\n}\n","import { useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useCompact } from \"../lib/use-compact\";\nimport { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"./ResizablePanels\";\nimport { Button } from \"./Button\";\nimport { Drawer } from \"./Drawer\";\nimport { Icon } from \"../lib/icons\";\nimport { useAppShell } from \"./AppShell\";\nimport { SplitPaneCtx } from \"../lib/surface-context\";\nimport { isTabStop, rememberFocus, tabStops, type RememberedFocus } from \"../lib/focus\";\n\nexport type SplitViewFrame = \"fill\" | \"page\";\n\nexport interface SplitViewProps {\n /** The list (an inbox, a queue): it scrolls on its own. */\n list: ReactNode;\n /** The selected item (a thread, a record): it fills the height and scrolls on its own. */\n detail: ReactNode;\n /** Context beside the detail (a contact, properties). The pane pads it (16 px, like the Drawer it collapses into), so pass content with no padding classes (round 8). Collapses to a toggle that opens it in a Drawer when the layout is narrower than `asideCollapseBelow`. */\n aside?: ReactNode;\n /** Names the aside's toggle and Drawer. */\n asideTitle?: string;\n /**\n * Below `collapseBelow` the list shows alone; with `detailOpen` the detail replaces it, with a Back button that calls\n * `onDetailClose` (detailMode \"stack\"). The page owns the selection, so it owns this flag.\n */\n detailOpen?: boolean;\n onDetailClose?: () => void;\n /** \"stack\" (default): the detail stacks over the list below the collapse width. \"route\": the app navigates to the detail's own route instead, and this renders the list alone there. */\n detailMode?: \"stack\" | \"route\";\n /**\n * \"shown\" (default): above the collapse width the detail pane is always there — an inbox's reading pane may show a\n * placeholder. \"hidden\" (Sprint 25.7, C3): `detailOpen` decides at every width. Closed, the list takes the whole\n * width; open, the pane appears beside it at the width the user last gave it, with a close control that calls\n * `onDetailClose`. For boards, timelines and lists of records. The list stays mounted, so a board or a timeline keeps\n * its scroll and zoom.\n */\n closedDetail?: \"shown\" | \"hidden\";\n /** The open pane's close control, named for a screen reader (closedDetail=\"hidden\"). Default \"Close\". */\n closeLabel?: string;\n backLabel?: string;\n /** Percent of the width the list takes; the divider is draggable. */\n listWidth?: number;\n listMinWidth?: number;\n asideWidth?: number;\n /** Below this width of the layout itself (px) the list and detail stop sitting side by side. */\n collapseBelow?: number;\n /** Below this width of the layout itself (px) the aside becomes a toggle. */\n asideCollapseBelow?: number;\n /** A bordered, rounded surface (inside a page with padding) instead of edge to edge (filling <main>). */\n bordered?: boolean;\n /** \"fill\": the page root, flush to the shell, h-full against main, each pane owns its inset. \"page\" (default): inside a PageContainer under a PageHeader. */\n frame?: SplitViewFrame;\n \"aria-label\"?: string;\n className?: string;\n}\n\n/**\n * The reading-pane composition (Sprint 25.2, R3): one surface with dividers —\n * list, detail and an optional aside — that fills the height it is given (an\n * AppShell's <main> already gives it) and never three cards with gaps. The\n * panels compose ResizablePanelGroup, so the dividers drag. Below the collapse\n * width, measured on the layout itself, the list shows alone and the detail\n * stacks over it (or the app routes to it); the aside collapses to a toggle.\n * The page owns the selection and assembles the contents; keyboard movement\n * through the list is the list's job (StackedList selectedId).\n */\nexport function SplitView({\n list,\n detail,\n aside,\n asideTitle = \"Details\",\n detailOpen = false,\n onDetailClose,\n detailMode = \"stack\",\n closedDetail = \"shown\",\n closeLabel = \"Close\",\n backLabel = \"Back\",\n listWidth = 35,\n listMinWidth = 25,\n asideWidth = 25,\n collapseBelow = 768,\n asideCollapseBelow = 1024,\n bordered = false,\n frame = \"page\",\n \"aria-label\": ariaLabel,\n className,\n}: SplitViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompact(rootRef, collapseBelow);\n const asideCollapsed = useCompact(rootRef, asideCollapseBelow);\n const [asideWantsOpen, setAsideWantsOpen] = useState(false);\n const hiding = closedDetail === \"hidden\";\n // Hidden: the aside belongs to the open item, so it (its pane, its toggle, its Drawer) goes with the pane, and a\n // Drawer left open does not come back with the next item (render-time state adjustment).\n const showAside = !!aside && (!hiding || detailOpen);\n if (hiding && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);\n const asideOpen = asideCollapsed && asideWantsOpen;\n const region = ariaLabel ? { role: \"region\" as const, \"aria-label\": ariaLabel } : {};\n // fill: the page root, flush to the shell on every side (no border, no radius, no padding of its own); h-full\n // resolves against <main> because the shell is a flex column down to it. page: inside a PageContainer under a\n // PageHeader, where `bordered` draws the surface. Each pane owns its own inset either way.\n const isFill = frame === \"fill\";\n // In a shell with an app footer the children wrapper has no definite height, so h-full cannot resolve; the shell\n // publishes main's height and fill takes it (the footer then sits below the fold, as it must under a full pane).\n const shell = useAppShell();\n const fillStyle = isFill && shell?.hasFooter ? { height: \"var(--ds-main-height, 100%)\" } : undefined;\n const surface = cn(\n \"h-full min-h-0 w-full bg-[var(--ds-bg-surface)]\",\n isFill && \"rounded-none border-0\",\n bordered && !isFill && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden\",\n className,\n );\n\n // closedDetail=\"hidden\" (C3): the pane opens beside the list at the width the user last gave it; closing it returns\n // focus to the item the pane shows, or its neighbour if that item is gone (D14(e)): the list's selected item, else\n // the last list item that took focus, checked on each render while the pane is open, so switching items is followed.\n // A selection kept in the URL changes the path, so the item is remembered whatever the route does. With no item\n // (opened on load, or from outside the list) focus goes to the list's first Tab stop. No overlay layer: an\n // assistant opened earlier keeps Escape.\n const [listPct, setListPct] = useState(listWidth);\n const paneRef = useRef<HTMLDivElement>(null);\n const shown = useRef<RememberedFocus | null>(null);\n const lastInList = useRef<HTMLElement | null>(null);\n const wasOpen = useRef(false);\n useLayoutEffect(() => {\n if (!hiding) return;\n const listPane = rootRef.current?.querySelector<HTMLElement>('[data-pane=\"list\"]') ?? null;\n if (detailOpen) {\n wasOpen.current = true;\n const selected = listPane?.querySelector<HTMLElement>('[aria-current=\"true\"], [aria-selected=\"true\"]');\n const last = lastInList.current;\n const item = (selected && (isTabStop(selected) ? selected : tabStops(selected)[0]))\n || (last?.isConnected && listPane?.contains(last) ? last : null);\n if (item && item !== shown.current?.el) shown.current = rememberFocus(item, { ignoreRoute: true });\n return;\n }\n if (!wasOpen.current) return;\n wasOpen.current = false;\n const r = shown.current;\n shown.current = null;\n const active = document.activeElement;\n if (active && active !== document.body && active.isConnected) return;\n const first = listPane ? tabStops(listPane)[0] : undefined;\n if (!r?.restore(first)) first?.focus();\n });\n const onPaneKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !onDetailClose) return;\n const t = e.target as HTMLElement;\n // the pane's own DOM only — a portalled popup inside it bubbles here in React's tree — and never from a field\n if (!paneRef.current?.contains(t) || t.closest(\"input, textarea, select, [contenteditable='true']\")) return;\n e.preventDefault();\n onDetailClose();\n };\n\n if (compact) {\n const showDetail = detailMode === \"stack\" && detailOpen;\n return (\n <div ref={rootRef} data-split-view={showDetail ? \"detail\" : \"list\"} data-frame={frame} {...region} className={cn(surface, \"flex flex-col\")} style={fillStyle}>\n {showDetail ? (\n <>\n <div className=\"shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"arrow-left\" size={14} />} onClick={onDetailClose}>{backLabel}</Button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </>\n ) : (\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{list}</SplitPaneCtx></div>\n )}\n </div>\n );\n }\n\n const asideButton = showAside && asideCollapsed\n ? <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"info\" size={14} />} onClick={() => setAsideWantsOpen(true)} aria-expanded={asideOpen}>{asideTitle}</Button>\n : null;\n const closeButton = hiding && onDetailClose\n ? <Button variant=\"ghost\" size=\"icon-sm\" aria-label={closeLabel} onClick={onDetailClose}><Icon name=\"close\" size={14} /></Button>\n : null;\n const paneBar = asideButton || closeButton ? (\n <div className=\"shrink-0 flex items-center justify-end gap-ds-1 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n {asideButton}\n {closeButton}\n </div>\n ) : null;\n const showDetail = !hiding || detailOpen;\n\n return (\n <div ref={rootRef} data-split-view={hiding && !detailOpen ? \"list\" : \"split\"} data-frame={frame} {...region} className={surface} style={fillStyle}>\n <ResizablePanelGroup\n direction=\"horizontal\"\n stackOnMobile={false}\n className=\"h-full\"\n // the width the user gives the list is kept, so a closed pane reopens at it\n onLayoutChanged={hiding ? (layout) => { if (layout.detail != null && layout.list != null) setListPct(layout.list); } : undefined}\n >\n <ResizablePanel id=\"list\" defaultSize={`${hiding ? (detailOpen ? listPct : 100) : listWidth}%`} minSize={`${listMinWidth}%`}>\n <div data-pane=\"list\" className=\"h-full min-h-0 overflow-y-auto\" onFocus={hiding ? (e) => { lastInList.current = e.target; } : undefined}>\n <SplitPaneCtx value={true}>{list}</SplitPaneCtx>\n </div>\n </ResizablePanel>\n {showDetail && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"detail\" minSize=\"30%\">\n <div ref={paneRef} data-pane=\"detail\" className=\"h-full min-h-0 flex flex-col\" onKeyDown={hiding ? onPaneKeyDown : undefined}>\n {paneBar}\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </div>\n </ResizablePanel>\n </>\n )}\n {showAside && !asideCollapsed && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"aside\" defaultSize={`${asideWidth}%`} minSize=\"15%\">\n <div data-pane=\"aside\" className=\"h-full min-h-0 overflow-y-auto p-ds-4\"><SplitPaneCtx value={true}>{aside}</SplitPaneCtx></div>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n {showAside && asideCollapsed && (\n <Drawer open={asideOpen} onClose={() => setAsideWantsOpen(false)} title={asideTitle} position=\"right\">\n {aside}\n </Drawer>\n )}\n </div>\n );\n}\n"]}
|
package/dist/roots.css
CHANGED
|
@@ -344,20 +344,23 @@
|
|
|
344
344
|
--ds-accent-subtle-text: var(--ds-accent-primary);
|
|
345
345
|
|
|
346
346
|
/* Categorical chart scale — 6 series colours for data visualisation.
|
|
347
|
-
chart-1 is the accent, chart-2–6 map to palette
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
chart-1 is the accent, chart-2–6 map to the palette's chart colours
|
|
348
|
+
(the bold in light mode, lifted in dark so a series shows at 3:1 on a
|
|
349
|
+
dark card; a theme's generated tokens set chart-1 to a primary step
|
|
350
|
+
that shows when the accent does not). When palette tokens are absent
|
|
351
|
+
(no theme loaded), chart-2–6 fall back to neutral shades so something
|
|
352
|
+
visible always renders. */
|
|
350
353
|
--ds-chart-1: var(--ds-accent-primary);
|
|
351
354
|
--ds-chart-1-soft: color-mix(in oklch, var(--ds-accent-primary) 30%, transparent);
|
|
352
|
-
--ds-chart-2: var(--ds-palette-1-bold, var(--ds-neutral-500));
|
|
355
|
+
--ds-chart-2: var(--ds-palette-1-chart, var(--ds-palette-1-bold, var(--ds-neutral-500)));
|
|
353
356
|
--ds-chart-2-soft: color-mix(in oklch, var(--ds-chart-2) 30%, transparent);
|
|
354
|
-
--ds-chart-3: var(--ds-palette-2-bold, var(--ds-neutral-400));
|
|
357
|
+
--ds-chart-3: var(--ds-palette-2-chart, var(--ds-palette-2-bold, var(--ds-neutral-400)));
|
|
355
358
|
--ds-chart-3-soft: color-mix(in oklch, var(--ds-chart-3) 30%, transparent);
|
|
356
|
-
--ds-chart-4: var(--ds-palette-3-bold, var(--ds-neutral-600));
|
|
359
|
+
--ds-chart-4: var(--ds-palette-3-chart, var(--ds-palette-3-bold, var(--ds-neutral-600)));
|
|
357
360
|
--ds-chart-4-soft: color-mix(in oklch, var(--ds-chart-4) 30%, transparent);
|
|
358
|
-
--ds-chart-5: var(--ds-palette-4-bold, var(--ds-neutral-300));
|
|
361
|
+
--ds-chart-5: var(--ds-palette-4-chart, var(--ds-palette-4-bold, var(--ds-neutral-300)));
|
|
359
362
|
--ds-chart-5-soft: color-mix(in oklch, var(--ds-chart-5) 30%, transparent);
|
|
360
|
-
--ds-chart-6: var(--ds-palette-5-bold, var(--ds-neutral-700));
|
|
363
|
+
--ds-chart-6: var(--ds-palette-5-chart, var(--ds-palette-5-bold, var(--ds-neutral-700)));
|
|
361
364
|
--ds-chart-6-soft: color-mix(in oklch, var(--ds-chart-6) 30%, transparent);
|
|
362
365
|
|
|
363
366
|
/* Backwards-compat aliases */
|
|
@@ -40,6 +40,7 @@ var CURATED = {
|
|
|
40
40
|
"type": "IconTypography",
|
|
41
41
|
"chevron-up": "IconChevronUp",
|
|
42
42
|
"chevron-down": "IconChevronDown",
|
|
43
|
+
"chevron-left": "IconChevronLeft",
|
|
43
44
|
"trash": "IconTrash",
|
|
44
45
|
"undo": "IconArrowBackUp",
|
|
45
46
|
"redo": "IconArrowForwardUp",
|
|
@@ -72,5 +73,5 @@ async function loadExtended() {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
export { loadCurated, loadExtended };
|
|
75
|
-
//# sourceMappingURL=tabler-
|
|
76
|
-
//# sourceMappingURL=tabler-
|
|
76
|
+
//# sourceMappingURL=tabler-YXXC7Y6B.js.map
|
|
77
|
+
//# sourceMappingURL=tabler-YXXC7Y6B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/icon-packs/tabler.ts"],"names":[],"mappings":";;;AAgBA,IAAM,OAAA,GAAkC;AAAA,EACtC,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA,EAAU,YAAA;AAAA,EACV,OAAA,EAAS,cAAA;AAAA,EACT,UAAA,EAAY,cAAA;AAAA,EACZ,eAAA,EAAiB,kBAAA;AAAA,EACjB,OAAA,EAAS,OAAA;AAAA,EACT,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA,EAAU,YAAA;AAAA,EACV,MAAA,EAAQ,UAAA;AAAA,EACR,MAAA,EAAQ,UAAA;AAAA,EACR,OAAA,EAAS,WAAA;AAAA,EACT,SAAA,EAAW,mBAAA;AAAA,EACX,MAAA,EAAQ,gBAAA;AAAA,EACR,OAAA,EAAS,iBAAA;AAAA,EACT,OAAA,EAAS,WAAA;AAAA,EACT,UAAA,EAAY,cAAA;AAAA,EACZ,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA,EAAU,YAAA;AAAA,EACV,WAAA,EAAa,eAAA;AAAA,EACb,eAAA,EAAiB,mBAAA;AAAA,EACjB,YAAA,EAAc,eAAA;AAAA,EACd,cAAA,EAAgB,iBAAA;AAAA,EAChB,aAAA,EAAe,gBAAA;AAAA,EACf,MAAA,EAAQ,UAAA;AAAA,EACR,cAAA,EAAgB,iBAAA;AAAA,EAChB,aAAA,EAAe,eAAA;AAAA,EACf,MAAA,EAAQ,UAAA;AAAA,EACR,OAAA,EAAS,WAAA;AAAA,EACT,OAAA,EAAS,WAAA;AAAA,EACT,MAAA,EAAQ,UAAA;AAAA,EACR,OAAA,EAAS,WAAA;AAAA,EACT,OAAA,EAAS,WAAA;AAAA,EACT,aAAA,EAAe,iBAAA;AAAA,EACf,WAAA,EAAa,eAAA;AAAA,EACb,WAAA,EAAa,eAAA;AAAA,EACb,MAAA,EAAQ,gBAAA;AAAA,EACR,YAAA,EAAc,eAAA;AAAA,EACd,cAAA,EAAgB,iBAAA;AAAA,EAChB,cAAA,EAAgB,iBAAA;AAAA,EAChB,OAAA,EAAS,WAAA;AAAA,EACT,MAAA,EAAQ,iBAAA;AAAA,EACR,MAAA,EAAQ,oBAAA;AAAA,EACR,YAAA,EAAc,eAAA;AAAA,EACd,eAAA,EAAiB,kBAAA;AAAA,EACjB,KAAA,EAAO,gBAAA;AAAA,EACP,WAAA,EAAa,eAAA;AAAA,EACb,MAAA,EAAQ,UAAA;AAAA,EACR,SAAA,EAAW,YAAA;AAAA,EACX,MAAA,EAAQ,YAAA;AAAA,EACR,MAAA,EAAQ,WAAA;AAAA,EACR,iBAAA,EAAmB,UAAA;AAAA,EACnB,MAAA,EAAQ,UAAA;AAAA,EACR,UAAA,EAAY,cAAA;AAAA,EACZ,UAAA,EAAY,cAAA;AAAA,EACZ,QAAA,EAAU,YAAA;AAAA,EACV,UAAA,EAAY;AACd,CAAA;AAEA,eAAe,IAAA,GAAyC;AACtD,EAAA,OAAQ,MAAM,OAAO,qBAAqB,CAAA;AAC5C;AAEA,eAAsB,WAAA,GAAgC;AACpD,EAAA,OAAO,WAAA,CAAY,MAAM,IAAA,EAAK,EAAG,OAAO,CAAA;AAC1C;AAEA,eAAsB,YAAA,GAAiC;AACrD,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,EAAK;AACvB,EAAA,MAAM,QAAA,GAAW,iBAAiB,GAAA,EAAK,IAAI,EAAE,WAAA,EAAa,QAAQ,CAAA;AAClE,EAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,WAAA,CAAY,GAAA,EAAK,OAAO,CAAC,CAAA;AACjD,EAAA,OAAO,QAAA;AACT","file":"tabler-YXXC7Y6B.js","sourcesContent":["import type { IconMap } from './types'\nimport { buildExtendedMap, pickCurated } from './utils'\n\n/**\n * Tabler icons.\n *\n * The pack is reached only through the dynamic import in peer() — never a\n * static named import. A static import of an optional peer makes a consumer's\n * production build fail on Vite 8 when the peer is not installed: Vite\n * substitutes an export-less stub and the bundler treats every missing name as\n * fatal (Sprint 22 bench finding 1, 13 Sep 2026; showcase/src/test/\n * vite8-optional-peers-build.test.ts guards it). The curated names are strings\n * for the same reason; icon-completeness.test.tsx proves every one resolves in\n * the installed pack. If the pack is missing, peer() rejects and loadPack in\n * icons.tsx reports which package to install.\n */\nconst CURATED: Record<string, string> = {\n \"home\": \"IconHome\",\n \"folder\": \"IconFolder\",\n \"chart\": \"IconChartBar\",\n \"settings\": \"IconSettings\",\n \"chevron-right\": \"IconChevronRight\",\n \"close\": \"IconX\",\n \"plus\": \"IconPlus\",\n \"search\": \"IconSearch\",\n \"bell\": \"IconBell\",\n \"user\": \"IconUser\",\n \"check\": \"IconCheck\",\n \"warning\": \"IconAlertTriangle\",\n \"info\": \"IconInfoCircle\",\n \"error\": \"IconAlertCircle\",\n \"clock\": \"IconClock\",\n \"calendar\": \"IconCalendar\",\n \"bold\": \"IconBold\",\n \"italic\": \"IconItalic\",\n \"underline\": \"IconUnderline\",\n \"strikethrough\": \"IconStrikethrough\",\n \"align-left\": \"IconAlignLeft\",\n \"align-center\": \"IconAlignCenter\",\n \"align-right\": \"IconAlignRight\",\n \"list\": \"IconList\",\n \"list-ordered\": \"IconListNumbers\",\n \"list-checks\": \"IconListCheck\",\n \"link\": \"IconLink\",\n \"image\": \"IconPhoto\",\n \"table\": \"IconTable\",\n \"code\": \"IconCode\",\n \"quote\": \"IconQuote\",\n \"minus\": \"IconMinus\",\n \"superscript\": \"IconSuperscript\",\n \"subscript\": \"IconSubscript\",\n \"highlight\": \"IconHighlight\",\n \"type\": \"IconTypography\",\n \"chevron-up\": \"IconChevronUp\",\n \"chevron-down\": \"IconChevronDown\",\n \"chevron-left\": \"IconChevronLeft\",\n \"trash\": \"IconTrash\",\n \"undo\": \"IconArrowBackUp\",\n \"redo\": \"IconArrowForwardUp\",\n \"arrow-left\": \"IconArrowLeft\",\n \"grip-vertical\": \"IconGripVertical\",\n \"mic\": \"IconMicrophone\",\n \"paperclip\": \"IconPaperclip\",\n \"send\": \"IconSend\",\n \"log-out\": \"IconLogout\",\n \"edit\": \"IconPencil\",\n \"menu\": \"IconMenu2\",\n \"more-horizontal\": \"IconDots\",\n \"star\": \"IconStar\",\n \"bookmark\": \"IconBookmark\",\n \"download\": \"IconDownload\",\n \"upload\": \"IconUpload\",\n \"sparkles\": \"IconSparkles\",\n}\n\nasync function peer(): Promise<Record<string, unknown>> {\n return (await import(\"@tabler/icons-react\")) as unknown as Record<string, unknown>\n}\n\nexport async function loadCurated(): Promise<IconMap> {\n return pickCurated(await peer(), CURATED)\n}\n\nexport async function loadExtended(): Promise<IconMap> {\n const mod = await peer()\n const extended = buildExtendedMap(mod, {}, { stripPrefix: \"Icon\" })\n Object.assign(extended, pickCurated(mod, CURATED))\n return extended\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/lib/use-position.ts","../../../showcase/src/components/Tooltip.tsx","../../../showcase/src/lib/use-text-overflow.ts"],"names":["rafId","useState","useRef","useCallback","useEffect"],"mappings":"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","file":"chunk-IYD6ZDM2.js","sourcesContent":["import { useState, useCallback, useLayoutEffect, useRef, type RefObject } from \"react\";\n\nexport type Side = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type Align = \"start\" | \"center\" | \"end\";\n\ninterface PositionOptions {\n side?: Side;\n align?: Align;\n offset?: number;\n flip?: boolean;\n}\n\ninterface PositionResult {\n top: number;\n left: number;\n placement: Side;\n}\n\nconst VIEWPORT_PADDING = 8;\n\nexport function usePosition(\n triggerRef: RefObject<HTMLElement | null>,\n floatingRef: RefObject<HTMLElement | null>,\n open: boolean,\n options: PositionOptions = {}\n): PositionResult {\n const { side = \"bottom\", align = \"center\", offset = 8, flip = true } = options;\n const [pos, setPos] = useState<PositionResult>({ top: 0, left: 0, placement: side });\n const rafId = useRef(0);\n\n const compute = useCallback(() => {\n const trigger = triggerRef.current;\n const floating = floatingRef.current;\n if (!trigger || !floating) return;\n\n const tRect = trigger.getBoundingClientRect();\n const fRect = floating.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n let placement = side;\n\n if (flip) {\n if (placement === \"bottom\" && tRect.bottom + offset + fRect.height > vh - VIEWPORT_PADDING && tRect.top - offset - fRect.height > VIEWPORT_PADDING) {\n placement = \"top\";\n } else if (placement === \"top\" && tRect.top - offset - fRect.height < VIEWPORT_PADDING && tRect.bottom + offset + fRect.height < vh - VIEWPORT_PADDING) {\n placement = \"bottom\";\n } else if (placement === \"right\" && tRect.right + offset + fRect.width > vw - VIEWPORT_PADDING && tRect.left - offset - fRect.width > VIEWPORT_PADDING) {\n placement = \"left\";\n } else if (placement === \"left\" && tRect.left - offset - fRect.width < VIEWPORT_PADDING && tRect.right + offset + fRect.width < vw - VIEWPORT_PADDING) {\n placement = \"right\";\n }\n }\n\n let top: number;\n let left: number;\n\n if (placement === \"bottom\" || placement === \"top\") {\n top = placement === \"bottom\" ? tRect.bottom + offset : tRect.top - fRect.height - offset;\n\n switch (align) {\n case \"start\":\n left = tRect.left;\n break;\n case \"end\":\n left = tRect.right - fRect.width;\n break;\n default:\n left = tRect.left + (tRect.width - fRect.width) / 2;\n }\n } else {\n left = placement === \"right\" ? tRect.right + offset : tRect.left - fRect.width - offset;\n\n switch (align) {\n case \"start\":\n top = tRect.top;\n break;\n case \"end\":\n top = tRect.bottom - fRect.height;\n break;\n default:\n top = tRect.top + (tRect.height - fRect.height) / 2;\n }\n }\n\n left = Math.max(VIEWPORT_PADDING, Math.min(left, vw - fRect.width - VIEWPORT_PADDING));\n top = Math.max(VIEWPORT_PADDING, Math.min(top, vh - fRect.height - VIEWPORT_PADDING));\n\n setPos({ top, left, placement });\n }, [triggerRef, floatingRef, side, align, offset, flip]);\n\n useLayoutEffect(() => {\n if (!open) return;\n\n compute();\n\n const onUpdate = () => {\n cancelAnimationFrame(rafId.current);\n rafId.current = requestAnimationFrame(compute);\n };\n\n window.addEventListener(\"scroll\", onUpdate, true);\n window.addEventListener(\"resize\", onUpdate);\n\n return () => {\n cancelAnimationFrame(rafId.current);\n window.removeEventListener(\"scroll\", onUpdate, true);\n window.removeEventListener(\"resize\", onUpdate);\n };\n }, [open, compute]);\n\n return pos;\n}\n","import { useState, useRef, useCallback, useEffect, useId, type HTMLAttributes, type ReactNode, type Ref, createContext } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Slot, mergeRefs } from \"../lib/slot\";\nimport { usePosition, type Side } from \"../lib/use-position\";\n\ninterface TooltipProps extends Omit<HTMLAttributes<HTMLElement>, \"content\"> {\n content: ReactNode;\n children: ReactNode;\n side?: Side;\n delay?: number;\n className?: string;\n container?: Element | DocumentFragment;\n /**\n * A Tooltip can itself be the child of a Slot — `<DropdownMenu trigger={<Tooltip …><Button/></Tooltip>}>` —\n * so the ref and handlers a parent merges onto it are passed through to its own trigger (14 Sep 2026).\n */\n ref?: Ref<HTMLElement>;\n}\n\n// 600ms: long enough that passing the pointer over a toolbar, or landing focus\n// on an icon button, does not flash a label; short enough to still feel live.\n/** True inside an explicit <Tooltip>: an icon-size Button then skips the tooltip it would otherwise make from its aria-label (round 15, 52.7 — two tooltips on one button). */\nexport const TooltipCtx = createContext(false);\n\nexport function Tooltip({ content, children, side = \"top\", delay = 600, className, container, ref: forwardedRef, ...slotProps }: TooltipProps) {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n // Set when the trigger is activated: the label must not reappear (from the\n // focus the click leaves behind, or a still-pending timer) until the pointer\n // leaves and comes back.\n const suppressedRef = useRef(false);\n const id = useId();\n\n const pos = usePosition(triggerRef, floatingRef, open, { side, offset: 6, flip: true });\n\n const cancel = useCallback(() => {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }, []);\n\n // One pending timer at a time: hover and focus both call show(), and the\n // earlier timer used to be orphaned, firing after hide() had run and leaving\n // the tooltip up with nothing left to close it.\n const show = useCallback(() => {\n if (suppressedRef.current) return;\n cancel();\n timeoutRef.current = setTimeout(() => setOpen(true), delay);\n }, [delay, cancel]);\n\n const hide = useCallback(() => {\n cancel();\n setOpen(false);\n }, [cancel]);\n\n // Pointer focus (a click) does not warrant a label; keyboard focus does.\n const showOnFocus = useCallback((e: React.FocusEvent<HTMLElement>) => {\n let keyboard = true;\n try { keyboard = e.currentTarget.matches(\":focus-visible\"); } catch { /* selector unsupported: assume keyboard */ }\n if (keyboard) show();\n }, [show]);\n\n // Activating the trigger dismisses the label and keeps it away until the\n // pointer has left — what opens next (a menu, a dialog) owns the screen.\n const dismissOnActivate = useCallback(() => {\n suppressedRef.current = true;\n hide();\n }, [hide]);\n\n // Suppression lifts when the pointer leaves or focus moves on — a keyboard\n // user never produces a mouseleave, so blur must reset it too.\n const leave = useCallback(() => {\n suppressedRef.current = false;\n hide();\n }, [hide]);\n\n // Escape dismisses an open tooltip (WCAG 1.4.13); it does not stop the key\n // reaching whatever overlay is also listening.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, hide]);\n\n useEffect(() => cancel, [cancel]);\n\n return (\n <TooltipCtx value={true}>\n <Slot\n {...slotProps}\n ref={mergeRefs(triggerRef, forwardedRef)}\n onMouseEnter={(e) => { slotProps.onMouseEnter?.(e); show(); }}\n onMouseLeave={(e) => { slotProps.onMouseLeave?.(e); leave(); }}\n onFocus={(e) => { slotProps.onFocus?.(e); showOnFocus(e); }}\n onBlur={(e) => { slotProps.onBlur?.(e); leave(); }}\n onPointerDown={(e) => { slotProps.onPointerDown?.(e); dismissOnActivate(); }}\n onKeyDown={(e) => { slotProps.onKeyDown?.(e); if (e.key === \"Enter\" || e.key === \" \") dismissOnActivate(); }}\n aria-describedby={open ? id : undefined}\n >\n {children}\n </Slot>\n {open && createPortal(\n <div\n ref={floatingRef}\n id={id}\n role=\"tooltip\"\n className={cn(\n \"fixed z-[var(--ds-z-tooltip)] pointer-events-none\",\n \"px-ds-2.5 py-ds-1.5 text-xs\",\n \"max-w-[280px] break-words\",\n \"bg-[var(--ds-bg-surface-elevated)] text-[var(--ds-text-primary)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n className\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {content}\n </div>,\n container ?? document.body\n )}\n </TooltipCtx>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { useLatest } from \"./use-latest\";\n\n// ─── Shared ResizeObserver (singleton per document) ────────────────────────────\n// All useTextOverflow instances share a single ResizeObserver. This avoids the\n// performance cost of 80+ independent observers on pages with many truncated elements.\n\ntype OverflowCallback = () => void;\n\nconst registry = new WeakMap<Element, Set<OverflowCallback>>();\nlet sharedObserver: ResizeObserver | null = null;\nlet rafId: number | null = null;\nconst pendingElements = new Set<Element>();\n\nfunction getSharedObserver(): ResizeObserver {\n if (sharedObserver) return sharedObserver;\n sharedObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n pendingElements.add(entry.target);\n }\n if (rafId === null) {\n rafId = requestAnimationFrame(flush);\n }\n });\n return sharedObserver;\n}\n\nfunction flush() {\n rafId = null;\n for (const el of pendingElements) {\n const callbacks = registry.get(el);\n if (callbacks) {\n for (const cb of callbacks) cb();\n }\n }\n pendingElements.clear();\n}\n\nfunction observe(el: Element, callback: OverflowCallback) {\n let set = registry.get(el);\n if (!set) {\n set = new Set();\n registry.set(el, set);\n }\n set.add(callback);\n if (set.size === 1) {\n getSharedObserver().observe(el);\n }\n}\n\nfunction unobserve(el: Element, callback: OverflowCallback) {\n const set = registry.get(el);\n if (!set) return;\n set.delete(callback);\n if (set.size === 0) {\n registry.delete(el);\n getSharedObserver().unobserve(el);\n }\n}\n\n// ─── Overflow detection ────────────────────────────────────────────────────────\n\nconst TOLERANCE = 1; // px — accounts for integer rounding in scrollWidth/clientWidth\n\nfunction checkOverflow(el: HTMLElement, lines: number): boolean {\n if (lines <= 1) {\n return el.scrollWidth - el.clientWidth > TOLERANCE;\n }\n return el.scrollHeight - el.clientHeight > TOLERANCE;\n}\n\n// ─── Hook ──────────────────────────────────────────────────────────────────────\n\nexport interface UseTextOverflowOptions {\n /** Number of visible lines before truncation. 1 = single-line ellipsis, >1 = line-clamp. */\n lines?: number;\n /** Disable overflow detection (e.g. when component is hidden) */\n disabled?: boolean;\n /** Content key — triggers re-check when this value changes (use for dynamic text content) */\n content?: unknown;\n}\n\nexport interface UseTextOverflowResult {\n /** Callback ref — attach to the element that has truncation CSS applied */\n ref: (node: HTMLElement | null) => void;\n /** True when the element's text is currently overflowing/truncated */\n isTruncated: boolean;\n}\n\n/**\n * Detects whether an element's text content is overflowing (truncated).\n * Uses a single shared ResizeObserver for all instances on the page.\n *\n * @example\n * const { ref, isTruncated } = useTextOverflow({ lines: 1 });\n * return (\n * <Tooltip content={fullText} disabled={!isTruncated}>\n * <span ref={ref} className=\"truncate\">{fullText}</span>\n * </Tooltip>\n * );\n */\nexport function useTextOverflow(options: UseTextOverflowOptions = {}): UseTextOverflowResult {\n const { lines = 1, disabled = false, content } = options;\n const [isTruncated, setIsTruncated] = useState(false);\n const elementRef = useRef<HTMLElement | null>(null);\n // The observer-registered wrapper delegates to the latest check logic via checkRef.\n const registeredRef = useRef<OverflowCallback | null>(null);\n\n const doCheck = useCallback(() => {\n if (!elementRef.current || disabled) {\n setIsTruncated(false);\n return;\n }\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }, [lines, disabled]);\n\n // Latest check logic, updated in a layout effect so the stable wrapper never runs a stale closure.\n const checkRef = useLatest<OverflowCallback>(doCheck);\n\n // Re-check when content changes (handles dynamic text like Dropdown selection)\n // or when disabled state changes\n // Measures the committed DOM, so it must stay a post-layout effect.\n /* eslint-disable react-hooks/set-state-in-effect -- overflow is a layout measurement */\n useEffect(() => {\n if (disabled) {\n setIsTruncated(false);\n } else if (elementRef.current) {\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }\n }, [content, lines, disabled]);\n /* eslint-enable react-hooks/set-state-in-effect */\n\n const ref = useCallback(\n (node: HTMLElement | null) => {\n const prev = elementRef.current;\n\n // Unregister previous element using the SAME function we registered with\n if (prev && registeredRef.current) {\n unobserve(prev, registeredRef.current);\n registeredRef.current = null;\n }\n\n elementRef.current = node;\n\n if (node && !disabled) {\n // Stable wrapper that delegates to the latest check logic\n const wrapper: OverflowCallback = () => checkRef.current?.();\n registeredRef.current = wrapper;\n observe(node, wrapper);\n // Initial check on mount\n const initial = checkOverflow(node, lines);\n setIsTruncated(initial);\n }\n },\n [lines, disabled, checkRef],\n );\n\n // Cleanup on unmount — uses the registered wrapper, not the check function\n useEffect(() => {\n return () => {\n if (elementRef.current && registeredRef.current) {\n unobserve(elementRef.current, registeredRef.current);\n }\n };\n }, []);\n\n return { ref, isTruncated };\n}\n"]}
|