@withpotter/pages-admin 0.13.0-next.72 → 0.13.0-next.74

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.
@@ -0,0 +1,14 @@
1
+ const FOLDABLE_GROUPS = /* @__PURE__ */ new Set([
2
+ "Workspaces",
3
+ "Operations",
4
+ "Inbox",
5
+ "Platform"
6
+ ]);
7
+ const foldsGroup = (label, rows, collapsed) => label !== null && FOLDABLE_GROUPS.has(label) && rows > 1 && !collapsed;
8
+ const isGroupOpen = (choice, label, containsActive) => choice === void 0 ? containsActive : choice === label;
9
+ export {
10
+ FOLDABLE_GROUPS,
11
+ foldsGroup,
12
+ isGroupOpen
13
+ };
14
+ //# sourceMappingURL=nav-fold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/_shell/nav-fold.ts"],"sourcesContent":["/**\n * Which sidebar groups fold, and which one is open. Pure, so it is tested\n * without a browser. The merchant rail's hubs follow the same rules.\n */\n\n/** The group the admin opened: one, none, or no choice yet (`undefined`). */\nexport type GroupChoice = string | null | undefined;\n\n/** The groups that fold; every other group keeps its plain heading. */\nexport const FOLDABLE_GROUPS: ReadonlySet<string> = new Set([\n 'Workspaces',\n 'Operations',\n 'Inbox',\n 'Platform',\n]);\n\n/**\n * A group folds only when it is one of those, has more than one row to hide,\n * and the rail is wide: the icon-only rail has no headings to press.\n */\nexport const foldsGroup = (\n label: string | null,\n rows: number,\n collapsed: boolean,\n): label is string =>\n label !== null && FOLDABLE_GROUPS.has(label) && rows > 1 && !collapsed;\n\n/**\n * With no choice yet the group that owns the current page is open, so the page\n * you are on always has a lit row. Once a group is chosen it is the only one\n * open: opening a second closes the first, and choosing none closes them all.\n */\nexport const isGroupOpen = (\n choice: GroupChoice,\n label: string,\n containsActive: boolean,\n) => (choice === undefined ? containsActive : choice === label);\n"],"mappings":"AASO,MAAM,kBAAuC,oBAAI,IAAI;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMM,MAAM,aAAa,CACxB,OACA,MACA,cAEA,UAAU,QAAQ,gBAAgB,IAAI,KAAK,KAAK,OAAO,KAAK,CAAC;AAOxD,MAAM,cAAc,CACzB,QACA,OACA,mBACI,WAAW,SAAY,iBAAiB,WAAW;","names":[]}
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown } from "lucide-react";
4
+ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
5
+ import { eases } from "@withpotter/lib/motion";
6
+ import { isActive } from "./nav-filter";
7
+ import { foldsGroup, isGroupOpen } from "./nav-fold";
8
+ import { NavRow } from "./nav-row";
9
+ import styles from "./sidebar.module.css";
10
+ function NavGroupView({
11
+ group,
12
+ pathname,
13
+ collapsed,
14
+ canSupport,
15
+ choice,
16
+ onToggle
17
+ }) {
18
+ const reduced = useReducedMotion();
19
+ const { label, items } = group;
20
+ const containsActive = items.some((item) => isActive(item, pathname));
21
+ const rows = items.map((item) => /* @__PURE__ */ jsx(
22
+ NavRow,
23
+ {
24
+ item,
25
+ active: isActive(item, pathname),
26
+ collapsed,
27
+ canSupport
28
+ },
29
+ item.href
30
+ ));
31
+ if (!foldsGroup(label, items.length, collapsed))
32
+ return /* @__PURE__ */ jsxs("div", { className: styles.group, children: [
33
+ label && /* @__PURE__ */ jsx("div", { className: styles.groupLabel, children: label }),
34
+ rows
35
+ ] });
36
+ const open = isGroupOpen(choice, label, containsActive);
37
+ return /* @__PURE__ */ jsxs("div", { className: styles.group, children: [
38
+ /* @__PURE__ */ jsxs(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ className: `${styles.groupLabel} ${styles.groupToggle}`,
43
+ "aria-expanded": open,
44
+ onClick: () => onToggle(label, !open),
45
+ children: [
46
+ /* @__PURE__ */ jsx("span", { children: label }),
47
+ /* @__PURE__ */ jsx(ChevronDown, { size: 14, className: styles.groupChevron, "aria-hidden": true })
48
+ ]
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: open && /* @__PURE__ */ jsx(
52
+ motion.div,
53
+ {
54
+ className: styles.groupRows,
55
+ initial: { height: 0, opacity: 0 },
56
+ animate: { height: "auto", opacity: 1 },
57
+ exit: { height: 0, opacity: 0 },
58
+ transition: reduced ? { duration: 0 } : {
59
+ height: { duration: 0.24, ease: eases.settle },
60
+ opacity: { duration: 0.16, ease: eases.out }
61
+ },
62
+ style: { overflow: "hidden" },
63
+ children: rows
64
+ }
65
+ ) })
66
+ ] });
67
+ }
68
+ export {
69
+ NavGroupView
70
+ };
71
+ //# sourceMappingURL=nav-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/_shell/nav-group.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronDown } from 'lucide-react';\nimport { AnimatePresence, motion, useReducedMotion } from 'motion/react';\nimport { eases } from '@withpotter/lib/motion';\nimport type { NavGroup } from './nav-config';\nimport { isActive } from './nav-filter';\nimport { foldsGroup, isGroupOpen, type GroupChoice } from './nav-fold';\nimport { NavRow } from './nav-row';\nimport styles from './sidebar.module.css';\n\n/**\n * One sidebar group. With a heading and more than one row, the heading is a\n * button that opens and closes the rows, and only one group is open at a time.\n * The group holding the current page is open until another is chosen, and the\n * icon-only rail shows every row.\n */\nexport function NavGroupView({\n group,\n pathname,\n collapsed,\n canSupport,\n choice,\n onToggle,\n}: {\n group: NavGroup;\n pathname: string;\n collapsed: boolean;\n canSupport: boolean;\n choice: GroupChoice;\n onToggle: (label: string, next: boolean) => void;\n}) {\n const reduced = useReducedMotion();\n const { label, items } = group;\n const containsActive = items.some((item) => isActive(item, pathname));\n\n const rows = items.map((item) => (\n <NavRow\n key={item.href}\n item={item}\n active={isActive(item, pathname)}\n collapsed={collapsed}\n canSupport={canSupport}\n />\n ));\n\n if (!foldsGroup(label, items.length, collapsed))\n return (\n <div className={styles.group}>\n {label && <div className={styles.groupLabel}>{label}</div>}\n {rows}\n </div>\n );\n\n const open = isGroupOpen(choice, label, containsActive);\n\n return (\n <div className={styles.group}>\n <button\n type=\"button\"\n className={`${styles.groupLabel} ${styles.groupToggle}`}\n aria-expanded={open}\n onClick={() => onToggle(label, !open)}\n >\n <span>{label}</span>\n <ChevronDown size={14} className={styles.groupChevron} aria-hidden />\n </button>\n {/* The rows grow and collapse rather than blink, as the merchant rail's\n hubs do: height so the groups below move with them, opacity finishing\n early on the way in. Clipped, or the rows spill past the closing box. */}\n <AnimatePresence initial={false}>\n {open && (\n <motion.div\n className={styles.groupRows}\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={\n reduced\n ? { duration: 0 }\n : {\n height: { duration: 0.24, ease: eases.settle },\n opacity: { duration: 0.16, ease: eases.out },\n }\n }\n style={{ overflow: 'hidden' }}\n >\n {rows}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n"],"mappings":";AAqCI,cAWE,YAXF;AAnCJ,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,QAAQ,wBAAwB;AAC1D,SAAS,aAAa;AAEtB,SAAS,gBAAgB;AACzB,SAAS,YAAY,mBAAqC;AAC1D,SAAS,cAAc;AACvB,OAAO,YAAY;AAQZ,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,UAAU,iBAAiB;AACjC,QAAM,EAAE,OAAO,MAAM,IAAI;AACzB,QAAM,iBAAiB,MAAM,KAAK,CAAC,SAAS,SAAS,MAAM,QAAQ,CAAC;AAEpE,QAAM,OAAO,MAAM,IAAI,CAAC,SACtB;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA,QAAQ,SAAS,MAAM,QAAQ;AAAA,MAC/B;AAAA,MACA;AAAA;AAAA,IAJK,KAAK;AAAA,EAKZ,CACD;AAED,MAAI,CAAC,WAAW,OAAO,MAAM,QAAQ,SAAS;AAC5C,WACE,qBAAC,SAAI,WAAW,OAAO,OACpB;AAAA,eAAS,oBAAC,SAAI,WAAW,OAAO,YAAa,iBAAM;AAAA,MACnD;AAAA,OACH;AAGJ,QAAM,OAAO,YAAY,QAAQ,OAAO,cAAc;AAEtD,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAW,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW;AAAA,QACrD,iBAAe;AAAA,QACf,SAAS,MAAM,SAAS,OAAO,CAAC,IAAI;AAAA,QAEpC;AAAA,8BAAC,UAAM,iBAAM;AAAA,UACb,oBAAC,eAAY,MAAM,IAAI,WAAW,OAAO,cAAc,eAAW,MAAC;AAAA;AAAA;AAAA,IACrE;AAAA,IAIA,oBAAC,mBAAgB,SAAS,OACvB,kBACC;AAAA,MAAC,OAAO;AAAA,MAAP;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,SAAS,EAAE,QAAQ,GAAG,SAAS,EAAE;AAAA,QACjC,SAAS,EAAE,QAAQ,QAAQ,SAAS,EAAE;AAAA,QACtC,MAAM,EAAE,QAAQ,GAAG,SAAS,EAAE;AAAA,QAC9B,YACE,UACI,EAAE,UAAU,EAAE,IACd;AAAA,UACE,QAAQ,EAAE,UAAU,MAAM,MAAM,MAAM,OAAO;AAAA,UAC7C,SAAS,EAAE,UAAU,MAAM,MAAM,MAAM,IAAI;AAAA,QAC7C;AAAA,QAEN,OAAO,EAAE,UAAU,SAAS;AAAA,QAE3B;AAAA;AAAA,IACH,GAEJ;AAAA,KACF;AAEJ;","names":[]}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { isActive } from "./nav-filter";
4
- import { NavRow } from "./nav-row";
4
+ import { NavGroupView } from "./nav-group";
5
+ import { useOpenGroup } from "./use-open-group";
5
6
  import { SidebarFooter } from "./sidebar-footer";
