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

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