@mirror-physics/fractal-ui 0.2.0-next.8 → 0.2.0-next.82

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/index.cjs CHANGED
@@ -28,46 +28,91 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
33
  Alert: () => Alert,
34
34
  AlertDescription: () => AlertDescription,
35
+ AlertGhost: () => AlertGhost,
35
36
  AlertTitle: () => AlertTitle,
37
+ Breadcrumbs: () => Breadcrumbs,
36
38
  Button: () => Button,
39
+ ButtonGhost: () => ButtonGhost,
37
40
  Card: () => Card,
38
41
  CardContent: () => CardContent,
39
42
  CardDescription: () => CardDescription,
40
43
  CardFooter: () => CardFooter,
44
+ CardGhost: () => CardGhost,
41
45
  CardHeader: () => CardHeader,
42
46
  CardTitle: () => CardTitle,
47
+ Checkbox: () => Checkbox,
48
+ CheckboxGhost: () => CheckboxGhost,
43
49
  Chip: () => Chip,
50
+ ChipGhost: () => ChipGhost,
51
+ CodeBlock: () => CodeBlock,
52
+ ColumnEditor: () => ColumnEditor,
53
+ ContentSwitcher: () => ContentSwitcher,
54
+ ContentSwitcherGhost: () => ContentSwitcherGhost,
55
+ ConversationGhost: () => ConversationGhost,
44
56
  DataTable: () => DataTable,
57
+ DataTableGhost: () => DataTableGhost,
45
58
  Disclosure: () => Disclosure,
59
+ DisclosureGhost: () => DisclosureGhost,
46
60
  Dropdown: () => Dropdown,
61
+ DropdownGhost: () => DropdownGhost,
47
62
  FeatureCard: () => FeatureCard,
63
+ FileDropzone: () => FileDropzone,
64
+ FileDropzoneGhost: () => FileDropzoneGhost,
48
65
  FolderCard: () => FolderCard,
66
+ Ghost: () => Ghost,
67
+ GhostLine: () => GhostLine,
49
68
  Input: () => Input,
69
+ InputGhost: () => InputGhost,
50
70
  Label: () => Label,
71
+ LabelGhost: () => LabelGhost,
72
+ LatticeLoader: () => LatticeLoader,
51
73
  Link: () => Link,
74
+ LinkGhost: () => LinkGhost,
52
75
  Modal: () => Modal,
76
+ ModalGhost: () => ModalGhost,
77
+ NumberInput: () => NumberInput,
78
+ Pagination: () => Pagination,
79
+ PaginationGhost: () => PaginationGhost,
80
+ PasswordInput: () => PasswordInput,
53
81
  PromptCard: () => PromptCard,
82
+ PromptCardGhost: () => PromptCardGhost,
54
83
  PromptGrid: () => PromptGrid,
84
+ Radio: () => Radio,
55
85
  SidePanel: () => SidePanel,
86
+ SidePanelGhost: () => SidePanelGhost,
56
87
  Sidebar: () => Sidebar,
88
+ SidebarAccountMenu: () => SidebarAccountMenu,
57
89
  SidebarBrand: () => SidebarBrand,
58
90
  SidebarFooter: () => SidebarFooter,
91
+ SidebarGhost: () => SidebarGhost,
59
92
  SidebarHeader: () => SidebarHeader,
60
93
  SidebarNav: () => SidebarNav,
61
94
  SidebarNavItem: () => SidebarNavItem,
62
95
  SidebarSection: () => SidebarSection,
96
+ Slider: () => Slider,
63
97
  SortableTable: () => SortableTable,
98
+ SortableTableGhost: () => SortableTableGhost,
99
+ TableSearch: () => TableSearch,
64
100
  Tabs: () => Tabs,
101
+ TabsGhost: () => TabsGhost,
102
+ TextButton: () => TextButton,
103
+ TextButtonGhost: () => TextButtonGhost,
65
104
  Textarea: () => Textarea,
105
+ TextareaGhost: () => TextareaGhost,
106
+ Toast: () => Toast,
107
+ ToastViewport: () => ToastViewport,
66
108
  Toggle: () => Toggle,
109
+ ToggleGhost: () => ToggleGhost,
110
+ Tooltip: () => Tooltip,
111
+ UILoader: () => UILoader,
67
112
  cn: () => cn,
68
113
  useEscapeDismiss: () => useEscapeDismiss
69
114
  });
70
- module.exports = __toCommonJS(index_exports);
115
+ module.exports = __toCommonJS(src_exports);
71
116
 
72
117
  // src/components/Button/Button.tsx
73
118
  var React = __toESM(require("react"), 1);
@@ -118,51 +163,34 @@ var colorPalette = {
118
163
  700: "var(--green-700)"
119
164
  }
120
165
  };
121
- var sizeConfig = {
122
- xs: { height: "28px", padding: "4px 8px", iconOnly: "28px", fontSize: "0.75rem" },
123
- sm: { height: "32px", padding: "6px 12px", iconOnly: "32px", fontSize: "0.75rem" },
124
- md: { height: "40px", padding: "8px 16px", iconOnly: "40px", fontSize: "0.875rem" },
125
- lg: { height: "44px", padding: "10px 20px", iconOnly: "44px", fontSize: "1rem" },
126
- xl: { height: "56px", padding: "14px 28px", iconOnly: "56px", fontSize: "1rem" }
127
- };
128
- function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled, color, hasIconOnly) {
129
- const palette = colorPalette[color];
130
- const size = sizeConfig[buttonSize];
166
+ function getButtonStyles(buttonType, isHovered, isActive, disabled, color, hasIconOnly) {
167
+ const palette = color ? colorPalette[color] : void 0;
131
168
  const base = {
132
- height: size.height,
133
- padding: hasIconOnly ? "0" : size.padding,
134
- borderRadius: hasIconOnly ? "var(--radius-lg, 8px)" : "var(--radius-full, 9999px)",
169
+ borderRadius: "var(--radius-sm, 4px)",
135
170
  cursor: disabled ? "not-allowed" : "pointer",
136
- fontSize: size.fontSize,
137
171
  transition: "background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease"
138
172
  };
139
173
  if (hasIconOnly) {
140
- base.width = size.iconOnly;
141
174
  base.justifyContent = "center";
142
175
  }
143
176
  if (disabled) {
144
177
  if (buttonType === "ghost") {
145
- return { ...base, backgroundColor: "transparent", border: "none", color: "var(--fg-disabled)" };
178
+ return { ...base, backgroundColor: "transparent", border: "1px solid transparent", color: "var(--fg-disabled)" };
146
179
  }
147
180
  return { ...base, backgroundColor: "transparent", border: "1px solid var(--fg-disabled)", color: "var(--fg-disabled)" };
148
181
  }
149
182
  switch (buttonType) {
150
- // Primary with text: dark pill. Icon-only: color palette (Synapse style).
183
+ // Text and icon-only primary actions inherit the same scoped theme.
151
184
  case "primary":
152
- if (hasIconOnly) {
153
- return {
154
- ...base,
155
- backgroundColor: isActive ? palette[700] : isHovered ? palette[500] : palette[600],
156
- border: "none",
157
- color: "var(--fg-inverse)"
158
- };
159
- }
160
185
  return {
161
186
  ...base,
162
- backgroundColor: "var(--fg-primary)",
163
- border: "1px solid var(--fg-primary)",
164
- color: "var(--fg-inverse)",
165
- ...isHovered || isActive ? { opacity: 0.85 } : {}
187
+ border: "1px solid transparent",
188
+ ...palette ? {
189
+ "--button-primary-bg": palette[600],
190
+ "--button-primary-hover": palette[500],
191
+ "--button-primary-active": palette[700],
192
+ "--button-primary-fg": "var(--grey-0)"
193
+ } : {}
166
194
  };
167
195
  // Secondary: neutral. Rest=canvas, hover=surface-1 (darken), pressed=surface-2. Subtle border, dark text.
168
196
  case "secondary":
@@ -177,7 +205,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
177
205
  return {
178
206
  ...base,
179
207
  backgroundColor: isHovered || isActive ? "var(--bg-surface-1)" : "transparent",
180
- border: "none",
208
+ border: "1px solid transparent",
181
209
  color: "var(--fg-primary)"
182
210
  };
183
211
  // Danger: low-intensity destructive action. Text stays steady; hover deepens the light red surface.
@@ -185,7 +213,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
185
213
  return {
186
214
  ...base,
187
215
  backgroundColor: isHovered || isActive ? "var(--danger-button-surface-hover)" : "var(--danger-button-surface)",
188
- border: "none",
216
+ border: "1px solid transparent",
189
217
  color: "var(--danger-button-fg)"
190
218
  };
191
219
  default:
@@ -193,10 +221,10 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
193
221
  }
194
222
  }
195
223
  var Button = React.forwardRef(
196
- ({ className, buttonType = "primary", buttonSize = "md", color = "blue", icon, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
224
+ ({ className, buttonType = "primary", buttonSize = "lg", controlSize, color, icon, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
197
225
  const [isHovered, setIsHovered] = React.useState(false);
198
226
  const [isActive, setIsActive] = React.useState(false);
199
- const typeStyles = getButtonStyles(buttonType, isHovered, isActive, buttonSize, !!disabled, color, hasIconOnly);
227
+ const typeStyles = getButtonStyles(buttonType, isHovered, isActive, !!disabled, color, hasIconOnly);
200
228
  const hasIconAndText = icon && children && !hasIconOnly;
201
229
  const finalStyles = hasIconAndText ? { ...typeStyles, gap: "var(--space-2, 8px)", ...style } : { ...typeStyles, ...style };
202
230
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -204,7 +232,9 @@ var Button = React.forwardRef(
204
232
  {
205
233
  ref,
206
234
  type: "button",
207
- className: cn("fractal-button", `fractal-button--${buttonType}`, className),
235
+ "data-control-size": controlSize ?? buttonSize,
236
+ "data-icon-only": hasIconOnly || void 0,
237
+ className: cn("fractal-control", "fractal-button", `fractal-button--${buttonType}`, className),
208
238
  style: finalStyles,
209
239
  disabled,
210
240
  onMouseEnter: (e) => {
@@ -236,44 +266,409 @@ var Button = React.forwardRef(
236
266
  );
237
267
  Button.displayName = "Button";
238
268
 
239
- // src/components/Card/Card.tsx
240
- var React2 = __toESM(require("react"), 1);
269
+ // src/components/CodeBlock/CodeBlock.tsx
270
+ var import_react = require("react");
271
+ var import_react_codemirror = __toESM(require("@uiw/react-codemirror"), 1);
272
+ var import_language = require("@codemirror/language");
273
+ var import_lang_json = require("@codemirror/lang-json");
274
+ var import_view = require("@codemirror/view");
275
+ var import_highlight = require("@lezer/highlight");
241
276
  var import_fractal_icons = require("@mirror-physics/fractal-icons");
242
277
  var import_jsx_runtime2 = require("react/jsx-runtime");
278
+ var codeTheme = import_view.EditorView.theme({
279
+ "&": {
280
+ backgroundColor: "transparent",
281
+ color: "var(--fg-primary)",
282
+ fontSize: "0.75rem"
283
+ },
284
+ "&.cm-focused": { outline: "none" },
285
+ ".cm-scroller": {
286
+ fontFamily: "var(--font-mono)",
287
+ lineHeight: "1.55",
288
+ overscrollBehavior: "none"
289
+ },
290
+ ".cm-content": { padding: "10px 0" },
291
+ ".cm-line": {
292
+ padding: "0 12px 0 calc(12px + var(--fractal-code-wrap-indent, 0ch))",
293
+ textIndent: "calc(0px - var(--fractal-code-wrap-indent, 0ch))"
294
+ },
295
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--fg-primary)" },
296
+ ".cm-gutters": {
297
+ backgroundColor: "var(--bg-canvas)",
298
+ borderRight: "1px solid var(--border-subtle)",
299
+ color: "var(--fg-tertiary)"
300
+ },
301
+ ".cm-lineNumbers .cm-gutterElement": { padding: "0 10px 0 4px" },
302
+ ".cm-foldGutter": { width: "14px" },
303
+ ".cm-foldGutter .cm-gutterElement": {
304
+ position: "relative",
305
+ display: "block",
306
+ width: "14px",
307
+ padding: "0"
308
+ },
309
+ ".cm-foldGutter .cm-gutterElement > span": {
310
+ position: "absolute",
311
+ top: "2px",
312
+ left: "0",
313
+ display: "inline-flex",
314
+ width: "14px",
315
+ height: "14px",
316
+ alignItems: "center",
317
+ justifyContent: "center",
318
+ padding: "0",
319
+ color: "var(--fg-tertiary)",
320
+ lineHeight: "14px",
321
+ transition: "color 120ms ease"
322
+ },
323
+ '.cm-foldGutter .cm-gutterElement > span[title="Fold line"]': { transform: "translateY(-3px)" },
324
+ ".cm-foldGutter .cm-gutterElement > span:hover": { color: "var(--fg-secondary)" },
325
+ ".fractal-code-block__escape": { color: "var(--fg-secondary)" },
326
+ ".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "var(--bg-surface-1)" },
327
+ ".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection": {
328
+ backgroundColor: "var(--accent-surface-stroke) !important"
329
+ },
330
+ ".cm-searchMatch": {
331
+ backgroundColor: "var(--warning-surface)",
332
+ outline: "1px solid var(--warning-surface-stroke)"
333
+ },
334
+ ".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "var(--warning-subtle)" },
335
+ ".cm-panels": { backgroundColor: "var(--bg-surface-1)", color: "var(--fg-primary)" },
336
+ ".cm-tooltip": {
337
+ borderColor: "var(--border-subtle)",
338
+ backgroundColor: "var(--bg-canvas)",
339
+ color: "var(--fg-primary)"
340
+ }
341
+ });
342
+ var fractalHighlightStyle = import_language.HighlightStyle.define([
343
+ { tag: import_highlight.tags.comment, color: "var(--fg-tertiary)", fontStyle: "italic" },
344
+ { tag: [import_highlight.tags.keyword, import_highlight.tags.controlKeyword, import_highlight.tags.operatorKeyword], color: "var(--purple-default)" },
345
+ { tag: [import_highlight.tags.string, import_highlight.tags.special(import_highlight.tags.string)], color: "var(--purple-default)" },
346
+ { tag: [import_highlight.tags.number, import_highlight.tags.bool, import_highlight.tags.null], color: "var(--orange-default)" },
347
+ { tag: [import_highlight.tags.function(import_highlight.tags.variableName), import_highlight.tags.labelName], color: "var(--accent-default)" },
348
+ { tag: [import_highlight.tags.typeName, import_highlight.tags.className, import_highlight.tags.namespace], color: "var(--pink-default)" },
349
+ { tag: [import_highlight.tags.propertyName, import_highlight.tags.attributeName], color: "var(--success-default)" },
350
+ { tag: [import_highlight.tags.operator, import_highlight.tags.punctuation], color: "var(--fg-secondary)" },
351
+ { tag: import_highlight.tags.bracket, color: "var(--accent-default)" },
352
+ { tag: [import_highlight.tags.heading, import_highlight.tags.strong], color: "var(--fg-primary)", fontWeight: "600" },
353
+ { tag: import_highlight.tags.emphasis, fontStyle: "italic" },
354
+ { tag: import_highlight.tags.link, color: "var(--accent-default)", textDecoration: "underline" },
355
+ { tag: import_highlight.tags.invalid, color: "var(--error-default)", textDecoration: "underline wavy" }
356
+ ]);
357
+ var jsonSupport = (0, import_lang_json.json)();
358
+ var JSON_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["json", "jsonl", "ndjson", "geojson"]);
359
+ var JSON_LINES_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["jsonl", "ndjson"]);
360
+ var JSON_LINES_PRETTY_PRINT_THRESHOLD = 160;
361
+ var JSON_PROPERTY_PREFIX = /^(\s*"(?:\\.|[^"\\])*"\s*:\s*)/;
362
+ function countVisualColumns(value) {
363
+ let columns = 0;
364
+ for (const character of value) columns += character === " " ? 2 : 1;
365
+ return columns;
366
+ }
367
+ function getWrappedLineIndent(line) {
368
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
369
+ if (JSON_PROPERTY_PREFIX.test(line)) return Math.min(countVisualColumns(leadingWhitespace), 24);
370
+ return Math.min(countVisualColumns(leadingWhitespace) + 2, 12);
371
+ }
372
+ function buildWrappedLineIndentation(view) {
373
+ const decorations = [];
374
+ const decoratedLines = /* @__PURE__ */ new Set();
375
+ for (const visibleRange of view.visibleRanges) {
376
+ let position = visibleRange.from;
377
+ while (position <= visibleRange.to) {
378
+ const line = view.state.doc.lineAt(position);
379
+ if (!decoratedLines.has(line.from)) {
380
+ const indent = getWrappedLineIndent(line.text);
381
+ decorations.push(import_view.Decoration.line({
382
+ attributes: { style: `--fractal-code-wrap-indent: ${indent}ch` }
383
+ }).range(line.from));
384
+ decoratedLines.add(line.from);
385
+ }
386
+ if (line.to >= visibleRange.to) break;
387
+ position = line.to + 1;
388
+ }
389
+ }
390
+ return import_view.Decoration.set(decorations, true);
391
+ }
392
+ var wrappedLineIndentation = import_view.ViewPlugin.fromClass(class {
393
+ constructor(view) {
394
+ this.decorations = buildWrappedLineIndentation(view);
395
+ }
396
+ update(update) {
397
+ this.decorations = buildWrappedLineIndentation(update.view);
398
+ }
399
+ }, {
400
+ decorations: (plugin) => plugin.decorations
401
+ });
402
+ var jsonEscapeMatcher = new import_view.MatchDecorator({
403
+ regexp: /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g,
404
+ decoration: import_view.Decoration.mark({ class: "fractal-code-block__escape" })
405
+ });
406
+ var jsonEscapeHighlighting = import_view.ViewPlugin.fromClass(class {
407
+ constructor(view) {
408
+ this.decorations = jsonEscapeMatcher.createDeco(view);
409
+ }
410
+ update(update) {
411
+ this.decorations = jsonEscapeMatcher.updateDeco(update, this.decorations);
412
+ }
413
+ }, {
414
+ decorations: (plugin) => plugin.decorations
415
+ });
416
+ function formatJsonLinesForDisplay(value) {
417
+ return value.split(/\r?\n/).map((line) => {
418
+ const firstContentIndex = line.search(/\S/);
419
+ if (firstContentIndex < 0 || line.length - firstContentIndex <= JSON_LINES_PRETTY_PRINT_THRESHOLD) return line;
420
+ const indentation = line.slice(0, firstContentIndex);
421
+ try {
422
+ const parsed = JSON.parse(line.slice(firstContentIndex));
423
+ if (parsed === null || typeof parsed !== "object") return line;
424
+ return JSON.stringify(parsed, null, 2).split("\n").map((formattedLine) => `${indentation}${formattedLine}`).join("\n");
425
+ } catch {
426
+ return line;
427
+ }
428
+ }).join("\n");
429
+ }
430
+ function findJsonContainerEnd(document2, start) {
431
+ const openingCharacter = document2[start];
432
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
433
+ const closingCharacters = [];
434
+ let isInsideString = false;
435
+ let isEscaped = false;
436
+ for (let index = start; index < document2.length; index += 1) {
437
+ const character = document2[index];
438
+ if (isInsideString) {
439
+ if (isEscaped) {
440
+ isEscaped = false;
441
+ } else if (character === "\\") {
442
+ isEscaped = true;
443
+ } else if (character === '"') {
444
+ isInsideString = false;
445
+ }
446
+ continue;
447
+ }
448
+ if (character === '"') {
449
+ isInsideString = true;
450
+ } else if (character === "{") {
451
+ closingCharacters.push("}");
452
+ } else if (character === "[") {
453
+ closingCharacters.push("]");
454
+ } else if (character === "}" || character === "]") {
455
+ if (closingCharacters.pop() !== character) return null;
456
+ if (closingCharacters.length === 0) return index;
457
+ }
458
+ }
459
+ return null;
460
+ }
461
+ var jsonLinesFoldSupport = import_language.foldService.of((state, lineStart, lineEnd) => {
462
+ const line = state.doc.sliceString(lineStart, lineEnd);
463
+ const firstContentIndex = line.search(/\S/);
464
+ if (firstContentIndex < 0) return null;
465
+ const containerStart = lineStart + firstContentIndex;
466
+ const openingCharacter = line[firstContentIndex];
467
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
468
+ const document2 = state.doc.toString();
469
+ const containerEnd = findJsonContainerEnd(document2, containerStart);
470
+ if (containerEnd === null || containerEnd - containerStart < 2) return null;
471
+ try {
472
+ const parsed = JSON.parse(document2.slice(containerStart, containerEnd + 1));
473
+ if (parsed === null || typeof parsed !== "object") return null;
474
+ } catch {
475
+ return null;
476
+ }
477
+ return {
478
+ from: containerStart + 1,
479
+ to: containerEnd
480
+ };
481
+ });
482
+ function resolveLanguage(language, fileName) {
483
+ const normalized = language?.trim().toLowerCase();
484
+ const extension = fileName?.split(/[?#]/, 1)[0].split(".").pop()?.toLowerCase();
485
+ if (normalized && JSON_LANGUAGE_NAMES.has(normalized) || extension && JSON_LANGUAGE_NAMES.has(extension)) {
486
+ const isJsonLines = Boolean(
487
+ normalized && JSON_LINES_LANGUAGE_NAMES.has(normalized) || extension && JSON_LINES_LANGUAGE_NAMES.has(extension)
488
+ );
489
+ return { label: isJsonLines ? "JSONL" : "JSON", support: jsonSupport, isJsonLines };
490
+ }
491
+ if (normalized && !["text", "txt", "plaintext", "plain"].includes(normalized)) {
492
+ return { label: language?.trim(), support: null, isJsonLines: false };
493
+ }
494
+ return { support: null, isJsonLines: false };
495
+ }
496
+ function CodeBlock({
497
+ value,
498
+ language,
499
+ fileName,
500
+ label,
501
+ editable = false,
502
+ onChange,
503
+ lineNumbers = true,
504
+ wrap = true,
505
+ copyable = true,
506
+ ariaLabel,
507
+ height,
508
+ minHeight,
509
+ maxHeight = "28rem",
510
+ placeholder,
511
+ autoFocus,
512
+ className,
513
+ ...props
514
+ }) {
515
+ const resolvedLanguage = (0, import_react.useMemo)(() => resolveLanguage(language, fileName), [fileName, language]);
516
+ const displayValue = (0, import_react.useMemo)(
517
+ () => !editable && resolvedLanguage.isJsonLines ? formatJsonLinesForDisplay(value) : value,
518
+ [editable, resolvedLanguage.isJsonLines, value]
519
+ );
520
+ const [copyStatus, setCopyStatus] = (0, import_react.useState)("idle");
521
+ const copyResetTimer = (0, import_react.useRef)(null);
522
+ (0, import_react.useEffect)(() => () => {
523
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
524
+ }, []);
525
+ const extensions = (0, import_react.useMemo)(() => [
526
+ codeTheme,
527
+ (0, import_language.syntaxHighlighting)(fractalHighlightStyle),
528
+ ...resolvedLanguage.support ? [resolvedLanguage.support] : [],
529
+ ...resolvedLanguage.support ? [jsonEscapeHighlighting] : [],
530
+ ...resolvedLanguage.isJsonLines ? [jsonLinesFoldSupport] : [],
531
+ ...lineNumbers ? [(0, import_language.foldGutter)(), (0, import_view.lineNumbers)()] : [],
532
+ ...wrap ? [import_view.EditorView.lineWrapping, wrappedLineIndentation] : []
533
+ ], [lineNumbers, resolvedLanguage.isJsonLines, resolvedLanguage.support, wrap]);
534
+ const languageLabel = resolvedLanguage.label;
535
+ const toolbarLabel = label ?? fileName ?? languageLabel;
536
+ const surfaceLabel = ariaLabel ?? (fileName ? `${fileName} ${editable ? "editor" : "viewer"}` : `Code ${editable ? "editor" : "viewer"}`);
537
+ const copyCode = async () => {
538
+ try {
539
+ await navigator.clipboard.writeText(value);
540
+ setCopyStatus("copied");
541
+ } catch {
542
+ setCopyStatus("error");
543
+ }
544
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
545
+ copyResetTimer.current = setTimeout(() => setCopyStatus("idle"), 1800);
546
+ };
547
+ const showToolbar = toolbarLabel !== void 0 || copyable || editable;
548
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
549
+ "div",
550
+ {
551
+ className: cn(
552
+ "fractal-code-block",
553
+ editable && "fractal-code-block--editable",
554
+ !lineNumbers && "fractal-code-block--no-line-numbers",
555
+ className
556
+ ),
557
+ ...props,
558
+ children: [
559
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-code-block__toolbar", children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-code-block__identity", children: [
561
+ toolbarLabel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__label", title: typeof toolbarLabel === "string" ? toolbarLabel : void 0, children: toolbarLabel }),
562
+ fileName && languageLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__language", children: languageLabel }),
563
+ editable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__mode", children: "Editing" })
564
+ ] }),
565
+ copyable && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
566
+ "button",
567
+ {
568
+ type: "button",
569
+ className: "fractal-code-block__copy",
570
+ "aria-label": copyStatus === "copied" ? "Code copied" : "Copy code",
571
+ onClick: () => void copyCode(),
572
+ children: [
573
+ copyStatus === "copied" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Copy, { size: 14 }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copyStatus === "copied" ? "Copied" : copyStatus === "error" ? "Copy failed" : "Copy" })
575
+ ]
576
+ }
577
+ )
578
+ ] }),
579
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-code-block__surface", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
580
+ import_react_codemirror.default,
581
+ {
582
+ "aria-label": surfaceLabel,
583
+ value: displayValue,
584
+ editable,
585
+ readOnly: !editable,
586
+ onChange,
587
+ extensions,
588
+ theme: "none",
589
+ basicSetup: {
590
+ lineNumbers: false,
591
+ foldGutter: false,
592
+ drawSelection: editable,
593
+ highlightActiveLine: editable,
594
+ highlightActiveLineGutter: editable,
595
+ highlightSelectionMatches: editable,
596
+ autocompletion: editable,
597
+ history: editable
598
+ },
599
+ indentWithTab: editable,
600
+ height,
601
+ minHeight,
602
+ maxHeight,
603
+ placeholder,
604
+ autoFocus
605
+ }
606
+ ) }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__status", "aria-live": "polite", children: copyStatus === "copied" ? "Code copied to clipboard." : copyStatus === "error" ? "Code could not be copied." : "" })
608
+ ]
609
+ }
610
+ );
611
+ }
612
+
613
+ // src/components/Breadcrumbs/Breadcrumbs.tsx
614
+ var import_fractal_icons2 = require("@mirror-physics/fractal-icons");
615
+ var import_jsx_runtime3 = require("react/jsx-runtime");
616
+ function Breadcrumbs({
617
+ items,
618
+ ariaLabel = "Breadcrumbs",
619
+ separator = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_fractal_icons2.ChevronRight, { "aria-hidden": "true", size: 14 }),
620
+ className,
621
+ ...props
622
+ }) {
623
+ if (items.length === 0) return null;
624
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("nav", { "aria-label": ariaLabel, className: cn("fractal-breadcrumbs", className), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "fractal-breadcrumbs__list", children: items.map((item, index) => {
625
+ const isCurrent = index === items.length - 1;
626
+ const content = isCurrent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-current": "page", className: "fractal-breadcrumbs__current", children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { className: "fractal-breadcrumbs__link", href: item.href ?? "#", onClick: item.onClick, children: item.label });
627
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "fractal-breadcrumbs__item", children: [
628
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", className: "fractal-breadcrumbs__separator", children: separator }),
629
+ content
630
+ ] }, index);
631
+ }) }) });
632
+ }
633
+
634
+ // src/components/Card/Card.tsx
635
+ var React2 = __toESM(require("react"), 1);
636
+ var import_fractal_icons3 = require("@mirror-physics/fractal-icons");
637
+ var import_jsx_runtime4 = require("react/jsx-runtime");
243
638
  var Card = React2.forwardRef(
244
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card", className), ...props })
639
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, "data-fractal-border-surface": "surface-1", className: cn("fractal-card", className), ...props })
245
640
  );