6
7
  import { useFluidHover } from "./use-fluid-hover";
7
8
  import { UserMenu } from "./user-menu";
@@ -17,6 +18,7 @@ function Sidebar({
17
18
  onSearch
18
19
  }) {
19
20
  const hover = useFluidHover();
21
+ const { choice, setGroupOpen } = useOpenGroup(pathname);
20
22
  const settings = groups.flatMap((group) => group.items).find((item) => item.href === FOOT_HREF);
21
23
  const navGroups = groups.map((group) => ({
22
24
  ...group,
@@ -44,19 +46,18 @@ function Sidebar({
44
46
  } : void 0
45
47
  }
46
48
  ),
47
- navGroups.map((group, index) => /* @__PURE__ */ jsxs("div", { className: styles.group, children: [
48
- group.label && /* @__PURE__ */ jsx("div", { className: styles.groupLabel, children: group.label }),
49
- group.items.map((item) => /* @__PURE__ */ jsx(
50
- NavRow,
51
- {
52
- item,
53
- active: isActive(item, pathname),
54
- collapsed,
55
- canSupport
56
- },
57
- item.href
58
- ))
59
- ] }, group.label ?? index))
49
+ navGroups.map((group, index) => /* @__PURE__ */ jsx(
50
+ NavGroupView,
51
+ {
52
+ group,
53
+ pathname,
54
+ collapsed,
55
+ canSupport,
56
+ choice,
57
+ onToggle: setGroupOpen
58
+ },
59
+ group.label ?? index
60
+ ))
60
61
  ]
61
62
  }
62
63
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_shell/sidebar.tsx"],"sourcesContent":["'use client';\n\nimport type { NavGroup } from './nav-config';\nimport { isActive } from './nav-filter';\nimport { NavRow } from './nav-row';\nimport { SidebarFooter } from './sidebar-footer';\nimport { useFluidHover } from './use-fluid-hover';\nimport { UserMenu } from './user-menu';\nimport styles from './sidebar.module.css';\n\n/** Settings lives in the pinned foot, beside the theme switch. */\nconst FOOT_HREF = '/admin/settings';\n\nexport function Sidebar({\n groups,\n pathname,\n canSupport,\n collapsed,\n email,\n role,\n onSearch,\n}: {\n groups: NavGroup[];\n pathname: string;\n canSupport: boolean;\n collapsed: boolean;\n email: string;\n role: string;\n onSearch: () => void;\n}) {\n const hover = useFluidHover();\n const settings = groups\n .flatMap((group) => group.items)\n .find((item) => item.href === FOOT_HREF);\n const navGroups = groups\n .map((group) => ({\n ...group,\n items: group.items.filter((item) => item.href !== FOOT_HREF),\n }))\n .filter((group) => group.items.length);\n\n return (\n <aside className={styles.sidebar} data-collapsed={collapsed}>\n <UserMenu email={email} role={role} collapsed={collapsed} />\n\n <nav\n className={styles.nav}\n aria-label=\"Admin\"\n onPointerMove={hover.onPointerMove}\n onPointerLeave={hover.onPointerLeave}\n >\n <span\n aria-hidden\n className={styles.glide}\n data-visible={Boolean(hover.rect)}\n style={\n hover.rect\n ? {\n transform: `translateY(${hover.rect.top}px)`,\n height: hover.rect.height,\n }\n : undefined\n }\n />\n {navGroups.map((group, index) => (\n <div key={group.label ?? index} className={styles.group}>\n {group.label && (\n <div className={styles.groupLabel}>{group.label}</div>\n )}\n {group.items.map((item) => (\n <NavRow\n key={item.href}\n item={item}\n active={isActive(item, pathname)}\n collapsed={collapsed}\n canSupport={canSupport}\n />\n ))}\n </div>\n ))}\n </nav>\n\n <SidebarFooter\n collapsed={collapsed}\n showSettings={Boolean(settings)}\n settingsActive={settings ? isActive(settings, pathname) : false}\n onSearch={onSearch}\n />\n </aside>\n );\n}\n"],"mappings":";AA2CM,cAsBI,YAtBJ;AAxCN,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGnB,MAAM,YAAY;AAEX,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,QAAM,QAAQ,cAAc;AAC5B,QAAM,WAAW,OACd,QAAQ,CAAC,UAAU,MAAM,KAAK,EAC9B,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS;AACzC,QAAM,YAAY,OACf,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS;AAAA,EAC7D,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,MAAM;AAEvC,SACE,qBAAC,WAAM,WAAW,OAAO,SAAS,kBAAgB,WAChD;AAAA,wBAAC,YAAS,OAAc,MAAY,WAAsB;AAAA,IAE1D;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,cAAW;AAAA,QACX,eAAe,MAAM;AAAA,QACrB,gBAAgB,MAAM;AAAA,QAEtB;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAW,OAAO;AAAA,cAClB,gBAAc,QAAQ,MAAM,IAAI;AAAA,cAChC,OACE,MAAM,OACF;AAAA,gBACE,WAAW,cAAc,MAAM,KAAK,GAAG;AAAA,gBACvC,QAAQ,MAAM,KAAK;AAAA,cACrB,IACA;AAAA;AAAA,UAER;AAAA,UACC,UAAU,IAAI,CAAC,OAAO,UACrB,qBAAC,SAA+B,WAAW,OAAO,OAC/C;AAAA,kBAAM,SACL,oBAAC,SAAI,WAAW,OAAO,YAAa,gBAAM,OAAM;AAAA,YAEjD,MAAM,MAAM,IAAI,CAAC,SAChB;AAAA,cAAC;AAAA;AAAA,gBAEC;AAAA,gBACA,QAAQ,SAAS,MAAM,QAAQ;AAAA,gBAC/B;AAAA,gBACA;AAAA;AAAA,cAJK,KAAK;AAAA,YAKZ,CACD;AAAA,eAZO,MAAM,SAAS,KAazB,CACD;AAAA;AAAA;AAAA,IACH;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,cAAc,QAAQ,QAAQ;AAAA,QAC9B,gBAAgB,WAAW,SAAS,UAAU,QAAQ,IAAI;AAAA,QAC1D;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_shell/sidebar.tsx"],"sourcesContent":["'use client';\n\nimport type { NavGroup } from './nav-config';\nimport { isActive } from './nav-filter';\nimport { NavGroupView } from './nav-group';\nimport { useOpenGroup } from './use-open-group';\nimport { SidebarFooter } from './sidebar-footer';\nimport { useFluidHover } from './use-fluid-hover';\nimport { UserMenu } from './user-menu';\nimport styles from './sidebar.module.css';\n\n/** Settings lives in the pinned foot, beside the theme switch. */\nconst FOOT_HREF = '/admin/settings';\n\nexport function Sidebar({\n groups,\n pathname,\n canSupport,\n collapsed,\n email,\n role,\n onSearch,\n}: {\n groups: NavGroup[];\n pathname: string;\n canSupport: boolean;\n collapsed: boolean;\n email: string;\n role: string;\n onSearch: () => void;\n}) {\n const hover = useFluidHover();\n const { choice, setGroupOpen } = useOpenGroup(pathname);\n const settings = groups\n .flatMap((group) => group.items)\n .find((item) => item.href === FOOT_HREF);\n const navGroups = groups\n .map((group) => ({\n ...group,\n items: group.items.filter((item) => item.href !== FOOT_HREF),\n }))\n .filter((group) => group.items.length);\n\n return (\n <aside className={styles.sidebar} data-collapsed={collapsed}>\n <UserMenu email={email} role={role} collapsed={collapsed} />\n\n <nav\n className={styles.nav}\n aria-label=\"Admin\"\n onPointerMove={hover.onPointerMove}\n onPointerLeave={hover.onPointerLeave}\n >\n <span\n aria-hidden\n className={styles.glide}\n data-visible={Boolean(hover.rect)}\n style={\n hover.rect\n ? {\n transform: `translateY(${hover.rect.top}px)`,\n height: hover.rect.height,\n }\n : undefined\n }\n />\n {navGroups.map((group, index) => (\n <NavGroupView\n key={group.label ?? index}\n group={group}\n pathname={pathname}\n collapsed={collapsed}\n canSupport={canSupport}\n choice={choice}\n onToggle={setGroupOpen}\n />\n ))}\n </nav>\n\n <SidebarFooter\n collapsed={collapsed}\n showSettings={Boolean(settings)}\n settingsActive={settings ? isActive(settings, pathname) : false}\n onSearch={onSearch}\n />\n </aside>\n );\n}\n"],"mappings":";AA6CM,cAEA,YAFA;AA1CN,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGnB,MAAM,YAAY;AAEX,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,QAAM,QAAQ,cAAc;AAC5B,QAAM,EAAE,QAAQ,aAAa,IAAI,aAAa,QAAQ;AACtD,QAAM,WAAW,OACd,QAAQ,CAAC,UAAU,MAAM,KAAK,EAC9B,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS;AACzC,QAAM,YAAY,OACf,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS;AAAA,EAC7D,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,MAAM;AAEvC,SACE,qBAAC,WAAM,WAAW,OAAO,SAAS,kBAAgB,WAChD;AAAA,wBAAC,YAAS,OAAc,MAAY,WAAsB;AAAA,IAE1D;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,cAAW;AAAA,QACX,eAAe,MAAM;AAAA,QACrB,gBAAgB,MAAM;AAAA,QAEtB;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAW,OAAO;AAAA,cAClB,gBAAc,QAAQ,MAAM,IAAI;AAAA,cAChC,OACE,MAAM,OACF;AAAA,gBACE,WAAW,cAAc,MAAM,KAAK,GAAG;AAAA,gBACvC,QAAQ,MAAM,KAAK;AAAA,cACrB,IACA;AAAA;AAAA,UAER;AAAA,UACC,UAAU,IAAI,CAAC,OAAO,UACrB;AAAA,YAAC;AAAA;AAAA,cAEC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,UAAU;AAAA;AAAA,YANL,MAAM,SAAS;AAAA,UAOtB,CACD;AAAA;AAAA;AAAA,IACH;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,cAAc,QAAQ,QAAQ;AAAA,QAC9B,gBAAgB,WAAW,SAAS,UAAU,QAAQ,IAAI;AAAA,QAC1D;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
@@ -64,6 +64,67 @@
64
64
  white-space: nowrap;
65
65
  }
