@geomak/ui 6.0.1 → 6.1.0
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.cjs +128 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -14
- package/dist/index.js.map +1 -1
- package/dist/styles.css +23 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -986,15 +986,27 @@ function Tabs({
|
|
|
986
986
|
const current = isControlled ? value : internal;
|
|
987
987
|
const reduced = !!framerMotion.useReducedMotion();
|
|
988
988
|
const indicatorId = React8.useId();
|
|
989
|
-
const
|
|
989
|
+
const select = React8.useCallback((next) => {
|
|
990
990
|
if (!isControlled) setInternal(next);
|
|
991
991
|
onValueChange?.(next);
|
|
992
|
-
};
|
|
993
|
-
|
|
992
|
+
}, [isControlled, onValueChange]);
|
|
993
|
+
const registry = React8.useRef(/* @__PURE__ */ new Map());
|
|
994
|
+
const orderRef = React8.useRef(0);
|
|
995
|
+
const [, bump] = React8.useState(0);
|
|
996
|
+
const registerTab = React8.useCallback((val, meta) => {
|
|
997
|
+
const existing = registry.current.get(val);
|
|
998
|
+
registry.current.set(val, { ...meta, order: existing?.order ?? orderRef.current++ });
|
|
999
|
+
if (!existing) bump((v) => v + 1);
|
|
1000
|
+
}, []);
|
|
1001
|
+
const unregisterTab = React8.useCallback((val) => {
|
|
1002
|
+
if (registry.current.delete(val)) bump((v) => v + 1);
|
|
1003
|
+
}, []);
|
|
1004
|
+
const getTabs = React8.useCallback(() => [...registry.current.entries()].sort((a, b) => a[1].order - b[1].order).map(([val, m]) => ({ value: val, label: m.label, icon: m.icon, disabled: m.disabled })), []);
|
|
1005
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: { value: current, variant, size, orientation, indicatorId, reduced, select, registerTab, unregisterTab, getTabs }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
994
1006
|
TabsPrimitive__namespace.Root,
|
|
995
1007
|
{
|
|
996
1008
|
value: current,
|
|
997
|
-
onValueChange:
|
|
1009
|
+
onValueChange: select,
|
|
998
1010
|
orientation,
|
|
999
1011
|
className: [
|
|
1000
1012
|
"flex min-w-0",
|
|
@@ -1007,7 +1019,7 @@ function Tabs({
|
|
|
1007
1019
|
) });
|
|
1008
1020
|
}
|
|
1009
1021
|
function TabsList({ children, "aria-label": ariaLabel, className = "" }) {
|
|
1010
|
-
const { variant, orientation, reduced } = useTabsContext();
|
|
1022
|
+
const { variant, orientation, reduced, value } = useTabsContext();
|
|
1011
1023
|
const horizontal = orientation === "horizontal";
|
|
1012
1024
|
const scrollRef = React8.useRef(null);
|
|
1013
1025
|
const [edges, setEdges] = React8.useState({ start: false, end: false });
|
|
@@ -1043,6 +1055,14 @@ function TabsList({ children, "aria-label": ariaLabel, className = "" }) {
|
|
|
1043
1055
|
const amount = (horizontal ? el.clientWidth : el.clientHeight) * 0.7 * dir;
|
|
1044
1056
|
el.scrollBy({ [horizontal ? "left" : "top"]: amount, behavior: reduced ? "auto" : "smooth" });
|
|
1045
1057
|
}, [horizontal, reduced]);
|
|
1058
|
+
React8.useLayoutEffect(() => {
|
|
1059
|
+
const el = scrollRef.current;
|
|
1060
|
+
if (!el || !scrollable) return;
|
|
1061
|
+
const active = el.querySelector("[role=tab][data-state=active]");
|
|
1062
|
+
if (active && typeof active.scrollIntoView === "function") {
|
|
1063
|
+
active.scrollIntoView({ block: "nearest", inline: "nearest", behavior: reduced ? "auto" : "smooth" });
|
|
1064
|
+
}
|
|
1065
|
+
}, [value, scrollable, reduced]);
|
|
1046
1066
|
const maskStyle = scrollable && (edges.start || edges.end) ? (() => {
|
|
1047
1067
|
const dir = horizontal ? "to right" : "to bottom";
|
|
1048
1068
|
const a = edges.start ? "transparent, black 36px" : "black";
|
|
@@ -1059,7 +1079,8 @@ function TabsList({ children, "aria-label": ariaLabel, className = "" }) {
|
|
|
1059
1079
|
return `flex ${horizontal ? "flex-row" : "flex-col"} ${align} gap-1 ${hairline}`;
|
|
1060
1080
|
})();
|
|
1061
1081
|
const scrollClass = scrollable ? horizontal ? "overflow-x-auto overflow-y-hidden hidden-scrollbar" : "overflow-y-auto overflow-x-hidden hidden-scrollbar" : "";
|
|
1062
|
-
|
|
1082
|
+
const overflowing = scrollable && (edges.start || edges.end);
|
|
1083
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["relative flex min-w-0 gap-1", horizontal ? "flex-row items-stretch" : "flex-col items-stretch", className].filter(Boolean).join(" "), children: [
|
|
1063
1084
|
scrollable && edges.start && /* @__PURE__ */ jsxRuntime.jsx(Chevron, { side: "start", orientation, onClick: () => nudge(-1) }),
|
|
1064
1085
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1065
1086
|
TabsPrimitive__namespace.List,
|
|
@@ -1071,37 +1092,131 @@ function TabsList({ children, "aria-label": ariaLabel, className = "" }) {
|
|
|
1071
1092
|
children
|
|
1072
1093
|
}
|
|
1073
1094
|
),
|
|
1074
|
-
scrollable && edges.end && /* @__PURE__ */ jsxRuntime.jsx(Chevron, { side: "end", orientation, onClick: () => nudge(1) })
|
|
1095
|
+
scrollable && edges.end && /* @__PURE__ */ jsxRuntime.jsx(Chevron, { side: "end", orientation, onClick: () => nudge(1) }),
|
|
1096
|
+
overflowing && /* @__PURE__ */ jsxRuntime.jsx(OverflowMenu, {})
|
|
1075
1097
|
] });
|
|
1076
1098
|
}
|
|
1077
1099
|
function Chevron({ side, orientation, onClick }) {
|
|
1078
1100
|
const horizontal = orientation === "horizontal";
|
|
1079
1101
|
const rotate = horizontal ? side === "start" ? "rotate-180" : "" : side === "start" ? "-rotate-90" : "rotate-90";
|
|
1080
|
-
const pos = horizontal ? side === "start" ? "left-0 top-1/2 -translate-y-1/2" : "right-0 top-1/2 -translate-y-1/2" : side === "start" ? "top-0 left-1/2 -translate-x-1/2" : "bottom-0 left-1/2 -translate-x-1/2";
|
|
1081
1102
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1082
1103
|
"button",
|
|
1083
1104
|
{
|
|
1084
1105
|
type: "button",
|
|
1085
1106
|
"aria-label": side === "start" ? "Scroll tabs backward" : "Scroll tabs forward",
|
|
1086
1107
|
onClick,
|
|
1087
|
-
className:
|
|
1108
|
+
className: "flex-shrink-0 self-center flex h-7 w-7 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-sm hover:text-foreground hover:bg-surface-raised transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
|
|
1088
1109
|
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: `h-4 w-4 ${rotate}`, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) })
|
|
1089
1110
|
}
|
|
1090
1111
|
);
|
|
1091
1112
|
}
|
|
1113
|
+
function OverflowMenu() {
|
|
1114
|
+
const { getTabs, value, select, orientation } = useTabsContext();
|
|
1115
|
+
const horizontal = orientation === "horizontal";
|
|
1116
|
+
const [open, setOpen] = React8.useState(false);
|
|
1117
|
+
const wrapRef = React8.useRef(null);
|
|
1118
|
+
const timer = React8.useRef(null);
|
|
1119
|
+
const openNow = () => {
|
|
1120
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1121
|
+
setOpen(true);
|
|
1122
|
+
};
|
|
1123
|
+
const closeSoon = () => {
|
|
1124
|
+
timer.current = setTimeout(() => setOpen(false), 160);
|
|
1125
|
+
};
|
|
1126
|
+
React8.useLayoutEffect(() => {
|
|
1127
|
+
if (!open) return;
|
|
1128
|
+
const onDoc = (e) => {
|
|
1129
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
|
|
1130
|
+
};
|
|
1131
|
+
const onKey = (e) => {
|
|
1132
|
+
if (e.key === "Escape") setOpen(false);
|
|
1133
|
+
};
|
|
1134
|
+
document.addEventListener("mousedown", onDoc);
|
|
1135
|
+
document.addEventListener("keydown", onKey);
|
|
1136
|
+
return () => {
|
|
1137
|
+
document.removeEventListener("mousedown", onDoc);
|
|
1138
|
+
document.removeEventListener("keydown", onKey);
|
|
1139
|
+
};
|
|
1140
|
+
}, [open]);
|
|
1141
|
+
const tabs = getTabs();
|
|
1142
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1143
|
+
"div",
|
|
1144
|
+
{
|
|
1145
|
+
ref: wrapRef,
|
|
1146
|
+
className: "relative flex-shrink-0 self-center",
|
|
1147
|
+
onMouseEnter: openNow,
|
|
1148
|
+
onMouseLeave: closeSoon,
|
|
1149
|
+
children: [
|
|
1150
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1151
|
+
"button",
|
|
1152
|
+
{
|
|
1153
|
+
type: "button",
|
|
1154
|
+
"aria-haspopup": "menu",
|
|
1155
|
+
"aria-expanded": open,
|
|
1156
|
+
"aria-label": "Show all tabs",
|
|
1157
|
+
onClick: () => setOpen((o) => !o),
|
|
1158
|
+
className: "flex h-7 w-7 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-sm hover:text-foreground hover:bg-surface-raised data-[expanded=true]:text-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
|
|
1159
|
+
"data-expanded": open,
|
|
1160
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: "h-4 w-4", "aria-hidden": "true", children: [
|
|
1161
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "12", r: "1.6" }),
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "1.6" }),
|
|
1163
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "19", cy: "12", r: "1.6" })
|
|
1164
|
+
] })
|
|
1165
|
+
}
|
|
1166
|
+
),
|
|
1167
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1168
|
+
"div",
|
|
1169
|
+
{
|
|
1170
|
+
role: "menu",
|
|
1171
|
+
onMouseEnter: openNow,
|
|
1172
|
+
onMouseLeave: closeSoon,
|
|
1173
|
+
className: `absolute z-30 ${horizontal ? "right-0 top-full mt-1.5" : "left-full top-0 ml-1.5"} min-w-[184px] max-h-72 overflow-y-auto hidden-scrollbar rounded-lg border border-border bg-surface p-1 shadow-md animate-in fade-in-0 zoom-in-95`,
|
|
1174
|
+
children: tabs.map((t) => {
|
|
1175
|
+
const isActive = t.value === value;
|
|
1176
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1177
|
+
"button",
|
|
1178
|
+
{
|
|
1179
|
+
type: "button",
|
|
1180
|
+
role: "menuitem",
|
|
1181
|
+
disabled: t.disabled,
|
|
1182
|
+
onClick: () => {
|
|
1183
|
+
select(t.value);
|
|
1184
|
+
setOpen(false);
|
|
1185
|
+
},
|
|
1186
|
+
className: `flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors disabled:opacity-40 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${isActive ? "bg-surface-raised text-accent" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"}`,
|
|
1187
|
+
children: [
|
|
1188
|
+
t.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 inline-flex h-4 w-4 items-center justify-center", children: t.icon }),
|
|
1189
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: t.label }),
|
|
1190
|
+
isActive && /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-4 w-4 flex-shrink-0 text-accent", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 10l4.5 4.5L16 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1191
|
+
]
|
|
1192
|
+
},
|
|
1193
|
+
t.value
|
|
1194
|
+
);
|
|
1195
|
+
})
|
|
1196
|
+
}
|
|
1197
|
+
)
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1092
1202
|
function TabsTrigger({ value, icon, badge, closeable, onClose, disabled, className = "", children }) {
|
|
1093
|
-
const { value: active, variant, size, orientation, indicatorId, reduced } = useTabsContext();
|
|
1203
|
+
const { value: active, variant, size, orientation, indicatorId, reduced, registerTab, unregisterTab } = useTabsContext();
|
|
1094
1204
|
const isActive = active === value;
|
|
1095
1205
|
const horizontal = orientation === "horizontal";
|
|
1096
1206
|
const sz = SIZE[size];
|
|
1097
|
-
|
|
1207
|
+
React8.useLayoutEffect(() => {
|
|
1208
|
+
registerTab(value, { label: children, icon, disabled });
|
|
1209
|
+
return () => unregisterTab(value);
|
|
1210
|
+
}, [value, children, icon, disabled, registerTab, unregisterTab]);
|
|
1211
|
+
const layoutCls = horizontal ? "justify-center flex-shrink-0" : "justify-start w-full";
|
|
1212
|
+
const base = "group/trigger relative inline-flex items-center whitespace-nowrap font-medium select-none transition-colors duration-150 focus:outline-none disabled:opacity-40 disabled:cursor-not-allowed";
|
|
1098
1213
|
const variantCls = variant === "segmented" ? `rounded-md ${isActive ? "text-accent" : "text-foreground-secondary hover:text-foreground"} focus-visible:text-accent` : variant === "enclosed" ? `${horizontal ? "rounded-t-md border border-b-0 -mb-px" : "rounded-l-md border border-r-0 -mr-px"} ${isActive ? "bg-surface border-border text-foreground" : "border-transparent text-foreground-secondary hover:text-foreground hover:bg-surface-raised"} focus-visible:text-accent` : `${isActive ? "text-accent" : "text-foreground-secondary hover:text-foreground"} focus-visible:text-accent`;
|
|
1099
1214
|
const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1100
1215
|
TabsPrimitive__namespace.Trigger,
|
|
1101
1216
|
{
|
|
1102
1217
|
value,
|
|
1103
1218
|
disabled,
|
|
1104
|
-
className: [base, sz.trigger, closeable ? "pr-8" : "", variantCls, className].filter(Boolean).join(" "),
|
|
1219
|
+
className: [base, sz.trigger, layoutCls, closeable ? "pr-8" : "", variantCls, className].filter(Boolean).join(" "),
|
|
1105
1220
|
children: [
|
|
1106
1221
|
variant === "segmented" && isActive && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1107
1222
|
framerMotion.motion.span,
|
|
@@ -1134,7 +1249,7 @@ function TabsTrigger({ value, icon, badge, closeable, onClose, disabled, classNa
|
|
|
1134
1249
|
}
|
|
1135
1250
|
);
|
|
1136
1251
|
if (!closeable) return trigger;
|
|
1137
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className:
|
|
1252
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `relative inline-flex items-center ${horizontal ? "flex-shrink-0" : "w-full"}`, children: [
|
|
1138
1253
|
trigger,
|
|
1139
1254
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1140
1255
|
"button",
|