246
641
  Card.displayName = "Card";
247
642
  var CardHeader = React2.forwardRef(
248
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-header", className), ...props })
643
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-header", className), ...props })
249
644
  );
250
645
  CardHeader.displayName = "CardHeader";
251
646
  var CardTitle = React2.forwardRef(
252
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { ref, className: cn("fractal-card-title", className), ...props })
647
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { ref, className: cn("fractal-card-title", className), ...props })
253
648
  );
254
649
  CardTitle.displayName = "CardTitle";
255
650
  var CardDescription = React2.forwardRef(
256
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { ref, className: cn("fractal-card-description", className), ...props })
651
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { ref, className: cn("fractal-card-description", className), ...props })
257
652
  );
258
653
  CardDescription.displayName = "CardDescription";
259
654
  var CardContent = React2.forwardRef(
260
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-content", className), ...props })
655
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-content", className), ...props })
261
656
  );
262
657
  CardContent.displayName = "CardContent";
263
658
  var CardFooter = React2.forwardRef(
264
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-footer", className), ...props })
659
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-footer", className), ...props })
265
660
  );
266
661
  CardFooter.displayName = "CardFooter";
267
662
  var FeatureCard = React2.forwardRef(
268
- ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
269
- icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-feature-card__icon", children: icon }),
270
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "fractal-feature-card__title", children: title }),
271
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fractal-feature-card__description", children: description })
663
+ ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
664
+ icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fractal-feature-card__icon", children: icon }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "fractal-feature-card__title", children: title }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fractal-feature-card__description", children: description })
272
667
  ] })
273
668
  );
274
669
  FeatureCard.displayName = "FeatureCard";
275
670
  var FolderCard = React2.forwardRef(
276
- ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
671
+ ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
277
672
  "div",
278
673
  {
279
674
  ref,
@@ -289,11 +684,11 @@ var FolderCard = React2.forwardRef(
289
684
  className: cn("fractal-folder-card", selected && "fractal-folder-card--selected", className),
290
685
  ...props,
291
686
  children: [
292
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-folder-card__header", children: [
293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Folder, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
294
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-folder-card__name", children: name })
687
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fractal-folder-card__header", children: [
688
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fractal_icons3.Folder, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
689
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fractal-folder-card__name", children: name })
295
690
  ] }),
296
- sessionCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "fractal-folder-card__sessions", children: [
691
+ sessionCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "fractal-folder-card__sessions", children: [
297
692
  sessionCount,
298
693
  " ",
299
694
  sessionCount === 1 ? "session" : "sessions"
@@ -306,7 +701,7 @@ FolderCard.displayName = "FolderCard";
306
701
 
307
702
  // src/components/PromptCard/PromptCard.tsx
308
703
  var React3 = __toESM(require("react"), 1);
309
- var import_jsx_runtime3 = require("react/jsx-runtime");
704
+ var import_jsx_runtime5 = require("react/jsx-runtime");
310
705
  var titleToPrompt = (title) => typeof title === "string" || typeof title === "number" ? String(title) : "";
311
706
  var PromptCard = React3.forwardRef(
312
707
  ({
@@ -327,7 +722,7 @@ var PromptCard = React3.forwardRef(
327
722
  onPromptSelect?.(prompt ?? titleToPrompt(title));
328
723
  }
329
724
  };
330
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
725
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
331
726
  "button",
332
727
  {
333
728
  ref,
@@ -342,8 +737,8 @@ var PromptCard = React3.forwardRef(
342
737
  onClick: handleClick,
343
738
  ...props,
344
739
  children: [
345
- icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
346
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fractal-prompt-card__title", children: title })
740
+ icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fractal-prompt-card__title", children: title })
347
742
  ]
348
743
  }
349
744
  );
@@ -351,7 +746,7 @@ var PromptCard = React3.forwardRef(
351
746
  );
352
747
  PromptCard.displayName = "PromptCard";
353
748
  var PromptGrid = React3.forwardRef(
354
- ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
749
+ ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
355
750
  "div",
356
751
  {
357
752
  ref,
@@ -369,28 +764,355 @@ PromptGrid.displayName = "PromptGrid";
369
764
 
370
765
  // src/components/Input/Input.tsx
371
766
  var React4 = __toESM(require("react"), 1);
372
- var import_jsx_runtime4 = require("react/jsx-runtime");
767
+ var import_jsx_runtime6 = require("react/jsx-runtime");
373
768
  var Input = React4.forwardRef(
374
- ({ className, type, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
375
- "input",
769
+ ({ className, type, controlSize = "lg", fieldLabel, id, placeholder, ...props }, ref) => {
770
+ const generatedId = React4.useId();
771
+ const fieldId = id ?? generatedId;
772
+ const floatingLabel = type === "search" ? void 0 : fieldLabel;
773
+ const input = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
774
+ "input",
775
+ {
776
+ type,
777
+ ref,
778
+ id: floatingLabel ? fieldId : id,
779
+ placeholder: floatingLabel ? placeholder || " " : placeholder,
780
+ "data-control-size": controlSize,
781
+ className: cn("fractal-control", "fractal-input", className),
782
+ ...props,
783
+ "aria-label": props["aria-label"] ?? (type === "search" && !props["aria-labelledby"] ? fieldLabel ?? "Search" : void 0)
784
+ }
785
+ );
786
+ if (!floatingLabel) return input;
787
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
788
+ "div",
789
+ {
790
+ className: "fractal-floating-field",
791
+ "data-control-size": controlSize,
792
+ "data-always-float": ["date", "datetime-local", "time", "month", "week", "color", "file", "range"].includes(type ?? "") || void 0,
793
+ children: [
794
+ input,
795
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: "fractal-floating-field__label", htmlFor: fieldId, children: fieldLabel })
796
+ ]
797
+ }
798
+ );
799
+ }
800
+ );
801
+ Input.displayName = "Input";
802
+
803
+ // src/components/TableSearch/TableSearch.tsx
804
+ var React5 = __toESM(require("react"), 1);
805
+ var import_fractal_icons4 = require("@mirror-physics/fractal-icons");
806
+ var import_jsx_runtime7 = require("react/jsx-runtime");
807
+ var TableSearch = React5.forwardRef(
808
+ ({
809
+ searchType = "text",
810
+ controlSize = "lg",
811
+ className,
812
+ inputClassName,
813
+ placeholder,
814
+ title,
815
+ ...props
816
+ }, ref) => {
817
+ const isGlob = searchType === "glob";
818
+ const SearchIcon = isGlob ? import_fractal_icons4.MagnifyingGlassLines : import_fractal_icons4.MagnifyingGlass;
819
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
820
+ "div",
821
+ {
822
+ className: cn("fractal-table-search", className),
823
+ "data-search-type": searchType,
824
+ "data-control-size": controlSize,
825
+ children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchIcon, { className: "fractal-table-search__icon", size: 17, "aria-hidden": "true" }),
827
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
828
+ Input,
829
+ {
830
+ ...props,
831
+ controlSize,
832
+ ref,
833
+ type: "search",
834
+ className: cn("fractal-table-search__input", inputClassName),
835
+ placeholder: placeholder ?? (isGlob ? "Glob search \u2014 use *, ?, or [a-z]" : "Search"),
836
+ title: title ?? (isGlob ? "Glob pattern: *, **, ?, [abc], [a-z], and [!abc]" : void 0)
837
+ }
838
+ )
839
+ ]
840
+ }
841
+ );
842
+ }
843
+ );
844
+ TableSearch.displayName = "TableSearch";
845
+
846
+ // src/components/NumberInput/NumberInput.tsx
847
+ var React6 = __toESM(require("react"), 1);
848
+ var import_fractal_icons5 = require("@mirror-physics/fractal-icons");
849
+ var import_jsx_runtime8 = require("react/jsx-runtime");
850
+ function NumberInput({
851
+ unit,
852
+ controlSize = "lg",
853
+ className,
854
+ value,
855
+ defaultValue,
856
+ disabled,
857
+ readOnly,
858
+ min,
859
+ max,
860
+ ...props
861
+ }) {
862
+ const inputRef = React6.useRef(null);
863
+ const numericValue = Number(value ?? defaultValue);
864
+ const characterCount = Math.max(String(value ?? defaultValue ?? "").length, 1);
865
+ const controlLabel = typeof props["aria-label"] === "string" ? props["aria-label"] : props.fieldLabel ?? "value";
866
+ const decrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && min !== void 0 && numericValue <= Number(min);
867
+ const incrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && max !== void 0 && numericValue >= Number(max);
868
+ const changeByStep = (direction) => {
869
+ const input = inputRef.current;
870
+ if (!input) return;
871
+ if (direction === -1) input.stepDown();
872
+ else input.stepUp();
873
+ input.dispatchEvent(new Event("input", { bubbles: true }));
874
+ input.focus();
875
+ };
876
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-control-size": controlSize, className: cn("fractal-number-input", unit && "fractal-number-input--with-unit", className), children: [
877
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
878
+ Input,
879
+ {
880
+ ref: inputRef,
881
+ controlSize,
882
+ type: "number",
883
+ value,
884
+ defaultValue,
885
+ disabled,
886
+ readOnly,
887
+ min,
888
+ max,
889
+ ...props
890
+ }
891
+ ),
892
+ unit && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
893
+ "span",
894
+ {
895
+ className: "fractal-number-input-unit",
896
+ style: { left: `calc(var(--control-padding-inline) + ${characterCount}ch)` },
897
+ "aria-hidden": "true",
898
+ children: unit
899
+ }
900
+ ),
901
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "fractal-number-input-controls", children: [
902
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
903
+ "button",
904
+ {
905
+ type: "button",
906
+ className: "fractal-number-input-step",
907
+ "aria-label": `Decrease ${controlLabel}`,
908
+ disabled: decrementDisabled,
909
+ onClick: () => changeByStep(-1),
910
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_fractal_icons5.Minus, { "aria-hidden": "true", size: 16 })
911
+ }
912
+ ),
913
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ className: "fractal-number-input-step",
918
+ "aria-label": `Increase ${controlLabel}`,
919
+ disabled: incrementDisabled,
920
+ onClick: () => changeByStep(1),
921
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_fractal_icons5.Plus, { "aria-hidden": "true", size: 16 })
922
+ }
923
+ )
924
+ ] })
925
+ ] });
926
+ }
927
+
928
+ // src/components/Slider/Slider.tsx
929
+ var import_jsx_runtime9 = require("react/jsx-runtime");
930
+ function Slider(props) {
931
+ const {
932
+ min = 0,
933
+ max = 100,
934
+ step = 1,
935
+ disabled = false,
936
+ className,
937
+ showValues = true,
938
+ formatValue = (value) => value,
939
+ knobs = 1
940
+ } = props;
941
+ const span = Math.max(max - min, 1);
942
+ const rawValues = knobs === 2 ? props.value : [props.value];
943
+ const firstValue = clamp(rawValues[0], min, max);
944
+ const secondValue = knobs === 2 ? clamp(rawValues[1], firstValue, max) : firstValue;
945
+ const selectionStart = knobs === 2 ? firstValue : min;
946
+ const selectionEnd = secondValue;
947
+ const selectionStartPercent = (selectionStart - min) / span * 100;
948
+ const selectionEndPercent = (selectionEnd - min) / span * 100;
949
+ const changeSingleValue = (value) => {
950
+ ;
951
+ props.onValueChange(clamp(value, min, max));
952
+ };
953
+ const changeRangeValue = (nextLower, nextUpper) => {
954
+ ;
955
+ props.onValueChange([
956
+ clamp(nextLower, min, nextUpper),
957
+ clamp(nextUpper, nextLower, max)
958
+ ]);
959
+ };
960
+ const getKeyboardValue = (event, value) => {
961
+ const pageStep = step * 10;
962
+ const adjustments = {
963
+ ArrowDown: -step,
964
+ ArrowLeft: -step,
965
+ ArrowRight: step,
966
+ ArrowUp: step,
967
+ PageDown: -pageStep,
968
+ PageUp: pageStep
969
+ };
970
+ if (event.key === "Home") return min;
971
+ if (event.key === "End") return max;
972
+ if (!(event.key in adjustments)) return null;
973
+ return value + adjustments[event.key];
974
+ };
975
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
976
+ "div",
376
977
  {
377
- type,
378
- ref,
379
- className: cn("fractal-input", className),
380
- ...props
978
+ className: cn("fractal-slider", disabled && "fractal-slider--disabled", className),
979
+ "data-disabled": disabled || void 0,
980
+ "data-knobs": knobs,
981
+ children: [
982
+ showValues && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "fractal-slider-values", "aria-hidden": "true", children: [
983
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("output", { children: formatValue(firstValue) }),
984
+ knobs === 2 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("output", { children: formatValue(secondValue) })
985
+ ] }),
986
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
987
+ "div",
988
+ {
989
+ className: "fractal-slider-control",
990
+ style: {
991
+ "--fractal-slider-selection-start": `${selectionStartPercent}%`,
992
+ "--fractal-slider-selection-end": `${selectionEndPercent}%`
993
+ },
994
+ children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "fractal-slider-track", "aria-hidden": "true" }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "fractal-slider-selection", "aria-hidden": "true" }),
997
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
998
+ "input",
999
+ {
1000
+ "aria-label": knobs === 2 ? props.lowerAriaLabel ?? "Lower value" : props.ariaLabel ?? "Slider value",
1001
+ disabled,
1002
+ max,
1003
+ min,
1004
+ onChange: (event) => {
1005
+ const nextValue = event.currentTarget.valueAsNumber;
1006
+ if (knobs === 2) changeRangeValue(Math.min(nextValue, secondValue), secondValue);
1007
+ else changeSingleValue(nextValue);
1008
+ },
1009
+ onKeyDown: (event) => {
1010
+ const nextValue = getKeyboardValue(event, firstValue);
1011
+ if (nextValue === null) return;
1012
+ event.preventDefault();
1013
+ if (knobs === 2) changeRangeValue(Math.min(nextValue, secondValue), secondValue);
1014
+ else changeSingleValue(nextValue);
1015
+ },
1016
+ step,
1017
+ type: "range",
1018
+ value: firstValue
1019
+ }
1020
+ ),
1021
+ knobs === 2 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1022
+ "input",
1023
+ {
1024
+ "aria-label": props.upperAriaLabel ?? "Upper value",
1025
+ disabled,
1026
+ max,
1027
+ min,
1028
+ onChange: (event) => {
1029
+ const nextValue = event.currentTarget.valueAsNumber;
1030
+ changeRangeValue(firstValue, Math.max(nextValue, firstValue));
1031
+ },
1032
+ onKeyDown: (event) => {
1033
+ const nextValue = getKeyboardValue(event, secondValue);
1034
+ if (nextValue === null) return;
1035
+ event.preventDefault();
1036
+ changeRangeValue(firstValue, Math.max(nextValue, firstValue));
1037
+ },
1038
+ step,
1039
+ type: "range",
1040
+ value: secondValue
1041
+ }
1042
+ )
1043
+ ]
1044
+ }
1045
+ )
1046
+ ]
381
1047
  }
