@mirror-physics/fractal-ui 0.2.0-next.6 → 0.2.0-next.61

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.js CHANGED
@@ -50,7 +50,7 @@ var colorPalette = {
50
50
  var sizeConfig = {
51
51
  xs: { height: "28px", padding: "4px 8px", iconOnly: "28px", fontSize: "0.75rem" },
52
52
  sm: { height: "32px", padding: "6px 12px", iconOnly: "32px", fontSize: "0.75rem" },
53
- md: { height: "38px", padding: "8px 16px", iconOnly: "38px", fontSize: "0.875rem" },
53
+ md: { height: "40px", padding: "8px 16px", iconOnly: "40px", fontSize: "0.875rem" },
54
54
  lg: { height: "44px", padding: "10px 20px", iconOnly: "44px", fontSize: "1rem" },
55
55
  xl: { height: "56px", padding: "14px 28px", iconOnly: "56px", fontSize: "1rem" }
56
56
  };
@@ -165,44 +165,415 @@ var Button = React.forwardRef(
165
165
  );
166
166
  Button.displayName = "Button";
167
167
 
168
+ // src/components/CodeBlock/CodeBlock.tsx
169
+ import { useEffect, useMemo, useRef, useState as useState2 } from "react";
170
+ import CodeMirror from "@uiw/react-codemirror";
171
+ import { HighlightStyle, foldGutter, foldService, syntaxHighlighting } from "@codemirror/language";
172
+ import { json } from "@codemirror/lang-json";
173
+ import {
174
+ Decoration,
175
+ EditorView,
176
+ MatchDecorator,
177
+ ViewPlugin,
178
+ lineNumbers as lineNumberGutter
179
+ } from "@codemirror/view";
180
+ import { tags } from "@lezer/highlight";
181
+ import { Check, Copy } from "@mirror-physics/fractal-icons";
182
+ import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
183
+ var codeTheme = EditorView.theme({
184
+ "&": {
185
+ backgroundColor: "transparent",
186
+ color: "var(--fg-primary)",
187
+ fontSize: "0.75rem"
188
+ },
189
+ "&.cm-focused": { outline: "none" },
190
+ ".cm-scroller": {
191
+ fontFamily: "var(--font-mono)",
192
+ lineHeight: "1.55",
193
+ overscrollBehavior: "none"
194
+ },
195
+ ".cm-content": { padding: "10px 0" },
196
+ ".cm-line": {
197
+ padding: "0 12px 0 calc(12px + var(--fractal-code-wrap-indent, 0ch))",
198
+ textIndent: "calc(0px - var(--fractal-code-wrap-indent, 0ch))"
199
+ },
200
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--fg-primary)" },
201
+ ".cm-gutters": {
202
+ backgroundColor: "var(--bg-canvas)",
203
+ borderRight: "1px solid var(--border-subtle)",
204
+ color: "var(--fg-tertiary)"
205
+ },
206
+ ".cm-lineNumbers .cm-gutterElement": { padding: "0 10px 0 4px" },
207
+ ".cm-foldGutter": { width: "14px" },
208
+ ".cm-foldGutter .cm-gutterElement": {
209
+ position: "relative",
210
+ display: "block",
211
+ width: "14px",
212
+ padding: "0"
213
+ },
214
+ ".cm-foldGutter .cm-gutterElement > span": {
215
+ position: "absolute",
216
+ top: "2px",
217
+ left: "0",
218
+ display: "inline-flex",
219
+ width: "14px",
220
+ height: "14px",
221
+ alignItems: "center",
222
+ justifyContent: "center",
223
+ padding: "0",
224
+ color: "var(--fg-tertiary)",
225
+ lineHeight: "14px",
226
+ transition: "color 120ms ease"
227
+ },
228
+ '.cm-foldGutter .cm-gutterElement > span[title="Fold line"]': { transform: "translateY(-3px)" },
229
+ ".cm-foldGutter .cm-gutterElement > span:hover": { color: "var(--fg-secondary)" },
230
+ ".fractal-code-block__escape": { color: "var(--fg-secondary)" },
231
+ ".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "var(--bg-surface-1)" },
232
+ ".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection": {
233
+ backgroundColor: "var(--accent-surface-stroke) !important"
234
+ },
235
+ ".cm-searchMatch": {
236
+ backgroundColor: "var(--warning-surface)",
237
+ outline: "1px solid var(--warning-surface-stroke)"
238
+ },
239
+ ".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "var(--warning-subtle)" },
240
+ ".cm-panels": { backgroundColor: "var(--bg-surface-1)", color: "var(--fg-primary)" },
241
+ ".cm-tooltip": {
242
+ borderColor: "var(--border-subtle)",
243
+ backgroundColor: "var(--bg-canvas)",
244
+ color: "var(--fg-primary)"
245
+ }
246
+ });
247
+ var fractalHighlightStyle = HighlightStyle.define([
248
+ { tag: tags.comment, color: "var(--fg-tertiary)", fontStyle: "italic" },
249
+ { tag: [tags.keyword, tags.controlKeyword, tags.operatorKeyword], color: "var(--purple-default)" },
250
+ { tag: [tags.string, tags.special(tags.string)], color: "var(--purple-default)" },
251
+ { tag: [tags.number, tags.bool, tags.null], color: "var(--orange-default)" },
252
+ { tag: [tags.function(tags.variableName), tags.labelName], color: "var(--accent-default)" },
253
+ { tag: [tags.typeName, tags.className, tags.namespace], color: "var(--pink-default)" },
254
+ { tag: [tags.propertyName, tags.attributeName], color: "var(--success-default)" },
255
+ { tag: [tags.operator, tags.punctuation], color: "var(--fg-secondary)" },
256
+ { tag: tags.bracket, color: "var(--accent-default)" },
257
+ { tag: [tags.heading, tags.strong], color: "var(--fg-primary)", fontWeight: "600" },
258
+ { tag: tags.emphasis, fontStyle: "italic" },
259
+ { tag: tags.link, color: "var(--accent-default)", textDecoration: "underline" },
260
+ { tag: tags.invalid, color: "var(--error-default)", textDecoration: "underline wavy" }
261
+ ]);
262
+ var jsonSupport = json();
263
+ var JSON_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["json", "jsonl", "ndjson", "geojson"]);
264
+ var JSON_LINES_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["jsonl", "ndjson"]);
265
+ var JSON_LINES_PRETTY_PRINT_THRESHOLD = 160;
266
+ var JSON_PROPERTY_PREFIX = /^(\s*"(?:\\.|[^"\\])*"\s*:\s*)/;
267
+ function countVisualColumns(value) {
268
+ let columns = 0;
269
+ for (const character of value) columns += character === " " ? 2 : 1;
270
+ return columns;
271
+ }
272
+ function getWrappedLineIndent(line) {
273
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
274
+ if (JSON_PROPERTY_PREFIX.test(line)) return Math.min(countVisualColumns(leadingWhitespace), 24);
275
+ return Math.min(countVisualColumns(leadingWhitespace) + 2, 12);
276
+ }
277
+ function buildWrappedLineIndentation(view) {
278
+ const decorations = [];
279
+ const decoratedLines = /* @__PURE__ */ new Set();
280
+ for (const visibleRange of view.visibleRanges) {
281
+ let position = visibleRange.from;
282
+ while (position <= visibleRange.to) {
283
+ const line = view.state.doc.lineAt(position);
284
+ if (!decoratedLines.has(line.from)) {
285
+ const indent = getWrappedLineIndent(line.text);
286
+ decorations.push(Decoration.line({
287
+ attributes: { style: `--fractal-code-wrap-indent: ${indent}ch` }
288
+ }).range(line.from));
289
+ decoratedLines.add(line.from);
290
+ }
291
+ if (line.to >= visibleRange.to) break;
292
+ position = line.to + 1;
293
+ }
294
+ }
295
+ return Decoration.set(decorations, true);
296
+ }
297
+ var wrappedLineIndentation = ViewPlugin.fromClass(class {
298
+ constructor(view) {
299
+ this.decorations = buildWrappedLineIndentation(view);
300
+ }
301
+ update(update) {
302
+ this.decorations = buildWrappedLineIndentation(update.view);
303
+ }
304
+ }, {
305
+ decorations: (plugin) => plugin.decorations
306
+ });
307
+ var jsonEscapeMatcher = new MatchDecorator({
308
+ regexp: /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g,
309
+ decoration: Decoration.mark({ class: "fractal-code-block__escape" })
310
+ });
311
+ var jsonEscapeHighlighting = ViewPlugin.fromClass(class {
312
+ constructor(view) {
313
+ this.decorations = jsonEscapeMatcher.createDeco(view);
314
+ }
315
+ update(update) {
316
+ this.decorations = jsonEscapeMatcher.updateDeco(update, this.decorations);
317
+ }
318
+ }, {
319
+ decorations: (plugin) => plugin.decorations
320
+ });
321
+ function formatJsonLinesForDisplay(value) {
322
+ return value.split(/\r?\n/).map((line) => {
323
+ const firstContentIndex = line.search(/\S/);
324
+ if (firstContentIndex < 0 || line.length - firstContentIndex <= JSON_LINES_PRETTY_PRINT_THRESHOLD) return line;
325
+ const indentation = line.slice(0, firstContentIndex);
326
+ try {
327
+ const parsed = JSON.parse(line.slice(firstContentIndex));
328
+ if (parsed === null || typeof parsed !== "object") return line;
329
+ return JSON.stringify(parsed, null, 2).split("\n").map((formattedLine) => `${indentation}${formattedLine}`).join("\n");
330
+ } catch {
331
+ return line;
332
+ }
333
+ }).join("\n");
334
+ }
335
+ function findJsonContainerEnd(document2, start) {
336
+ const openingCharacter = document2[start];
337
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
338
+ const closingCharacters = [];
339
+ let isInsideString = false;
340
+ let isEscaped = false;
341
+ for (let index = start; index < document2.length; index += 1) {
342
+ const character = document2[index];
343
+ if (isInsideString) {
344
+ if (isEscaped) {
345
+ isEscaped = false;
346
+ } else if (character === "\\") {
347
+ isEscaped = true;
348
+ } else if (character === '"') {
349
+ isInsideString = false;
350
+ }
351
+ continue;
352
+ }
353
+ if (character === '"') {
354
+ isInsideString = true;
355
+ } else if (character === "{") {
356
+ closingCharacters.push("}");
357
+ } else if (character === "[") {
358
+ closingCharacters.push("]");
359
+ } else if (character === "}" || character === "]") {
360
+ if (closingCharacters.pop() !== character) return null;
361
+ if (closingCharacters.length === 0) return index;
362
+ }
363
+ }
364
+ return null;
365
+ }
366
+ var jsonLinesFoldSupport = foldService.of((state, lineStart, lineEnd) => {
367
+ const line = state.doc.sliceString(lineStart, lineEnd);
368
+ const firstContentIndex = line.search(/\S/);
369
+ if (firstContentIndex < 0) return null;
370
+ const containerStart = lineStart + firstContentIndex;
371
+ const openingCharacter = line[firstContentIndex];
372
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
373
+ const document2 = state.doc.toString();
374
+ const containerEnd = findJsonContainerEnd(document2, containerStart);
375
+ if (containerEnd === null || containerEnd - containerStart < 2) return null;
376
+ try {
377
+ const parsed = JSON.parse(document2.slice(containerStart, containerEnd + 1));
378
+ if (parsed === null || typeof parsed !== "object") return null;
379
+ } catch {
380
+ return null;
381
+ }
382
+ return {
383
+ from: containerStart + 1,
384
+ to: containerEnd
385
+ };
386
+ });
387
+ function resolveLanguage(language, fileName) {
388
+ const normalized = language?.trim().toLowerCase();
389
+ const extension = fileName?.split(/[?#]/, 1)[0].split(".").pop()?.toLowerCase();
390
+ if (normalized && JSON_LANGUAGE_NAMES.has(normalized) || extension && JSON_LANGUAGE_NAMES.has(extension)) {
391
+ const isJsonLines = Boolean(
392
+ normalized && JSON_LINES_LANGUAGE_NAMES.has(normalized) || extension && JSON_LINES_LANGUAGE_NAMES.has(extension)
393
+ );
394
+ return { label: isJsonLines ? "JSONL" : "JSON", support: jsonSupport, isJsonLines };
395
+ }
396
+ if (normalized && !["text", "txt", "plaintext", "plain"].includes(normalized)) {
397
+ return { label: language?.trim(), support: null, isJsonLines: false };
398
+ }
399
+ return { support: null, isJsonLines: false };
400
+ }
401
+ function CodeBlock({
402
+ value,
403
+ language,
404
+ fileName,
405
+ label,
406
+ editable = false,
407
+ onChange,
408
+ lineNumbers = true,
409
+ wrap = true,
410
+ copyable = true,
411
+ ariaLabel,
412
+ height,
413
+ minHeight,
414
+ maxHeight = "28rem",
415
+ placeholder,
416
+ autoFocus,
417
+ className,
418
+ ...props
419
+ }) {
420
+ const resolvedLanguage = useMemo(() => resolveLanguage(language, fileName), [fileName, language]);
421
+ const displayValue = useMemo(
422
+ () => !editable && resolvedLanguage.isJsonLines ? formatJsonLinesForDisplay(value) : value,
423
+ [editable, resolvedLanguage.isJsonLines, value]
424
+ );
425
+ const [copyStatus, setCopyStatus] = useState2("idle");
426
+ const copyResetTimer = useRef(null);
427
+ useEffect(() => () => {
428
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
429
+ }, []);
430
+ const extensions = useMemo(() => [
431
+ codeTheme,
432
+ syntaxHighlighting(fractalHighlightStyle),
433
+ ...resolvedLanguage.support ? [resolvedLanguage.support] : [],
434
+ ...resolvedLanguage.support ? [jsonEscapeHighlighting] : [],
435
+ ...resolvedLanguage.isJsonLines ? [jsonLinesFoldSupport] : [],
436
+ ...lineNumbers ? [foldGutter(), lineNumberGutter()] : [],
437
+ ...wrap ? [EditorView.lineWrapping, wrappedLineIndentation] : []
438
+ ], [lineNumbers, resolvedLanguage.isJsonLines, resolvedLanguage.support, wrap]);
439
+ const languageLabel = resolvedLanguage.label;
440
+ const toolbarLabel = label ?? fileName ?? languageLabel;
441
+ const surfaceLabel = ariaLabel ?? (fileName ? `${fileName} ${editable ? "editor" : "viewer"}` : `Code ${editable ? "editor" : "viewer"}`);
442
+ const copyCode = async () => {
443
+ try {
444
+ await navigator.clipboard.writeText(value);
445
+ setCopyStatus("copied");
446
+ } catch {
447
+ setCopyStatus("error");
448
+ }
449
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
450
+ copyResetTimer.current = setTimeout(() => setCopyStatus("idle"), 1800);
451
+ };
452
+ const showToolbar = toolbarLabel !== void 0 || copyable || editable;
453
+ return /* @__PURE__ */ jsxs2(
454
+ "div",
455
+ {
456
+ className: cn(
457
+ "fractal-code-block",
458
+ editable && "fractal-code-block--editable",
459
+ !lineNumbers && "fractal-code-block--no-line-numbers",
460
+ className
461
+ ),
462
+ ...props,
463
+ children: [
464
+ showToolbar && /* @__PURE__ */ jsxs2("div", { className: "fractal-code-block__toolbar", children: [
465
+ /* @__PURE__ */ jsxs2("div", { className: "fractal-code-block__identity", children: [
466
+ toolbarLabel !== void 0 && /* @__PURE__ */ jsx("span", { className: "fractal-code-block__label", title: typeof toolbarLabel === "string" ? toolbarLabel : void 0, children: toolbarLabel }),
467
+ fileName && languageLabel && /* @__PURE__ */ jsx("span", { className: "fractal-code-block__language", children: languageLabel }),
468
+ editable && /* @__PURE__ */ jsx("span", { className: "fractal-code-block__mode", children: "Editing" })
469
+ ] }),
470
+ copyable && /* @__PURE__ */ jsxs2(
471
+ "button",
472
+ {
473
+ type: "button",
474
+ className: "fractal-code-block__copy",
475
+ "aria-label": copyStatus === "copied" ? "Code copied" : "Copy code",
476
+ onClick: () => void copyCode(),
477
+ children: [
478
+ copyStatus === "copied" ? /* @__PURE__ */ jsx(Check, { size: 14 }) : /* @__PURE__ */ jsx(Copy, { size: 14 }),
479
+ /* @__PURE__ */ jsx("span", { children: copyStatus === "copied" ? "Copied" : copyStatus === "error" ? "Copy failed" : "Copy" })
480
+ ]
481
+ }
482
+ )
483
+ ] }),
484
+ /* @__PURE__ */ jsx("div", { className: "fractal-code-block__surface", children: /* @__PURE__ */ jsx(
485
+ CodeMirror,
486
+ {
487
+ "aria-label": surfaceLabel,
488
+ value: displayValue,
489
+ editable,
490
+ readOnly: !editable,
491
+ onChange,
492
+ extensions,
493
+ theme: "none",
494
+ basicSetup: {
495
+ lineNumbers: false,
496
+ foldGutter: false,
497
+ drawSelection: editable,
498
+ highlightActiveLine: editable,
499
+ highlightActiveLineGutter: editable,
500
+ highlightSelectionMatches: editable,
501
+ autocompletion: editable,
502
+ history: editable
503
+ },
504
+ indentWithTab: editable,
505
+ height,
506
+ minHeight,
507
+ maxHeight,
508
+ placeholder,
509
+ autoFocus
510
+ }
511
+ ) }),
512
+ /* @__PURE__ */ jsx("span", { className: "fractal-code-block__status", "aria-live": "polite", children: copyStatus === "copied" ? "Code copied to clipboard." : copyStatus === "error" ? "Code could not be copied." : "" })
513
+ ]
514
+ }
515
+ );
516
+ }
517
+
518
+ // src/components/Breadcrumbs/Breadcrumbs.tsx
519
+ import { ChevronRight } from "@mirror-physics/fractal-icons";
520
+ import { jsx as jsx2, jsxs as jsxs3 } from "react/jsx-runtime";
521
+ function Breadcrumbs({
522
+ items,
523
+ ariaLabel = "Breadcrumbs",
524
+ separator = /* @__PURE__ */ jsx2(ChevronRight, { "aria-hidden": "true", size: 14 }),
525
+ className,
526
+ ...props
527
+ }) {
528
+ if (items.length === 0) return null;
529
+ return /* @__PURE__ */ jsx2("nav", { "aria-label": ariaLabel, className: cn("fractal-breadcrumbs", className), ...props, children: /* @__PURE__ */ jsx2("ol", { className: "fractal-breadcrumbs__list", children: items.map((item, index) => {
530
+ const isCurrent = index === items.length - 1;
531
+ const content = isCurrent ? /* @__PURE__ */ jsx2("span", { "aria-current": "page", className: "fractal-breadcrumbs__current", children: item.label }) : /* @__PURE__ */ jsx2("a", { className: "fractal-breadcrumbs__link", href: item.href ?? "#", onClick: item.onClick, children: item.label });
532
+ return /* @__PURE__ */ jsxs3("li", { className: "fractal-breadcrumbs__item", children: [
533
+ index > 0 && /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "fractal-breadcrumbs__separator", children: separator }),
534
+ content
535
+ ] }, index);
536
+ }) }) });
537
+ }
538
+
168
539
  // src/components/Card/Card.tsx
