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

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,8 +851,8 @@ 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,
@@ -817,7 +867,22 @@ function Tabs({
817
867
  );
818
868
  const isControlled = controlledValue !== void 0;
819
869
  const activeId = isControlled ? controlledValue : internalValue;
870
+ const tabListRef = useRef3(null);
820
871
  const tabRefs = useRef3({});
872
+ const [indicator, setIndicator] = useState4(null);
873
+ useLayoutEffect2(() => {
874
+ const updateIndicator = () => {
875
+ const activeTab = tabRefs.current[activeId];
876
+ if (!activeTab) return;
877
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
878
+ };
879
+ updateIndicator();
880
+ const list = tabListRef.current;
881
+ if (!list || typeof ResizeObserver === "undefined") return;
882
+ const observer = new ResizeObserver(updateIndicator);
883
+ observer.observe(list);
884
+ return () => observer.disconnect();
885
+ }, [activeId, items.length]);
821
886
  const selectTab = (id) => {
822
887
  if (isControlled) {
823
888
  onValueChange?.(id);
@@ -827,6 +892,10 @@ function Tabs({
827
892
  }
828
893
  };
829
894
  const focusable = items.filter((i) => !i.disabled);
895
+ const indicatorStyle = indicator ? {
896
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
897
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
898
+ } : void 0;
830
899
  const onKeyDown = (event) => {
831
900
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
832
901
  if (currentIndex === -1) return;
@@ -853,40 +922,51 @@ function Tabs({
853
922
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
854
923
  };
855
924
  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
+ /* @__PURE__ */ jsx14(
926
+ "div",
927
+ {
928
+ ref: tabListRef,
929
+ role: "tablist",
930
+ "aria-label": ariaLabel,
931
+ className: "fractal-tabs-list",
932
+ "data-indicator-ready": indicator ? "true" : void 0,
933
+ style: indicatorStyle,
934
+ children: items.map((item) => {
935
+ const isActive = item.id === activeId;
936
+ const tabId = `${reactId}-tab-${item.id}`;
937
+ const panelId = `${reactId}-panel-${item.id}`;
938
+ return /* @__PURE__ */ jsx14(
939
+ "button",
940
+ {
941
+ ref: (el) => {
942
+ tabRefs.current[item.id] = el;
943
+ },
944
+ id: tabId,
945
+ role: "tab",
946
+ type: "button",
947
+ "aria-selected": isActive,
948
+ "aria-controls": panelId,
949
+ tabIndex: isActive ? 0 : -1,
950
+ disabled: item.disabled,
951
+ className: cn(
952
+ "fractal-tabs-trigger",
953
+ isActive && "fractal-tabs-trigger--active",
954
+ item.disabled && "fractal-tabs-trigger--disabled"
955
+ ),
956
+ onClick: () => !item.disabled && selectTab(item.id),
957
+ onKeyDown,
958
+ children: item.label
959
+ },
960
+ item.id
961
+ );
962
+ })
963
+ }
964
+ ),
885
965
  items.map((item) => {
886
966
  const tabId = `${reactId}-tab-${item.id}`;
887
967
  const panelId = `${reactId}-panel-${item.id}`;
888
968
  const isActive = item.id === activeId;
889
- return /* @__PURE__ */ jsx13(
969
+ return /* @__PURE__ */ jsx14(
890
970
  "div",
891
971
  {
892
972
  id: panelId,
@@ -902,10 +982,55 @@ function Tabs({
902
982
  ] });
903
983
  }
904
984
 
985
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
986
+ import * as React10 from "react";
987
+ import { jsx as jsx15 } from "react/jsx-runtime";
988
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
989
+ const buttonRefs = React10.useRef([]);
990
+ const moveFocus = (index, direction) => {
991
+ let nextIndex = index;
992
+ do {
993
+ nextIndex = (nextIndex + direction + items.length) % items.length;
994
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
995
+ if (!items[nextIndex]?.disabled) {
996
+ buttonRefs.current[nextIndex]?.focus();
997
+ onValueChange(items[nextIndex].value);
998
+ }
999
+ };
1000
+ 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) => {
1001
+ const active = item.value === value;
1002
+ return /* @__PURE__ */ jsx15(
1003
+ "button",
1004
+ {
1005
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
1006
+ type: "button",
1007
+ "aria-pressed": active,
1008
+ disabled: item.disabled,
1009
+ onClick: () => onValueChange(item.value),
1010
+ onKeyDown: (event) => {
1011
+ if (event.key === "ArrowRight") {
1012
+ event.preventDefault();
1013
+ moveFocus(index, 1);
1014
+ }
1015
+ if (event.key === "ArrowLeft") {
1016
+ event.preventDefault();
1017
+ moveFocus(index, -1);
1018
+ }
1019
+ },
1020
+ ref: (element) => {
1021
+ buttonRefs.current[index] = element;
1022
+ },
1023
+ children: item.label
1024
+ },
1025
+ item.value
1026
+ );
1027
+ }) });
1028
+ }
1029
+
905
1030
  // src/components/Disclosure/Disclosure.tsx
906
1031
  import { useId as useId2, useState as useState5 } from "react";
907
1032
  import { ChevronRight, ChevronDown as ChevronDown2 } from "@mirror-physics/fractal-icons";
908
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1033
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
909
1034
  function Disclosure({
910
1035
  title,
911
1036
  meta,
@@ -913,6 +1038,7 @@ function Disclosure({
913
1038
  defaultOpen = false,
914
1039
  open: controlledOpen,
915
1040
  onOpenChange,
1041
+ disabled = false,
916
1042
  className,
917
1043
  variant = "card"
918
1044
  }) {
@@ -923,6 +1049,7 @@ function Disclosure({
923
1049
  const isControlled = controlledOpen !== void 0;
924
1050
  const isOpen = isControlled ? controlledOpen : internalOpen;
925
1051
  const toggle = () => {
1052
+ if (disabled) return;
926
1053
  const next = !isOpen;
927
1054
  if (isControlled) {
928
1055
  onOpenChange?.(next);
@@ -938,6 +1065,7 @@ function Disclosure({
938
1065
  "fractal-disclosure",
939
1066
  variant === "card" && "fractal-disclosure--card",
940
1067
  isOpen && "fractal-disclosure--open",
1068
+ disabled && "fractal-disclosure--disabled",
941
1069
  className
942
1070
  ),
943
1071
  children: [
@@ -949,15 +1077,16 @@ function Disclosure({
949
1077
  className: "fractal-disclosure-trigger",
950
1078
  "aria-expanded": isOpen,
951
1079
  "aria-controls": panelId,
1080
+ disabled,
952
1081
  onClick: toggle,
953
1082
  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 })
1083
+ /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ jsx16(ChevronDown2, { size: 16 }) : /* @__PURE__ */ jsx16(ChevronRight, { size: 16 }) }),
1084
+ /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-title", children: title }),
1085
+ meta && /* @__PURE__ */ jsx16("span", { className: "fractal-disclosure-meta", children: meta })
957
1086
  ]
958
1087
  }
959
1088
  ),
960
- /* @__PURE__ */ jsx14(
1089
+ /* @__PURE__ */ jsx16(
961
1090
  "div",
962
1091
  {
963
1092
  id: panelId,
@@ -965,7 +1094,7 @@ function Disclosure({
965
1094
  "aria-labelledby": headerId,
966
1095
  className: "fractal-disclosure-panel",
967
1096
  hidden: !isOpen,
968
- children: /* @__PURE__ */ jsx14("div", { className: "fractal-disclosure-panel-inner", children })
1097
+ children: /* @__PURE__ */ jsx16("div", { className: "fractal-disclosure-panel-inner", children })
969
1098
  }
970
1099
  )
971
1100
  ]
@@ -973,11 +1102,36 @@ function Disclosure({
973
1102
  );
974
1103
  }
975
1104
 
1105
+ // src/components/LatticeLoader/LatticeLoader.tsx
1106
+ import { jsx as jsx17 } from "react/jsx-runtime";
1107
+ var LATTICE_COUNT = 160;
1108
+ var WAVE_PERIOD = 80;
1109
+ function LatticeLoader({ className, label = "Loading", ...props }) {
1110
+ return /* @__PURE__ */ jsx17(
1111
+ "span",
1112
+ {
1113
+ "aria-label": label,
1114
+ className: cn("fractal-lattice-loader", className),
1115
+ role: "progressbar",
1116
+ ...props,
1117
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ jsx17(
1118
+ "span",
1119
+ {
1120
+ "aria-hidden": "true",
1121
+ className: "fractal-lattice-loader__cell",
1122
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
1123
+ },
1124
+ index
1125
+ ))
1126
+ }
1127
+ );
1128
+ }
1129
+
976
1130
  // 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(
1131
+ import * as React11 from "react";
1132
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1133
+ var Sidebar = React11.forwardRef(
1134
+ ({ className, width, style, ...props }, ref) => /* @__PURE__ */ jsx18(
981
1135
  "aside",
982
1136
  {
983
1137
  ref,
@@ -988,23 +1142,23 @@ var Sidebar = React10.forwardRef(
988
1142
  )
989
1143
  );
990
1144
  Sidebar.displayName = "Sidebar";
991
- var SidebarHeader = React10.forwardRef(
992
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1145
+ var SidebarHeader = React11.forwardRef(
1146
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
993
1147
  );
994
1148
  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 })
1149
+ var SidebarBrand = React11.forwardRef(
1150
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ jsx18("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
997
1151
  );
998
1152
  SidebarBrand.displayName = "SidebarBrand";
999
- var SidebarNav = React10.forwardRef(
1000
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1153
+ var SidebarNav = React11.forwardRef(
1154
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1001
1155
  );
1002
1156
  SidebarNav.displayName = "SidebarNav";
1003
- var SidebarSection = React10.forwardRef(
1004
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1157
+ var SidebarSection = React11.forwardRef(
1158
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1005
1159
  );
1006
1160
  SidebarSection.displayName = "SidebarSection";
1007
- var SidebarNavItem = React10.forwardRef(
1161
+ var SidebarNavItem = React11.forwardRef(
1008
1162
  ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ jsxs12(
1009
1163
  "button",
1010
1164
  {
@@ -1015,18 +1169,245 @@ var SidebarNavItem = React10.forwardRef(
1015
1169
  "aria-current": active ? "page" : void 0,
1016
1170
  ...props,
1017
1171
  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 })
1172
+ icon && /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1173
+ /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-label", children }),
1174
+ endAdornment && /* @__PURE__ */ jsx18("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1021
1175
  ]
1022
1176
  }
1023
1177
  )
1024
1178
  );
1025
1179
  SidebarNavItem.displayName = "SidebarNavItem";
1026
- var SidebarFooter = React10.forwardRef(
1027
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx15("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1180
+ var SidebarFooter = React11.forwardRef(
1181
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx18("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1028
1182
  );
1029
1183
  SidebarFooter.displayName = "SidebarFooter";
1184
+
1185
+ // src/components/SidePanel/SidePanel.tsx
1186
+ import { createPortal as createPortal2 } from "react-dom";
1187
+ import { Close } from "@mirror-physics/fractal-icons";
1188
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1189
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
1190
+ useEscapeDismiss(80, onClose, open);
1191
+ if (!open) return null;
1192
+ return createPortal2(
1193
+ /* @__PURE__ */ jsx19("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ jsxs13(
1194
+ "aside",
1195
+ {
1196
+ className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
1197
+ "aria-modal": "true",
1198
+ "aria-label": typeof title === "string" ? title : "Side panel",
1199
+ role: "dialog",
1200
+ onMouseDown: (event) => event.stopPropagation(),
1201
+ children: [
1202
+ /* @__PURE__ */ jsxs13("header", { className: "fractal-side-panel__header", children: [
1203
+ /* @__PURE__ */ jsxs13("div", { className: "fractal-side-panel__heading", children: [
1204
+ /* @__PURE__ */ jsx19("h2", { className: "fractal-side-panel__title", children: title }),
1205
+ description && /* @__PURE__ */ jsx19("p", { className: "fractal-side-panel__description", children: description })
1206
+ ] }),
1207
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ jsx19(Close, { size: 18 }) })
1208
+ ] }),
1209
+ /* @__PURE__ */ jsx19("div", { className: "fractal-side-panel__body", children }),
1210
+ footer && /* @__PURE__ */ jsx19("footer", { className: "fractal-side-panel__footer", children: footer })
1211
+ ]
1212
+ }
1213
+ ) }),
1214
+ document.body
1215
+ );
1216
+ }
1217
+
1218
+ // src/components/Textarea/Textarea.tsx
1219
+ import * as React12 from "react";
1220
+ import { jsx as jsx20 } from "react/jsx-runtime";
1221
+ var Textarea = React12.forwardRef(
1222
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx20("textarea", { ref, className: cn("fractal-textarea", className), ...props })
1223
+ );
1224
+ Textarea.displayName = "Textarea";
1225
+
1226
+ // src/components/TextButton/TextButton.tsx
1227
+ import * as React13 from "react";
1228
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
1229
+ var TextButton = React13.forwardRef(
1230
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ jsxs14(
1231
+ "button",
1232
+ {
1233
+ ref,
1234
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
1235
+ type: "button",
1236
+ ...props,
1237
+ children: [
1238
+ icon && iconPosition === "start" && /* @__PURE__ */ jsx21("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
1239
+ /* @__PURE__ */ jsx21("span", { children }),
1240
+ icon && iconPosition === "end" && /* @__PURE__ */ jsx21("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
1241
+ ]
1242
+ }
1243
+ )
1244
+ );
1245
+ TextButton.displayName = "TextButton";
1246
+
1247
+ // src/components/FileDropzone/FileDropzone.tsx
1248
+ import * as React14 from "react";
1249
+ import { Close as Close2, FileArrowUp } from "@mirror-physics/fractal-icons";
1250
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1251
+ function FileDropzone({
1252
+ files,
1253
+ onFilesChange,
1254
+ accept,
1255
+ multiple = true,
1256
+ disabled = false,
1257
+ title = "Drop files here or click to browse",
1258
+ description = "Files stay attached to this record.",
1259
+ className
1260
+ }) {
1261
+ const inputId = React14.useId();
1262
+ const inputRef = React14.useRef(null);
1263
+ const [isDragging, setIsDragging] = React14.useState(false);
1264
+ const addFiles = (nextFiles) => {
1265
+ if (disabled) return;
1266
+ const additions = Array.from(nextFiles);
1267
+ if (additions.length === 0) return;
1268
+ onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
1269
+ };
1270
+ return /* @__PURE__ */ jsxs15("div", { className: cn("fractal-file-dropzone", className), children: [
1271
+ /* @__PURE__ */ jsx22(
1272
+ "input",
1273
+ {
1274
+ ref: inputRef,
1275
+ id: inputId,
1276
+ className: "fractal-file-dropzone__input",
1277
+ type: "file",
1278
+ accept,
1279
+ multiple,
1280
+ disabled,
1281
+ onChange: (event) => {
1282
+ if (event.target.files) addFiles(event.target.files);
1283
+ event.target.value = "";
1284
+ }
1285
+ }
1286
+ ),
1287
+ /* @__PURE__ */ jsxs15(
1288
+ "button",
1289
+ {
1290
+ className: "fractal-file-dropzone__target",
1291
+ "data-dragging": isDragging || void 0,
1292
+ type: "button",
1293
+ disabled,
1294
+ onClick: () => inputRef.current?.click(),
1295
+ onDragEnter: (event) => {
1296
+ event.preventDefault();
1297
+ if (!disabled) setIsDragging(true);
1298
+ },
1299
+ onDragOver: (event) => event.preventDefault(),
1300
+ onDragLeave: (event) => {
1301
+ event.preventDefault();
1302
+ setIsDragging(false);
1303
+ },
1304
+ onDrop: (event) => {
1305
+ event.preventDefault();
1306
+ setIsDragging(false);
1307
+ addFiles(event.dataTransfer.files);
1308
+ },
1309
+ children: [
1310
+ /* @__PURE__ */ jsx22(FileArrowUp, { "aria-hidden": "true", size: 20 }),
1311
+ /* @__PURE__ */ jsxs15("span", { className: "fractal-file-dropzone__copy", children: [
1312
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__title", children: title }),
1313
+ description && /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__description", children: description })
1314
+ ] })
1315
+ ]
1316
+ }
1317
+ ),
1318
+ 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: [
1319
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
1320
+ /* @__PURE__ */ jsx22("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
1321
+ /* @__PURE__ */ jsx22(
1322
+ "button",
1323
+ {
1324
+ className: "fractal-file-dropzone__remove",
1325
+ type: "button",
1326
+ "aria-label": `Remove ${file.name}`,
1327
+ disabled,
1328
+ onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
1329
+ children: /* @__PURE__ */ jsx22(Close2, { "aria-hidden": "true", size: 14 })
1330
+ }
1331
+ )
1332
+ ] }, `${file.name}-${file.size}-${index}`)) })
1333
+ ] });
1334
+ }
1335
+ function formatFileSize(bytes) {
1336
+ if (bytes < 1024) return `${bytes} B`;
1337
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
1338
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1339
+ }
1340
+
1341
+ // src/components/Pagination/Pagination.tsx
1342
+ import { ArrowLeft, ArrowRight } from "@mirror-physics/fractal-icons";
1343
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1344
+ function Pagination({
1345
+ page,
1346
+ pageSize,
1347
+ totalItems,
1348
+ onPageChange,
1349
+ pageSizeOptions,
1350
+ onPageSizeChange,
1351
+ ariaLabel = "Pagination",
1352
+ className
1353
+ }) {
1354
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
1355
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
1356
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
1357
+ const end = Math.min(currentPage * pageSize, totalItems);
1358
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
1359
+ return /* @__PURE__ */ jsxs16("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
1360
+ /* @__PURE__ */ jsx23("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
1361
+ /* @__PURE__ */ jsxs16("div", { className: "fractal-pagination__controls", children: [
1362
+ canChangePageSize && /* @__PURE__ */ jsx23(
1363
+ Dropdown,
1364
+ {
1365
+ align: "right",
1366
+ className: "fractal-pagination__page-size",
1367
+ direction: "up",
1368
+ items: pageSizeOptions.map((option) => ({
1369
+ value: String(option),
1370
+ description: `${option} per page`
1371
+ })),
1372
+ onSelect: (value) => onPageSizeChange(Number(value)),
1373
+ selectedValue: String(pageSize),
1374
+ size: "sm",
1375
+ triggerLabel: "Results per page",
1376
+ triggerVariant: "tertiary"
1377
+ }
1378
+ ),
1379
+ /* @__PURE__ */ jsx23(
1380
+ "button",
1381
+ {
1382
+ className: "fractal-pagination__button",
1383
+ type: "button",
1384
+ "aria-label": "Previous page",
1385
+ title: "Previous page",
1386
+ disabled: currentPage === 1 || totalItems === 0,
1387
+ onClick: () => onPageChange(currentPage - 1),
1388
+ children: /* @__PURE__ */ jsx23(ArrowLeft, { "aria-hidden": "true", size: 16 })
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsxs16("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
1392
+ currentPage,
1393
+ " of ",
1394
+ totalPages
1395
+ ] }),
1396
+ /* @__PURE__ */ jsx23(
1397
+ "button",
1398
+ {
1399
+ className: "fractal-pagination__button",
1400
+ type: "button",
1401
+ "aria-label": "Next page",
1402
+ title: "Next page",
1403
+ disabled: currentPage === totalPages || totalItems === 0,
1404
+ onClick: () => onPageChange(currentPage + 1),
1405
+ children: /* @__PURE__ */ jsx23(ArrowRight, { "aria-hidden": "true", size: 16 })
1406
+ }
1407
+ )
1408
+ ] })
1409
+ ] });
1410
+ }
1030
1411
  export {
1031
1412
  Alert,
1032
1413
  AlertDescription,
@@ -1038,18 +1419,24 @@ export {
1038
1419
  CardFooter,
1039
1420
  CardHeader,
1040
1421
  CardTitle,
1422
+ Checkbox,
1041
1423
  Chip,
1424
+ ContentSwitcher,
1042
1425
  DataTable,
1043
1426
  Disclosure,
1044
1427
  Dropdown,
1045
1428
  FeatureCard,
1429
+ FileDropzone,
1046
1430
  FolderCard,
1047
1431
  Input,
1048
1432
  Label,
1433
+ LatticeLoader,
1049
1434
  Link,
1050
1435
  Modal,
1436
+ Pagination,
1051
1437
  PromptCard,
1052
1438
  PromptGrid,
1439
+ SidePanel,
1053
1440
  Sidebar,
1054
1441
  SidebarBrand,
1055
1442
  SidebarFooter,
@@ -1059,6 +1446,8 @@ export {
1059
1446
  SidebarSection,
1060
1447
  SortableTable,
1061
1448
  Tabs,
1449
+ TextButton,
1450
+ Textarea,
1062
1451
  Toggle,
1063
1452
  cn,
1064
1453
  useEscapeDismiss