382
- )
1048
+ );
1049
+ }
1050
+ function clamp(value, minimum, maximum) {
1051
+ return Math.min(maximum, Math.max(minimum, value));
1052
+ }
1053
+
1054
+ // src/components/PasswordInput/PasswordInput.tsx
1055
+ var React7 = __toESM(require("react"), 1);
1056
+ var import_fractal_icons6 = require("@mirror-physics/fractal-icons");
1057
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1058
+ var PasswordInput = React7.forwardRef(
1059
+ ({
1060
+ className,
1061
+ controlSize = "lg",
1062
+ disabled,
1063
+ showPasswordLabel = "Show password",
1064
+ hidePasswordLabel = "Hide password",
1065
+ ...props
1066
+ }, ref) => {
1067
+ const [passwordVisible, setPasswordVisible] = React7.useState(false);
1068
+ const toggleLabel = passwordVisible ? hidePasswordLabel : showPasswordLabel;
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1070
+ "div",
1071
+ {
1072
+ className: cn("fractal-password-input", className),
1073
+ "data-control-size": controlSize,
1074
+ "data-password-visible": passwordVisible ? "" : void 0,
1075
+ children: [
1076
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1077
+ Input,
1078
+ {
1079
+ ...props,
1080
+ controlSize,
1081
+ ref,
1082
+ className: "fractal-password-input-field",
1083
+ disabled,
1084
+ type: passwordVisible ? "text" : "password"
1085
+ }
1086
+ ),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1088
+ "button",
1089
+ {
1090
+ type: "button",
1091
+ className: "fractal-password-input-toggle",
1092
+ "aria-label": toggleLabel,
1093
+ "aria-pressed": passwordVisible,
1094
+ disabled,
1095
+ title: toggleLabel,
1096
+ onClick: () => setPasswordVisible((visible) => !visible),
1097
+ children: passwordVisible ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_fractal_icons6.EyeSlash, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_fractal_icons6.Eye, { "aria-hidden": "true", size: 18 })
1098
+ }
1099
+ )
1100
+ ]
1101
+ }
1102
+ );
1103
+ }
383
1104
  );
384
- Input.displayName = "Input";
1105
+ PasswordInput.displayName = "PasswordInput";
385
1106
 
386
1107
  // src/components/Label/Label.tsx
387
- var React5 = __toESM(require("react"), 1);
388
- var import_jsx_runtime5 = require("react/jsx-runtime");
389
- var Label = React5.forwardRef(
390
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1108
+ var React8 = __toESM(require("react"), 1);
1109
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1110
+ var Label = React8.forwardRef(
1111
+ ({ className, controlSize = "lg", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
391
1112
  "label",
392
1113
  {
393
1114
  ref,
1115
+ "data-control-size": controlSize,
394
1116
  className: cn("fractal-label", className),
395
1117
  ...props
396
1118
  }
@@ -399,44 +1121,147 @@ var Label = React5.forwardRef(
399
1121
  Label.displayName = "Label";
400
1122
 
401
1123
  // src/components/Alert/Alert.tsx
402
- var React6 = __toESM(require("react"), 1);
403
- var import_jsx_runtime6 = require("react/jsx-runtime");
1124
+ var React9 = __toESM(require("react"), 1);
1125
+ var import_fractal_icons7 = require("@mirror-physics/fractal-icons");
1126
+ var import_jsx_runtime12 = require("react/jsx-runtime");
404
1127
  var defaultIcons = {
405
- default: "\u2139",
406
- info: "\u2139",
407
- success: "\u2713",
408
- warning: "\u26A0",
409
- error: "\u2715"
1128
+ default: import_fractal_icons7.CircleInfo,
1129
+ info: import_fractal_icons7.CircleInfo,
1130
+ success: import_fractal_icons7.CircleCheck,
1131
+ warning: import_fractal_icons7.Warning,
1132
+ error: import_fractal_icons7.CircleClose
1133
+ };
1134
+ var Alert = React9.forwardRef(
1135
+ ({ className, variant = "default", icon, children, ...props }, ref) => {
1136
+ const DefaultIcon = defaultIcons[variant];
1137
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1138
+ "div",
1139
+ {
1140
+ ref,
1141
+ role: "alert",
1142
+ className: cn("fractal-alert", `fractal-alert--${variant}`, className),
1143
+ ...props,
1144
+ children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DefaultIcon, { size: 18 }) }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "fractal-alert-content", children })
1147
+ ]
1148
+ }
1149
+ );
1150
+ }
1151
+ );
1152
+ Alert.displayName = "Alert";
1153
+ var AlertTitle = React9.forwardRef(
1154
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h5", { ref, className: cn("fractal-alert-title", className), ...props })
1155
+ );
1156
+ AlertTitle.displayName = "AlertTitle";
1157
+ var AlertDescription = React9.forwardRef(
1158
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { ref, className: cn("fractal-alert-description", className), ...props })
1159
+ );
1160
+ AlertDescription.displayName = "AlertDescription";
1161
+
1162
+ // src/components/Toast/Toast.tsx
1163
+ var React10 = __toESM(require("react"), 1);
1164
+ var import_react_dom = require("react-dom");
1165
+ var import_fractal_icons8 = require("@mirror-physics/fractal-icons");
1166
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1167
+ var defaultIcons2 = {
1168
+ default: import_fractal_icons8.CircleInfo,
1169
+ info: import_fractal_icons8.CircleInfo,
1170
+ success: import_fractal_icons8.CircleCheck,
1171
+ warning: import_fractal_icons8.Warning,
1172
+ error: import_fractal_icons8.CircleClose
410
1173
  };
411
- var Alert = React6.forwardRef(
412
- ({ className, variant = "default", icon, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1174
+ var Toast = React10.forwardRef(function Toast2({
1175
+ action,
1176
+ children,
1177
+ className,
1178
+ dismissLabel = "Dismiss notification",
1179
+ duration,
1180
+ icon,
1181
+ onBlurCapture,
1182
+ onDismiss,
1183
+ onFocusCapture,
1184
+ onPointerEnter,
1185
+ onPointerLeave,
1186
+ role,
1187
+ variant = "default",
1188
+ ...props
1189
+ }, ref) {
1190
+ const [paused, setPaused] = React10.useState(false);
1191
+ const dismissRef = React10.useRef(onDismiss);
1192
+ const DefaultIcon = defaultIcons2[variant];
1193
+ const resolvedDuration = duration === void 0 ? variant === "error" ? null : 5e3 : duration;
1194
+ React10.useEffect(() => {
1195
+ dismissRef.current = onDismiss;
1196
+ }, [onDismiss]);
1197
+ React10.useEffect(() => {
1198
+ if (!dismissRef.current || paused || resolvedDuration === null || resolvedDuration <= 0) return;
1199
+ const timeout = window.setTimeout(() => dismissRef.current?.(), resolvedDuration);
1200
+ return () => window.clearTimeout(timeout);
1201
+ }, [paused, resolvedDuration]);
1202
+ const announcementRole = role ?? (variant === "error" ? "alert" : "status");
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
413
1204
  "div",
414
1205
  {
415
1206
  ref,
416
- role: "alert",
417
- className: cn("fractal-alert", `fractal-alert--${variant}`, className),
1207
+ "aria-atomic": "true",
1208
+ "aria-live": announcementRole === "alert" ? "assertive" : "polite",
1209
+ className: cn("fractal-toast", `fractal-toast--${variant}`, className),
1210
+ role: announcementRole,
1211
+ onBlurCapture: (event) => {
1212
+ onBlurCapture?.(event);
1213
+ if (!event.currentTarget.contains(event.relatedTarget)) setPaused(false);
1214
+ },
1215
+ onFocusCapture: (event) => {
1216
+ onFocusCapture?.(event);
1217
+ setPaused(true);
1218
+ },
1219
+ onPointerEnter: (event) => {
1220
+ onPointerEnter?.(event);
1221
+ setPaused(true);
1222
+ },
1223
+ onPointerLeave: (event) => {
1224
+ onPointerLeave?.(event);
1225
+ setPaused(false);
1226
+ },
418
1227
  ...props,
419
1228
  children: [
420
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? defaultIcons[variant] }),
421
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fractal-alert-content", children })
1229
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { "aria-hidden": "true", className: "fractal-toast__icon", children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DefaultIcon, { size: 18 }) }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-toast__content", children }),
1231
+ action && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-toast__action", children: action }),
1232
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1233
+ "button",
1234
+ {
1235
+ "aria-label": dismissLabel,
1236
+ className: "fractal-toast__dismiss",
1237
+ onClick: onDismiss,
1238
+ type: "button",
1239
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons8.Close, { "aria-hidden": "true", size: 14 })
1240
+ }
1241
+ )
422
1242
  ]
423
1243
  }
424
- )
425
- );
426
- Alert.displayName = "Alert";
427
- var AlertTitle = React6.forwardRef(
428
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h5", { ref, className: cn("fractal-alert-title", className), ...props })
429
- );
430
- AlertTitle.displayName = "AlertTitle";
431
- var AlertDescription = React6.forwardRef(
432
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: cn("fractal-alert-description", className), ...props })
433
- );
434
- AlertDescription.displayName = "AlertDescription";
1244
+ );
1245
+ });
1246
+ var ToastViewport = React10.forwardRef(function ToastViewport2({ children, className, portal = true, position = "top-center", ...props }, ref) {
1247
+ const viewport = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1248
+ "div",
1249
+ {
1250
+ ref,
1251
+ className: cn("fractal-toast-viewport", `fractal-toast-viewport--${position}`, className),
1252
+ "data-position": position,
1253
+ ...props,
1254
+ children
1255
+ }
1256
+ );
1257
+ if (!portal || typeof document === "undefined") return viewport;
1258
+ return (0, import_react_dom.createPortal)(viewport, document.body);
1259
+ });
435
1260
 
436
1261
  // src/components/Toggle/Toggle.tsx
437
- var import_jsx_runtime7 = require("react/jsx-runtime");
1262
+ var import_jsx_runtime14 = require("react/jsx-runtime");
438
1263
  function Toggle({ checked, onChange, label, className }) {
439
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1264
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
440
1265
  "button",
441
1266
  {
442
1267
  type: "button",
@@ -445,18 +1270,19 @@ function Toggle({ checked, onChange, label, className }) {
445
1270
  className: cn("fractal-toggle", className),
446
1271
  onClick: () => onChange(!checked),
447
1272
  children: [
448
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
449
- label && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "fractal-toggle-label", children: label })
1273
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
1274
+ label && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "fractal-toggle-label", children: label })
450
1275
  ]
451
1276
  }
452
1277
  );
453
1278
  }
454
1279
 
455
1280
  // src/components/Modal/Modal.tsx
456
- var import_react_dom = require("react-dom");
457
-
1281
+ var React11 = __toESM(require("react"), 1);
1282
+ var import_react_dom2 = require("react-dom");
1283
+
458
1284
  // src/hooks/useEscapeDismiss.ts
459
- var import_react = require("react");
1285
+ var import_react2 = require("react");
460
1286
  var nextId = 0;
461
1287
  var stack = [];
462
1288
  function insertSorted(entry) {
@@ -489,9 +1315,9 @@ function ensureGlobalListener() {
489
1315
  );
490
1316
  }
491
1317
  function useEscapeDismiss(priority, handler, enabled = true) {
492
- const handlerRef = (0, import_react.useRef)(handler);
1318
+ const handlerRef = (0, import_react2.useRef)(handler);
493
1319
  handlerRef.current = handler;
494
- (0, import_react.useEffect)(() => {
1320
+ (0, import_react2.useEffect)(() => {
495
1321
  if (!enabled) return;
496
1322
  ensureGlobalListener();
497
1323
  const id = nextId++;
@@ -505,27 +1331,99 @@ function useEscapeDismiss(priority, handler, enabled = true) {
505
1331
  }, [priority, enabled]);
506
1332
  }
507
1333
 
1334
+ // src/components/UILoader/UILoader.tsx
1335
+ var import_react3 = require("react");
1336
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1337
+ var LEFT = "M0 53C0 51 1.6 49.5 3.6 49.5h16.5c1 0 1.9.4 2.5 1L42.1 69.6c.7.7 1.1 1.6 1.1 2.5v16.2c0 2-1.6 3.5-3.6 3.5H23.1c-1 0-1.9-.4-2.5-1L1.1 71.7C.4 71 0 70.1 0 69.2V53Z";
1338
+ var CENTER = "M0 3.5C0 1.6 1.6 0 3.6 0h16.5c1 0 1.9.4 2.5 1L92.5 69.6c.7.7 1 1.6 1 2.5v16.2c0 2-1.6 3.5-3.6 3.5H73.4c-1 0-1.9-.4-2.5-1L1.1 22.2C.4 21.6 0 20.7 0 19.7V3.5Z";
1339
+ var RIGHT = "M50.4 3.5C50.4 1.6 52 0 54 0h16.5c1 0 1.9.4 2.5 1l69.8 68.6c.7.7 1.1 1.6 1.1 2.5v16.2c0 2-1.6 3.5-3.6 3.5h-16.5c-1 0-1.9-.4-2.5-1L51.4 22.2c-.7-.6-1-1.5-1-2.5V3.5Z";
1340
+ function UILoader({ size = 48, tone = "neutral", className, style, label = "Loading", ariaHidden = false }) {
1341
+ const rawId = (0, import_react3.useId)().replace(/[^a-zA-Z0-9_-]/g, "");
1342
+ const clipId = `fractal-ui-loader-clip-${rawId}`;
1343
+ const gradientId = `fractal-ui-loader-gradient-${rawId}`;
1344
+ const height = typeof size === "number" ? `${size}px` : size;
1345
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1346
+ "span",
1347
+ {
1348
+ "aria-hidden": ariaHidden || void 0,
1349
+ "aria-label": ariaHidden ? void 0 : label,
1350
+ className: cn("fractal-ui-loader", `fractal-ui-loader--${tone}`, className),
1351
+ role: ariaHidden ? void 0 : "status",
1352
+ style,
1353
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { "aria-hidden": "true", fill: "none", viewBox: "0 0 144 92", style: { height, width: "auto" }, children: [
1354
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("defs", { children: [
1355
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("linearGradient", { id: gradientId, x1: "59", x2: "94.3", y1: "0", y2: "89.1", gradientUnits: "userSpaceOnUse", children: [
1356
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("stop", { className: "fractal-ui-loader__stop-start", offset: "0" }),
1357
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("stop", { className: "fractal-ui-loader__stop-end", offset: "1" })
1358
+ ] }),
1359
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("linearGradient", { id: `${gradientId}-shine`, x1: "0", x2: "1", y1: "0", y2: "0", children: [
1360
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("stop", { offset: "0.4", stopColor: "white", stopOpacity: "0" }),
1361
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("stop", { offset: "0.5", stopColor: "white", stopOpacity: "0.62" }),
1362
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("stop", { offset: "0.6", stopColor: "white", stopOpacity: "0" })
1363
+ ] }),
1364
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("clipPath", { id: clipId, children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: LEFT }),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: CENTER }),
1367
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: RIGHT })
1368
+ ] })
1369
+ ] }),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: CENTER, fill: `url(#${gradientId})` }),
1371
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: RIGHT, fill: `url(#${gradientId})` }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: LEFT, fill: `url(#${gradientId})` }),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { className: "fractal-ui-loader__shine-axis", transform: "translate(72 46) rotate(68.4)", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { className: "fractal-ui-loader__shine", x: "-160", y: "-100", width: "320", height: "200", fill: `url(#${gradientId}-shine)` }) }) })
1374
+ ] })
1375
+ }
1376
+ );
1377
+ }
1378
+
508
1379
  // src/components/Modal/Modal.tsx
509
- var import_jsx_runtime8 = require("react/jsx-runtime");
510
- var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
511
- function Modal({ open, onClose, title, children, danger, warn, footer, noClose, size = "sm", className }) {
512
- useEscapeDismiss(80, onClose, open && !noClose);
1380
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1381
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1382
+ function Modal({ open, onClose, title, children, danger, warn, footer, noClose, closeDisabled = false, size = "sm", loading = false, loadingLabel = "Loading", scrollable = false, className }) {
1383
+ const [bodyScrolled, setBodyScrolled] = React11.useState(false);
1384
+ useEscapeDismiss(80, onClose, open && !noClose && !closeDisabled);
1385
+ React11.useEffect(() => {
1386
+ if (open) setBodyScrolled(false);
1387
+ }, [open]);
513
1388
  if (!open) return null;
514
1389
  const sizeClass = `fractal-modal-panel--${size}`;
515
1390
  const accentClass = danger ? "fractal-modal-panel--danger" : warn ? "fractal-modal-panel--warn" : "";
516
- return (0, import_react_dom.createPortal)(
517
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fractal-modal-overlay", onClick: noClose ? void 0 : onClose, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1391
+ return (0, import_react_dom2.createPortal)(
1392
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "fractal-modal-overlay", onClick: noClose || closeDisabled ? void 0 : onClose, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
518
1393
  "div",
519
1394
  {
520
- className: cn("fractal-modal-panel", sizeClass, accentClass, className),
1395
+ className: cn("fractal-modal-panel", sizeClass, accentClass, scrollable && "fractal-modal-panel--scrollable", className),
521
1396
  onClick: (e) => e.stopPropagation(),
522
1397
  children: [
523
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "fractal-modal-title-row", children: [
524
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
525
- !noClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: onClose, title: "Close", className: "fractal-modal-close", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CloseIcon, {}) })
526
- ] }),
527
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("fractal-modal-body", size === "lg" && "fractal-modal-body--lg"), children }),
528
- footer && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fractal-modal-footer", children: footer })
1398
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1399
+ "div",
1400
+ {
1401
+ className: cn(
1402
+ "fractal-modal-title-row",
1403
+ scrollable && "fractal-modal-title-row--scrollable",
1404
+ scrollable && bodyScrolled && "fractal-modal-title-row--scrolled"
1405
+ ),
1406
+ children: [
1407
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
1408
+ !noClose && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick: onClose, title: "Close", disabled: closeDisabled, className: "fractal-modal-close", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CloseIcon, {}) })
1409
+ ]
1410
+ }
1411
+ ),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1413
+ "div",
1414
+ {
1415
+ "aria-busy": loading || void 0,
1416
+ className: cn(
1417
+ "fractal-modal-body",
1418
+ size === "lg" && "fractal-modal-body--lg",
1419
+ loading && "fractal-modal-body--loading",
1420
+ scrollable && "fractal-modal-body--scrollable"
1421
+ ),
1422
+ onScroll: scrollable ? (event) => setBodyScrolled(event.currentTarget.scrollTop > 0) : void 0,
1423
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(UILoader, { label: loadingLabel }) : children
1424
+ }
1425
+ ),
1426
+ footer && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cn("fractal-modal-footer", scrollable && "fractal-modal-footer--divided"), children: footer })
529
1427
  ]
530
1428
  }
531
1429
  ) }),
@@ -533,94 +1431,441 @@ function Modal({ open, onClose, title, children, danger, warn, footer, noClose,
533
1431
  );
534
1432
  }
535
1433
 