169
540
  import * as React2 from "react";
170
541
  import { Folder as FolderIcon } from "@mirror-physics/fractal-icons";
171
- import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
542
+ import { jsx as jsx3, jsxs as jsxs4 } from "react/jsx-runtime";
172
543
  var Card = React2.forwardRef(
173
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("fractal-card", className), ...props })
544
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("div", { ref, className: cn("fractal-card", className), ...props })
174
545
  );
175
546
  Card.displayName = "Card";
176
547
  var CardHeader = React2.forwardRef(
177
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("fractal-card-header", className), ...props })
548
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("div", { ref, className: cn("fractal-card-header", className), ...props })
178
549
  );
179
550
  CardHeader.displayName = "CardHeader";
180
551
  var CardTitle = React2.forwardRef(
181
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("h3", { ref, className: cn("fractal-card-title", className), ...props })
552
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("h3", { ref, className: cn("fractal-card-title", className), ...props })
182
553
  );
183
554
  CardTitle.displayName = "CardTitle";
184
555
  var CardDescription = React2.forwardRef(
185
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, className: cn("fractal-card-description", className), ...props })
556
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("p", { ref, className: cn("fractal-card-description", className), ...props })
186
557
  );
187
558
  CardDescription.displayName = "CardDescription";
188
559
  var CardContent = React2.forwardRef(
189
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("fractal-card-content", className), ...props })
560
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("div", { ref, className: cn("fractal-card-content", className), ...props })
190
561
  );
191
562
  CardContent.displayName = "CardContent";
192
563
  var CardFooter = React2.forwardRef(
193
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("fractal-card-footer", className), ...props })
564
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx3("div", { ref, className: cn("fractal-card-footer", className), ...props })
194
565
  );
195
566
  CardFooter.displayName = "CardFooter";
196
567
  var FeatureCard = React2.forwardRef(
197
- ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ jsxs2("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
198
- icon && /* @__PURE__ */ jsx("div", { className: "fractal-feature-card__icon", children: icon }),
199
- /* @__PURE__ */ jsx("h3", { className: "fractal-feature-card__title", children: title }),
200
- /* @__PURE__ */ jsx("p", { className: "fractal-feature-card__description", children: description })
568
+ ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ jsxs4("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
569
+ icon && /* @__PURE__ */ jsx3("div", { className: "fractal-feature-card__icon", children: icon }),
570
+ /* @__PURE__ */ jsx3("h3", { className: "fractal-feature-card__title", children: title }),
571
+ /* @__PURE__ */ jsx3("p", { className: "fractal-feature-card__description", children: description })
201
572
  ] })
202
573
  );
203
574
  FeatureCard.displayName = "FeatureCard";
