@waveinno/ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +5 -0
  3. package/README.md +64 -2
  4. package/dist/brand/waveinno-logo.svg +1 -0
  5. package/dist/components/brand/index.d.ts +3 -0
  6. package/dist/components/brand/index.js +1 -0
  7. package/dist/components/brand/logo.d.ts +89 -0
  8. package/dist/components/brand/logo.js +1 -0
  9. package/dist/components/brand/waveinno-logo.d.ts +52 -0
  10. package/dist/components/brand/waveinno-logo.js +1 -0
  11. package/dist/components/charts/bar-chart.d.ts +20 -0
  12. package/dist/components/charts/bar-chart.js +1 -0
  13. package/dist/components/charts/bar-list.d.ts +27 -0
  14. package/dist/components/charts/bar-list.js +1 -0
  15. package/dist/components/charts/chart-card.d.ts +32 -0
  16. package/dist/components/charts/chart-card.js +1 -0
  17. package/dist/components/charts/donut-chart.d.ts +27 -0
  18. package/dist/components/charts/donut-chart.js +1 -0
  19. package/dist/components/charts/gauge-chart.d.ts +21 -0
  20. package/dist/components/charts/gauge-chart.js +1 -0
  21. package/dist/components/charts/heatmap.d.ts +23 -0
  22. package/dist/components/charts/heatmap.js +1 -0
  23. package/dist/components/charts/index.d.ts +10 -0
  24. package/dist/components/charts/index.js +1 -0
  25. package/dist/components/charts/line-chart.d.ts +24 -0
  26. package/dist/components/charts/line-chart.js +1 -0
  27. package/dist/components/charts/palette.d.ts +39 -0
  28. package/dist/components/charts/palette.js +1 -0
  29. package/dist/components/charts/trend-chart.d.ts +22 -0
  30. package/dist/components/charts/trend-chart.js +1 -0
  31. package/dist/components/patterns/activity-feed.d.ts +28 -0
  32. package/dist/components/patterns/activity-feed.js +1 -0
  33. package/dist/components/patterns/data-table-cells.d.ts +39 -0
  34. package/dist/components/patterns/data-table-cells.js +1 -0
  35. package/dist/components/patterns/data-table-edit.d.ts +56 -0
  36. package/dist/components/patterns/data-table-edit.js +1 -0
  37. package/dist/components/patterns/data-table-filters.d.ts +84 -0
  38. package/dist/components/patterns/data-table-filters.js +1 -0
  39. package/dist/components/patterns/data-table.d.ts +76 -0
  40. package/dist/components/patterns/data-table.js +2 -0
  41. package/dist/components/patterns/index.d.ts +16 -0
  42. package/dist/components/patterns/index.js +1 -0
  43. package/dist/components/patterns/kanban.d.ts +28 -0
  44. package/dist/components/patterns/kanban.js +1 -0
  45. package/dist/components/patterns/kpi-card.d.ts +22 -0
  46. package/dist/components/patterns/kpi-card.js +1 -0
  47. package/dist/components/patterns/money.d.ts +28 -0
  48. package/dist/components/patterns/money.js +1 -0
  49. package/dist/components/patterns/page-header.d.ts +38 -0
  50. package/dist/components/patterns/page-header.js +1 -0
  51. package/dist/components/patterns/page-hero.d.ts +50 -0
  52. package/dist/components/patterns/page-hero.js +1 -0
  53. package/dist/components/patterns/page-shell.d.ts +27 -0
  54. package/dist/components/patterns/page-shell.js +1 -0
  55. package/dist/components/patterns/stat-card.d.ts +56 -0
  56. package/dist/components/patterns/stat-card.js +1 -0
  57. package/dist/components/patterns/states.d.ts +27 -0
  58. package/dist/components/patterns/states.js +1 -0
  59. package/dist/components/patterns/timeline.d.ts +100 -0
  60. package/dist/components/patterns/timeline.js +1 -0
  61. package/dist/components/ui/avatar.d.ts +37 -0
  62. package/dist/components/ui/avatar.js +1 -0
  63. package/dist/components/ui/badge.d.ts +41 -0
  64. package/dist/components/ui/badge.js +1 -0
  65. package/dist/components/ui/button.d.ts +25 -0
  66. package/dist/components/ui/button.js +1 -0
  67. package/dist/components/ui/calendar.d.ts +42 -0
  68. package/dist/components/ui/calendar.js +1 -0
  69. package/dist/components/ui/card.d.ts +11 -0
  70. package/dist/components/ui/card.js +1 -0
  71. package/dist/components/ui/checkbox.d.ts +3 -0
  72. package/dist/components/ui/checkbox.js +1 -0
  73. package/dist/components/ui/combobox.d.ts +47 -0
  74. package/dist/components/ui/combobox.js +1 -0
  75. package/dist/components/ui/confirm-dialog.d.ts +34 -0
  76. package/dist/components/ui/confirm-dialog.js +1 -0
  77. package/dist/components/ui/date-picker.d.ts +80 -0
  78. package/dist/components/ui/date-picker.js +1 -0
  79. package/dist/components/ui/dialog.d.ts +54 -0
  80. package/dist/components/ui/dialog.js +1 -0
  81. package/dist/components/ui/dropdown-menu.d.ts +44 -0
  82. package/dist/components/ui/dropdown-menu.js +1 -0
  83. package/dist/components/ui/form-field.d.ts +27 -0
  84. package/dist/components/ui/form-field.js +1 -0
  85. package/dist/components/ui/index.d.ts +26 -0
  86. package/dist/components/ui/index.js +1 -0
  87. package/dist/components/ui/input.d.ts +21 -0
  88. package/dist/components/ui/input.js +1 -0
  89. package/dist/components/ui/label.d.ts +5 -0
  90. package/dist/components/ui/label.js +1 -0
  91. package/dist/components/ui/popover.d.ts +22 -0
  92. package/dist/components/ui/popover.js +1 -0
  93. package/dist/components/ui/progress.d.ts +43 -0
  94. package/dist/components/ui/progress.js +1 -0
  95. package/dist/components/ui/segmented.d.ts +42 -0
  96. package/dist/components/ui/segmented.js +1 -0
  97. package/dist/components/ui/select.d.ts +10 -0
  98. package/dist/components/ui/select.js +1 -0
  99. package/dist/components/ui/separator.d.ts +3 -0
  100. package/dist/components/ui/separator.js +1 -0
  101. package/dist/components/ui/sheet.d.ts +19 -0
  102. package/dist/components/ui/sheet.js +1 -0
  103. package/dist/components/ui/skeleton.d.ts +10 -0
  104. package/dist/components/ui/skeleton.js +1 -0
  105. package/dist/components/ui/switch.d.ts +3 -0
  106. package/dist/components/ui/switch.js +1 -0
  107. package/dist/components/ui/tabs.d.ts +19 -0
  108. package/dist/components/ui/tabs.js +1 -0
  109. package/dist/components/ui/time-picker.d.ts +52 -0
  110. package/dist/components/ui/time-picker.js +1 -0
  111. package/dist/components/ui/tooltip.d.ts +13 -0
  112. package/dist/components/ui/tooltip.js +1 -0
  113. package/dist/hooks/index.d.ts +4 -0
  114. package/dist/hooks/index.js +1 -0
  115. package/dist/hooks/use-hotkey.d.ts +16 -0
  116. package/dist/hooks/use-hotkey.js +1 -0
  117. package/dist/hooks/use-media-query.d.ts +4 -0
  118. package/dist/hooks/use-media-query.js +1 -0
  119. package/dist/hooks/use-mounted.d.ts +2 -0
  120. package/dist/hooks/use-mounted.js +1 -0
  121. package/dist/index.d.ts +17 -0
  122. package/dist/index.js +1 -0
  123. package/dist/lib/boot-script.d.ts +15 -0
  124. package/dist/lib/boot-script.js +12 -0
  125. package/dist/lib/currency.d.ts +55 -0
  126. package/dist/lib/currency.js +1 -0
  127. package/dist/lib/format.d.ts +28 -0
  128. package/dist/lib/format.js +1 -0
  129. package/dist/lib/index.d.ts +5 -0
  130. package/dist/lib/index.js +1 -0
  131. package/dist/lib/utils.d.ts +7 -0
  132. package/dist/lib/utils.js +1 -0
  133. package/dist/shell/app-shell.d.ts +18 -0
  134. package/dist/shell/app-shell.js +1 -0
  135. package/dist/shell/app-switcher.d.ts +9 -0
  136. package/dist/shell/app-switcher.js +1 -0
  137. package/dist/shell/appearance-controls.d.ts +13 -0
  138. package/dist/shell/appearance-controls.js +1 -0
  139. package/dist/shell/command-palette.d.ts +9 -0
  140. package/dist/shell/command-palette.js +1 -0
  141. package/dist/shell/config.d.ts +66 -0
  142. package/dist/shell/config.js +1 -0
  143. package/dist/shell/icon-rail.d.ts +2 -0
  144. package/dist/shell/icon-rail.js +1 -0
  145. package/dist/shell/index.d.ts +7 -0
  146. package/dist/shell/index.js +1 -0
  147. package/dist/shell/mobile-nav.d.ts +7 -0
  148. package/dist/shell/mobile-nav.js +1 -0
  149. package/dist/shell/module-sidebar.d.ts +9 -0
  150. package/dist/shell/module-sidebar.js +1 -0
  151. package/dist/shell/notifications.d.ts +5 -0
  152. package/dist/shell/notifications.js +1 -0
  153. package/dist/shell/placeholder-screen.d.ts +11 -0
  154. package/dist/shell/placeholder-screen.js +1 -0
  155. package/dist/shell/record-drawer.d.ts +32 -0
  156. package/dist/shell/record-drawer.js +1 -0
  157. package/dist/shell/registry.d.ts +24 -0
  158. package/dist/shell/registry.js +1 -0
  159. package/dist/shell/topbar.d.ts +9 -0
  160. package/dist/shell/topbar.js +1 -0
  161. package/dist/shell/use-active.d.ts +21 -0
  162. package/dist/shell/use-active.js +1 -0
  163. package/dist/shell/user-menu.d.ts +5 -0
  164. package/dist/shell/user-menu.js +1 -0
  165. package/dist/store/app-store.d.ts +10 -0
  166. package/dist/store/app-store.js +1 -0
  167. package/dist/store/drawer-store.d.ts +22 -0
  168. package/dist/store/drawer-store.js +1 -0
  169. package/dist/store/index.d.ts +5 -0
  170. package/dist/store/index.js +1 -0
  171. package/dist/store/settings-store.d.ts +22 -0
  172. package/dist/store/settings-store.js +1 -0
  173. package/dist/store/ui-store.d.ts +28 -0
  174. package/dist/store/ui-store.js +1 -0
  175. package/dist/styles.css +658 -0
  176. package/dist/types.d.ts +140 -0
  177. package/dist/types.js +0 -0
  178. package/package.json +98 -4
