@xenide-io/the-old-ui-theme 0.4.0 → 0.4.3

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/suite.mjs CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ DropdownItem,
3
+ DropdownMenu,
4
+ Tooltip
5
+ } from "./chunk-G53YLHVE.mjs";
1
6
  import {
2
7
  __spreadProps,
3
8
  __spreadValues
@@ -755,12 +760,12 @@ function AppSwitcher({
755
760
  mark,
756
761
  title,
757
762
  collapsed = false,
758
- dropdownMenu: DropdownMenu,
759
- dropdownItem: DropdownItem
763
+ dropdownMenu: DropdownMenu2,
764
+ dropdownItem: DropdownItem2
760
765
  }) {
761
766
  const [open, setOpen] = useState(false);
762
767
  return /* @__PURE__ */ jsx6(
763
- DropdownMenu,
768
+ DropdownMenu2,
764
769
  {
765
770
  "aria-label": "Switch application",
766
771
  className: collapsed ? "w-11 shrink-0" : "min-w-0 flex-1",
@@ -787,7 +792,7 @@ function AppSwitcher({
787
792
  children: /* @__PURE__ */ jsxs6("div", { className: "p-1", "data-test": "app-switcher-menu", children: [
788
793
  /* @__PURE__ */ jsx6("p", { className: "px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext", children: "Switch application" }),
789
794
  apps.map((app) => /* @__PURE__ */ jsx6(
790
- DropdownItem,
795
+ DropdownItem2,
791
796
  {
792
797
  "data-test": `switch-app-${app.slug}`,
793
798
  onMouseDown: (e) => e.preventDefault(),
@@ -848,7 +853,7 @@ function SuiteNotificationBell({
848
853
  fetchNotifications,
849
854
  markRead,
850
855
  markAllRead,
851
- dropdownMenu: DropdownMenu,
856
+ dropdownMenu: DropdownMenu2,
852
857
  cacheKey = "suite-notifications-cache"
853
858
  }) {
854
859
  const router = useRouter();
@@ -943,7 +948,7 @@ function SuiteNotificationBell({
943
948
  }
944
949
  }, [markAllRead]);
945
950
  return /* @__PURE__ */ jsxs7(
946
- DropdownMenu,
951
+ DropdownMenu2,
947
952
  {
948
953
  "aria-label": "Notifications",
949
954
  align: "end",
@@ -1228,10 +1233,9 @@ function SuiteMobileHeader({
1228
1233
 
1229
1234
  // src/suite/components/suite-user-menu.tsx
1230
1235
  import Image2 from "next/image";
1231
- import Link from "next/link";
1232
- import { useState as useState6 } from "react";
1236
+ import { useRouter as useRouter2 } from "next/navigation";
1233
1237
  import { LogOut, Settings } from "iconoir-react";
1234
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1238
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1235
1239
  function computeInitials(name, email) {
1236
1240
  const source = (name || email || "?").trim();
1237
1241
  return source.split(/[\s@]+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join("").toUpperCase();
@@ -1246,94 +1250,67 @@ function SuiteUserMenu({
1246
1250
  dataTest = "suite-user-menu",
1247
1251
  className
1248
1252
  }) {
1249
- const [open, setOpen] = useState6(false);
1253
+ const router = useRouter2();
1250
1254
  const initials = fallbackInitials != null ? fallbackInitials : computeInitials(name, email);
1251
- return /* @__PURE__ */ jsxs10("div", { className: cn("relative", className), "data-test": dataTest, children: [
1252
- /* @__PURE__ */ jsx12(
1253
- "button",
1254
- {
1255
- type: "button",
1256
- "aria-label": "Account menu",
1257
- "aria-expanded": open,
1258
- "aria-haspopup": "menu",
1259
- title: "Account",
1260
- onClick: () => setOpen((value) => !value),
1261
- className: "suite-press inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1262
- children: image ? /* @__PURE__ */ jsx12(
1263
- Image2,
1255
+ const avatar = /* @__PURE__ */ jsx12("span", { className: "inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted", children: image ? /* @__PURE__ */ jsx12(
1256
+ Image2,
1257
+ {
1258
+ src: image,
1259
+ alt: "",
1260
+ width: 32,
1261
+ height: 32,
1262
+ className: "h-8 w-8 rounded-full object-cover",
1263
+ unoptimized: true
1264
+ }
1265
+ ) : /* @__PURE__ */ jsx12("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand", children: initials }) });
1266
+ return /* @__PURE__ */ jsxs10(
1267
+ DropdownMenu,
1268
+ {
1269
+ trigger: avatar,
1270
+ "aria-label": "Account menu",
1271
+ align: "end",
1272
+ side: "bottom",
1273
+ sideOffset: 6,
1274
+ collisionPadding: 16,
1275
+ modal: false,
1276
+ className,
1277
+ "data-test": dataTest,
1278
+ children: [
1279
+ /* @__PURE__ */ jsxs10("div", { className: "px-2 py-1.5", children: [
1280
+ /* @__PURE__ */ jsx12("p", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1281
+ email ? /* @__PURE__ */ jsx12("p", { className: "truncate text-xs text-ph-mutedtext", children: email }) : null
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx12("div", { className: "my-1 border-t border-ph-border" }),
1284
+ /* @__PURE__ */ jsxs10(
1285
+ DropdownItem,
1264
1286
  {
1265
- src: image,
1266
- alt: "",
1267
- width: 32,
1268
- height: 32,
1269
- className: "h-8 w-8 rounded-full object-cover",
1270
- unoptimized: true
1287
+ "data-test": `${dataTest}-settings`,
1288
+ onClick: () => router.push(settingsHref),
1289
+ children: [
1290
+ /* @__PURE__ */ jsx12(Settings, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1291
+ "Profile settings"
1292
+ ]
1271
1293
  }
1272
- ) : /* @__PURE__ */ jsx12("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand", children: initials })
1273
- }
1274
- ),
1275
- open ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1276
- /* @__PURE__ */ jsx12(
1277
- "button",
1278
- {
1279
- type: "button",
1280
- "aria-label": "Close account menu",
1281
- className: "fixed inset-0 z-40 cursor-default",
1282
- onClick: () => setOpen(false)
1283
- }
1284
- ),
1285
- /* @__PURE__ */ jsxs10(
1286
- "div",
1287
- {
1288
- role: "menu",
1289
- className: "absolute right-0 top-full z-50 mt-1 w-56 rounded-xl border border-ph-border bg-ph-surface p-1 shadow-xl",
1290
- children: [
1291
- /* @__PURE__ */ jsxs10("div", { className: "px-2 py-1.5", children: [
1292
- /* @__PURE__ */ jsx12("p", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1293
- email ? /* @__PURE__ */ jsx12("p", { className: "truncate text-xs text-ph-mutedtext", children: email }) : null
1294
- ] }),
1295
- /* @__PURE__ */ jsx12("div", { className: "my-1 border-t border-ph-border" }),
1296
- /* @__PURE__ */ jsxs10(
1297
- Link,
1298
- {
1299
- role: "menuitem",
1300
- href: settingsHref,
1301
- "data-test": `${dataTest}-settings`,
1302
- className: "flex min-h-11 items-center gap-2 rounded-lg px-2 text-sm text-ph-ink transition-colors hover:bg-ph-muted",
1303
- onClick: () => setOpen(false),
1304
- children: [
1305
- /* @__PURE__ */ jsx12(Settings, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1306
- "Profile settings"
1307
- ]
1308
- }
1309
- ),
1310
- /* @__PURE__ */ jsxs10(
1311
- "button",
1312
- {
1313
- role: "menuitem",
1314
- type: "button",
1315
- "data-test": `${dataTest}-sign-out`,
1316
- className: "flex min-h-11 w-full items-center gap-2 rounded-lg px-2 text-left text-sm text-ph-ink transition-colors hover:bg-ph-muted",
1317
- onClick: () => {
1318
- setOpen(false);
1319
- onSignOut();
1320
- },
1321
- children: [
1322
- /* @__PURE__ */ jsx12(LogOut, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1323
- "Sign out"
1324
- ]
1325
- }
1326
- )
1327
- ]
1328
- }
1329
- )
1330
- ] }) : null
1331
- ] });
1294
+ ),
1295
+ /* @__PURE__ */ jsxs10(
1296
+ DropdownItem,
1297
+ {
1298
+ "data-test": `${dataTest}-sign-out`,
1299
+ onClick: onSignOut,
1300
+ children: [
1301
+ /* @__PURE__ */ jsx12(LogOut, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1302
+ "Sign out"
1303
+ ]
1304
+ }
1305
+ )
1306
+ ]
1307
+ }
1308
+ );
1332
1309
  }
1333
1310
 
1334
1311
  // src/suite/components/suite-bottom-nav.tsx
1335
- import Link2 from "next/link";
1336
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1312
+ import Link from "next/link";
1313
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1337
1314
  function SuiteBottomNav({
1338
1315
  items,
1339
1316
  ariaLabel = "Primary navigation",
@@ -1346,7 +1323,7 @@ function SuiteBottomNav({
1346
1323
  );
1347
1324
  }
1348
1325
  const activeIndex = items.findIndex((item) => item.active);
1349
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1326
+ return /* @__PURE__ */ jsxs11(Fragment3, { children: [
1350
1327
  /* @__PURE__ */ jsx13(
1351
1328
  "div",
1352
1329
  {
@@ -1376,7 +1353,7 @@ function SuiteBottomNav({
1376
1353
  const Icon = item.icon;
1377
1354
  const active = Boolean(item.active);
1378
1355
  return /* @__PURE__ */ jsxs11(
1379
- Link2,
1356
+ Link,
1380
1357
  {
1381
1358
  href: item.href,
1382
1359
  "data-test": (_a = item.dataTest) != null ? _a : `suite-nav-${item.label.toLowerCase().replace(/\s+/g, "-")}`,
@@ -1421,7 +1398,7 @@ var SUITE_APPS2 = [
1421
1398
  slug: "shellstack",
1422
1399
  name: "ShellStack",
1423
1400
  description: "Organisation and billing",
1424
- icon: "/logo-shellstack-static.svg",
1401
+ icon: "/icon-shellstack.svg",
1425
1402
  landing: "/home",
1426
1403
  baseUrlEnv: "NEXT_PUBLIC_SHELLSTACK_URL",
1427
1404
  baseUrlFallback: "http://localhost:3002"
@@ -1463,7 +1440,7 @@ function suiteAppBaseUrl(slug) {
1463
1440
 
1464
1441
  // src/suite/components/today-calibrating.tsx
1465
1442
  import { Refresh as RefreshCw2, SunLight as Sun } from "iconoir-react";
1466
- import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1443
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1467
1444
  function TodayCalibrating({
1468
1445
  onRetry,
1469
1446
  retrying = false,
@@ -1489,7 +1466,7 @@ function TodayCalibrating({
1489
1466
  }
1490
1467
  ),
1491
1468
  /* @__PURE__ */ jsxs12("div", { className: "relative mb-6 flex h-24 w-24 items-center justify-center", children: [
1492
- variant === "ring" ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
1469
+ variant === "ring" ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
1493
1470
  /* @__PURE__ */ jsx14(
1494
1471
  Spinner,
1495
1472
  {
@@ -1550,7 +1527,7 @@ import {
1550
1527
  useContext,
1551
1528
  useEffect as useEffect5,
1552
1529
  useMemo,
1553
- useState as useState7
1530
+ useState as useState6
1554
1531
  } from "react";
1555
1532
  import { jsx as jsx15 } from "react/jsx-runtime";
1556
1533
  var SuiteThemeContext = createContext(null);
@@ -1590,7 +1567,7 @@ function SuiteThemeProvider({
1590
1567
  config,
1591
1568
  children
1592
1569
  }) {
1593
- const [state, setState] = useState7(() => {
1570
+ const [state, setState] = useState6(() => {
1594
1571
  const theme2 = readStoredTheme(config.storageKey);
1595
1572
  if (typeof document === "undefined") {
1596
1573
  return { theme: theme2, resolvedTheme: config.fallbackTheme };
@@ -1639,9 +1616,9 @@ function useSuiteTheme() {
1639
1616
  }
1640
1617
 
1641
1618
  // src/suite/components/ai-panel.tsx
1642
- import { useState as useState8 } from "react";
1619
+ import { useState as useState7 } from "react";
1643
1620
  import { Check as Check2, Copy, Xmark as X2 } from "iconoir-react";
1644
- import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1621
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1645
1622
  function SuiteAiPanel({
1646
1623
  presets,
1647
1624
  emptyState,
@@ -1650,12 +1627,12 @@ function SuiteAiPanel({
1650
1627
  brandIcon: BrandIcon,
1651
1628
  spinner: Spinner
1652
1629
  }) {
1653
- const [open, setOpen] = useState8(false);
1654
- const [prompt, setPrompt] = useState8("");
1655
- const [loading, setLoading] = useState8(false);
1656
- const [answer, setAnswer] = useState8("");
1657
- const [error, setError] = useState8("");
1658
- const [copied, setCopied] = useState8(false);
1630
+ const [open, setOpen] = useState7(false);
1631
+ const [prompt, setPrompt] = useState7("");
1632
+ const [loading, setLoading] = useState7(false);
1633
+ const [answer, setAnswer] = useState7("");
1634
+ const [error, setError] = useState7("");
1635
+ const [copied, setCopied] = useState7(false);
1659
1636
  async function ask(text) {
1660
1637
  const trimmed = text.trim();
1661
1638
  if (!trimmed || loading) return;
@@ -1688,7 +1665,7 @@ function SuiteAiPanel({
1688
1665
  } catch (e) {
1689
1666
  }
1690
1667
  }
1691
- return /* @__PURE__ */ jsxs13(Fragment6, { children: [
1668
+ return /* @__PURE__ */ jsxs13(Fragment5, { children: [
1692
1669
  /* @__PURE__ */ jsxs13(
1693
1670
  "button",
1694
1671
  {
@@ -1871,9 +1848,8 @@ function SuitePage({
1871
1848
  className: cn(
1872
1849
  "mx-auto flex w-full min-w-0 flex-col",
1873
1850
  WIDTHS[width],
1874
- // Same inset as TodayPageFrame so every page title sits at the
1875
- // same baseline regardless of app.
1876
- inset && "px-4 pb-10 pt-5 sm:px-6 sm:pt-6 lg:px-8",
1851
+ // Tight inset keeps page titles aligned without excessive whitespace.
1852
+ inset && "px-4 pb-6 pt-4 sm:px-6 sm:pb-7 sm:pt-5 lg:px-8",
1877
1853
  className
1878
1854
  ),
1879
1855
  children
@@ -1912,7 +1888,7 @@ function SuitePageHeader({
1912
1888
  {
1913
1889
  "data-test": dataTest,
1914
1890
  className: cn(
1915
- "flex items-start justify-between gap-4 pb-6 sm:pb-7",
1891
+ "flex items-start justify-between gap-4 pb-4 sm:pb-5",
1916
1892
  className
1917
1893
  ),
1918
1894
  children: [
@@ -2208,7 +2184,7 @@ function SuiteSettingsMobileNav({
2208
2184
  }
2209
2185
 
2210
2186
  // src/suite/components/suite-sidebar.tsx
2211
- import Link3 from "next/link";
2187
+ import Link2 from "next/link";
2212
2188
  import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
2213
2189
  function renderNode(node, collapsed) {
2214
2190
  if (typeof node === "function") return node(collapsed);
@@ -2253,7 +2229,7 @@ function SuiteSidebar({
2253
2229
  "div",
2254
2230
  {
2255
2231
  className: cn(
2256
- "flex min-h-0 flex-1 flex-col",
2232
+ "flex min-h-0 flex-1 flex-col overflow-y-auto",
2257
2233
  surface ? "bg-ph-surface" : "bg-ph-canvas",
2258
2234
  collapsed ? "px-1.5 py-2" : "p-2"
2259
2235
  ),
@@ -2268,12 +2244,11 @@ function SuiteSidebar({
2268
2244
  /* @__PURE__ */ jsx21("div", { className: "space-y-0.5", children: navItems.map((item) => {
2269
2245
  const Icon = item.icon;
2270
2246
  const active = Boolean(item.active);
2271
- return /* @__PURE__ */ jsxs17(
2272
- Link3,
2247
+ const link = /* @__PURE__ */ jsxs17(
2248
+ Link2,
2273
2249
  {
2274
2250
  href: item.href,
2275
2251
  "data-test": "nav-link",
2276
- title: collapsed ? item.label : void 0,
2277
2252
  "aria-current": active ? "page" : void 0,
2278
2253
  onClick: item.onClick,
2279
2254
  className: cn(
@@ -2315,6 +2290,7 @@ function SuiteSidebar({
2315
2290
  },
2316
2291
  item.label
2317
2292
  );
2293
+ return collapsed ? /* @__PURE__ */ jsx21(Tooltip, { content: item.label, side: "right", children: link }, item.label) : link;
2318
2294
  }) })
2319
2295
  ]
2320
2296
  }
@@ -2322,7 +2298,7 @@ function SuiteSidebar({
2322
2298
  secondaryNav ? /* @__PURE__ */ jsx21(
2323
2299
  "div",
2324
2300
  {
2325
- className: "min-h-0 flex-1 overflow-y-auto",
2301
+ className: "min-h-0 flex-1",
2326
2302
  "data-test": "suite-sidebar-secondary-nav",
2327
2303
  children: secondaryNav
2328
2304
  }
package/dist/ui.mjs CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  Banner,
15
15
  Breadcrumbs,
16
16
  Button,
17
- ButtonChrome,
18
17
  ButtonGroup,
19
18
  Calendar,
20
19
  Caption,
@@ -36,11 +35,6 @@ import {
36
35
  Divider,
37
36
  Dot,
38
37
  Drawer,
39
- DropdownButton,
40
- DropdownItem,
41
- DropdownMenu,
42
- DropdownRadioGroup,
43
- DropdownRadioItem,
44
38
  EmptyState,
45
39
  FileUpload,
46
40
  FilterBar,
@@ -54,8 +48,6 @@ import {
54
48
  H4,
55
49
  H5,
56
50
  HoverCard,
57
- IconBase,
58
- IconByName,
59
51
  Indicator,
60
52
  Input,
61
53
  InputGroup,
@@ -115,10 +107,20 @@ import {
115
107
  Toggle,
116
108
  ToggleButton,
117
109
  ToggleGroup,
118
- Tooltip,
119
- TooltipProvider,
120
110
  Widget
121
- } from "./chunk-KORWJCIO.mjs";
111
+ } from "./chunk-54ZR4VL5.mjs";
112
+ import {
113
+ ButtonChrome,
114
+ DropdownButton,
115
+ DropdownItem,
116
+ DropdownMenu,
117
+ DropdownRadioGroup,
118
+ DropdownRadioItem,
119
+ IconBase,
120
+ IconByName,
121
+ Tooltip,
122
+ TooltipProvider
123
+ } from "./chunk-G53YLHVE.mjs";
122
124
  import "./chunk-FWCSY2DS.mjs";
123
125
  export {
124
126
  Accordion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenide-io/the-old-ui-theme",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
4
4
  "description": "Props-driven React components and theme tokens inspired by classic interface design — optimized for Next.js apps and internal tools.",
5
5
  "author": "Xenide",
6
6
  "license": "MIT",
@@ -90,10 +90,8 @@ export function SuitePage({
90
90
  className={cn(
91
91
  'mx-auto flex w-full min-w-0 flex-col',
92
92
  WIDTHS[width],
93
- // Same inset as TodayPageFrame so every page title sits at the
94
- // same baseline regardless of app.
95
- inset &&
96
- 'px-4 pb-10 pt-5 sm:px-6 sm:pt-6 lg:px-8',
93
+ // Tight inset keeps page titles aligned without excessive whitespace.
94
+ inset && 'px-4 pb-6 pt-4 sm:px-6 sm:pb-7 sm:pt-5 lg:px-8',
97
95
  className,
98
96
  )}
99
97
  >
@@ -148,7 +146,7 @@ export function SuitePageHeader({
148
146
  <header
149
147
  data-test={dataTest}
150
148
  className={cn(
151
- 'flex items-start justify-between gap-4 pb-6 sm:pb-7',
149
+ 'flex items-start justify-between gap-4 pb-4 sm:pb-5',
152
150
  className,
153
151
  )}
154
152
  >
@@ -4,6 +4,7 @@ import Link from 'next/link';
4
4
  import { type ReactNode } from 'react';
5
5
 
6
6
  import { cn } from '../lib/cn';
7
+ import { Tooltip } from '../../components/ui/Tooltip';
7
8
  import type { SuiteNavIcon } from './suite-bottom-nav';
8
9
 
9
10
  type CollapsedNode = ReactNode | ((collapsed: boolean) => ReactNode);
@@ -91,7 +92,7 @@ export function SuiteSidebar({
91
92
  {/* Nav body */}
92
93
  <div
93
94
  className={cn(
94
- 'flex min-h-0 flex-1 flex-col',
95
+ 'flex min-h-0 flex-1 flex-col overflow-y-auto',
95
96
  surface ? 'bg-ph-surface' : 'bg-ph-canvas',
96
97
  collapsed ? 'px-1.5 py-2' : 'p-2',
97
98
  )}
@@ -108,12 +109,11 @@ export function SuiteSidebar({
108
109
  {navItems.map((item) => {
109
110
  const Icon = item.icon;
110
111
  const active = Boolean(item.active);
111
- return (
112
+ const link = (
112
113
  <Link
113
114
  key={item.label}
114
115
  href={item.href}
115
116
  data-test="nav-link"
116
- title={collapsed ? item.label : undefined}
117
117
  aria-current={active ? 'page' : undefined}
118
118
  onClick={item.onClick}
119
119
  className={cn(
@@ -152,13 +152,20 @@ export function SuiteSidebar({
152
152
  ) : null}
153
153
  </Link>
154
154
  );
155
+ return collapsed ? (
156
+ <Tooltip key={item.label} content={item.label} side="right">
157
+ {link}
158
+ </Tooltip>
159
+ ) : (
160
+ link
161
+ );
155
162
  })}
156
163
  </div>
157
164
  </nav>
158
165
 
159
166
  {secondaryNav ? (
160
167
  <div
161
- className="min-h-0 flex-1 overflow-y-auto"
168
+ className="min-h-0 flex-1"
162
169
  data-test="suite-sidebar-secondary-nav"
163
170
  >
164
171
  {secondaryNav}
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import Image from 'next/image';
4
- import Link from 'next/link';
5
- import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
6
5
  import { LogOut, Settings } from 'iconoir-react';
7
6
 
8
7
  import { cn } from '../lib/cn';
8
+ import { DropdownMenu, DropdownItem } from '../../components/ui/DropdownMenu';
9
9
 
10
10
  export interface SuiteUserMenuProps {
11
11
  name?: string | null;
@@ -47,82 +47,63 @@ export function SuiteUserMenu({
47
47
  dataTest = 'suite-user-menu',
48
48
  className,
49
49
  }: SuiteUserMenuProps) {
50
- const [open, setOpen] = useState(false);
50
+ const router = useRouter();
51
51
  const initials = fallbackInitials ?? computeInitials(name, email);
52
52
 
53
+ const avatar = (
54
+ <span className="inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted">
55
+ {image ? (
56
+ <Image
57
+ src={image}
58
+ alt=""
59
+ width={32}
60
+ height={32}
61
+ className="h-8 w-8 rounded-full object-cover"
62
+ unoptimized
63
+ />
64
+ ) : (
65
+ <span className="flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand">
66
+ {initials}
67
+ </span>
68
+ )}
69
+ </span>
70
+ );
71
+
53
72
  return (
54
- <div className={cn('relative', className)} data-test={dataTest}>
55
- <button
56
- type="button"
57
- aria-label="Account menu"
58
- aria-expanded={open}
59
- aria-haspopup="menu"
60
- title="Account"
61
- onClick={() => setOpen((value) => !value)}
62
- className="suite-press inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
73
+ <DropdownMenu
74
+ trigger={avatar}
75
+ aria-label="Account menu"
76
+ align="end"
77
+ side="bottom"
78
+ sideOffset={6}
79
+ collisionPadding={16}
80
+ modal={false}
81
+ className={className}
82
+ data-test={dataTest}
83
+ >
84
+ <div className="px-2 py-1.5">
85
+ <p className="truncate text-sm font-medium text-ph-ink">
86
+ {name || email || 'Account'}
87
+ </p>
88
+ {email ? (
89
+ <p className="truncate text-xs text-ph-mutedtext">{email}</p>
90
+ ) : null}
91
+ </div>
92
+ <div className="my-1 border-t border-ph-border" />
93
+ <DropdownItem
94
+ data-test={`${dataTest}-settings`}
95
+ onClick={() => router.push(settingsHref)}
96
+ >
97
+ <Settings className="h-4 w-4 text-ph-mutedtext" aria-hidden />
98
+ Profile settings
99
+ </DropdownItem>
100
+ <DropdownItem
101
+ data-test={`${dataTest}-sign-out`}
102
+ onClick={onSignOut}
63
103
  >
64
- {image ? (
65
- <Image
66
- src={image}
67
- alt=""
68
- width={32}
69
- height={32}
70
- className="h-8 w-8 rounded-full object-cover"
71
- unoptimized
72
- />
73
- ) : (
74
- <span className="flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand">
75
- {initials}
76
- </span>
77
- )}
78
- </button>
79
- {open ? (
80
- <>
81
- <button
82
- type="button"
83
- aria-label="Close account menu"
84
- className="fixed inset-0 z-40 cursor-default"
85
- onClick={() => setOpen(false)}
86
- />
87
- <div
88
- role="menu"
89
- className="absolute right-0 top-full z-50 mt-1 w-56 rounded-xl border border-ph-border bg-ph-surface p-1 shadow-xl"
90
- >
91
- <div className="px-2 py-1.5">
92
- <p className="truncate text-sm font-medium text-ph-ink">
93
- {name || email || 'Account'}
94
- </p>
95
- {email ? (
96
- <p className="truncate text-xs text-ph-mutedtext">{email}</p>
97
- ) : null}
98
- </div>
99
- <div className="my-1 border-t border-ph-border" />
100
- <Link
101
- role="menuitem"
102
- href={settingsHref}
103
- data-test={`${dataTest}-settings`}
104
- className="flex min-h-11 items-center gap-2 rounded-lg px-2 text-sm text-ph-ink transition-colors hover:bg-ph-muted"
105
- onClick={() => setOpen(false)}
106
- >
107
- <Settings className="h-4 w-4 text-ph-mutedtext" aria-hidden />
108
- Profile settings
109
- </Link>
110
- <button
111
- role="menuitem"
112
- type="button"
113
- data-test={`${dataTest}-sign-out`}
114
- className="flex min-h-11 w-full items-center gap-2 rounded-lg px-2 text-left text-sm text-ph-ink transition-colors hover:bg-ph-muted"
115
- onClick={() => {
116
- setOpen(false);
117
- onSignOut();
118
- }}
119
- >
120
- <LogOut className="h-4 w-4 text-ph-mutedtext" aria-hidden />
121
- Sign out
122
- </button>
123
- </div>
124
- </>
125
- ) : null}
126
- </div>
104
+ <LogOut className="h-4 w-4 text-ph-mutedtext" aria-hidden />
105
+ Sign out
106
+ </DropdownItem>
107
+ </DropdownMenu>
127
108
  );
128
109
  }