@mirror-physics/fractal-ui 0.2.0-next.7 → 0.2.0-next.70

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
@@ -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,18 +919,19 @@ 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
  );
382
927
  }
383
928
 
384
929
  // src/components/Modal/Modal.tsx
930
+ import * as React10 from "react";
385
931
  import { createPortal } from "react-dom";
386
932
 
387
933
  // src/hooks/useEscapeDismiss.ts
388
- import { useEffect, useRef } from "react";
934
+ import { useEffect as useEffect2, useRef as useRef3 } from "react";
389
935
  var nextId = 0;
390
936
  var stack = [];
391
937
  function insertSorted(entry) {
@@ -418,9 +964,9 @@ function ensureGlobalListener() {
418
964
  );
419
965
  }
420
966
  function useEscapeDismiss(priority, handler, enabled = true) {
421
- const handlerRef = useRef(handler);
967
+ const handlerRef = useRef3(handler);
422
968
  handlerRef.current = handler;
423
- useEffect(() => {
969
+ useEffect2(() => {
424
970
  if (!enabled) return;
425
971
  ensureGlobalListener();
426
972
  const id = nextId++;
@@ -434,27 +980,99 @@ function useEscapeDismiss(priority, handler, enabled = true) {
434
980
  }, [priority, enabled]);
435
981
  }
436
982
 
983
+ // src/components/UILoader/UILoader.tsx
984
+ import { useId } from "react";
985
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
986
+ 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";
987
+ 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";
988
+ 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";
989
+ function UILoader({ size = 48, tone = "neutral", className, style, label = "Loading", ariaHidden = false }) {
990
+ const rawId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
991
+ const clipId = `fractal-ui-loader-clip-${rawId}`;
992
+ const gradientId = `fractal-ui-loader-gradient-${rawId}`;
993
+ const height = typeof size === "number" ? `${size}px` : size;
994
+ return /* @__PURE__ */ jsx12(
995
+ "span",
996
+ {
997
+ "aria-hidden": ariaHidden || void 0,
998
+ "aria-label": ariaHidden ? void 0 : label,
999
+ className: cn("fractal-ui-loader", `fractal-ui-loader--${tone}`, className),
1000
+ role: ariaHidden ? void 0 : "status",
1001
+ style,
1002
+ children: /* @__PURE__ */ jsxs11("svg", { "aria-hidden": "true", fill: "none", viewBox: "0 0 144 92", style: { height, width: "auto" }, children: [
1003
+ /* @__PURE__ */ jsxs11("defs", { children: [
1004
+ /* @__PURE__ */ jsxs11("linearGradient", { id: gradientId, x1: "59", x2: "94.3", y1: "0", y2: "89.1", gradientUnits: "userSpaceOnUse", children: [
1005
+ /* @__PURE__ */ jsx12("stop", { className: "fractal-ui-loader__stop-start", offset: "0" }),
1006
+ /* @__PURE__ */ jsx12("stop", { className: "fractal-ui-loader__stop-end", offset: "1" })
1007
+ ] }),
1008
+ /* @__PURE__ */ jsxs11("linearGradient", { id: `${gradientId}-shine`, x1: "0", x2: "1", y1: "0", y2: "0", children: [
1009
+ /* @__PURE__ */ jsx12("stop", { offset: "0.4", stopColor: "white", stopOpacity: "0" }),
1010
+ /* @__PURE__ */ jsx12("stop", { offset: "0.5", stopColor: "white", stopOpacity: "0.62" }),
1011
+ /* @__PURE__ */ jsx12("stop", { offset: "0.6", stopColor: "white", stopOpacity: "0" })
1012
+ ] }),
1013
+ /* @__PURE__ */ jsxs11("clipPath", { id: clipId, children: [
1014
+ /* @__PURE__ */ jsx12("path", { d: LEFT }),
1015
+ /* @__PURE__ */ jsx12("path", { d: CENTER }),
1016
+ /* @__PURE__ */ jsx12("path", { d: RIGHT })
1017
+ ] })
1018
+ ] }),
1019
+ /* @__PURE__ */ jsx12("path", { d: CENTER, fill: `url(#${gradientId})` }),
1020
+ /* @__PURE__ */ jsx12("path", { d: RIGHT, fill: `url(#${gradientId})` }),
1021
+ /* @__PURE__ */ jsx12("path", { d: LEFT, fill: `url(#${gradientId})` }),
1022
+ /* @__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)` }) }) })
1023
+ ] })
1024
+ }
1025
+ );
1026
+ }
1027
+
437
1028
  // 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);
1029
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1030
+ 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" }) });
1031
+ function Modal({ open, onClose, title, children, danger, warn, footer, noClose, closeDisabled = false, size = "sm", loading = false, loadingLabel = "Loading", scrollable = false, className }) {
1032
+ const [bodyScrolled, setBodyScrolled] = React10.useState(false);
1033
+ useEscapeDismiss(80, onClose, open && !noClose && !closeDisabled);
1034
+ React10.useEffect(() => {
1035
+ if (open) setBodyScrolled(false);
1036
+ }, [open]);
442
1037
  if (!open) return null;
443
1038
  const sizeClass = `fractal-modal-panel--${size}`;
444
1039
  const accentClass = danger ? "fractal-modal-panel--danger" : warn ? "fractal-modal-panel--warn" : "";
445
1040
  return createPortal(
446
- /* @__PURE__ */ jsx7("div", { className: "fractal-modal-overlay", onClick: noClose ? void 0 : onClose, children: /* @__PURE__ */ jsxs6(
1041
+ /* @__PURE__ */ jsx13("div", { className: "fractal-modal-overlay", onClick: noClose || closeDisabled ? void 0 : onClose, children: /* @__PURE__ */ jsxs12(
447
1042
  "div",
448
1043
  {
449
- className: cn("fractal-modal-panel", sizeClass, accentClass, className),
1044
+ className: cn("fractal-modal-panel", sizeClass, accentClass, scrollable && "fractal-modal-panel--scrollable", className),
450
1045
  onClick: (e) => e.stopPropagation(),
451
1046
  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, {}) })
455
- ] }),
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 })
1047
+ /* @__PURE__ */ jsxs12(
1048
+ "div",
1049
+ {
1050
+ className: cn(
1051
+ "fractal-modal-title-row",
1052
+ scrollable && "fractal-modal-title-row--scrollable",
1053
+ scrollable && bodyScrolled && "fractal-modal-title-row--scrolled"
1054
+ ),
1055
+ children: [
1056
+ /* @__PURE__ */ jsx13("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
1057
+ !noClose && /* @__PURE__ */ jsx13("button", { type: "button", onClick: onClose, title: "Close", disabled: closeDisabled, className: "fractal-modal-close", children: /* @__PURE__ */ jsx13(CloseIcon, {}) })
1058
+ ]
1059
+ }
1060
+ ),
1061
+ /* @__PURE__ */ jsx13(
1062
+ "div",
1063
+ {
1064
+ "aria-busy": loading || void 0,
1065
+ className: cn(
1066
+ "fractal-modal-body",
1067
+ size === "lg" && "fractal-modal-body--lg",
1068
+ loading && "fractal-modal-body--loading",
1069
+ scrollable && "fractal-modal-body--scrollable"
1070
+ ),
1071
+ onScroll: scrollable ? (event) => setBodyScrolled(event.currentTarget.scrollTop > 0) : void 0,
1072
+ children: loading ? /* @__PURE__ */ jsx13(UILoader, { label: loadingLabel }) : children
1073
+ }
1074
+ ),
1075
+ footer && /* @__PURE__ */ jsx13("div", { className: cn("fractal-modal-footer", scrollable && "fractal-modal-footer--divided"), children: footer })
458
1076
  ]
459
1077
  }
460
1078
  ) }),
@@ -463,17 +1081,17 @@ function Modal({ open, onClose, title, children, danger, warn, footer, noClose,
463
1081
  }
464
1082
 
465
1083
  // src/components/DataTable/DataTable.tsx
466
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1084
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
467
1085
  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(
1086
+ return /* @__PURE__ */ jsx14("div", { className: cn("fractal-datatable", `fractal-datatable--${size}`, className), children: /* @__PURE__ */ jsxs13("div", { className: "fractal-datatable-surface", children: [
1087
+ header && /* @__PURE__ */ jsx14("div", { className: "fractal-datatable-header", children: header }),
1088
+ data.length === 0 ? /* @__PURE__ */ jsx14("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs13("table", { className: "fractal-datatable-table", children: [
1089
+ /* @__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)) }) }),
1090
+ /* @__PURE__ */ jsx14("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx14(
473
1091
  "tr",
474
1092
  {
475
1093
  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))
1094
+ children: columns.map((col) => /* @__PURE__ */ jsx14("td", { className: "fractal-datatable-td", style: { width: col.width }, children: col.render(row, rowIndex) }, col.key))
477
1095
  },
478
1096
  rowIndex
479
1097
  )) })
@@ -482,9 +1100,9 @@ function DataTable({ columns, data, emptyMessage, header, size = "md", className
482
1100
  }
483
1101
 
484
1102
  // 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";
1103
+ import * as React11 from "react";
1104
+ import { ChevronDown, ChevronLeft, ChevronRight as ChevronRight2, ChevronUp } from "@mirror-physics/fractal-icons";
1105
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
488
1106
  var MARGIN = 4;
489
1107
  function Dropdown({
490
1108
  items,
@@ -493,20 +1111,23 @@ function Dropdown({
493
1111
  triggerLabel = "Options",
494
1112
  direction = "down",
495
1113
  align = "left",
1114
+ triggerOrientation = "horizontal",
496
1115
  triggerContent,
497
1116
  label,
1117
+ iconOnly = false,
498
1118
  noChevron = false,
499
1119
  size = "md",
500
1120
  triggerVariant = "outline",
501
1121
  className,
502
1122
  triggerClassName,
503
1123
  panelClassName,
504
- closeOnSelect = true
1124
+ closeOnSelect = true,
1125
+ disabled = false
505
1126
  }) {
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({});
1127
+ const [open, setOpen] = React11.useState(false);
1128
+ const triggerRef = React11.useRef(null);
1129
+ const panelRef = React11.useRef(null);
1130
+ const [panelStyle, setPanelStyle] = React11.useState({});
510
1131
  const visibleItems = items.filter(
511
1132
  (item) => item.icon != null || item.description != null && (typeof item.description === "string" ? item.description !== "" : true)
512
1133
  );
@@ -514,42 +1135,60 @@ function Dropdown({
514
1135
  const selectableItems = visibleItems.filter((i) => i.kind !== "header");
515
1136
  const selected = items.find((i) => i.value === selectedValue && i.kind !== "header") ?? selectableItems[0] ?? visibleItems[0];
516
1137
  const isUp = direction === "up";
517
- const ChevronIcon = isUp ? ChevronUp : ChevronDown;
518
- React7.useLayoutEffect(() => {
1138
+ const isHorizontalDirection = direction === "left" || direction === "right";
1139
+ const ChevronIcon = direction === "up" ? ChevronUp : direction === "left" ? ChevronLeft : direction === "right" ? ChevronRight2 : ChevronDown;
1140
+ React11.useLayoutEffect(() => {
519
1141
  if (!open || !triggerRef.current || !panelRef.current) return;
520
1142
  const triggerRect = triggerRef.current.getBoundingClientRect();
521
1143
  const panelRect = panelRef.current.getBoundingClientRect();
522
1144
  const vw = window.innerWidth;
523
1145
  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;
1146
+ let top;
533
1147
  let left;
534
1148
  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;
1149
+ if (isHorizontalDirection) {
1150
+ const spaceRight = vw - triggerRect.right - MARGIN;
1151
+ const spaceLeft = triggerRect.left - MARGIN;
1152
+ let placeRight = direction === "right";
1153
+ if (placeRight && panelRect.width > spaceRight && spaceLeft > spaceRight) {
1154
+ placeRight = false;
1155
+ } else if (!placeRight && panelRect.width > spaceLeft && spaceRight > spaceLeft) {
1156
+ placeRight = true;
541
1157
  }
1158
+ left = placeRight ? triggerRect.width + MARGIN : -(panelRect.width + MARGIN);
1159
+ top = 0;
1160
+ const panelBottom = triggerRect.top + panelRect.height;
1161
+ if (panelBottom > vh - MARGIN) top -= panelBottom - (vh - MARGIN);
1162
+ if (triggerRect.top + top < MARGIN) top += MARGIN - (triggerRect.top + top);
542
1163
  } else {
543
- left = 0;
544
- const panelRight = triggerRect.left + panelRect.width;
545
- if (panelRight > vw) {
546
- left = void 0;
1164
+ const spaceBelow = vh - triggerRect.bottom - MARGIN;
1165
+ const spaceAbove = triggerRect.top - MARGIN;
1166
+ let placeAbove = isUp;
1167
+ if (placeAbove && panelRect.height > spaceAbove && spaceBelow > spaceAbove) {
1168
+ placeAbove = false;
1169
+ } else if (!placeAbove && panelRect.height > spaceBelow && spaceAbove > spaceBelow) {
1170
+ placeAbove = true;
1171
+ }
1172
+ top = placeAbove ? -(panelRect.height + MARGIN) : triggerRect.height + MARGIN;
1173
+ if (align === "right") {
547
1174
  right = 0;
1175
+ const panelLeft = triggerRect.right - panelRect.width;
1176
+ if (panelLeft < 0) {
1177
+ right = void 0;
1178
+ left = 0;
1179
+ }
1180
+ } else {
1181
+ left = 0;
1182
+ const panelRight = triggerRect.left + panelRect.width;
1183
+ if (panelRight > vw) {
1184
+ left = void 0;
1185
+ right = 0;
1186
+ }
548
1187
  }
549
1188
  }
550
1189
  setPanelStyle({ top, left, right });
551
- }, [open, isUp, align]);
552
- React7.useEffect(() => {
1190
+ }, [open, direction, isHorizontalDirection, isUp, align]);
1191
+ React11.useEffect(() => {
553
1192
  if (!open) return;
554
1193
  const handleClickOutside = (e) => {
555
1194
  const target = e.target;
@@ -559,12 +1198,13 @@ function Dropdown({
559
1198
  document.addEventListener("mousedown", handleClickOutside);
560
1199
  return () => document.removeEventListener("mousedown", handleClickOutside);
561
1200
  }, [open]);
562
- return /* @__PURE__ */ jsxs8("div", { className: cn("fractal-dropdown", className), children: [
563
- /* @__PURE__ */ jsxs8(
1201
+ return /* @__PURE__ */ jsxs14("div", { className: cn("fractal-dropdown", className), children: [
1202
+ /* @__PURE__ */ jsxs14(
564
1203
  "button",
565
1204
  {
566
1205
  ref: triggerRef,
567
1206
  type: "button",
1207
+ disabled,
568
1208
  "aria-haspopup": "listbox",
569
1209
  "aria-expanded": open,
570
1210
  "aria-label": triggerLabel,
@@ -573,18 +1213,20 @@ function Dropdown({
573
1213
  "fractal-dropdown-trigger",
574
1214
  `fractal-dropdown-trigger--${size}`,
575
1215
  `fractal-dropdown-trigger--${triggerVariant}`,
1216
+ `fractal-dropdown-trigger--${triggerOrientation}`,
1217
+ iconOnly && "fractal-dropdown-trigger--icon-only",
576
1218
  triggerClassName
577
1219
  ),
578
1220
  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 })
1221
+ triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs14(Fragment2, { children: [
1222
+ selected.icon != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected.icon }),
1223
+ selected.description != null && (typeof selected.description !== "string" || selected.description !== "") && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-selected-text", children: selected.description })
582
1224
  ] }),
583
- !noChevron && /* @__PURE__ */ jsx9("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ jsx9(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
1225
+ !noChevron && !iconOnly && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ jsx15(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
584
1226
  ]
585
1227
  }
586
1228
  ),
587
- open && /* @__PURE__ */ jsxs8(
1229
+ open && /* @__PURE__ */ jsxs14(
588
1230
  "div",
589
1231
  {
590
1232
  ref: panelRef,
@@ -593,21 +1235,21 @@ function Dropdown({
593
1235
  className: cn("fractal-dropdown-panel", `fractal-dropdown-panel--${size}`, panelClassName),
594
1236
  style: panelStyle,
595
1237
  children: [
596
- label != null && /* @__PURE__ */ jsx9("div", { className: "fractal-dropdown-label", children: label }),
1238
+ label != null && /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-label", children: label }),
597
1239
  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;
1240
+ const topDivider = item.border === "top" ? /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-separator" }) : null;
1241
+ const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ jsx15("div", { className: "fractal-dropdown-separator" }) : null;
600
1242
  if (item.kind === "header") {
601
- return /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
1243
+ return /* @__PURE__ */ jsxs14(React11.Fragment, { children: [
602
1244
  topDivider,
603
- /* @__PURE__ */ jsx9("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
1245
+ /* @__PURE__ */ jsx15("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
604
1246
  bottomDivider
605
1247
  ] }, `header-${index}`);
606
1248
  }
607
1249
  const isSelected = item.value === selectedValue;
608
- return /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
1250
+ return /* @__PURE__ */ jsxs14(React11.Fragment, { children: [
609
1251
  topDivider,
610
- /* @__PURE__ */ jsxs8(
1252
+ /* @__PURE__ */ jsxs14(
611
1253
  "button",
612
1254
  {
613
1255
  type: "button",
@@ -619,9 +1261,9 @@ function Dropdown({
619
1261
  },
620
1262
  className: "fractal-dropdown-option",
621
1263
  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 })
1264
+ item.icon != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-icon", children: item.icon }),
1265
+ item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-option-text", children: item.description }),
1266
+ item.trailing != null && /* @__PURE__ */ jsx15("span", { className: "fractal-dropdown-trailing", children: item.trailing })
625
1267
  ]
626
1268
  }
627
1269
  ),
@@ -635,10 +1277,10 @@ function Dropdown({
635
1277
  }
636
1278
 
637
1279
  // 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(
1280
+ import * as React12 from "react";
1281
+ import { jsx as jsx16 } from "react/jsx-runtime";
1282
+ var Link = React12.forwardRef(
1283
+ ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ jsx16(
642
1284
  "a",
643
1285
  {
644
1286
  ref,
@@ -650,10 +1292,10 @@ var Link = React8.forwardRef(
650
1292
  Link.displayName = "Link";
651
1293
 
652
1294
  // 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(
1295
+ import * as React13 from "react";
1296
+ import { jsx as jsx17 } from "react/jsx-runtime";
1297
+ var Chip = React13.forwardRef(
1298
+ ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ jsx17(
657
1299
  "span",
658
1300
  {
659
1301
  ref,
@@ -664,36 +1306,92 @@ var Chip = React9.forwardRef(
664
1306
  );
665
1307
  Chip.displayName = "Chip";
666
1308
 
1309
+ // src/components/Checkbox/Checkbox.tsx
1310
+ import { Checkbox as CheckboxIcon, CheckboxChecked, Minus as Minus2 } from "@mirror-physics/fractal-icons";
1311
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1312
+ function Checkbox({
1313
+ checked,
1314
+ indeterminate = false,
1315
+ onCheckedChange,
1316
+ className,
1317
+ disabled,
1318
+ onClick,
1319
+ onKeyDown,
1320
+ ...props
1321
+ }) {
1322
+ return /* @__PURE__ */ jsx18(
1323
+ "button",
1324
+ {
1325
+ ...props,
1326
+ className: cn(
1327
+ "fractal-checkbox",
1328
+ (checked || indeterminate) && "fractal-checkbox--checked",
1329
+ indeterminate && "fractal-checkbox--indeterminate",
1330
+ className
1331
+ ),
1332
+ type: "button",
1333
+ role: "checkbox",
1334
+ "aria-checked": indeterminate ? "mixed" : checked,
1335
+ disabled,
1336
+ onClick: (event) => {
1337
+ onClick?.(event);
1338
+ if (!event.defaultPrevented) onCheckedChange(indeterminate || !checked);
1339
+ },
1340
+ onKeyDown: (event) => {
1341
+ onKeyDown?.(event);
1342
+ if (event.key === " " || event.key === "Enter") event.stopPropagation();
1343
+ },
1344
+ children: indeterminate ? /* @__PURE__ */ jsxs15("span", { className: "fractal-checkbox__indeterminate-icon", "aria-hidden": "true", children: [
1345
+ /* @__PURE__ */ jsx18(CheckboxIcon, { size: 18 }),
1346
+ /* @__PURE__ */ jsx18(Minus2, { className: "fractal-checkbox__indeterminate-mark", size: 12 })
1347
+ ] }) : checked ? /* @__PURE__ */ jsx18(CheckboxChecked, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ jsx18(CheckboxIcon, { "aria-hidden": "true", size: 18 })
1348
+ }
1349
+ );
1350
+ }
1351
+
667
1352
  // src/components/SortableTable/SortableTable.tsx
668
- import { useMemo, useState as useState3 } from "react";
1353
+ import { useMemo as useMemo2, useState as useState6 } from "react";
669
1354
  import { ArrowUp, ArrowDown, ArrowsUpDown } from "@mirror-physics/fractal-icons";
670
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1355
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
671
1356
  function SortableTable({
672
1357
  columns,
673
1358
  data,
1359
+ "aria-label": ariaLabel,
674
1360
  emptyMessage,
675
1361
  header,
676
1362
  size = "md",
1363
+ embedded = false,
677
1364
  className,
1365
+ defaultSort,
678
1366
  defaultSortKey = void 0,
679
1367
  defaultSortDirection = null,
680
1368
  sortKey: controlledKey,
681
1369
  sortDirection: controlledDirection,
682
1370
  onSortChange,
1371
+ pagination,
683
1372
  highlightRow,
684
- onRowClick
1373
+ selection,
1374
+ isRowDisabled,
1375
+ isRowSelectionDisabled,
1376
+ onRowClick,
1377
+ onRowDoubleClick,
1378
+ rowAction
685
1379
  }) {
686
- const [internalKey, setInternalKey] = useState3(defaultSortKey ?? null);
687
- const [internalDirection, setInternalDirection] = useState3(defaultSortDirection);
1380
+ const [internalKey, setInternalKey] = useState6(defaultSortKey ?? null);
1381
+ const [internalDirection, setInternalDirection] = useState6(defaultSortDirection);
688
1382
  const isControlled = controlledKey !== void 0 && controlledDirection !== void 0;
689
1383
  const activeKey = isControlled ? controlledKey : internalKey;
690
1384
  const activeDirection = isControlled ? controlledDirection : internalDirection;
691
- const sorted = useMemo(() => {
692
- if (!activeKey || !activeDirection) return data;
693
- const col = columns.find((c) => c.key === activeKey);
1385
+ const baselineKey = defaultSort?.key ?? null;
1386
+ const baselineDirection = defaultSort?.direction ?? null;
1387
+ const effectiveKey = activeKey && activeDirection ? activeKey : baselineKey;
1388
+ const effectiveDirection = activeKey && activeDirection ? activeDirection : baselineDirection;
1389
+ const sorted = useMemo2(() => {
1390
+ if (!effectiveKey || !effectiveDirection) return data;
1391
+ const col = columns.find((c) => c.key === effectiveKey);
694
1392
  if (!col) return data;
695
1393
  const accessor = col.sortAccessor ?? ((row) => {
696
- const value = row[activeKey];
1394
+ const value = row[effectiveKey];
697
1395
  return typeof value === "string" || typeof value === "number" ? value : null;
698
1396
  });
699
1397
  const copy = [...data];
@@ -704,26 +1402,41 @@ function SortableTable({
704
1402
  if (av == null) return 1;
705
1403
  if (bv == null) return -1;
706
1404
  if (typeof av === "number" && typeof bv === "number") {
707
- return activeDirection === "asc" ? av - bv : bv - av;
1405
+ return effectiveDirection === "asc" ? av - bv : bv - av;
708
1406
  }
709
1407
  const as = String(av).toLowerCase();
710
1408
  const bs = String(bv).toLowerCase();
711
- if (as < bs) return activeDirection === "asc" ? -1 : 1;
712
- if (as > bs) return activeDirection === "asc" ? 1 : -1;
1409
+ if (as < bs) return effectiveDirection === "asc" ? -1 : 1;
1410
+ if (as > bs) return effectiveDirection === "asc" ? 1 : -1;
713
1411
  return 0;
714
1412
  });
715
1413
  return copy;
716
- }, [data, columns, activeKey, activeDirection]);
1414
+ }, [data, columns, effectiveKey, effectiveDirection]);
1415
+ const visibleRows = useMemo2(() => {
1416
+ if (!pagination) return sorted;
1417
+ const pageSize = Math.max(1, pagination.pageSize);
1418
+ const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
1419
+ const page = Math.min(Math.max(1, pagination.page), totalPages);
1420
+ const startIndex = (page - 1) * pageSize;
1421
+ return sorted.slice(startIndex, startIndex + pageSize);
1422
+ }, [pagination, sorted]);
1423
+ const selectedRows = selection ? sorted.filter(selection.isSelected) : [];
1424
+ const selectableRows = selection ? sorted.filter((row) => !isRowDisabled?.(row) && !isRowSelectionDisabled?.(row)) : [];
1425
+ const selectedRowCount = selection ? selectableRows.filter(selection.isSelected).length : 0;
1426
+ const allRowsSelected = selectableRows.length > 0 && selectedRowCount === selectableRows.length;
1427
+ const someRowsSelected = selectedRows.length > 0 && !allRowsSelected;
717
1428
  const handleSortClick = (key) => {
718
1429
  let nextDirection;
719
- if (activeKey !== key) {
1430
+ if (!activeKey || !activeDirection) {
1431
+ nextDirection = baselineKey === key && baselineDirection ? baselineDirection : "asc";
1432
+ } else if (activeKey !== key) {
720
1433
  nextDirection = "asc";
1434
+ } else if (baselineKey === key && baselineDirection) {
1435
+ nextDirection = activeDirection === baselineDirection ? baselineDirection === "asc" ? "desc" : "asc" : null;
721
1436
  } else if (activeDirection === "asc") {
722
1437
  nextDirection = "desc";
723
- } else if (activeDirection === "desc") {
724
- nextDirection = null;
725
1438
  } else {
726
- nextDirection = "asc";
1439
+ nextDirection = null;
727
1440
  }
728
1441
  const nextKey = nextDirection === null ? null : key;
729
1442
  if (isControlled) {
@@ -734,75 +1447,140 @@ function SortableTable({
734
1447
  onSortChange?.(nextKey, nextDirection);
735
1448
  }
736
1449
  };
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) => {
741
- const isActive = activeKey === col.key && activeDirection !== null;
742
- if (!col.sortable) {
743
- return /* @__PURE__ */ jsx12(
1450
+ 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: [
1451
+ header && /* @__PURE__ */ jsx19("div", { className: "fractal-sortable-table-header", children: header }),
1452
+ visibleRows.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs16("table", { "aria-label": ariaLabel, className: "fractal-sortable-table-table", children: [
1453
+ /* @__PURE__ */ jsx19("thead", { children: /* @__PURE__ */ jsxs16("tr", { className: "fractal-sortable-table-thead-row", children: [
1454
+ selection && /* @__PURE__ */ jsx19(
1455
+ "th",
1456
+ {
1457
+ className: "fractal-sortable-table-th fractal-sortable-table-selection-cell",
1458
+ scope: "col",
1459
+ children: /* @__PURE__ */ jsx19(
1460
+ Checkbox,
1461
+ {
1462
+ "aria-label": selection.selectAllLabel ?? "Select all rows",
1463
+ checked: allRowsSelected,
1464
+ disabled: selectableRows.length === 0 && selectedRows.length === 0,
1465
+ indeterminate: someRowsSelected,
1466
+ onCheckedChange: (checked) => {
1467
+ const rows = checked ? selectableRows : selectedRows;
1468
+ if (selection.onSelectAllChange) {
1469
+ selection.onSelectAllChange(rows, checked);
1470
+ return;
1471
+ }
1472
+ rows.forEach((row) => {
1473
+ if (selection.isSelected(row) !== checked) {
1474
+ selection.onSelectedChange(row, checked);
1475
+ }
1476
+ });
1477
+ }
1478
+ }
1479
+ )
1480
+ }
1481
+ ),
1482
+ columns.map((col) => {
1483
+ const isActive = activeKey === col.key && activeDirection !== null;
1484
+ if (!col.sortable) {
1485
+ return /* @__PURE__ */ jsx19(
1486
+ "th",
1487
+ {
1488
+ className: "fractal-sortable-table-th",
1489
+ scope: "col",
1490
+ style: { width: col.width },
1491
+ children: col.header
1492
+ },
1493
+ col.key
1494
+ );
1495
+ }
1496
+ return /* @__PURE__ */ jsx19(
744
1497
  "th",
745
1498
  {
746
- className: "fractal-sortable-table-th",
1499
+ className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
1500
+ scope: "col",
747
1501
  style: { width: col.width },
748
- children: col.header
1502
+ children: /* @__PURE__ */ jsxs16(
1503
+ "button",
1504
+ {
1505
+ type: "button",
1506
+ className: cn(
1507
+ "fractal-sortable-table-sort-button",
1508
+ isActive && "fractal-sortable-table-sort-button--active"
1509
+ ),
1510
+ onClick: () => handleSortClick(col.key),
1511
+ "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
1512
+ children: [
1513
+ /* @__PURE__ */ jsx19("span", { children: col.header }),
1514
+ /* @__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 }) })
1515
+ ]
1516
+ }
1517
+ )
749
1518
  },
750
1519
  col.key
751
1520
  );
752
- }
753
- return /* @__PURE__ */ jsx12(
754
- "th",
755
- {
756
- className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
757
- style: { width: col.width },
758
- children: /* @__PURE__ */ jsxs9(
759
- "button",
760
- {
761
- type: "button",
762
- className: cn(
763
- "fractal-sortable-table-sort-button",
764
- isActive && "fractal-sortable-table-sort-button--active"
765
- ),
766
- onClick: () => handleSortClick(col.key),
767
- "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
768
- 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 }) })
771
- ]
772
- }
773
- )
774
- },
775
- col.key
776
- );
777
- }) }) }),
778
- /* @__PURE__ */ jsx12("tbody", { children: sorted.map((row, rowIndex) => {
1521
+ })
1522
+ ] }) }),
1523
+ /* @__PURE__ */ jsx19("tbody", { children: visibleRows.map((row, rowIndex) => {
779
1524
  const highlighted = highlightRow?.(row, rowIndex) ?? false;
780
- return /* @__PURE__ */ jsx12(
1525
+ const disabled = isRowDisabled?.(row) ?? false;
1526
+ const selectionDisabled = disabled || (isRowSelectionDisabled?.(row) ?? false);
1527
+ const interactive = !disabled && Boolean(onRowClick || onRowDoubleClick);
1528
+ const action = !disabled && rowAction ? {
1529
+ label: rowAction.label(row, rowIndex),
1530
+ icon: rowAction.icon(row, rowIndex)
1531
+ } : null;
1532
+ return /* @__PURE__ */ jsxs16(
781
1533
  "tr",
782
1534
  {
783
1535
  className: cn(
784
1536
  "fractal-sortable-table-row",
785
1537
  highlighted && "fractal-sortable-table-row--highlighted",
786
- onRowClick && "fractal-sortable-table-row--interactive"
1538
+ interactive && "fractal-sortable-table-row--interactive",
1539
+ disabled && "fractal-sortable-table-row--disabled"
787
1540
  ),
788
- onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
789
- onKeyDown: onRowClick ? (event) => {
1541
+ "data-disabled": disabled || void 0,
1542
+ onClick: !disabled && onRowClick ? () => onRowClick(row, rowIndex) : void 0,
1543
+ onDoubleClick: !disabled && onRowDoubleClick ? () => onRowDoubleClick(row, rowIndex) : void 0,
1544
+ onKeyDown: interactive ? (event) => {
790
1545
  if (event.key === "Enter" || event.key === " ") {
791
1546
  event.preventDefault();
792
- onRowClick(row, rowIndex);
1547
+ const activateRow = onRowClick ?? onRowDoubleClick;
1548
+ activateRow?.(row, rowIndex);
793
1549
  }
794
1550
  } : void 0,
795
- role: onRowClick ? "button" : void 0,
796
- tabIndex: onRowClick ? 0 : void 0,
797
- children: columns.map((col) => /* @__PURE__ */ jsx12(
798
- "td",
799
- {
800
- className: "fractal-sortable-table-td",
801
- style: { width: col.width, textAlign: col.align ?? "left" },
802
- children: col.render(row, rowIndex)
803
- },
804
- col.key
805
- ))
1551
+ "aria-label": action?.label,
1552
+ "aria-disabled": disabled || void 0,
1553
+ role: interactive ? "button" : void 0,
1554
+ tabIndex: interactive ? 0 : void 0,
1555
+ children: [
1556
+ selection && /* @__PURE__ */ jsx19("td", { className: "fractal-sortable-table-td fractal-sortable-table-selection-cell", children: /* @__PURE__ */ jsx19(
1557
+ Checkbox,
1558
+ {
1559
+ "aria-label": selection.getRowLabel(row),
1560
+ checked: !selectionDisabled && selection.isSelected(row),
1561
+ disabled: selectionDisabled,
1562
+ onClick: (event) => event.stopPropagation(),
1563
+ onCheckedChange: (checked) => {
1564
+ if (!selectionDisabled) selection.onSelectedChange(row, checked);
1565
+ }
1566
+ }
1567
+ ) }),
1568
+ columns.map((col, columnIndex) => /* @__PURE__ */ jsxs16(
1569
+ "td",
1570
+ {
1571
+ className: cn(
1572
+ "fractal-sortable-table-td",
1573
+ action && columnIndex === columns.length - 1 && "fractal-sortable-table-td--row-action"
1574
+ ),
1575
+ style: { width: col.width, textAlign: col.align ?? "left" },
1576
+ children: [
1577
+ col.render(row, rowIndex),
1578
+ 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 }) })
1579
+ ]
1580
+ },
1581
+ col.key
1582
+ ))
1583
+ ]
806
1584
  },
807
1585
  rowIndex
808
1586
  );
@@ -812,23 +1590,40 @@ function SortableTable({
812
1590
  }
813
1591
 
814
1592
  // 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";
1593
+ import { useId as useId2, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState7 } from "react";
1594
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
817
1595
  function Tabs({
818
1596
  items,
819
1597
  defaultValue,
820
1598
  value: controlledValue,
821
1599
  onValueChange,
822
1600
  className,
823
- ariaLabel
1601
+ ariaLabel,
1602
+ divider = false,
1603
+ variant = "line"
824
1604
  }) {
825
- const reactId = useId();
826
- const [internalValue, setInternalValue] = useState4(
1605
+ const reactId = useId2();
1606
+ const [internalValue, setInternalValue] = useState7(
827
1607
  defaultValue ?? items.find((i) => !i.disabled)?.id ?? items[0]?.id ?? ""
828
1608
  );
829
1609
  const isControlled = controlledValue !== void 0;
830
1610
  const activeId = isControlled ? controlledValue : internalValue;
831
- const tabRefs = useRef3({});
1611
+ const tabListRef = useRef5(null);
1612
+ const tabRefs = useRef5({});
1613
+ const [indicator, setIndicator] = useState7(null);
1614
+ useLayoutEffect2(() => {
1615
+ const updateIndicator = () => {
1616
+ const activeTab = tabRefs.current[activeId];
1617
+ if (!activeTab) return;
1618
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
1619
+ };
1620
+ updateIndicator();
1621
+ const list = tabListRef.current;
1622
+ if (!list || typeof ResizeObserver === "undefined") return;
1623
+ const observer = new ResizeObserver(updateIndicator);
1624
+ observer.observe(list);
1625
+ return () => observer.disconnect();
1626
+ }, [activeId, items.length]);
832
1627
  const selectTab = (id) => {
833
1628
  if (isControlled) {
834
1629
  onValueChange?.(id);
@@ -838,6 +1633,10 @@ function Tabs({
838
1633
  }
839
1634
  };
840
1635
  const focusable = items.filter((i) => !i.disabled);
1636
+ const indicatorStyle = indicator ? {
1637
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
1638
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
1639
+ } : void 0;
841
1640
  const onKeyDown = (event) => {
842
1641
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
843
1642
  if (currentIndex === -1) return;
@@ -863,41 +1662,52 @@ function Tabs({
863
1662
  selectTab(nextId2);
864
1663
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
865
1664
  };
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
- }) }),
1665
+ return /* @__PURE__ */ jsxs17("div", { className: cn("fractal-tabs", `fractal-tabs--${variant}`, divider && "fractal-tabs--divider", className), children: [
1666
+ /* @__PURE__ */ jsx20(
1667
+ "div",
1668
+ {
1669
+ ref: tabListRef,
1670
+ role: "tablist",
1671
+ "aria-label": ariaLabel,
1672
+ className: "fractal-tabs-list",
1673
+ "data-indicator-ready": indicator ? "true" : void 0,
1674
+ style: indicatorStyle,
1675
+ children: items.map((item) => {
1676
+ const isActive = item.id === activeId;
1677
+ const tabId = `${reactId}-tab-${item.id}`;
1678
+ const panelId = `${reactId}-panel-${item.id}`;
1679
+ return /* @__PURE__ */ jsx20(
1680
+ "button",
1681
+ {
1682
+ ref: (el) => {
1683
+ tabRefs.current[item.id] = el;
1684
+ },
1685
+ id: tabId,
1686
+ role: "tab",
1687
+ type: "button",
1688
+ "aria-selected": isActive,
1689
+ "aria-controls": panelId,
1690
+ tabIndex: isActive ? 0 : -1,
1691
+ disabled: item.disabled,
1692
+ className: cn(
1693
+ "fractal-tabs-trigger",
1694
+ isActive && "fractal-tabs-trigger--active",
1695
+ item.disabled && "fractal-tabs-trigger--disabled"
1696
+ ),
1697
+ onClick: () => !item.disabled && selectTab(item.id),
1698
+ onKeyDown,
1699
+ children: item.label
1700
+ },
1701
+ item.id
1702
+ );
1703
+ })
1704
+ }
1705
+ ),
896
1706
  items.map((item) => {
897
1707
  const tabId = `${reactId}-tab-${item.id}`;
898
1708
  const panelId = `${reactId}-panel-${item.id}`;
899
1709
  const isActive = item.id === activeId;
900
- return /* @__PURE__ */ jsx13(
1710
+ return /* @__PURE__ */ jsx20(
901
1711
  "div",
902
1712
  {
903
1713
  id: panelId,
@@ -913,10 +1723,112 @@ function Tabs({
913
1723
  ] });
914
1724
  }
915
1725
 
1726
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
1727
+ import * as React14 from "react";
1728
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1729
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
1730
+ const switcherRef = React14.useRef(null);
1731
+ const buttonRefs = React14.useRef([]);
1732
+ const indicatorMotionReadyRef = React14.useRef(false);
1733
+ const activeIndex = items.findIndex((item) => item.value === value);
1734
+ React14.useLayoutEffect(() => {
1735
+ const switcher = switcherRef.current;
1736
+ const activeButton = buttonRefs.current[activeIndex];
1737
+ let motionFrame = 0;
1738
+ if (!switcher || !activeButton) {
1739
+ switcher?.removeAttribute("data-indicator-ready");
1740
+ switcher?.removeAttribute("data-indicator-animate");
1741
+ indicatorMotionReadyRef.current = false;
1742
+ return;
1743
+ }
1744
+ const updateIndicator = () => {
1745
+ const switcherRect = switcher.getBoundingClientRect();
1746
+ const activeButtonRect = activeButton.getBoundingClientRect();
1747
+ switcher.style.setProperty("--fractal-content-switcher-indicator-x", `${activeButtonRect.left - switcherRect.left - switcher.clientLeft}px`);
1748
+ switcher.style.setProperty("--fractal-content-switcher-indicator-y", `${activeButtonRect.top - switcherRect.top - switcher.clientTop}px`);
1749
+ switcher.style.setProperty("--fractal-content-switcher-indicator-width", `${activeButtonRect.width}px`);
1750
+ switcher.style.setProperty("--fractal-content-switcher-indicator-height", `${activeButtonRect.height}px`);
1751
+ switcher.setAttribute("data-indicator-ready", "true");
1752
+ if (!indicatorMotionReadyRef.current && motionFrame === 0) {
1753
+ motionFrame = window.requestAnimationFrame(() => {
1754
+ indicatorMotionReadyRef.current = true;
1755
+ switcher.setAttribute("data-indicator-animate", "true");
1756
+ motionFrame = 0;
1757
+ });
1758
+ }
1759
+ };
1760
+ updateIndicator();
1761
+ if (typeof ResizeObserver === "undefined") {
1762
+ return () => {
1763
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
1764
+ };
1765
+ }
1766
+ const resizeObserver = new ResizeObserver(updateIndicator);
1767
+ resizeObserver.observe(switcher);
1768
+ buttonRefs.current.forEach((button) => {
1769
+ if (button) resizeObserver.observe(button);
1770
+ });
1771
+ return () => {
1772
+ resizeObserver.disconnect();
1773
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
1774
+ };
1775
+ }, [activeIndex, items.length]);
1776
+ const moveFocus = (index, direction) => {
1777
+ let nextIndex = index;
1778
+ do {
1779
+ nextIndex = (nextIndex + direction + items.length) % items.length;
1780
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
1781
+ if (!items[nextIndex]?.disabled) {
1782
+ buttonRefs.current[nextIndex]?.focus();
1783
+ onValueChange(items[nextIndex].value);
1784
+ }
1785
+ };
1786
+ return /* @__PURE__ */ jsxs18(
1787
+ "div",
1788
+ {
1789
+ className: cn("fractal-content-switcher", fullWidth && "fractal-content-switcher--full-width", className),
1790
+ role: "group",
1791
+ "aria-label": ariaLabel,
1792
+ ref: switcherRef,
1793
+ children: [
1794
+ /* @__PURE__ */ jsx21("span", { className: "fractal-content-switcher__indicator", "aria-hidden": true }),
1795
+ items.map((item, index) => {
1796
+ const active = item.value === value;
1797
+ return /* @__PURE__ */ jsx21(
1798
+ "button",
1799
+ {
1800
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
1801
+ type: "button",
1802
+ "aria-pressed": active,
1803
+ disabled: item.disabled,
1804
+ onClick: () => onValueChange(item.value),
1805
+ onKeyDown: (event) => {
1806
+ if (event.key === "ArrowRight") {
1807
+ event.preventDefault();
1808
+ moveFocus(index, 1);
1809
+ }
1810
+ if (event.key === "ArrowLeft") {
1811
+ event.preventDefault();
1812
+ moveFocus(index, -1);
1813
+ }
1814
+ },
1815
+ ref: (element) => {
1816
+ buttonRefs.current[index] = element;
1817
+ },
1818
+ children: item.label
1819
+ },
1820
+ item.value
1821
+ );
1822
+ })
1823
+ ]
1824
+ }
1825
+ );
1826
+ }
1827
+
916
1828
  // 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";
1829
+ import { useId as useId3, useState as useState8 } from "react";
1830
+ import { ChevronRight as ChevronRight3, ChevronDown as ChevronDown2 } from "@mirror-physics/fractal-icons";
1831
+ import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
920
1832
  function Disclosure({
921
1833
  title,
922
1834
  meta,
@@ -924,16 +1836,18 @@ function Disclosure({
924
1836
  defaultOpen = false,
925
1837
  open: controlledOpen,
926
1838
  onOpenChange,
1839
+ disabled = false,
927
1840
  className,
928
1841
  variant = "card"
929
1842
  }) {
930
- const reactId = useId2();
1843
+ const reactId = useId3();
931
1844
  const headerId = `${reactId}-header`;
932
1845
  const panelId = `${reactId}-panel`;
933
- const [internalOpen, setInternalOpen] = useState5(defaultOpen);
1846
+ const [internalOpen, setInternalOpen] = useState8(defaultOpen);
934
1847
  const isControlled = controlledOpen !== void 0;
935
1848
  const isOpen = isControlled ? controlledOpen : internalOpen;
936
1849
  const toggle = () => {
1850
+ if (disabled) return;
937
1851
  const next = !isOpen;
938
1852
  if (isControlled) {
939
1853
  onOpenChange?.(next);
@@ -942,17 +1856,18 @@ function Disclosure({
942
1856
  onOpenChange?.(next);
943
1857
  }
944
1858
  };
945
- return /* @__PURE__ */ jsxs11(
1859
+ return /* @__PURE__ */ jsxs19(
946
1860
  "div",
947
1861
  {
948
1862
  className: cn(
949
1863
  "fractal-disclosure",
950
1864
  variant === "card" && "fractal-disclosure--card",
951
1865
  isOpen && "fractal-disclosure--open",
1866
+ disabled && "fractal-disclosure--disabled",
952
1867
  className
953
1868
  ),
954
1869
  children: [
955
- /* @__PURE__ */ jsxs11(
1870
+ /* @__PURE__ */ jsxs19(
956
1871
  "button",
957
1872
  {
958
1873
  type: "button",
@@ -960,15 +1875,16 @@ function Disclosure({
960
1875
  className: "fractal-disclosure-trigger",
961
1876
  "aria-expanded": isOpen,
962
1877
  "aria-controls": panelId,
1878
+ disabled,
963
1879
  onClick: toggle,
964
1880
  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 })
1881
+ /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx22(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx22(ChevronRight3, { size: 16 }) }),
1882
+ /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-title", children: title }),
1883
+ meta && /* @__PURE__ */ jsx22("span", { className: "fractal-disclosure-meta", children: meta })
968
1884
  ]
969
1885
  }
970
1886
  ),
971
- /* @__PURE__ */ jsx14(
1887
+ /* @__PURE__ */ jsx22(
972
1888
  "div",
973
1889
  {
974
1890
  id: panelId,
@@ -976,7 +1892,7 @@ function Disclosure({
976
1892
  "aria-labelledby": headerId,
977
1893
  className: "fractal-disclosure-panel",
978
1894
  hidden: !isOpen,
979
- children: /* @__PURE__ */ jsx14("div", { className: "fractal-disclosure-panel-inner", children })
1895
+ children: /* @__PURE__ */ jsx22("div", { className: "fractal-disclosure-panel-inner", children })
980
1896
  }
981
1897
  )
982
1898
  ]
@@ -984,39 +1900,260 @@ function Disclosure({
984
1900
  );
985
1901
  }
986
1902
 
1903
+ // src/components/LatticeLoader/LatticeLoader.tsx
1904
+ import { jsx as jsx23 } from "react/jsx-runtime";
1905
+ var LATTICE_COUNT = 160;
1906
+ var WAVE_PERIOD = 80;
1907
+ function LatticeLoader({ className, label = "Loading", ...props }) {
1908
+ return /* @__PURE__ */ jsx23(
1909
+ "span",
1910
+ {
1911
+ "aria-label": label,
1912
+ className: cn("fractal-lattice-loader", className),
1913
+ role: "progressbar",
1914
+ ...props,
1915
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ jsx23(
1916
+ "span",
1917
+ {
1918
+ "aria-hidden": "true",
1919
+ className: "fractal-lattice-loader__cell",
1920
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
1921
+ },
1922
+ index
1923
+ ))
1924
+ }
1925
+ );
1926
+ }
1927
+
1928
+ // src/components/Ghost/Ghost.tsx
1929
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1930
+ var toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
1931
+ function Ghost({ className, width, height, radius, style, ...props }) {
1932
+ return /* @__PURE__ */ jsx24(
1933
+ "div",
1934
+ {
1935
+ "aria-hidden": "true",
1936
+ className: cn("fractal-ghost", className),
1937
+ style: {
1938
+ width: toCssLength(width),
1939
+ height: toCssLength(height),
1940
+ borderRadius: toCssLength(radius),
1941
+ ...style
1942
+ },
1943
+ ...props
1944
+ }
1945
+ );
1946
+ }
1947
+ function GhostLine({ className, size = "md", width = "100%", ...props }) {
1948
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-line", `fractal-ghost-line--${size}`, className), width, ...props });
1949
+ }
1950
+ function ButtonGhost({ className, size = "md", iconOnly = false, width, ...props }) {
1951
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
1952
+ }
1953
+ function CardGhost({ className, lines = 3, showHeader = true, ...props }) {
1954
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-card", className), ...props, children: [
1955
+ showHeader && /* @__PURE__ */ jsx24(GhostLine, { width: "38%" }),
1956
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx24(GhostLine, { width: index === lines - 1 ? "62%" : "100%" }, index)) })
1957
+ ] });
1958
+ }
1959
+ function DataTableGhost({
1960
+ className,
1961
+ showHeader = true,
1962
+ headers,
1963
+ columns = 4,
1964
+ rows = 5,
1965
+ columnWidths,
1966
+ size = "md",
1967
+ ...props
1968
+ }) {
1969
+ const cells = Array.from({ length: columns }, (_, index) => index);
1970
+ 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: [
1971
+ (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)) }) }),
1972
+ /* @__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)) })
1973
+ ] }) });
1974
+ }
1975
+ function InputGhost({ className, labelWidth = "26%", ...props }) {
1976
+ return /* @__PURE__ */ jsx24(FormFieldGhost, { className, labelWidth, ...props });
1977
+ }
1978
+ function TextareaGhost({ className, labelWidth = "26%", ...props }) {
1979
+ return /* @__PURE__ */ jsx24(FormFieldGhost, { className, labelWidth, multiline: true, ...props });
1980
+ }
1981
+ function LabelGhost({ className, width = "26%", ...props }) {
1982
+ return /* @__PURE__ */ jsx24(GhostLine, { className: cn("fractal-ghost-label", className), size: "sm", width, ...props });
1983
+ }
1984
+ function FormFieldGhost({ className, labelWidth = "26%", multiline = false, ...props }) {
1985
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-field", className), ...props, children: [
1986
+ /* @__PURE__ */ jsx24(LabelGhost, { width: labelWidth }),
1987
+ /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-field__control", multiline && "fractal-ghost-field__control--multiline") })
1988
+ ] });
1989
+ }
1990
+ function AlertGhost({ className, ...props }) {
1991
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-alert", className), ...props, children: [
1992
+ /* @__PURE__ */ jsx24(GhostLine, { width: "30%" }),
1993
+ /* @__PURE__ */ jsx24(GhostLine, { size: "sm", width: "78%" })
1994
+ ] });
1995
+ }
1996
+ function CheckboxGhost({ className, ...props }) {
1997
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-choice", className), ...props, children: [
1998
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-choice__control" }),
1999
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" })
2000
+ ] });
2001
+ }
2002
+ var ToggleGhost = CheckboxGhost;
2003
+ function ChipGhost({ className, width = 74, ...props }) {
2004
+ return /* @__PURE__ */ jsx24(Ghost, { className: cn("fractal-ghost-chip", className), height: 24, width, ...props });
2005
+ }
2006
+ function LinkGhost({ className, width = 96, ...props }) {
2007
+ return /* @__PURE__ */ jsx24(GhostLine, { className: cn("fractal-ghost-link", className), size: "sm", width, ...props });
2008
+ }
2009
+ var TextButtonGhost = LinkGhost;
2010
+ function DropdownGhost({ className, ...props }) {
2011
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropdown", className), ...props, children: [
2012
+ /* @__PURE__ */ jsx24(GhostLine, { width: "38%" }),
2013
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-dropdown__chevron" })
2014
+ ] });
2015
+ }
2016
+ function ContentSwitcherGhost({ className, options = 3, ...props }) {
2017
+ return /* @__PURE__ */ jsx24("div", { "aria-hidden": "true", className: cn("fractal-ghost-switcher", className), ...props, children: Array.from({ length: options }, (_, index) => /* @__PURE__ */ jsx24(Ghost, {}, index)) });
2018
+ }
2019
+ var TabsGhost = ContentSwitcherGhost;
2020
+ function DisclosureGhost({ className, ...props }) {
2021
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-disclosure", className), ...props, children: [
2022
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-disclosure__icon" }),
2023
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" })
2024
+ ] });
2025
+ }
2026
+ function PaginationGhost({ className, pages = 5, ...props }) {
2027
+ return /* @__PURE__ */ jsx24("div", { "aria-hidden": "true", className: cn("fractal-ghost-pagination", className), ...props, children: Array.from({ length: pages }, (_, index) => /* @__PURE__ */ jsx24(Ghost, {}, index)) });
2028
+ }
2029
+ function FileDropzoneGhost({ className, ...props }) {
2030
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropzone", className), ...props, children: [
2031
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-dropzone__icon" }),
2032
+ /* @__PURE__ */ jsx24(GhostLine, { width: "26%" }),
2033
+ /* @__PURE__ */ jsx24(GhostLine, { size: "sm", width: "46%" })
2034
+ ] });
2035
+ }
2036
+ function PromptCardGhost({ className, ...props }) {
2037
+ return /* @__PURE__ */ jsx24(CardGhost, { className: cn("fractal-ghost-prompt-card", className), lines: 4, ...props });
2038
+ }
2039
+ function ConversationGhost({
2040
+ className,
2041
+ "aria-label": ariaLabel = "Loading conversation",
2042
+ ...props
2043
+ }) {
2044
+ return /* @__PURE__ */ jsxs20(
2045
+ "div",
2046
+ {
2047
+ "aria-busy": "true",
2048
+ "aria-label": ariaLabel,
2049
+ className: cn("fractal-ghost-conversation", className),
2050
+ role: "status",
2051
+ ...props,
2052
+ children: [
2053
+ /* @__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: [
2054
+ /* @__PURE__ */ jsx24(GhostLine, { width: "100%" }),
2055
+ /* @__PURE__ */ jsx24(GhostLine, { width: "86%" }),
2056
+ /* @__PURE__ */ jsx24(GhostLine, { width: "64%" })
2057
+ ] }) }),
2058
+ /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--assistant", children: [
2059
+ /* @__PURE__ */ jsx24(GhostLine, { width: "92%" }),
2060
+ /* @__PURE__ */ jsx24(GhostLine, { width: "78%" }),
2061
+ /* @__PURE__ */ jsx24(GhostLine, { width: "58%" })
2062
+ ] })
2063
+ ]
2064
+ }
2065
+ );
2066
+ }
2067
+ function SortableTableGhost(props) {
2068
+ return /* @__PURE__ */ jsx24(DataTableGhost, { ...props });
2069
+ }
2070
+ function SidebarGhost({ className, items = 6, ...props }) {
2071
+ return /* @__PURE__ */ jsxs20("aside", { "aria-hidden": "true", className: cn("fractal-ghost-sidebar", className), ...props, children: [
2072
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-sidebar__brand" }),
2073
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-sidebar__items", children: Array.from({ length: items }, (_, index) => /* @__PURE__ */ jsxs20("div", { className: "fractal-ghost-sidebar__item", children: [
2074
+ /* @__PURE__ */ jsx24(Ghost, {}),
2075
+ /* @__PURE__ */ jsx24(GhostLine, { width: `${[54, 68, 44, 61][index % 4]}%` })
2076
+ ] }, index)) })
2077
+ ] });
2078
+ }
2079
+ function ModalGhost({ className, title, lines = 3, ...props }) {
2080
+ return /* @__PURE__ */ jsx24(SurfaceGhost, { className: cn("fractal-ghost-modal", className), title, lines, ...props });
2081
+ }
2082
+ function SidePanelGhost({ className, title, lines = 5, ...props }) {
2083
+ return /* @__PURE__ */ jsx24(SurfaceGhost, { className: cn("fractal-ghost-side-panel", className), title, lines, ...props });
2084
+ }
2085
+ function SurfaceGhost({ className, title, lines = 3, ...props }) {
2086
+ return /* @__PURE__ */ jsxs20("div", { "aria-hidden": "true", className: cn("fractal-ghost-surface", className), ...props, children: [
2087
+ /* @__PURE__ */ jsxs20("div", { className: "fractal-ghost-surface__header", children: [
2088
+ /* @__PURE__ */ jsx24(GhostLine, { width: "42%" }),
2089
+ /* @__PURE__ */ jsx24(Ghost, { className: "fractal-ghost-surface__close" })
2090
+ ] }),
2091
+ title && /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-surface__title", children: title }),
2092
+ /* @__PURE__ */ jsx24("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx24(GhostLine, { width: index === lines - 1 ? "64%" : "100%" }, index)) })
2093
+ ] });
2094
+ }
2095
+
987
2096
  // 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(
2097
+ import * as React15 from "react";
2098
+ import { Fragment as Fragment3, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
2099
+ var Sidebar = React15.forwardRef(
2100
+ ({ className, width, compact = false, style, ...props }, ref) => /* @__PURE__ */ jsx25(
992
2101
  "aside",
993
2102
  {
994
2103
  ref,
995
2104
  className: cn("fractal-sidebar", className),
2105
+ "data-compact": compact || void 0,
996
2106
  style: { ...style, ...width ? { "--fractal-sidebar-width": width } : {} },
997
2107
  ...props
998
2108
  }
999
2109
  )
1000
2110
  );
1001
2111
  Sidebar.displayName = "Sidebar";
1002
- var SidebarHeader = React10.forwardRef(
1003
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
2112
+ var SidebarHeader = React15.forwardRef(
2113
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1004
2114
  );
1005
2115
  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 })
2116
+ var SidebarBrand = React15.forwardRef(
2117
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx25("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
1008
2118
  );
1009
2119
  SidebarBrand.displayName = "SidebarBrand";
1010
- var SidebarNav = React10.forwardRef(
1011
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
2120
+ function SidebarAccountMenu({
2121
+ name,
2122
+ items,
2123
+ onSelect,
2124
+ triggerLabel = "Account menu",
2125
+ icon,
2126
+ className
2127
+ }) {
2128
+ return /* @__PURE__ */ jsx25(
2129
+ Dropdown,
2130
+ {
2131
+ align: "left",
2132
+ className: cn("fractal-sidebar__account", className),
2133
+ items,
2134
+ onSelect,
2135
+ panelClassName: "fractal-sidebar__account-panel",
2136
+ selectedValue: "",
2137
+ triggerClassName: "fractal-sidebar__account-trigger",
2138
+ triggerContent: /* @__PURE__ */ jsxs21(Fragment3, { children: [
2139
+ icon && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__account-icon", "aria-hidden": "true", children: icon }),
2140
+ /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__account-name", children: name })
2141
+ ] }),
2142
+ triggerLabel,
2143
+ triggerVariant: "tertiary"
2144
+ }
2145
+ );
2146
+ }
2147
+ var SidebarNav = React15.forwardRef(
2148
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1012
2149
  );
1013
2150
  SidebarNav.displayName = "SidebarNav";
1014
- var SidebarSection = React10.forwardRef(
1015
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
2151
+ var SidebarSection = React15.forwardRef(
2152
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1016
2153
  );
1017
2154
  SidebarSection.displayName = "SidebarSection";
1018
- var SidebarNavItem = React10.forwardRef(
1019
- ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs12(
2155
+ var SidebarNavItem = React15.forwardRef(
2156
+ ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs21(
1020
2157
  "button",
1021
2158
  {
1022
2159
  ref,
@@ -1026,28 +2163,28 @@ var SidebarNavItem = React10.forwardRef(
1026
2163
  "aria-current": active ? "page" : void 0,
1027
2164
  ...props,
1028
2165
  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 })
2166
+ icon && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
2167
+ /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-label", children }),
2168
+ endAdornment && /* @__PURE__ */ jsx25("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1032
2169
  ]
1033
2170
  }
1034
2171
  )
1035
2172
  );
1036
2173
  SidebarNavItem.displayName = "SidebarNavItem";
1037
- var SidebarFooter = React10.forwardRef(
1038
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
2174
+ var SidebarFooter = React15.forwardRef(
2175
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx25("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1039
2176
  );
1040
2177
  SidebarFooter.displayName = "SidebarFooter";
1041
2178
 
1042
2179
  // src/components/SidePanel/SidePanel.tsx
1043
2180
  import { createPortal as createPortal2 } from "react-dom";
1044
2181
  import { Close } from "@mirror-physics/fractal-icons";
1045
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1046
- function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
2182
+ import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
2183
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", loading = false, loadingLabel = "Loading", className }) {
1047
2184
  useEscapeDismiss(80, onClose, open);
1048
2185
  if (!open) return null;
1049
2186
  return createPortal2(
1050
- /* @__PURE__ */ jsx16("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs13(
2187
+ /* @__PURE__ */ jsx26("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs22(
1051
2188
  "aside",
1052
2189
  {
1053
2190
  className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
@@ -1056,15 +2193,15 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1056
2193
  role: "dialog",
1057
2194
  onMouseDown: (event) => event.stopPropagation(),
1058
2195
  children: [
1059
- /* @__PURE__ */ jsxs13("header", { className: "fractal-side-panel__header", children: [
1060
- /* @__PURE__ */ jsxs13("div", { className: "fractal-side-panel__heading", children: [
1061
- /* @__PURE__ */ jsx16("h2", { className: "fractal-side-panel__title", children: title }),
1062
- description && /* @__PURE__ */ jsx16("p", { className: "fractal-side-panel__description", children: description })
2196
+ /* @__PURE__ */ jsxs22("header", { className: "fractal-side-panel__header", children: [
2197
+ /* @__PURE__ */ jsxs22("div", { className: "fractal-side-panel__heading", children: [
2198
+ typeof title === "string" ? /* @__PURE__ */ jsx26("h2", { className: "fractal-side-panel__title", children: title }) : /* @__PURE__ */ jsx26("div", { className: "fractal-side-panel__title", children: title }),
2199
+ description && /* @__PURE__ */ jsx26("p", { className: "fractal-side-panel__description", children: description })
1063
2200
  ] }),
1064
- /* @__PURE__ */ jsx16("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx16(Close, { size: 18 }) })
2201
+ /* @__PURE__ */ jsx26("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx26(Close, { size: 18 }) })
1065
2202
  ] }),
1066
- /* @__PURE__ */ jsx16("div", { className: "fractal-side-panel__body", children }),
1067
- footer && /* @__PURE__ */ jsx16("footer", { className: "fractal-side-panel__footer", children: footer })
2203
+ /* @__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 }),
2204
+ footer && /* @__PURE__ */ jsx26("footer", { className: "fractal-side-panel__footer", children: footer })
1068
2205
  ]
1069
2206
  }
1070
2207
  ) }),
@@ -1073,47 +2210,272 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1073
2210
  }
1074
2211
 
1075
2212
  // src/components/Textarea/Textarea.tsx
1076
- import * as React11 from "react";
1077
- import { jsx as jsx17 } from "react/jsx-runtime";
1078
- var Textarea = React11.forwardRef(
1079
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx17("textarea", { ref, className: cn("fractal-textarea", className), ...props })
2213
+ import * as React16 from "react";
2214
+ import { jsx as jsx27 } from "react/jsx-runtime";
2215
+ var Textarea = React16.forwardRef(
2216
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx27("textarea", { ref, className: cn("fractal-textarea", className), ...props })
1080
2217
  );
1081
2218
  Textarea.displayName = "Textarea";
2219
+
2220
+ // src/components/TextButton/TextButton.tsx
2221
+ import * as React17 from "react";
2222
+ import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
2223
+ var TextButton = React17.forwardRef(
2224
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ jsxs23(
2225
+ "button",
2226
+ {
2227
+ ref,
2228
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
2229
+ type: "button",
2230
+ ...props,
2231
+ children: [
2232
+ icon && iconPosition === "start" && /* @__PURE__ */ jsx28("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
2233
+ /* @__PURE__ */ jsx28("span", { children }),
2234
+ icon && iconPosition === "end" && /* @__PURE__ */ jsx28("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
2235
+ ]
2236
+ }
2237
+ )
2238
+ );
2239
+ TextButton.displayName = "TextButton";
2240
+
2241
+ // src/components/FileDropzone/FileDropzone.tsx
2242
+ import * as React18 from "react";
2243
+ import { Close as Close2, FileArrowUp } from "@mirror-physics/fractal-icons";
2244
+ import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
2245
+ function FileDropzone({
2246
+ files,
2247
+ onFilesChange,
2248
+ accept,
2249
+ multiple = true,
2250
+ disabled = false,
2251
+ title = "Drop files here or click to browse",
2252
+ description = "Files stay attached to this record.",
2253
+ className
2254
+ }) {
2255
+ const inputId = React18.useId();
2256
+ const inputRef = React18.useRef(null);
2257
+ const [isDragging, setIsDragging] = React18.useState(false);
2258
+ const addFiles = (nextFiles) => {
2259
+ if (disabled) return;
2260
+ const additions = Array.from(nextFiles);
2261
+ if (additions.length === 0) return;
2262
+ onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
2263
+ };
2264
+ return /* @__PURE__ */ jsxs24("div", { className: cn("fractal-file-dropzone", className), children: [
2265
+ /* @__PURE__ */ jsx29(
2266
+ "input",
2267
+ {
2268
+ ref: inputRef,
2269
+ id: inputId,
2270
+ className: "fractal-file-dropzone__input",
2271
+ type: "file",
2272
+ accept,
2273
+ multiple,
2274
+ disabled,
2275
+ onChange: (event) => {
2276
+ if (event.target.files) addFiles(event.target.files);
2277
+ event.target.value = "";
2278
+ }
2279
+ }
2280
+ ),
2281
+ /* @__PURE__ */ jsxs24(
2282
+ "button",
2283
+ {
2284
+ className: "fractal-file-dropzone__target",
2285
+ "data-dragging": isDragging || void 0,
2286
+ type: "button",
2287
+ disabled,
2288
+ onClick: () => inputRef.current?.click(),
2289
+ onDragEnter: (event) => {
2290
+ event.preventDefault();
2291
+ if (!disabled) setIsDragging(true);
2292
+ },
2293
+ onDragOver: (event) => event.preventDefault(),
2294
+ onDragLeave: (event) => {
2295
+ event.preventDefault();
2296
+ setIsDragging(false);
2297
+ },
2298
+ onDrop: (event) => {
2299
+ event.preventDefault();
2300
+ setIsDragging(false);
2301
+ addFiles(event.dataTransfer.files);
2302
+ },
2303
+ children: [
2304
+ /* @__PURE__ */ jsx29(FileArrowUp, { "aria-hidden": "true", size: 20 }),
2305
+ /* @__PURE__ */ jsxs24("span", { className: "fractal-file-dropzone__copy", children: [
2306
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__title", children: title }),
2307
+ description && /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__description", children: description })
2308
+ ] })
2309
+ ]
2310
+ }
2311
+ ),
2312
+ 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: [
2313
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
2314
+ /* @__PURE__ */ jsx29("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
2315
+ /* @__PURE__ */ jsx29(
2316
+ "button",
2317
+ {
2318
+ className: "fractal-file-dropzone__remove",
2319
+ type: "button",
2320
+ "aria-label": `Remove ${file.name}`,
2321
+ disabled,
2322
+ onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
2323
+ children: /* @__PURE__ */ jsx29(Close2, { "aria-hidden": "true", size: 14 })
2324
+ }
2325
+ )
2326
+ ] }, `${file.name}-${file.size}-${index}`)) })
2327
+ ] });
2328
+ }
2329
+ function formatFileSize(bytes) {
2330
+ if (bytes < 1024) return `${bytes} B`;
2331
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
2332
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2333
+ }
2334
+
2335
+ // src/components/Pagination/Pagination.tsx
2336
+ import { ArrowLeft, ArrowRight } from "@mirror-physics/fractal-icons";
2337
+ import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
2338
+ function Pagination({
2339
+ page,
2340
+ pageSize,
2341
+ totalItems,
2342
+ onPageChange,
2343
+ pageSizeOptions,
2344
+ onPageSizeChange,
2345
+ ariaLabel = "Pagination",
2346
+ className
2347
+ }) {
2348
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
2349
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
2350
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
2351
+ const end = Math.min(currentPage * pageSize, totalItems);
2352
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
2353
+ return /* @__PURE__ */ jsxs25("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
2354
+ /* @__PURE__ */ jsx30("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
2355
+ /* @__PURE__ */ jsxs25("div", { className: "fractal-pagination__controls", children: [
2356
+ canChangePageSize && /* @__PURE__ */ jsx30(
2357
+ Dropdown,
2358
+ {
2359
+ align: "right",
2360
+ className: "fractal-pagination__page-size",
2361
+ direction: "up",
2362
+ items: pageSizeOptions.map((option) => ({
2363
+ value: String(option),
2364
+ description: `${option} per page`
2365
+ })),
2366
+ onSelect: (value) => onPageSizeChange(Number(value)),
2367
+ selectedValue: String(pageSize),
2368
+ size: "sm",
2369
+ triggerLabel: "Results per page",
2370
+ triggerVariant: "tertiary"
2371
+ }
2372
+ ),
2373
+ /* @__PURE__ */ jsx30(
2374
+ "button",
2375
+ {
2376
+ className: "fractal-pagination__button",
2377
+ type: "button",
2378
+ "aria-label": "Previous page",
2379
+ title: "Previous page",
2380
+ disabled: currentPage === 1 || totalItems === 0,
2381
+ onClick: () => onPageChange(currentPage - 1),
2382
+ children: /* @__PURE__ */ jsx30(ArrowLeft, { "aria-hidden": "true", size: 16 })
2383
+ }
2384
+ ),
2385
+ /* @__PURE__ */ jsxs25("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
2386
+ currentPage,
2387
+ " of ",
2388
+ totalPages
2389
+ ] }),
2390
+ /* @__PURE__ */ jsx30(
2391
+ "button",
2392
+ {
2393
+ className: "fractal-pagination__button",
2394
+ type: "button",
2395
+ "aria-label": "Next page",
2396
+ title: "Next page",
2397
+ disabled: currentPage === totalPages || totalItems === 0,
2398
+ onClick: () => onPageChange(currentPage + 1),
2399
+ children: /* @__PURE__ */ jsx30(ArrowRight, { "aria-hidden": "true", size: 16 })
2400
+ }
2401
+ )
2402
+ ] })
2403
+ ] });
2404
+ }
1082
2405
  export {
1083
2406
  Alert,
1084
2407
  AlertDescription,
2408
+ AlertGhost,
1085
2409
  AlertTitle,
2410
+ Breadcrumbs,
1086
2411
  Button,
2412
+ ButtonGhost,
1087
2413
  Card,
1088
2414
  CardContent,
1089
2415
  CardDescription,
1090
2416
  CardFooter,
2417
+ CardGhost,
1091
2418
  CardHeader,
1092
2419
  CardTitle,
2420
+ Checkbox,
2421
+ CheckboxGhost,
1093
2422
  Chip,
2423
+ ChipGhost,
2424
+ CodeBlock,
2425
+ ContentSwitcher,
2426
+ ContentSwitcherGhost,
2427
+ ConversationGhost,
1094
2428
  DataTable,
2429
+ DataTableGhost,
1095
2430
  Disclosure,
2431
+ DisclosureGhost,
1096
2432
  Dropdown,
2433
+ DropdownGhost,
1097
2434
  FeatureCard,
2435
+ FileDropzone,
2436
+ FileDropzoneGhost,
1098
2437
  FolderCard,
2438
+ Ghost,
2439
+ GhostLine,
1099
2440
  Input,
2441
+ InputGhost,
1100
2442
  Label,
2443
+ LabelGhost,
2444
+ LatticeLoader,
1101
2445
  Link,
2446
+ LinkGhost,
1102
2447
  Modal,
2448
+ ModalGhost,
2449
+ NumberInput,
2450
+ Pagination,
2451
+ PaginationGhost,
2452
+ PasswordInput,
1103
2453
  PromptCard,
2454
+ PromptCardGhost,
1104
2455
  PromptGrid,
1105
2456
  SidePanel,
2457
+ SidePanelGhost,
1106
2458
  Sidebar,
2459
+ SidebarAccountMenu,
1107
2460
  SidebarBrand,
1108
2461
  SidebarFooter,
2462
+ SidebarGhost,
1109
2463
  SidebarHeader,
1110
2464
  SidebarNav,
1111
2465
  SidebarNavItem,
1112
2466
  SidebarSection,
1113
2467
  SortableTable,
2468
+ SortableTableGhost,
2469
+ TableSearch,
1114
2470
  Tabs,
2471
+ TabsGhost,
2472
+ TextButton,
2473
+ TextButtonGhost,
1115
2474
  Textarea,
2475
+ TextareaGhost,
1116
2476
  Toggle,
2477
+ ToggleGhost,
2478
+ UILoader,
1117
2479
  cn,
1118
2480
  useEscapeDismiss
1119
2481
  };