@mirror-physics/fractal-ui 0.2.0-next.2 → 0.2.0-next.21

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
  };
@@ -664,23 +664,52 @@ var Chip = React9.forwardRef(
664
664
  );
665
665
  Chip.displayName = "Chip";
666
666
 
667
+ // src/components/Checkbox/Checkbox.tsx
668
+ import { Checkbox as CheckboxIcon, CheckboxChecked } from "@mirror-physics/fractal-icons";
669
+ import { jsx as jsx12 } from "react/jsx-runtime";
670
+ function Checkbox({ checked, onCheckedChange, className, disabled, onClick, onKeyDown, ...props }) {
671
+ return /* @__PURE__ */ jsx12(
672
+ "button",
673
+ {
674
+ ...props,
675
+ className: cn("fractal-checkbox", checked && "fractal-checkbox--checked", className),
676
+ type: "button",
677
+ role: "checkbox",
678
+ "aria-checked": checked,
679
+ disabled,
680
+ onClick: (event) => {
681
+ onClick?.(event);
682
+ if (!event.defaultPrevented) onCheckedChange(!checked);
683
+ },
684
+ onKeyDown: (event) => {
685
+ onKeyDown?.(event);
686
+ if (event.key === " " || event.key === "Enter") event.stopPropagation();
687
+ },
688
+ children: checked ? /* @__PURE__ */ jsx12(CheckboxChecked, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ jsx12(CheckboxIcon, { "aria-hidden": "true", size: 18 })
689
+ }
690
+ );
691
+ }
692
+
667
693
  // src/components/SortableTable/SortableTable.tsx
668
694
  import { useMemo, useState as useState3 } from "react";
669
695
  import { ArrowUp, ArrowDown, ArrowsUpDown } from "@mirror-physics/fractal-icons";
670
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
696
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
671
697
  function SortableTable({
672
698
  columns,
673
699
  data,
674
700
  emptyMessage,
675
701
  header,
676
702
  size = "md",
703
+ embedded = false,
677
704
  className,
678
705
  defaultSortKey = void 0,
679
706
  defaultSortDirection = null,
680
707
  sortKey: controlledKey,
681
708
  sortDirection: controlledDirection,
682
709
  onSortChange,
683
- highlightRow
710
+ highlightRow,
711
+ onRowClick,
712
+ rowAction
684
713
  }) {
685
714
  const [internalKey, setInternalKey] = useState3(defaultSortKey ?? null);
686
715
  const [internalDirection, setInternalDirection] = useState3(defaultSortDirection);
@@ -733,13 +762,13 @@ function SortableTable({
733
762
  onSortChange?.(nextKey, nextDirection);
734
763
  }
735
764
  };
736
- return /* @__PURE__ */ jsx12("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, className), children: /* @__PURE__ */ jsxs9("div", { className: "fractal-sortable-table-surface", children: [
737
- header && /* @__PURE__ */ jsx12("div", { className: "fractal-sortable-table-header", children: header }),
738
- sorted.length === 0 ? /* @__PURE__ */ jsx12("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs9("table", { className: "fractal-sortable-table-table", children: [
739
- /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsx12("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
765
+ return /* @__PURE__ */ jsx13("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, embedded && "fractal-sortable-table--embedded", className), children: /* @__PURE__ */ jsxs9("div", { className: "fractal-sortable-table-surface", children: [
766
+ header && /* @__PURE__ */ jsx13("div", { className: "fractal-sortable-table-header", children: header }),
767
+ sorted.length === 0 ? /* @__PURE__ */ jsx13("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ jsxs9("table", { className: "fractal-sortable-table-table", children: [
768
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
740
769
  const isActive = activeKey === col.key && activeDirection !== null;
741
770
  if (!col.sortable) {
742
- return /* @__PURE__ */ jsx12(
771
+ return /* @__PURE__ */ jsx13(
743
772
  "th",
744
773
  {
745
774
  className: "fractal-sortable-table-th",
@@ -749,7 +778,7 @@ function SortableTable({
749
778
  col.key
750
779
  );
751
780
  }
752
- return /* @__PURE__ */ jsx12(
781
+ return /* @__PURE__ */ jsx13(
753
782
  "th",
754
783
  {
755
784
  className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
@@ -765,8 +794,8 @@ function SortableTable({
765
794
  onClick: () => handleSortClick(col.key),
766
795
  "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
767
796
  children: [
768
- /* @__PURE__ */ jsx12("span", { children: col.header }),
769
- /* @__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 }) })
797
+ /* @__PURE__ */ jsx13("span", { children: col.header }),
798
+ /* @__PURE__ */ jsx13("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ jsx13(ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ jsx13(ArrowDown, { size: 14 }) : /* @__PURE__ */ jsx13(ArrowsUpDown, { size: 14 }) })
770
799
  ]
771
800
  }
772
801
  )
@@ -774,21 +803,42 @@ function SortableTable({
774
803
  col.key
775
804
  );
776
805
  }) }) }),
777
- /* @__PURE__ */ jsx12("tbody", { children: sorted.map((row, rowIndex) => {
806
+ /* @__PURE__ */ jsx13("tbody", { children: sorted.map((row, rowIndex) => {
778
807
  const highlighted = highlightRow?.(row, rowIndex) ?? false;
779
- return /* @__PURE__ */ jsx12(
808
+ const action = rowAction ? {
809
+ label: rowAction.label(row, rowIndex),
810
+ icon: rowAction.icon(row, rowIndex)
811
+ } : null;
812
+ return /* @__PURE__ */ jsx13(
780
813
  "tr",
781
814
  {
782
815
  className: cn(
783
816
  "fractal-sortable-table-row",
784
- highlighted && "fractal-sortable-table-row--highlighted"
817
+ highlighted && "fractal-sortable-table-row--highlighted",
818
+ onRowClick && "fractal-sortable-table-row--interactive"
785
819
  ),
786
- children: columns.map((col) => /* @__PURE__ */ jsx12(
820
+ onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
821
+ onKeyDown: onRowClick ? (event) => {
822
+ if (event.key === "Enter" || event.key === " ") {
823
+ event.preventDefault();
824
+ onRowClick(row, rowIndex);
825
+ }
826
+ } : void 0,
827
+ "aria-label": action?.label,
828
+ role: onRowClick ? "button" : void 0,
829
+ tabIndex: onRowClick ? 0 : void 0,
830
+ children: columns.map((col, columnIndex) => /* @__PURE__ */ jsxs9(
787
831
  "td",
788
832
  {
789
- className: "fractal-sortable-table-td",
833
+ className: cn(
834
+ "fractal-sortable-table-td",
835
+ action && columnIndex === columns.length - 1 && "fractal-sortable-table-td--row-action"
836
+ ),
790
837
  style: { width: col.width, textAlign: col.align ?? "left" },
791
- children: col.render(row, rowIndex)
838
+ children: [
839
+ col.render(row, rowIndex),
840
+ action && columnIndex === columns.length - 1 && /* @__PURE__ */ jsx13("span", { className: "fractal-sortable-table-row-action", "aria-hidden": "true", children: /* @__PURE__ */ jsx13("span", { className: "fractal-sortable-table-row-action__icon", children: action.icon }) })
841
+ ]
792
842
  },
793
843
  col.key
794
844
  ))
@@ -801,15 +851,16 @@ function SortableTable({
801
851
  }
802
852
 
803
853
  // src/components/Tabs/Tabs.tsx
804
- import { useId, useRef as useRef3, useState as useState4 } from "react";
805
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
854
+ import { useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
855
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
806
856
  function Tabs({
807
857
  items,
808
858
  defaultValue,
809
859
  value: controlledValue,
810
860
  onValueChange,
811
861
  className,
812
- ariaLabel
862
+ ariaLabel,
863
+ divider = false
813
864
  }) {
814
865
  const reactId = useId();
815
866
  const [internalValue, setInternalValue] = useState4(
@@ -817,7 +868,22 @@ function Tabs({
817
868
  );
818
869
  const isControlled = controlledValue !== void 0;
819
870
  const activeId = isControlled ? controlledValue : internalValue;
871
+ const tabListRef = useRef3(null);
820
872
  const tabRefs = useRef3({});
873
+ const [indicator, setIndicator] = useState4(null);
874
+ useLayoutEffect2(() => {
875
+ const updateIndicator = () => {
876
+ const activeTab = tabRefs.current[activeId];
877
+ if (!activeTab) return;
878
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
879
+ };
880
+ updateIndicator();
881
+ const list = tabListRef.current;
882
+ if (!list || typeof ResizeObserver === "undefined") return;
883
+ const observer = new ResizeObserver(updateIndicator);
884
+ observer.observe(list);
885
+ return () => observer.disconnect();
886
+ }, [activeId, items.length]);
821
887
  const selectTab = (id) => {
822
888
  if (isControlled) {
823
889
  onValueChange?.(id);
@@ -827,6 +893,10 @@ function Tabs({
827
893
  }
828
894
  };
829
895
  const focusable = items.filter((i) => !i.disabled);
896
+ const indicatorStyle = indicator ? {
897
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
898
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
899
+ } : void 0;
830
900
  const onKeyDown = (event) => {
831
901
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
832
902
  if (currentIndex === -1) return;
@@ -852,41 +922,52 @@ function Tabs({
852
922
  selectTab(nextId2);
853
923
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
854
924
  };
855
- return /* @__PURE__ */ jsxs10("div", { className: cn("fractal-tabs", className), children: [
856
- /* @__PURE__ */ jsx13("div", { role: "tablist", "aria-label": ariaLabel, className: "fractal-tabs-list", children: items.map((item) => {
857
- const isActive = item.id === activeId;
858
- const tabId = `${reactId}-tab-${item.id}`;
859
- const panelId = `${reactId}-panel-${item.id}`;
860
- return /* @__PURE__ */ jsx13(
861
- "button",
862
- {
863
- ref: (el) => {
864
- tabRefs.current[item.id] = el;
865
- },
866
- id: tabId,
867
- role: "tab",
868
- type: "button",
869
- "aria-selected": isActive,
870
- "aria-controls": panelId,
871
- tabIndex: isActive ? 0 : -1,
872
- disabled: item.disabled,
873
- className: cn(
874
- "fractal-tabs-trigger",
875
- isActive && "fractal-tabs-trigger--active",
876
- item.disabled && "fractal-tabs-trigger--disabled"
877
- ),
878
- onClick: () => !item.disabled && selectTab(item.id),
879
- onKeyDown,
880
- children: item.label
881
- },
882
- item.id
883
- );
884
- }) }),
925
+ return /* @__PURE__ */ jsxs10("div", { className: cn("fractal-tabs", divider && "fractal-tabs--divider", className), children: [
926
+ /* @__PURE__ */ jsx14(
927
+ "div",
928
+ {
929
+ ref: tabListRef,
930
+ role: "tablist",
931
+ "aria-label": ariaLabel,
932
+ className: "fractal-tabs-list",
933
+ "data-indicator-ready": indicator ? "true" : void 0,
934
+ style: indicatorStyle,
935
+ children: items.map((item) => {
936
+ const isActive = item.id === activeId;
937
+ const tabId = `${reactId}-tab-${item.id}`;
938
+ const panelId = `${reactId}-panel-${item.id}`;
939
+ return /* @__PURE__ */ jsx14(
940
+ "button",
941
+ {
942
+ ref: (el) => {
943
+ tabRefs.current[item.id] = el;
944
+ },
945
+ id: tabId,
946
+ role: "tab",
947
+ type: "button",
948
+ "aria-selected": isActive,
949
+ "aria-controls": panelId,
950
+ tabIndex: isActive ? 0 : -1,
951
+ disabled: item.disabled,
952
+ className: cn(
953
+ "fractal-tabs-trigger",
954
+ isActive && "fractal-tabs-trigger--active",
955
+ item.disabled && "fractal-tabs-trigger--disabled"
956
+ ),
957
+ onClick: () => !item.disabled && selectTab(item.id),
958
+ onKeyDown,
959
+ children: item.label
960
+ },
961
+ item.id
962
+ );
963
+ })
964
+ }
965
+ ),
885
966
  items.map((item) => {
886
967
  const tabId = `${reactId}-tab-${item.id}`;
887
968
  const panelId = `${reactId}-panel-${item.id}`;
888
969
  const isActive = item.id === activeId;
889
- return /* @__PURE__ */ jsx13(
970
+ return /* @__PURE__ */ jsx14(
890
971
  "div",
891
972
  {
892
973
  id: panelId,
@@ -902,10 +983,55 @@ function Tabs({
902
983
  ] });
903
984
  }
904
985
 
986
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
987
+ import * as React10 from "react";
988
+ import { jsx as jsx15 } from "react/jsx-runtime";
989
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
990
+ const buttonRefs = React10.useRef([]);
991
+ const moveFocus = (index, direction) => {
992
+ let nextIndex = index;
993
+ do {
994
+ nextIndex = (nextIndex + direction + items.length) % items.length;
995
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
996
+ if (!items[nextIndex]?.disabled) {
997
+ buttonRefs.current[nextIndex]?.focus();
998
+ onValueChange(items[nextIndex].value);
999
+ }
1000
+ };
1001
+ return /* @__PURE__ */ jsx15("div", { className: cn("fractal-content-switcher", fullWidth && "fractal-content-switcher--full-width", className), role: "group", "aria-label": ariaLabel, children: items.map((item, index) => {
1002
+ const active = item.value === value;
1003
+ return /* @__PURE__ */ jsx15(
1004
+ "button",
1005
+ {
1006
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
1007
+ type: "button",
1008
+ "aria-pressed": active,
1009
+ disabled: item.disabled,
1010
+ onClick: () => onValueChange(item.value),
1011
+ onKeyDown: (event) => {
1012
+ if (event.key === "ArrowRight") {
1013
+ event.preventDefault();
1014
+ moveFocus(index, 1);
1015
+ }
1016
+ if (event.key === "ArrowLeft") {
1017
+ event.preventDefault();
1018
+ moveFocus(index, -1);
1019
+ }
1020
+ },
1021
+ ref: (element) => {
1022
+ buttonRefs.current[index] = element;
1023
+ },
1024
+ children: item.label
1025
+ },
1026
+ item.value
1027
+ );
1028
+ }) });
1029
+ }
1030
+
905
1031
  // src/components/Disclosure/Disclosure.tsx
906
1032
  import { useId as useId2, useState as useState5 } from "react";
907
1033
  import { ChevronRight, ChevronDown as ChevronDown2 } from "@mirror-physics/fractal-icons";
908
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1034
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
909
1035
  function Disclosure({
910
1036
  title,
911
1037
  meta,
@@ -913,6 +1039,7 @@ function Disclosure({
913
1039
  defaultOpen = false,
914
1040
  open: controlledOpen,
915
1041
  onOpenChange,
1042
+ disabled = false,
916
1043
  className,
917
1044
  variant = "card"
918
1045
  }) {
@@ -923,6 +1050,7 @@ function Disclosure({
923
1050
  const isControlled = controlledOpen !== void 0;
924
1051
  const isOpen = isControlled ? controlledOpen : internalOpen;
925
1052
  const toggle = () => {
1053
+ if (disabled) return;
926
1054
  const next = !isOpen;
927
1055
  if (isControlled) {
928
1056
  onOpenChange?.(next);
@@ -938,6 +1066,7 @@ function Disclosure({
938
1066
  "fractal-disclosure",
939
1067
  variant === "card" && "fractal-disclosure--card",
940
1068
  isOpen && "fractal-disclosure--open",
1069
+ disabled && "fractal-disclosure--disabled",
941
1070
  className
942
1071
  ),
943
1072
  children: [
@@ -949,15 +1078,16 @@ function Disclosure({
949
1078
  className: "fractal-disclosure-trigger",
950
1079
  "aria-expanded": isOpen,
951
1080
  "aria-controls": panelId,
1081
+ disabled,
952
1082
  onClick: toggle,
953
1083
  children: [
954
- /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx14(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx14(ChevronRight, { size: 16 }) }),
955
- /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-title", children: title }),
956
- meta && /* @__PURE__ */ jsx14("span", { className: "fractal-disclosure-meta", children: meta })
1084
+ /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx16(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx16(ChevronRight, { size: 16 }) }),
1085
+ /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-title", children: title }),
1086
+ meta && /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-meta", children: meta })
957
1087
  ]
958
1088
  }
959
1089
  ),
960
- /* @__PURE__ */ jsx14(
1090
+ /* @__PURE__ */ jsx16(
961
1091
  "div",
962
1092
  {
963
1093
  id: panelId,
@@ -965,7 +1095,7 @@ function Disclosure({
965
1095
  "aria-labelledby": headerId,
966
1096
  className: "fractal-disclosure-panel",
967
1097
  hidden: !isOpen,
968
- children: /* @__PURE__ */ jsx14("div", { className: "fractal-disclosure-panel-inner", children })
1098
+ children: /* @__PURE__ */ jsx16("div", { className: "fractal-disclosure-panel-inner", children })
969
1099
  }
970
1100
  )
971
1101
  ]
@@ -973,11 +1103,36 @@ function Disclosure({
973
1103
  );
974
1104
  }
975
1105
 
1106
+ // src/components/LatticeLoader/LatticeLoader.tsx
1107
+ import { jsx as jsx17 } from "react/jsx-runtime";
1108
+ var LATTICE_COUNT = 160;
1109
+ var WAVE_PERIOD = 80;
1110
+ function LatticeLoader({ className, label = "Loading", ...props }) {
1111
+ return /* @__PURE__ */ jsx17(
1112
+ "span",
1113
+ {
1114
+ "aria-label": label,
1115
+ className: cn("fractal-lattice-loader", className),
1116
+ role: "progressbar",
1117
+ ...props,
1118
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ jsx17(
1119
+ "span",
1120
+ {
1121
+ "aria-hidden": "true",
1122
+ className: "fractal-lattice-loader__cell",
1123
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
1124
+ },
1125
+ index
1126
+ ))
1127
+ }
1128
+ );
1129
+ }
1130
+
976
1131
  // src/components/Sidebar/Sidebar.tsx
977
- import * as React10 from "react";
978
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
979
- var Sidebar = React10.forwardRef(
980
- ({ className, width, style, ...props }, ref) => /* @__PURE__ */ jsx15(
1132
+ import * as React11 from "react";
1133
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1134
+ var Sidebar = React11.forwardRef(
1135
+ ({ className, width, style, ...props }, ref) => /* @__PURE__ */ jsx18(
981
1136
  "aside",
982
1137
  {
983
1138
  ref,
@@ -988,23 +1143,23 @@ var Sidebar = React10.forwardRef(
988
1143
  )
989
1144
  );
990
1145
  Sidebar.displayName = "Sidebar";
991
- var SidebarHeader = React10.forwardRef(
992
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1146
+ var SidebarHeader = React11.forwardRef(
1147
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
993
1148
  );
994
1149
  SidebarHeader.displayName = "SidebarHeader";
995
- var SidebarBrand = React10.forwardRef(
996
- ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx15("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
1150
+ var SidebarBrand = React11.forwardRef(
1151
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx18("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
997
1152
  );
998
1153
  SidebarBrand.displayName = "SidebarBrand";
999
- var SidebarNav = React10.forwardRef(
1000
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1154
+ var SidebarNav = React11.forwardRef(
1155
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1001
1156
  );
1002
1157
  SidebarNav.displayName = "SidebarNav";
1003
- var SidebarSection = React10.forwardRef(
1004
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1158
+ var SidebarSection = React11.forwardRef(
1159
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1005
1160
  );
1006
1161
  SidebarSection.displayName = "SidebarSection";
1007
- var SidebarNavItem = React10.forwardRef(
1162
+ var SidebarNavItem = React11.forwardRef(
1008
1163
  ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs12(
1009
1164
  "button",
1010
1165
  {
@@ -1015,18 +1170,245 @@ var SidebarNavItem = React10.forwardRef(
1015
1170
  "aria-current": active ? "page" : void 0,
1016
1171
  ...props,
1017
1172
  children: [
1018
- icon && /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1019
- /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-label", children }),
1020
- endAdornment && /* @__PURE__ */ jsx15("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1173
+ icon && /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1174
+ /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-label", children }),
1175
+ endAdornment && /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1021
1176
  ]
1022
1177
  }
1023
1178
  )
1024
1179
  );
1025
1180
  SidebarNavItem.displayName = "SidebarNavItem";
1026
- var SidebarFooter = React10.forwardRef(
1027
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1181
+ var SidebarFooter = React11.forwardRef(
1182
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1028
1183
  );
1029
1184
  SidebarFooter.displayName = "SidebarFooter";
1185
+
1186
+ // src/components/SidePanel/SidePanel.tsx
1187
+ import { createPortal as createPortal2 } from "react-dom";
1188
+ import { Close } from "@mirror-physics/fractal-icons";
1189
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1190
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
1191
+ useEscapeDismiss(80, onClose, open);
1192
+ if (!open) return null;
1193
+ return createPortal2(
1194
+ /* @__PURE__ */ jsx19("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs13(
1195
+ "aside",
1196
+ {
1197
+ className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
1198
+ "aria-modal": "true",
1199
+ "aria-label": typeof title === "string" ? title : "Side panel",
1200
+ role: "dialog",
1201
+ onMouseDown: (event) => event.stopPropagation(),
1202
+ children: [
1203
+ /* @__PURE__ */ jsxs13("header", { className: "fractal-side-panel__header", children: [
1204
+ /* @__PURE__ */ jsxs13("div", { className: "fractal-side-panel__heading", children: [
1205
+ /* @__PURE__ */ jsx19("h2", { className: "fractal-side-panel__title", children: title }),
1206
+ description && /* @__PURE__ */ jsx19("p", { className: "fractal-side-panel__description", children: description })
1207
+ ] }),
1208
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx19(Close, { size: 18 }) })
1209
+ ] }),
1210
+ /* @__PURE__ */ jsx19("div", { className: "fractal-side-panel__body", children }),
1211
+ footer && /* @__PURE__ */ jsx19("footer", { className: "fractal-side-panel__footer", children: footer })
1212
+ ]
1213
+ }
1214
+ ) }),
1215
+ document.body
1216
+ );
1217
+ }
1218
+
1219
+ // src/components/Textarea/Textarea.tsx
1220
+ import * as React12 from "react";
1221
+ import { jsx as jsx20 } from "react/jsx-runtime";
1222
+ var Textarea = React12.forwardRef(
1223
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx20("textarea", { ref, className: cn("fractal-textarea", className), ...props })
1224
+ );
1225
+ Textarea.displayName = "Textarea";
1226
+
1227
+ // src/components/TextButton/TextButton.tsx
1228
+ import * as React13 from "react";
1229
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
1230
+ var TextButton = React13.forwardRef(
1231
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ jsxs14(
1232
+ "button",
1233
+ {
1234
+ ref,
1235
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
1236
+ type: "button",
1237
+ ...props,
1238
+ children: [
1239
+ icon && iconPosition === "start" && /* @__PURE__ */ jsx21("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
1240
+ /* @__PURE__ */ jsx21("span", { children }),
1241
+ icon && iconPosition === "end" && /* @__PURE__ */ jsx21("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
1242
+ ]
1243
+ }
1244
+ )
1245
+ );
1246
+ TextButton.displayName = "TextButton";
1247
+
1248
+ // src/components/FileDropzone/FileDropzone.tsx
1249
+ import * as React14 from "react";
1250
+ import { Close as Close2, FileArrowUp } from "@mirror-physics/fractal-icons";
1251
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1252
+ function FileDropzone({
1253
+ files,
1254
+ onFilesChange,
1255
+ accept,
1256
+ multiple = true,
1257
+ disabled = false,
1258
+ title = "Drop files here or click to browse",
1259
+ description = "Files stay attached to this record.",
1260
+ className
1261
+ }) {
1262
+ const inputId = React14.useId();
1263
+ const inputRef = React14.useRef(null);
1264
+ const [isDragging, setIsDragging] = React14.useState(false);
1265
+ const addFiles = (nextFiles) => {
1266
+ if (disabled) return;
1267
+ const additions = Array.from(nextFiles);
1268
+ if (additions.length === 0) return;
1269
+ onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
1270
+ };
1271
+ return /* @__PURE__ */ jsxs15("div", { className: cn("fractal-file-dropzone", className), children: [
1272
+ /* @__PURE__ */ jsx22(
1273
+ "input",
1274
+ {
1275
+ ref: inputRef,
1276
+ id: inputId,
1277
+ className: "fractal-file-dropzone__input",
1278
+ type: "file",
1279
+ accept,
1280
+ multiple,
1281
+ disabled,
1282
+ onChange: (event) => {
1283
+ if (event.target.files) addFiles(event.target.files);
1284
+ event.target.value = "";
1285
+ }
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsxs15(
1289
+ "button",
1290
+ {
1291
+ className: "fractal-file-dropzone__target",
1292
+ "data-dragging": isDragging || void 0,
1293
+ type: "button",
1294
+ disabled,
1295
+ onClick: () => inputRef.current?.click(),
1296
+ onDragEnter: (event) => {
1297
+ event.preventDefault();
1298
+ if (!disabled) setIsDragging(true);
1299
+ },
1300
+ onDragOver: (event) => event.preventDefault(),
1301
+ onDragLeave: (event) => {
1302
+ event.preventDefault();
1303
+ setIsDragging(false);
1304
+ },
1305
+ onDrop: (event) => {
1306
+ event.preventDefault();
1307
+ setIsDragging(false);
1308
+ addFiles(event.dataTransfer.files);
1309
+ },
1310
+ children: [
1311
+ /* @__PURE__ */ jsx22(FileArrowUp, { "aria-hidden": "true", size: 20 }),
1312
+ /* @__PURE__ */ jsxs15("span", { className: "fractal-file-dropzone__copy", children: [
1313
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__title", children: title }),
1314
+ description && /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__description", children: description })
1315
+ ] })
1316
+ ]
1317
+ }
1318
+ ),
1319
+ files.length > 0 && /* @__PURE__ */ jsx22("ul", { className: "fractal-file-dropzone__files", "aria-label": "Attached files", children: files.map((file, index) => /* @__PURE__ */ jsxs15("li", { className: "fractal-file-dropzone__file", children: [
1320
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
1321
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
1322
+ /* @__PURE__ */ jsx22(
1323
+ "button",
1324
+ {
1325
+ className: "fractal-file-dropzone__remove",
1326
+ type: "button",
1327
+ "aria-label": `Remove ${file.name}`,
1328
+ disabled,
1329
+ onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
1330
+ children: /* @__PURE__ */ jsx22(Close2, { "aria-hidden": "true", size: 14 })
1331
+ }
1332
+ )
1333
+ ] }, `${file.name}-${file.size}-${index}`)) })
1334
+ ] });
1335
+ }
1336
+ function formatFileSize(bytes) {
1337
+ if (bytes < 1024) return `${bytes} B`;
1338
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
1339
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1340
+ }
1341
+
1342
+ // src/components/Pagination/Pagination.tsx
1343
+ import { ArrowLeft, ArrowRight } from "@mirror-physics/fractal-icons";
1344
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1345
+ function Pagination({
1346
+ page,
1347
+ pageSize,
1348
+ totalItems,
1349
+ onPageChange,
1350
+ pageSizeOptions,
1351
+ onPageSizeChange,
1352
+ ariaLabel = "Pagination",
1353
+ className
1354
+ }) {
1355
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
1356
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
1357
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
1358
+ const end = Math.min(currentPage * pageSize, totalItems);
1359
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
1360
+ return /* @__PURE__ */ jsxs16("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
1361
+ /* @__PURE__ */ jsx23("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
1362
+ /* @__PURE__ */ jsxs16("div", { className: "fractal-pagination__controls", children: [
1363
+ canChangePageSize && /* @__PURE__ */ jsx23(
1364
+ Dropdown,
1365
+ {
1366
+ align: "right",
1367
+ className: "fractal-pagination__page-size",
1368
+ direction: "up",
1369
+ items: pageSizeOptions.map((option) => ({
1370
+ value: String(option),
1371
+ description: `${option} per page`
1372
+ })),
1373
+ onSelect: (value) => onPageSizeChange(Number(value)),
1374
+ selectedValue: String(pageSize),
1375
+ size: "sm",
1376
+ triggerLabel: "Results per page",
1377
+ triggerVariant: "tertiary"
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsx23(
1381
+ "button",
1382
+ {
1383
+ className: "fractal-pagination__button",
1384
+ type: "button",
1385
+ "aria-label": "Previous page",
1386
+ title: "Previous page",
1387
+ disabled: currentPage === 1 || totalItems === 0,
1388
+ onClick: () => onPageChange(currentPage - 1),
1389
+ children: /* @__PURE__ */ jsx23(ArrowLeft, { "aria-hidden": "true", size: 16 })
1390
+ }
1391
+ ),
1392
+ /* @__PURE__ */ jsxs16("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
1393
+ currentPage,
1394
+ " of ",
1395
+ totalPages
1396
+ ] }),
1397
+ /* @__PURE__ */ jsx23(
1398
+ "button",
1399
+ {
1400
+ className: "fractal-pagination__button",
1401
+ type: "button",
1402
+ "aria-label": "Next page",
1403
+ title: "Next page",
1404
+ disabled: currentPage === totalPages || totalItems === 0,
1405
+ onClick: () => onPageChange(currentPage + 1),
1406
+ children: /* @__PURE__ */ jsx23(ArrowRight, { "aria-hidden": "true", size: 16 })
1407
+ }
1408
+ )
1409
+ ] })
1410
+ ] });
1411
+ }
1030
1412
  export {
1031
1413
  Alert,
1032
1414
  AlertDescription,
@@ -1038,18 +1420,24 @@ export {
1038
1420
  CardFooter,
1039
1421
  CardHeader,
1040
1422
  CardTitle,
1423
+ Checkbox,
1041
1424
  Chip,
1425
+ ContentSwitcher,
1042
1426
  DataTable,
1043
1427
  Disclosure,
1044
1428
  Dropdown,
1045
1429
  FeatureCard,
1430
+ FileDropzone,
1046
1431
  FolderCard,
1047
1432
  Input,
1048
1433
  Label,
1434
+ LatticeLoader,
1049
1435
  Link,
1050
1436
  Modal,
1437
+ Pagination,
1051
1438
  PromptCard,
1052
1439
  PromptGrid,
1440
+ SidePanel,
1053
1441
  Sidebar,
1054
1442
  SidebarBrand,
1055
1443
  SidebarFooter,
@@ -1059,6 +1447,8 @@ export {
1059
1447
  SidebarSection,
1060
1448
  SortableTable,
1061
1449
  Tabs,
1450
+ TextButton,
1451
+ Textarea,
1062
1452
  Toggle,
1063
1453
  cn,
1064
1454
  useEscapeDismiss