1434
+ // src/components/TableLayout/TableOverflowMenu.tsx
1435
+ var import_react4 = require("react");
1436
+ var import_react_dom3 = require("react-dom");
1437
+ var import_fractal_icons9 = require("@mirror-physics/fractal-icons");
1438
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1439
+ function TableOverflowMenu({ items, label, disabled, onSelect }) {
1440
+ const id = (0, import_react4.useId)();
1441
+ const trigger = (0, import_react4.useRef)(null);
1442
+ const panel = (0, import_react4.useRef)(null);
1443
+ const initialLast = (0, import_react4.useRef)(false);
1444
+ const [open, setOpen] = (0, import_react4.useState)(false);
1445
+ const [position, setPosition] = (0, import_react4.useState)({ top: 0, left: 0 });
1446
+ const buttons = () => [...panel.current?.querySelectorAll("button:not(:disabled)") ?? []];
1447
+ const close = (restore = true) => {
1448
+ setOpen(false);
1449
+ if (restore) trigger.current?.focus({ preventScroll: true });
1450
+ };
1451
+ useEscapeDismiss(100, () => close(), open);
1452
+ (0, import_react4.useLayoutEffect)(() => {
1453
+ if (!open || !trigger.current || !panel.current) return;
1454
+ const place = () => {
1455
+ const anchor = trigger.current.getBoundingClientRect();
1456
+ const menu = panel.current.getBoundingClientRect();
1457
+ const above = window.innerHeight - anchor.bottom < menu.height + 8 && anchor.top > window.innerHeight - anchor.bottom;
1458
+ setPosition({
1459
+ top: Math.max(8, Math.min(above ? anchor.top - menu.height - 4 : anchor.bottom + 4, window.innerHeight - menu.height - 8)),
1460
+ left: Math.max(8, Math.min(anchor.right - menu.width, window.innerWidth - menu.width - 8))
1461
+ });
1462
+ };
1463
+ place();
1464
+ const enabled = buttons();
1465
+ (initialLast.current ? enabled[enabled.length - 1] : enabled[0])?.focus({ preventScroll: true });
1466
+ if (enabled.length === 0) panel.current.focus({ preventScroll: true });
1467
+ const outside = (event) => {
1468
+ if (!panel.current?.contains(event.target) && !trigger.current?.contains(event.target)) setOpen(false);
1469
+ };
1470
+ const scroll = (event) => {
1471
+ if (!panel.current?.contains(event.target)) setOpen(false);
1472
+ };
1473
+ const observer = new ResizeObserver(place);
1474
+ observer.observe(panel.current);
1475
+ document.addEventListener("pointerdown", outside);
1476
+ window.addEventListener("scroll", scroll, true);
1477
+ window.addEventListener("resize", place);
1478
+ return () => {
1479
+ observer.disconnect();
1480
+ document.removeEventListener("pointerdown", outside);
1481
+ window.removeEventListener("scroll", scroll, true);
1482
+ window.removeEventListener("resize", place);
1483
+ };
1484
+ }, [open]);
1485
+ (0, import_react4.useLayoutEffect)(() => {
1486
+ if (disabled || items.length === 0) setOpen(false);
1487
+ }, [disabled, items.length]);
1488
+ if (items.length === 0) return null;
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "fractal-table-overflow-trigger", children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1491
+ Button,
1492
+ {
1493
+ ref: trigger,
1494
+ buttonType: "ghost",
1495
+ controlSize: "sm",
1496
+ hasIconOnly: true,
1497
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fractal_icons9.Ellipsis, { "aria-hidden": true }),
1498
+ "aria-label": label,
1499
+ "aria-haspopup": "menu",
1500
+ "aria-expanded": open,
1501
+ "aria-controls": open ? id : void 0,
1502
+ disabled,
1503
+ onClick: () => {
1504
+ initialLast.current = false;
1505
+ setOpen(!open);
1506
+ },
1507
+ onKeyDown: (event) => {
1508
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1509
+ event.preventDefault();
1510
+ initialLast.current = event.key === "ArrowUp";
1511
+ setOpen(true);
1512
+ }
1513
+ }
1514
+ }
1515
+ ),
1516
+ open && (0, import_react_dom3.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1517
+ "div",
1518
+ {
1519
+ ref: panel,
1520
+ id,
1521
+ role: "menu",
1522
+ tabIndex: -1,
1523
+ "aria-label": label,
1524
+ className: "fractal-table-overflow-menu",
1525
+ style: position,
1526
+ onClick: (event) => event.stopPropagation(),
1527
+ onKeyDown: (event) => {
1528
+ event.stopPropagation();
1529
+ const enabled = buttons(), index = enabled.indexOf(document.activeElement);
1530
+ if (event.key === "Escape") {
1531
+ event.preventDefault();
1532
+ close();
1533
+ } else if (event.key === "Tab") {
1534
+ close();
1535
+ } else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
1536
+ event.preventDefault();
1537
+ const next = event.key === "Home" ? 0 : event.key === "End" ? enabled.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + enabled.length) % enabled.length;
1538
+ enabled[next]?.focus();
1539
+ } else if (event.key.length === 1 && /\S/.test(event.key)) {
1540
+ const ordered = [...enabled.slice(index + 1), ...enabled.slice(0, index + 1)];
1541
+ ordered.find((button) => button.textContent?.trim().toLocaleLowerCase().startsWith(event.key.toLocaleLowerCase()))?.focus();
1542
+ }
1543
+ },
1544
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1545
+ "button",
1546
+ {
1547
+ type: "button",
1548
+ role: "menuitem",
1549
+ tabIndex: -1,
1550
+ disabled: item.disabled,
1551
+ onClick: () => {
1552
+ onSelect(item.value);
1553
+ close();
1554
+ },
1555
+ children: [
1556
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "aria-hidden": "true", children: item.icon }),
1557
+ item.label
1558
+ ]
1559
+ },
1560
+ item.value
1561
+ ))
1562
+ }
1563
+ ), document.body)
1564
+ ] });
1565
+ }
1566
+
1567
+ // src/components/TableLayout/TableLayout.tsx
1568
+ var import_react5 = require("react");
1569
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1570
+ var length = (value) => typeof value === "number" ? `${value}px` : value;
1571
+ function columnStyle(column) {
1572
+ return {
1573
+ width: column.width,
1574
+ "--table-column-width": !column.width || column.width.includes("%") ? void 0 : `calc(${column.width} - 1px)`,
1575
+ "--table-column-min": length(column.minWidth),
1576
+ "--table-column-max": length(column.maxWidth)
1577
+ };
1578
+ }
1579
+ function useOverflow() {
1580
+ const ref = (0, import_react5.useRef)(null);
1581
+ const [overflow, setOverflow] = (0, import_react5.useState)(false);
1582
+ (0, import_react5.useLayoutEffect)(() => {
1583
+ const el = ref.current;
1584
+ if (!el) return;
1585
+ const measure = () => setOverflow(el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1);
1586
+ const resize = new ResizeObserver(measure);
1587
+ const observe = () => {
1588
+ resize.disconnect();
1589
+ resize.observe(el);
1590
+ for (const child of el.children) resize.observe(child);
1591
+ measure();
1592
+ };
1593
+ const mutation = new MutationObserver(observe);
1594
+ mutation.observe(el, { childList: true, subtree: true, characterData: true });
1595
+ observe();
1596
+ return () => {
1597
+ resize.disconnect();
1598
+ mutation.disconnect();
1599
+ };
1600
+ }, []);
1601
+ return { ref, overflow };
1602
+ }
1603
+ function TableViewport({ children, maxHeight, label }) {
1604
+ const { ref, overflow } = useOverflow();
1605
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref, className: "fractal-table-viewport", style: { maxHeight }, tabIndex: overflow ? 0 : void 0, role: overflow ? "region" : void 0, "aria-label": overflow ? `${label}, scrollable table` : void 0, children });
1606
+ }
1607
+ function TableCellContent({ children, label }) {
1608
+ const { ref, overflow } = useOverflow();
1609
+ (0, import_react5.useLayoutEffect)(() => {
1610
+ const cell = ref.current;
1611
+ if (!cell) return;
1612
+ const wheel = (event) => {
1613
+ if (event.defaultPrevented || event.ctrlKey || !event.cancelable || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) return;
1614
+ const range = cell.scrollWidth - cell.clientWidth;
1615
+ if (range <= 1) return;
1616
+ const rtl = getComputedStyle(cell).direction === "rtl";
1617
+ const left = rtl ? -range : 0, right = rtl ? 0 : range;
1618
+ if (event.deltaX > 0 ? cell.scrollLeft < right - 1 : cell.scrollLeft > left + 1) return;
1619
+ const table = cell.closest(".fractal-table-viewport");
1620
+ if (!table) return;
1621
+ const tableRange = table.scrollWidth - table.clientWidth;
1622
+ const tableRtl = getComputedStyle(table).direction === "rtl";
1623
+ const tableLeft = tableRtl ? -tableRange : 0, tableRight = tableRtl ? 0 : tableRange;
1624
+ if (event.deltaX > 0 ? table.scrollLeft >= tableRight - 1 : table.scrollLeft <= tableLeft + 1) return;
1625
+ const unit = event.deltaMode === 1 ? parseFloat(getComputedStyle(cell).lineHeight) || 16 : event.deltaMode === 2 ? table.clientWidth : 1;
1626
+ event.preventDefault();
1627
+ table.scrollLeft += event.deltaX * unit;
1628
+ };
1629
+ cell.addEventListener("wheel", wheel, { passive: false });
1630
+ return () => cell.removeEventListener("wheel", wheel);
1631
+ }, [ref]);
1632
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1633
+ "div",
1634
+ {
1635
+ ref,
1636
+ className: "fractal-table-cell-scroll",
1637
+ "data-overflow": overflow || void 0,
1638
+ tabIndex: overflow ? 0 : void 0,
1639
+ role: overflow ? "region" : void 0,
1640
+ "aria-label": overflow ? `${label}, scroll horizontally for full content` : void 0,
1641
+ onKeyDown: (event) => {
1642
+ if (!overflow || event.target !== event.currentTarget) return;
1643
+ if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) {
1644
+ event.preventDefault();
1645
+ event.stopPropagation();
1646
+ const el = event.currentTarget;
1647
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") el.scrollBy({ left: event.key === "ArrowLeft" ? -40 : 40 });
1648
+ else el.scrollLeft = event.key === "Home" ? 0 : el.scrollWidth * (getComputedStyle(el).direction === "rtl" ? -1 : 1);
1649
+ } else if (event.key === " " || event.key === "Enter") event.stopPropagation();
1650
+ },
1651
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "fractal-table-cell-content", children })
1652
+ }
1653
+ );
1654
+ }
1655
+
536
1656
  // src/components/DataTable/DataTable.tsx
537
- var import_jsx_runtime9 = require("react/jsx-runtime");
538
- function DataTable({ columns, data, emptyMessage, header, size = "md", className }) {
539
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn("fractal-datatable", `fractal-datatable--${size}`, className), children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "fractal-datatable-surface", children: [
540
- header && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fractal-datatable-header", children: header }),
541
- data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "fractal-datatable-table", children: [
542
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tr", { className: "fractal-datatable-thead-row", children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { className: "fractal-datatable-th", style: { width: col.width }, children: col.header }, col.key)) }) }),
543
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1657
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1658
+ function DataTable({ columns, data, emptyMessage, header, size = "md", className, overflowMenu, layout = "fill", stickyHeader = false, stickyFirstColumn = false, maxHeight, "aria-label": ariaLabel = "Data table" }) {
1659
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cn("fractal-table-layout", "fractal-datatable", `fractal-datatable--${size}`, className), "data-overflow-menu": Boolean(overflowMenu), "data-table-layout": layout, "data-sticky-first": stickyFirstColumn, "data-sticky-header": stickyHeader, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fractal-datatable-surface", children: [
1660
+ header && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "fractal-datatable-header", children: header }),
1661
+ data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TableViewport, { maxHeight, label: ariaLabel, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("table", { "aria-label": ariaLabel, className: "fractal-datatable-table", children: [
1662
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("tr", { className: "fractal-datatable-thead-row", children: [
1663
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("th", { scope: "col", className: "fractal-datatable-th", style: columnStyle(col), "data-pinned-column": columnIndex === 0 ? "data" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TableCellContent, { label: `${col.header} column`, children: col.header }) }, col.key)),
1664
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("th", { scope: "col", "aria-label": "Row actions", className: "fractal-datatable-th fractal-table-overflow-cell", "data-pinned-column": "overflow" })
1665
+ ] }) }),
1666
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
544
1667
  "tr",
545
1668
  {
546
1669
  className: "fractal-datatable-row",
547
- children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { className: "fractal-datatable-td", style: { width: col.width }, children: col.render(row, rowIndex) }, col.key))
1670
+ children: [
1671
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("td", { className: "fractal-datatable-td", style: columnStyle(col), "data-pinned-column": columnIndex === 0 ? "data" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TableCellContent, { label: `${col.header || "Value"}, row ${rowIndex + 1}`, children: col.render(row, rowIndex) }) }, col.key)),
1672
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("td", { className: "fractal-datatable-td fractal-table-overflow-cell", "data-pinned-column": "overflow", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TableOverflowMenu, { items: overflowMenu.items(row, rowIndex), label: overflowMenu.getLabel?.(row, rowIndex) ?? `Actions for row ${rowIndex + 1}`, disabled: overflowMenu.isDisabled?.(row, rowIndex), onSelect: (value) => overflowMenu.onSelect(value, row, rowIndex) }) })
1673
+ ]
548
1674
  },
549
1675
  rowIndex
550
1676
  )) })
551
- ] })
1677
+ ] }) })
552
1678
  ] }) });
553
1679
  }
554
1680
 
555
1681
  // src/components/Dropdown/Dropdown.tsx