66
66
 
67
+ /* A heading that folds its group: the label's look, as a button. */
68
+ .groupToggle {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: space-between;
72
+ width: 100%;
73
+ padding-right: 8px;
74
+ border: 0;
75
+ background: none;
76
+ font-family: inherit;
77
+ text-align: left;
78
+ cursor: pointer;
79
+ }
80
+
81
+ .groupToggle:hover {
82
+ color: var(--text-secondary);
83
+ }
84
+
85
+ .groupToggle:focus-visible {
86
+ outline: 2px solid var(--focus-ring);
87
+ outline-offset: 1px;
88
+ border-radius: 6px;
89
+ }
90
+
91
+ .groupChevron {
92
+ flex: none;
93
+ opacity: 0;
94
+ transition:
95
+ transform var(--adm-fast),
96
+ opacity var(--adm-fast);
97
+ }
98
+
99
+ /* The chevron arrives on hover, as on the merchant rail, and stays while the
100
+ group is open, rotated, to say the rows below belong to this heading. A
101
+ keyboard has no pointer and touch has no hover, so those keep it too. */
102
+ .groupToggle:hover .groupChevron,
103
+ .groupToggle:focus-visible .groupChevron,
104
+ .groupToggle[aria-expanded='true'] .groupChevron {
105
+ opacity: 1;
106
+ }
107
+
108
+ .groupToggle[aria-expanded='true'] .groupChevron {
109
+ transform: rotate(180deg);
110
+ }
111
+
112
+ @media (hover: none) {
113
+ .groupChevron {
114
+ opacity: 1;
115
+ }
116
+ }
117
+
118
+ /* The rows of a folding group, laid out like the group's own. The padding keeps
119
+ a focus ring from being clipped by the closing box; the margin takes it back. */
120
+ .groupRows {
121
+ display: flex;
122
+ flex-direction: column;
123
+ gap: 2px;
124
+ padding: 2px;
125
+ margin: -2px;
126
+ }
127
+
67
128
  .link {
68
129
  position: relative;
69
130
  display: flex;
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ function useOpenGroup(pathname) {
4
+ const [choice, setChoice] = useState(void 0);
5
+ const setGroupOpen = (label, next) => setChoice(next ? label : null);
6
+ const [choiceRoute, setChoiceRoute] = useState(pathname);
7
+ if (choiceRoute !== pathname) {
8
+ setChoiceRoute(pathname);
9
+ setChoice(void 0);
10
+ }
11
+ return { choice, setGroupOpen };
12
+ }
13
+ export {
14
+ useOpenGroup
15
+ };
16
+ //# sourceMappingURL=use-open-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/_shell/use-open-group.ts"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport type { GroupChoice } from './nav-fold';\n\n/**\n * Which sidebar group is showing its rows: ONE, none, or \"not said yet\"\n * (`undefined`, which leaves the group owning the current page open). The\n * merchant rail's useOpenHub, for the admin's groups.\n */\nexport function useOpenGroup(pathname: string) {\n const [choice, setChoice] = useState<GroupChoice>(undefined);\n const setGroupOpen = (label: string, next: boolean) =>\n setChoice(next ? label : null);\n // A new route retires the choice, or arriving from inside the page would\n // leave the rail on the last group pressed, with the current row hidden.\n // Adjusted during render, not in an effect, so no stale frame paints first.\n const [choiceRoute, setChoiceRoute] = useState(pathname);\n\n if (choiceRoute !== pathname) {\n setChoiceRoute(pathname);\n setChoice(undefined);\n }\n\n return { choice, setGroupOpen };\n}\n"],"mappings":";AAEA,SAAS,gBAAgB;AAQlB,SAAS,aAAa,UAAkB;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsB,MAAS;AAC3D,QAAM,eAAe,CAAC,OAAe,SACnC,UAAU,OAAO,QAAQ,IAAI;AAI/B,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,QAAQ;AAEvD,MAAI,gBAAgB,UAAU;AAC5B,mBAAe,QAAQ;AACvB,cAAU,MAAS;AAAA,EACrB;AAEA,SAAO,EAAE,QAAQ,aAAa;AAChC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withpotter/pages-admin",
3
- "version": "0.13.0-next.72",
3
+ "version": "0.13.0-next.74",
4
4
  "description": "Box console platform-admin routes: overview, revenue, accounts, tenants, admins, roles, audit.",
5
5
  "license": "UNLICENSED",
6
6
  "sideEffects": false,
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "@radix-ui/react-popover": "^1.1.23",
33
33
  "@tanstack/react-query": "^5.90.20",
34
- "@withpotter/lib": "^0.13.0-next.72",
35
- "@withpotter/platform-admin": "^0.13.0-next.72",
36
- "@withpotter/ui-kit": "^0.13.0-next.72",
34
+ "@withpotter/lib": "^0.13.0-next.74",
35
+ "@withpotter/platform-admin": "^0.13.0-next.74",
36
+ "@withpotter/ui-kit": "^0.13.0-next.74",
37
37
  "lucide-react": "^0.562.0",
38
38
  "motion": "^12.42.2"
39
39
  },