@mirror-physics/fractal-ui 0.2.0-next.5 → 0.2.0-next.51

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