556
- var React7 = __toESM(require("react"), 1);
557
- var import_fractal_icons2 = require("@mirror-physics/fractal-icons");
558
- var import_jsx_runtime10 = require("react/jsx-runtime");
559
- var MARGIN = 4;
1682
+ var React12 = __toESM(require("react"), 1);
1683
+ var import_fractal_icons10 = require("@mirror-physics/fractal-icons");
1684
+
1685
+ // src/components/Dropdown/position.ts
1686
+ function positionDropdown(trigger, panel, bounds, direction, align) {
1687
+ const gap = 4;
1688
+ const width = Math.min(panel.width, Math.max(0, bounds.right - bounds.left));
1689
+ let maxHeight = Math.max(0, bounds.bottom - bounds.top);
1690
+ let left;
1691
+ let top;
1692
+ if (direction === "left" || direction === "right") {
1693
+ const after = bounds.right - trigger.right - gap;
1694
+ const before = trigger.left - bounds.left - gap;
1695
+ let placeRight = direction === "right";
1696
+ if (width > (placeRight ? after : before) && (placeRight ? before > after : after > before)) {
1697
+ placeRight = !placeRight;
1698
+ }
1699
+ left = placeRight ? trigger.right + gap : trigger.left - width - gap;
1700
+ top = trigger.top;
1701
+ } else {
1702
+ const below = Math.max(0, bounds.bottom - trigger.bottom - gap);
1703
+ const above = Math.max(0, trigger.top - bounds.top - gap);
1704
+ let placeAbove = direction === "up";
1705
+ if (panel.height > (placeAbove ? above : below) && (placeAbove ? below > above : above > below)) {
1706
+ placeAbove = !placeAbove;
1707
+ }
1708
+ maxHeight = Math.min(maxHeight, placeAbove ? above : below);
1709
+ top = placeAbove ? trigger.top - Math.min(panel.height, maxHeight) - gap : trigger.bottom + gap;
1710
+ left = align === "right" ? trigger.right - width : trigger.left;
1711
+ }
1712
+ const height = Math.min(panel.height, maxHeight);
1713
+ return {
1714
+ left: Math.max(bounds.left, Math.min(left, bounds.right - width)),
1715
+ top: Math.max(bounds.top, Math.min(top, bounds.bottom - height)),
1716
+ maxHeight
1717
+ };
1718
+ }
1719
+
1720
+ // src/components/Dropdown/Dropdown.tsx
1721
+ var import_jsx_runtime20 = require("react/jsx-runtime");
560
1722
  function Dropdown({
561
1723
  items,
562
- selectedValue,
1724
+ selectedValue = "",
563
1725
  onSelect,
564
- triggerLabel = "Options",
1726
+ multiple = false,
1727
+ selectedValues = [],
1728
+ onSelectionChange,
1729
+ searchable = false,
1730
+ searchPlaceholder = "Find an option",
1731
+ emptyMessage = "No matching options.",
1732
+ triggerLabel,
1733
+ fieldLabel,
565
1734
  direction = "down",
566
1735
  align = "left",
1736
+ maxHeight = 320,
1737
+ collisionPadding = 8,
1738
+ triggerOrientation = "horizontal",
567
1739
  triggerContent,
568
1740
  label,
1741
+ iconOnly = false,
569
1742
  noChevron = false,
570
- size = "md",
1743
+ size: sizeAlias = "lg",
1744
+ controlSize,
1745
+ triggerId,
571
1746
  triggerVariant = "outline",
572
1747
  className,
573
1748
  triggerClassName,
574
1749
  panelClassName,
575
- closeOnSelect = true
1750
+ closeOnSelect = true,
1751
+ disabled = false
576
1752
  }) {
577
- const [open, setOpen] = React7.useState(false);
578
- const triggerRef = React7.useRef(null);
579
- const panelRef = React7.useRef(null);
580
- const [panelStyle, setPanelStyle] = React7.useState({});
581
- const visibleItems = items.filter(
1753
+ const [query, setQuery] = React12.useState("");
1754
+ const searchRef = React12.useRef(null);
1755
+ const accessibleLabel = triggerLabel ?? fieldLabel ?? "Options";
1756
+ const size = controlSize ?? sizeAlias;
1757
+ const [open, setOpen] = React12.useState(false);
1758
+ React12.useEffect(() => {
1759
+ if (!open) setQuery("");
1760
+ }, [open]);
1761
+ const rootRef = React12.useRef(null);
1762
+ const menuId = React12.useId();
1763
+ const focusOnOpen = React12.useRef(false);
1764
+ const triggerRef = React12.useRef(null);
1765
+ const panelRef = React12.useRef(null);
1766
+ const [panelStyle, setPanelStyle] = React12.useState({});
1767
+ const renderableItems = items.filter(
582
1768
  (item) => item.icon != null || item.description != null && (typeof item.description === "string" ? item.description !== "" : true)
583
1769
  );
584
- if (visibleItems.length === 0) return null;
1770
+ const normalizedQuery = query.trim().toLocaleLowerCase();
1771
+ let groupTitle = "";
1772
+ const matching = new Set(renderableItems.filter((item) => {
1773
+ if (item.kind === "header") {
1774
+ groupTitle = String(item.description ?? "");
1775
+ return false;
1776
+ }
1777
+ return `${groupTitle} ${typeof item.description === "string" ? item.description : item.value}`.toLocaleLowerCase().includes(normalizedQuery);
1778
+ }).map((item) => item.value));
1779
+ const visibleItems = renderableItems.filter((item, index) => {
1780
+ if (item.kind !== "header") return matching.has(item.value);
1781
+ const nextHeader = renderableItems.findIndex((next, i) => i > index && next.kind === "header");
1782
+ return renderableItems.slice(index + 1, nextHeader < 0 ? void 0 : nextHeader).some((next) => matching.has(next.value));
1783
+ });
585
1784
  const selectableItems = visibleItems.filter((i) => i.kind !== "header");
586
1785
  const selected = items.find((i) => i.value === selectedValue && i.kind !== "header") ?? selectableItems[0] ?? visibleItems[0];
587
- const isUp = direction === "up";
588
- const ChevronIcon = isUp ? import_fractal_icons2.ChevronUp : import_fractal_icons2.ChevronDown;
589
- React7.useLayoutEffect(() => {
590
- if (!open || !triggerRef.current || !panelRef.current) return;
591
- const triggerRect = triggerRef.current.getBoundingClientRect();
592
- const panelRect = panelRef.current.getBoundingClientRect();
593
- const vw = window.innerWidth;
594
- const vh = window.innerHeight;
595
- const spaceBelow = vh - triggerRect.bottom - MARGIN;
596
- const spaceAbove = triggerRect.top - MARGIN;
597
- let placeAbove = isUp;
598
- if (placeAbove && panelRect.height > spaceAbove && spaceBelow > spaceAbove) {
599
- placeAbove = false;
600
- } else if (!placeAbove && panelRect.height > spaceBelow && spaceAbove > spaceBelow) {
601
- placeAbove = true;
602
- }
603
- const top = placeAbove ? -(panelRect.height + MARGIN) : triggerRect.height + MARGIN;
604
- let left;
605
- let right;
606
- if (align === "right") {
607
- right = 0;
608
- const panelLeft = triggerRect.right - panelRect.width;
609
- if (panelLeft < 0) {
610
- right = void 0;
611
- left = 0;
1786
+ const ChevronIcon = direction === "up" ? import_fractal_icons10.ChevronUp : direction === "left" ? import_fractal_icons10.ChevronLeft : direction === "right" ? import_fractal_icons10.ChevronRight : import_fractal_icons10.ChevronDown;
1787
+ useEscapeDismiss(100, () => {
1788
+ setOpen(false);
1789
+ triggerRef.current?.focus();
1790
+ }, open);
1791
+ React12.useLayoutEffect(() => {
1792
+ const root = rootRef.current;
1793
+ const trigger = triggerRef.current;
1794
+ const panel = panelRef.current;
1795
+ if (!open || !root || !trigger || !panel) return;
1796
+ const updatePosition = () => {
1797
+ const viewport = window.visualViewport;
1798
+ const bounds = {
1799
+ left: viewport?.offsetLeft ?? 0,
1800
+ top: viewport?.offsetTop ?? 0,
1801
+ right: (viewport?.offsetLeft ?? 0) + (viewport?.width ?? document.documentElement.clientWidth),
1802
+ bottom: (viewport?.offsetTop ?? 0) + (viewport?.height ?? document.documentElement.clientHeight)
1803
+ };
1804
+ for (let ancestor = root.parentElement; ancestor; ancestor = ancestor.parentElement) {
1805
+ const style = getComputedStyle(ancestor);
1806
+ const rect = ancestor.getBoundingClientRect();
1807
+ if (/(auto|scroll|hidden|clip)/.test(style.overflowX)) {
1808
+ bounds.left = Math.max(bounds.left, rect.left + ancestor.clientLeft);
1809
+ bounds.right = Math.min(bounds.right, rect.left + ancestor.clientLeft + ancestor.clientWidth);
1810
+ }
1811
+ if (/(auto|scroll|hidden|clip)/.test(style.overflowY)) {
1812
+ bounds.top = Math.max(bounds.top, rect.top + ancestor.clientTop);
1813
+ bounds.bottom = Math.min(bounds.bottom, rect.top + ancestor.clientTop + ancestor.clientHeight);
1814
+ }
612
1815
  }
613
- } else {
614
- left = 0;
615
- const panelRight = triggerRect.left + panelRect.width;
616
- if (panelRight > vw) {
617
- left = void 0;
618
- right = 0;
1816
+ const padding = Math.max(0, collisionPadding);
1817
+ bounds.left += padding;
1818
+ bounds.right = Math.max(bounds.left, bounds.right - padding);
1819
+ bounds.top += padding;
1820
+ bounds.bottom = Math.max(bounds.top, bounds.bottom - padding);
1821
+ const triggerRect = trigger.getBoundingClientRect();
1822
+ const rootRect = root.getBoundingClientRect();
1823
+ const availableWidth = bounds.right - bounds.left;
1824
+ const heightLimit = Math.max(0, maxHeight);
1825
+ panel.style.maxWidth = `${availableWidth}px`;
1826
+ panel.style.minWidth = `${Math.min(triggerRect.width, availableWidth)}px`;
1827
+ const panelRect = panel.getBoundingClientRect();
1828
+ const desiredHeight = Math.min(heightLimit, panel.scrollHeight + panel.offsetHeight - panel.clientHeight);
1829
+ const position = positionDropdown(triggerRect, { width: panelRect.width, height: desiredHeight }, bounds, direction, align);
1830
+ const nextStyle = {
1831
+ left: position.left - rootRect.left - root.clientLeft + root.scrollLeft,
1832
+ top: position.top - rootRect.top - root.clientTop + root.scrollTop,
1833
+ minWidth: Math.min(triggerRect.width, availableWidth),
1834
+ maxWidth: availableWidth,
1835
+ maxHeight: Math.min(heightLimit, position.maxHeight)
1836
+ };
1837
+ for (const key of Object.keys(nextStyle)) {
1838
+ panel.style[key] = `${nextStyle[key]}px`;
619
1839
  }
1840
+ setPanelStyle((previous) => Object.entries(nextStyle).every(([key, value]) => previous[key] === value) ? previous : nextStyle);
1841
+ };
1842
+ updatePosition();
1843
+ if (searchable && !panel.contains(document.activeElement)) searchRef.current?.focus({ preventScroll: true });
1844
+ if (focusOnOpen.current) {
1845
+ focusOnOpen.current = false;
1846
+ const option = panel.querySelector('[aria-selected="true"]') ?? panel.querySelector('[role="option"]');
1847
+ option?.focus({ preventScroll: true });
1848
+ option?.scrollIntoView({ block: "nearest" });
1849
+ }
1850
+ const resize = new ResizeObserver(updatePosition);
1851
+ resize.observe(root);
1852
+ resize.observe(panel);
1853
+ for (let ancestor = root.parentElement; ancestor; ancestor = ancestor.parentElement) {
1854
+ resize.observe(ancestor);
620
1855
  }
621
- setPanelStyle({ top, left, right });
622
- }, [open, isUp, align]);
623
- React7.useEffect(() => {
1856
+ window.addEventListener("resize", updatePosition);
1857
+ window.addEventListener("scroll", updatePosition, true);
1858
+ window.visualViewport?.addEventListener("resize", updatePosition);
1859
+ window.visualViewport?.addEventListener("scroll", updatePosition);
1860
+ return () => {
1861
+ resize.disconnect();
1862
+ window.removeEventListener("resize", updatePosition);
1863
+ window.removeEventListener("scroll", updatePosition, true);
1864
+ window.visualViewport?.removeEventListener("resize", updatePosition);
1865
+ window.visualViewport?.removeEventListener("scroll", updatePosition);
1866
+ };
1867
+ }, [open, direction, align, maxHeight, collisionPadding, items, label, query, searchable]);
1868
+ React12.useEffect(() => {
624
1869
  if (!open) return;
625
1870
  const handleClickOutside = (e) => {
626
1871
  const target = e.target;
@@ -630,75 +1875,120 @@ function Dropdown({
630
1875
  document.addEventListener("mousedown", handleClickOutside);
631
1876
  return () => document.removeEventListener("mousedown", handleClickOutside);
632
1877
  }, [open]);
633
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("fractal-dropdown", className), children: [
634
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1878
+ if (renderableItems.length === 0) return null;
1879
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { ref: rootRef, "data-control-size": size, "data-filled": multiple ? selectedValues.length > 0 : Boolean(selectedValue), "data-open": open, className: cn("fractal-dropdown", fieldLabel && "fractal-floating-field fractal-floating-field--dropdown", className), children: [
1880
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
635
1881
  "button",
636
1882
  {
637
1883
  ref: triggerRef,
1884
+ id: triggerId ?? `${menuId}-trigger`,
1885
+ "data-control-size": size,
638
1886
  type: "button",
639
- "aria-haspopup": "listbox",
1887
+ disabled,
1888
+ "aria-haspopup": searchable ? "dialog" : "listbox",
640
1889
  "aria-expanded": open,
641
- "aria-label": triggerLabel,
1890
+ "aria-controls": open ? searchable ? menuId : `${menuId}-options` : void 0,
1891
+ onKeyDown: (event) => {
1892
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1893
+ event.preventDefault();
1894
+ if (open) {
1895
+ const option = panelRef.current?.querySelector('[aria-selected="true"]') ?? panelRef.current?.querySelector('[role="option"]');
1896
+ option?.focus({ preventScroll: true });
1897
+ option?.scrollIntoView({ block: "nearest" });
1898
+ } else {
1899
+ focusOnOpen.current = true;
1900
+ setOpen(true);
1901
+ }
1902
+ }
1903
+ },
1904
+ "aria-label": accessibleLabel,
642
1905
  onClick: () => setOpen((o) => !o),
643
1906
  className: cn(
1907
+ "fractal-control",
644
1908
  "fractal-dropdown-trigger",
645
1909
  `fractal-dropdown-trigger--${size}`,
646
1910
  `fractal-dropdown-trigger--${triggerVariant}`,
1911
+ `fractal-dropdown-trigger--${triggerOrientation}`,
1912
+ iconOnly && "fractal-dropdown-trigger--icon-only",
647
1913
  triggerClassName
648
1914
  ),
649
1915
  children: [
650
- triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
651
- selected.icon != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected.icon }),
652
- selected.description != null && (typeof selected.description !== "string" || selected.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-selected-text", children: selected.description })
653
- ] }),
654
- !noChevron && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
1916
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: fieldLabel ? "fractal-dropdown-field-value" : void 0, style: fieldLabel ? void 0 : { display: "contents" }, children: triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1917
+ selected?.icon != null && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected?.icon }),
1918
+ selected?.description != null && (typeof selected?.description !== "string" || selected?.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-selected-text", children: selected?.description })
1919
+ ] }) }),
1920
+ !noChevron && !iconOnly && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
655
1921
  ]
656
1922
  }
657
1923
  ),
658
- open && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1924
+ fieldLabel ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("label", { className: "fractal-floating-field__label", htmlFor: triggerId ?? `${menuId}-trigger`, children: fieldLabel }) : null,
1925
+ open && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
659
1926
  "div",
660
1927
  {
661
1928
  ref: panelRef,
662
- role: "listbox",
663
- "aria-label": triggerLabel,
1929
+ id: menuId,
1930
+ onKeyDown: (event) => {
1931
+ if (event.target === searchRef.current && event.key !== "ArrowDown") return;
1932
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1933
+ event.preventDefault();
1934
+ const options = Array.from(panelRef.current?.querySelectorAll('[role="option"]') ?? []);
1935
+ const current = options.indexOf(document.activeElement);
1936
+ const index = event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : (current + (event.key === "ArrowDown" ? 1 : -1) + options.length) % options.length;
1937
+ options[index]?.focus({ preventScroll: true });
1938
+ options[index]?.scrollIntoView({ block: "nearest" });
1939
+ },
1940
+ role: searchable ? "dialog" : void 0,
1941
+ "aria-label": accessibleLabel,
664
1942
  className: cn("fractal-dropdown-panel", `fractal-dropdown-panel--${size}`, panelClassName),
665
1943
  style: panelStyle,
666
1944
  children: [
667
- label != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-label", children: label }),
668
- visibleItems.map((item, index) => {
669
- const topDivider = item.border === "top" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
670
- const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
671
- if (item.kind === "header") {
672
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(React7.Fragment, { children: [
1945
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "fractal-dropdown-search", children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_fractal_icons10.MagnifyingGlass, { "aria-hidden": "true" }),
1947
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("input", { ref: searchRef, type: "search", "aria-label": searchPlaceholder, placeholder: searchPlaceholder, value: query, onChange: (event) => setQuery(event.target.value) })
1948
+ ] }),
1949
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { role: "listbox", id: `${menuId}-options`, "aria-label": accessibleLabel, "aria-multiselectable": multiple || void 0, children: [
1950
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "fractal-dropdown-label", children: label }),
1951
+ visibleItems.map((item, index) => {
1952
+ const topDivider = item.border === "top" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
1953
+ const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
1954
+ if (item.kind === "header") {
1955
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(React12.Fragment, { children: [
1956
+ topDivider,
1957
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
1958
+ bottomDivider
1959
+ ] }, `header-${index}`);
1960
+ }
1961
+ const isSelected = multiple ? selectedValues.includes(item.value) : item.value === selectedValue;
1962
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(React12.Fragment, { children: [
673
1963
  topDivider,
674
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
1964
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1965
+ "button",
1966
+ {
1967
+ type: "button",
1968
+ role: "option",
1969
+ "aria-selected": isSelected,
1970
+ onClick: () => {
1971
+ if (multiple) onSelectionChange?.(isSelected ? selectedValues.filter((value) => value !== item.value) : [...selectedValues, item.value]);
1972
+ else onSelect?.(item.value);
1973
+ if (!multiple && closeOnSelect) {
1974
+ setOpen(false);
1975
+ triggerRef.current?.focus();
1976
+ }
1977
+ },
1978
+ className: "fractal-dropdown-option",
1979
+ children: [
1980
+ multiple && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-checkbox", "data-checked": isSelected, "aria-hidden": "true", children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_fractal_icons10.Check, {}) }),
1981
+ item.icon != null && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-icon", children: item.icon }),
1982
+ item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-option-text", children: item.description }),
1983
+ item.trailing != null && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "fractal-dropdown-trailing", children: item.trailing })
1984
+ ]
1985
+ }
1986
+ ),
675
1987
  bottomDivider
676
- ] }, `header-${index}`);
677
- }
678
- const isSelected = item.value === selectedValue;
679
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(React7.Fragment, { children: [
680
- topDivider,
681
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
682
- "button",
683
- {
684
- type: "button",
685
- role: "option",
686
- "aria-selected": isSelected,
687
- onClick: () => {
688
- onSelect(item.value);
689
- if (closeOnSelect) setOpen(false);
690
- },
691
- className: "fractal-dropdown-option",
692
- children: [
693
- item.icon != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-icon", children: item.icon }),
694
- item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-option-text", children: item.description }),
695
- item.trailing != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-trailing", children: item.trailing })
696
- ]
697
- }
698
- ),
699
- bottomDivider
700
- ] }, item.value);
701
- })
1988
+ ] }, item.value);
1989
+ })
1990
+ ] }),
1991
+ !selectableItems.length && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { role: "status", className: "fractal-dropdown-label", children: emptyMessage })
702
1992
  ]
703
1993
  }
704
1994
  )
@@ -706,10 +1996,10 @@ function Dropdown({
706
1996
  }
707
1997
 
708
1998
  // src/components/Link/Link.tsx
709
- var React8 = __toESM(require("react"), 1);
710
- var import_jsx_runtime11 = require("react/jsx-runtime");
711
- var Link = React8.forwardRef(
712
- ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1999
+ var React13 = __toESM(require("react"), 1);
2000
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2001
+ var Link = React13.forwardRef(
2002
+ ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
713
2003
  "a",
714
2004
  {
715
2005
  ref,
@@ -721,10 +2011,10 @@ var Link = React8.forwardRef(
721
2011
  Link.displayName = "Link";
722
2012
 
723
2013
  // src/components/Chip/Chip.tsx
724
- var React9 = __toESM(require("react"), 1);
725
- var import_jsx_runtime12 = require("react/jsx-runtime");
726
- var Chip = React9.forwardRef(
727
- ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2014
+ var React14 = __toESM(require("react"), 1);
2015
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2016
+ var Chip = React14.forwardRef(
2017
+ ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
728
2018
  "span",
729
2019
  {
730
2020
  ref,
@@ -735,36 +2025,128 @@ var Chip = React9.forwardRef(
735
2025
  );
736
2026
  Chip.displayName = "Chip";
737
2027
 
2028
+ // src/components/Checkbox/Checkbox.tsx
2029
+ var import_fractal_icons11 = require("@mirror-physics/fractal-icons");
2030
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2031
+ function Checkbox({
2032
+ checked,
2033
+ indeterminate = false,
2034
+ onCheckedChange,
2035
+ className,
2036
+ disabled,
2037
+ onClick,
2038
+ onKeyDown,
2039
+ ...props
2040
+ }) {
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2042
+ "button",
2043
+ {
2044
+ ...props,
2045
+ className: cn(
2046
+ "fractal-checkbox",
2047
+ (checked || indeterminate) && "fractal-checkbox--checked",
2048
+ indeterminate && "fractal-checkbox--indeterminate",
2049
+ className
2050
+ ),
2051
+ type: "button",
2052
+ role: "checkbox",
2053
+ "aria-checked": indeterminate ? "mixed" : checked,
2054
+ disabled,
2055
+ onClick: (event) => {
2056
+ onClick?.(event);
2057
+ if (!event.defaultPrevented) onCheckedChange(indeterminate || !checked);
2058
+ },
2059
+ onKeyDown: (event) => {
2060
+ onKeyDown?.(event);
2061
+ if (event.key === " " || event.key === "Enter") event.stopPropagation();
2062
+ },
2063
+ children: indeterminate ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "fractal-checkbox__indeterminate-icon", "aria-hidden": "true", children: [
2064
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fractal_icons11.Checkbox, { size: 18 }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fractal_icons11.Minus, { className: "fractal-checkbox__indeterminate-mark", size: 12 })
2066
+ ] }) : checked ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fractal_icons11.CheckboxChecked, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fractal_icons11.Checkbox, { "aria-hidden": "true", size: 18 })
2067
+ }
2068
+ );
2069
+ }
2070
+
2071
+ // src/components/Radio/Radio.tsx
2072
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2073
+ function Radio({
2074
+ checked,
2075
+ className,
2076
+ description,
2077
+ disabled,
2078
+ label,
2079
+ onCheckedChange,
2080
+ ...props
2081
+ }) {
2082
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("label", { className: cn("fractal-radio", disabled && "fractal-radio--disabled", className), children: [
2083
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2084
+ "input",
2085
+ {
2086
+ ...props,
2087
+ checked,
2088
+ className: "fractal-radio__input",
2089
+ disabled,
2090
+ onChange: (event) => onCheckedChange?.(event.currentTarget.checked),
2091
+ type: "radio"
2092
+ }
2093
+ ),
2094
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": "true", className: "fractal-radio__control", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "fractal-radio__dot" }) }),
2095
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "fractal-radio__content", children: [
2096
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "fractal-radio__label", children: label }),
2097
+ description && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "fractal-radio__description", children: description })
2098
+ ] })
2099
+ ] });
2100
+ }
2101
+
738
2102
  // src/components/SortableTable/SortableTable.tsx
