@geomak/ui 7.2.1 → 7.3.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.d.cts CHANGED
@@ -2647,12 +2647,20 @@ declare function TopBar({ brand, center, actions, height, className, }: TopBarPr
2647
2647
  /** ─────────────────── types ─────────────────── */
2648
2648
  interface SidebarItem {
2649
2649
  key: string;
2650
- icon: react__default.ReactNode;
2650
+ /** Leading icon. Optional for nested sub-items (they show a dot instead). */
2651
+ icon?: react__default.ReactNode;
2651
2652
  label: string;
2652
2653
  isActive?: boolean;
2653
2654
  onClick?: () => void;
2654
2655
  /** Numeric badge shown on the icon */
2655
2656
  badge?: number;
2657
+ /**
2658
+ * Nested sub-items. When present (and the sidebar is expanded), the item
2659
+ * becomes an expandable group with a chevron; clicking toggles the children.
2660
+ */
2661
+ items?: SidebarItem[];
2662
+ /** Start the sub-menu expanded. Defaults to open when a descendant is active. */
2663
+ defaultOpen?: boolean;
2656
2664
  }
2657
2665
  interface SidebarSection {
2658
2666
  key: string;
package/dist/index.d.ts CHANGED
@@ -2647,12 +2647,20 @@ declare function TopBar({ brand, center, actions, height, className, }: TopBarPr
2647
2647
  /** ─────────────────── types ─────────────────── */
2648
2648
  interface SidebarItem {
2649
2649
  key: string;
2650
- icon: react__default.ReactNode;
2650
+ /** Leading icon. Optional for nested sub-items (they show a dot instead). */
2651
+ icon?: react__default.ReactNode;
2651
2652
  label: string;
2652
2653
  isActive?: boolean;
2653
2654
  onClick?: () => void;
2654
2655
  /** Numeric badge shown on the icon */
2655
2656
  badge?: number;
2657
+ /**
2658
+ * Nested sub-items. When present (and the sidebar is expanded), the item
2659
+ * becomes an expandable group with a chevron; clicking toggles the children.
2660
+ */
2661
+ items?: SidebarItem[];
2662
+ /** Start the sub-menu expanded. Defaults to open when a descendant is active. */
2663
+ defaultOpen?: boolean;
2656
2664
  }
2657
2665
  interface SidebarSection {
2658
2666
  key: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { icons_default as Icon, createIcon } from './chunk-KAFJJO5O.js';
2
2
  import { colors_default } from './chunk-I2P4JJDB.js';
3
3
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-I2P4JJDB.js';
4
- import React29, { createContext, useState, useEffect, useMemo, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
4
+ import React30, { createContext, useState, useEffect, useMemo, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
@@ -555,7 +555,7 @@ var SIZE_CLASSES = {
555
555
  md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
556
556
  lg: "h-11 px-5 text-sm gap-2 rounded-xl"
557
557
  };
558
- var Button = React29.forwardRef(function Button2({
558
+ var Button = React30.forwardRef(function Button2({
559
559
  content,
560
560
  variant = "primary",
561
561
  size = "md",
@@ -663,7 +663,7 @@ function MenuButton({
663
663
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
664
664
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
665
665
  ].join(" "),
666
- children: items.map((item) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
666
+ children: items.map((item) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
667
667
  item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenu.Separator, { className: "my-1 h-px bg-border" }),
668
668
  /* @__PURE__ */ jsxs(
669
669
  DropdownMenu.Item,
@@ -1762,7 +1762,7 @@ function Kbd({
1762
1762
  style
1763
1763
  }) {
1764
1764
  if (keys && keys.length > 0) {
1765
- return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
1765
+ return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
1766
1766
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-xs select-none", children: separator }),
1767
1767
  /* @__PURE__ */ jsx("kbd", { className: [cap, SIZE3[size]].join(" "), children: k })
1768
1768
  ] }, `${k}-${i}`)) });
@@ -1854,7 +1854,7 @@ function FlatCarousel({
1854
1854
  style
1855
1855
  }) {
1856
1856
  const scrollerRef = useRef(null);
1857
- const slides = React29.Children.toArray(children);
1857
+ const slides = React30.Children.toArray(children);
1858
1858
  const [active, setActive] = useState(0);
1859
1859
  const [atStart, setAtStart] = useState(true);
1860
1860
  const [atEnd, setAtEnd] = useState(false);
@@ -1909,7 +1909,7 @@ function RotatingCarousel({
1909
1909
  className = "",
1910
1910
  style
1911
1911
  }) {
1912
- const slides = React29.Children.toArray(children);
1912
+ const slides = React30.Children.toArray(children);
1913
1913
  const count = slides.length;
1914
1914
  const [active, setActive] = useState(0);
1915
1915
  const reduced = useReducedMotion();
@@ -5131,7 +5131,7 @@ function Wizard({
5131
5131
  ] });
5132
5132
  }
5133
5133
  var SearchIcon = /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) });
5134
- var SearchInput = React29.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5134
+ var SearchInput = React30.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5135
5135
  return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
5136
5136
  "div",
5137
5137
  {
@@ -5429,7 +5429,7 @@ function TableBody({
5429
5429
  return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
5430
5430
  const rowKey = getRowKey(row, i);
5431
5431
  const isExpanded = expanded.has(rowKey);
5432
- return /* @__PURE__ */ jsxs(React29.Fragment, { children: [
5432
+ return /* @__PURE__ */ jsxs(React30.Fragment, { children: [
5433
5433
  /* @__PURE__ */ jsxs(
5434
5434
  "tr",
5435
5435
  {
@@ -5478,23 +5478,12 @@ function Pagination({
5478
5478
  onPageChange,
5479
5479
  maxPage,
5480
5480
  options,
5481
- onPerPageChange,
5482
- serverSide = false
5481
+ perPage,
5482
+ onPerPageChange
5483
5483
  }) {
5484
5484
  const picker = options.pickerOptions ?? DEFAULT_PICKER;
5485
- const matchedOption = picker.find(
5486
- (o) => o.label === options.perPage || o.value === options.perPage
5487
- );
5488
- const [perPageKey, setPerPageKey] = useState(() => matchedOption?.key ?? picker[0]?.key);
5489
- const displayPerPageKey = serverSide ? matchedOption?.key ?? perPageKey : perPageKey;
5490
- useEffect(() => {
5491
- if (serverSide && options.perPage != null) {
5492
- const next = picker.find((o) => o.label === options.perPage || o.value === options.perPage);
5493
- if (next) setPerPageKey(next.key);
5494
- }
5495
- }, [serverSide, options.perPage, picker]);
5496
- const currentOpt = picker.find((o) => o.key === displayPerPageKey);
5497
- const currentPerPageLabel = currentOpt?.label ?? currentOpt?.value ?? options.perPage ?? "";
5485
+ const currentOpt = picker.find((o) => o.value === perPage || o.label === perPage);
5486
+ const currentPerPageLabel = currentOpt?.label ?? currentOpt?.value ?? perPage ?? "";
5498
5487
  const FOCUS = "focus-visible:!ring-0 focus-visible:!border-accent";
5499
5488
  const navBtn = (icon, disabled, onClick, title) => /* @__PURE__ */ jsx(Button_default, { variant: "outline", size: "sm", disabled, onClick, icon, className: `w-7 !px-0 ${FOCUS}`, "aria-label": title, title });
5500
5489
  const chevronRight = /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 6l6 6-6 6" }) });
@@ -5513,10 +5502,7 @@ function Pagination({
5513
5502
  items: picker.map((o) => ({
5514
5503
  key: o.key,
5515
5504
  label: String(o.label ?? o.value ?? o.key),
5516
- onSelect: () => {
5517
- if (!serverSide) setPerPageKey(o.key);
5518
- onPerPageChange(o.label ?? o.value ?? o.key);
5519
- }
5505
+ onSelect: () => onPerPageChange(o.value ?? o.label ?? o.key)
5520
5506
  }))
5521
5507
  }
5522
5508
  )
@@ -5656,9 +5642,9 @@ function Table({
5656
5642
  activePage,
5657
5643
  onPageChange: handlePageChange,
5658
5644
  maxPage: MAX_PAGE,
5645
+ perPage,
5659
5646
  onPerPageChange: onPaginationChange,
5660
- options: pagination,
5661
- serverSide: isServerSide
5647
+ options: pagination
5662
5648
  }
5663
5649
  );
5664
5650
  return /* @__PURE__ */ jsxs("div", { className: `w-full h-max rounded-lg ${className}`.trim(), style, children: [
@@ -5868,13 +5854,22 @@ function TopBar({
5868
5854
  }
5869
5855
  function NavItem({
5870
5856
  item,
5871
- isExpanded
5857
+ isExpanded,
5858
+ depth = 0
5872
5859
  }) {
5860
+ const hasChildren = !!(item.items && item.items.length);
5861
+ const [open, setOpen] = useState(item.defaultOpen ?? (hasChildren && !!item.items?.some((c) => c.isActive)));
5862
+ const handleClick = () => {
5863
+ if (hasChildren && isExpanded) setOpen((o) => !o);
5864
+ item.onClick?.();
5865
+ };
5873
5866
  const btn = /* @__PURE__ */ jsxs(
5874
5867
  "button",
5875
5868
  {
5876
5869
  type: "button",
5877
- onClick: item.onClick,
5870
+ onClick: handleClick,
5871
+ "aria-expanded": hasChildren && isExpanded ? open : void 0,
5872
+ style: isExpanded && depth > 0 ? { paddingLeft: 10 + depth * 18 } : void 0,
5878
5873
  className: [
5879
5874
  "group relative flex w-full items-center gap-2.5 rounded-md",
5880
5875
  "px-2.5 py-2 transition-colors duration-100",
@@ -5883,24 +5878,44 @@ function NavItem({
5883
5878
  ].join(" "),
5884
5879
  children: [
5885
5880
  /* @__PURE__ */ jsxs("span", { className: "relative flex h-5 w-5 flex-shrink-0 items-center justify-center", children: [
5886
- item.icon,
5881
+ item.icon ?? (depth > 0 ? /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-50" }) : null),
5887
5882
  item.badge !== void 0 && item.badge > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -right-1 -top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-status-error text-[9px] font-bold text-white leading-none", children: item.badge > 99 ? "99+" : item.badge })
5888
5883
  ] }),
5889
- isExpanded && /* @__PURE__ */ jsx(
5890
- motion.span,
5884
+ isExpanded && /* @__PURE__ */ jsx(motion.span, { initial: false, animate: { opacity: 1 }, className: "flex-1 truncate text-left text-sm font-medium", children: item.label }),
5885
+ isExpanded && hasChildren && /* @__PURE__ */ jsx(
5886
+ "svg",
5891
5887
  {
5892
- initial: false,
5893
- animate: { opacity: 1 },
5894
- className: "truncate text-sm font-medium",
5895
- children: item.label
5888
+ viewBox: "0 0 24 24",
5889
+ fill: "none",
5890
+ stroke: "currentColor",
5891
+ strokeWidth: 2,
5892
+ "aria-hidden": "true",
5893
+ className: `h-4 w-4 flex-shrink-0 transition-transform duration-200 ${open ? "rotate-180" : ""}`,
5894
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m6 9 6 6 6-6" })
5896
5895
  }
5897
5896
  ),
5898
- item.isActive && /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-0 w-[3px] rounded-r-full bg-accent" })
5897
+ item.isActive && depth === 0 && /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-0 w-[3px] rounded-r-full bg-accent" })
5899
5898
  ]
5900
5899
  }
5901
5900
  );
5902
- if (isExpanded) return btn;
5903
- return /* @__PURE__ */ jsx(Tooltip, { title: item.label, placement: "right", delayDuration: 200, children: btn });
5901
+ if (!isExpanded) {
5902
+ return /* @__PURE__ */ jsx(Tooltip, { title: item.label, placement: "right", delayDuration: 200, children: btn });
5903
+ }
5904
+ if (!hasChildren) return btn;
5905
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5906
+ btn,
5907
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: open && /* @__PURE__ */ jsx(
5908
+ motion.div,
5909
+ {
5910
+ initial: { height: 0, opacity: 0 },
5911
+ animate: { height: "auto", opacity: 1 },
5912
+ exit: { height: 0, opacity: 0 },
5913
+ transition: { duration: 0.2, ease: [0.16, 1, 0.3, 1] },
5914
+ style: { overflow: "hidden" },
5915
+ children: /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex flex-col gap-0.5", children: item.items.map((child) => /* @__PURE__ */ jsx(NavItem, { item: child, isExpanded, depth: depth + 1 }, child.key)) })
5916
+ }
5917
+ ) })
5918
+ ] });
5904
5919
  }
5905
5920
  function Sidebar({
5906
5921
  sections,
@@ -6095,8 +6110,8 @@ function MegaMenuLink({ href, icon, description, active, onClick, children, clas
6095
6110
  function MegaMenuFeatured({ children, className = "" }) {
6096
6111
  return /* @__PURE__ */ jsx("div", { className: cx("min-w-0 rounded-lg bg-surface-raised border border-border p-4 flex flex-col", className), children });
6097
6112
  }
6098
- var elementsOfType = (children, type) => React29.Children.toArray(children).filter(
6099
- (c) => React29.isValidElement(c) && c.type === type
6113
+ var elementsOfType = (children, type) => React30.Children.toArray(children).filter(
6114
+ (c) => React30.isValidElement(c) && c.type === type
6100
6115
  );
6101
6116
  var MOBILE_CHEVRON = /* @__PURE__ */ jsx(
6102
6117
  "svg",
@@ -6133,9 +6148,9 @@ function MobileLinkRow({ link, onNavigate }) {
6133
6148
  );
6134
6149
  }
6135
6150
  function MobilePanel({ panel, onNavigate }) {
6136
- const nodes = React29.Children.toArray(panel.props.children);
6151
+ const nodes = React30.Children.toArray(panel.props.children);
6137
6152
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 px-2 pb-3 pt-1", children: nodes.map((node, i) => {
6138
- if (!React29.isValidElement(node)) return null;
6153
+ if (!React30.isValidElement(node)) return null;
6139
6154
  const el = node;
6140
6155
  if (el.type === MegaMenuSection) {
6141
6156
  const { title, children } = el.props;
@@ -6544,7 +6559,7 @@ function ThemeProvider({
6544
6559
  className = "",
6545
6560
  style
6546
6561
  }) {
6547
- const id = React29.useId().replace(/:/g, "");
6562
+ const id = React30.useId().replace(/:/g, "");
6548
6563
  const scopeClass = `geo-th-${id}`;
6549
6564
  const divRef = useRef(null);
6550
6565
  useEffect(() => {
@@ -8848,7 +8863,7 @@ function OtpInput({
8848
8863
  emit(valid.join(""));
8849
8864
  focusBox(valid.length);
8850
8865
  };
8851
- return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
8866
+ return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
8852
8867
  /* @__PURE__ */ jsx(
8853
8868
  "input",
8854
8869
  {
@@ -9904,7 +9919,7 @@ function Blog({
9904
9919
  post.tag != null && !post.image && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Badge, { tone: "accent", variant: "soft", size: "sm", children: post.tag }) }),
9905
9920
  /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold leading-snug text-foreground transition-colors group-hover:text-accent", children: post.title }),
9906
9921
  post.excerpt != null && /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-sm leading-relaxed text-foreground-secondary", children: post.excerpt }),
9907
- meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
9922
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
9908
9923
  j > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
9909
9924
  /* @__PURE__ */ jsx("span", { children: m })
9910
9925
  ] }, j)) })