@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.59
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/admin/_money/cash-postings.js +3 -20
- package/dist/admin/_money/cash-postings.js.map +1 -1
- package/dist/admin/_money/cash.module.css +0 -10
- package/dist/admin/_money/finder-format.js +0 -1
- package/dist/admin/_money/finder-format.js.map +1 -1
- package/dist/admin/_money/format.js +0 -1
- package/dist/admin/_money/format.js.map +1 -1
- package/dist/admin/_overview/activation-panel.js +6 -6
- package/dist/admin/_overview/activation-panel.js.map +1 -1
- package/dist/admin/_overview/attention-groups.js +8 -8
- package/dist/admin/_overview/attention-groups.js.map +1 -1
- package/dist/admin/_overview/attention-panel.js +33 -37
- package/dist/admin/_overview/attention-panel.js.map +1 -1
- package/dist/admin/_overview/attention.module.css +3 -0
- package/dist/admin/_overview/control-room.js +13 -41
- package/dist/admin/_overview/control-room.js.map +1 -1
- package/dist/admin/_overview/kpi-row.js +7 -7
- package/dist/admin/_overview/kpi-row.js.map +1 -1
- package/dist/admin/_overview/leaders-panel.js +1 -1
- package/dist/admin/_overview/leaders-panel.js.map +1 -1
- package/dist/admin/_overview/overdue-outstanding.js +1 -2
- package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
- package/dist/admin/_overview/portfolio-panel.js +5 -5
- package/dist/admin/_overview/portfolio-panel.js.map +1 -1
- package/dist/admin/_overview/position-panel.js +12 -8
- package/dist/admin/_overview/position-panel.js.map +1 -1
- package/dist/admin/_pos/pos-figures.js +5 -18
- package/dist/admin/_pos/pos-figures.js.map +1 -1
- package/dist/admin/_pos/pos-rules.js +0 -2
- package/dist/admin/_pos/pos-rules.js.map +1 -1
- package/dist/admin/_pos/pos-words.js +1 -6
- package/dist/admin/_pos/pos-words.js.map +1 -1
- package/dist/admin/_pos/use-pos.js +1 -70
- package/dist/admin/_pos/use-pos.js.map +1 -1
- package/dist/admin/_shell/nav-config.js +3 -3
- package/dist/admin/_shell/nav-config.js.map +1 -1
- package/dist/admin/_shell/nav-filter.js +2 -4
- package/dist/admin/_shell/nav-filter.js.map +1 -1
- package/dist/admin/_shell/nav-row.js +2 -5
- package/dist/admin/_shell/nav-row.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.js +0 -20
- package/dist/admin/_shell/sidebar-footer.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.module.css +0 -17
- package/dist/admin/_shell/sidebar.js +15 -25
- package/dist/admin/_shell/sidebar.js.map +1 -1
- package/dist/admin/_shell/sidebar.module.css +0 -69
- package/dist/admin/_shell/use-fluid-hover.js +6 -37
- package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
- package/dist/admin/_ui/admin.css +0 -7
- package/dist/admin/_ui/data-table.js +1 -1
- package/dist/admin/_ui/data-table.js.map +1 -1
- package/dist/admin/_ui/list-card.js +1 -1
- package/dist/admin/_ui/list-card.js.map +1 -1
- package/dist/admin/_ui/list-card.module.css +16 -2
- package/dist/admin/_ui/money-view.js +0 -1
- package/dist/admin/_ui/money-view.js.map +1 -1
- package/dist/admin/_ui/page-header.js +4 -31
- package/dist/admin/_ui/page-header.js.map +1 -1
- package/dist/admin/_ui/panel.module.css +10 -0
- package/dist/admin/_ui/stat-card.js +2 -11
- package/dist/admin/_ui/stat-card.js.map +1 -1
- package/dist/admin/_ui/trend.js +2 -2
- package/dist/admin/_ui/trend.js.map +1 -1
- package/dist/admin/accounts/account-panel.js +6 -49
- package/dist/admin/accounts/account-panel.js.map +1 -1
- package/dist/admin/accounts/account-panel.module.css +0 -73
- package/dist/admin/accounts/account-row.js +2 -11
- package/dist/admin/accounts/account-row.js.map +1 -1
- package/dist/admin/accounts/account-vitals.js +13 -15
- package/dist/admin/accounts/account-vitals.js.map +1 -1
- package/dist/admin/accounts/accounts-table.js +8 -39
- package/dist/admin/accounts/accounts-table.js.map +1 -1
- package/dist/admin/accounts/accounts-workspace.js +2 -2
- package/dist/admin/accounts/accounts-workspace.js.map +1 -1
- package/dist/admin/accounts/owner-session.js +6 -0
- package/dist/admin/accounts/owner-session.js.map +1 -1
- package/dist/admin/accounts/rows.module.css +0 -17
- package/dist/admin/audit/status-pill.js +0 -6
- package/dist/admin/audit/status-pill.js.map +1 -1
- package/dist/admin/audit/status-pill.module.css +0 -4
- package/dist/admin/commerce/commerce-overview.js +2 -2
- package/dist/admin/commerce/commerce-overview.js.map +1 -1
- package/dist/admin/commerce/merchant-meta.js +0 -1
- package/dist/admin/commerce/merchant-meta.js.map +1 -1
- package/dist/admin/commerce/merchant-view.js +2 -2
- package/dist/admin/commerce/merchant-view.js.map +1 -1
- package/dist/admin/feedback/prompt-operations.module.css +2 -0
- package/dist/admin/feedback/prompt-preview.module.css +2 -0
- package/dist/admin/hooks.js +1 -57
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/layout.js +2 -8
- package/dist/admin/layout.js.map +1 -1
- package/dist/admin/page.js +8 -28
- package/dist/admin/page.js.map +1 -1
- package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
- package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
- package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
- package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-table.js +7 -6
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
- package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
- package/dist/admin/pos/pos-workspace.js +2 -6
- package/dist/admin/pos/pos-workspace.js.map +1 -1
- package/dist/admin/pos/shifts-tab.js +9 -16
- package/dist/admin/pos/shifts-tab.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +6 -6
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/health-panel.js +21 -22
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/plan-mix-panel.js +3 -3
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/quality-panel.js +19 -19
- package/dist/admin/revenue/quality-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +50 -111
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +46 -39
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-retention.js +13 -17
- package/dist/admin/revenue/revenue-retention.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +6 -53
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/revenue/subscriptions-panel.js +14 -14
- package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
- package/dist/admin/revenue/top-tenants-panel.js +4 -4
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/dist/admin/settings/account-tab.js +25 -1
- package/dist/admin/settings/account-tab.js.map +1 -1
- package/dist/admin/settings/general-tab.js +2 -2
- package/dist/admin/settings/general-tab.js.map +1 -1
- package/dist/admin/settings/system-status-card.js +30 -0
- package/dist/admin/settings/system-status-card.js.map +1 -0
- package/dist/admin/support/queue-card.module.css +4 -1
- package/dist/admin/tenants/[id]/commerce/format.js +1 -2
- package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
- package/dist/admin/tenants/[id]/page.js +10 -37
- package/dist/admin/tenants/[id]/page.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
- package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
- package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
- package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
- package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
- package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
- package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
- package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
- package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
- package/dist/admin/tenants/tenant-list-row.js +1 -2
- package/dist/admin/tenants/tenant-list-row.js.map +1 -1
- package/dist/admin/tenants/tenants-table.js +2 -3
- package/dist/admin/tenants/tenants-table.js.map +1 -1
- package/dist/admin/tenants/tenants-workspace.js +2 -2
- package/dist/admin/tenants/tenants-workspace.js.map +1 -1
- package/dist/admin/verification/verification-page.js +3 -19
- package/dist/admin/verification/verification-page.js.map +1 -1
- package/dist/admin/verification/verification.module.css +0 -10
- package/dist/admin/verification/waiting-case-actions.js +68 -0
- package/dist/admin/verification/waiting-case-actions.js.map +1 -0
- package/dist/admin/verification/waiting-cases.js +48 -66
- package/dist/admin/verification/waiting-cases.js.map +1 -1
- package/package.json +5 -5
- package/dist/admin/_pos/olive-balance.js +0 -40
- package/dist/admin/_pos/olive-balance.js.map +0 -1
- package/dist/admin/_pos/olive-state.js +0 -23
- package/dist/admin/_pos/olive-state.js.map +0 -1
- package/dist/admin/_pos/payments-model.js +0 -55
- package/dist/admin/_pos/payments-model.js.map +0 -1
- package/dist/admin/_pos/tenant-operators.js +0 -63
- package/dist/admin/_pos/tenant-operators.js.map +0 -1
- package/dist/admin/_pos/tenant-outlets.js +0 -77
- package/dist/admin/_pos/tenant-outlets.js.map +0 -1
- package/dist/admin/_pos/tenant-pos-figures.js +0 -82
- package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
- package/dist/admin/_pos/tenant-requests.js +0 -64
- package/dist/admin/_pos/tenant-requests.js.map +0 -1
- package/dist/admin/_pos/tenant-transactions.js +0 -145
- package/dist/admin/_pos/tenant-transactions.js.map +0 -1
- package/dist/admin/_shell/nav-fold.js +0 -14
- package/dist/admin/_shell/nav-fold.js.map +0 -1
- package/dist/admin/_shell/nav-group.js +0 -78
- package/dist/admin/_shell/nav-group.js.map +0 -1
- package/dist/admin/_shell/use-open-groups.js +0 -64
- package/dist/admin/_shell/use-open-groups.js.map +0 -1
- package/dist/admin/_ui/page-header.module.css +0 -45
- package/dist/admin/accounts/account-sessions.js +0 -70
- package/dist/admin/accounts/account-sessions.js.map +0 -1
- package/dist/admin/accounts/account-sessions.module.css +0 -55
- package/dist/admin/accounts/account-stage-filter.js +0 -29
- package/dist/admin/accounts/account-stage-filter.js.map +0 -1
- package/dist/admin/accounts/account-stage.js +0 -81
- package/dist/admin/accounts/account-stage.js.map +0 -1
- package/dist/admin/accounts/hint.js +0 -12
- package/dist/admin/accounts/hint.js.map +0 -1
- package/dist/admin/payout-tiers/requirement-labels.js +0 -70
- package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
- package/dist/admin/pos/transactions-tab.js +0 -214
- package/dist/admin/pos/transactions-tab.js.map +0 -1
- package/dist/admin/revenue/channel-panel.js +0 -49
- package/dist/admin/revenue/channel-panel.js.map +0 -1
- package/dist/admin/revenue/channel-rows.js +0 -17
- package/dist/admin/revenue/channel-rows.js.map +0 -1
- package/dist/admin/revenue/download-button.js +0 -40
- package/dist/admin/revenue/download-button.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
- package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
- package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-summary.js +0 -308
- package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-table.js +0 -143
- package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings.module.css +0 -201
- package/dist/admin/revenue/earnings/use-earnings.js +0 -27
- package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
- package/dist/admin/revenue/ledger-model.js +0 -104
- package/dist/admin/revenue/ledger-model.js.map +0 -1
- package/dist/admin/revenue/ledger.module.css +0 -144
- package/dist/admin/revenue/merchant-headline.js +0 -37
- package/dist/admin/revenue/merchant-headline.js.map +0 -1
- package/dist/admin/revenue/merchant-payments-view.js +0 -234
- package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
- package/dist/admin/revenue/method-cards.js +0 -40
- package/dist/admin/revenue/method-cards.js.map +0 -1
- package/dist/admin/revenue/payment-drawer.js +0 -53
- package/dist/admin/revenue/payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plan-lines.js +0 -36
- package/dist/admin/revenue/plan-lines.js.map +0 -1
- package/dist/admin/revenue/plan-payment-drawer.js +0 -42
- package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plans-panel.js +0 -48
- package/dist/admin/revenue/plans-panel.js.map +0 -1
- package/dist/admin/revenue/platform-fees.js +0 -9
- package/dist/admin/revenue/platform-fees.js.map +0 -1
- package/dist/admin/revenue/print-summary-button.js +0 -27
- package/dist/admin/revenue/print-summary-button.js.map +0 -1
- package/dist/admin/revenue/subscriptions-view.js +0 -213
- package/dist/admin/revenue/subscriptions-view.js.map +0 -1
- package/dist/admin/revenue/summary-download.js +0 -38
- package/dist/admin/revenue/summary-download.js.map +0 -1
- package/dist/admin/revenue/summary-print.js +0 -82
- package/dist/admin/revenue/summary-print.js.map +0 -1
- package/dist/admin/revenue/summary-rows.js +0 -44
- package/dist/admin/revenue/summary-rows.js.map +0 -1
- package/dist/admin/revenue/url-filters.js +0 -36
- package/dist/admin/revenue/url-filters.js.map +0 -1
- package/dist/admin/revenue/use-url-filters.js +0 -24
- package/dist/admin/revenue/use-url-filters.js.map +0 -1
- package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
- package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
- package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
- package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
- package/dist/admin/verification/preview-dialog.js +0 -111
- package/dist/admin/verification/preview-dialog.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/nav-filter.ts"],"sourcesContent":["import {\n NAV_GROUPS,\n type NavAddition,\n type NavGroup,\n type NavItem,\n} from './nav-config';\nimport { moduleOn, type AdminModules } from './nav-modules';\n\nexport const isActive = (item: NavItem, pathname: string) =>\n item.href === '/admin'\n ? pathname === '/admin'\n : (item.activeRoutes ?? [item.href]).some((route) =>\n pathname.startsWith(route),\n );\n\nexport interface AdminSessionLike {\n role: string;\n permissions: string[];\n}\n\nexport const canAccess = (session: AdminSessionLike) => (item: NavItem) => {\n if (session.role === 'superadmin') return true;\n\n if (item.superadmin) return false;\n\n return !item.permission || session.permissions.includes(item.permission);\n};\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_shell/nav-filter.ts"],"sourcesContent":["import {\n NAV_GROUPS,\n type NavAddition,\n type NavGroup,\n type NavItem,\n} from './nav-config';\nimport { moduleOn, type AdminModules } from './nav-modules';\n\nexport const isActive = (item: NavItem, pathname: string) =>\n item.href === '/admin'\n ? pathname === '/admin'\n : (item.activeRoutes ?? [item.href]).some((route) =>\n pathname.startsWith(route),\n );\n\nexport interface AdminSessionLike {\n role: string;\n permissions: string[];\n}\n\nexport const canAccess = (session: AdminSessionLike) => (item: NavItem) => {\n if (session.role === 'superadmin') return true;\n\n if (item.superadmin) return false;\n\n return !item.permission || session.permissions.includes(item.permission);\n};\n\n/** The host's pages placed into their groups: after the Box items in a group (or instead of them), a new group before Platform. */\nexport function withAdditions(\n groups: NavGroup[],\n additions: NavAddition[] = [],\n): NavGroup[] {\n const merged = groups.map((g) => ({ ...g, items: [...g.items] }));\n\n additions.forEach(({ group, items, replace }) => {\n const found = merged.find((g) => g.label === group);\n\n if (found) {\n if (replace) found.items = [];\n found.items.push(...items);\n return;\n }\n\n const platform = merged.findIndex((g) => g.label === 'Platform');\n const at = platform === -1 ? merged.length : platform;\n\n merged.splice(at, 0, { label: group, items: [...items] });\n });\n\n return merged;\n}\n\n/** The groups this admin may see, modules off removed, empty groups dropped. */\nexport function visibleGroups(\n session: AdminSessionLike,\n modules: AdminModules = {},\n additions: NavAddition[] = [],\n): NavGroup[] {\n const allowed = canAccess(session);\n\n return withAdditions(NAV_GROUPS, additions)\n .map((group) => ({\n ...group,\n items: group.items.filter(\n (item) =>\n moduleOn(modules, item.module) &&\n !(item.unlessModule && moduleOn(modules, item.unlessModule)) &&\n allowed(item),\n ),\n }))\n .filter((group) => group.items.length);\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,OAIK;AACP,SAAS,gBAAmC;AAErC,MAAM,WAAW,CAAC,MAAe,aACtC,KAAK,SAAS,WACV,aAAa,YACZ,KAAK,gBAAgB,CAAC,KAAK,IAAI,GAAG;AAAA,EAAK,CAAC,UACvC,SAAS,WAAW,KAAK;AAC3B;AAOC,MAAM,YAAY,CAAC,YAA8B,CAAC,SAAkB;AACzE,MAAI,QAAQ,SAAS,aAAc,QAAO;AAE1C,MAAI,KAAK,WAAY,QAAO;AAE5B,SAAO,CAAC,KAAK,cAAc,QAAQ,YAAY,SAAS,KAAK,UAAU;AACzE;AAGO,SAAS,cACd,QACA,YAA2B,CAAC,GAChB;AACZ,QAAM,SAAS,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;AAEhE,YAAU,QAAQ,CAAC,EAAE,OAAO,OAAO,QAAQ,MAAM;AAC/C,UAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AAElD,QAAI,OAAO;AACT,UAAI,QAAS,OAAM,QAAQ,CAAC;AAC5B,YAAM,MAAM,KAAK,GAAG,KAAK;AACzB;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,UAAU,CAAC,MAAM,EAAE,UAAU,UAAU;AAC/D,UAAM,KAAK,aAAa,KAAK,OAAO,SAAS;AAE7C,WAAO,OAAO,IAAI,GAAG,EAAE,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC;AAAA,EAC1D,CAAC;AAED,SAAO;AACT;AAGO,SAAS,cACd,SACA,UAAwB,CAAC,GACzB,YAA2B,CAAC,GAChB;AACZ,QAAM,UAAU,UAAU,OAAO;AAEjC,SAAO,cAAc,YAAY,SAAS,EACvC,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM;AAAA,MACjB,CAAC,SACC,SAAS,SAAS,KAAK,MAAM,KAC7B,EAAE,KAAK,gBAAgB,SAAS,SAAS,KAAK,YAAY,MAC1D,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,MAAM;AACzC;","names":[]}
|
|
@@ -24,11 +24,9 @@ function NavRow({
|
|
|
24
24
|
item,
|
|
25
25
|
active,
|
|
26
26
|
collapsed,
|
|
27
|
-
canSupport
|
|
28
|
-
badges
|
|
27
|
+
canSupport
|
|
29
28
|
}) {
|
|
30
29
|
const Icon = item.icon;
|
|
31
|
-
const Badge = badges?.[item.href];
|
|
32
30
|
return /* @__PURE__ */ jsx(RailTip, { label: item.label, on: collapsed, children: /* @__PURE__ */ jsxs(
|
|
33
31
|
Link,
|
|
34
32
|
{
|
|
@@ -49,8 +47,7 @@ function NavRow({
|
|
|
49
47
|
}
|
|
50
48
|
),
|
|
51
49
|
/* @__PURE__ */ jsx("span", { className: styles.linkText, children: /* @__PURE__ */ jsx("span", { className: styles.linkLabel, children: item.label }) }),
|
|
52
|
-
item.inbox && /* @__PURE__ */ jsx("span", { className: styles.badgeSlot, children: /* @__PURE__ */ jsx(NavBadge, { kind: item.inbox, canSupport }) })
|
|
53
|
-
Badge && !item.inbox && /* @__PURE__ */ jsx("span", { className: styles.badgeSlot, children: /* @__PURE__ */ jsx(Badge, {}) })
|
|
50
|
+
item.inbox && /* @__PURE__ */ jsx("span", { className: styles.badgeSlot, children: /* @__PURE__ */ jsx(NavBadge, { kind: item.inbox, canSupport }) })
|
|
54
51
|
]
|
|
55
52
|
}
|
|
56
53
|
) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/nav-row.tsx"],"sourcesContent":["'use client';\n\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_shell/nav-row.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactElement } from 'react';\nimport Link from 'next/link';\nimport {\n TooltipContent,\n TooltipProvider,\n TooltipRoot,\n TooltipTrigger,\n} from '@withpotter/ui-kit/tooltip';\nimport { NavBadge } from '../_inbox/nav-badge';\nimport type { NavItem } from './nav-config';\nimport styles from './sidebar.module.css';\n\n/**\n * The collapsed rail is icons only, so the name arrives as a tooltip on hover\n * and keyboard focus (the merchant rail's RailLabel, without the drop shadow).\n * Expanded, the visible label is the name and this passes straight through.\n */\nexport function RailTip({\n label,\n on,\n children,\n}: {\n label: string;\n on: boolean;\n children: ReactElement;\n}) {\n if (!on) return children;\n\n return (\n <TooltipProvider delayDuration={200}>\n <TooltipRoot>\n <TooltipTrigger asChild>{children}</TooltipTrigger>\n <TooltipContent side=\"right\" className=\"shadow-none\">\n {label}\n </TooltipContent>\n </TooltipRoot>\n </TooltipProvider>\n );\n}\n\n/** One sidebar row: a link to its page. */\nexport function NavRow({\n item,\n active,\n collapsed,\n canSupport,\n}: {\n item: NavItem;\n active: boolean;\n collapsed: boolean;\n canSupport: boolean;\n}) {\n const Icon = item.icon;\n\n return (\n <RailTip label={item.label} on={collapsed}>\n <Link\n href={item.href}\n className={styles.link}\n data-fluid-item\n data-active={active}\n aria-current={active ? 'page' : undefined}\n aria-label={collapsed ? item.label : undefined}\n >\n <Icon\n size={16}\n strokeWidth={active ? 2 : 1.75}\n className={styles.icon}\n aria-hidden\n />\n <span className={styles.linkText}>\n <span className={styles.linkLabel}>{item.label}</span>\n </span>\n {item.inbox && (\n <span className={styles.badgeSlot}>\n <NavBadge kind={item.inbox} canSupport={canSupport} />\n </span>\n )}\n </Link>\n </RailTip>\n );\n}\n"],"mappings":";AAgCM,SACE,KADF;AA7BN,OAAO,UAAU;AACjB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AAEzB,OAAO,YAAY;AAOZ,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,GAAI,QAAO;AAEhB,SACE,oBAAC,mBAAgB,eAAe,KAC9B,+BAAC,eACC;AAAA,wBAAC,kBAAe,SAAO,MAAE,UAAS;AAAA,IAClC,oBAAC,kBAAe,MAAK,SAAQ,WAAU,eACpC,iBACH;AAAA,KACF,GACF;AAEJ;AAGO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,OAAO,KAAK;AAElB,SACE,oBAAC,WAAQ,OAAO,KAAK,OAAO,IAAI,WAC9B;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,WAAW,OAAO;AAAA,MAClB,mBAAe;AAAA,MACf,eAAa;AAAA,MACb,gBAAc,SAAS,SAAS;AAAA,MAChC,cAAY,YAAY,KAAK,QAAQ;AAAA,MAErC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,aAAa,SAAS,IAAI;AAAA,YAC1B,WAAW,OAAO;AAAA,YAClB,eAAW;AAAA;AAAA,QACb;AAAA,QACA,oBAAC,UAAK,WAAW,OAAO,UACtB,8BAAC,UAAK,WAAW,OAAO,WAAY,eAAK,OAAM,GACjD;AAAA,QACC,KAAK,SACJ,oBAAC,UAAK,WAAW,OAAO,WACtB,8BAAC,YAAS,MAAM,KAAK,OAAO,YAAwB,GACtD;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;","names":[]}
|
|
@@ -9,8 +9,6 @@ function SidebarFooter({
|
|
|
9
9
|
collapsed,
|
|
10
10
|
settingsActive,
|
|
11
11
|
showSettings,
|
|
12
|
-
actions = [],
|
|
13
|
-
pathname,
|
|
14
12
|
onSearch
|
|
15
13
|
}) {
|
|
16
14
|
const { resolvedTheme, toggleTheme } = useTheme();
|
|
@@ -47,24 +45,6 @@ function SidebarFooter({
|
|
|
47
45
|
]
|
|
48
46
|
}
|
|
49
47
|
) }),
|
|
50
|
-
actions.map(({ href, label, icon: Icon, badge: Badge }) => {
|
|
51
|
-
const active = pathname === href || pathname.startsWith(`${href}/`);
|
|
52
|
-
return /* @__PURE__ */ jsx(RailTip, { label, on: collapsed, children: /* @__PURE__ */ jsxs(
|
|
53
|
-
Link,
|
|
54
|
-
{
|
|
55
|
-
href,
|
|
56
|
-
className: styles.theme,
|
|
57
|
-
"data-active": active,
|
|
58
|
-
"aria-current": active ? "page" : void 0,
|
|
59
|
-
"aria-label": label,
|
|
60
|
-
title: collapsed ? void 0 : label,
|
|
61
|
-
children: [
|
|
62
|
-
/* @__PURE__ */ jsx(Icon, { size: 16, "aria-hidden": true }),
|
|
63
|
-
Badge && /* @__PURE__ */ jsx("span", { className: styles.badge, children: /* @__PURE__ */ jsx(Badge, {}) })
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
) }, href);
|
|
67
|
-
}),
|
|
68
48
|
/* @__PURE__ */ jsx(RailTip, { label: themeLabel, on: collapsed, children: /* @__PURE__ */ jsx(
|
|
69
49
|
"button",
|
|
70
50
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/sidebar-footer.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_shell/sidebar-footer.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { Moon, Search, Settings, Sun } from 'lucide-react';\nimport { useTheme } from '@withpotter/lib/theme-context';\nimport { RailTip } from './nav-row';\nimport styles from './sidebar-footer.module.css';\n\n/**\n * The pinned foot of the sidebar: jump anywhere (⌘K), and Settings with the\n * theme switch beside it. In the rail each is its own 40px target.\n */\nexport function SidebarFooter({\n collapsed,\n settingsActive,\n showSettings,\n onSearch,\n}: {\n collapsed: boolean;\n settingsActive: boolean;\n showSettings: boolean;\n onSearch: () => void;\n}) {\n const { resolvedTheme, toggleTheme } = useTheme();\n const dark = resolvedTheme === 'dark';\n const themeLabel = `Switch to ${dark ? 'light' : 'dark'} mode`;\n\n return (\n <div className={styles.foot} data-collapsed={collapsed}>\n <RailTip label=\"Search\" on={collapsed}>\n <button\n type=\"button\"\n className={styles.row}\n onClick={onSearch}\n aria-label={collapsed ? 'Search' : undefined}\n aria-keyshortcuts=\"Meta+K\"\n >\n <Search size={16} className={styles.icon} aria-hidden />\n <span className={styles.label}>Search</span>\n <kbd className={styles.kbd}>⌘K</kbd>\n </button>\n </RailTip>\n\n <div className={styles.split}>\n {showSettings && (\n <RailTip label=\"Settings\" on={collapsed}>\n <Link\n href=\"/admin/settings\"\n className={styles.row}\n data-active={settingsActive}\n aria-current={settingsActive ? 'page' : undefined}\n aria-label={collapsed ? 'Settings' : undefined}\n >\n <Settings size={16} className={styles.icon} aria-hidden />\n <span className={styles.label}>Settings</span>\n </Link>\n </RailTip>\n )}\n <RailTip label={themeLabel} on={collapsed}>\n <button\n type=\"button\"\n className={styles.theme}\n onClick={toggleTheme}\n aria-label={themeLabel}\n title={collapsed ? undefined : themeLabel}\n >\n {dark ? <Sun size={16} /> : <Moon size={16} />}\n </button>\n </RailTip>\n </div>\n </div>\n );\n}\n"],"mappings":";AA8BQ,SAOE,KAPF;AA5BR,OAAO,UAAU;AACjB,SAAS,MAAM,QAAQ,UAAU,WAAW;AAC5C,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,OAAO,YAAY;AAMZ,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,EAAE,eAAe,YAAY,IAAI,SAAS;AAChD,QAAM,OAAO,kBAAkB;AAC/B,QAAM,aAAa,aAAa,OAAO,UAAU,MAAM;AAEvD,SACE,qBAAC,SAAI,WAAW,OAAO,MAAM,kBAAgB,WAC3C;AAAA,wBAAC,WAAQ,OAAM,UAAS,IAAI,WAC1B;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,SAAS;AAAA,QACT,cAAY,YAAY,WAAW;AAAA,QACnC,qBAAkB;AAAA,QAElB;AAAA,8BAAC,UAAO,MAAM,IAAI,WAAW,OAAO,MAAM,eAAW,MAAC;AAAA,UACtD,oBAAC,UAAK,WAAW,OAAO,OAAO,oBAAM;AAAA,UACrC,oBAAC,SAAI,WAAW,OAAO,KAAK,qBAAE;AAAA;AAAA;AAAA,IAChC,GACF;AAAA,IAEA,qBAAC,SAAI,WAAW,OAAO,OACpB;AAAA,sBACC,oBAAC,WAAQ,OAAM,YAAW,IAAI,WAC5B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,OAAO;AAAA,UAClB,eAAa;AAAA,UACb,gBAAc,iBAAiB,SAAS;AAAA,UACxC,cAAY,YAAY,aAAa;AAAA,UAErC;AAAA,gCAAC,YAAS,MAAM,IAAI,WAAW,OAAO,MAAM,eAAW,MAAC;AAAA,YACxD,oBAAC,UAAK,WAAW,OAAO,OAAO,sBAAQ;AAAA;AAAA;AAAA,MACzC,GACF;AAAA,MAEF,oBAAC,WAAQ,OAAO,YAAY,IAAI,WAC9B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,OAAO;AAAA,UAClB,SAAS;AAAA,UACT,cAAY;AAAA,UACZ,OAAO,YAAY,SAAY;AAAA,UAE9B,iBAAO,oBAAC,OAAI,MAAM,IAAI,IAAK,oBAAC,QAAK,MAAM,IAAI;AAAA;AAAA,MAC9C,GACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -42,27 +42,10 @@
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
.theme {
|
|
45
|
-
position: relative;
|
|
46
45
|
flex: none;
|
|
47
46
|
justify-content: center;
|
|
48
47
|
width: 32px;
|
|
49
48
|
height: 32px;
|
|
50
|
-
text-decoration: none;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.theme[data-active='true'] {
|
|
54
|
-
border-color: var(--border-default);
|
|
55
|
-
background: var(--bg-base);
|
|
56
|
-
color: var(--text-primary);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* A host's count or dot, on the icon's top right corner. */
|
|
60
|
-
.badge {
|
|
61
|
-
position: absolute;
|
|
62
|
-
top: 2px;
|
|
63
|
-
right: 2px;
|
|
64
|
-
display: flex;
|
|
65
|
-
pointer-events: none;
|
|
66
49
|
}
|
|
67
50
|
|
|
68
51
|
.row:hover,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { isActive } from "./nav-filter";
|
|
4
|
-
import {
|
|
5
|
-
import { useOpenGroups } from "./use-open-groups";
|
|
4
|
+
import { NavRow } from "./nav-row";
|
|
6
5
|
import { SidebarFooter } from "./sidebar-footer";
|
|
7
6
|
import { useFluidHover } from "./use-fluid-hover";
|
|
8
7
|
import { UserMenu } from "./user-menu";
|
|
@@ -15,9 +14,7 @@ function Sidebar({
|
|
|
15
14
|
collapsed,
|
|
16
15
|
email,
|
|
17
16
|
role,
|
|
18
|
-
onSearch
|
|
19
|
-
badges,
|
|
20
|
-
footerActions
|
|
17
|
+
onSearch
|
|
21
18
|
}) {
|
|
22
19
|
const hover = useFluidHover();
|
|
23
20
|
const settings = groups.flatMap((group) => group.items).find((item) => item.href === FOOT_HREF);
|
|
@@ -25,10 +22,6 @@ function Sidebar({
|
|
|
25
22
|
...group,
|
|
26
23
|
items: group.items.filter((item) => item.href !== FOOT_HREF)
|
|
27
24
|
})).filter((group) => group.items.length);
|
|
28
|
-
const activeGroup = navGroups.find(
|
|
29
|
-
(group) => group.items.some((item) => isActive(item, pathname))
|
|
30
|
-
)?.label ?? null;
|
|
31
|
-
const { choices, setGroupOpen } = useOpenGroups(pathname, activeGroup);
|
|
32
25
|
return /* @__PURE__ */ jsxs("aside", { className: styles.sidebar, "data-collapsed": collapsed, children: [
|
|
33
26
|
/* @__PURE__ */ jsx(UserMenu, { email, role, collapsed }),
|
|
34
27
|
/* @__PURE__ */ jsxs(
|
|
@@ -38,7 +31,6 @@ function Sidebar({
|
|
|
38
31
|
"aria-label": "Admin",
|
|
39
32
|
onPointerMove: hover.onPointerMove,
|
|
40
33
|
onPointerLeave: hover.onPointerLeave,
|
|
41
|
-
onClick: hover.onClick,
|
|
42
34
|
children: [
|
|
43
35
|
/* @__PURE__ */ jsx(
|
|
44
36
|
"span",
|
|
@@ -52,19 +44,19 @@ function Sidebar({
|
|
|
52
44
|
} : void 0
|
|
53
45
|
}
|
|
54
46
|
),
|
|
55
|
-
navGroups.map((group, index) => /* @__PURE__ */
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
))
|
|
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))
|
|
68
60
|
]
|
|
69
61
|
}
|
|
70
62
|
),
|
|
@@ -74,8 +66,6 @@ function Sidebar({
|
|
|
74
66
|
collapsed,
|
|
75
67
|
showSettings: Boolean(settings),
|
|
76
68
|
settingsActive: settings ? isActive(settings, pathname) : false,
|
|
77
|
-
actions: footerActions,
|
|
78
|
-
pathname,
|
|
79
69
|
onSearch
|
|
80
70
|
}
|
|
81
71
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/sidebar.tsx"],"sourcesContent":["'use client';\n\nimport type {
|
|
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":[]}
|
|
@@ -64,75 +64,6 @@
|
|
|
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
|
-
/* A folding group has no gap of its own between heading and rows: that 2px
|
|
119
|
-
would pop in and out as the rows mount, so it lives inside the rows' padding
|
|
120
|
-
and grows with their height. */
|
|
121
|
-
.groupFold {
|
|
122
|
-
gap: 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* The rows of a folding group, laid out like the group's own. The padding keeps
|
|
126
|
-
a focus ring from being clipped by the closing box; the side and bottom
|
|
127
|
-
margins take it back, and the top padding is the gap under the heading. */
|
|
128
|
-
.groupRows {
|
|
129
|
-
display: flex;
|
|
130
|
-
flex-direction: column;
|
|
131
|
-
gap: 2px;
|
|
132
|
-
padding: 2px;
|
|
133
|
-
margin: 0 -2px -2px;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
67
|
.link {
|
|
137
68
|
position: relative;
|
|
138
69
|
display: flex;
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
useCallback,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState
|
|
7
|
-
} from "react";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
8
3
|
function useFluidHover() {
|
|
9
4
|
const [rect, setRect] = useState(null);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const measure = useCallback((container, y) => {
|
|
5
|
+
const onPointerMove = useCallback((event) => {
|
|
6
|
+
const container = event.currentTarget;
|
|
13
7
|
const items = container.querySelectorAll("[data-fluid-item]");
|
|
14
8
|
let nearest = null;
|
|
15
9
|
let best = Infinity;
|
|
16
10
|
items.forEach((item) => {
|
|
17
11
|
const box2 = item.getBoundingClientRect();
|
|
18
|
-
const distance = Math.abs(
|
|
12
|
+
const distance = Math.abs(event.clientY - (box2.top + box2.height / 2));
|
|
19
13
|
if (distance < best) {
|
|
20
14
|
best = distance;
|
|
21
15
|
nearest = box2;
|
|
@@ -28,33 +22,8 @@ function useFluidHover() {
|
|
|
28
22
|
(current) => current && current.top === top && current.height === box.height ? current : { top, height: box.height }
|
|
29
23
|
);
|
|
30
24
|
}, []);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
pointerY.current = event.clientY;
|
|
34
|
-
measure(event.currentTarget, event.clientY);
|
|
35
|
-
},
|
|
36
|
-
[measure]
|
|
37
|
-
);
|
|
38
|
-
const onPointerLeave = useCallback(() => {
|
|
39
|
-
pointerY.current = null;
|
|
40
|
-
setRect(null);
|
|
41
|
-
}, []);
|
|
42
|
-
const onClick = useCallback(
|
|
43
|
-
(event) => {
|
|
44
|
-
const container = event.currentTarget;
|
|
45
|
-
const until = performance.now() + 320;
|
|
46
|
-
cancelAnimationFrame(frame.current);
|
|
47
|
-
const follow = () => {
|
|
48
|
-
if (pointerY.current !== null) measure(container, pointerY.current);
|
|
49
|
-
if (performance.now() < until)
|
|
50
|
-
frame.current = requestAnimationFrame(follow);
|
|
51
|
-
};
|
|
52
|
-
frame.current = requestAnimationFrame(follow);
|
|
53
|
-
},
|
|
54
|
-
[measure]
|
|
55
|
-
);
|
|
56
|
-
useEffect(() => () => cancelAnimationFrame(frame.current), []);
|
|
57
|
-
return { rect, onPointerMove, onPointerLeave, onClick };
|
|
25
|
+
const onPointerLeave = useCallback(() => setRect(null), []);
|
|
26
|
+
return { rect, onPointerMove, onPointerLeave };
|
|
58
27
|
}
|
|
59
28
|
export {
|
|
60
29
|
useFluidHover
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/use-fluid-hover.ts"],"sourcesContent":["'use client';\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_shell/use-fluid-hover.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useState, type PointerEvent } from 'react';\n\ntype Rect = { top: number; height: number } | null;\n\n/**\n * Fluid hover: one highlight that glides to the item nearest the pointer\n * (not just the one under it), so the target is lit before the click lands.\n * Items opt in with `data-fluid-item`; bind the handlers on their scrolling\n * container. The glide is a CSS spring on `transform`, so it retargets\n * smoothly when the pointer changes direction.\n */\nexport function useFluidHover() {\n const [rect, setRect] = useState<Rect>(null);\n\n const onPointerMove = useCallback((event: PointerEvent<HTMLElement>) => {\n const container = event.currentTarget;\n const items = container.querySelectorAll<HTMLElement>('[data-fluid-item]');\n let nearest: DOMRect | null = null;\n let best = Infinity;\n\n items.forEach((item) => {\n const box = item.getBoundingClientRect();\n const distance = Math.abs(event.clientY - (box.top + box.height / 2));\n\n if (distance < best) {\n best = distance;\n nearest = box;\n }\n });\n\n if (!nearest) return;\n\n const box: DOMRect = nearest;\n const top =\n box.top - container.getBoundingClientRect().top + container.scrollTop;\n\n setRect((current) =>\n current && current.top === top && current.height === box.height\n ? current\n : { top, height: box.height },\n );\n }, []);\n\n const onPointerLeave = useCallback(() => setRect(null), []);\n\n return { rect, onPointerMove, onPointerLeave };\n}\n"],"mappings":";AAEA,SAAS,aAAa,gBAAmC;AAWlD,SAAS,gBAAgB;AAC9B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe,IAAI;AAE3C,QAAM,gBAAgB,YAAY,CAAC,UAAqC;AACtE,UAAM,YAAY,MAAM;AACxB,UAAM,QAAQ,UAAU,iBAA8B,mBAAmB;AACzE,QAAI,UAA0B;AAC9B,QAAI,OAAO;AAEX,UAAM,QAAQ,CAAC,SAAS;AACtB,YAAMA,OAAM,KAAK,sBAAsB;AACvC,YAAM,WAAW,KAAK,IAAI,MAAM,WAAWA,KAAI,MAAMA,KAAI,SAAS,EAAE;AAEpE,UAAI,WAAW,MAAM;AACnB,eAAO;AACP,kBAAUA;AAAA,MACZ;AAAA,IACF,CAAC;AAED,QAAI,CAAC,QAAS;AAEd,UAAM,MAAe;AACrB,UAAM,MACJ,IAAI,MAAM,UAAU,sBAAsB,EAAE,MAAM,UAAU;AAE9D;AAAA,MAAQ,CAAC,YACP,WAAW,QAAQ,QAAQ,OAAO,QAAQ,WAAW,IAAI,SACrD,UACA,EAAE,KAAK,QAAQ,IAAI,OAAO;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAiB,YAAY,MAAM,QAAQ,IAAI,GAAG,CAAC,CAAC;AAE1D,SAAO,EAAE,MAAM,eAAe,eAAe;AAC/C;","names":["box"]}
|
package/dist/admin/_ui/admin.css
CHANGED
|
@@ -209,13 +209,6 @@
|
|
|
209
209
|
min-width: 0;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
/* A colour key along the top edge, matching the chart slice the card stands for. */
|
|
213
|
-
.adm-stat[data-accent] {
|
|
214
|
-
box-shadow:
|
|
215
|
-
inset 0 3px 0 var(--adm-stat-accent),
|
|
216
|
-
var(--adm-card-shadow);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
212
|
.adm-stat-head {
|
|
220
213
|
display: grid;
|
|
221
214
|
gap: 2px;
|
|
@@ -32,7 +32,7 @@ function DataTable({
|
|
|
32
32
|
{
|
|
33
33
|
tone: "error",
|
|
34
34
|
title: errorTitle,
|
|
35
|
-
description: "Please
|
|
35
|
+
description: "Please retry. If this persists, check the API logs."
|
|
36
36
|
}
|
|
37
37
|
) }) }),
|
|
38
38
|
!isLoading && !isError && rows && rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: colCount, style: { padding: 0 }, children: /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_ui/data-table.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport { tableStyles } from './styles';\n\nexport interface Column<T> {\n /** Unique key for the column. */\n key: string;\n /** Header label. */\n header: string;\n /** Cell renderer. Defaults to String(row[key]). */\n render?: (row: T) => ReactNode;\n /** Render muted (secondary text colour). */\n muted?: boolean;\n /** Prevent wrapping. */\n nowrap?: boolean;\n width?: number | string;\n}\n\n/**\n * Generic admin data table. Owns the loading (skeleton), error and empty\n * states so list pages never hand-roll a <table> again. Supports an optional\n * expandable sub-row per record.\n */\nexport function DataTable<T>({\n columns,\n rows,\n getRowKey,\n isLoading,\n isError,\n errorTitle = 'Failed to load',\n emptyTitle = 'Nothing here yet',\n emptyDescription,\n emptyIcon,\n skeletonRows = 6,\n renderExpanded,\n isExpanded,\n onRowClick,\n}: {\n columns: Column<T>[];\n rows: T[] | undefined;\n getRowKey: (row: T) => string;\n isLoading?: boolean;\n isError?: boolean;\n errorTitle?: string;\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIcon?: React.ComponentType<{ size?: number; color?: string }>;\n skeletonRows?: number;\n renderExpanded?: (row: T) => ReactNode;\n isExpanded?: (row: T) => boolean;\n /** When provided, the whole row becomes clickable (excluding links/buttons). */\n onRowClick?: (row: T) => void;\n}) {\n const colCount = columns.length;\n\n return (\n <div className=\"adm-table-card\">\n <table style={tableStyles.table}>\n <thead>\n <tr style={tableStyles.headRow}>\n {columns.map((c) => (\n <th key={c.key} style={{ ...tableStyles.th, width: c.width }}>\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {isLoading &&\n Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={`sk-${i}`}>\n {columns.map((c) => (\n <td key={c.key} style={tableStyles.cell}>\n <Skeleton\n width={`${50 + ((i + c.key.length) % 4) * 12}%`}\n />\n </td>\n ))}\n </tr>\n ))}\n\n {!isLoading && isError && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_ui/data-table.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport { tableStyles } from './styles';\n\nexport interface Column<T> {\n /** Unique key for the column. */\n key: string;\n /** Header label. */\n header: string;\n /** Cell renderer. Defaults to String(row[key]). */\n render?: (row: T) => ReactNode;\n /** Render muted (secondary text colour). */\n muted?: boolean;\n /** Prevent wrapping. */\n nowrap?: boolean;\n width?: number | string;\n}\n\n/**\n * Generic admin data table. Owns the loading (skeleton), error and empty\n * states so list pages never hand-roll a <table> again. Supports an optional\n * expandable sub-row per record.\n */\nexport function DataTable<T>({\n columns,\n rows,\n getRowKey,\n isLoading,\n isError,\n errorTitle = 'Failed to load',\n emptyTitle = 'Nothing here yet',\n emptyDescription,\n emptyIcon,\n skeletonRows = 6,\n renderExpanded,\n isExpanded,\n onRowClick,\n}: {\n columns: Column<T>[];\n rows: T[] | undefined;\n getRowKey: (row: T) => string;\n isLoading?: boolean;\n isError?: boolean;\n errorTitle?: string;\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIcon?: React.ComponentType<{ size?: number; color?: string }>;\n skeletonRows?: number;\n renderExpanded?: (row: T) => ReactNode;\n isExpanded?: (row: T) => boolean;\n /** When provided, the whole row becomes clickable (excluding links/buttons). */\n onRowClick?: (row: T) => void;\n}) {\n const colCount = columns.length;\n\n return (\n <div className=\"adm-table-card\">\n <table style={tableStyles.table}>\n <thead>\n <tr style={tableStyles.headRow}>\n {columns.map((c) => (\n <th key={c.key} style={{ ...tableStyles.th, width: c.width }}>\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {isLoading &&\n Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={`sk-${i}`}>\n {columns.map((c) => (\n <td key={c.key} style={tableStyles.cell}>\n <Skeleton\n width={`${50 + ((i + c.key.length) % 4) * 12}%`}\n />\n </td>\n ))}\n </tr>\n ))}\n\n {!isLoading && isError && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please retry. If this persists, check the API logs.\"\n />\n </td>\n </tr>\n )}\n\n {!isLoading && !isError && rows && rows.length === 0 && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />\n </td>\n </tr>\n )}\n\n {!isLoading &&\n !isError &&\n rows?.map((row) => (\n <RowGroup\n key={getRowKey(row)}\n row={row}\n columns={columns}\n renderExpanded={renderExpanded}\n expanded={isExpanded?.(row) ?? false}\n colCount={colCount}\n onRowClick={onRowClick}\n />\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction RowGroup<T>({\n row,\n columns,\n renderExpanded,\n expanded,\n colCount,\n onRowClick,\n}: {\n row: T;\n columns: Column<T>[];\n renderExpanded?: (row: T) => ReactNode;\n expanded: boolean;\n colCount: number;\n onRowClick?: (row: T) => void;\n}) {\n const clickable = !!onRowClick;\n return (\n <>\n <tr\n onClick={\n clickable\n ? (e) => {\n // Let inner links/buttons handle their own clicks.\n if ((e.target as HTMLElement).closest('a,button')) return;\n onRowClick!(row);\n }\n : undefined\n }\n style={clickable ? { cursor: 'pointer' } : undefined}\n onMouseEnter={\n clickable\n ? (e) => (e.currentTarget.style.background = 'var(--bg-subtle)')\n : undefined\n }\n onMouseLeave={\n clickable ? (e) => (e.currentTarget.style.background = '') : undefined\n }\n >\n {columns.map((c) => (\n <td\n key={c.key}\n style={{\n ...(c.muted ? tableStyles.cellMuted : tableStyles.cell),\n whiteSpace: c.nowrap ? 'nowrap' : undefined,\n }}\n >\n {c.render\n ? c.render(row)\n : String((row as Record<string, unknown>)[c.key] ?? '—')}\n </td>\n ))}\n </tr>\n {expanded && renderExpanded && (\n <tr>\n <td\n colSpan={colCount}\n style={{ ...tableStyles.cell, background: 'var(--bg-subtle)' }}\n >\n {renderExpanded(row)}\n </td>\n </tr>\n )}\n </>\n );\n}\n"],"mappings":"AA8Dc,SAgFV,UAhFU,KAMN,YANM;AA7Dd,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAqBrB,SAAS,UAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AACF,GAeG;AACD,QAAM,WAAW,QAAQ;AAEzB,SACE,oBAAC,SAAI,WAAU,kBACb,+BAAC,WAAM,OAAO,YAAY,OACxB;AAAA,wBAAC,WACC,8BAAC,QAAG,OAAO,YAAY,SACpB,kBAAQ,IAAI,CAAC,MACZ,oBAAC,QAAe,OAAO,EAAE,GAAG,YAAY,IAAI,OAAO,EAAE,MAAM,GACxD,YAAE,UADI,EAAE,GAEX,CACD,GACH,GACF;AAAA,IACA,qBAAC,WACE;AAAA,mBACC,MAAM,KAAK,EAAE,QAAQ,aAAa,CAAC,EAAE,IAAI,CAAC,GAAG,MAC3C,oBAAC,QACE,kBAAQ,IAAI,CAAC,MACZ,oBAAC,QAAe,OAAO,YAAY,MACjC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,GAAG,MAAO,IAAI,EAAE,IAAI,UAAU,IAAK,EAAE;AAAA;AAAA,MAC9C,KAHO,EAAE,GAIX,CACD,KAPM,MAAM,CAAC,EAQhB,CACD;AAAA,MAEF,CAAC,aAAa,WACb,oBAAC,QACC,8BAAC,QAAG,SAAS,UAAU,OAAO,EAAE,SAAS,EAAE,GACzC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAY;AAAA;AAAA,MACd,GACF,GACF;AAAA,MAGD,CAAC,aAAa,CAAC,WAAW,QAAQ,KAAK,WAAW,KACjD,oBAAC,QACC,8BAAC,QAAG,SAAS,UAAU,OAAO,EAAE,SAAS,EAAE,GACzC;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,OAAO;AAAA,UACP,aAAa;AAAA;AAAA,MACf,GACF,GACF;AAAA,MAGD,CAAC,aACA,CAAC,WACD,MAAM,IAAI,CAAC,QACT;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU,aAAa,GAAG,KAAK;AAAA,UAC/B;AAAA,UACA;AAAA;AAAA,QANK,UAAU,GAAG;AAAA,MAOpB,CACD;AAAA,OACL;AAAA,KACF,GACF;AAEJ;AAEA,SAAS,SAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,YAAY,CAAC,CAAC;AACpB,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SACE,YACI,CAAC,MAAM;AAEL,cAAK,EAAE,OAAuB,QAAQ,UAAU,EAAG;AACnD,qBAAY,GAAG;AAAA,QACjB,IACA;AAAA,QAEN,OAAO,YAAY,EAAE,QAAQ,UAAU,IAAI;AAAA,QAC3C,cACE,YACI,CAAC,MAAO,EAAE,cAAc,MAAM,aAAa,qBAC3C;AAAA,QAEN,cACE,YAAY,CAAC,MAAO,EAAE,cAAc,MAAM,aAAa,KAAM;AAAA,QAG9D,kBAAQ,IAAI,CAAC,MACZ;AAAA,UAAC;AAAA;AAAA,YAEC,OAAO;AAAA,cACL,GAAI,EAAE,QAAQ,YAAY,YAAY,YAAY;AAAA,cAClD,YAAY,EAAE,SAAS,WAAW;AAAA,YACpC;AAAA,YAEC,YAAE,SACC,EAAE,OAAO,GAAG,IACZ,OAAQ,IAAgC,EAAE,GAAG,KAAK,QAAG;AAAA;AAAA,UARpD,EAAE;AAAA,QAST,CACD;AAAA;AAAA,IACH;AAAA,IACC,YAAY,kBACX,oBAAC,QACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO,EAAE,GAAG,YAAY,MAAM,YAAY,mBAAmB;AAAA,QAE5D,yBAAe,GAAG;AAAA;AAAA,IACrB,GACF;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -53,7 +53,7 @@ function ListCard({
|
|
|
53
53
|
{
|
|
54
54
|
tone: "error",
|
|
55
55
|
title: errorTitle,
|
|
56
|
-
description: "Please
|
|
56
|
+
description: "Please retry. If this persists, check the API logs.",
|
|
57
57
|
action: onRetry && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: onRetry, children: "Try again" })
|
|
58
58
|
}
|
|
59
59
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please retry. If this persists, check the API logs.\"\n action={\n onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n Try again\n </Button>\n )\n }\n />,\n )\n : isEmpty\n ? state(\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />,\n )\n : children}\n </TableShell>\n </section>\n );\n}\n\n/** Count on the left, Previous · Page n of m · Next on the right. */\nexport function ListPager({\n page,\n totalPages,\n total,\n noun,\n onPage,\n}: {\n page: number;\n totalPages: number;\n total?: number;\n noun: string;\n onPage: (next: number) => void;\n}) {\n return (\n <div className={styles.footer}>\n <span>\n {total != null\n ? `${new Intl.NumberFormat().format(total)} ${noun}`\n : ''}\n </span>\n <div className={styles.pages}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.max(1, page - 1))}\n disabled={page <= 1}\n >\n Previous\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.min(totalPages, page + 1))}\n disabled={page >= totalPages}\n >\n Next\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";AA0DM,SAmBI,UAnBJ,KAoBM,YApBN;AAvDN,SAAS,QAAQ,kBAAkB;AACnC,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGZ,MAAM,QAAQ;AAOd,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqBG;AACD,QAAM,QAAQ,CAAC,SACb,oBAAC,QACC,8BAAC,QAAG,SAAS,SAAS,WAAU,QAC7B,gBACH,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,eAAe,OAAO,IAAI;AAAA,MACrC,cAAY;AAAA,MACZ,aAAW,aAAa;AAAA,MAExB;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,SACE,iCACE;AAAA,iCAAC,QAAG,WAAW,OAAO,OACnB;AAAA;AAAA,cACA,UAAU,UACT,oBAAC,UAAK,WAAW,OAAO,OAAQ,iBAAM;AAAA,eAE1C;AAAA,YACC;AAAA,aACH;AAAA,UAEF;AAAA,UAEC,sBACG,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,QAC5B,oBAAC,QAAqB,eAAW,MAC9B,gBAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAACA,IAAG,QACnC,oBAAC,QACC,8BAAC,YAAS,OAAO,GAAG,MAAO,MAAM,OAAO,IAAK,EAAE,KAAK,KAD7C,GAET,CACD,KALM,MAAM,GAAG,EAMlB,CACD,IACD,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,OAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,QACE,WACE,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,SAAS,uBAExD;AAAA;AAAA,YAGN;AAAA,UACF,IACA,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,aAAa;AAAA;AAAA,YACf;AAAA,UACF,IACA;AAAA;AAAA,MACV;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,QACrB;AAAA,wBAAC,UACE,mBAAS,OACN,GAAG,IAAI,KAAK,aAAa,EAAE,OAAO,KAAK,CAAC,IAAI,IAAI,KAChD,IACN;AAAA,IACA,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,UAC3C,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,MACA,qBAAC,UAAK;AAAA;AAAA,QACE;AAAA,QAAK;AAAA,QAAK;AAAA,SAClB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,YAAY,OAAO,CAAC,CAAC;AAAA,UACpD,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;","names":["_"]}
|
|
@@ -31,13 +31,23 @@
|
|
|
31
31
|
color: var(--text-secondary);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/* The
|
|
34
|
+
/* The table's own scroller holds both axes, so the header can stick to it. */
|
|
35
35
|
.card div:has(> table),
|
|
36
36
|
.fill div:has(> table) {
|
|
37
|
-
|
|
37
|
+
max-height: calc(var(--adm-panel-h) * 1.4);
|
|
38
|
+
overflow: auto;
|
|
39
|
+
overscroll-behavior: contain;
|
|
38
40
|
scrollbar-width: thin;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
/* Inside a Panel: the table fills the body and scrolls on its own. */
|
|
44
|
+
.fill,
|
|
45
|
+
.fill > div,
|
|
46
|
+
.fill div:has(> table) {
|
|
47
|
+
height: 100%;
|
|
48
|
+
max-height: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
.note {
|
|
42
52
|
margin: 0;
|
|
43
53
|
padding: 0 20px;
|
|
@@ -45,8 +55,12 @@
|
|
|
45
55
|
|
|
46
56
|
.card thead th,
|
|
47
57
|
.fill thead th {
|
|
58
|
+
position: sticky;
|
|
59
|
+
top: 0;
|
|
60
|
+
z-index: 1;
|
|
48
61
|
white-space: nowrap;
|
|
49
62
|
background: var(--bg-base);
|
|
63
|
+
box-shadow: inset 0 -1px 0 var(--border-subtle);
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
.footer {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_ui/money-view.ts"],"sourcesContent":["/**\n * How a page shows money that comes back once per currency.\n *\n * Amounts in different currencies are never added together and never\n * converted. A headline figure shows one line per currency; a chart or a\n * leaderboard needs one currency, so it follows the currency filter, or the\n * only currency there is, and otherwise asks for one to be picked.\n */\n\n/** An amount in its own currency's major unit: 120000, 'NGN' → \"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_ui/money-view.ts"],"sourcesContent":["/**\n * How a page shows money that comes back once per currency.\n *\n * Amounts in different currencies are never added together and never\n * converted. A headline figure shows one line per currency; a chart or a\n * leaderboard needs one currency, so it follows the currency filter, or the\n * only currency there is, and otherwise asks for one to be picked.\n */\n\n/** An amount in its own currency's major unit: 120000, 'NGN' → \"NGN 120,000\". */\nexport function formatMoney(amount: number, currency: string, decimals = 0): string {\n const plain = () =>\n new Intl.NumberFormat('en-US', { maximumFractionDigits: decimals }).format(amount);\n // XXX is ISO \"no currency\": the engine's label for a row with none.\n if (currency === 'XXX') return `${plain()} (no currency)`;\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: decimals,\n }).format(amount);\n } catch {\n return `${currency} ${plain()}`;\n }\n}\n\n/** A formatter for one currency, for charts and tables that take `(n) => string`. */\nexport const moneyIn =\n (currency: string, decimals = 0) =>\n (amount: number): string =>\n formatMoney(amount, currency, decimals);\n\n/**\n * The one currency a chart or leaderboard shows: the filter's choice, else\n * the only currency in the data, else null (several, none chosen).\n */\nexport function focusCurrency(currencies: readonly string[], selected: string): string | null {\n if (selected) return selected;\n return currencies.length === 1 ? currencies[0] : null;\n}\n\n/** The rows for the chosen currency; every row when none is chosen. */\nexport function shownRows<T extends { currency: string }>(\n rows: readonly T[] | undefined,\n selected: string,\n): T[] {\n return (rows ?? []).filter((r) => !selected || r.currency === selected);\n}\n\n/** The row for one currency, if the data has it. */\nexport function rowFor<T extends { currency: string }>(\n rows: readonly T[] | undefined,\n currency: string | null,\n): T | undefined {\n return currency ? (rows ?? []).find((r) => r.currency === currency) : undefined;\n}\n\n/** One formatted line per shown currency, A→Z as the API sends them. */\nexport function moneyLines<T extends { currency: string }>(\n rows: readonly T[] | undefined,\n selected: string,\n amountOf: (row: T) => number,\n decimals = 0,\n): string[] {\n return shownRows(rows, selected).map((r) => formatMoney(amountOf(r), r.currency, decimals));\n}\n\n/** What an empty money figure says: \"No NGN\" when filtered, else the fallback. */\nexport function noneText(selected: string, fallback = '–'): string {\n return selected ? `No ${selected}` : fallback;\n}\n\n/** The line under a chart that cannot add currencies together. */\nexport const PICK_CURRENCY = 'Pick a currency to see this. Amounts in different currencies are never added together.';\n"],"mappings":"AAUO,SAAS,YAAY,QAAgB,UAAkB,WAAW,GAAW;AAClF,QAAM,QAAQ,MACZ,IAAI,KAAK,aAAa,SAAS,EAAE,uBAAuB,SAAS,CAAC,EAAE,OAAO,MAAM;AAEnF,MAAI,aAAa,MAAO,QAAO,GAAG,MAAM,CAAC;AACzC,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,OAAO;AAAA,MACP;AAAA,MACA,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,MAAM;AAAA,EAClB,QAAQ;AACN,WAAO,GAAG,QAAQ,IAAI,MAAM,CAAC;AAAA,EAC/B;AACF;AAGO,MAAM,UACX,CAAC,UAAkB,WAAW,MAC9B,CAAC,WACC,YAAY,QAAQ,UAAU,QAAQ;AAMnC,SAAS,cAAc,YAA+B,UAAiC;AAC5F,MAAI,SAAU,QAAO;AACrB,SAAO,WAAW,WAAW,IAAI,WAAW,CAAC,IAAI;AACnD;AAGO,SAAS,UACd,MACA,UACK;AACL,UAAQ,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,QAAQ;AACxE;AAGO,SAAS,OACd,MACA,UACe;AACf,SAAO,YAAY,QAAQ,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,IAAI;AACxE;AAGO,SAAS,WACd,MACA,UACA,UACA,WAAW,GACD;AACV,SAAO,UAAU,MAAM,QAAQ,EAAE,IAAI,CAAC,MAAM,YAAY,SAAS,CAAC,GAAG,EAAE,UAAU,QAAQ,CAAC;AAC5F;AAGO,SAAS,SAAS,UAAkB,WAAW,UAAa;AACjE,SAAO,WAAW,MAAM,QAAQ,KAAK;AACvC;AAGO,MAAM,gBAAgB;","names":[]}
|