739
- var import_react2 = require("react");
740
- var import_fractal_icons3 = require("@mirror-physics/fractal-icons");
741
- var import_jsx_runtime13 = require("react/jsx-runtime");
2103
+ var import_react6 = require("react");
2104
+ var import_fractal_icons12 = require("@mirror-physics/fractal-icons");
2105
+ var import_jsx_runtime25 = require("react/jsx-runtime");
742
2106
  function SortableTable({
743
2107
  columns,
744
2108
  data,
2109
+ "aria-label": ariaLabel,
745
2110
  emptyMessage,
746
2111
  header,
747
2112
  size = "md",
2113
+ embedded = false,
2114
+ layout = "fill",
2115
+ stickyHeader = false,
2116
+ stickyFirstColumn = false,
2117
+ maxHeight,
2118
+ overflowMenu,
748
2119
  className,
2120
+ defaultSort,
749
2121
  defaultSortKey = void 0,
750
2122
  defaultSortDirection = null,
751
2123
  sortKey: controlledKey,
752
2124
  sortDirection: controlledDirection,
753
2125
  onSortChange,
2126
+ pagination,
754
2127
  highlightRow,
755
- onRowClick
2128
+ selection,
2129
+ isRowDisabled,
2130
+ isRowSelectionDisabled,
2131
+ onRowClick,
2132
+ onRowDoubleClick,
2133
+ rowAction
756
2134
  }) {
757
- const [internalKey, setInternalKey] = (0, import_react2.useState)(defaultSortKey ?? null);
758
- const [internalDirection, setInternalDirection] = (0, import_react2.useState)(defaultSortDirection);
2135
+ const [internalKey, setInternalKey] = (0, import_react6.useState)(defaultSortKey ?? null);
2136
+ const [internalDirection, setInternalDirection] = (0, import_react6.useState)(defaultSortDirection);
759
2137
  const isControlled = controlledKey !== void 0 && controlledDirection !== void 0;
760
2138
  const activeKey = isControlled ? controlledKey : internalKey;
761
2139
  const activeDirection = isControlled ? controlledDirection : internalDirection;
762
- const sorted = (0, import_react2.useMemo)(() => {
763
- if (!activeKey || !activeDirection) return data;
764
- const col = columns.find((c) => c.key === activeKey);
2140
+ const baselineKey = defaultSort?.key ?? null;
2141
+ const baselineDirection = defaultSort?.direction ?? null;
2142
+ const effectiveKey = activeKey && activeDirection ? activeKey : baselineKey;
2143
+ const effectiveDirection = activeKey && activeDirection ? activeDirection : baselineDirection;
2144
+ const sorted = (0, import_react6.useMemo)(() => {
2145
+ if (!effectiveKey || !effectiveDirection) return data;
2146
+ const col = columns.find((c) => c.key === effectiveKey);
765
2147
  if (!col) return data;
766
2148
  const accessor = col.sortAccessor ?? ((row) => {
767
- const value = row[activeKey];
2149
+ const value = row[effectiveKey];
768
2150
  return typeof value === "string" || typeof value === "number" ? value : null;
769
2151
  });
770
2152
  const copy = [...data];
@@ -775,26 +2157,41 @@ function SortableTable({
775
2157
  if (av == null) return 1;
776
2158
  if (bv == null) return -1;
777
2159
  if (typeof av === "number" && typeof bv === "number") {
778
- return activeDirection === "asc" ? av - bv : bv - av;
2160
+ return effectiveDirection === "asc" ? av - bv : bv - av;
779
2161
  }
780
2162
  const as = String(av).toLowerCase();
781
2163
  const bs = String(bv).toLowerCase();
782
- if (as < bs) return activeDirection === "asc" ? -1 : 1;
783
- if (as > bs) return activeDirection === "asc" ? 1 : -1;
2164
+ if (as < bs) return effectiveDirection === "asc" ? -1 : 1;
2165
+ if (as > bs) return effectiveDirection === "asc" ? 1 : -1;
784
2166
  return 0;
785
2167
  });
786
2168
  return copy;
787
- }, [data, columns, activeKey, activeDirection]);
2169
+ }, [data, columns, effectiveKey, effectiveDirection]);
2170
+ const visibleRows = (0, import_react6.useMemo)(() => {
2171
+ if (!pagination) return sorted;
2172
+ const pageSize = Math.max(1, pagination.pageSize);
2173
+ const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
2174
+ const page = Math.min(Math.max(1, pagination.page), totalPages);
2175
+ const startIndex = (page - 1) * pageSize;
2176
+ return sorted.slice(startIndex, startIndex + pageSize);
2177
+ }, [pagination, sorted]);
2178
+ const selectedRows = selection ? sorted.filter(selection.isSelected) : [];
2179
+ const selectableRows = selection ? sorted.filter((row) => !isRowDisabled?.(row) && !isRowSelectionDisabled?.(row)) : [];
2180
+ const selectedRowCount = selection ? selectableRows.filter(selection.isSelected).length : 0;
2181
+ const allRowsSelected = selectableRows.length > 0 && selectedRowCount === selectableRows.length;
2182
+ const someRowsSelected = selectedRows.length > 0 && !allRowsSelected;
788
2183
  const handleSortClick = (key) => {
789
2184
  let nextDirection;
790
- if (activeKey !== key) {
2185
+ if (!activeKey || !activeDirection) {
2186
+ nextDirection = baselineKey === key && baselineDirection ? baselineDirection : "asc";
2187
+ } else if (activeKey !== key) {
791
2188
  nextDirection = "asc";
2189
+ } else if (baselineKey === key && baselineDirection) {
2190
+ nextDirection = activeDirection === baselineDirection ? baselineDirection === "asc" ? "desc" : "asc" : null;
792
2191
  } else if (activeDirection === "asc") {
793
2192
  nextDirection = "desc";
794
- } else if (activeDirection === "desc") {
795
- nextDirection = null;
796
2193
  } else {
797
- nextDirection = "asc";
2194
+ nextDirection = null;
798
2195
  }
799
2196
  const nextKey = nextDirection === null ? null : key;
800
2197
  if (isControlled) {
@@ -805,101 +2202,200 @@ function SortableTable({
805
2202
  onSortChange?.(nextKey, nextDirection);
806
2203
  }
807
2204
  };
808
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, className), children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "fractal-sortable-table-surface", children: [
809
- header && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-sortable-table-header", children: header }),
810
- sorted.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: "fractal-sortable-table-table", children: [
811
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
812
- const isActive = activeKey === col.key && activeDirection !== null;
813
- if (!col.sortable) {
814
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
815
- "th",
816
- {
817
- className: "fractal-sortable-table-th",
818
- style: { width: col.width },
819
- children: col.header
820
- },
821
- col.key
822
- );
823
- }
824
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2205
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cn("fractal-table-layout", "fractal-sortable-table", `fractal-sortable-table--${size}`, embedded && "fractal-sortable-table--embedded", className), "data-overflow-menu": Boolean(overflowMenu), "data-table-layout": layout, "data-sticky-first": stickyFirstColumn, "data-sticky-header": stickyHeader, "data-table-selection": Boolean(selection), children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "fractal-sortable-table-surface", children: [
2206
+ header && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "fractal-sortable-table-header", children: header }),
2207
+ visibleRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TableViewport, { maxHeight, label: ariaLabel || "Data table", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("table", { "aria-label": ariaLabel, className: "fractal-sortable-table-table", children: [
2208
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("tr", { className: "fractal-sortable-table-thead-row", children: [
2209
+ selection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
825
2210
  "th",
826
2211
  {
827
- className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
828
- style: { width: col.width },
829
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
830
- "button",
2212
+ className: "fractal-sortable-table-th fractal-sortable-table-selection-cell",
2213
+ "data-pinned-column": "selection",
2214
+ scope: "col",
2215
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2216
+ Checkbox,
831
2217
  {
832
- type: "button",
833
- className: cn(
834
- "fractal-sortable-table-sort-button",
835
- isActive && "fractal-sortable-table-sort-button--active"
836
- ),
837
- onClick: () => handleSortClick(col.key),
838
- "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
839
- children: [
840
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: col.header }),
841
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowDown, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowsUpDown, { size: 14 }) })
842
- ]
2218
+ "aria-label": selection.selectAllLabel ?? "Select all rows",
2219
+ checked: allRowsSelected,
2220
+ disabled: selectableRows.length === 0 && selectedRows.length === 0,
2221
+ indeterminate: someRowsSelected,
2222
+ onCheckedChange: (checked) => {
2223
+ const rows = checked ? selectableRows : selectedRows;
2224
+ if (selection.onSelectAllChange) {
2225
+ selection.onSelectAllChange(rows, checked);
2226
+ return;
2227
+ }
2228
+ rows.forEach((row) => {
2229
+ if (selection.isSelected(row) !== checked) {
2230
+ selection.onSelectedChange(row, checked);
2231
+ }
2232
+ });
2233
+ }
843
2234
  }
844
2235
  )
845
- },
846
- col.key
847
- );
848
- }) }) }),
849
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: sorted.map((row, rowIndex) => {
2236
+ }
2237
+ ),
2238
+ columns.map((col, columnIndex) => {
2239
+ const isActive = activeKey === col.key && activeDirection !== null;
2240
+ if (!col.sortable) {
2241
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2242
+ "th",
2243
+ {
2244
+ className: "fractal-sortable-table-th",
2245
+ scope: "col",
2246
+ style: columnStyle(col),
2247
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2248
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TableCellContent, { label: `${col.header} column`, children: col.header })
2249
+ },
2250
+ col.key
2251
+ );
2252
+ }
2253
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2254
+ "th",
2255
+ {
2256
+ className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
2257
+ scope: "col",
2258
+ style: columnStyle(col),
2259
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2260
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TableCellContent, { label: `${col.header} column`, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2261
+ "button",
2262
+ {
2263
+ type: "button",
2264
+ className: cn(
2265
+ "fractal-sortable-table-sort-button",
2266
+ isActive && "fractal-sortable-table-sort-button--active"
2267
+ ),
2268
+ onClick: () => handleSortClick(col.key),
2269
+ "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
2270
+ children: [
2271
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: col.header }),
2272
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_fractal_icons12.ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_fractal_icons12.ArrowDown, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_fractal_icons12.ArrowsUpDown, { size: 14 }) })
2273
+ ]
2274
+ }
2275
+ ) })
2276
+ },
2277
+ col.key
2278
+ );
2279
+ }),
2280
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("th", { scope: "col", "aria-label": "Row actions", className: "fractal-sortable-table-th fractal-table-overflow-cell", "data-pinned-column": "overflow" })
2281
+ ] }) }),
2282
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("tbody", { children: visibleRows.map((row, rowIndex) => {
850
2283
  const highlighted = highlightRow?.(row, rowIndex) ?? false;
851
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2284
+ const disabled = isRowDisabled?.(row) ?? false;
2285
+ const selectionDisabled = disabled || (isRowSelectionDisabled?.(row) ?? false);
2286
+ const interactive = !disabled && Boolean(onRowClick || onRowDoubleClick);
2287
+ const action = !disabled && rowAction ? {
2288
+ label: rowAction.label(row, rowIndex),
2289
+ icon: rowAction.icon(row, rowIndex)
2290
+ } : null;
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
852
2292
  "tr",
853
2293
  {
854
2294
  className: cn(
855
2295
  "fractal-sortable-table-row",
856
2296
  highlighted && "fractal-sortable-table-row--highlighted",
857
- onRowClick && "fractal-sortable-table-row--interactive"
2297
+ interactive && "fractal-sortable-table-row--interactive",
2298
+ disabled && "fractal-sortable-table-row--disabled"
858
2299
  ),
859
- onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
860
- onKeyDown: onRowClick ? (event) => {
2300
+ "data-disabled": disabled || void 0,
2301
+ onClick: !disabled && onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2302
+ onDoubleClick: !disabled && onRowDoubleClick ? () => onRowDoubleClick(row, rowIndex) : void 0,
2303
+ onKeyDown: interactive ? (event) => {
861
2304
  if (event.key === "Enter" || event.key === " ") {
862
2305
  event.preventDefault();
863
- onRowClick(row, rowIndex);
2306
+ const activateRow = onRowClick ?? onRowDoubleClick;
2307
+ activateRow?.(row, rowIndex);
864
2308
  }
865
2309
  } : void 0,
866
- role: onRowClick ? "button" : void 0,
867
- tabIndex: onRowClick ? 0 : void 0,
868
- children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
869
- "td",
870
- {
871
- className: "fractal-sortable-table-td",
872
- style: { width: col.width, textAlign: col.align ?? "left" },
873
- children: col.render(row, rowIndex)
874
- },
875
- col.key
876
- ))
2310
+ "aria-label": action?.label,
2311
+ "aria-disabled": disabled || void 0,
2312
+ role: interactive ? "button" : void 0,
2313
+ tabIndex: interactive ? 0 : void 0,
2314
+ children: [
2315
+ selection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("td", { className: "fractal-sortable-table-td fractal-sortable-table-selection-cell", "data-pinned-column": "selection", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2316
+ Checkbox,
2317
+ {
2318
+ "aria-label": selection.getRowLabel(row),
2319
+ checked: !selectionDisabled && selection.isSelected(row),
2320
+ disabled: selectionDisabled,
2321
+ onClick: (event) => event.stopPropagation(),
2322
+ onCheckedChange: (checked) => {
2323
+ if (!selectionDisabled) selection.onSelectedChange(row, checked);
2324
+ }
2325
+ }
2326
+ ) }),
2327
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2328
+ "td",
2329
+ {
2330
+ className: cn(
2331
+ "fractal-sortable-table-td",
2332
+ action && columnIndex === columns.length - 1 && "fractal-sortable-table-td--row-action"
2333
+ ),
2334
+ style: { ...columnStyle(col), textAlign: col.align ?? "left" },
2335
+ "data-column-align": col.align,
2336
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2337
+ children: [
2338
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TableCellContent, { label: `${col.header || "Value"}, row ${rowIndex + 1}`, children: col.render(row, rowIndex) }),
2339
+ action && columnIndex === columns.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-sortable-table-row-action", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-sortable-table-row-action__icon", children: action.icon }) })
2340
+ ]
2341
+ },
2342
+ col.key
2343
+ )),
2344
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2345
+ "td",
2346
+ {
2347
+ className: "fractal-sortable-table-td fractal-table-overflow-cell",
2348
+ "data-pinned-column": "overflow",
2349
+ onClick: (event) => event.stopPropagation(),
2350
+ onDoubleClick: (event) => event.stopPropagation(),
2351
+ onKeyDown: (event) => event.stopPropagation(),
2352
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TableOverflowMenu, { items: overflowMenu.items(row, rowIndex), label: overflowMenu.getLabel?.(row, rowIndex) ?? `Actions for row ${rowIndex + 1}`, disabled: disabled || overflowMenu.isDisabled?.(row, rowIndex), onSelect: (value) => overflowMenu.onSelect(value, row, rowIndex) })
2353
+ }
2354
+ )
2355
+ ]
877
2356
  },
878
2357
  rowIndex
879
2358
  );
880
2359
  }) })
881
- ] })
2360
+ ] }) })
882
2361
  ] }) });
883
2362
  }
884
2363
 
885
2364
  // src/components/Tabs/Tabs.tsx
886
- var import_react3 = require("react");
887
- var import_jsx_runtime14 = require("react/jsx-runtime");
2365
+ var import_react7 = require("react");
2366
+ var import_jsx_runtime26 = require("react/jsx-runtime");
888
2367
  function Tabs({
889
2368
  items,
890
2369
  defaultValue,
891
2370
  value: controlledValue,
892
2371
  onValueChange,
893
2372
  className,
894
- ariaLabel
2373
+ ariaLabel,
2374
+ divider = false,
2375
+ variant = "line"
895
2376
  }) {
896
- const reactId = (0, import_react3.useId)();
897
- const [internalValue, setInternalValue] = (0, import_react3.useState)(
2377
+ const reactId = (0, import_react7.useId)();
2378
+ const [internalValue, setInternalValue] = (0, import_react7.useState)(
898
2379
  defaultValue ?? items.find((i) => !i.disabled)?.id ?? items[0]?.id ?? ""
899
2380
  );
900
2381
  const isControlled = controlledValue !== void 0;
901
2382
  const activeId = isControlled ? controlledValue : internalValue;
902
- const tabRefs = (0, import_react3.useRef)({});
2383
+ const tabListRef = (0, import_react7.useRef)(null);
2384
+ const tabRefs = (0, import_react7.useRef)({});
2385
+ const [indicator, setIndicator] = (0, import_react7.useState)(null);
2386
+ (0, import_react7.useLayoutEffect)(() => {
2387
+ const updateIndicator = () => {
2388
+ const activeTab = tabRefs.current[activeId];
2389
+ if (!activeTab) return;
2390
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
2391
+ };
2392
+ updateIndicator();
2393
+ const list = tabListRef.current;
2394
+ if (!list || typeof ResizeObserver === "undefined") return;
2395
+ const observer = new ResizeObserver(updateIndicator);
2396
+ observer.observe(list);
2397
+ return () => observer.disconnect();
2398
+ }, [activeId, items.length]);
903
2399
  const selectTab = (id) => {
904
2400
  if (isControlled) {
905
2401
  onValueChange?.(id);
@@ -909,6 +2405,10 @@ function Tabs({
909
2405
  }
910
2406
  };
911
2407
  const focusable = items.filter((i) => !i.disabled);
2408
+ const indicatorStyle = indicator ? {
2409
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
2410
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
2411
+ } : void 0;
912
2412
  const onKeyDown = (event) => {
913
2413
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
914
2414
  if (currentIndex === -1) return;
@@ -934,41 +2434,52 @@ function Tabs({
934
2434
  selectTab(nextId2);
935
2435
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
936
2436
  };
937
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cn("fractal-tabs", className), children: [
938
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { role: "tablist", "aria-label": ariaLabel, className: "fractal-tabs-list", children: items.map((item) => {
939
- const isActive = item.id === activeId;
940
- const tabId = `${reactId}-tab-${item.id}`;
941
- const panelId = `${reactId}-panel-${item.id}`;
942
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
943
- "button",
944
- {
945
- ref: (el) => {
946
- tabRefs.current[item.id] = el;
947
- },
948
- id: tabId,
949
- role: "tab",
950
- type: "button",
951
- "aria-selected": isActive,
952
- "aria-controls": panelId,
953
- tabIndex: isActive ? 0 : -1,
954
- disabled: item.disabled,
955
- className: cn(
956
- "fractal-tabs-trigger",
957
- isActive && "fractal-tabs-trigger--active",
958
- item.disabled && "fractal-tabs-trigger--disabled"
959
- ),
960
- onClick: () => !item.disabled && selectTab(item.id),
961
- onKeyDown,
962
- children: item.label
963
- },
964
- item.id
965
- );
966
- }) }),
2437
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: cn("fractal-tabs", `fractal-tabs--${variant}`, divider && "fractal-tabs--divider", className), children: [
2438
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2439
+ "div",
2440
+ {
2441
+ ref: tabListRef,
2442
+ role: "tablist",
2443
+ "aria-label": ariaLabel,
2444
+ className: "fractal-tabs-list",
2445
+ "data-indicator-ready": indicator ? "true" : void 0,
2446
+ style: indicatorStyle,
2447
+ children: items.map((item) => {
2448
+ const isActive = item.id === activeId;
2449
+ const tabId = `${reactId}-tab-${item.id}`;
2450
+ const panelId = `${reactId}-panel-${item.id}`;
2451
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2452
+ "button",
2453
+ {
2454
+ ref: (el) => {
2455
+ tabRefs.current[item.id] = el;
2456
+ },
2457
+ id: tabId,
2458
+ role: "tab",
2459
+ type: "button",
2460
+ "aria-selected": isActive,
2461
+ "aria-controls": panelId,
2462
+ tabIndex: isActive ? 0 : -1,
2463
+ disabled: item.disabled,
2464
+ className: cn(
2465
+ "fractal-tabs-trigger",
2466
+ isActive && "fractal-tabs-trigger--active",
2467
+ item.disabled && "fractal-tabs-trigger--disabled"
2468
+ ),
2469
+ onClick: () => !item.disabled && selectTab(item.id),
2470
+ onKeyDown,
2471
+ children: item.label
2472
+ },
2473
+ item.id
2474
+ );
2475
+ })
2476
+ }
2477
+ ),
967
2478
  items.map((item) => {
968
2479
  const tabId = `${reactId}-tab-${item.id}`;
969
2480
  const panelId = `${reactId}-panel-${item.id}`;
970
2481
  const isActive = item.id === activeId;
971
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2482
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
972
2483
  "div",
973
2484
  {
974
2485
  id: panelId,
@@ -984,10 +2495,112 @@ function Tabs({
984
2495
  ] });
985
2496
  }
986
2497
 
2498
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
2499
+ var React15 = __toESM(require("react"), 1);
2500
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2501
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
2502
+ const switcherRef = React15.useRef(null);
2503
+ const buttonRefs = React15.useRef([]);
2504
+ const indicatorMotionReadyRef = React15.useRef(false);
2505
+ const activeIndex = items.findIndex((item) => item.value === value);
2506
+ React15.useLayoutEffect(() => {
2507
+ const switcher = switcherRef.current;
2508
+ const activeButton = buttonRefs.current[activeIndex];
2509
+ let motionFrame = 0;
2510
+ if (!switcher || !activeButton) {
2511
+ switcher?.removeAttribute("data-indicator-ready");
2512
+ switcher?.removeAttribute("data-indicator-animate");
2513
+ indicatorMotionReadyRef.current = false;
2514
+ return;
2515
+ }
2516
+ const updateIndicator = () => {
2517
+ const switcherRect = switcher.getBoundingClientRect();
2518
+ const activeButtonRect = activeButton.getBoundingClientRect();
2519
+ switcher.style.setProperty("--fractal-content-switcher-indicator-x", `${activeButtonRect.left - switcherRect.left - switcher.clientLeft}px`);
2520
+ switcher.style.setProperty("--fractal-content-switcher-indicator-y", `${activeButtonRect.top - switcherRect.top - switcher.clientTop}px`);
2521
+ switcher.style.setProperty("--fractal-content-switcher-indicator-width", `${activeButtonRect.width}px`);
2522
+ switcher.style.setProperty("--fractal-content-switcher-indicator-height", `${activeButtonRect.height}px`);
2523
+ switcher.setAttribute("data-indicator-ready", "true");
2524
+ if (!indicatorMotionReadyRef.current && motionFrame === 0) {
2525
+ motionFrame = window.requestAnimationFrame(() => {
2526
+ indicatorMotionReadyRef.current = true;
2527
+ switcher.setAttribute("data-indicator-animate", "true");
2528
+ motionFrame = 0;
2529
+ });
2530
+ }
2531
+ };
2532
+ updateIndicator();
2533
+ if (typeof ResizeObserver === "undefined") {
2534
+ return () => {
2535
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
2536
+ };
2537
+ }
2538
+ const resizeObserver = new ResizeObserver(updateIndicator);
2539
+ resizeObserver.observe(switcher);
2540
+ buttonRefs.current.forEach((button) => {
2541
+ if (button) resizeObserver.observe(button);
2542
+ });
2543
+ return () => {
2544
+ resizeObserver.disconnect();
2545
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
2546
+ };
2547
+ }, [activeIndex, items.length]);
2548
+ const moveFocus = (index, direction) => {
2549
+ let nextIndex = index;
2550
+ do {
2551
+ nextIndex = (nextIndex + direction + items.length) % items.length;
2552
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
2553
+ if (!items[nextIndex]?.disabled) {
2554
+ buttonRefs.current[nextIndex]?.focus();
2555
+ onValueChange(items[nextIndex].value);
2556
+ }
2557
+ };
2558
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2559
+ "div",
2560
+ {
2561
+ className: cn("fractal-content-switcher", fullWidth && "fractal-content-switcher--full-width", className),
2562
+ role: "group",
2563
+ "aria-label": ariaLabel,
2564
+ ref: switcherRef,
2565
+ children: [
2566
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "fractal-content-switcher__indicator", "aria-hidden": true }),
2567
+ items.map((item, index) => {
2568
+ const active = item.value === value;
2569
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2570
+ "button",
2571
+ {
2572
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
2573
+ type: "button",
2574
+ "aria-pressed": active,
2575
+ disabled: item.disabled,
2576
+ onClick: () => onValueChange(item.value),
2577
+ onKeyDown: (event) => {
2578
+ if (event.key === "ArrowRight") {
2579
+ event.preventDefault();
2580
+ moveFocus(index, 1);
2581
+ }
2582
+ if (event.key === "ArrowLeft") {
2583
+ event.preventDefault();
2584
+ moveFocus(index, -1);
2585
+ }
2586
+ },
2587
+ ref: (element) => {
2588
+ buttonRefs.current[index] = element;
2589
+ },
2590
+ children: item.label
2591
+ },
2592
+ item.value
2593
+ );
2594
+ })
2595
+ ]
2596
+ }
2597
+ );
2598
+ }
2599
+
987
2600
  // src/components/Disclosure/Disclosure.tsx
988
- var import_react4 = require("react");
989
- var import_fractal_icons4 = require("@mirror-physics/fractal-icons");
990
- var import_jsx_runtime15 = require("react/jsx-runtime");
2601
+ var import_react8 = require("react");
2602
+ var import_fractal_icons13 = require("@mirror-physics/fractal-icons");
2603
+ var import_jsx_runtime28 = require("react/jsx-runtime");
991
2604
  function Disclosure({
992
2605
  title,
993
2606
  meta,
@@ -995,16 +2608,18 @@ function Disclosure({
995
2608
  defaultOpen = false,
996
2609
  open: controlledOpen,
997
2610
  onOpenChange,
2611
+ disabled = false,
998
2612
  className,
999
2613
  variant = "card"
1000
2614
  }) {
1001
- const reactId = (0, import_react4.useId)();
2615
+ const reactId = (0, import_react8.useId)();
1002
2616
  const headerId = `${reactId}-header`;
1003
2617
  const panelId = `${reactId}-panel`;
1004
- const [internalOpen, setInternalOpen] = (0, import_react4.useState)(defaultOpen);
2618
+ const [internalOpen, setInternalOpen] = (0, import_react8.useState)(defaultOpen);
1005
2619
  const isControlled = controlledOpen !== void 0;
1006
2620
  const isOpen = isControlled ? controlledOpen : internalOpen;
1007
2621
  const toggle = () => {
2622
+ if (disabled) return;
1008
2623
  const next = !isOpen;
1009
2624
  if (isControlled) {
1010
2625
  onOpenChange?.(next);
@@ -1013,17 +2628,18 @@ function Disclosure({
1013
2628
  onOpenChange?.(next);
1014
2629
  }
1015
2630
  };
1016
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2631
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1017
2632
  "div",
1018
2633
  {
1019
2634
  className: cn(
1020
2635
  "fractal-disclosure",
1021
2636
  variant === "card" && "fractal-disclosure--card",
1022
2637
  isOpen && "fractal-disclosure--open",
2638
+ disabled && "fractal-disclosure--disabled",
1023
2639
  className
1024
2640
  ),
1025
2641
  children: [
1026
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2642
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1027
2643
  "button",
1028
2644
  {
1029
2645
  type: "button",
@@ -1031,15 +2647,16 @@ function Disclosure({
1031
2647
  className: "fractal-disclosure-trigger",
1032
2648
  "aria-expanded": isOpen,
1033
2649
  "aria-controls": panelId,
2650
+ disabled,
1034
2651
  onClick: toggle,
1035
2652
  children: [
1036
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_fractal_icons4.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_fractal_icons4.ChevronRight, { size: 16 }) }),
1037
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-title", children: title }),
1038
- meta && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-meta", children: meta })
2653
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fractal_icons13.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fractal_icons13.ChevronRight, { size: 16 }) }),
2654
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "fractal-disclosure-title", children: title }),
2655
+ meta && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "fractal-disclosure-meta", children: meta })
1039
2656
  ]
1040
2657
  }