@@ -0,0 +1,19 @@
1
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
2
+ import * as React from "react";
3
+ /**
4
+ * Tabs in four looks, one API — set `variant` on TabsList:
5
+ * underline — the default: record drawer, in-page sections.
6
+ * pill — a soft track with a raised chip: settings, filters, small panels.
7
+ * solid — the active tab in the brand gradient: the main switch of a page.
8
+ * enclosed — folder tabs joined to the panel below: editors, code, documents.
9
+ * The indicator slides to the chosen tab rather than jumping.
10
+ */
11
+ export type TabsVariant = "underline" | "pill" | "solid" | "enclosed";
12
+ export declare const Tabs: React.ForwardRefExoticComponent<TabsPrimitive.TabsProps & React.RefAttributes<HTMLDivElement>>;
13
+ export declare const TabsList: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
14
+ variant?: TabsVariant;
15
+ } & React.RefAttributes<HTMLDivElement>>;
16
+ export declare const TabsTrigger: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
17
+ count?: number;
18
+ } & React.RefAttributes<HTMLButtonElement>>;
19
+ export declare const TabsContent: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as l,jsxs as g}from"react/jsx-runtime";import*as b from"@radix-ui/react-tabs";import*as r from"react";import{cn as d}from"../../lib/utils.js";const f=r.createContext("underline"),L=b.Root,T={underline:"border-b border-border px-1",pill:"rounded-[11px] border border-border bg-hover p-1",solid:"rounded-[11px] border border-border bg-surface p-1",enclosed:"border-b border-border px-1"},h={underline:"bottom-[-1px] h-0.5 rounded-full bg-primary",pill:"inset-y-1 rounded-[8px] bg-raised shadow-[var(--lit),0_1px_2px_rgba(5,8,22,.08),0_2px_8px_-2px_rgba(5,8,22,.14)]",solid:"inset-y-1 rounded-[8px] bg-[linear-gradient(135deg,#1f45ea,#5b4af5)] shadow-[inset_0_1px_0_rgba(255,255,255,.25),0_4px_12px_-4px_rgba(20,55,211,.55)]",enclosed:""},R=r.forwardRef(({className:i,variant:e="underline",children:a,...p},o)=>{const n=r.useRef(null),[s,v]=r.useState(null);return r.useLayoutEffect(()=>{const t=n.current;if(!t||e==="enclosed")return;const u=()=>{const c=t.querySelector('[role="tab"][data-state="active"]');v(c?{left:c.offsetLeft,width:c.offsetWidth}:null)};u();const x=new MutationObserver(u);x.observe(t,{subtree:!0,attributes:!0,attributeFilter:["data-state"]});const m=new ResizeObserver(u);return m.observe(t),()=>{x.disconnect(),m.disconnect()}},[e]),l(f.Provider,{value:e,children:g(b.List,{ref:t=>{n.current=t,typeof o=="function"?o(t):o&&(o.current=t)},className:d("no-bar relative isolate flex items-center gap-0.5 overflow-x-auto",T[e],i),...p,children:[s&&e!=="enclosed"?l("span",{"aria-hidden":!0,className:d("absolute -z-10 transition-[left,width] duration-300 ease-[cubic-bezier(.3,.7,.2,1)]",h[e],e==="underline"&&"z-0"),style:{left:e==="underline"?s.left+6:s.left,width:e==="underline"?s.width-12:s.width}}):null,a]})})});R.displayName="TabsList";const y={underline:"px-2.5 py-2.5 text-muted hover:text-text data-[state=active]:text-primary",pill:"rounded-[8px] px-3 py-1.5 text-muted hover:text-text data-[state=active]:text-text",solid:"rounded-[8px] px-3.5 py-1.5 text-muted hover:text-text data-[state=active]:text-white",enclosed:"-mb-px rounded-t-[9px] border border-transparent px-3.5 py-2 text-muted hover:bg-hover/60 hover:text-text data-[state=active]:border-border data-[state=active]:border-b-canvas data-[state=active]:bg-canvas data-[state=active]:text-text"},w={underline:"bg-hover text-muted group-data-[state=active]/tab:bg-primary-subtle group-data-[state=active]/tab:text-primary",pill:"bg-raised text-muted group-data-[state=active]/tab:bg-primary-subtle group-data-[state=active]/tab:text-primary",solid:"bg-hover text-muted group-data-[state=active]/tab:bg-white/20 group-data-[state=active]/tab:text-white",enclosed:"bg-hover text-muted"},_=r.forwardRef(({className:i,children:e,count:a,...p},o)=>{const n=r.useContext(f);return l(b.Trigger,{ref:o,className:d("group/tab relative shrink-0 cursor-pointer whitespace-nowrap text-[13px] font-medium transition-colors duration-200 [&_svg]:size-4",y[n],i),...p,children:g("span",{className:"flex items-center gap-1.5",children:[e,typeof a=="number"?l("span",{className:d("num rounded-full px-1.5 text-[10px] leading-4 transition-colors",w[n]),children:a}):null]})})});_.displayName="TabsTrigger";const C=r.forwardRef(({className:i,...e},a)=>l(b.Content,{ref:a,className:d("outline-none data-[state=active]:animate-[wave-in_220ms_var(--ease)]",i),...e}));C.displayName="TabsContent";export{L as Tabs,C as TabsContent,R as TabsList,_ as TabsTrigger};
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Time picker — hour and minute columns (and AM/PM in 12-hour mode) instead of
4
+ * the browser's time input, which every browser draws differently.
5
+ *
6
+ * The value is "HH:mm" in 24-hour form or "" — what an API stores — whatever
7
+ * the display uses. `step` sets the minute grid (5 by default: nobody books a
8
+ * slot at 14:37). Digits and AM/PM follow `locale`.
9
+ */
10
+ export interface TimeParts {
11
+ h: number;
12
+ m: number;
13
+ }
14
+ export declare function parseTime(v: string | null | undefined): TimeParts | null;
15
+ export declare const toTime: ({ h, m }: TimeParts) => string;
16
+ /** "HH:mm" for display: 14:30, or 2:30 PM with hourCycle 12. */
17
+ export declare function formatTime(v: string, { locale, hourCycle }?: {
18
+ locale?: string;
19
+ hourCycle?: 12 | 24;
20
+ }): string;
21
+ /** The columns on their own — DateTimePicker uses them beside its calendar. */
22
+ export declare function TimeColumns({ value, onChange, step, hourCycle, min, max, locale, className, }: {
23
+ value: TimeParts | null;
24
+ onChange: (t: TimeParts) => void;
25
+ step?: number;
26
+ hourCycle?: 12 | 24;
27
+ /** Earliest / latest allowed, "HH:mm". */
28
+ min?: string;
29
+ max?: string;
30
+ locale?: string;
31
+ className?: string;
32
+ }): React.JSX.Element;
33
+ export declare function TimePicker({ value, onChange, id, placeholder, step, hourCycle, min, max, locale, disabled, clearable, className, labels, }: {
34
+ /** "HH:mm" (24-hour) or "". */
35
+ value: string;
36
+ onChange: (value: string) => void;
37
+ id?: string;
38
+ placeholder?: string;
39
+ step?: number;
40
+ hourCycle?: 12 | 24;
41
+ min?: string;
42
+ max?: string;
43
+ locale?: string;
44
+ disabled?: boolean;
45
+ clearable?: boolean;
46
+ className?: string;
47
+ labels?: {
48
+ now?: string;
49
+ done?: string;
50
+ clear?: string;
51
+ };
52
+ }): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as a,jsxs as d}from"react/jsx-runtime";import*as C from"@radix-ui/react-popover";import{Clock as O,X as z}from"lucide-react";import*as w from"react";import{cn as k}from"../../lib/utils.js";import{inputClass as G}from"./input.js";function M(n){const r=/^(\d{1,2}):(\d{2})/.exec(n??"");if(!r)return null;const l=Number(r[1]),o=Number(r[2]);return l<24&&o<60?{h:l,m:o}:null}const A=({h:n,m:r})=>`${String(n).padStart(2,"0")}:${String(r).padStart(2,"0")}`;function I(n,{locale:r="en-GB",hourCycle:l=24}={}){const o=M(n);return o?new Intl.DateTimeFormat(r,{hour:"numeric",minute:"2-digit",hourCycle:l===12?"h12":"h23"}).format(new Date(2026,0,1,o.h,o.m)):""}const D=n=>n.h*60+n.m;function R({value:n,onChange:r,step:l=5,hourCycle:o=24,min:g,max:P,locale:p="en-GB",className:S}){const h=w.useMemo(()=>new Intl.NumberFormat(p,{minimumIntegerDigits:2,useGrouping:!1}),[p]),T=w.useMemo(()=>{const e=new Intl.DateTimeFormat(p,{hour:"numeric",hourCycle:"h12"}),t=i=>e.formatToParts(new Date(2026,0,1,i)).find(E=>E.type==="dayPeriod")?.value??(i<12?"AM":"PM");return[t(9),t(15)]},[p]),x=M(g),N=M(P),b=e=>(!x||D(e)>=D(x))&&(!N||D(e)<=D(N)),s=n??{h:9,m:0},v=s.h>=12,c=o===12?Array.from({length:12},(e,t)=>t===0?12:t):Array.from({length:24},(e,t)=>t),m=(e,t=v)=>o===12?e%12+(t?12:0):e,y=Array.from({length:Math.ceil(60/l)},(e,t)=>t*l),u=e=>y.some(t=>b({h:e,m:t})),f=e=>{if(b(e))return r(e);const t=y.find(i=>b({h:e.h,m:i}));t!==void 0&&r({h:e.h,m:t})},H=(e,t)=>d("div",{className:"flex min-w-0 flex-1 flex-col",children:[a("p",{className:"pb-1.5 text-center text-[10.5px] font-semibold uppercase tracking-[0.08em] text-muted",children:e}),a("div",{role:"listbox","aria-label":e,className:"h-[236px] snap-y overflow-y-auto overscroll-contain rounded-ctl [scrollbar-width:none]",children:t.map(i=>a("button",{type:"button",role:"option","aria-selected":i.on,"data-on":i.on||void 0,disabled:i.disabled,onClick:i.pick,className:k("num block h-9 w-full cursor-pointer snap-start rounded-ctl text-center text-[13.5px] transition-colors",i.on?"bg-primary font-semibold text-white shadow-[0_1px_2px_rgba(5,8,22,.14)]":"text-text hover:bg-hover",i.disabled&&"cursor-not-allowed opacity-30 hover:bg-transparent"),children:i.text},i.key))})]}),_=w.useRef(null);return w.useEffect(()=>{_.current?.querySelectorAll("[data-on]").forEach(e=>{const t=e.parentElement;t.scrollTop=e.offsetTop-t.clientHeight/2+e.clientHeight/2})},[]),d("div",{ref:_,className:k("flex w-[200px] gap-1.5",o===12&&"w-[250px]",S),children:[H("Hour",c.map(e=>({key:`h${e}`,text:o===12?String(e):h.format(e),on:n!==null&&m(e)===s.h,disabled:!u(m(e)),pick:()=>f({h:m(e),m:s.m})}))),H("Min",y.map(e=>({key:`m${e}`,text:h.format(e),on:n!==null&&e===s.m,disabled:!b({h:s.h,m:e}),pick:()=>f({h:s.h,m:e})}))),o===12?H("",[!1,!0].map(e=>({key:e?"pm":"am",text:T[e?1:0],on:n!==null&&v===e,disabled:!u(m(s.h%12||12,e)),pick:()=>f({h:m(s.h%12||12,e),m:s.m})}))):null]})}function L({value:n,onChange:r,id:l,placeholder:o="Pick a time",step:g=5,hourCycle:P=24,min:p,max:S,locale:h="en-GB",disabled:T,clearable:x=!0,className:N,labels:b}){const[s,v]=w.useState(!1),c=M(n),m={now:"Now",done:"Done",clear:"Clear time",...b},y=()=>{const u=new Date,f=Math.round(u.getMinutes()/g)*g;return A({h:(u.getHours()+Math.floor(f/60))%24,m:f%60})};return d(C.Root,{open:s,onOpenChange:v,children:[d("div",{className:k("relative",N),children:[a(C.Trigger,{asChild:!0,disabled:T,children:d("button",{id:l,type:"button",className:k(G,"flex cursor-pointer items-center gap-2 text-left",s&&"border-primary ring-2 ring-primary/20",x&&c&&"pr-9"),children:[a(O,{className:"size-4 shrink-0 text-muted"}),a("span",{className:k("num min-w-0 flex-1 truncate",!c&&"text-placeholder"),children:c?I(n,{locale:h,hourCycle:P}):o})]})}),x&&c&&!T?a("button",{type:"button","aria-label":m.clear,onClick:()=>r(""),className:"absolute right-2 top-1/2 grid size-6 -translate-y-1/2 cursor-pointer place-items-center rounded-full text-muted transition-colors hover:bg-hover hover:text-text",children:a(z,{className:"size-3.5"})}):null]}),a(C.Portal,{children:d(C.Content,{"data-pop":"",align:"start",sideOffset:6,collisionPadding:12,className:"z-[60] rounded-dlg border border-border bg-raised p-3 shadow-elev3 outline-none",children:[a(R,{value:c,onChange:u=>r(A(u)),step:g,hourCycle:P,min:p,max:S,locale:h}),d("div",{className:"mt-2 flex items-center justify-between border-t border-border pt-2",children:[a("button",{type:"button",onClick:()=>r(y()),className:"cursor-pointer rounded-ctl px-2 py-1 text-[12.5px] font-medium text-primary transition-colors hover:bg-primary-subtle",children:m.now}),a("button",{type:"button",onClick:()=>v(!1),className:"cursor-pointer rounded-ctl bg-primary px-3 py-1 text-[12.5px] font-medium text-white transition-colors hover:bg-primary-hover",children:m.done})]})]})})]})}export{R as TimeColumns,L as TimePicker,I as formatTime,M as parseTime,A as toTime};
@@ -0,0 +1,13 @@
1
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
+ import * as React from "react";
3
+ export declare const TooltipProvider: React.FC<TooltipPrimitive.TooltipProviderProps>;
4
+ export declare const TooltipRoot: React.FC<TooltipPrimitive.TooltipProps>;
5
+ export declare const TooltipTrigger: React.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & React.RefAttributes<HTMLButtonElement>>;
6
+ export declare const TooltipContent: React.ForwardRefExoticComponent<Omit<TooltipPrimitive.TooltipContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
7
+ /** Convenience wrapper — the 95% case is "one trigger, one line of text". */
8
+ export declare function Tooltip({ label, shortcut, children, side, }: {
9
+ label: string;
10
+ shortcut?: string;
11
+ children: React.ReactNode;
12
+ side?: "top" | "right" | "bottom" | "left";
13
+ }): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as t,jsxs as l}from"react/jsx-runtime";import*as o from"@radix-ui/react-tooltip";import*as s from"react";import{cn as a}from"../../lib/utils.js";const T=o.Provider,d=o.Root,m=o.Trigger,n=s.forwardRef(({className:r,sideOffset:e=6,...i},p)=>t(o.Portal,{children:t(o.Content,{ref:p,sideOffset:e,"data-pop":"",className:a("z-[80] rounded-ctl border border-border bg-raised px-2.5 py-1.5 text-xs text-text shadow-elev2",r),...i})}));n.displayName="TooltipContent";function f({label:r,shortcut:e,children:i,side:p="bottom"}){return l(d,{children:[t(m,{asChild:!0,children:i}),t(n,{side:p,children:l("span",{className:"flex items-center gap-2",children:[r,e?t("kbd",{className:"rounded border border-border2 px-1 text-[10px] text-muted",children:e}):null]})})]})}export{f as Tooltip,n as TooltipContent,T as TooltipProvider,d as TooltipRoot,m as TooltipTrigger};
@@ -0,0 +1,4 @@
1
+ /** Hooks: small client-side helpers. */
2
+ export * from "./use-hotkey";
3
+ export * from "./use-media-query";
4
+ export * from "./use-mounted";
@@ -0,0 +1 @@
1
+ export*from"./use-hotkey.js";export*from"./use-media-query.js";export*from"./use-mounted.js";
@@ -0,0 +1,16 @@
1
+ type Combo = {
2
+ key: string;
3
+ mod?: boolean;
4
+ shift?: boolean;
5
+ alt?: boolean;
6
+ };
7
+ /**
8
+ * Global shortcut binding — docs/DESIGN-SYSTEM.md §9.
9
+ *
10
+ * Shortcuts without a modifier ("/", "g") are suppressed inside text fields;
11
+ * a user typing a customer name must never trigger navigation.
12
+ */
13
+ export declare function useHotkey(combo: Combo | Combo[], handler: (e: KeyboardEvent) => void, enabled?: boolean): void;
14
+ /** Alt+1…9 → module n. Separate hook because the key is dynamic. */
15
+ export declare function useNumberHotkeys(handler: (index: number) => void): void;
16
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{useEffect as a}from"react";function y(e){return e instanceof HTMLElement?e.tagName==="INPUT"||e.tagName==="TEXTAREA"||e.tagName==="SELECT"||e.isContentEditable:!1}function d(e,r,t=!0){a(()=>{if(!t)return;const s=Array.isArray(e)?e:[e],i=n=>{for(const o of s){const u=n.metaKey||n.ctrlKey;if(o.key.toLowerCase()===n.key.toLowerCase()&&!!o.mod===u&&!!o.shift===n.shiftKey&&!!o.alt===n.altKey&&!(!o.mod&&!o.alt&&y(n.target))){n.preventDefault(),r(n);return}}};return window.addEventListener("keydown",i),()=>window.removeEventListener("keydown",i)},[e,r,t])}function m(e){a(()=>{const r=t=>{!t.altKey||t.metaKey||t.ctrlKey||/^[1-9]$/.test(t.key)&&(t.preventDefault(),e(Number(t.key)-1))};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[e])}export{d as useHotkey,m as useNumberHotkeys};
@@ -0,0 +1,4 @@
1
+ /** SSR-safe media query. Returns false on the server, so the desktop shell is
2
+ * the default render and mobile adjusts after mount. */
3
+ export declare function useMediaQuery(query: string): boolean;
4
+ export declare const useIsMobile: () => boolean;
@@ -0,0 +1 @@
1
+ "use client";import{useEffect as i,useState as o}from"react";function r(t){const[a,n]=o(!1);return i(()=>{const e=window.matchMedia(t);n(e.matches);const s=c=>n(c.matches);return e.addEventListener("change",s),()=>e.removeEventListener("change",s)},[t]),a}const m=()=>r("(max-width: 1024px)");export{m as useIsMobile,r as useMediaQuery};
@@ -0,0 +1,2 @@
1
+ /** True only after hydration — guards anything that reads the DOM or localStorage. */
2
+ export declare function useMounted(): boolean;
@@ -0,0 +1 @@
1
+ "use client";import{useEffect as u,useState as n}from"react";function s(){const[e,t]=n(!1);return u(()=>t(!0),[]),e}export{s as useMounted};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @waveinno/ui — the WAVEINNO design system as a package.
3
+ *
4
+ * Tokens live in `@waveinno/ui/styles.css`; everything else is exported from
5
+ * here, one barrel per folder. A new component is a file in its folder plus a
6
+ * line in that folder's index.ts (`npm run new` does both). Products provide
7
+ * their own registry through <AppShell config={…}>.
8
+ */
9
+ export * from "./types";
10
+ export * from "./components/ui";
11
+ export * from "./components/brand";
12
+ export * from "./components/charts";
13
+ export * from "./components/patterns";
14
+ export * from "./shell";
15
+ export * from "./store";
16
+ export * from "./hooks";
17
+ export * from "./lib";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export*from"./types.js";export*from"./components/ui/index.js";export*from"./components/brand/index.js";export*from"./components/charts/index.js";export*from"./components/patterns/index.js";export*from"./shell/index.js";export*from"./store/index.js";export*from"./hooks/index.js";export*from"./lib/index.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Appearance keys and the pre-paint boot script.
3
+ *
4
+ * Kept out of the "use client" store so a server layout can import the script
5
+ * as a plain string — importing a value from a client module in a server
6
+ * component yields a client reference, not the value.
7
+ */
8
+ export declare const THEME_KEY = "waveinno.theme";
9
+ export declare const DENSITY_KEY = "waveinno.density";
10
+ export declare const PANEL_KEY = "waveinno.panel";
11
+ /**
12
+ * Injected into <head>; runs before paint, so the theme never flashes.
13
+ * `defaultDensity` lets the Admin portal start compact (doc 29 §4).
14
+ */
15
+ export declare function themeBootScript(defaultDensity?: "compact" | "default" | "comfortable"): string;
@@ -0,0 +1,12 @@
1
+ const e="waveinno.theme",a="waveinno.density",o="waveinno.panel";function r(t="default"){return`
2
+ (function(){try{
3
+ var t=localStorage.getItem("${e}");
4
+ if(!t)t=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";
5
+ var d=localStorage.getItem("${a}")||"${t}";
6
+ var e=document.documentElement;
7
+ e.setAttribute("data-theme",t);
8
+ e.setAttribute("data-density",d);
9
+ e.style.colorScheme=t;
10
+ if(localStorage.getItem("${o}")==="1")e.setAttribute("data-panel-collapsed","");
11
+ }catch(_){}})();
12
+ `}export{a as DENSITY_KEY,o as PANEL_KEY,e as THEME_KEY,r as themeBootScript};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * CURRENCY
3
+ *
4
+ * The reference prototype hard-codes `৳` into every amount. That is correct for
5
+ * one deployment and wrong for a master template: the same build runs for a
6
+ * group whose companies invoice in BDT, USD and AED, and the symbol is a
7
+ * *setting*, not a constant.
8
+ *
9
+ * Three things are configurable, and they are genuinely independent:
10
+ *
11
+ * • **Which currency** — belongs to the company, because it is an accounting
12
+ * fact. Switching company in the topbar switches the symbol.
13
+ * • **Digit grouping** — South Asian (1,80,240) or Western (180,240). This is
14
+ * a reader preference, not a property of the currency: a Dhaka finance team
15
+ * reading USD still reads in lakh.
16
+ * • **Symbol position and decimals** — display only.
17
+ *
18
+ * Amounts are always stored as numbers. Nothing in the data layer holds a
19
+ * formatted string, so changing the setting reformats every figure in the
20
+ * product without touching a record.
21
+ */
22
+ export type Grouping = "south-asian" | "western";
23
+ export type SymbolPosition = "before" | "after";
24
+ export interface CurrencyDef {
25
+ code: string;
26
+ symbol: string;
27
+ name: string;
28
+ decimals: number;
29
+ /** What this currency's home market expects, used as the default. */
30
+ grouping: Grouping;
31
+ }
32
+ export declare const CURRENCIES: CurrencyDef[];
33
+ export declare const CURRENCY_MAP: Record<string, CurrencyDef>;
34
+ export declare const DEFAULT_CURRENCY: CurrencyDef;
35
+ export interface CurrencyConfig {
36
+ code: string;
37
+ symbol: string;
38
+ decimals: number;
39
+ grouping: Grouping;
40
+ position: SymbolPosition;
41
+ }
42
+ /** Grouped digits, no symbol. */
43
+ export declare function groupDigits(value: number, cfg: CurrencyConfig): string;
44
+ export declare function groupPlain(value: number, grouping: Grouping): string;
45
+ /**
46
+ * Compact magnitude for KPI tiles. South Asian readers want lakh and crore;
47
+ * Western readers want K/M/B. Rendering "48.2L" to someone who thinks in
48
+ * millions is not a formatting nicety — it is an unreadable number.
49
+ */
50
+ export declare function compact(value: number, cfg: CurrencyConfig): string;
51
+ /** Full string including the symbol — for places that cannot render JSX. */
52
+ export declare function formatMoney(value: number, cfg: CurrencyConfig, opts?: {
53
+ compact?: boolean;
54
+ }): string;
55
+ export declare function configFor(code: string, overrides?: Partial<Omit<CurrencyConfig, "code">>): CurrencyConfig;
@@ -0,0 +1 @@
1
+ const t=[{code:"BDT",symbol:"\u09F3",name:"Bangladeshi taka",decimals:2,grouping:"south-asian"},{code:"INR",symbol:"\u20B9",name:"Indian rupee",decimals:2,grouping:"south-asian"},{code:"PKR",symbol:"\u20A8",name:"Pakistani rupee",decimals:2,grouping:"south-asian"},{code:"LKR",symbol:"Rs",name:"Sri Lankan rupee",decimals:2,grouping:"south-asian"},{code:"NPR",symbol:"\u0930\u0942",name:"Nepalese rupee",decimals:2,grouping:"south-asian"},{code:"USD",symbol:"$",name:"US dollar",decimals:2,grouping:"western"},{code:"EUR",symbol:"\u20AC",name:"Euro",decimals:2,grouping:"western"},{code:"GBP",symbol:"\xA3",name:"Pound sterling",decimals:2,grouping:"western"},{code:"AED",symbol:"\u062F.\u0625",name:"UAE dirham",decimals:2,grouping:"western"},{code:"SAR",symbol:"\uFDFC",name:"Saudi riyal",decimals:2,grouping:"western"},{code:"MYR",symbol:"RM",name:"Malaysian ringgit",decimals:2,grouping:"western"},{code:"SGD",symbol:"S$",name:"Singapore dollar",decimals:2,grouping:"western"},{code:"AUD",symbol:"A$",name:"Australian dollar",decimals:2,grouping:"western"},{code:"CAD",symbol:"C$",name:"Canadian dollar",decimals:2,grouping:"western"},{code:"CNY",symbol:"\xA5",name:"Chinese yuan",decimals:2,grouping:"western"},{code:"JPY",symbol:"\xA5",name:"Japanese yen",decimals:0,grouping:"western"}],i=Object.fromEntries(t.map(e=>[e.code,e])),a=i.BDT,s={"south-asian":"en-IN",western:"en-US"};function m(e,n){return new Intl.NumberFormat(s[n.grouping],{minimumFractionDigits:n.decimals,maximumFractionDigits:n.decimals}).format(e)}function c(e,n){return new Intl.NumberFormat(s[n]).format(e)}function g(e,n){const o=Math.abs(e);if(n.grouping==="south-asian"){if(o>=1e7)return`${(e/1e7).toFixed(2).replace(/\.00$/,"")}Cr`;if(o>=1e5)return`${(e/1e5).toFixed(1).replace(/\.0$/,"")}L`;if(o>=1e3)return`${(e/1e3).toFixed(1).replace(/\.0$/,"")}K`}else{if(o>=1e9)return`${(e/1e9).toFixed(2).replace(/\.00$/,"")}B`;if(o>=1e6)return`${(e/1e6).toFixed(2).replace(/\.00$/,"")}M`;if(o>=1e3)return`${(e/1e3).toFixed(1).replace(/\.0$/,"")}K`}return c(e,n.grouping)}function u(e,n,o={}){const r=o.compact?g(e,n):m(e,n);return n.position==="before"?`${n.symbol}${r}`:`${r} ${n.symbol}`}function l(e,n={}){const o=i[e]??a;return{code:o.code,symbol:n.symbol??o.symbol,decimals:n.decimals??o.decimals,grouping:n.grouping??o.grouping,position:n.position??"before"}}export{t as CURRENCIES,i as CURRENCY_MAP,a as DEFAULT_CURRENCY,g as compact,l as configFor,u as formatMoney,m as groupDigits,c as groupPlain};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Numeric formatting rules — docs/DESIGN-SYSTEM.md §2.5 and §8.
3
+ *
4
+ * Three laws, inherited from the reference prototype:
5
+ * 1. Zero and null render as an em dash, never as "0" — a real zero balance and
6
+ * "no data" are different facts and must not look identical.
7
+ * 2. Money and quantities use South Asian digit grouping (1,80,240), because the
8
+ * product is read in lakh/crore, not thousands/millions.
9
+ * 3. The currency symbol is a separate muted span so the magnitude leads the eye.
10
+ */
11
+ export declare const DASH = "\u2014";
12
+ type Num = number | null | undefined;
13
+ /** Whole counts: bags, orders, users. */
14
+ export declare function fmtNumber(n: Num): string;
15
+ /** Money amount without the symbol — pair with <Money /> or a muted ৳ span. */
16
+ export declare function fmtAmount(n: Num): string;
17
+ /** Weights in kg, three decimals as the weighbridge reports them. */
18
+ export declare function fmtWeight(n: Num): string;
19
+ /** Compact magnitude for KPI tiles: 4820000 -> "48.2L". */
20
+ export declare function fmtCompact(n: Num): string;
21
+ export declare function fmtPercent(n: Num, digits?: number): string;
22
+ /** Signed delta for trend lines: +3.1 / -2.4 */
23
+ export declare function fmtDelta(n: Num, digits?: number): string;
24
+ export declare function fmtDate(d: string | Date | null | undefined): string;
25
+ export declare function fmtDateTime(d: string | Date | null | undefined): string;
26
+ /** "12 min ago" — relative to a caller-supplied now so SSR stays deterministic. */
27
+ export declare function fmtRelative(d: string | Date | null | undefined, now?: Date): string;
28
+ export {};
@@ -0,0 +1 @@
1
+ const D="\u2014",m=new Intl.NumberFormat("en-IN"),a=new Intl.NumberFormat("en-IN",{minimumFractionDigits:2,maximumFractionDigits:2}),f=new Intl.NumberFormat("en-IN",{minimumFractionDigits:3,maximumFractionDigits:3}),r=t=>t==null||Number.isNaN(t);function d(t){return r(t)||t===0?"\u2014":m.format(t)}function l(t){return r(t)||t===0?"\u2014":a.format(t)}function p(t){return r(t)||t===0?"\u2014":f.format(t)}function N(t){if(r(t)||t===0)return"\u2014";const e=Math.abs(t);return e>=1e7?`${(t/1e7).toFixed(2).replace(/\.00$/,"")}Cr`:e>=1e5?`${(t/1e5).toFixed(1).replace(/\.0$/,"")}L`:e>=1e3?`${(t/1e3).toFixed(1).replace(/\.0$/,"")}K`:m.format(t)}function x(t,e=1){return r(t)?"\u2014":`${t.toFixed(e)}%`}function F(t,e=1){return r(t)?"\u2014":`${t>0?"+":""}${t.toFixed(e)}`}const s=new Intl.DateTimeFormat("en-GB",{day:"2-digit",month:"short",year:"numeric"}),g=new Intl.DateTimeFormat("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit",hour12:!1});function c(t){return t?s.format(typeof t=="string"?new Date(t):t):"\u2014"}function h(t){return t?g.format(typeof t=="string"?new Date(t):t):"\u2014"}function $(t,e=new Date){if(!t)return"\u2014";const u=typeof t=="string"?new Date(t):t,n=Math.round((e.getTime()-u.getTime())/6e4);if(n<1)return"just now";if(n<60)return`${n} min ago`;const i=Math.round(n/60);if(i<24)return`${i} hr ago`;const o=Math.round(i/24);return o<30?`${o} d ago`:c(u)}export{D as DASH,l as fmtAmount,N as fmtCompact,c as fmtDate,h as fmtDateTime,F as fmtDelta,d as fmtNumber,x as fmtPercent,$ as fmtRelative,p as fmtWeight};
@@ -0,0 +1,5 @@
1
+ /** Lib: framework-free helpers (formatting, currency, class names, the theme boot script). */
2
+ export * from "./currency";
3
+ export * from "./format";
4
+ export * from "./utils";
5
+ export * from "./boot-script";
@@ -0,0 +1 @@
1
+ export*from"./currency.js";export*from"./format.js";export*from"./utils.js";export*from"./boot-script.js";
@@ -0,0 +1,7 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ /** Initials for avatars: "Nazrul Islam" -> "NI". Never more than two glyphs. */
4
+ export declare function initials(name: string): string;
5
+ /** Stable pseudo-random in [0,1) from a string — keeps mock data identical between
6
+ * server render and hydration, which Math.random() would not. */
7
+ export declare function seeded(seed: string): number;
@@ -0,0 +1 @@
1
+ import{clsx as l}from"clsx";import{extendTailwindMerge as i}from"tailwind-merge";const n=i({extend:{theme:{radius:["ctl","card","dlg"],shadow:["elev1","elev2","elev3"],spacing:["rail","panel","topbar","drawer","row","ctl","item","cellx","celly"]}}});function s(...e){return n(l(e))}function c(e){return e.split(/\s+/).filter(Boolean).slice(0,2).map(t=>t[0].toUpperCase()).join("")}function p(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t=Math.imul(t,16777619);return(t>>>0)%1e5/1e5}export{s as cn,c as initials,p as seeded};
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ import { type ShellConfig } from "./config";
3
+ /**
4
+ * THE SHELL — docs/DESIGN-SYSTEM.md §5.
5
+ *
6
+ * ┌────┬──────────┬─────────────────────────────┐
7
+ * │ │ │ topbar │ rail + panel span both
8
+ * │rail│ panel ├─────────────────────────────┤ rows, so the brand mark
9
+ * │72px│ 240px │ main [ drawer ] │ aligns with the rail and
10
+ * └────┴──────────┴─────────────────────────────┘ not with a full header.
11
+ *
12
+ * Collapse is a token change (--panel → 0), so the grid animates rather than
13
+ * the DOM reflowing. See styles.css → SHELL GEOMETRY.
14
+ */
15
+ export declare function AppShell({ config, children, }: {
16
+ config: ShellConfig;
17
+ children: React.ReactNode;
18
+ }): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as u}from"react/jsx-runtime";import{useRouter as g}from"next/navigation";import*as i from"react";import{Toaster as v}from"sonner";import{TooltipProvider as R}from"../components/ui/tooltip.js";import{useHotkey as x,useNumberHotkeys as N}from"../hooks/use-hotkey.js";import{useApp as c}from"../store/app-store.js";import{useUi as f}from"../store/ui-store.js";import{CommandPalette as S}from"./command-palette.js";import{ShellProvider as w}from"./config.js";import{IconRail as P}from"./icon-rail.js";import{MobileNav as k}from"./mobile-nav.js";import{ModuleSidebar as A}from"./module-sidebar.js";import{RecordDrawer as C}from"./record-drawer.js";import{Topbar as D}from"./topbar.js";import{useActive as F}from"./use-active.js";function V({config:t,children:r}){return e(w,{config:t,children:e(T,{children:r})})}function T({children:t}){const r=f(o=>o.hydrate),a=c(o=>o.hydrate),m=c(o=>o.syncFromRoute),h=f(o=>o.togglePanel),l=g(),{cfg:p,pathname:y,mod:s,rail:b}=F();i.useEffect(()=>{r(),a(p.apps.map(o=>o.id))},[r,a,p.apps]),i.useEffect(()=>{m(s.app)},[s.app,m]),x({key:"b",mod:!0},h);const n=b.order;return N(i.useCallback(o=>{const d=n[o];d&&l.push(d.href)},[n,l])),u(R,{delayDuration:400,skipDelayDuration:200,children:[u("div",{className:"shell-grid overflow-hidden",children:[e(P,{}),e(A,{}),e(D,{}),e("main",{id:"main",className:"col-start-3 col-end-4 row-start-2 overflow-y-auto px-4 pb-16 pt-5 sm:px-6",children:e("div",{className:"page-in mx-auto w-full max-w-[1600px]",children:t},y)})]}),e(C,{}),e(S,{}),e(k,{}),e(v,{position:"bottom-right",toastOptions:{className:"!rounded-ctl !border !border-border !bg-raised !text-text !shadow-elev3 !text-[13px]"}})]})}export{V as AppShell};
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ /**
3
+ * App switcher — the suite's front door.
4
+ *
5
+ * Only rendered when a product has more than one app (the Admin portal has
6
+ * Platform and WaveReach; the SaaS portal has one and hides this). It answers
7
+ * which apps exist and what each carries before the choice is made.
8
+ */
9
+ export declare function AppSwitcher(): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{Fragment as _,jsx as t,jsxs as s}from"react/jsx-runtime";import{ArrowRight as E,Check as I,CornerDownLeft as R}from"lucide-react";import{useRouter as S}from"next/navigation";import*as f from"react";import{WaveWordmark as T}from"../components/brand/logo.js";import{StatusBadge as K}from"../components/ui/badge.js";import{Button as B}from"../components/ui/button.js";import{Dialog as O,DialogContent as L,DialogDescription as H,DialogTitle as U}from"../components/ui/dialog.js";import{Tooltip as W}from"../components/ui/tooltip.js";import{useHotkey as q}from"../hooks/use-hotkey.js";import{cn as y}from"../lib/utils.js";import{useApp as F}from"../store/app-store.js";import{useUi as k}from"../store/ui-store.js";import{useActive as P}from"./use-active.js";function ae(){const i=k(e=>e.launcherOpen),l=k(e=>e.setLauncher),w=F(e=>e.setApp),{cfg:p,app:n}=P(),D=S(),r=p.apps,x=p.isEntitled??(()=>!0),c=Math.max(0,r.findIndex(e=>e.id===n.id)),[m,d]=f.useState(c),h=f.useRef(null);q({key:"k",mod:!0,shift:!0},()=>l(!i)),f.useEffect(()=>{i&&d(c)},[i,c]);const o=r[m]??r[c],b=p.modules.filter(e=>e.app===o.id&&x(e)),v=e=>{const a=r[e];a&&(w(a.id),l(!1),D.push(a.href))},A=e=>{e.key==="ArrowDown"?(e.preventDefault(),d(a=>Math.min(a+1,r.length-1))):e.key==="ArrowUp"?(e.preventDefault(),d(a=>Math.max(a-1,0))):e.key==="Enter"&&(e.preventDefault(),v(m))},z=n.icon,C=o.icon;return s(_,{children:[t(W,{label:"Switch app",shortcut:"\u2318\u21E7K",children:s(B,{variant:"secondary",size:"sm",onClick:()=>l(!0),className:"h-8 shrink-0 rounded-full pl-1.5","aria-label":`App: ${n.name}. Switch app`,children:[t("span",{className:"grid size-5 shrink-0 place-items-center rounded-full text-white",style:{background:n.tint},"aria-hidden":!0,children:t(z,{className:"size-3"})}),t("span",{className:"hidden max-w-[110px] truncate md:inline",children:n.name})]})}),t(O,{open:i,onOpenChange:l,children:s(L,{variant:"center",width:"max-w-[720px]",hideClose:!0,className:"overflow-hidden p-0",onKeyDown:A,onOpenAutoFocus:e=>{e.preventDefault(),h.current?.focus()},children:[t(U,{className:"sr-only",children:"Switch app"}),t(H,{className:"sr-only",children:"Choose which app to open. The rail carries that app\u2019s modules."}),s("div",{className:"grid h-[60dvh] max-h-[400px] grid-cols-1 md:h-[400px] md:grid-cols-[260px_minmax(0,1fr)]",children:[s("div",{className:"flex min-h-0 flex-col border-border bg-surface md:border-r",children:[t("div",{className:"shrink-0 border-b border-border px-4 pb-3 pt-3.5",children:t(T,{size:"sm",suffix:"Apps you can operate"})}),t("ul",{className:"min-h-0 flex-1 overflow-y-auto p-1.5 pb-3",children:r.map((e,a)=>{const M=e.icon,u=e.id===n.id,g=a===m;return t("li",{children:s("button",{type:"button",ref:u?h:void 0,onMouseMove:()=>d(a),onClick:()=>v(a),className:y("flex w-full cursor-pointer items-center gap-2.5 rounded-ctl px-2.5 py-2 text-left transition-colors",g?"bg-raised shadow-elev2":"hover:bg-hover"),children:[t("span",{className:"grid size-7 shrink-0 place-items-center rounded-[7px] text-white",style:{background:e.tint},"aria-hidden":!0,children:t(M,{className:"size-3.5"})}),s("span",{className:"min-w-0 flex-1",children:[t("span",{className:y("block truncate text-[13px]",u?"font-semibold text-primary":"text-text"),children:e.name}),s("span",{className:"num block text-[11px] text-muted",children:[p.modules.filter(N=>N.app===e.id&&x(N)).length," modules"]})]}),u?t(I,{className:"size-3.5 shrink-0 text-primary"}):g?t(E,{className:"size-3.5 shrink-0 text-muted"}):null]})},e.id)})})]}),s("div",{className:"hidden min-h-0 flex-col md:flex",children:[s("div",{className:"flex shrink-0 items-start gap-3 border-b border-border px-5 py-4",children:[t("span",{className:"grid size-9 shrink-0 place-items-center rounded-ctl text-white",style:{background:o.tint},"aria-hidden":!0,children:t(C,{className:"size-4"})}),s("div",{className:"min-w-0 flex-1",children:[t("h2",{className:"disp text-[15px] font-semibold",children:o.name}),t("p",{className:"mt-0.5 text-xs text-muted",children:o.tagline})]}),o.status?t(K,{label:o.status.label,tone:o.status.tone}):null]}),s("div",{className:"min-h-0 flex-1 overflow-y-auto px-5 py-4",children:[s("div",{className:"ov mb-2.5",children:[b.length," modules"]}),t("ul",{className:"grid grid-cols-1 gap-x-4 gap-y-0.5 sm:grid-cols-2",children:b.map(e=>{const a=e.icon;return s("li",{className:"flex items-center gap-2.5 rounded-ctl px-1.5 py-1.5",children:[t(a,{className:"size-3.5 shrink-0 text-body"}),t("span",{className:"min-w-0 flex-1 truncate text-[13px] text-text",children:e.title})]},e.key)})})]})]})]}),s("div",{className:"flex items-center gap-4 border-t border-border bg-surface px-4 py-2 text-[11px] text-muted",children:[s("span",{className:"flex items-center gap-1",children:[t("kbd",{className:"rounded border border-border2 px-1",children:"\u2191"}),t("kbd",{className:"rounded border border-border2 px-1",children:"\u2193"})," navigate"]}),s("span",{className:"flex items-center gap-1",children:[t(R,{className:"size-3"})," open"]}),t("span",{className:"ml-auto hidden sm:inline",children:"Every screen stays in \u2318K"})]})]})})]})}export{ae as AppSwitcher};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Theme and density — §4.4.
3
+ *
4
+ * Both are attributes on <html>, written before paint by the boot script, so
5
+ * these controls render a placeholder until mount rather than guessing and
6
+ * causing a hydration mismatch.
7
+ */
8
+ export declare function ThemeToggle(): import("react").JSX.Element;
9
+ export declare function DensityToggle(): import("react").JSX.Element;
10
+ /** Labelled variant for the settings page and the account menu. */
11
+ export declare function DensityPicker({ compact }: {
12
+ compact?: boolean;
13
+ }): import("react").JSX.Element | null;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as f}from"react/jsx-runtime";import{Moon as m,Rows3 as c,Sun as p}from"lucide-react";import{Segmented as s}from"../components/ui/segmented.js";import{Tooltip as d}from"../components/ui/tooltip.js";import{useMounted as r}from"../hooks/use-mounted.js";import{useUi as l}from"../store/ui-store.js";function g(){const o=r(),a=l(t=>t.theme),i=l(t=>t.setTheme);return o?e(s,{ariaLabel:"Colour theme",value:a,onChange:i,options:[{value:"light",label:e(p,{className:"size-3.5"}),title:"Light"},{value:"dark",label:e(m,{className:"size-3.5"}),title:"Dark"}]}):e("div",{className:"h-[26px] w-[62px] rounded-full bg-hover","aria-hidden":!0})}function D(){const o=r(),a=l(t=>t.density),i=l(t=>t.setDensity);return o?e(d,{label:"Row density",children:e("div",{children:e(s,{ariaLabel:"Row density",value:a,onChange:i,options:[{value:"compact",label:"S",title:"Compact \u2014 32px rows"},{value:"default",label:"M",title:"Default \u2014 40px rows"},{value:"comfortable",label:"L",title:"Comfortable \u2014 48px rows"}]})})}):e("div",{className:"h-[26px] w-[86px] rounded-full bg-hover","aria-hidden":!0})}function w({compact:o}){const a=r(),i=l(n=>n.density),t=l(n=>n.setDensity);if(!a)return null;const u=e(s,{ariaLabel:"Row density",value:i,onChange:t,options:o?[{value:"compact",label:"S",title:"Compact \u2014 32px rows"},{value:"default",label:"M",title:"Default \u2014 40px rows"},{value:"comfortable",label:"L",title:"Comfortable \u2014 48px rows"}]:[{value:"compact",label:"Compact"},{value:"default",label:"Default"},{value:"comfortable",label:"Comfortable"}]});return o?u:f("div",{className:"flex items-center gap-2",children:[e(c,{className:"size-4 text-muted"}),u]})}export{w as DensityPicker,D as DensityToggle,g as ThemeToggle};
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Command palette — the fastest path to any screen.
4
+ *
5
+ * Ordering rule: Actions before AI before Screens before Modules, because a
6
+ * user who types "crawl" more often wants to *start* one than to read about it.
7
+ * Only entitled modules are listed — ⌘K must not be a back door to locked work.
8
+ */
9
+ export declare function CommandPalette(): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as o,jsxs as a}from"react/jsx-runtime";import{CornerDownLeft as M,Moon as E,Search as I,Sparkles as R,Sun as A}from"lucide-react";import{useRouter as L}from"next/navigation";import*as s from"react";import{Dialog as T,DialogContent as O,DialogTitle as z}from"../components/ui/dialog.js";import{useHotkey as C}from"../hooks/use-hotkey.js";import{cn as D}from"../lib/utils.js";import{useUi as h}from"../store/ui-store.js";import{useShell as $}from"./config.js";function Q(){const l=$(),i=h(e=>e.paletteOpen),c=h(e=>e.setPalette),g=h(e=>e.toggleTheme),x=h(e=>e.theme),k=L(),[d,v]=s.useState(""),[u,m]=s.useState(0),y=s.useRef(null);C({key:"k",mod:!0},()=>c(!i)),C({key:"/"},()=>c(!0));const b=s.useMemo(()=>{const e=[...l.commands??[]];e.push({id:"act-theme",label:x==="dark"?"Switch to light theme":"Switch to dark theme",group:"Actions",icon:x==="dark"?A:E,run:g});const n=l.isEntitled??(()=>!0);for(const t of l.modules.filter(n)){e.push({id:`mod-${t.key}`,label:t.title,hint:t.description,group:"Modules",icon:t.icon,href:t.href});for(const r of t.nav)r.kind==="item"&&e.push({id:`scr-${t.key}-${r.href}`,label:r.label,hint:t.title,group:"Screens",icon:t.icon,href:r.href})}return e},[l.commands,l.modules,l.isEntitled,x,g]),p=s.useMemo(()=>{const e=d.trim().toLowerCase(),n=e?b.filter(r=>r.label.toLowerCase().includes(e)||r.hint?.toLowerCase().includes(e)):b.filter(r=>r.group!=="Screens"),t=["Actions","AI","Screens","Modules","Records"];return n.slice().sort((r,f)=>t.indexOf(r.group)-t.indexOf(f.group)).slice(0,40)},[b,d]);s.useEffect(()=>m(0),[d]),s.useEffect(()=>{i||v("")},[i]);const N=s.useCallback(e=>{e&&(c(!1),e.run?e.run():e.href&&k.push(e.href))},[k,c]),S=e=>{e.key==="ArrowDown"?(e.preventDefault(),m(n=>Math.min(n+1,p.length-1))):e.key==="ArrowUp"?(e.preventDefault(),m(n=>Math.max(n-1,0))):e.key==="Enter"&&(e.preventDefault(),N(p[u]))};s.useEffect(()=>{y.current?.querySelector(`[data-index="${u}"]`)?.scrollIntoView({block:"nearest"})},[u]);let w="";return o(T,{open:i,onOpenChange:c,children:a(O,{variant:"center",width:"max-w-[580px]",hideClose:!0,className:"top-[14vh] translate-y-0 overflow-hidden p-0 shadow-elev3",onKeyDown:S,children:[o(z,{className:"sr-only",children:"Command palette"}),a("div",{className:"flex items-center gap-2.5 border-b border-border px-4",children:[o(I,{className:"size-4 shrink-0 text-muted"}),o("input",{autoFocus:!0,value:d,onChange:e=>v(e.target.value),placeholder:"Search screens, run an agent, or ask\u2026",className:"h-12 flex-1 bg-transparent text-sm text-text outline-none placeholder:text-placeholder"}),o("kbd",{className:"rounded border border-border2 px-1.5 text-[11px] text-muted",children:"esc"})]}),o("div",{ref:y,className:"max-h-[54vh] overflow-y-auto p-1.5",children:p.length===0?a("p",{className:"px-3 py-8 text-center text-[13px] text-muted",children:["Nothing matches \u201C",d,"\u201D."]}):p.map((e,n)=>{const t=e.icon,r=e.group!==w?e.group:null;w=e.group;const f=n===u;return a(s.Fragment,{children:[r?o("div",{className:"ov px-2.5 pb-1 pt-2.5",children:r}):null,a("button",{type:"button","data-index":n,onMouseMove:()=>m(n),onClick:()=>N(e),className:D("flex w-full cursor-pointer items-center gap-2.5 rounded-ctl px-2.5 py-2 text-left text-[13px] transition-colors",f?"bg-hover text-text":"text-body"),children:[t?o(t,{className:D("size-4 shrink-0",e.group==="AI"?"text-accent":"text-muted")}):null,o("span",{className:"flex-1 truncate",children:e.label}),e.hint?o("span",{className:"hidden max-w-[45%] truncate text-[11px] text-muted sm:inline",children:e.hint}):null,f?o(M,{className:"size-3.5 shrink-0 text-muted"}):null]})]},e.id)})}),a("div",{className:"flex items-center gap-4 border-t border-border bg-surface px-4 py-2 text-[11px] text-muted",children:[a("span",{className:"flex items-center gap-1",children:[o("kbd",{className:"rounded border border-border2 px-1",children:"\u2191"}),o("kbd",{className:"rounded border border-border2 px-1",children:"\u2193"})," navigate"]}),a("span",{className:"flex items-center gap-1",children:[o("kbd",{className:"rounded border border-border2 px-1",children:"\u21B5"})," open"]}),a("span",{className:"ml-auto flex items-center gap-1",children:[o(R,{className:"size-3 text-accent"})," agents run with a credit estimate first"]})]})]})})}export{Q as CommandPalette};
@@ -0,0 +1,66 @@
1
+ import * as React from "react";
2
+ import type { AppDef, CommandItem, MenuLink, ModuleDef, NotificationItem, ShellUser } from "../types";
3
+ /**
4
+ * Everything a product tells the shell about itself.
5
+ *
6
+ * The template's shell imported its registry, tenant and notifications
7
+ * directly, which made it one product's shell. Lifting those into a config is
8
+ * what lets the SaaS portal and the Admin portal run the same rail, panel,
9
+ * palette and drawer — each with its own apps, modules and scope.
10
+ */
11
+ export interface ShellConfig {
12
+ apps: AppDef[];
13
+ modules: ModuleDef[];
14
+ /**
15
+ * Hide the app switcher when there is only one app to switch to. The rail
16
+ * mark still carries the app badge — the company and the product are both
17
+ * always on screen (§4.5).
18
+ */
19
+ showAppSwitcher?: boolean;
20
+ /** Plan or role gate. Locked modules never appear in the rail. */
21
+ isEntitled?: (m: ModuleDef) => boolean;
22
+ /** Scope selector for the topbar and the mobile sheet (org → workspace → site). */
23
+ scope?: React.ReactNode;
24
+ /** Scope rendered with its label, for the mobile sheet. */
25
+ scopeLabelled?: React.ReactNode;
26
+ /** Extra topbar controls before notifications — e.g. a credits pill. */
27
+ topbarExtra?: React.ReactNode;
28
+ user: ShellUser;
29
+ userLinks?: MenuLink[];
30
+ /** Ends the session. Without it the menu hides "Sign out". */
31
+ onSignOut?: () => void;
32
+ notifications?: NotificationItem[];
33
+ notificationsHref?: string;
34
+ notificationsHrefLabel?: string;
35
+ /** The bell opened — refresh the list. */
36
+ onNotificationsOpen?: () => void;
37
+ /** Mark read: one clicked notification, or "all". */
38
+ onNotificationsRead?: (ids: string[] | "all") => void;
39
+ searchPlaceholder?: string;
40
+ /**
41
+ * Room the centred search leaves for each side of the topbar (§4.5d).
42
+ * "default" = 360px, sized for scope + notifications + avatar. "wide" = 480px
43
+ * for products that add controls to the right cluster (e.g. a credits pill).
44
+ */
45
+ searchClearance?: "default" | "wide";
46
+ /** Product actions listed first in ⌘K. */
47
+ commands?: CommandItem[];
48
+ /** Where "manage plan" links go. */
49
+ billingHref?: string;
50
+ /** Currency code for money formatting (org currency). */
51
+ currency?: string;
52
+ /** Crumb root, e.g. { label: "WaveReach", href: "/dashboard" }. */
53
+ homeCrumb?: {
54
+ label: string;
55
+ href: string;
56
+ };
57
+ /** Deterministic "now" for relative times in mock builds. */
58
+ now?: Date;
59
+ }
60
+ export declare function ShellProvider({ config, children, }: {
61
+ config: ShellConfig;
62
+ children: React.ReactNode;
63
+ }): React.JSX.Element;
64
+ export declare function useShell(): ShellConfig;
65
+ /** Currency code without requiring a shell — money renders outside it too. */
66
+ export declare function useShellCurrency(): string;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as i}from"react/jsx-runtime";import*as t from"react";const o=t.createContext(null);function r({config:e,children:n}){return i(o.Provider,{value:e,children:n})}function l(){const e=t.useContext(o);if(!e)throw new Error("useShell must be used inside <ShellProvider>");return e}function s(){return t.useContext(o)?.currency??"USD"}export{r as ShellProvider,l as useShell,s as useShellCurrency};
@@ -0,0 +1,2 @@
1
+ import * as React from "react";
2
+ export declare function IconRail(): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{Fragment as W,jsx as e,jsxs as c}from"react/jsx-runtime";import x from"next/link";import*as i from"react";import{WaveMark as T}from"../components/brand/logo.js";import{CountBadge as $}from"../components/ui/badge.js";import{Tooltip as f}from"../components/ui/tooltip.js";import{cn as v}from"../lib/utils.js";import{useUi as E}from"../store/ui-store.js";import{useActive as R}from"./use-active.js";function d({mod:l,active:r,index:n,visiting:t}){const u=l.icon;return e("li",{className:"relative w-full","data-active":r||void 0,children:e(f,{label:t?`${l.title} \u2014 outside this app`:l.title,shortcut:n<9?`Alt+${n+1}`:void 0,side:"right",children:c(x,{href:l.href,"aria-current":r?"page":void 0,className:v("relative mx-auto flex min-h-14 w-[60px] flex-col items-center justify-center gap-[3px] rounded-ctl px-0.5 py-1.5","transition-colors duration-150",r?"bg-primary-subtle text-primary":"text-muted hover:bg-hover hover:text-text"),children:[e(u,{className:"size-[19px]",strokeWidth:r?2.2:1.8}),e("span",{className:"w-full truncate px-0.5 text-center text-[10px] font-medium leading-3",children:l.label}),l.badge?e($,{value:l.badge,tone:l.badgeTone==="danger"?"danger":"neutral",className:"absolute right-1.5 top-1"}):null,t?e("span",{className:"absolute left-1.5 top-1.5 size-1.5 rounded-full bg-accent","aria-hidden":!0}):null]})})})}function C(){const{cfg:l,pathname:r,mod:n,app:t,rail:u}=R(),y=E(a=>a.setLauncher),N=t.icon,{modules:o,platform:k,visiting:p}=u,w=l.showAppSwitcher!==!1&&l.apps.length>1,h=i.useRef(null),[m,A]=i.useState(null),b=i.useRef(t.id),[I,L]=i.useState(!1);i.useLayoutEffect(()=>{const a=h.current?.querySelector('[data-active="true"]'),s=b.current===t.id;b.current=t.id,L(s),A(a?{y:a.offsetTop+10,h:a.offsetHeight-20}:null)},[r,t.id,o.length]);const g=c("span",{className:"relative block",children:[e(T,{}),e("span",{className:"absolute -bottom-1 -right-1 grid size-[17px] place-items-center rounded-full text-white ring-2 ring-surface",style:{background:t.tint},children:e(N,{className:"size-2.5"})})]});return c("nav",{ref:h,"data-shell-rail":"","aria-label":`${t.name} modules`,className:"rail-fade relative col-start-1 row-start-1 row-span-2 flex flex-col items-center gap-0.5 overflow-y-auto overflow-x-hidden border-r border-border bg-surface py-2",children:[m?e("span",{"aria-hidden":!0,className:v("pointer-events-none absolute left-0 top-0 w-[3px] rounded-r-[3px] bg-primary",I&&"transition-[transform,height] duration-[220ms] ease-wave"),style:{transform:`translateY(${m.y}px)`,height:m.h}}):null,w?e(f,{label:`WAVEINNO \xB7 ${t.name} \u2014 switch app`,shortcut:"\u2318\u21E7K",side:"right",children:e("button",{type:"button",onClick:()=>y(!0),"aria-label":`WAVEINNO, ${t.name}. Switch app`,className:"relative mb-3 shrink-0 cursor-pointer transition-transform duration-150 hover:scale-105",children:g})}):e(f,{label:`WAVEINNO \xB7 ${t.name}`,side:"right",children:e(x,{href:t.href,"aria-label":`WAVEINNO ${t.name} home`,className:"relative mb-3 shrink-0 transition-transform duration-150 hover:scale-105",children:g})}),e("ul",{className:"flex w-full flex-col items-center gap-0.5",children:o.map((a,s)=>e(d,{mod:a,index:s,active:a.key===n.key},a.key))}),p?c(W,{children:[e("span",{className:"my-1.5 h-px w-8 shrink-0 bg-border2","aria-hidden":!0}),e("ul",{className:"flex w-full flex-col items-center",children:e(d,{mod:p,index:o.length,active:!0,visiting:!0})})]}):null,e("div",{className:"min-h-6 flex-1"}),e("ul",{className:"flex w-full flex-col items-center gap-0.5 pb-1",children:k.map((a,s)=>e(d,{mod:a,index:o.length+(p?1:0)+s,active:a.key===n.key},a.key))})]})}export{C as IconRail};
@@ -0,0 +1,7 @@
1
+ /** Shell: rail → panel → content → drawer, ⌘K. Products pass their registry through <AppShell config>. */
2
+ export * from "./app-shell";
3
+ export * from "./appearance-controls";
4
+ export * from "./config";
5
+ export * from "./placeholder-screen";
6
+ export * from "./record-drawer";
7
+ export * from "./registry";
@@ -0,0 +1 @@
1
+ export*from"./app-shell.js";export*from"./appearance-controls.js";export*from"./config.js";export*from"./placeholder-screen.js";export*from"./record-drawer.js";export*from"./registry.js";
@@ -0,0 +1,7 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Below 1024px the rail and panel collapse into one left sheet: the app strip
4
+ * (when there is more than one app), then the app's modules with the active
5
+ * module's screens expanded underneath.
6
+ */
7
+ export declare function MobileNav(): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as t,jsxs as s}from"react/jsx-runtime";import l from"next/link";import*as k from"react";import{WaveWordmark as N}from"../components/brand/logo.js";import{CountBadge as y}from"../components/ui/badge.js";import{Sheet as w,SheetContent as A,SheetTitle as S}from"../components/ui/sheet.js";import{cn as p}from"../lib/utils.js";import{useApp as C}from"../store/app-store.js";import{useUi as f}from"../store/ui-store.js";import{useActive as L}from"./use-active.js";function R(){const u=f(e=>e.mobileNavOpen),n=f(e=>e.setMobileNav),h=C(e=>e.setApp),{cfg:o,pathname:d,mod:b,app:c,rail:x,isEntitled:v}=L(),g=x.order;return k.useEffect(()=>{n(!1)},[d,n]),t(w,{open:u,onOpenChange:n,children:s(A,{side:"left",width:"w-[300px]",className:"lg:hidden",children:[s("div",{className:"shrink-0 border-b border-border px-4 py-3",children:[t(S,{asChild:!0,children:t(N,{size:"sm",suffix:c.name})}),o.scopeLabelled??o.scope?t("div",{className:"mt-2",children:o.scopeLabelled??o.scope}):null]}),o.apps.length>1&&o.showAppSwitcher!==!1?t("div",{className:"flex shrink-0 gap-1.5 overflow-x-auto border-b border-border px-3 py-2.5",children:o.apps.map(e=>{const a=e.icon,i=e.id===c.id,r=o.modules.find(m=>m.app===e.id&&v(m));return t(l,{href:r?.href??e.href,onClick:()=>h(e.id),"aria-label":e.name,"aria-current":i?"true":void 0,className:p("grid size-9 shrink-0 place-items-center rounded-ctl text-white transition-[box-shadow,transform] duration-150",i?"ring-2 ring-text ring-offset-2 ring-offset-raised":"opacity-90 hover:opacity-100"),style:{background:e.tint},children:t(a,{className:"size-4"})},e.id)})}):null,t("div",{className:"flex-1 overflow-y-auto p-2",children:g.map(e=>{const a=e.icon,i=e.key===b.key;return s("div",{children:[s(l,{href:e.href,className:p("flex h-item items-center gap-2.5 rounded-ctl px-2.5 text-[13px] transition-colors",i?"bg-primary-subtle font-medium text-primary":"text-body hover:bg-hover"),children:[t(a,{className:"size-4 shrink-0"}),t("span",{className:"flex-1 truncate",children:e.title}),e.badge?t(y,{value:e.badge,tone:e.badgeTone==="danger"?"danger":"neutral"}):null]}),i?t("ul",{className:"mb-2 ml-[26px] mt-0.5 border-l border-border pl-2",children:e.nav.filter(r=>r.kind==="item").map(r=>t("li",{children:s(l,{href:r.href,className:p("flex h-8 items-center gap-2 rounded-ctl px-2 text-[13px] transition-colors",d===r.href?"font-medium text-primary":"text-muted hover:bg-hover hover:text-text"),children:[t("span",{className:"flex-1 truncate",children:r.label}),r.count?t("span",{className:"num text-[11px]",children:r.count}):null]})},r.href))}):null]},e.key)})})]})})}export{R as MobileNav};
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Tier two — docs/DESIGN-SYSTEM.md §2.1.
4
+ *
5
+ * The panel answers "which screen", re-rendering per module. The selection is
6
+ * one pill that **slides** between rows; it snaps when the module changes,
7
+ * otherwise it would fly through rows that no longer exist.
8
+ */
9
+ export declare function ModuleSidebar(): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as a,jsxs as o}from"react/jsx-runtime";import d from"next/link";import*as r from"react";import{cn as i}from"../lib/utils.js";import{useActive as b}from"./use-active.js";function k(){const{pathname:c,mod:t,item:p}=b(),u=r.useRef(null),[s,f]=r.useState(null),m=r.useRef(t.key),[x,h]=r.useState(!1);return r.useLayoutEffect(()=>{const e=u.current?.querySelector('li[data-active="true"]'),n=m.current===t.key;m.current=t.key,h(n),f(e?{y:e.offsetTop,h:e.offsetHeight}:null)},[c,t.key]),o("nav",{"data-shell-panel":"","aria-label":`${t.title} sections`,className:"col-start-2 row-start-1 row-span-2 overflow-y-auto overflow-x-hidden border-r border-border bg-surface px-2.5 py-3",children:[a("h2",{className:"px-2.5 pb-2.5 pt-1.5 text-[13px] font-semibold leading-[18px] text-text",children:t.title}),o("ul",{ref:u,className:"relative",children:[s?a("li",{"aria-hidden":!0,className:i("pointer-events-none absolute inset-x-0 top-0 rounded-ctl bg-primary-subtle",x&&"transition-[transform,height] duration-[220ms] ease-wave"),style:{transform:`translateY(${s.y}px)`,height:s.h}}):null,t.nav.map((e,n)=>{if(e.kind==="group")return a("li",{className:"ov px-2.5 pb-1.5 pt-3.5",children:e.label},`g-${n}`);const l=p?.href===e.href;return a("li",{"data-active":l||void 0,children:o(d,{href:e.href,"aria-current":l?"page":void 0,className:i("relative mb-px flex h-item items-center gap-2.5 rounded-ctl px-2.5 text-[13px] transition-colors duration-150",l?"font-medium text-primary":"text-body hover:bg-hover hover:text-text"),children:[a("span",{className:"truncate",children:e.label}),e.count?a("span",{className:i("num ml-auto shrink-0 text-xs",l?"text-primary":e.hot?"font-semibold text-text":"text-muted"),children:e.count}):null]})},e.href)})]})]})}export{k as ModuleSidebar};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Notifications are a *queue of decisions*, not a news feed. Every entry names
3
+ * what it concerns and links to where it is resolved.
4
+ */
5
+ export declare function Notifications(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{Bell as p}from"lucide-react";import n from"next/link";import{Badge as m}from"../components/ui/badge.js";import{Button as s}from"../components/ui/button.js";import{Popover as c,PopoverContent as d,PopoverTrigger as x}from"../components/ui/popover.js";import{Tooltip as f}from"../components/ui/tooltip.js";import{fmtRelative as u}from"../lib/format.js";import{useShell as N}from"./config.js";function C(){const o=N(),r=o.notifications??[],i=r.filter(t=>!t.read).length,l=o.now??new Date;return a(c,{onOpenChange:t=>t&&o.onNotificationsOpen?.(),children:[e(f,{label:"Notifications",children:e(x,{asChild:!0,children:a(s,{variant:"ghost",size:"icon",className:"relative","aria-label":`Notifications, ${i} unread`,children:[e(p,{}),i>0?e("span",{className:"num absolute right-[3px] top-[3px] grid h-4 min-w-4 place-items-center rounded-full bg-danger px-1 text-[10px] font-semibold leading-none text-white ring-2 ring-canvas","aria-hidden":!0,children:i>9?"9+":i}):null]})})}),a(d,{align:"end",className:"w-[380px] max-w-[calc(100vw-32px)] p-0",children:[a("div",{className:"flex items-center justify-between border-b border-border px-3.5 py-2.5",children:[e("span",{className:"text-[13px] font-semibold text-text",children:"Notifications"}),i>0&&o.onNotificationsRead?e(s,{variant:"link",size:"sm",className:"h-auto",onClick:()=>o.onNotificationsRead?.("all"),children:"Mark all read"}):null]}),r.length===0?e("p",{className:"px-3.5 py-8 text-center text-[13px] text-muted",children:"You are all caught up."}):e("ul",{className:"max-h-[420px] overflow-y-auto",children:r.map(t=>e("li",{children:a(n,{href:t.href,onClick:()=>!t.read&&o.onNotificationsRead?.([t.id]),className:"flex gap-2.5 border-b border-border px-3.5 py-3 transition-colors last:border-0 hover:bg-hover",children:[e(m,{tone:t.tone,className:"mt-0.5 h-[18px] px-1.5",children:e("span",{className:"sr-only",children:t.tone})}),a("div",{className:"min-w-0 flex-1",children:[e("p",{className:"text-[13px] font-medium leading-[18px] text-text",children:t.title}),e("p",{className:"mt-0.5 truncate text-xs text-muted",children:t.detail}),a("p",{className:"mt-1 text-[11px] text-muted",children:[u(t.at,l)," \xB7 ",t.source]})]}),t.read?null:e("span",{className:"mt-2 size-1.5 shrink-0 rounded-full bg-primary","aria-label":"unread"})]})},t.id))}),o.notificationsHref?e("div",{className:"border-t border-border px-3.5 py-2",children:e(n,{href:o.notificationsHref,className:"text-xs font-medium text-primary hover:underline",children:o.notificationsHrefLabel??"Open activity"})}):null]})]})}export{C as Notifications};