204
575
  var FolderCard = React2.forwardRef(
205
- ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ jsxs2(
576
+ ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ jsxs4(
206
577
  "div",
207
578
  {
208
579
  ref,
@@ -218,11 +589,11 @@ var FolderCard = React2.forwardRef(
218
589
  className: cn("fractal-folder-card", selected && "fractal-folder-card--selected", className),
219
590
  ...props,
220
591
  children: [
221
- /* @__PURE__ */ jsxs2("div", { className: "fractal-folder-card__header", children: [
222
- /* @__PURE__ */ jsx(FolderIcon, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
223
- /* @__PURE__ */ jsx("span", { className: "fractal-folder-card__name", children: name })
592
+ /* @__PURE__ */ jsxs4("div", { className: "fractal-folder-card__header", children: [
593
+ /* @__PURE__ */ jsx3(FolderIcon, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
594
+ /* @__PURE__ */ jsx3("span", { className: "fractal-folder-card__name", children: name })
224
595
  ] }),
225
- sessionCount !== void 0 && /* @__PURE__ */ jsx("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ jsxs2("span", { className: "fractal-folder-card__sessions", children: [
596
+ sessionCount !== void 0 && /* @__PURE__ */ jsx3("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ jsxs4("span", { className: "fractal-folder-card__sessions", children: [
226
597
  sessionCount,
227
598
  " ",
228
599
  sessionCount === 1 ? "session" : "sessions"
@@ -235,7 +606,7 @@ FolderCard.displayName = "FolderCard";
235
606
 
236
607
  // src/components/PromptCard/PromptCard.tsx
237
608
  import * as React3 from "react";
238
- import { jsx as jsx2, jsxs as jsxs3 } from "react/jsx-runtime";
609
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
239
610
  var titleToPrompt = (title) => typeof title === "string" || typeof title === "number" ? String(title) : "";
240
611
  var PromptCard = React3.forwardRef(
241
612
  ({
@@ -256,7 +627,7 @@ var PromptCard = React3.forwardRef(
256
627
  onPromptSelect?.(prompt ?? titleToPrompt(title));
257
628
  }
258
629
  };
259
- return /* @__PURE__ */ jsxs3(
630
+ return /* @__PURE__ */ jsxs5(
260
631
  "button",
261
632
  {
262
633
  ref,
@@ -271,8 +642,8 @@ var PromptCard = React3.forwardRef(
271
642
  onClick: handleClick,
272
643
  ...props,
273
644
  children: [
274
- icon && /* @__PURE__ */ jsx2("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
275
- /* @__PURE__ */ jsx2("span", { className: "fractal-prompt-card__title", children: title })
645
+ icon && /* @__PURE__ */ jsx4("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
646
+ /* @__PURE__ */ jsx4("span", { className: "fractal-prompt-card__title", children: title })
276
647
  ]
277
648
  }
278
649
  );
@@ -280,7 +651,7 @@ var PromptCard = React3.forwardRef(
280
651
  );
281
652
  PromptCard.displayName = "PromptCard";
282
653
  var PromptGrid = React3.forwardRef(
283
- ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ jsx2(
654
+ ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ jsx4(
284
655
  "div",
285
656
  {
286
657
  ref,
@@ -298,9 +669,9 @@ PromptGrid.displayName = "PromptGrid";
298
669
 
299
670
  // src/components/Input/Input.tsx
300
671
  import * as React4 from "react";
301
- import { jsx as jsx3 } from "react/jsx-runtime";
672
+ import { jsx as jsx5 } from "react/jsx-runtime";
302
673
  var Input = React4.forwardRef(
303
- ({ className, type, ...props }, ref) => /* @__PURE__ */ jsx3(
674
+ ({ className, type, ...props }, ref) => /* @__PURE__ */ jsx5(
304
675
  "input",
305
676
  {
306
677
  type,
@@ -312,11 +683,181 @@ var Input = React4.forwardRef(
312
683
  );
313
684
  Input.displayName = "Input";
314
685
 
315
- // src/components/Label/Label.tsx
686
+ // src/components/TableSearch/TableSearch.tsx
316
687
  import * as React5 from "react";
317
- import { jsx as jsx4 } from "react/jsx-runtime";
318
- var Label = React5.forwardRef(
319
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
688
+ import { MagnifyingGlass, MagnifyingGlassLines } from "@mirror-physics/fractal-icons";
689
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
690
+ var TableSearch = React5.forwardRef(
691
+ ({
692
+ searchType = "text",
693
+ className,
694
+ inputClassName,
695
+ placeholder,
696
+ title,
697
+ ...props
698
+ }, ref) => {
699
+ const isGlob = searchType === "glob";
700
+ const SearchIcon = isGlob ? MagnifyingGlassLines : MagnifyingGlass;
701
+ return /* @__PURE__ */ jsxs6(
702
+ "div",
703
+ {
704
+ className: cn("fractal-table-search", className),
705
+ "data-search-type": searchType,
706
+ children: [
707
+ /* @__PURE__ */ jsx6(SearchIcon, { className: "fractal-table-search__icon", size: 17, "aria-hidden": "true" }),
708
+ /* @__PURE__ */ jsx6(
709
+ Input,
710
+ {
711
+ ...props,
712
+ ref,
713
+ type: "search",
714
+ className: cn("fractal-table-search__input", inputClassName),
715
+ placeholder: placeholder ?? (isGlob ? "Glob search \u2014 use *, ?, or [a-z]" : "Search"),
716
+ title: title ?? (isGlob ? "Glob pattern: *, **, ?, [abc], [a-z], and [!abc]" : void 0)
717
+ }
718
+ )
719
+ ]
720
+ }
721
+ );
722
+ }
723
+ );
724
+ TableSearch.displayName = "TableSearch";
725
+
726
+ // src/components/NumberInput/NumberInput.tsx
727
+ import * as React6 from "react";
728
+ import { Minus, Plus } from "@mirror-physics/fractal-icons";
729
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
730
+ function NumberInput({
731
+ unit,
732
+ className,
733
+ value,
734
+ defaultValue,
735
+ disabled,
736
+ readOnly,
737
+ min,
738
+ max,
739
+ ...props
740
+ }) {
741
+ const inputRef = React6.useRef(null);
742
+ const numericValue = Number(value ?? defaultValue);
743
+ const characterCount = Math.max(String(value ?? defaultValue ?? "").length, 1);
744
+ const controlLabel = typeof props["aria-label"] === "string" ? props["aria-label"] : "value";
745
+ const decrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && min !== void 0 && numericValue <= Number(min);
746
+ const incrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && max !== void 0 && numericValue >= Number(max);
747
+ const changeByStep = (direction) => {
748
+ const input = inputRef.current;
749
+ if (!input) return;
750
+ if (direction === -1) input.stepDown();
751
+ else input.stepUp();
752
+ input.dispatchEvent(new Event("input", { bubbles: true }));
753
+ input.focus();
754
+ };
755
+ return /* @__PURE__ */ jsxs7("div", { className: cn("fractal-number-input", unit && "fractal-number-input--with-unit", className), children: [
756
+ /* @__PURE__ */ jsx7(
757
+ Input,
758
+ {
759
+ ref: inputRef,
760
+ type: "number",
761
+ value,
762
+ defaultValue,
763
+ disabled,
764
+ readOnly,
765
+ min,
766
+ max,
767
+ ...props
768
+ }
769
+ ),
770
+ unit && /* @__PURE__ */ jsx7(
771
+ "span",
772
+ {
773
+ className: "fractal-number-input-unit",
774
+ style: { left: `calc(var(--space-3, 12px) + ${characterCount}ch)` },
775
+ "aria-hidden": "true",
776
+ children: unit
777
+ }
778
+ ),
779
+ /* @__PURE__ */ jsxs7("div", { className: "fractal-number-input-controls", children: [
780
+ /* @__PURE__ */ jsx7(
781
+ "button",
782
+ {
783
+ type: "button",
784
+ className: "fractal-number-input-step",
785
+ "aria-label": `Decrease ${controlLabel}`,
786
+ disabled: decrementDisabled,
787
+ onClick: () => changeByStep(-1),
788
+ children: /* @__PURE__ */ jsx7(Minus, { "aria-hidden": "true", size: 16 })
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsx7(
792
+ "button",
793
+ {
794
+ type: "button",
795
+ className: "fractal-number-input-step",
796
+ "aria-label": `Increase ${controlLabel}`,
797
+ disabled: incrementDisabled,
798
+ onClick: () => changeByStep(1),
799
+ children: /* @__PURE__ */ jsx7(Plus, { "aria-hidden": "true", size: 16 })
800
+ }
801
+ )
802
+ ] })
803
+ ] });
804
+ }
805
+
806
+ // src/components/PasswordInput/PasswordInput.tsx
807
+ import * as React7 from "react";
808
+ import { Eye, EyeSlash } from "@mirror-physics/fractal-icons";
809
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
810
+ var PasswordInput = React7.forwardRef(
811
+ ({
812
+ className,
813
+ disabled,
814
+ showPasswordLabel = "Show password",
815
+ hidePasswordLabel = "Hide password",
816
+ ...props
817
+ }, ref) => {
818
+ const [passwordVisible, setPasswordVisible] = React7.useState(false);
819
+ const toggleLabel = passwordVisible ? hidePasswordLabel : showPasswordLabel;
820
+ return /* @__PURE__ */ jsxs8(
821
+ "div",
822
+ {
823
+ className: cn("fractal-password-input", className),
824
+ "data-password-visible": passwordVisible ? "" : void 0,
825
+ children: [
826
+ /* @__PURE__ */ jsx8(
827
+ Input,
828
+ {
829
+ ...props,
830
+ ref,
831
+ className: "fractal-password-input-field",
832
+ disabled,
833
+ type: passwordVisible ? "text" : "password"
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsx8(
837
+ "button",
838
+ {
839
+ type: "button",
840
+ className: "fractal-password-input-toggle",
841
+ "aria-label": toggleLabel,
842
+ "aria-pressed": passwordVisible,
843
+ disabled,
844
+ title: toggleLabel,
845
+ onClick: () => setPasswordVisible((visible) => !visible),
846
+ children: passwordVisible ? /* @__PURE__ */ jsx8(EyeSlash, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ jsx8(Eye, { "aria-hidden": "true", size: 18 })
847
+ }
848
+ )
849
+ ]
850
+ }
851
+ );
852
+ }
853
+ );
854
+ PasswordInput.displayName = "PasswordInput";
855
+
856
+ // src/components/Label/Label.tsx
857
+ import * as React8 from "react";
858
+ import { jsx as jsx9 } from "react/jsx-runtime";
859
+ var Label = React8.forwardRef(
860
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
320
861
  "label",
321
862
  {
322
863
  ref,
@@ -328,44 +869,48 @@ var Label = React5.forwardRef(
328
869
  Label.displayName = "Label";
329
870
 
330
871
  // src/components/Alert/Alert.tsx
331
- import * as React6 from "react";
332
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
872
+ import * as React9 from "react";
873
+ import { CircleCheck, CircleClose, CircleInfo, Warning } from "@mirror-physics/fractal-icons";
874
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
333
875
  var defaultIcons = {
334
- default: "\u2139",
335
- info: "\u2139",
336
- success: "\u2713",
337
- warning: "\u26A0",
338
- error: "\u2715"
876
+ default: CircleInfo,
877
+ info: CircleInfo,
878
+ success: CircleCheck,
879
+ warning: Warning,
880
+ error: CircleClose
339
881
  };
340
- var Alert = React6.forwardRef(
341
- ({ className, variant = "default", icon, children, ...props }, ref) => /* @__PURE__ */ jsxs4(
342
- "div",
343
- {
344
- ref,
345
- role: "alert",
346
- className: cn("fractal-alert", `fractal-alert--${variant}`, className),
347
- ...props,
348
- children: [
349
- /* @__PURE__ */ jsx5("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? defaultIcons[variant] }),
350
- /* @__PURE__ */ jsx5("div", { className: "fractal-alert-content", children })
351
- ]
352
- }
353
- )
882
+ var Alert = React9.forwardRef(
883
+ ({ className, variant = "default", icon, children, ...props }, ref) => {
884
+ const DefaultIcon = defaultIcons[variant];
885
+ return /* @__PURE__ */ jsxs9(
886
+ "div",
887
+ {
888
+ ref,
889
+ role: "alert",
890
+ className: cn("fractal-alert", `fractal-alert--${variant}`, className),
891
+ ...props,
892
+ children: [
893
+ /* @__PURE__ */ jsx10("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? /* @__PURE__ */ jsx10(DefaultIcon, { size: 18 }) }),
894
+ /* @__PURE__ */ jsx10("div", { className: "fractal-alert-content", children })
895
+ ]
896
+ }
897
+ );
898
+ }
354
899
  );
355
900
  Alert.displayName = "Alert";
356
- var AlertTitle = React6.forwardRef(
357
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx5("h5", { ref, className: cn("fractal-alert-title", className), ...props })
901
+ var AlertTitle = React9.forwardRef(
902
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx10("h5", { ref, className: cn("fractal-alert-title", className), ...props })
358
903
  );
359
904
  AlertTitle.displayName = "AlertTitle";
360
- var AlertDescription = React6.forwardRef(
361
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx5("div", { ref, className: cn("fractal-alert-description", className), ...props })
905
+ var AlertDescription = React9.forwardRef(
906
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx10("div", { ref, className: cn("fractal-alert-description", className), ...props })
362
907
  );
363
908
  AlertDescription.displayName = "AlertDescription";
364
909
 
365
910
  // src/components/Toggle/Toggle.tsx
366
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
911
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
367
912
  function Toggle({ checked, onChange, label, className }) {
368
- return /* @__PURE__ */ jsxs5(
913
+ return /* @__PURE__ */ jsxs10(
369
914
  "button",
370
915
  {
371
916
  type: "button",
@@ -374,8 +919,8 @@ function Toggle({ checked, onChange, label, className }) {
374
919
  className: cn("fractal-toggle", className),
375
920
  onClick: () => onChange(!checked),
376
921
  children: [
377
- /* @__PURE__ */ jsx6("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ jsx6("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
378
- label && /* @__PURE__ */ jsx6("span", { className: "fractal-toggle-label", children: label })
922
+ /* @__PURE__ */ jsx11("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ jsx11("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
923
+ label && /* @__PURE__ */ jsx11("span", { className: "fractal-toggle-label", children: label })
379
924
  ]
380
925
  }
381
926
  );
@@ -385,7 +930,7 @@ function Toggle({ checked, onChange, label, className }) {
385
930
  import { createPortal } from "react-dom";
386
931
 
387
932
  // src/hooks/useEscapeDismiss.ts
388
- import { useEffect, useRef } from "react";
933
+ import { useEffect as useEffect2, useRef as useRef3 } from "react";
389
934
  var nextId = 0;
390
935
  var stack = [];
391
936
  function insertSorted(entry) {
@@ -418,9 +963,9 @@ function ensureGlobalListener() {
418
963
  );
419
964
  }
420
965
  function useEscapeDismiss(priority, handler, enabled = true) {
421
- const handlerRef = useRef(handler);
966
+ const handlerRef = useRef3(handler);
422
967
  handlerRef.current = handler;
423
- useEffect(() => {
968
+ useEffect2(() => {
424
969
  if (!enabled) return;
425
970
  ensureGlobalListener();
426
971
  const id = nextId++;
@@ -434,27 +979,84 @@ function useEscapeDismiss(priority, handler, enabled = true) {
434
979
  }, [priority, enabled]);
435
980
  }
436
981
 
982
+ // src/components/UILoader/UILoader.tsx
983
+ import { useId } from "react";
984
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
985
+ 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";
986
+ 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";
987
+ 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";
988
+ function UILoader({ size = 48, tone = "neutral", className, style, label = "Loading", ariaHidden = false }) {
989
+ const rawId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
990
+ const clipId = `fractal-ui-loader-clip-${rawId}`;
991
+ const gradientId = `fractal-ui-loader-gradient-${rawId}`;
992
+ const height = typeof size === "number" ? `${size}px` : size;
993
+ return /* @__PURE__ */ jsx12(
994
+ "span",
995
+ {
996
+ "aria-hidden": ariaHidden || void 0,
997
+ "aria-label": ariaHidden ? void 0 : label,
998
+ className: cn("fractal-ui-loader", `fractal-ui-loader--${tone}`, className),
999
+ role: ariaHidden ? void 0 : "status",
1000
+ style,
1001
+ children: /* @__PURE__ */ jsxs11("svg", { "aria-hidden": "true", fill: "none", viewBox: "0 0 144 92", style: { height, width: "auto" }, children: [
1002
+ /* @__PURE__ */ jsxs11("defs", { children: [
1003
+ /* @__PURE__ */ jsxs11("linearGradient", { id: gradientId, x1: "59", x2: "94.3", y1: "0", y2: "89.1", gradientUnits: "userSpaceOnUse", children: [
1004
+ /* @__PURE__ */ jsx12("stop", { className: "fractal-ui-loader__stop-start", offset: "0" }),
1005
+ /* @__PURE__ */ jsx12("stop", { className: "fractal-ui-loader__stop-end", offset: "1" })
1006
+ ] }),
1007
+ /* @__PURE__ */ jsxs11("linearGradient", { id: `${gradientId}-shine`, x1: "0", x2: "1", y1: "0", y2: "0", children: [
1008
+ /* @__PURE__ */ jsx12("stop", { offset: "0.4", stopColor: "white", stopOpacity: "0" }),
1009
+ /* @__PURE__ */ jsx12("stop", { offset: "0.5", stopColor: "white", stopOpacity: "0.62" }),
1010
+ /* @__PURE__ */ jsx12("stop", { offset: "0.6", stopColor: "white", stopOpacity: "0" })
1011
+ ] }),
1012
+ /* @__PURE__ */ jsxs11("clipPath", { id: clipId, children: [
1013
+ /* @__PURE__ */ jsx12("path", { d: LEFT }),
1014
+ /* @__PURE__ */ jsx12("path", { d: CENTER }),
1015
+ /* @__PURE__ */ jsx12("path", { d: RIGHT })
1016
+ ] })
1017
+ ] }),
1018
+ /* @__PURE__ */ jsx12("path", { d: CENTER, fill: `url(#${gradientId})` }),
1019
+ /* @__PURE__ */ jsx12("path", { d: RIGHT, fill: `url(#${gradientId})` }),
1020
+ /* @__PURE__ */ jsx12("path", { d: LEFT, fill: `url(#${gradientId})` }),
1021
+ /* @__PURE__ */ jsx12("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ jsx12("g", { className: "fractal-ui-loader__shine-axis", transform: "translate(72 46) rotate(68.4)", children: /* @__PURE__ */ jsx12("rect", { className: "fractal-ui-loader__shine", x: "-160", y: "-100", width: "320", height: "200", fill: `url(#${gradientId}-shine)` }) }) })
1022
+ ] })
1023
+ }
1024
+ );
1025
+ }
1026
+
437
1027
  // src/components/Modal/Modal.tsx
438
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
439
- var CloseIcon = () => /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx7("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
440
- function Modal({ open, onClose, title, children, danger, warn, footer, noClose, size = "sm", className }) {
441
- useEscapeDismiss(80, onClose, open && !noClose);
1028
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1029
+ var CloseIcon = () => /* @__PURE__ */ jsx13("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx13("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1030
+ function Modal({ open, onClose, title, children, danger, warn, footer, noClose, closeDisabled = false, size = "sm", loading = false, loadingLabel = "Loading", scrollable = false, className }) {
1031
+ useEscapeDismiss(80, onClose, open && !noClose && !closeDisabled);
442
1032
  if (!open) return null;
443
1033
  const sizeClass = `fractal-modal-panel--${size}`;
444
1034
  const accentClass = danger ? "fractal-modal-panel--danger" : warn ? "fractal-modal-panel--warn" : "";
445
1035
  return createPortal(
446
- /* @__PURE__ */ jsx7("div", { className: "fractal-modal-overlay", onClick: noClose ? void 0 : onClose, children: /* @__PURE__ */ jsxs6(
1036
+ /* @__PURE__ */ jsx13("div", { className: "fractal-modal-overlay", onClick: noClose || closeDisabled ? void 0 : onClose, children: /* @__PURE__ */ jsxs12(
447
1037
  "div",
448
1038
  {
449
- className: cn("fractal-modal-panel", sizeClass, accentClass, className),
1039
+ className: cn("fractal-modal-panel", sizeClass, accentClass, scrollable && "fractal-modal-panel--scrollable", className),
450
1040
  onClick: (e) => e.stopPropagation(),
451
1041
  children: [
452
- /* @__PURE__ */ jsxs6("div", { className: "fractal-modal-title-row", children: [
453
- /* @__PURE__ */ jsx7("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
454
- !noClose && /* @__PURE__ */ jsx7("button", { type: "button", onClick: onClose, title: "Close", className: "fractal-modal-close", children: /* @__PURE__ */ jsx7(CloseIcon, {}) })
1042
+ /* @__PURE__ */ jsxs12("div", { className: "fractal-modal-title-row", children: [
1043
+ /* @__PURE__ */ jsx13("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
1044
+ !noClose && /* @__PURE__ */ jsx13("button", { type: "button", onClick: onClose, title: "Close", disabled: closeDisabled, className: "fractal-modal-close", children: /* @__PURE__ */ jsx13(CloseIcon, {}) })
455
1045
  ] }),
456
- /* @__PURE__ */ jsx7("div", { className: cn("fractal-modal-body", size === "lg" && "fractal-modal-body--lg"), children }),
457
- footer && /* @__PURE__ */ jsx7("div", { className: "fractal-modal-footer", children: footer })
1046
+ /* @__PURE__ */ jsx13(
1047
+ "div",
1048
+ {
1049
+ "aria-busy": loading || void 0,
1050
+ className: cn(
1051
+ "fractal-modal-body",
1052
+ size === "lg" && "fractal-modal-body--lg",
1053
+ loading && "fractal-modal-body--loading",
1054
+ scrollable && "fractal-modal-body--scrollable"
1055
+ ),
1056
+ children: loading ? /* @__PURE__ */ jsx13(UILoader, { label: loadingLabel }) : children
1057
+ }
1058
+ ),
1059
+ footer && /* @__PURE__ */ jsx13("div", { className: cn("fractal-modal-footer", scrollable && "fractal-modal-footer--divided"), children: footer })
458
1060
  ]
459
1061
  }
460
1062
  ) }),
@@ -463,17 +1065,17 @@ function Modal({ open, onClose, title, children, danger, warn, footer, noClose,
463
1065
  }
464
1066
 
465
1067
  // src/components/DataTable/DataTable.tsx
466
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1068
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
467
1069
  function DataTable({ columns, data, emptyMessage, header, size = "md", className }) {
468
- return /* @__PURE__ */ jsx8("div", { className: cn("fractal-datatable", `fractal-datatable--${size}`, className), children: /* @__PURE__ */ jsxs7("div", { className: "fractal-datatable-surface", children: [
469
- header && /* @__PURE__ */ jsx8("div", { className: "fractal-datatable-header", children: header }),
470
- data.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs7("table", { className: "fractal-datatable-table", children: [
471
- /* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsx8("tr", { className: "fractal-datatable-thead-row", children: columns.map((col) => /* @__PURE__ */ jsx8("th", { className: "fractal-datatable-th", style: { width: col.width }, children: col.header }, col.key)) }) }),
472
- /* @__PURE__ */ jsx8("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx8(
1070
+ return /* @__PURE__ */ jsx14("div", { className: cn("fractal-datatable", `fractal-datatable--${size}`, className), children: /* @__PURE__ */ jsxs13("div", { className: "fractal-datatable-surface", children: [
1071
+ header && /* @__PURE__ */ jsx14("div", { className: "fractal-datatable-header", children: header }),
1072
+ data.length === 0 ? /* @__PURE__ */ jsx14("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs13("table", { className: "fractal-datatable-table", children: [
1073
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsx14("tr", { className: "fractal-datatable-thead-row", children: columns.map((col) => /* @__PURE__ */ jsx14("th", { className: "fractal-datatable-th", style: { width: col.width }, children: col.header }, col.key)) }) }),
1074
+ /* @__PURE__ */ jsx14("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx14(
473
1075
  "tr",
474
1076
  {
475
1077
  className: "fractal-datatable-row",
476
- children: columns.map((col) => /* @__PURE__ */ jsx8("td", { className: "fractal-datatable-td", style: { width: col.width }, children: col.render(row, rowIndex) }, col.key))
1078
+ children: columns.map((col) => /* @__PURE__ */ jsx14("td", { className: "fractal-datatable-td", style: { width: col.width }, children: col.render(row, rowIndex) }, col.key))
477
1079
  },
478
1080
  rowIndex
479
1081
  )) })
@@ -482,9 +1084,9 @@ function DataTable({ columns, data, emptyMessage, header, size = "md", className
482
1084
  }
483
1085
 
484
1086
  // src/components/Dropdown/Dropdown.tsx
485
- import * as React7 from "react";
486
- import { ChevronDown, ChevronUp } from "@mirror-physics/fractal-icons";
487
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1087
+ import * as React10 from "react";
1088
+ import { ChevronDown, ChevronLeft, ChevronRight as ChevronRight2, ChevronUp } from "@mirror-physics/fractal-icons";
1089
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
488
1090
  var MARGIN = 4;
489
1091
  function Dropdown({
490
1092
  items,
@@ -493,20 +1095,23 @@ function Dropdown({
493
1095
  triggerLabel = "Options",
494
1096
  direction = "down",
495
1097
  align = "left",
1098
+ triggerOrientation = "horizontal",
496
1099
  triggerContent,
497
1100
  label,
1101
+ iconOnly = false,
498
1102
  noChevron = false,
499
1103
  size = "md",
500
1104
  triggerVariant = "outline",
501
1105
  className,
502
1106
  triggerClassName,
503
1107
  panelClassName,
504
- closeOnSelect = true
1108
+ closeOnSelect = true,
1109
+ disabled = false
505
1110
  }) {
506
- const [open, setOpen] = React7.useState(false);
507
- const triggerRef = React7.useRef(null);
508
- const panelRef = React7.useRef(null);
509
- const [panelStyle, setPanelStyle] = React7.useState({});
1111
+ const [open, setOpen] = React10.useState(false);
1112
+ const triggerRef = React10.useRef(null);
1113
+ const panelRef = React10.useRef(null);
1114
+ const [panelStyle, setPanelStyle] = React10.useState({});
510
1115
  const visibleItems = items.filter(
511
1116
  (item) => item.icon != null || item.description != null && (typeof item.description === "string" ? item.description !== "" : true)
512
1117
  );
@@ -514,42 +1119,60 @@ function Dropdown({
514
1119
  const selectableItems = visibleItems.filter((i) => i.kind !== "header");
515
1120
  const selected = items.find((i) => i.value === selectedValue && i.kind !== "header") ?? selectableItems[0] ?? visibleItems[0];
516
1121
  const isUp = direction === "up";
517
- const ChevronIcon = isUp ? ChevronUp : ChevronDown;
518
- React7.useLayoutEffect(() => {
1122
+ const isHorizontalDirection = direction === "left" || direction === "right";
1123
+ const ChevronIcon = direction === "up" ? ChevronUp : direction === "left" ? ChevronLeft : direction === "right" ? ChevronRight2 : ChevronDown;
1124
+ React10.useLayoutEffect(() => {
519
1125
  if (!open || !triggerRef.current || !panelRef.current) return;
520
1126
  const triggerRect = triggerRef.current.getBoundingClientRect();
521
1127
  const panelRect = panelRef.current.getBoundingClientRect();
522
1128
  const vw = window.innerWidth;
523
1129
  const vh = window.innerHeight;
524
- const spaceBelow = vh - triggerRect.bottom - MARGIN;
525
- const spaceAbove = triggerRect.top - MARGIN;
526
- let placeAbove = isUp;
527
- if (placeAbove && panelRect.height > spaceAbove && spaceBelow > spaceAbove) {
528
- placeAbove = false;
529
- } else if (!placeAbove && panelRect.height > spaceBelow && spaceAbove > spaceBelow) {
530
- placeAbove = true;
531
- }
532
- const top = placeAbove ? -(panelRect.height + MARGIN) : triggerRect.height + MARGIN;
1130
+ let top;
533
1131
  let left;
534
1132
  let right;
535
- if (align === "right") {
536
- right = 0;
537
- const panelLeft = triggerRect.right - panelRect.width;
538
- if (panelLeft < 0) {
539
- right = void 0;
540
- left = 0;
1133
+ if (isHorizontalDirection) {
1134
+ const spaceRight = vw - triggerRect.right - MARGIN;
1135
+ const spaceLeft = triggerRect.left - MARGIN;
1136
+ let placeRight = direction === "right";
1137
+ if (placeRight && panelRect.width > spaceRight && spaceLeft > spaceRight) {
1138
+ placeRight = false;
1139
+ } else if (!placeRight && panelRect.width > spaceLeft && spaceRight > spaceLeft) {
1140
+ placeRight = true;
541
1141
  }
1142
+ left = placeRight ? triggerRect.width + MARGIN : -(panelRect.width + MARGIN);
1143
+ top = 0;
1144
+ const panelBottom = triggerRect.top + panelRect.height;
1145
+ if (panelBottom > vh - MARGIN) top -= panelBottom - (vh - MARGIN);
1146
+ if (triggerRect.top + top < MARGIN) top += MARGIN - (triggerRect.top + top);
542
1147
  } else {
543
- left = 0;
544
- const panelRight = triggerRect.left + panelRect.width;
545
- if (panelRight > vw) {
546
- left = void 0;
1148
+ const spaceBelow = vh - triggerRect.bottom - MARGIN;
1149
+ const spaceAbove = triggerRect.top - MARGIN;
1150
+ let placeAbove = isUp;
1151
+ if (placeAbove && panelRect.height > spaceAbove && spaceBelow > spaceAbove) {
1152
+ placeAbove = false;
1153
+ } else if (!placeAbove && panelRect.height > spaceBelow && spaceAbove > spaceBelow) {
1154
+ placeAbove = true;
1155
+ }
1156
+ top = placeAbove ? -(panelRect.height + MARGIN) : triggerRect.height + MARGIN;
1157
+ if (align === "right") {
547
1158
  right = 0;
1159
+ const panelLeft = triggerRect.right - panelRect.width;
1160
+ if (panelLeft < 0) {
1161
+ right = void 0;
1162
+ left = 0;
1163
+ }
1164
+ } else {
1165
+ left = 0;
1166
+ const panelRight = triggerRect.left + panelRect.width;
1167
+ if (panelRight > vw) {
1168
+ left = void 0;
1169
+ right = 0;
1170
+ }
548
1171
  }
549
1172
  }
550
1173
  setPanelStyle({ top, left, right });
551
- }, [open, isUp, align]);
552
- React7.useEffect(() => {
1174
+ }, [open, direction, isHorizontalDirection, isUp, align]);
1175
+ React10.useEffect(() => {
553
1176
  if (!open) return;
554
1177
  const handleClickOutside = (e) => {
555
1178
  const target = e.target;
@@ -559,12 +1182,13 @@ function Dropdown({
559
1182
  document.addEventListener("mousedown", handleClickOutside);
560
1183
  return () => document.removeEventListener("mousedown", handleClickOutside);
561
1184
  }, [open]);
562
- return /* @__PURE__ */ jsxs8("div", { className: cn("fractal-dropdown", className), children: [
563
- /* @__PURE__ */ jsxs8(
1185
+ return /* @__PURE__ */ jsxs14("div", { className: cn("fractal-dropdown", className), children: [
1186
+ /* @__PURE__ */ jsxs14(
564
1187
  "button",
565
1188
  {
566
1189
  ref: triggerRef,
567
1190
  type: "button",
1191
+ disabled,
568
1192
  "aria-haspopup": "listbox",
569
1193
  "aria-expanded": open,
570
1194
  "aria-label": triggerLabel,
@@ -573,18 +1197,20 @@ function Dropdown({
573
1197
  "fractal-dropdown-trigger",
574
1198
  `fractal-dropdown-trigger--${size}`,
575
1199
  `fractal-dropdown-trigger--${triggerVariant}`,
1200
+ `fractal-dropdown-trigger--${triggerOrientation}`,
1201
+ iconOnly && "fractal-dropdown-trigger--icon-only",
576
1202
  triggerClassName
577
1203
  ),
578
1204
  children: [
579
- triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs8(Fragment2, { children: [
580
- selected.icon != null && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected.icon }),
581
- selected.description != null && (typeof selected.description !== "string" || selected.description !== "") && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-selected-text", children: selected.description })
1205
+ triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs14(Fragment2, { children: [
1206
+ selected.icon != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected.icon }),
1207
+ selected.description != null && (typeof selected.description !== "string" || selected.description !== "") && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-selected-text", children: selected.description })
582
1208
  ] }),
583
- !noChevron && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ jsx9(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
1209
+ !noChevron && !iconOnly && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ jsx15(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
584
1210
  ]
585
1211
  }
586
1212
  ),
587
- open && /* @__PURE__ */ jsxs8(
1213
+ open && /* @__PURE__ */ jsxs14(
588
1214
  "div",
589
1215
  {
590
1216
  ref: panelRef,
@@ -593,21 +1219,21 @@ function Dropdown({
593
1219
  className: cn("fractal-dropdown-panel", `fractal-dropdown-panel--${size}`, panelClassName),
594
1220
  style: panelStyle,
595
1221
  children: [
596
- label != null && /* @__PURE__ */ jsx9("div", { className: "fractal-dropdown-label", children: label }),
1222
+ label != null && /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-label", children: label }),
597
1223
  visibleItems.map((item, index) => {
598
- const topDivider = item.border === "top" ? /* @__PURE__ */ jsx9("div", { className: "fractal-dropdown-separator" }) : null;
599
- const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ jsx9("div", { className: "fractal-dropdown-separator" }) : null;
1224
+ const topDivider = item.border === "top" ? /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-separator" }) : null;
1225
+ const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-separator" }) : null;
600
1226
  if (item.kind === "header") {
601
- return /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
1227
+ return /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
602
1228
  topDivider,
603
- /* @__PURE__ */ jsx9("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
1229
+ /* @__PURE__ */ jsx15("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
604
1230
  bottomDivider
605
1231
  ] }, `header-${index}`);
606
1232
  }
607
1233
  const isSelected = item.value === selectedValue;
608
- return /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
1234
+ return /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
609
1235
  topDivider,
610
- /* @__PURE__ */ jsxs8(
1236
+ /* @__PURE__ */ jsxs14(
611
1237
  "button",
612
1238
  {
613
1239
  type: "button",
@@ -619,9 +1245,9 @@ function Dropdown({
619
1245
  },
620
1246
  className: "fractal-dropdown-option",
621
1247
  children: [
622
- item.icon != null && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-icon", children: item.icon }),
623
- item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-option-text", children: item.description }),
624
- item.trailing != null && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-trailing", children: item.trailing })
1248
+ item.icon != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-icon", children: item.icon }),
1249
+ item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-option-text", children: item.description }),
1250
+ item.trailing != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-trailing", children: item.trailing })
625
1251
  ]
626
1252
  }
627
1253
  ),
@@ -635,10 +1261,10 @@ function Dropdown({
635
1261
  }
636
1262
 
637
1263
  // src/components/Link/Link.tsx
638
- import * as React8 from "react";
639
- import { jsx as jsx10 } from "react/jsx-runtime";
640
- var Link = React8.forwardRef(
641
- ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ jsx10(
1264
+ import * as React11 from "react";
1265
+ import { jsx as jsx16 } from "react/jsx-runtime";
1266
+ var Link = React11.forwardRef(
1267
+ ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ jsx16(
642
1268
  "a",
643
1269
  {
644
1270
  ref,
@@ -650,10 +1276,10 @@ var Link = React8.forwardRef(
650
1276
  Link.displayName = "Link";
651
1277
 
652
1278
  // src/components/Chip/Chip.tsx
653
- import * as React9 from "react";
654
- import { jsx as jsx11 } from "react/jsx-runtime";
655
- var Chip = React9.forwardRef(
656
- ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ jsx11(
1279
+ import * as React12 from "react";
1280
+ import { jsx as jsx17 } from "react/jsx-runtime";
1281
+ var Chip = React12.forwardRef(
1282
+ ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ jsx17(
657
1283
  "span",
658
1284
  {
659
1285
  ref,
@@ -664,36 +1290,88 @@ var Chip = React9.forwardRef(
664
1290
  );
665
1291
  Chip.displayName = "Chip";
666
1292
 
1293
+ // src/components/Checkbox/Checkbox.tsx
1294
+ import { Checkbox as CheckboxIcon, CheckboxChecked, Minus as Minus2 } from "@mirror-physics/fractal-icons";
1295
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1296
+ function Checkbox({
1297
+ checked,
1298
+ indeterminate = false,
1299
+ onCheckedChange,
1300
+ className,
1301
+ disabled,
1302
+ onClick,
1303
+ onKeyDown,
1304
+ ...props
1305
+ }) {
1306
+ return /* @__PURE__ */ jsx18(
1307
+ "button",
1308
+ {
1309
+ ...props,
1310
+ className: cn(
1311
+ "fractal-checkbox",
1312
+ (checked || indeterminate) && "fractal-checkbox--checked",
1313
+ indeterminate && "fractal-checkbox--indeterminate",
1314
+ className
1315
+ ),
1316
+ type: "button",
1317
+ role: "checkbox",
1318
+ "aria-checked": indeterminate ? "mixed" : checked,
1319
+ disabled,
1320
+ onClick: (event) => {
1321
+ onClick?.(event);
1322
+ if (!event.defaultPrevented) onCheckedChange(indeterminate || !checked);
1323
+ },
1324
+ onKeyDown: (event) => {
1325
+ onKeyDown?.(event);
1326
+ if (event.key === " " || event.key === "Enter") event.stopPropagation();
1327
+ },
1328
+ children: indeterminate ? /* @__PURE__ */ jsxs15("span", { className: "fractal-checkbox__indeterminate-icon", "aria-hidden": "true", children: [
1329
+ /* @__PURE__ */ jsx18(CheckboxIcon, { size: 18 }),
1330
+ /* @__PURE__ */ jsx18(Minus2, { className: "fractal-checkbox__indeterminate-mark", size: 12 })
1331
+ ] }) : checked ? /* @__PURE__ */ jsx18(CheckboxChecked, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ jsx18(CheckboxIcon, { "aria-hidden": "true", size: 18 })
1332
+ }
1333
+ );
1334
+ }
1335
+
667
1336
  // src/components/SortableTable/SortableTable.tsx
668
- import { useMemo, useState as useState3 } from "react";
1337
+ import { useMemo as useMemo2, useState as useState5 } from "react";
669
1338
  import { ArrowUp, ArrowDown, ArrowsUpDown } from "@mirror-physics/fractal-icons";
670
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1339
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
671
1340
  function SortableTable({
672
1341
  columns,
673
1342
  data,
674
1343
  emptyMessage,
675
1344
  header,
676
1345
  size = "md",
1346
+ embedded = false,
677
1347
  className,
1348
+ defaultSort,
678
1349
  defaultSortKey = void 0,
679
1350
  defaultSortDirection = null,
680
1351
  sortKey: controlledKey,
681
1352
  sortDirection: controlledDirection,
682
1353
  onSortChange,
1354
+ pagination,
683
1355
  highlightRow,
684
- onRowClick
1356
+ onRowClick,
1357
+ onRowDoubleClick,
1358
+ rowAction
685
1359
  }) {
686
- const [internalKey, setInternalKey] = useState3(defaultSortKey ?? null);
687
- const [internalDirection, setInternalDirection] = useState3(defaultSortDirection);
1360
+ const [internalKey, setInternalKey] = useState5(defaultSortKey ?? null);
1361
+ const [internalDirection, setInternalDirection] = useState5(defaultSortDirection);
688
1362
  const isControlled = controlledKey !== void 0 && controlledDirection !== void 0;
689
1363
  const activeKey = isControlled ? controlledKey : internalKey;
690
1364
  const activeDirection = isControlled ? controlledDirection : internalDirection;
691
- const sorted = useMemo(() => {
692
- if (!activeKey || !activeDirection) return data;
693
- const col = columns.find((c) => c.key === activeKey);
1365
+ const baselineKey = defaultSort?.key ?? null;
1366
+ const baselineDirection = defaultSort?.direction ?? null;
1367
+ const effectiveKey = activeKey && activeDirection ? activeKey : baselineKey;
1368
+ const effectiveDirection = activeKey && activeDirection ? activeDirection : baselineDirection;
1369
+ const sorted = useMemo2(() => {
1370
+ if (!effectiveKey || !effectiveDirection) return data;
1371
+ const col = columns.find((c) => c.key === effectiveKey);
694
1372
  if (!col) return data;
695
1373
  const accessor = col.sortAccessor ?? ((row) => {
696
- const value = row[activeKey];
1374
+ const value = row[effectiveKey];
697
1375
  return typeof value === "string" || typeof value === "number" ? value : null;
698
1376
  });
699
1377
  const copy = [...data];
@@ -704,26 +1382,36 @@ function SortableTable({
704
1382
  if (av == null) return 1;
705
1383
  if (bv == null) return -1;
706
1384
  if (typeof av === "number" && typeof bv === "number") {
707
- return activeDirection === "asc" ? av - bv : bv - av;
1385
+ return effectiveDirection === "asc" ? av - bv : bv - av;
708
1386
  }
709
1387
  const as = String(av).toLowerCase();
710
1388
  const bs = String(bv).toLowerCase();
711
- if (as < bs) return activeDirection === "asc" ? -1 : 1;
712
- if (as > bs) return activeDirection === "asc" ? 1 : -1;
1389
+ if (as < bs) return effectiveDirection === "asc" ? -1 : 1;
1390
+ if (as > bs) return effectiveDirection === "asc" ? 1 : -1;
713
1391
  return 0;
714
1392
  });
715
1393
  return copy;
716
- }, [data, columns, activeKey, activeDirection]);
1394
+ }, [data, columns, effectiveKey, effectiveDirection]);
1395
+ const visibleRows = useMemo2(() => {
1396
+ if (!pagination) return sorted;
1397
+ const pageSize = Math.max(1, pagination.pageSize);
1398
+ const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
1399
+ const page = Math.min(Math.max(1, pagination.page), totalPages);
1400
+ const startIndex = (page - 1) * pageSize;
1401
+ return sorted.slice(startIndex, startIndex + pageSize);
1402
+ }, [pagination, sorted]);
717
1403
  const handleSortClick = (key) => {
718
1404
  let nextDirection;
719
- if (activeKey !== key) {
1405
+ if (!activeKey || !activeDirection) {
1406
+ nextDirection = baselineKey === key && baselineDirection ? baselineDirection : "asc";
1407
+ } else if (activeKey !== key) {
720
1408
  nextDirection = "asc";
1409
+ } else if (baselineKey === key && baselineDirection) {
1410
+ nextDirection = activeDirection === baselineDirection ? baselineDirection === "asc" ? "desc" : "asc" : null;
721
1411
  } else if (activeDirection === "asc") {
722
1412
  nextDirection = "desc";
723
- } else if (activeDirection === "desc") {
724
- nextDirection = null;
725
1413
  } else {
726
- nextDirection = "asc";
1414
+ nextDirection = null;
727
1415
  }
728
1416
  const nextKey = nextDirection === null ? null : key;
729
1417
  if (isControlled) {
@@ -734,13 +1422,13 @@ function SortableTable({
734
1422
  onSortChange?.(nextKey, nextDirection);
735
1423
  }
736
1424
  };
737
- return /* @__PURE__ */ jsx12("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, className), children: /* @__PURE__ */ jsxs9("div", { className: "fractal-sortable-table-surface", children: [
738
- header && /* @__PURE__ */ jsx12("div", { className: "fractal-sortable-table-header", children: header }),
739
- sorted.length === 0 ? /* @__PURE__ */ jsx12("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs9("table", { className: "fractal-sortable-table-table", children: [
740
- /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsx12("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
1425
+ return /* @__PURE__ */ jsx19("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, embedded && "fractal-sortable-table--embedded", className), children: /* @__PURE__ */ jsxs16("div", { className: "fractal-sortable-table-surface", children: [
1426
+ header && /* @__PURE__ */ jsx19("div", { className: "fractal-sortable-table-header", children: header }),
1427
+ visibleRows.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs16("table", { className: "fractal-sortable-table-table", children: [
1428
+ /* @__PURE__ */ jsx19("thead", { children: /* @__PURE__ */ jsx19("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
741
1429
  const isActive = activeKey === col.key && activeDirection !== null;
742
1430
  if (!col.sortable) {
743
- return /* @__PURE__ */ jsx12(
1431
+ return /* @__PURE__ */ jsx19(
744
1432
  "th",
745
1433
  {
746
1434
  className: "fractal-sortable-table-th",
@@ -750,12 +1438,12 @@ function SortableTable({
750
1438
  col.key
751
1439
  );
752
1440
  }
753
- return /* @__PURE__ */ jsx12(
1441
+ return /* @__PURE__ */ jsx19(
754
1442
  "th",
755
1443
  {
756
1444
  className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
757
1445
  style: { width: col.width },
758
- children: /* @__PURE__ */ jsxs9(
1446
+ children: /* @__PURE__ */ jsxs16(
759
1447
  "button",
760
1448
  {
761
1449
  type: "button",
@@ -766,8 +1454,8 @@ function SortableTable({
766
1454
  onClick: () => handleSortClick(col.key),
767
1455
  "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
768
1456
  children: [
769
- /* @__PURE__ */ jsx12("span", { children: col.header }),
770
- /* @__PURE__ */ jsx12("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ jsx12(ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ jsx12(ArrowDown, { size: 14 }) : /* @__PURE__ */ jsx12(ArrowsUpDown, { size: 14 }) })
1457
+ /* @__PURE__ */ jsx19("span", { children: col.header }),
1458
+ /* @__PURE__ */ jsx19("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ jsx19(ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ jsx19(ArrowDown, { size: 14 }) : /* @__PURE__ */ jsx19(ArrowsUpDown, { size: 14 }) })
771
1459
  ]
772
1460
  }
773
1461
  )
@@ -775,31 +1463,45 @@ function SortableTable({
775
1463
  col.key
776
1464
  );
777
1465
  }) }) }),
778
- /* @__PURE__ */ jsx12("tbody", { children: sorted.map((row, rowIndex) => {
1466
+ /* @__PURE__ */ jsx19("tbody", { children: visibleRows.map((row, rowIndex) => {
779
1467
  const highlighted = highlightRow?.(row, rowIndex) ?? false;
780
- return /* @__PURE__ */ jsx12(
1468
+ const interactive = Boolean(onRowClick || onRowDoubleClick);
1469
+ const action = rowAction ? {
1470
+ label: rowAction.label(row, rowIndex),
1471
+ icon: rowAction.icon(row, rowIndex)
1472
+ } : null;
1473
+ return /* @__PURE__ */ jsx19(
781
1474
  "tr",
782
1475
  {
783
1476
  className: cn(
784
1477
  "fractal-sortable-table-row",
785
1478
  highlighted && "fractal-sortable-table-row--highlighted",
786
- onRowClick && "fractal-sortable-table-row--interactive"
1479
+ interactive && "fractal-sortable-table-row--interactive"
787
1480
  ),
788
1481
  onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
789
- onKeyDown: onRowClick ? (event) => {
1482
+ onDoubleClick: onRowDoubleClick ? () => onRowDoubleClick(row, rowIndex) : void 0,
1483
+ onKeyDown: interactive ? (event) => {
790
1484
  if (event.key === "Enter" || event.key === " ") {
791
1485
  event.preventDefault();
792
- onRowClick(row, rowIndex);
1486
+ const activateRow = onRowClick ?? onRowDoubleClick;
1487
+ activateRow?.(row, rowIndex);
793
1488
  }
794
1489
  } : void 0,
795
- role: onRowClick ? "button" : void 0,
796
- tabIndex: onRowClick ? 0 : void 0,
797
- children: columns.map((col) => /* @__PURE__ */ jsx12(
1490
+ "aria-label": action?.label,
1491
+ role: interactive ? "button" : void 0,
1492
+ tabIndex: interactive ? 0 : void 0,
1493
+ children: columns.map((col, columnIndex) => /* @__PURE__ */ jsxs16(
798
1494
  "td",
799
1495
  {
800
- className: "fractal-sortable-table-td",
1496
+ className: cn(
1497
+ "fractal-sortable-table-td",
1498
+ action && columnIndex === columns.length - 1 && "fractal-sortable-table-td--row-action"
1499
+ ),
801
1500
  style: { width: col.width, textAlign: col.align ?? "left" },
802
- children: col.render(row, rowIndex)
1501
+ children: [
1502
+ col.render(row, rowIndex),
1503
+ action && columnIndex === columns.length - 1 && /* @__PURE__ */ jsx19("span", { className: "fractal-sortable-table-row-action", "aria-hidden": "true", children: /* @__PURE__ */ jsx19("span", { className: "fractal-sortable-table-row-action__icon", children: action.icon }) })
1504
+ ]
803
1505
  },
804
1506
  col.key
805
1507
  ))
@@ -812,23 +1514,40 @@ function SortableTable({
812
1514
  }
813
1515
 
814
1516
  // src/components/Tabs/Tabs.tsx
815
- import { useId, useRef as useRef3, useState as useState4 } from "react";
816
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1517
+ import { useId as useId2, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState6 } from "react";
1518
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
817
1519
  function Tabs({
818
1520
  items,
819
1521
  defaultValue,
820
1522
  value: controlledValue,
821
1523
  onValueChange,
822
1524
  className,
823
- ariaLabel
1525
+ ariaLabel,
1526
+ divider = false,
1527
+ variant = "line"
824
1528
  }) {
825
- const reactId = useId();
826
- const [internalValue, setInternalValue] = useState4(
1529
+ const reactId = useId2();
1530
+ const [internalValue, setInternalValue] = useState6(
827
1531
  defaultValue ?? items.find((i) => !i.disabled)?.id ?? items[0]?.id ?? ""
828
1532
  );
829
1533
  const isControlled = controlledValue !== void 0;
830
1534
  const activeId = isControlled ? controlledValue : internalValue;
831
- const tabRefs = useRef3({});
1535
+ const tabListRef = useRef5(null);
1536
+ const tabRefs = useRef5({});
1537
+ const [indicator, setIndicator] = useState6(null);
1538
+ useLayoutEffect2(() => {
1539
+ const updateIndicator = () => {
1540
+ const activeTab = tabRefs.current[activeId];
1541
+ if (!activeTab) return;
1542
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
1543
+ };
1544
+ updateIndicator();
1545
+ const list = tabListRef.current;
1546
+ if (!list || typeof ResizeObserver === "undefined") return;
1547
+ const observer = new ResizeObserver(updateIndicator);
1548
+ observer.observe(list);
1549
+ return () => observer.disconnect();
1550
+ }, [activeId, items.length]);
832
1551
  const selectTab = (id) => {
833
1552
  if (isControlled) {
834
1553
  onValueChange?.(id);
@@ -838,6 +1557,10 @@ function Tabs({
838
1557
  }
839
1558
  };
840
1559
  const focusable = items.filter((i) => !i.disabled);
1560
+ const indicatorStyle = indicator ? {
1561
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
1562
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
1563
+ } : void 0;
841
1564
  const onKeyDown = (event) => {
842
1565
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
843
1566
  if (currentIndex === -1) return;
@@ -863,41 +1586,52 @@ function Tabs({
863
1586
  selectTab(nextId2);
864
1587
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
865
1588
  };
866
- return /* @__PURE__ */ jsxs10("div", { className: cn("fractal-tabs", className), children: [
867
- /* @__PURE__ */ jsx13("div", { role: "tablist", "aria-label": ariaLabel, className: "fractal-tabs-list", children: items.map((item) => {
868
- const isActive = item.id === activeId;
869
- const tabId = `${reactId}-tab-${item.id}`;
870
- const panelId = `${reactId}-panel-${item.id}`;
871
- return /* @__PURE__ */ jsx13(
872
- "button",
873
- {
874
- ref: (el) => {
875
- tabRefs.current[item.id] = el;
876
- },
877
- id: tabId,
878
- role: "tab",
879
- type: "button",
880
- "aria-selected": isActive,
881
- "aria-controls": panelId,
882
- tabIndex: isActive ? 0 : -1,
883
- disabled: item.disabled,
884
- className: cn(
885
- "fractal-tabs-trigger",
886
- isActive && "fractal-tabs-trigger--active",
887
- item.disabled && "fractal-tabs-trigger--disabled"
888
- ),
889
- onClick: () => !item.disabled && selectTab(item.id),
890
- onKeyDown,
891
- children: item.label
892
- },
893
- item.id
894
- );
895
- }) }),
1589
+ return /* @__PURE__ */ jsxs17("div", { className: cn("fractal-tabs", `fractal-tabs--${variant}`, divider && "fractal-tabs--divider", className), children: [
1590
+ /* @__PURE__ */ jsx20(
1591
+ "div",
1592
+ {
1593
+ ref: tabListRef,
1594
+ role: "tablist",
1595
+ "aria-label": ariaLabel,
1596
+ className: "fractal-tabs-list",
1597
+ "data-indicator-ready": indicator ? "true" : void 0,
1598
+ style: indicatorStyle,
1599
+ children: items.map((item) => {
1600
+ const isActive = item.id === activeId;
1601
+ const tabId = `${reactId}-tab-${item.id}`;
1602
+ const panelId = `${reactId}-panel-${item.id}`;
1603
+ return /* @__PURE__ */ jsx20(
1604
+ "button",
1605
+ {
1606
+ ref: (el) => {
1607
+ tabRefs.current[item.id] = el;
1608
+ },
1609
+ id: tabId,
1610
+ role: "tab",
1611
+ type: "button",
1612
+ "aria-selected": isActive,
1613
+ "aria-controls": panelId,
1614
+ tabIndex: isActive ? 0 : -1,
1615
+ disabled: item.disabled,
1616
+ className: cn(
1617
+ "fractal-tabs-trigger",
1618
+ isActive && "fractal-tabs-trigger--active",
1619
+ item.disabled && "fractal-tabs-trigger--disabled"
1620
+ ),
1621
+ onClick: () => !item.disabled && selectTab(item.id),
1622
+ onKeyDown,
1623
+ children: item.label
1624
+ },
1625
+ item.id
1626
+ );
1627
+ })
1628
+ }
1629
+ ),
896
1630
  items.map((item) => {
897
1631
  const tabId = `${reactId}-tab-${item.id}`;
898
1632
  const panelId = `${reactId}-panel-${item.id}`;
899
1633
  const isActive = item.id === activeId;
900
- return /* @__PURE__ */ jsx13(
1634
+ return /* @__PURE__ */ jsx20(
901
1635
  "div",
902
1636
  {
903
1637
  id: panelId,
@@ -913,10 +1647,112 @@ function Tabs({
913
1647
  ] });
914
1648
  }
915
1649
 
1650
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
1651
+ import * as React13 from "react";
1652
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1653
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
1654
+ const switcherRef = React13.useRef(null);
1655
+ const buttonRefs = React13.useRef([]);
1656
+ const indicatorMotionReadyRef = React13.useRef(false);
1657
+ const activeIndex = items.findIndex((item) => item.value === value);
1658
+ React13.useLayoutEffect(() => {
1659
+ const switcher = switcherRef.current;
1660
+ const activeButton = buttonRefs.current[activeIndex];
1661
+ let motionFrame = 0;
1662
+ if (!switcher || !activeButton) {
1663
+ switcher?.removeAttribute("data-indicator-ready");
1664
+ switcher?.removeAttribute("data-indicator-animate");
1665
+ indicatorMotionReadyRef.current = false;
1666
+ return;
1667
+ }
1668
+ const updateIndicator = () => {
1669
+ const switcherRect = switcher.getBoundingClientRect();
1670
+ const activeButtonRect = activeButton.getBoundingClientRect();
1671
+ switcher.style.setProperty("--fractal-content-switcher-indicator-x", `${activeButtonRect.left - switcherRect.left - switcher.clientLeft}px`);
1672
+ switcher.style.setProperty("--fractal-content-switcher-indicator-y", `${activeButtonRect.top - switcherRect.top - switcher.clientTop}px`);
1673
+ switcher.style.setProperty("--fractal-content-switcher-indicator-width", `${activeButtonRect.width}px`);
1674
+ switcher.style.setProperty("--fractal-content-switcher-indicator-height", `${activeButtonRect.height}px`);
1675
+ switcher.setAttribute("data-indicator-ready", "true");
1676
+ if (!indicatorMotionReadyRef.current && motionFrame === 0) {
1677
+ motionFrame = window.requestAnimationFrame(() => {
1678
+ indicatorMotionReadyRef.current = true;
1679
+ switcher.setAttribute("data-indicator-animate", "true");
1680
+ motionFrame = 0;
1681
+ });
1682
+ }
1683
+ };
1684
+ updateIndicator();
1685
+ if (typeof ResizeObserver === "undefined") {
1686
+ return () => {
1687
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
1688
+ };
1689
+ }
1690
+ const resizeObserver = new ResizeObserver(updateIndicator);
1691
+ resizeObserver.observe(switcher);
1692
+ buttonRefs.current.forEach((button) => {
1693
+ if (button) resizeObserver.observe(button);
1694
+ });
1695
+ return () => {
1696
+ resizeObserver.disconnect();
1697
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
1698
+ };
1699
+ }, [activeIndex, items.length]);
1700
+ const moveFocus = (index, direction) => {
1701
+ let nextIndex = index;
1702
+ do {
1703
+ nextIndex = (nextIndex + direction + items.length) % items.length;
1704
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
1705
+ if (!items[nextIndex]?.disabled) {
1706
+ buttonRefs.current[nextIndex]?.focus();
1707
+ onValueChange(items[nextIndex].value);
1708
+ }
1709
+ };
1710
+ return /* @__PURE__ */ jsxs18(
1711
+ "div",
1712
+ {
1713
+ className: cn("fractal-content-switcher", fullWidth && "fractal-content-switcher--full-width", className),
1714
+ role: "group",
1715
+ "aria-label": ariaLabel,
1716
+ ref: switcherRef,
1717
+ children: [
1718
+ /* @__PURE__ */ jsx21("span", { className: "fractal-content-switcher__indicator", "aria-hidden": true }),
1719
+ items.map((item, index) => {
1720
+ const active = item.value === value;
1721
+ return /* @__PURE__ */ jsx21(
1722
+ "button",
1723
+ {
1724
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
1725
+ type: "button",
1726
+ "aria-pressed": active,
1727
+ disabled: item.disabled,
1728
+ onClick: () => onValueChange(item.value),
1729
+ onKeyDown: (event) => {
1730
+ if (event.key === "ArrowRight") {
1731
+ event.preventDefault();
1732
+ moveFocus(index, 1);
1733
+ }
1734
+ if (event.key === "ArrowLeft") {
1735
+ event.preventDefault();
1736
+ moveFocus(index, -1);
1737
+ }
1738
+ },
1739
+ ref: (element) => {
1740
+ buttonRefs.current[index] = element;
1741
+ },
1742
+ children: item.label
1743
+ },
1744
+ item.value
1745
+ );
1746
+ })
1747
+ ]
1748
+ }
1749
+ );
1750
+ }
1751
+
916
1752
  // src/components/Disclosure/Disclosure.tsx
917
- import { useId as useId2, useState as useState5 } from "react";
918
- import { ChevronRight, ChevronDown as ChevronDown2 } from "@mirror-physics/fractal-icons";
919
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1753
+ import { useId as useId3, useState as useState7 } from "react";
1754
+ import { ChevronRight as ChevronRight3, ChevronDown as ChevronDown2 } from "@mirror-physics/fractal-icons";
1755
+ import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
920
1756
  function Disclosure({
921
1757
  title,
922
1758
  meta,
@@ -924,16 +1760,18 @@ function Disclosure({
924
1760
  defaultOpen = false,
925
1761
  open: controlledOpen,
926
1762
  onOpenChange,
1763
+ disabled = false,
927
1764
  className,
928
1765
  variant = "card"
929
1766
  }) {
930
- const reactId = useId2();
1767
+ const reactId = useId3();
931
1768
  const headerId = `${reactId}-header`;
932
1769
  const panelId = `${reactId}-panel`;
933
- const [internalOpen, setInternalOpen] = useState5(defaultOpen);
1770
+ const [internalOpen, setInternalOpen] = useState7(defaultOpen);
934
1771
  const isControlled = controlledOpen !== void 0;
935
1772
  const isOpen = isControlled ? controlledOpen : internalOpen;
936
1773
  const toggle = () => {
1774
+ if (disabled) return;
937
1775
  const next = !isOpen;
938
1776
  if (isControlled) {
939
1777
  onOpenChange?.(next);
@@ -942,17 +1780,18 @@ function Disclosure({
942
1780
  onOpenChange?.(next);
943
1781
  }
944
1782
  };
945
- return /* @__PURE__ */ jsxs11(
1783
+ return /* @__PURE__ */ jsxs19(
946
1784
  "div",
947
1785
  {
948
1786
  className: cn(
949
1787
  "fractal-disclosure",
950
1788
  variant === "card" && "fractal-disclosure--card",
951
1789
  isOpen && "fractal-disclosure--open",
1790
+ disabled && "fractal-disclosure--disabled",
952
1791
  className
953
1792
  ),
954
1793
  children: [
955
- /* @__PURE__ */ jsxs11(
1794
+ /* @__PURE__ */ jsxs19(
956
1795
  "button",
957
1796
  {
958
1797
  type: "button",
@@ -960,15 +1799,16 @@ function Disclosure({
960
1799
  className: "fractal-disclosure-trigger",
961
1800
  "aria-expanded": isOpen,
962
1801
  "aria-controls": panelId,
1802
+ disabled,
963
1803
  onClick: toggle,
964
1804
  children: [
965
- /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx14(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx14(ChevronRight, { size: 16 }) }),
966
- /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-title", children: title }),
967
- meta && /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-meta", children: meta })
1805
+ /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx22(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx22(ChevronRight3, { size: 16 }) }),
1806
+ /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-title", children: title }),
1807
+ meta && /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-meta", children: meta })
968
1808
  ]
969
1809
  }
970
1810
  ),
971
- /* @__PURE__ */ jsx14(
1811
+ /* @__PURE__ */ jsx22(
972
1812
  "div",
973
1813
  {
974
1814
  id: panelId,
@@ -976,7 +1816,7 @@ function Disclosure({
976
1816
  "aria-labelledby": headerId,
977
1817
  className: "fractal-disclosure-panel",
978
1818
  hidden: !isOpen,
979
- children: /* @__PURE__ */ jsx14("div", { className: "fractal-disclosure-panel-inner", children })
1819
+ children: /* @__PURE__ */ jsx22("div", { className: "fractal-disclosure-panel-inner", children })
980
1820
  }
981
1821
  )
982
1822
  ]
@@ -984,39 +1824,260 @@ function Disclosure({
984
1824
  );
985
1825
  }
986
1826
 
1827
+ // src/components/LatticeLoader/LatticeLoader.tsx
1828
+ import { jsx as jsx23 } from "react/jsx-runtime";
1829
+ var LATTICE_COUNT = 160;
1830
+ var WAVE_PERIOD = 80;
1831
+ function LatticeLoader({ className, label = "Loading", ...props }) {
1832
+ return /* @__PURE__ */ jsx23(
1833
+ "span",
1834
+ {
1835
+ "aria-label": label,
1836
+ className: cn("fractal-lattice-loader", className),
1837
+ role: "progressbar",
1838
+ ...props,
1839
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ jsx23(
1840
+ "span",
1841
+ {
1842
+ "aria-hidden": "true",
1843
+ className: "fractal-lattice-loader__cell",
1844
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
1845
+ },
1846
+ index
1847
+ ))
1848
+ }
1849
+ );
1850
+ }
1851
+
1852
+ // src/components/Ghost/Ghost.tsx
1853
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1854
+ var toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
1855
+ function Ghost({ className, width, height, radius, style, ...props }) {
1856
+ return /* @__PURE__ */ jsx24(
1857
+ "div",
1858
+ {
1859
+ "aria-hidden": "true",
1860
+ className: cn("fractal-ghost", className),
1861
+ style: {
1862
+ width: toCssLength(width),
1863
+ height: toCssLength(height),
1864
+ borderRadius: toCssLength(radius),
1865
+ ...style
1866
+ },
1867
+ ...props
1868
+ }
1869
+ );
1870
+ }
1871
+ function GhostLine({ className, size = "md", width = "100%", ...props }) {
1872
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-line", `fractal-ghost-line--${size}`, className), width, ...props });
1873
+ }
1874
+ function ButtonGhost({ className, size = "md", iconOnly = false, width, ...props }) {
1875
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
1876
+ }
1877
+ function CardGhost({ className, lines = 3, showHeader = true, ...props }) {
1878
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-card", className), ...props, children: [
1879
+ showHeader && /* @__PURE__ */ jsx24(GhostLine, { width: "38%" }),
1880
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx24(GhostLine, { width: index === lines - 1 ? "62%" : "100%" }, index)) })
1881
+ ] });
1882
+ }
1883
+ function DataTableGhost({
1884
+ className,
1885
+ showHeader = true,
1886
+ headers,
1887
+ columns = 4,
1888
+ rows = 5,
1889
+ columnWidths,
1890
+ size = "md",
1891
+ ...props
1892
+ }) {
1893
+ const cells = Array.from({ length: columns }, (_, index) => index);
1894
+ return /* @__PURE__ */ jsx24("div", { "aria-busy": "true", "aria-label": "Loading table", className: cn("fractal-ghost-table", `fractal-ghost-table--${size}`, className), role: "status", ...props, children: /* @__PURE__ */ jsxs20("table", { className: "fractal-ghost-table__table", children: [
1895
+ (showHeader || headers) && /* @__PURE__ */ jsx24("thead", { children: /* @__PURE__ */ jsx24("tr", { className: "fractal-ghost-table__head-row", children: cells.map((index) => /* @__PURE__ */ jsx24("th", { style: { width: columnWidths?.[index] }, children: headers?.[index] ?? /* @__PURE__ */ jsx24(GhostLine, { size: "sm", width: index === columns - 1 ? "62%" : "76%" }) }, index)) }) }),
1896
+ /* @__PURE__ */ jsx24("tbody", { children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx24("tr", { className: "fractal-ghost-table__row", children: cells.map((columnIndex) => /* @__PURE__ */ jsx24("td", { style: { width: columnWidths?.[columnIndex] }, children: /* @__PURE__ */ jsx24(GhostLine, { width: `${[72, 54, 82, 63, 46][(rowIndex + columnIndex) % 5]}%` }) }, columnIndex)) }, rowIndex)) })
1897
+ ] }) });
1898
+ }
1899
+ function InputGhost({ className, labelWidth = "26%", ...props }) {
1900
+ return /* @__PURE__ */ jsx24(FormFieldGhost, { className, labelWidth, ...props });
1901
+ }
1902
+ function TextareaGhost({ className, labelWidth = "26%", ...props }) {
1903
+ return /* @__PURE__ */ jsx24(FormFieldGhost, { className, labelWidth, multiline: true, ...props });
1904
+ }
1905
+ function LabelGhost({ className, width = "26%", ...props }) {
1906
+ return /* @__PURE__ */ jsx24(GhostLine, { className: cn("fractal-ghost-label", className), size: "sm", width, ...props });
1907
+ }
1908
+ function FormFieldGhost({ className, labelWidth = "26%", multiline = false, ...props }) {
1909
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-field", className), ...props, children: [
1910
+ /* @__PURE__ */ jsx24(LabelGhost, { width: labelWidth }),
1911
+ /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-field__control", multiline && "fractal-ghost-field__control--multiline") })
1912
+ ] });
1913
+ }
1914
+ function AlertGhost({ className, ...props }) {
1915
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-alert", className), ...props, children: [
1916
+ /* @__PURE__ */ jsx24(GhostLine, { width: "30%" }),
1917
+ /* @__PURE__ */ jsx24(GhostLine, { size: "sm", width: "78%" })
1918
+ ] });
1919
+ }
1920
+ function CheckboxGhost({ className, ...props }) {
1921
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-choice", className), ...props, children: [
1922
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-choice__control" }),
1923
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" })
1924
+ ] });
1925
+ }
1926
+ var ToggleGhost = CheckboxGhost;
1927
+ function ChipGhost({ className, width = 74, ...props }) {
1928
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-chip", className), height: 24, width, ...props });
1929
+ }
1930
+ function LinkGhost({ className, width = 96, ...props }) {
1931
+ return /* @__PURE__ */ jsx24(GhostLine, { className: cn("fractal-ghost-link", className), size: "sm", width, ...props });
1932
+ }
1933
+ var TextButtonGhost = LinkGhost;
1934
+ function DropdownGhost({ className, ...props }) {
1935
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropdown", className), ...props, children: [
1936
+ /* @__PURE__ */ jsx24(GhostLine, { width: "38%" }),
1937
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-dropdown__chevron" })
1938
+ ] });
1939
+ }
1940
+ function ContentSwitcherGhost({ className, options = 3, ...props }) {
1941
+ return /* @__PURE__ */ jsx24("div", { "aria-hidden": "true", className: cn("fractal-ghost-switcher", className), ...props, children: Array.from({ length: options }, (_, index) => /* @__PURE__ */ jsx24(Ghost, {}, index)) });
1942
+ }
1943
+ var TabsGhost = ContentSwitcherGhost;
1944
+ function DisclosureGhost({ className, ...props }) {
1945
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-disclosure", className), ...props, children: [
1946
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-disclosure__icon" }),
1947
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" })
1948
+ ] });
1949
+ }
1950
+ function PaginationGhost({ className, pages = 5, ...props }) {
1951
+ return /* @__PURE__ */ jsx24("div", { "aria-hidden": "true", className: cn("fractal-ghost-pagination", className), ...props, children: Array.from({ length: pages }, (_, index) => /* @__PURE__ */ jsx24(Ghost, {}, index)) });
1952
+ }
1953
+ function FileDropzoneGhost({ className, ...props }) {
1954
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropzone", className), ...props, children: [
1955
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-dropzone__icon" }),
1956
+ /* @__PURE__ */ jsx24(GhostLine, { width: "26%" }),
1957
+ /* @__PURE__ */ jsx24(GhostLine, { size: "sm", width: "46%" })
1958
+ ] });
1959
+ }
1960
+ function PromptCardGhost({ className, ...props }) {
1961
+ return /* @__PURE__ */ jsx24(CardGhost, { className: cn("fractal-ghost-prompt-card", className), lines: 4, ...props });
1962
+ }
1963
+ function ConversationGhost({
1964
+ className,
1965
+ "aria-label": ariaLabel = "Loading conversation",
1966
+ ...props
1967
+ }) {
1968
+ return /* @__PURE__ */ jsxs20(
1969
+ "div",
1970
+ {
1971
+ "aria-busy": "true",
1972
+ "aria-label": ariaLabel,
1973
+ className: cn("fractal-ghost-conversation", className),
1974
+ role: "status",
1975
+ ...props,
1976
+ children: [
1977
+ /* @__PURE__ */ jsx24("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--user", children: /* @__PURE__ */ jsxs20("div", { className: "fractal-ghost-conversation__bubble", children: [
1978
+ /* @__PURE__ */ jsx24(GhostLine, { width: "100%" }),
1979
+ /* @__PURE__ */ jsx24(GhostLine, { width: "86%" }),
1980
+ /* @__PURE__ */ jsx24(GhostLine, { width: "64%" })
1981
+ ] }) }),
1982
+ /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--assistant", children: [
1983
+ /* @__PURE__ */ jsx24(GhostLine, { width: "92%" }),
1984
+ /* @__PURE__ */ jsx24(GhostLine, { width: "78%" }),
1985
+ /* @__PURE__ */ jsx24(GhostLine, { width: "58%" })
1986
+ ] })
1987
+ ]
1988
+ }
1989
+ );
1990
+ }
1991
+ function SortableTableGhost(props) {
1992
+ return /* @__PURE__ */ jsx24(DataTableGhost, { ...props });
1993
+ }
1994
+ function SidebarGhost({ className, items = 6, ...props }) {
1995
+ return /* @__PURE__ */ jsxs20("aside", { "aria-hidden": "true", className: cn("fractal-ghost-sidebar", className), ...props, children: [
1996
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-sidebar__brand" }),
1997
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-sidebar__items", children: Array.from({ length: items }, (_, index) => /* @__PURE__ */ jsxs20("div", { className: "fractal-ghost-sidebar__item", children: [
1998
+ /* @__PURE__ */ jsx24(Ghost, {}),
1999
+ /* @__PURE__ */ jsx24(GhostLine, { width: `${[54, 68, 44, 61][index % 4]}%` })
2000
+ ] }, index)) })
2001
+ ] });
2002
+ }
2003
+ function ModalGhost({ className, title, lines = 3, ...props }) {
2004
+ return /* @__PURE__ */ jsx24(SurfaceGhost, { className: cn("fractal-ghost-modal", className), title, lines, ...props });
2005
+ }
2006
+ function SidePanelGhost({ className, title, lines = 5, ...props }) {
2007
+ return /* @__PURE__ */ jsx24(SurfaceGhost, { className: cn("fractal-ghost-side-panel", className), title, lines, ...props });
2008
+ }
2009
+ function SurfaceGhost({ className, title, lines = 3, ...props }) {
2010
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-surface", className), ...props, children: [
2011
+ /* @__PURE__ */ jsxs20("div", { className: "fractal-ghost-surface__header", children: [
2012
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" }),
2013
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-surface__close" })
2014
+ ] }),
2015
+ title && /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-surface__title", children: title }),
2016
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx24(GhostLine, { width: index === lines - 1 ? "64%" : "100%" }, index)) })
2017
+ ] });
2018
+ }
2019
+
987
2020
  // src/components/Sidebar/Sidebar.tsx
988
- import * as React10 from "react";
989
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
990
- var Sidebar = React10.forwardRef(
991
- ({ className, width, style, ...props }, ref) => /* @__PURE__ */ jsx15(
2021
+ import * as React14 from "react";
2022
+ import { Fragment as Fragment3, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
2023
+ var Sidebar = React14.forwardRef(
2024
+ ({ className, width, compact = false, style, ...props }, ref) => /* @__PURE__ */ jsx25(
992
2025
  "aside",
993
2026
  {
994
2027
  ref,
995
2028
  className: cn("fractal-sidebar", className),
2029
+ "data-compact": compact || void 0,
996
2030
  style: { ...style, ...width ? { "--fractal-sidebar-width": width } : {} },
997
2031
  ...props
998
2032
  }
999
2033
  )
1000
2034
  );
1001
2035
  Sidebar.displayName = "Sidebar";
1002
- var SidebarHeader = React10.forwardRef(
1003
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
2036
+ var SidebarHeader = React14.forwardRef(
2037
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1004
2038
  );
1005
2039
  SidebarHeader.displayName = "SidebarHeader";
1006
- var SidebarBrand = React10.forwardRef(
1007
- ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx15("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
2040
+ var SidebarBrand = React14.forwardRef(
2041
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx25("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
1008
2042
  );
1009
2043
  SidebarBrand.displayName = "SidebarBrand";
1010
- var SidebarNav = React10.forwardRef(
1011
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
2044
+ function SidebarAccountMenu({
2045
+ name,
2046
+ items,
2047
+ onSelect,
2048
+ triggerLabel = "Account menu",
2049
+ icon,
2050
+ className
2051
+ }) {
2052
+ return /* @__PURE__ */ jsx25(
2053
+ Dropdown,
2054
+ {
2055
+ align: "left",
2056
+ className: cn("fractal-sidebar__account", className),
2057
+ items,
2058
+ onSelect,
2059
+ panelClassName: "fractal-sidebar__account-panel",
2060
+ selectedValue: "",
2061
+ triggerClassName: "fractal-sidebar__account-trigger",
2062
+ triggerContent: /* @__PURE__ */ jsxs21(Fragment3, { children: [
2063
+ icon && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__account-icon", "aria-hidden": "true", children: icon }),
2064
+ /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__account-name", children: name })
2065
+ ] }),
2066
+ triggerLabel,
2067
+ triggerVariant: "tertiary"
2068
+ }
2069
+ );
2070
+ }
2071
+ var SidebarNav = React14.forwardRef(
2072
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1012
2073
  );
1013
2074
  SidebarNav.displayName = "SidebarNav";
1014
- var SidebarSection = React10.forwardRef(
1015
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
2075
+ var SidebarSection = React14.forwardRef(
2076
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1016
2077
  );
1017
2078
  SidebarSection.displayName = "SidebarSection";
1018
- var SidebarNavItem = React10.forwardRef(
1019
- ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs12(
2079
+ var SidebarNavItem = React14.forwardRef(
2080
+ ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs21(
1020
2081
  "button",
1021
2082
  {
1022
2083
  ref,
@@ -1026,39 +2087,28 @@ var SidebarNavItem = React10.forwardRef(
1026
2087
  "aria-current": active ? "page" : void 0,
1027
2088
  ...props,
1028
2089
  children: [
1029
- icon && /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1030
- /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-label", children }),
1031
- endAdornment && /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
2090
+ icon && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
2091
+ /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-label", children }),
2092
+ endAdornment && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1032
2093
  ]
1033
2094
  }
1034
2095
  )
1035
2096
  );
1036
2097
  SidebarNavItem.displayName = "SidebarNavItem";
1037
- var SidebarFooter = React10.forwardRef(
1038
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
2098
+ var SidebarFooter = React14.forwardRef(
2099
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1039
2100
  );
1040
2101
  SidebarFooter.displayName = "SidebarFooter";
1041
2102
 
1042
- // src/components/PageHeader/PageHeader.tsx
1043
- import * as React11 from "react";
1044
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1045
- var PageHeader = React11.forwardRef(
1046
- ({ title, actions, className, ...props }, ref) => /* @__PURE__ */ jsxs13("div", { ref, className: cn("fractal-page-header", className), ...props, children: [
1047
- /* @__PURE__ */ jsx16("div", { className: "fractal-page-header__content", children: /* @__PURE__ */ jsx16("h1", { className: "fractal-page-header__title", children: title }) }),
1048
- actions && /* @__PURE__ */ jsx16("div", { className: "fractal-page-header__actions", children: actions })
1049
- ] })
1050
- );
1051
- PageHeader.displayName = "PageHeader";
1052
-
1053
2103
  // src/components/SidePanel/SidePanel.tsx
1054
2104
  import { createPortal as createPortal2 } from "react-dom";
1055
2105
  import { Close } from "@mirror-physics/fractal-icons";
1056
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1057
- function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
2106
+ import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
2107
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", loading = false, loadingLabel = "Loading", className }) {
1058
2108
  useEscapeDismiss(80, onClose, open);
1059
2109
  if (!open) return null;
1060
2110
  return createPortal2(
1061
- /* @__PURE__ */ jsx17("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs14(
2111
+ /* @__PURE__ */ jsx26("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs22(
1062
2112
  "aside",
1063
2113
  {
1064
2114
  className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
@@ -1067,15 +2117,15 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1067
2117
  role: "dialog",
1068
2118
  onMouseDown: (event) => event.stopPropagation(),
1069
2119
  children: [
1070
- /* @__PURE__ */ jsxs14("header", { className: "fractal-side-panel__header", children: [
1071
- /* @__PURE__ */ jsxs14("div", { className: "fractal-side-panel__heading", children: [
1072
- /* @__PURE__ */ jsx17("h2", { className: "fractal-side-panel__title", children: title }),
1073
- description && /* @__PURE__ */ jsx17("p", { className: "fractal-side-panel__description", children: description })
2120
+ /* @__PURE__ */ jsxs22("header", { className: "fractal-side-panel__header", children: [
2121
+ /* @__PURE__ */ jsxs22("div", { className: "fractal-side-panel__heading", children: [
2122
+ typeof title === "string" ? /* @__PURE__ */ jsx26("h2", { className: "fractal-side-panel__title", children: title }) : /* @__PURE__ */ jsx26("div", { className: "fractal-side-panel__title", children: title }),
2123
+ description && /* @__PURE__ */ jsx26("p", { className: "fractal-side-panel__description", children: description })
1074
2124
  ] }),
1075
- /* @__PURE__ */ jsx17("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx17(Close, { size: 18 }) })
2125
+ /* @__PURE__ */ jsx26("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx26(Close, { size: 18 }) })
1076
2126
  ] }),
1077
- /* @__PURE__ */ jsx17("div", { className: "fractal-side-panel__body", children }),
1078
- footer && /* @__PURE__ */ jsx17("footer", { className: "fractal-side-panel__footer", children: footer })
2127
+ /* @__PURE__ */ jsx26("div", { "aria-busy": loading || void 0, className: cn("fractal-side-panel__body", loading && "fractal-side-panel__body--loading"), children: loading ? /* @__PURE__ */ jsx26(UILoader, { label: loadingLabel }) : children }),
2128
+ footer && /* @__PURE__ */ jsx26("footer", { className: "fractal-side-panel__footer", children: footer })
1079
2129
  ]
1080
2130
  }
1081
2131
  ) }),
@@ -1084,48 +2134,272 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1084
2134
  }
1085
2135
 
1086
2136
  // src/components/Textarea/Textarea.tsx
1087
- import * as React12 from "react";
1088
- import { jsx as jsx18 } from "react/jsx-runtime";
1089
- var Textarea = React12.forwardRef(
1090
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("textarea", { ref, className: cn("fractal-textarea", className), ...props })
2137
+ import * as React15 from "react";
2138
+ import { jsx as jsx27 } from "react/jsx-runtime";
2139
+ var Textarea = React15.forwardRef(
2140
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx27("textarea", { ref, className: cn("fractal-textarea", className), ...props })
1091
2141
  );
1092
2142
  Textarea.displayName = "Textarea";
2143
+
2144
+ // src/components/TextButton/TextButton.tsx
2145
+ import * as React16 from "react";
2146
+ import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
2147
+ var TextButton = React16.forwardRef(
2148
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ jsxs23(
2149
+ "button",
2150
+ {
2151
+ ref,
2152
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
2153
+ type: "button",
2154
+ ...props,
2155
+ children: [
2156
+ icon && iconPosition === "start" && /* @__PURE__ */ jsx28("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
2157
+ /* @__PURE__ */ jsx28("span", { children }),
2158
+ icon && iconPosition === "end" && /* @__PURE__ */ jsx28("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
2159
+ ]
2160
+ }
2161
+ )
2162
+ );
2163
+ TextButton.displayName = "TextButton";
2164
+
2165
+ // src/components/FileDropzone/FileDropzone.tsx
2166
+ import * as React17 from "react";
2167
+ import { Close as Close2, FileArrowUp } from "@mirror-physics/fractal-icons";
2168
+ import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
2169
+ function FileDropzone({
2170
+ files,
2171
+ onFilesChange,
2172
+ accept,
2173
+ multiple = true,
2174
+ disabled = false,
2175
+ title = "Drop files here or click to browse",
2176
+ description = "Files stay attached to this record.",
2177
+ className
2178
+ }) {
2179
+ const inputId = React17.useId();
2180
+ const inputRef = React17.useRef(null);
2181
+ const [isDragging, setIsDragging] = React17.useState(false);
2182
+ const addFiles = (nextFiles) => {
2183
+ if (disabled) return;
2184
+ const additions = Array.from(nextFiles);
2185
+ if (additions.length === 0) return;
2186
+ onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
2187
+ };
2188
+ return /* @__PURE__ */ jsxs24("div", { className: cn("fractal-file-dropzone", className), children: [
2189
+ /* @__PURE__ */ jsx29(
2190
+ "input",
2191
+ {
2192
+ ref: inputRef,
2193
+ id: inputId,
2194
+ className: "fractal-file-dropzone__input",
2195
+ type: "file",
2196
+ accept,
2197
+ multiple,
2198
+ disabled,
2199
+ onChange: (event) => {
2200
+ if (event.target.files) addFiles(event.target.files);
2201
+ event.target.value = "";
2202
+ }
2203
+ }
2204
+ ),
2205
+ /* @__PURE__ */ jsxs24(
2206
+ "button",
2207
+ {
2208
+ className: "fractal-file-dropzone__target",
2209
+ "data-dragging": isDragging || void 0,
2210
+ type: "button",
2211
+ disabled,
2212
+ onClick: () => inputRef.current?.click(),
2213
+ onDragEnter: (event) => {
2214
+ event.preventDefault();
2215
+ if (!disabled) setIsDragging(true);
2216
+ },
2217
+ onDragOver: (event) => event.preventDefault(),
2218
+ onDragLeave: (event) => {
2219
+ event.preventDefault();
2220
+ setIsDragging(false);
2221
+ },
2222
+ onDrop: (event) => {
2223
+ event.preventDefault();
2224
+ setIsDragging(false);
2225
+ addFiles(event.dataTransfer.files);
2226
+ },
2227
+ children: [
2228
+ /* @__PURE__ */ jsx29(FileArrowUp, { "aria-hidden": "true", size: 20 }),
2229
+ /* @__PURE__ */ jsxs24("span", { className: "fractal-file-dropzone__copy", children: [
2230
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__title", children: title }),
2231
+ description && /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__description", children: description })
2232
+ ] })
2233
+ ]
2234
+ }
2235
+ ),
2236
+ files.length > 0 && /* @__PURE__ */ jsx29("ul", { className: "fractal-file-dropzone__files", "aria-label": "Attached files", children: files.map((file, index) => /* @__PURE__ */ jsxs24("li", { className: "fractal-file-dropzone__file", children: [
2237
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
2238
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
2239
+ /* @__PURE__ */ jsx29(
2240
+ "button",
2241
+ {
2242
+ className: "fractal-file-dropzone__remove",
2243
+ type: "button",
2244
+ "aria-label": `Remove ${file.name}`,
2245
+ disabled,
2246
+ onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
2247
+ children: /* @__PURE__ */ jsx29(Close2, { "aria-hidden": "true", size: 14 })
2248
+ }
2249
+ )
2250
+ ] }, `${file.name}-${file.size}-${index}`)) })
2251
+ ] });
2252
+ }
2253
+ function formatFileSize(bytes) {
2254
+ if (bytes < 1024) return `${bytes} B`;
2255
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
2256
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2257
+ }
2258
+
2259
+ // src/components/Pagination/Pagination.tsx
2260
+ import { ArrowLeft, ArrowRight } from "@mirror-physics/fractal-icons";
2261
+ import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
2262
+ function Pagination({
2263
+ page,
2264
+ pageSize,
2265
+ totalItems,
2266
+ onPageChange,
2267
+ pageSizeOptions,
2268
+ onPageSizeChange,
2269
+ ariaLabel = "Pagination",
2270
+ className
2271
+ }) {
2272
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
2273
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
2274
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
2275
+ const end = Math.min(currentPage * pageSize, totalItems);
2276
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
2277
+ return /* @__PURE__ */ jsxs25("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
2278
+ /* @__PURE__ */ jsx30("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
2279
+ /* @__PURE__ */ jsxs25("div", { className: "fractal-pagination__controls", children: [
2280
+ canChangePageSize && /* @__PURE__ */ jsx30(
2281
+ Dropdown,
2282
+ {
2283
+ align: "right",
2284
+ className: "fractal-pagination__page-size",
2285
+ direction: "up",
2286
+ items: pageSizeOptions.map((option) => ({
2287
+ value: String(option),
2288
+ description: `${option} per page`
2289
+ })),
2290
+ onSelect: (value) => onPageSizeChange(Number(value)),
2291
+ selectedValue: String(pageSize),
2292
+ size: "sm",
2293
+ triggerLabel: "Results per page",
2294
+ triggerVariant: "tertiary"
2295
+ }
2296
+ ),
2297
+ /* @__PURE__ */ jsx30(
2298
+ "button",
2299
+ {
2300
+ className: "fractal-pagination__button",
2301
+ type: "button",
2302
+ "aria-label": "Previous page",
2303
+ title: "Previous page",
2304
+ disabled: currentPage === 1 || totalItems === 0,
2305
+ onClick: () => onPageChange(currentPage - 1),
2306
+ children: /* @__PURE__ */ jsx30(ArrowLeft, { "aria-hidden": "true", size: 16 })
2307
+ }
2308
+ ),
2309
+ /* @__PURE__ */ jsxs25("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
2310
+ currentPage,
2311
+ " of ",
2312
+ totalPages
2313
+ ] }),
2314
+ /* @__PURE__ */ jsx30(
2315
+ "button",
2316
+ {
2317
+ className: "fractal-pagination__button",
2318
+ type: "button",
2319
+ "aria-label": "Next page",
2320
+ title: "Next page",
2321
+ disabled: currentPage === totalPages || totalItems === 0,
2322
+ onClick: () => onPageChange(currentPage + 1),
2323
+ children: /* @__PURE__ */ jsx30(ArrowRight, { "aria-hidden": "true", size: 16 })
2324
+ }
2325
+ )
2326
+ ] })
2327
+ ] });
2328
+ }
1093
2329
  export {
1094
2330
  Alert,
1095
2331
  AlertDescription,
2332
+ AlertGhost,
1096
2333
  AlertTitle,
2334
+ Breadcrumbs,
1097
2335
  Button,
2336
+ ButtonGhost,
1098
2337
  Card,
1099
2338
  CardContent,
1100
2339
  CardDescription,
1101
2340
  CardFooter,
2341
+ CardGhost,
1102
2342
  CardHeader,
1103
2343
  CardTitle,
2344
+ Checkbox,
2345
+ CheckboxGhost,
1104
2346
  Chip,
2347
+ ChipGhost,
2348
+ CodeBlock,
2349
+ ContentSwitcher,
2350
+ ContentSwitcherGhost,
2351
+ ConversationGhost,
1105
2352
  DataTable,
2353
+ DataTableGhost,
1106
2354
  Disclosure,
2355
+ DisclosureGhost,
1107
2356
  Dropdown,
2357
+ DropdownGhost,
1108
2358
  FeatureCard,
2359
+ FileDropzone,
2360
+ FileDropzoneGhost,
1109
2361
  FolderCard,
2362
+ Ghost,
2363
+ GhostLine,
1110
2364
  Input,
2365
+ InputGhost,
1111
2366
  Label,
2367
+ LabelGhost,
2368
+ LatticeLoader,
1112
2369
  Link,
2370
+ LinkGhost,
1113
2371
  Modal,
1114
- PageHeader,
2372
+ ModalGhost,
2373
+ NumberInput,
2374
+ Pagination,
2375
+ PaginationGhost,
2376
+ PasswordInput,
1115
2377
  PromptCard,
2378
+ PromptCardGhost,
1116
2379
  PromptGrid,
1117
2380
  SidePanel,
2381
+ SidePanelGhost,
1118
2382
  Sidebar,
2383
+ SidebarAccountMenu,
1119
2384
  SidebarBrand,
1120
2385
  SidebarFooter,
2386
+ SidebarGhost,
1121
2387
  SidebarHeader,
1122
2388
  SidebarNav,
1123
2389
  SidebarNavItem,
1124
2390
  SidebarSection,
1125
2391
  SortableTable,
2392
+ SortableTableGhost,
2393
+ TableSearch,
1126
2394
  Tabs,
2395
+ TabsGhost,
2396
+ TextButton,
2397
+ TextButtonGhost,
1127
2398
  Textarea,
2399
+ TextareaGhost,
1128
2400
  Toggle,
2401
+ ToggleGhost,
2402
+ UILoader,
1129
2403
  cn,
1130
2404
  useEscapeDismiss
1131
2405
  };