1041
2658
  ),
1042
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2659
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1043
2660
  "div",
1044
2661
  {
1045
2662
  id: panelId,
@@ -1047,7 +2664,7 @@ function Disclosure({
1047
2664
  "aria-labelledby": headerId,
1048
2665
  className: "fractal-disclosure-panel",
1049
2666
  hidden: !isOpen,
1050
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "fractal-disclosure-panel-inner", children })
2667
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "fractal-disclosure-panel-inner", children })
1051
2668
  }
1052
2669
  )
1053
2670
  ]
@@ -1055,39 +2672,260 @@ function Disclosure({
1055
2672
  );
1056
2673
  }
1057
2674
 
2675
+ // src/components/LatticeLoader/LatticeLoader.tsx
2676
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2677
+ var LATTICE_COUNT = 160;
2678
+ var WAVE_PERIOD = 80;
2679
+ function LatticeLoader({ className, label = "Loading", ...props }) {
2680
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2681
+ "span",
2682
+ {
2683
+ "aria-label": label,
2684
+ className: cn("fractal-lattice-loader", className),
2685
+ role: "progressbar",
2686
+ ...props,
2687
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2688
+ "span",
2689
+ {
2690
+ "aria-hidden": "true",
2691
+ className: "fractal-lattice-loader__cell",
2692
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
2693
+ },
2694
+ index
2695
+ ))
2696
+ }
2697
+ );
2698
+ }
2699
+
2700
+ // src/components/Ghost/Ghost.tsx
2701
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2702
+ var toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
2703
+ function Ghost({ className, width, height, radius, style, ...props }) {
2704
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2705
+ "div",
2706
+ {
2707
+ "aria-hidden": "true",
2708
+ className: cn("fractal-ghost", className),
2709
+ style: {
2710
+ width: toCssLength(width),
2711
+ height: toCssLength(height),
2712
+ borderRadius: toCssLength(radius),
2713
+ ...style
2714
+ },
2715
+ ...props
2716
+ }
2717
+ );
2718
+ }
2719
+ function GhostLine({ className, size = "md", width = "100%", ...props }) {
2720
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: cn("fractal-ghost-line", `fractal-ghost-line--${size}`, className), width, ...props });
2721
+ }
2722
+ function ButtonGhost({ className, size = "lg", controlSize, iconOnly = false, width, ...props }) {
2723
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { "data-control-size": controlSize ?? size, className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
2724
+ }
2725
+ function CardGhost({ className, lines = 3, showHeader = true, ...props }) {
2726
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-card", className), ...props, children: [
2727
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "38%" }),
2728
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: index === lines - 1 ? "62%" : "100%" }, index)) })
2729
+ ] });
2730
+ }
2731
+ function DataTableGhost({
2732
+ className,
2733
+ showHeader = true,
2734
+ headers,
2735
+ columns = 4,
2736
+ rows = 5,
2737
+ columnWidths,
2738
+ size = "md",
2739
+ ...props
2740
+ }) {
2741
+ const cells = Array.from({ length: columns }, (_, index) => index);
2742
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-busy": "true", "aria-label": "Loading table", className: cn("fractal-ghost-table", `fractal-ghost-table--${size}`, className), role: "status", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("table", { className: "fractal-ghost-table__table", children: [
2743
+ (showHeader || headers) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("tr", { className: "fractal-ghost-table__head-row", children: cells.map((index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("th", { style: { width: columnWidths?.[index] }, children: headers?.[index] ?? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { size: "sm", width: index === columns - 1 ? "62%" : "76%" }) }, index)) }) }),
2744
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("tbody", { children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("tr", { className: "fractal-ghost-table__row", children: cells.map((columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("td", { style: { width: columnWidths?.[columnIndex] }, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: `${[72, 54, 82, 63, 46][(rowIndex + columnIndex) % 5]}%` }) }, columnIndex)) }, rowIndex)) })
2745
+ ] }) });
2746
+ }
2747
+ function InputGhost({ className, labelWidth = "26%", ...props }) {
2748
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(FormFieldGhost, { className, labelWidth, ...props });
2749
+ }
2750
+ function TextareaGhost({ className, labelWidth = "26%", ...props }) {
2751
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(FormFieldGhost, { className, labelWidth, multiline: true, ...props });
2752
+ }
2753
+ function LabelGhost({ className, width = "26%", ...props }) {
2754
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { className: cn("fractal-ghost-label", className), size: "sm", width, ...props });
2755
+ }
2756
+ function FormFieldGhost({ className, labelWidth = "26%", multiline = false, controlSize = "lg", ...props }) {
2757
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-field", className), ...props, children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(LabelGhost, { width: labelWidth }),
2759
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: cn("fractal-ghost-field__control", multiline && "fractal-ghost-field__control--multiline") })
2760
+ ] });
2761
+ }
2762
+ function AlertGhost({ className, ...props }) {
2763
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-alert", className), ...props, children: [
2764
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "30%" }),
2765
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { size: "sm", width: "78%" })
2766
+ ] });
2767
+ }
2768
+ function CheckboxGhost({ className, ...props }) {
2769
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-choice", className), ...props, children: [
2770
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-choice__control" }),
2771
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "42%" })
2772
+ ] });
2773
+ }
2774
+ var ToggleGhost = CheckboxGhost;
2775
+ function ChipGhost({ className, width = 74, ...props }) {
2776
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: cn("fractal-ghost-chip", className), height: 24, width, ...props });
2777
+ }
2778
+ function LinkGhost({ className, width = 96, ...props }) {
2779
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { className: cn("fractal-ghost-link", className), size: "sm", width, ...props });
2780
+ }
2781
+ var TextButtonGhost = LinkGhost;
2782
+ function DropdownGhost({ className, controlSize = "lg", ...props }) {
2783
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-dropdown", className), ...props, children: [
2784
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "38%" }),
2785
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-dropdown__chevron" })
2786
+ ] });
2787
+ }
2788
+ function ContentSwitcherGhost({ className, options = 3, ...props }) {
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-hidden": "true", className: cn("fractal-ghost-switcher", className), ...props, children: Array.from({ length: options }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, {}, index)) });
2790
+ }
2791
+ var TabsGhost = ContentSwitcherGhost;
2792
+ function DisclosureGhost({ className, ...props }) {
2793
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-disclosure", className), ...props, children: [
2794
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-disclosure__icon" }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "42%" })
2796
+ ] });
2797
+ }
2798
+ function PaginationGhost({ className, pages = 5, ...props }) {
2799
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-hidden": "true", className: cn("fractal-ghost-pagination", className), ...props, children: Array.from({ length: pages }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, {}, index)) });
2800
+ }
2801
+ function FileDropzoneGhost({ className, ...props }) {
2802
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropzone", className), ...props, children: [
2803
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-dropzone__icon" }),
2804
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "26%" }),
2805
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { size: "sm", width: "46%" })
2806
+ ] });
2807
+ }
2808
+ function PromptCardGhost({ className, ...props }) {
2809
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(CardGhost, { className: cn("fractal-ghost-prompt-card", className), lines: 4, ...props });
2810
+ }
2811
+ function ConversationGhost({
2812
+ className,
2813
+ "aria-label": ariaLabel = "Loading conversation",
2814
+ ...props
2815
+ }) {
2816
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
2817
+ "div",
2818
+ {
2819
+ "aria-busy": "true",
2820
+ "aria-label": ariaLabel,
2821
+ className: cn("fractal-ghost-conversation", className),
2822
+ role: "status",
2823
+ ...props,
2824
+ children: [
2825
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--user", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "fractal-ghost-conversation__bubble", children: [
2826
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "100%" }),
2827
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "86%" }),
2828
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "64%" })
2829
+ ] }) }),
2830
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--assistant", children: [
2831
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "92%" }),
2832
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "78%" }),
2833
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "58%" })
2834
+ ] })
2835
+ ]
2836
+ }
2837
+ );
2838
+ }
2839
+ function SortableTableGhost(props) {
2840
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(DataTableGhost, { ...props });
2841
+ }
2842
+ function SidebarGhost({ className, items = 6, ...props }) {
2843
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("aside", { "aria-hidden": "true", className: cn("fractal-ghost-sidebar", className), ...props, children: [
2844
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-sidebar__brand" }),
2845
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "fractal-ghost-sidebar__items", children: Array.from({ length: items }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "fractal-ghost-sidebar__item", children: [
2846
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, {}),
2847
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: `${[54, 68, 44, 61][index % 4]}%` })
2848
+ ] }, index)) })
2849
+ ] });
2850
+ }
2851
+ function ModalGhost({ className, title, lines = 3, ...props }) {
2852
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SurfaceGhost, { className: cn("fractal-ghost-modal", className), title, lines, ...props });
2853
+ }
2854
+ function SidePanelGhost({ className, title, lines = 5, ...props }) {
2855
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SurfaceGhost, { className: cn("fractal-ghost-side-panel", className), title, lines, ...props });
2856
+ }
2857
+ function SurfaceGhost({ className, title, lines = 3, ...props }) {
2858
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-surface", className), ...props, children: [
2859
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "fractal-ghost-surface__header", children: [
2860
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: "42%" }),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Ghost, { className: "fractal-ghost-surface__close" })
2862
+ ] }),
2863
+ title && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "fractal-ghost-surface__title", children: title }),
2864
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GhostLine, { width: index === lines - 1 ? "64%" : "100%" }, index)) })
2865
+ ] });
2866
+ }
2867
+
1058
2868
  // src/components/Sidebar/Sidebar.tsx
1059
- var React10 = __toESM(require("react"), 1);
1060
- var import_jsx_runtime16 = require("react/jsx-runtime");
1061
- var Sidebar = React10.forwardRef(
1062
- ({ className, width, style, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2869
+ var React16 = __toESM(require("react"), 1);
2870
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2871
+ var Sidebar = React16.forwardRef(
2872
+ ({ className, width, compact = false, style, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1063
2873
  "aside",
1064
2874
  {
1065
2875
  ref,
1066
2876
  className: cn("fractal-sidebar", className),
2877
+ "data-compact": compact || void 0,
1067
2878
  style: { ...style, ...width ? { "--fractal-sidebar-width": width } : {} },
1068
2879
  ...props
1069
2880
  }
1070
2881
  )
1071
2882
  );
1072
2883
  Sidebar.displayName = "Sidebar";
1073
- var SidebarHeader = React10.forwardRef(
1074
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
2884
+ var SidebarHeader = React16.forwardRef(
2885
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1075
2886
  );
1076
2887
  SidebarHeader.displayName = "SidebarHeader";
1077
- var SidebarBrand = React10.forwardRef(
1078
- ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
2888
+ var SidebarBrand = React16.forwardRef(
2889
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
1079
2890
  );
1080
2891
  SidebarBrand.displayName = "SidebarBrand";
1081
- var SidebarNav = React10.forwardRef(
1082
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
2892
+ function SidebarAccountMenu({
2893
+ name,
2894
+ items,
2895
+ onSelect,
2896
+ triggerLabel = "Account menu",
2897
+ icon,
2898
+ className
2899
+ }) {
2900
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2901
+ Dropdown,
2902
+ {
2903
+ align: "left",
2904
+ className: cn("fractal-sidebar__account", className),
2905
+ items,
2906
+ onSelect,
2907
+ panelClassName: "fractal-sidebar__account-panel",
2908
+ selectedValue: "",
2909
+ triggerClassName: "fractal-sidebar__account-trigger",
2910
+ triggerContent: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2911
+ icon && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "fractal-sidebar__account-icon", "aria-hidden": "true", children: icon }),
2912
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "fractal-sidebar__account-name", children: name })
2913
+ ] }),
2914
+ triggerLabel,
2915
+ triggerVariant: "tertiary"
2916
+ }
2917
+ );
2918
+ }
2919
+ var SidebarNav = React16.forwardRef(
2920
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1083
2921
  );
1084
2922
  SidebarNav.displayName = "SidebarNav";
1085
- var SidebarSection = React10.forwardRef(
1086
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
2923
+ var SidebarSection = React16.forwardRef(
2924
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1087
2925
  );
1088
2926
  SidebarSection.displayName = "SidebarSection";
1089
- var SidebarNavItem = React10.forwardRef(
1090
- ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2927
+ var SidebarNavItem = React16.forwardRef(
2928
+ ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1091
2929
  "button",
1092
2930
  {
1093
2931
  ref,
@@ -1097,28 +2935,60 @@ var SidebarNavItem = React10.forwardRef(
1097
2935
  "aria-current": active ? "page" : void 0,
1098
2936
  ...props,
1099
2937
  children: [
1100
- icon && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1101
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-label", children }),
1102
- endAdornment && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
2938
+ icon && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
2939
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "fractal-sidebar__nav-item-label", children }),
2940
+ endAdornment && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1103
2941
  ]
1104
2942
  }
1105
2943
  )
1106
2944
  );
1107
2945
  SidebarNavItem.displayName = "SidebarNavItem";
1108
- var SidebarFooter = React10.forwardRef(
1109
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
2946
+ var SidebarFooter = React16.forwardRef(
2947
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1110
2948
  );
1111
2949
  SidebarFooter.displayName = "SidebarFooter";
1112
2950
 
1113
2951
  // src/components/SidePanel/SidePanel.tsx
1114
- var import_react_dom2 = require("react-dom");
1115
- var import_fractal_icons5 = require("@mirror-physics/fractal-icons");
1116
- var import_jsx_runtime17 = require("react/jsx-runtime");
1117
- function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
2952
+ var React17 = __toESM(require("react"), 1);
2953
+ var import_react_dom4 = require("react-dom");
2954
+ var import_fractal_icons14 = require("@mirror-physics/fractal-icons");
2955
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2956
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", loading = false, loadingLabel = "Loading", className }) {
1118
2957
  useEscapeDismiss(80, onClose, open);
2958
+ const bodyRef = React17.useRef(null);
2959
+ const [scrollEdges, setScrollEdges] = React17.useState({ above: false, below: false });
2960
+ React17.useLayoutEffect(() => {
2961
+ const body = bodyRef.current;
2962
+ if (!open || !body) return;
2963
+ const measure = () => {
2964
+ const maxScroll = Math.max(0, body.scrollHeight - body.clientHeight);
2965
+ const top = Math.max(0, Math.min(body.scrollTop, maxScroll));
2966
+ const above = maxScroll > 1 && top > 0;
2967
+ const below = maxScroll - top > 1;
2968
+ setScrollEdges((previous) => previous.above === above && previous.below === below ? previous : { above, below });
2969
+ };
2970
+ const resize = new ResizeObserver(measure);
2971
+ const observeContent = () => {
2972
+ resize.disconnect();
2973
+ resize.observe(body);
2974
+ for (const child of body.children) resize.observe(child);
2975
+ measure();
2976
+ };
2977
+ const mutations = new MutationObserver(observeContent);
2978
+ mutations.observe(body, { childList: true, subtree: true, characterData: true, attributes: true });
2979
+ body.addEventListener("scroll", measure, { passive: true });
2980
+ body.addEventListener("load", measure, true);
2981
+ observeContent();
2982
+ return () => {
2983
+ resize.disconnect();
2984
+ mutations.disconnect();
2985
+ body.removeEventListener("scroll", measure);
2986
+ body.removeEventListener("load", measure, true);
2987
+ };
2988
+ }, [open, loading]);
1119
2989
  if (!open) return null;
1120
- return (0, import_react_dom2.createPortal)(
1121
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2990
+ return (0, import_react_dom4.createPortal)(
2991
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1122
2992
  "aside",
1123
2993
  {
1124
2994
  className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
@@ -1127,15 +2997,15 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1127
2997
  role: "dialog",
1128
2998
  onMouseDown: (event) => event.stopPropagation(),
1129
2999
  children: [
1130
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("header", { className: "fractal-side-panel__header", children: [
1131
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "fractal-side-panel__heading", children: [
1132
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { className: "fractal-side-panel__title", children: title }),
1133
- description && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "fractal-side-panel__description", children: description })
3000
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("header", { className: "fractal-side-panel__header", "data-scroll-divider": scrollEdges.above, children: [
3001
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "fractal-side-panel__heading", children: [
3002
+ typeof title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h2", { className: "fractal-side-panel__title", children: title }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "fractal-side-panel__title", children: title }),
3003
+ description && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "fractal-side-panel__description", children: description })
1134
3004
  ] }),
1135
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fractal_icons5.Close, { size: 18 }) })
3005
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_fractal_icons14.Close, { size: 18 }) })
1136
3006
  ] }),
1137
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "fractal-side-panel__body", children }),
1138
- footer && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("footer", { className: "fractal-side-panel__footer", children: footer })
3007
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: bodyRef, "aria-busy": loading || void 0, className: cn("fractal-side-panel__body", loading && "fractal-side-panel__body--loading"), children: loading ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(UILoader, { label: loadingLabel }) : children }),
3008
+ footer && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("footer", { className: "fractal-side-panel__footer", "data-scroll-divider": scrollEdges.below, children: footer })
1139
3009
  ]
1140
3010
  }
1141
3011
  ) }),
@@ -1144,48 +3014,509 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1144
3014
  }
1145
3015
 
1146
3016
  // src/components/Textarea/Textarea.tsx
1147
- var React11 = __toESM(require("react"), 1);
1148
- var import_jsx_runtime18 = require("react/jsx-runtime");
1149
- var Textarea = React11.forwardRef(
1150
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("textarea", { ref, className: cn("fractal-textarea", className), ...props })
3017
+ var React18 = __toESM(require("react"), 1);
3018
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3019
+ var Textarea = React18.forwardRef(
3020
+ ({ className, fieldLabel, textStyle = "source", id, placeholder, ...props }, ref) => {
3021
+ const generatedId = React18.useId();
3022
+ const fieldId = id ?? generatedId;
3023
+ const input = /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3024
+ "textarea",
3025
+ {
3026
+ ref,
3027
+ id: fieldLabel ? fieldId : id,
3028
+ placeholder: fieldLabel ? placeholder || " " : placeholder,
3029
+ className: cn("fractal-textarea", className),
3030
+ "data-text-style": textStyle,
3031
+ ...props
3032
+ }
3033
+ );
3034
+ return fieldLabel ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "fractal-floating-field fractal-floating-field--textarea", "data-control-size": "md", children: [
3035
+ input,
3036
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("label", { className: "fractal-floating-field__label", htmlFor: fieldId, children: fieldLabel })
3037
+ ] }) : input;
3038
+ }
1151
3039
  );
1152
3040
  Textarea.displayName = "Textarea";
3041
+
3042
+ // src/components/TextButton/TextButton.tsx
3043
+ var React19 = __toESM(require("react"), 1);
3044
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3045
+ var TextButton = React19.forwardRef(
3046
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
3047
+ "button",
3048
+ {
3049
+ ref,
3050
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
3051
+ type: "button",
3052
+ ...props,
3053
+ children: [
3054
+ icon && iconPosition === "start" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
3055
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children }),
3056
+ icon && iconPosition === "end" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
3057
+ ]
3058
+ }
3059
+ )
3060
+ );
3061
+ TextButton.displayName = "TextButton";
3062
+
3063
+ // src/components/FileDropzone/FileDropzone.tsx
3064
+ var React20 = __toESM(require("react"), 1);
3065
+ var import_fractal_icons15 = require("@mirror-physics/fractal-icons");
3066
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3067
+ function FileDropzone({
3068
+ files,
3069
+ onFilesChange,
3070
+ accept,
3071
+ multiple = true,
3072
+ disabled = false,
3073
+ title = "Drop files here or click to browse",
3074
+ description = "Files stay attached to this record.",
3075
+ className
3076
+ }) {
3077
+ const inputId = React20.useId();
3078
+ const inputRef = React20.useRef(null);
3079
+ const [isDragging, setIsDragging] = React20.useState(false);
3080
+ const addFiles = (nextFiles) => {
3081
+ if (disabled) return;
3082
+ const additions = Array.from(nextFiles);
3083
+ if (additions.length === 0) return;
3084
+ onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
3085
+ };
3086
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: cn("fractal-file-dropzone", className), children: [
3087
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3088
+ "input",
3089
+ {
3090
+ ref: inputRef,
3091
+ id: inputId,
3092
+ className: "fractal-file-dropzone__input",
3093
+ type: "file",
3094
+ accept,
3095
+ multiple,
3096
+ disabled,
3097
+ onChange: (event) => {
3098
+ if (event.target.files) addFiles(event.target.files);
3099
+ event.target.value = "";
3100
+ }
3101
+ }
3102
+ ),
3103
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3104
+ "button",
3105
+ {
3106
+ className: "fractal-file-dropzone__target",
3107
+ "data-dragging": isDragging || void 0,
3108
+ type: "button",
3109
+ disabled,
3110
+ onClick: () => inputRef.current?.click(),
3111
+ onDragEnter: (event) => {
3112
+ event.preventDefault();
3113
+ if (!disabled) setIsDragging(true);
3114
+ },
3115
+ onDragOver: (event) => event.preventDefault(),
3116
+ onDragLeave: (event) => {
3117
+ event.preventDefault();
3118
+ setIsDragging(false);
3119
+ },
3120
+ onDrop: (event) => {
3121
+ event.preventDefault();
3122
+ setIsDragging(false);
3123
+ addFiles(event.dataTransfer.files);
3124
+ },
3125
+ children: [
3126
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_fractal_icons15.FileArrowUp, { "aria-hidden": "true", size: 20 }),
3127
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "fractal-file-dropzone__copy", children: [
3128
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-file-dropzone__title", children: title }),
3129
+ description && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-file-dropzone__description", children: description })
3130
+ ] })
3131
+ ]
3132
+ }
3133
+ ),
3134
+ files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "fractal-file-dropzone__files", "aria-label": "Attached files", children: files.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "fractal-file-dropzone__file", children: [
3135
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
3136
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
3137
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3138
+ "button",
3139
+ {
3140
+ className: "fractal-file-dropzone__remove",
3141
+ type: "button",
3142
+ "aria-label": `Remove ${file.name}`,
3143
+ disabled,
3144
+ onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
3145
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_fractal_icons15.Close, { "aria-hidden": "true", size: 14 })
3146
+ }
3147
+ )
3148
+ ] }, `${file.name}-${file.size}-${index}`)) })
3149
+ ] });
3150
+ }
3151
+ function formatFileSize(bytes) {
3152
+ if (bytes < 1024) return `${bytes} B`;
3153
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
3154
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3155
+ }
3156
+
3157
+ // src/components/Pagination/Pagination.tsx
3158
+ var import_fractal_icons16 = require("@mirror-physics/fractal-icons");
3159
+ var import_jsx_runtime36 = require("react/jsx-runtime");
3160
+ function Pagination({
3161
+ page,
3162
+ pageSize,
3163
+ totalItems,
3164
+ onPageChange,
3165
+ pageSizeOptions,
3166
+ onPageSizeChange,
3167
+ ariaLabel = "Pagination",
3168
+ className
3169
+ }) {
3170
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
3171
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
3172
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
3173
+ const end = Math.min(currentPage * pageSize, totalItems);
3174
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
3175
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
3176
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
3177
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "fractal-pagination__controls", children: [
3178
+ canChangePageSize && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3179
+ Dropdown,
3180
+ {
3181
+ align: "right",
3182
+ className: "fractal-pagination__page-size",
3183
+ direction: "up",
3184
+ items: pageSizeOptions.map((option) => ({
3185
+ value: String(option),
3186
+ description: `${option} per page`
3187
+ })),
3188
+ onSelect: (value) => onPageSizeChange(Number(value)),
3189
+ selectedValue: String(pageSize),
3190
+ size: "sm",
3191
+ triggerLabel: "Results per page",
3192
+ triggerVariant: "tertiary"
3193
+ }
3194
+ ),
3195
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3196
+ "button",
3197
+ {
3198
+ className: "fractal-pagination__button",
3199
+ type: "button",
3200
+ "aria-label": "Previous page",
3201
+ title: "Previous page",
3202
+ disabled: currentPage === 1 || totalItems === 0,
3203
+ onClick: () => onPageChange(currentPage - 1),
3204
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_fractal_icons16.ArrowLeft, { "aria-hidden": "true", size: 16 })
3205
+ }
3206
+ ),
3207
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
3208
+ currentPage,
3209
+ " of ",
3210
+ totalPages
3211
+ ] }),
3212
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3213
+ "button",
3214
+ {
3215
+ className: "fractal-pagination__button",
3216
+ type: "button",
3217
+ "aria-label": "Next page",
3218
+ title: "Next page",
3219
+ disabled: currentPage === totalPages || totalItems === 0,
3220
+ onClick: () => onPageChange(currentPage + 1),
3221
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_fractal_icons16.ArrowRight, { "aria-hidden": "true", size: 16 })
3222
+ }
3223
+ )
3224
+ ] })
3225
+ ] });
3226
+ }
3227
+
3228
+ // src/components/ColumnEditor/ColumnEditor.tsx
3229
+ var React21 = __toESM(require("react"), 1);
3230
+ var import_fractal_icons17 = require("@mirror-physics/fractal-icons");
3231
+ var import_jsx_runtime37 = require("react/jsx-runtime");
3232
+ var equal = (a, b) => a.length === b.length && a.every((id, index) => id === b[index]);
3233
+ function ColumnEditor({ items, value, onChange, label = "Edit columns", hiddenLabel = "Hidden columns", className }) {
3234
+ const [open, setOpen] = React21.useState(false);
3235
+ const [preview, setPreview] = React21.useState(null);
3236
+ const [announcement, setAnnouncement] = React21.useState("");
3237
+ const root = React21.useRef(null);
3238
+ const trigger = React21.useRef(null);
3239
+ const panel = React21.useRef(null);
3240
+ const drag = React21.useRef(null);
3241
+ const positions = React21.useRef(null);
3242
+ const id = React21.useId();
3243
+ function capturePositions() {
3244
+ positions.current = new Map(Array.from(panel.current?.querySelectorAll("[data-column-editor-item]") ?? []).map((row2) => [row2.dataset.columnEditorItem, row2.getBoundingClientRect()]));
3245
+ }
3246
+ React21.useLayoutEffect(() => {
3247
+ const before = positions.current;
3248
+ positions.current = null;
3249
+ if (!before || !panel.current || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
3250
+ const styles = getComputedStyle(panel.current);
3251
+ const token = styles.getPropertyValue("--duration-normal").trim();
3252
+ const duration = parseFloat(token) * (token.endsWith("ms") ? 1 : 1e3);
3253
+ const easing = styles.getPropertyValue("--ease-out").trim();
3254
+ for (const row2 of panel.current.querySelectorAll("[data-column-editor-item]")) {
3255
+ const previous = before.get(row2.dataset.columnEditorItem);
3256
+ row2.getAnimations().filter((animation) => animation.id === "fractal-column-position").forEach((animation) => animation.cancel());
3257
+ if (!previous) continue;
3258
+ const next = row2.getBoundingClientRect();
3259
+ const x = previous.left - next.left, y = previous.top - next.top;
3260
+ if (Math.abs(x) < 1 && Math.abs(y) < 1) continue;
3261
+ row2.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "translate(0, 0)" }], { duration, easing, id: "fractal-column-position" });
3262
+ }
3263
+ });
3264
+ const valid = [...new Set(value)].filter((key) => items.some((item) => item.id === key));
3265
+ const committed = [...valid, ...items.filter((item) => item.required && !valid.includes(item.id)).map((item) => item.id)];
3266
+ const visible = preview?.visible ?? committed;
3267
+ const hidden = preview?.hidden ?? items.filter((item) => !committed.includes(item.id)).map((item) => item.id);
3268
+ function cancel() {
3269
+ capturePositions();
3270
+ drag.current = null;
3271
+ setPreview(null);
3272
+ }
3273
+ React21.useEffect(() => {
3274
+ if (!open) return;
3275
+ panel.current?.focus();
3276
+ function outside(event) {
3277
+ if (!drag.current && !root.current?.contains(event.target)) setOpen(false);
3278
+ }
3279
+ function escape(event) {
3280
+ if (event.key !== "Escape") return;
3281
+ event.preventDefault();
3282
+ if (drag.current) {
3283
+ capturePositions();
3284
+ drag.current = null;
3285
+ setPreview(null);
3286
+ setAnnouncement("Reordering cancelled");
3287
+ } else {
3288
+ setOpen(false);
3289
+ trigger.current?.focus();
3290
+ }
3291
+ }
3292
+ function blur(event) {
3293
+ if (event.relatedTarget instanceof Node && !root.current?.contains(event.relatedTarget)) setOpen(false);
3294
+ }
3295
+ const element = root.current;
3296
+ document.addEventListener("pointerdown", outside);
3297
+ document.addEventListener("keydown", escape);
3298
+ element?.addEventListener("focusout", blur);
3299
+ return () => {
3300
+ document.removeEventListener("pointerdown", outside);
3301
+ document.removeEventListener("keydown", escape);
3302
+ element?.removeEventListener("focusout", blur);
3303
+ };
3304
+ }, [open]);
3305
+ function commit(next, message) {
3306
+ capturePositions();
3307
+ onChange(next);
3308
+ setAnnouncement(message);
3309
+ }
3310
+ function move(key, offset) {
3311
+ const index = committed.indexOf(key);
3312
+ const next = committed.filter((item) => item !== key);
3313
+ if (offset > 0 && index === committed.length - 1 && !items.find((item) => item.id === key)?.required) {
3314
+ commit(next, `${items.find((item) => item.id === key)?.label} hidden`);
3315
+ return;
3316
+ }
3317
+ next.splice(Math.max(0, Math.min(next.length, index + offset)), 0, key);
3318
+ commit(next, `${items.find((item) => item.id === key)?.label} moved ${offset < 0 ? "up" : "down"}`);
3319
+ }
3320
+ function start(event, key) {
3321
+ if (event.button !== 0 || event.target.closest("button")) return;
3322
+ drag.current = { id: key, pointerId: event.pointerId, x: event.clientX, y: event.clientY, active: false, visible: [...committed], hidden: [...hidden] };
3323
+ panel.current?.setPointerCapture(event.pointerId);
3324
+ event.preventDefault();
3325
+ }
3326
+ function update(event) {
3327
+ const current = drag.current;
3328
+ if (!current || event.pointerId !== current.pointerId) return;
3329
+ if (!current.active && Math.hypot(event.clientX - current.x, event.clientY - current.y) < 4) return;
3330
+ current.active = true;
3331
+ const container = panel.current;
3332
+ if (!container) return;
3333
+ const bounds = container.getBoundingClientRect();
3334
+ const hiddenGroup = container.querySelector('[data-column-editor-group="hidden"]');
3335
+ const inside = event.clientX >= bounds.left && event.clientX <= bounds.right && event.clientY >= bounds.top && event.clientY <= bounds.bottom;
3336
+ if (!inside) return;
3337
+ const destination = event.clientY >= hiddenGroup.getBoundingClientRect().top ? "hidden" : "visible";
3338
+ if (destination === "hidden" && items.find((item) => item.id === current.id)?.required) return;
3339
+ const slots = Array.from(container.querySelectorAll(`[data-column-editor-group="${destination}"] [data-column-editor-item]`));
3340
+ const localY = event.clientY - bounds.top + container.scrollTop - container.clientTop;
3341
+ const ownSlot = slots.find((row2) => row2.dataset.columnEditorItem === current.id);
3342
+ if (ownSlot && localY >= ownSlot.offsetTop && localY <= ownSlot.offsetTop + ownSlot.offsetHeight) return;
3343
+ const nextVisible = current.visible.filter((key) => key !== current.id);
3344
+ let nextHidden = current.hidden.filter((key) => key !== current.id);
3345
+ if (destination === "visible") {
3346
+ const index = slots.filter((row2) => row2.dataset.columnEditorItem !== current.id && localY > row2.offsetTop + row2.offsetHeight / 2).length;
3347
+ nextVisible.splice(index, 0, current.id);
3348
+ } else {
3349
+ nextHidden.push(current.id);
3350
+ nextHidden = items.filter((item) => nextHidden.includes(item.id)).map((item) => item.id);
3351
+ }
3352
+ if (!equal(nextVisible, current.visible) || !equal(nextHidden, current.hidden)) {
3353
+ capturePositions();
3354
+ current.visible = nextVisible;
3355
+ current.hidden = nextHidden;
3356
+ }
3357
+ setPreview({ visible: current.visible, hidden: current.hidden, active: current.id });
3358
+ if (event.clientY < bounds.top + 32) container.scrollTop -= 12;
3359
+ else if (event.clientY > bounds.bottom - 32) container.scrollTop += 12;
3360
+ }
3361
+ function finish(event) {
3362
+ const current = drag.current;
3363
+ if (!current || current.pointerId !== event.pointerId) return;
3364
+ const hit = document.elementFromPoint(event.clientX, event.clientY);
3365
+ if (current.active && hit && panel.current?.contains(hit)) commit(current.visible, "Column order updated");
3366
+ cancel();
3367
+ if (panel.current?.hasPointerCapture(event.pointerId)) panel.current.releasePointerCapture(event.pointerId);
3368
+ }
3369
+ function row(key, isHidden, index) {
3370
+ const item = items.find((item2) => item2.id === key);
3371
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3372
+ "li",
3373
+ {
3374
+ className: "fractal-column-editor__row",
3375
+ "data-column-editor-item": key,
3376
+ "data-hidden": isHidden || void 0,
3377
+ "data-dragging": preview?.active === key || void 0,
3378
+ onPointerDown: (event) => start(event, key),
3379
+ children: [
3380
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.DotsSixVertical, { className: "fractal-column-editor__grip", "aria-hidden": "true" }),
3381
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "fractal-column-editor__name", children: item.label }),
3382
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "fractal-column-editor__actions", onPointerDown: (event) => event.stopPropagation(), children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.Eye, { "aria-hidden": "true" }), "aria-label": `Show ${item.label} column`, onClick: () => commit([...committed, key], `${item.label} shown`) }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
3383
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.ArrowUp, { "aria-hidden": "true" }), "aria-label": `Move ${item.label} up`, disabled: index === 0, onClick: () => move(key, -1) }),
3384
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: index === visible.length - 1 && !item.required ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.EyeSlash, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.ArrowDown, { "aria-hidden": "true" }), "aria-label": index === visible.length - 1 && !item.required ? `Hide ${item.label} column` : `Move ${item.label} down`, disabled: index === visible.length - 1 && item.required, onClick: () => move(key, 1) })
3385
+ ] }) })
3386
+ ]
3387
+ },
3388
+ key
3389
+ );
3390
+ }
3391
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: cn("fractal-column-editor", className), ref: root, children: [
3392
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { ref: trigger, controlSize: "lg", buttonType: "secondary", icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.Gear, { "aria-hidden": "true" }), iconPosition: "right", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": id, onClick: () => {
3393
+ cancel();
3394
+ setOpen((current) => !current);
3395
+ }, children: label }),
3396
+ open ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { id, className: "fractal-column-editor__panel", ref: panel, role: "dialog", "data-dragging": Boolean(preview) || void 0, "aria-label": label, tabIndex: -1, onPointerMove: update, onPointerUp: finish, onPointerCancel: cancel, onLostPointerCapture: cancel, children: [
3397
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ol", { className: "fractal-column-editor__list", "data-column-editor-group": "visible", "aria-label": "Visible columns", children: visible.map((key, index) => row(key, false, index)) }),
3398
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("section", { "data-column-editor-group": "hidden", className: "fractal-column-editor__hidden", "aria-label": hiddenLabel, children: [
3399
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "fractal-column-editor__divider", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
3400
+ hiddenLabel,
3401
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons17.EyeSlash, { "aria-hidden": "true" })
3402
+ ] }) }),
3403
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ol", { className: "fractal-column-editor__list", children: hidden.map((key, index) => row(key, true, index)) }),
3404
+ !hidden.length ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "fractal-column-editor__empty", children: "Drag columns here to hide them." }) : null
3405
+ ] }),
3406
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "fractal-column-editor__announcement", role: "status", "aria-live": "polite", children: announcement })
3407
+ ] }) : null
3408
+ ] });
3409
+ }
3410
+
3411
+ // src/components/Tooltip/Tooltip.tsx
3412
+ var TooltipPrimitive = __toESM(require("@radix-ui/react-tooltip"), 1);
3413
+ var import_jsx_runtime38 = require("react/jsx-runtime");
3414
+ function Tooltip({
3415
+ content,
3416
+ children,
3417
+ placement = "bottom",
3418
+ maxWidth = 320,
3419
+ delay = 0,
3420
+ disabled = false,
3421
+ className
3422
+ }) {
3423
+ if (disabled || content == null || content === "") return children;
3424
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(TooltipPrimitive.Provider, { delayDuration: delay, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(TooltipPrimitive.Root, { children: [
3425
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(TooltipPrimitive.Trigger, { asChild: true, children }),
3426
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(TooltipPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3427
+ TooltipPrimitive.Content,
3428
+ {
3429
+ side: placement,
3430
+ sideOffset: 8,
3431
+ collisionPadding: 8,
3432
+ className: cn("fractal-tooltip", className),
3433
+ style: { "--tooltip-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth },
3434
+ children: content
3435
+ }
3436
+ ) })
3437
+ ] }) });
3438
+ }
1153
3439
  // Annotate the CommonJS export names for ESM import in node:
1154
3440
  0 && (module.exports = {
1155
3441
  Alert,
1156
3442
  AlertDescription,
3443
+ AlertGhost,
1157
3444
  AlertTitle,
3445
+ Breadcrumbs,
1158
3446
  Button,
3447
+ ButtonGhost,
1159
3448
  Card,
1160
3449
  CardContent,
1161
3450
  CardDescription,
1162
3451
  CardFooter,
3452
+ CardGhost,
1163
3453
  CardHeader,
1164
3454
  CardTitle,
3455
+ Checkbox,
3456
+ CheckboxGhost,
1165
3457
  Chip,
3458
+ ChipGhost,
3459
+ CodeBlock,
3460
+ ColumnEditor,
3461
+ ContentSwitcher,
3462
+ ContentSwitcherGhost,
3463
+ ConversationGhost,
1166
3464
  DataTable,
3465
+ DataTableGhost,
1167
3466
  Disclosure,
3467
+ DisclosureGhost,
1168
3468
  Dropdown,
3469
+ DropdownGhost,
1169
3470
  FeatureCard,
3471
+ FileDropzone,
3472
+ FileDropzoneGhost,
1170
3473
  FolderCard,
3474
+ Ghost,
3475
+ GhostLine,
1171
3476
  Input,
3477
+ InputGhost,
1172
3478
  Label,
3479
+ LabelGhost,
3480
+ LatticeLoader,
1173
3481
  Link,
3482
+ LinkGhost,
1174
3483
  Modal,
3484
+ ModalGhost,
3485
+ NumberInput,
3486
+ Pagination,
3487
+ PaginationGhost,
3488
+ PasswordInput,
1175
3489
  PromptCard,
3490
+ PromptCardGhost,
1176
3491
  PromptGrid,
3492
+ Radio,
1177
3493
  SidePanel,
3494
+ SidePanelGhost,
1178
3495
  Sidebar,
3496
+ SidebarAccountMenu,
1179
3497
  SidebarBrand,
1180
3498
  SidebarFooter,
3499
+ SidebarGhost,
1181
3500
  SidebarHeader,
1182
3501
  SidebarNav,
1183
3502
  SidebarNavItem,
1184
3503
  SidebarSection,
3504
+ Slider,
1185
3505
  SortableTable,
3506
+ SortableTableGhost,
3507
+ TableSearch,
1186
3508
  Tabs,
3509
+ TabsGhost,
3510
+ TextButton,
3511
+ TextButtonGhost,
1187
3512
  Textarea,
3513
+ TextareaGhost,
3514
+ Toast,
3515
+ ToastViewport,
1188
3516
  Toggle,
3517
+ ToggleGhost,
3518
+ Tooltip,
3519
+ UILoader,
1189
3520
  cn,
1190
3521
  useEscapeDismiss
1191
3522
  });