@open-mercato/ui 0.6.2-develop.3372.1.de9a169973 → 0.6.2-develop.3380.1.bb14509041

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 (30) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -0
  3. package/dist/ai/AiIcon.js +1 -1
  4. package/dist/ai/AiIcon.js.map +2 -2
  5. package/dist/backend/AppShell.js +223 -153
  6. package/dist/backend/AppShell.js.map +2 -2
  7. package/dist/backend/ProfileDropdown.js +101 -79
  8. package/dist/backend/ProfileDropdown.js.map +2 -2
  9. package/dist/backend/notifications/NotificationCountBadge.js +1 -1
  10. package/dist/backend/notifications/NotificationCountBadge.js.map +2 -2
  11. package/dist/backend/notifications/NotificationItem.js +67 -50
  12. package/dist/backend/notifications/NotificationItem.js.map +2 -2
  13. package/dist/backend/notifications/NotificationPanel.js +108 -85
  14. package/dist/backend/notifications/NotificationPanel.js.map +2 -2
  15. package/dist/primitives/breadcrumb.js +161 -0
  16. package/dist/primitives/breadcrumb.js.map +7 -0
  17. package/dist/primitives/sheet.js +114 -0
  18. package/dist/primitives/sheet.js.map +7 -0
  19. package/package.json +3 -3
  20. package/src/ai/AiIcon.tsx +1 -1
  21. package/src/backend/AppShell.tsx +151 -49
  22. package/src/backend/ProfileDropdown.tsx +115 -88
  23. package/src/backend/__tests__/AppShell.test.tsx +8 -0
  24. package/src/backend/notifications/NotificationCountBadge.tsx +1 -1
  25. package/src/backend/notifications/NotificationItem.tsx +97 -104
  26. package/src/backend/notifications/NotificationPanel.tsx +126 -102
  27. package/src/primitives/__tests__/breadcrumb.test.tsx +298 -0
  28. package/src/primitives/__tests__/sheet.test.tsx +152 -0
  29. package/src/primitives/breadcrumb.tsx +185 -0
  30. package/src/primitives/sheet.tsx +165 -0
@@ -1,13 +1,14 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
- import { User, LogOut, Bell, Moon, Sun, Globe, Key, Check } from "lucide-react";
5
+ import { User, LogOut, Bell, Moon, Sun, Globe, Key, Check, ChevronRight } from "lucide-react";
6
6
  import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { locales } from "@open-mercato/shared/lib/i18n/config";
8
8
  import { useTheme } from "@open-mercato/ui/theme";
9
- import { Button } from "../primitives/button.js";
9
+ import { cn } from "@open-mercato/shared/lib/utils";
10
10
  import { IconButton } from "../primitives/icon-button.js";
11
+ import { Switch } from "../primitives/switch.js";
11
12
  import { useInjectedMenuItems } from "./injection/useInjectedMenuItems.js";
12
13
  import { mergeMenuItems } from "./injection/mergeMenuItems.js";
13
14
  import { resolveInjectedIcon } from "./injection/resolveInjectedIcon.js";
@@ -78,7 +79,12 @@ function ProfileDropdown({
78
79
  } catch {
79
80
  }
80
81
  };
81
- const menuItemClass = "w-full text-left text-sm cursor-pointer px-3 py-2 rounded hover:bg-accent inline-flex items-center gap-2.5 outline-none focus-visible:ring-1 focus-visible:ring-ring";
82
+ const menuItemClass = cn(
83
+ "group flex h-9 w-full items-center gap-3 rounded-md px-2.5 text-sm text-foreground",
84
+ "transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60",
85
+ "cursor-pointer"
86
+ );
87
+ const menuIconClass = "size-4 shrink-0 text-muted-foreground group-hover:text-foreground";
82
88
  const resolveMenuLabel = React.useCallback(
83
89
  (item) => {
84
90
  if (item.labelKey && item.label) return t(item.labelKey, item.label);
@@ -113,8 +119,12 @@ function ProfileDropdown({
113
119
  (item) => {
114
120
  const label = resolveMenuLabel(item);
115
121
  const icon = resolveInjectedIcon(item.icon);
122
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
123
+ /* @__PURE__ */ jsx("span", { className: menuIconClass, children: icon }),
124
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: label })
125
+ ] });
116
126
  if (item.href) {
117
- return /* @__PURE__ */ jsxs(
127
+ return /* @__PURE__ */ jsx(
118
128
  Link,
119
129
  {
120
130
  href: item.href,
@@ -122,36 +132,28 @@ function ProfileDropdown({
122
132
  role: "menuitem",
123
133
  "data-menu-item-id": item.id,
124
134
  onClick: () => setOpen(false),
125
- children: [
126
- icon,
127
- /* @__PURE__ */ jsx("span", { children: label })
128
- ]
135
+ children: inner
129
136
  },
130
137
  item.id
131
138
  );
132
139
  }
133
- return /* @__PURE__ */ jsxs(
134
- Button,
140
+ return /* @__PURE__ */ jsx(
141
+ "button",
135
142
  {
136
143
  type: "button",
137
- variant: "ghost",
138
- size: "sm",
139
- className: "w-full justify-start",
144
+ className: menuItemClass,
140
145
  role: "menuitem",
141
146
  "data-menu-item-id": item.id,
142
147
  onClick: () => {
143
148
  item.onClick?.();
144
149
  setOpen(false);
145
150
  },
146
- children: [
147
- icon,
148
- /* @__PURE__ */ jsx("span", { children: label })
149
- ]
151
+ children: inner
150
152
  },
151
153
  item.id
152
154
  );
153
155
  },
154
- [menuItemClass, resolveMenuLabel]
156
+ [menuItemClass, menuIconClass, resolveMenuLabel]
155
157
  );
156
158
  const renderBuiltInItem = React.useCallback(
157
159
  (id) => {
@@ -164,8 +166,8 @@ function ProfileDropdown({
164
166
  role: "menuitem",
165
167
  onClick: () => setOpen(false),
166
168
  children: [
167
- /* @__PURE__ */ jsx(Key, { className: "size-4" }),
168
- /* @__PURE__ */ jsx("span", { children: t("ui.profileMenu.changePassword", "Change Password") })
169
+ /* @__PURE__ */ jsx(Key, { className: menuIconClass }),
170
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: t("ui.profileMenu.changePassword", "Change Password") })
169
171
  ]
170
172
  },
171
173
  id
@@ -180,66 +182,86 @@ function ProfileDropdown({
180
182
  role: "menuitem",
181
183
  onClick: () => setOpen(false),
182
184
  children: [
183
- /* @__PURE__ */ jsx(Bell, { className: "size-4" }),
184
- /* @__PURE__ */ jsx("span", { children: t("ui.profileMenu.notifications", "Notification Preferences") })
185
+ /* @__PURE__ */ jsx(Bell, { className: menuIconClass }),
186
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: t("ui.profileMenu.notifications", "Notification Preferences") })
185
187
  ]
186
188
  },
187
189
  id
188
190
  );
189
191
  }
190
192
  if (id === "theme-toggle") {
191
- return mounted ? /* @__PURE__ */ jsxs(
192
- Button,
193
+ if (!mounted) return null;
194
+ return /* @__PURE__ */ jsxs(
195
+ "div",
193
196
  {
194
- type: "button",
195
- variant: "ghost",
196
- size: "sm",
197
- className: "w-full justify-between",
197
+ className: cn(menuItemClass, "justify-between"),
198
198
  role: "menuitem",
199
199
  onClick: handleThemeToggle,
200
+ onKeyDown: (e) => {
201
+ if (e.key === "Enter" || e.key === " ") {
202
+ e.preventDefault();
203
+ handleThemeToggle();
204
+ }
205
+ },
206
+ tabIndex: 0,
200
207
  children: [
201
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2.5", children: [
202
- isDark ? /* @__PURE__ */ jsx(Moon, { className: "size-4" }) : /* @__PURE__ */ jsx(Sun, { className: "size-4" }),
203
- /* @__PURE__ */ jsx("span", { children: t("ui.profileMenu.theme", "Dark Mode") })
208
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-1 items-center gap-3", children: [
209
+ isDark ? /* @__PURE__ */ jsx(Moon, { className: menuIconClass }) : /* @__PURE__ */ jsx(Sun, { className: menuIconClass }),
210
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: t("ui.profileMenu.theme", "Theme") })
204
211
  ] }),
205
- /* @__PURE__ */ jsx("div", { className: `w-8 h-4 rounded-full transition-colors ${isDark ? "bg-primary" : "bg-muted"} relative`, children: /* @__PURE__ */ jsx("div", { className: `absolute top-0.5 w-3 h-3 rounded-full bg-background shadow transition-transform ${isDark ? "translate-x-4" : "translate-x-0.5"}` }) })
212
+ /* @__PURE__ */ jsx(
213
+ Switch,
214
+ {
215
+ checked: isDark,
216
+ onCheckedChange: handleThemeToggle,
217
+ "aria-label": t("ui.profileMenu.theme", "Theme"),
218
+ onClick: (e) => e.stopPropagation()
219
+ }
220
+ )
206
221
  ]
207
222
  },
208
223
  id
209
- ) : null;
224
+ );
210
225
  }
211
226
  if (id === "language") {
212
- return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
227
+ return /* @__PURE__ */ jsxs("div", { className: "contents", children: [
213
228
  /* @__PURE__ */ jsxs(
214
- Button,
229
+ "button",
215
230
  {
216
231
  type: "button",
217
- variant: "ghost",
218
- size: "sm",
219
- className: "w-full justify-between",
232
+ className: menuItemClass,
220
233
  role: "menuitem",
221
234
  onClick: () => setLanguageOpen(!languageOpen),
222
235
  "aria-expanded": languageOpen,
223
236
  children: [
224
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2.5", children: [
225
- /* @__PURE__ */ jsx(Globe, { className: "size-4" }),
226
- /* @__PURE__ */ jsx("span", { children: t("ui.profileMenu.language", "Language") })
227
- ] }),
228
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: localeLabels[currentLocale] })
237
+ /* @__PURE__ */ jsx(Globe, { className: menuIconClass }),
238
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: t("ui.profileMenu.language", "Language") }),
239
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: localeLabels[currentLocale] }),
240
+ /* @__PURE__ */ jsx(
241
+ ChevronRight,
242
+ {
243
+ className: cn(
244
+ "ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform",
245
+ languageOpen && "rotate-90"
246
+ ),
247
+ "aria-hidden": "true"
248
+ }
249
+ )
229
250
  ]
230
251
  }
231
252
  ),
232
- languageOpen && /* @__PURE__ */ jsx("div", { className: "mt-1 ml-6 space-y-0.5 border-l pl-2", children: locales.map((locale) => /* @__PURE__ */ jsxs(
233
- Button,
253
+ languageOpen && /* @__PURE__ */ jsx("div", { className: "ml-7 mr-1 flex flex-col gap-0.5 border-l pl-2 py-1", children: locales.map((locale) => /* @__PURE__ */ jsxs(
254
+ "button",
234
255
  {
235
256
  type: "button",
236
- variant: "ghost",
237
- size: "sm",
238
- className: `w-full justify-start gap-2 ${locale === currentLocale ? "text-primary font-medium" : ""}`,
257
+ className: cn(
258
+ "flex h-8 w-full items-center justify-between gap-2 rounded-md px-2 text-sm transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60",
259
+ locale === currentLocale ? "font-medium text-foreground" : "text-muted-foreground"
260
+ ),
239
261
  onClick: () => handleLocaleChange(locale),
240
262
  children: [
241
- /* @__PURE__ */ jsx("span", { children: localeLabels[locale] }),
242
- locale === currentLocale && /* @__PURE__ */ jsx(Check, { className: "size-3.5" })
263
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: localeLabels[locale] }),
264
+ locale === currentLocale && /* @__PURE__ */ jsx(Check, { className: "size-3.5 shrink-0 text-accent-indigo", "aria-hidden": "true" })
243
265
  ]
244
266
  },
245
267
  locale
@@ -248,16 +270,14 @@ function ProfileDropdown({
248
270
  }
249
271
  if (id === "sign-out") {
250
272
  return /* @__PURE__ */ jsx("form", { action: "/api/auth/logout", method: "POST", children: /* @__PURE__ */ jsxs(
251
- Button,
273
+ "button",
252
274
  {
253
- variant: "ghost",
254
- size: "sm",
255
- className: "w-full justify-start",
256
275
  type: "submit",
276
+ className: menuItemClass,
257
277
  role: "menuitem",
258
278
  children: [
259
- /* @__PURE__ */ jsx(LogOut, { className: "size-4" }),
260
- /* @__PURE__ */ jsx("span", { children: t("ui.userMenu.logout", "Sign Out") })
279
+ /* @__PURE__ */ jsx(LogOut, { className: menuIconClass }),
280
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: t("ui.userMenu.logout", "Sign Out") })
261
281
  ]
262
282
  }
263
283
  ) }, id);
@@ -271,6 +291,7 @@ function ProfileDropdown({
271
291
  isDark,
272
292
  languageOpen,
273
293
  menuItemClass,
294
+ menuIconClass,
274
295
  mounted,
275
296
  notificationsHref,
276
297
  t
@@ -295,33 +316,34 @@ function ProfileDropdown({
295
316
  "div",
296
317
  {
297
318
  ref: menuRef,
298
- className: "absolute right-0 top-full mt-1 w-56 rounded-md border bg-background p-1 shadow-lg z-dropdown",
319
+ className: "absolute right-0 top-full z-popover mt-2 w-64 overflow-hidden rounded-lg border bg-popover p-0 shadow-lg",
299
320
  role: "menu",
300
321
  "data-testid": "profile-dropdown",
301
322
  children: [
302
- (displayName || email) && /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 border-b mb-1", children: [
303
- displayName && /* @__PURE__ */ jsxs("div", { className: "font-medium text-sm flex items-center gap-2", children: [
304
- /* @__PURE__ */ jsx(User, { className: "size-4" }),
305
- displayName
306
- ] }),
307
- displayName && email && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-0.5 ml-6", children: email }),
308
- !displayName && email && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
309
- t("ui.userMenu.loggedInAs", "Logged in as:"),
310
- " ",
311
- email
323
+ (displayName || email) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b bg-muted/30 px-3 py-3", children: [
324
+ /* @__PURE__ */ jsx("div", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-accent-indigo/10 text-accent-indigo", children: /* @__PURE__ */ jsx(User, { className: "size-4", "aria-hidden": "true" }) }),
325
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
326
+ displayName ? /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium leading-5 text-foreground", children: displayName }) : null,
327
+ email ? /* @__PURE__ */ jsx("span", { className: cn(
328
+ "truncate text-xs leading-4",
329
+ displayName ? "text-muted-foreground" : "text-foreground font-medium"
330
+ ), children: email }) : null,
331
+ !displayName && email ? /* @__PURE__ */ jsx("span", { className: "truncate text-overline uppercase tracking-wider text-muted-foreground/80", children: t("ui.userMenu.loggedInAs", "Logged in") }) : null
312
332
  ] })
313
333
  ] }),
314
- mergedMenuItems.map((item) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
315
- item.separator ? /* @__PURE__ */ jsx("div", { className: "my-1 border-t" }) : null,
316
- item.source === "injected" ? item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null : renderBuiltInItem(item.id)
317
- ] }, item.id)),
318
- /* @__PURE__ */ jsx(
319
- InjectionSpot,
320
- {
321
- spotId: BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID,
322
- context: injectionContext
323
- }
324
- )
334
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-1.5", children: [
335
+ mergedMenuItems.map((item) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
336
+ item.separator ? /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border", "aria-hidden": "true" }) : null,
337
+ item.source === "injected" ? item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null : renderBuiltInItem(item.id)
338
+ ] }, item.id)),
339
+ /* @__PURE__ */ jsx(
340
+ InjectionSpot,
341
+ {
342
+ spotId: BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID,
343
+ context: injectionContext
344
+ }
345
+ )
346
+ ] })
325
347
  ]
326
348
  }
327
349
  )
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/ProfileDropdown.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { User, LogOut, Bell, Moon, Sun, Globe, Key, Check } from 'lucide-react'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { useInjectedMenuItems } from './injection/useInjectedMenuItems'\nimport { mergeMenuItems, type MergedMenuItem } from './injection/mergeMenuItems'\nimport { resolveInjectedIcon } from './injection/resolveInjectedIcon'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID } from './injection/spotIds'\n\nexport type ProfileDropdownProps = {\n email?: string\n displayName?: string\n changePasswordHref?: string\n notificationsHref?: string\n}\n\nconst localeLabels: Record<Locale, string> = {\n en: 'English',\n de: 'Deutsch',\n es: 'Espa\u00F1ol',\n pl: 'Polski',\n}\n\nexport function ProfileDropdown({\n email,\n displayName,\n changePasswordHref = '/backend/profile/change-password',\n notificationsHref,\n}: ProfileDropdownProps) {\n const t = useT()\n const currentLocale = useLocale()\n const { resolvedTheme, setTheme } = useTheme()\n const [open, setOpen] = React.useState(false)\n const [languageOpen, setLanguageOpen] = React.useState(false)\n const [mounted, setMounted] = React.useState(false)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const { items: injectedItems } = useInjectedMenuItems('menu:topbar:profile-dropdown')\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n const isDark = resolvedTheme === 'dark'\n\n // Close on click outside\n React.useEffect(() => {\n if (!open) return\n function handleClick(event: MouseEvent) {\n if (\n menuRef.current &&\n !menuRef.current.contains(event.target as Node) &&\n buttonRef.current &&\n !buttonRef.current.contains(event.target as Node)\n ) {\n setOpen(false)\n setLanguageOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Close on Escape\n React.useEffect(() => {\n if (!open) return\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (languageOpen) {\n setLanguageOpen(false)\n } else {\n setOpen(false)\n buttonRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, languageOpen])\n\n const handleThemeToggle = () => {\n setTheme(isDark ? 'light' : 'dark')\n }\n\n const handleLocaleChange = async (locale: Locale) => {\n try {\n await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n window.location.reload()\n } catch {}\n }\n\n const menuItemClass =\n 'w-full text-left text-sm cursor-pointer px-3 py-2 rounded hover:bg-accent inline-flex items-center gap-2.5 outline-none focus-visible:ring-1 focus-visible:ring-ring'\n\n const resolveMenuLabel = React.useCallback(\n (item: Pick<MergedMenuItem, 'id' | 'label' | 'labelKey'>): string => {\n if (item.labelKey && item.label) return t(item.labelKey, item.label)\n if (item.labelKey) return t(item.labelKey, item.id)\n if (item.label && item.label.includes('.')) return t(item.label, item.id)\n return item.label ?? item.id\n },\n [t],\n )\n\n const builtInMenuItems = React.useMemo(\n () => {\n const items: Array<{ id: string; separator?: boolean }> = [{ id: 'change-password' }]\n if (notificationsHref) items.push({ id: 'notifications' })\n items.push({ id: 'theme-toggle', separator: true }, { id: 'language' }, { id: 'sign-out', separator: true })\n return items\n },\n [notificationsHref],\n )\n\n const mergedMenuItems = React.useMemo(\n () => mergeMenuItems(builtInMenuItems, injectedItems),\n [builtInMenuItems, injectedItems],\n )\n const injectionContext = React.useMemo(\n () => ({\n email,\n displayName,\n locale: currentLocale,\n }),\n [currentLocale, displayName, email],\n )\n\n const renderInjectedItem = React.useCallback(\n (item: MergedMenuItem) => {\n const label = resolveMenuLabel(item)\n const icon = resolveInjectedIcon(item.icon)\n if (item.href) {\n return (\n <Link\n key={item.id}\n href={item.href}\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => setOpen(false)}\n >\n {icon}\n <span>{label}</span>\n </Link>\n )\n }\n return (\n <Button\n key={item.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start\"\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => {\n item.onClick?.()\n setOpen(false)\n }}\n >\n {icon}\n <span>{label}</span>\n </Button>\n )\n },\n [menuItemClass, resolveMenuLabel],\n )\n\n const renderBuiltInItem = React.useCallback(\n (id: string) => {\n if (id === 'change-password') {\n return (\n <Link\n key={id}\n href={changePasswordHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Key className=\"size-4\" />\n <span>{t('ui.profileMenu.changePassword', 'Change Password')}</span>\n </Link>\n )\n }\n\n if (id === 'notifications' && notificationsHref) {\n return (\n <Link\n key={id}\n href={notificationsHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Bell className=\"size-4\" />\n <span>{t('ui.profileMenu.notifications', 'Notification Preferences')}</span>\n </Link>\n )\n }\n\n if (id === 'theme-toggle') {\n return mounted ? (\n <Button\n key={id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between\"\n role=\"menuitem\"\n onClick={handleThemeToggle}\n >\n <span className=\"inline-flex items-center gap-2.5\">\n {isDark ? <Moon className=\"size-4\" /> : <Sun className=\"size-4\" />}\n <span>{t('ui.profileMenu.theme', 'Dark Mode')}</span>\n </span>\n <div className={`w-8 h-4 rounded-full transition-colors ${isDark ? 'bg-primary' : 'bg-muted'} relative`}>\n <div className={`absolute top-0.5 w-3 h-3 rounded-full bg-background shadow transition-transform ${isDark ? 'translate-x-4' : 'translate-x-0.5'}`} />\n </div>\n </Button>\n ) : null\n }\n\n if (id === 'language') {\n return (\n <div key={id} className=\"relative\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between\"\n role=\"menuitem\"\n onClick={() => setLanguageOpen(!languageOpen)}\n aria-expanded={languageOpen}\n >\n <span className=\"inline-flex items-center gap-2.5\">\n <Globe className=\"size-4\" />\n <span>{t('ui.profileMenu.language', 'Language')}</span>\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {localeLabels[currentLocale]}\n </span>\n </Button>\n {languageOpen && (\n <div className=\"mt-1 ml-6 space-y-0.5 border-l pl-2\">\n {locales.map((locale) => (\n <Button\n key={locale}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`w-full justify-start gap-2 ${locale === currentLocale ? 'text-primary font-medium' : ''}`}\n onClick={() => handleLocaleChange(locale)}\n >\n <span>{localeLabels[locale]}</span>\n {locale === currentLocale && <Check className=\"size-3.5\" />}\n </Button>\n ))}\n </div>\n )}\n </div>\n )\n }\n\n if (id === 'sign-out') {\n return (\n <form key={id} action=\"/api/auth/logout\" method=\"POST\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start\"\n type=\"submit\"\n role=\"menuitem\"\n >\n <LogOut className=\"size-4\" />\n <span>{t('ui.userMenu.logout', 'Sign Out')}</span>\n </Button>\n </form>\n )\n }\n\n return null\n },\n [\n changePasswordHref,\n currentLocale,\n handleThemeToggle,\n isDark,\n languageOpen,\n menuItemClass,\n mounted,\n notificationsHref,\n t,\n ],\n )\n\n return (\n <div className=\"relative\">\n <IconButton\n ref={buttonRef}\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n aria-haspopup=\"menu\"\n data-testid=\"profile-dropdown-trigger\"\n title={email || t('ui.userMenu.userFallback', 'User')}\n >\n <User className=\"size-4\" />\n </IconButton>\n\n {open && (\n <div\n ref={menuRef}\n className=\"absolute right-0 top-full mt-1 w-56 rounded-md border bg-background p-1 shadow-lg z-dropdown\"\n role=\"menu\"\n data-testid=\"profile-dropdown\"\n >\n {/* User info header */}\n {(displayName || email) && (\n <div className=\"px-3 py-2.5 border-b mb-1\">\n {displayName && (\n <div className=\"font-medium text-sm flex items-center gap-2\">\n <User className=\"size-4\" />\n {displayName}\n </div>\n )}\n {displayName && email && (\n <div className=\"text-xs text-muted-foreground mt-0.5 ml-6\">{email}</div>\n )}\n {!displayName && email && (\n <div className=\"text-xs text-muted-foreground\">\n {t('ui.userMenu.loggedInAs', 'Logged in as:')} {email}\n </div>\n )}\n </div>\n )}\n\n {mergedMenuItems.map((item) => (\n <React.Fragment key={item.id}>\n {item.separator ? <div className=\"my-1 border-t\" /> : null}\n {item.source === 'injected'\n ? (item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null)\n : renderBuiltInItem(item.id)}\n </React.Fragment>\n ))}\n <InjectionSpot\n spotId={BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { User, LogOut, Bell, Moon, Sun, Globe, Key, Check, ChevronRight } from 'lucide-react'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { IconButton } from '../primitives/icon-button'\nimport { Switch } from '../primitives/switch'\nimport { useInjectedMenuItems } from './injection/useInjectedMenuItems'\nimport { mergeMenuItems, type MergedMenuItem } from './injection/mergeMenuItems'\nimport { resolveInjectedIcon } from './injection/resolveInjectedIcon'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID } from './injection/spotIds'\n\nexport type ProfileDropdownProps = {\n email?: string\n displayName?: string\n changePasswordHref?: string\n notificationsHref?: string\n}\n\nconst localeLabels: Record<Locale, string> = {\n en: 'English',\n de: 'Deutsch',\n es: 'Espa\u00F1ol',\n pl: 'Polski',\n}\n\nexport function ProfileDropdown({\n email,\n displayName,\n changePasswordHref = '/backend/profile/change-password',\n notificationsHref,\n}: ProfileDropdownProps) {\n const t = useT()\n const currentLocale = useLocale()\n const { resolvedTheme, setTheme } = useTheme()\n const [open, setOpen] = React.useState(false)\n const [languageOpen, setLanguageOpen] = React.useState(false)\n const [mounted, setMounted] = React.useState(false)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const { items: injectedItems } = useInjectedMenuItems('menu:topbar:profile-dropdown')\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n const isDark = resolvedTheme === 'dark'\n\n // Close on click outside\n React.useEffect(() => {\n if (!open) return\n function handleClick(event: MouseEvent) {\n if (\n menuRef.current &&\n !menuRef.current.contains(event.target as Node) &&\n buttonRef.current &&\n !buttonRef.current.contains(event.target as Node)\n ) {\n setOpen(false)\n setLanguageOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Close on Escape\n React.useEffect(() => {\n if (!open) return\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (languageOpen) {\n setLanguageOpen(false)\n } else {\n setOpen(false)\n buttonRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, languageOpen])\n\n const handleThemeToggle = () => {\n setTheme(isDark ? 'light' : 'dark')\n }\n\n const handleLocaleChange = async (locale: Locale) => {\n try {\n await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n window.location.reload()\n } catch {}\n }\n\n // Unified row class \u2014 every menu item uses this for perfectly aligned layout.\n // h-9 keeps all rows the same height regardless of whether they have a trailing element.\n const menuItemClass = cn(\n 'group flex h-9 w-full items-center gap-3 rounded-md px-2.5 text-sm text-foreground',\n 'transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n 'cursor-pointer',\n )\n const menuIconClass = 'size-4 shrink-0 text-muted-foreground group-hover:text-foreground'\n\n const resolveMenuLabel = React.useCallback(\n (item: Pick<MergedMenuItem, 'id' | 'label' | 'labelKey'>): string => {\n if (item.labelKey && item.label) return t(item.labelKey, item.label)\n if (item.labelKey) return t(item.labelKey, item.id)\n if (item.label && item.label.includes('.')) return t(item.label, item.id)\n return item.label ?? item.id\n },\n [t],\n )\n\n const builtInMenuItems = React.useMemo(\n () => {\n const items: Array<{ id: string; separator?: boolean }> = [{ id: 'change-password' }]\n if (notificationsHref) items.push({ id: 'notifications' })\n items.push({ id: 'theme-toggle', separator: true }, { id: 'language' }, { id: 'sign-out', separator: true })\n return items\n },\n [notificationsHref],\n )\n\n const mergedMenuItems = React.useMemo(\n () => mergeMenuItems(builtInMenuItems, injectedItems),\n [builtInMenuItems, injectedItems],\n )\n const injectionContext = React.useMemo(\n () => ({\n email,\n displayName,\n locale: currentLocale,\n }),\n [currentLocale, displayName, email],\n )\n\n const renderInjectedItem = React.useCallback(\n (item: MergedMenuItem) => {\n const label = resolveMenuLabel(item)\n const icon = resolveInjectedIcon(item.icon)\n const inner = (\n <>\n <span className={menuIconClass}>{icon}</span>\n <span className=\"flex-1 truncate\">{label}</span>\n </>\n )\n if (item.href) {\n return (\n <Link\n key={item.id}\n href={item.href}\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => setOpen(false)}\n >\n {inner}\n </Link>\n )\n }\n return (\n <button\n key={item.id}\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => {\n item.onClick?.()\n setOpen(false)\n }}\n >\n {inner}\n </button>\n )\n },\n [menuItemClass, menuIconClass, resolveMenuLabel],\n )\n\n const renderBuiltInItem = React.useCallback(\n (id: string) => {\n if (id === 'change-password') {\n return (\n <Link\n key={id}\n href={changePasswordHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Key className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.changePassword', 'Change Password')}</span>\n </Link>\n )\n }\n\n if (id === 'notifications' && notificationsHref) {\n return (\n <Link\n key={id}\n href={notificationsHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Bell className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.notifications', 'Notification Preferences')}</span>\n </Link>\n )\n }\n\n if (id === 'theme-toggle') {\n if (!mounted) return null\n return (\n <div\n key={id}\n className={cn(menuItemClass, 'justify-between')}\n role=\"menuitem\"\n onClick={handleThemeToggle}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleThemeToggle()\n }\n }}\n tabIndex={0}\n >\n <span className=\"flex flex-1 items-center gap-3\">\n {isDark ? <Moon className={menuIconClass} /> : <Sun className={menuIconClass} />}\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.theme', 'Theme')}</span>\n </span>\n <Switch\n checked={isDark}\n onCheckedChange={handleThemeToggle}\n aria-label={t('ui.profileMenu.theme', 'Theme')}\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n )\n }\n\n if (id === 'language') {\n return (\n <div key={id} className=\"contents\">\n <button\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setLanguageOpen(!languageOpen)}\n aria-expanded={languageOpen}\n >\n <Globe className={menuIconClass} />\n <span className=\"truncate\">{t('ui.profileMenu.language', 'Language')}</span>\n <span className=\"text-xs text-muted-foreground\">{localeLabels[currentLocale]}</span>\n <ChevronRight\n className={cn(\n 'ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform',\n languageOpen && 'rotate-90',\n )}\n aria-hidden=\"true\"\n />\n </button>\n {languageOpen && (\n <div className=\"ml-7 mr-1 flex flex-col gap-0.5 border-l pl-2 py-1\">\n {locales.map((locale) => (\n <button\n key={locale}\n type=\"button\"\n className={cn(\n 'flex h-8 w-full items-center justify-between gap-2 rounded-md px-2 text-sm transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n locale === currentLocale ? 'font-medium text-foreground' : 'text-muted-foreground',\n )}\n onClick={() => handleLocaleChange(locale)}\n >\n <span className=\"truncate\">{localeLabels[locale]}</span>\n {locale === currentLocale && <Check className=\"size-3.5 shrink-0 text-accent-indigo\" aria-hidden=\"true\" />}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n }\n\n if (id === 'sign-out') {\n return (\n <form key={id} action=\"/api/auth/logout\" method=\"POST\">\n <button\n type=\"submit\"\n className={menuItemClass}\n role=\"menuitem\"\n >\n <LogOut className={menuIconClass} />\n <span className=\"flex-1 truncate text-left\">{t('ui.userMenu.logout', 'Sign Out')}</span>\n </button>\n </form>\n )\n }\n\n return null\n },\n [\n changePasswordHref,\n currentLocale,\n handleThemeToggle,\n isDark,\n languageOpen,\n menuItemClass,\n menuIconClass,\n mounted,\n notificationsHref,\n t,\n ],\n )\n\n return (\n <div className=\"relative\">\n <IconButton\n ref={buttonRef}\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n aria-haspopup=\"menu\"\n data-testid=\"profile-dropdown-trigger\"\n title={email || t('ui.userMenu.userFallback', 'User')}\n >\n <User className=\"size-4\" />\n </IconButton>\n\n {open && (\n <div\n ref={menuRef}\n className=\"absolute right-0 top-full z-popover mt-2 w-64 overflow-hidden rounded-lg border bg-popover p-0 shadow-lg\"\n role=\"menu\"\n data-testid=\"profile-dropdown\"\n >\n {/* User info header */}\n {(displayName || email) && (\n <div className=\"flex items-center gap-3 border-b bg-muted/30 px-3 py-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-accent-indigo/10 text-accent-indigo\">\n <User className=\"size-4\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {displayName ? (\n <span className=\"truncate text-sm font-medium leading-5 text-foreground\">{displayName}</span>\n ) : null}\n {email ? (\n <span className={cn(\n 'truncate text-xs leading-4',\n displayName ? 'text-muted-foreground' : 'text-foreground font-medium',\n )}>\n {email}\n </span>\n ) : null}\n {!displayName && email ? (\n <span className=\"truncate text-overline uppercase tracking-wider text-muted-foreground/80\">\n {t('ui.userMenu.loggedInAs', 'Logged in')}\n </span>\n ) : null}\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col p-1.5\">\n {mergedMenuItems.map((item) => (\n <React.Fragment key={item.id}>\n {item.separator ? <div className=\"my-1 h-px bg-border\" aria-hidden=\"true\" /> : null}\n {item.source === 'injected'\n ? (item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null)\n : renderBuiltInItem(item.id)}\n </React.Fragment>\n ))}\n <InjectionSpot\n spotId={BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAqJQ,mBACE,KADF;AApJR,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,MAAM,QAAQ,MAAM,MAAM,KAAK,OAAO,KAAK,OAAO,oBAAoB;AAC/E,SAAS,MAAM,iBAAiB;AAChC,SAAS,eAA4B;AACrC,SAAS,gBAAgB;AACzB,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC,SAAS,sBAA2C;AACpD,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qDAAqD;AAS9D,MAAM,eAAuC;AAAA,EAC3C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AACF,GAAyB;AACvB,QAAM,IAAI,KAAK;AACf,QAAM,gBAAgB,UAAU;AAChC,QAAM,EAAE,eAAe,SAAS,IAAI,SAAS;AAC7C,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAC5C,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAC5D,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,YAAY,MAAM,OAA0B,IAAI;AACtD,QAAM,UAAU,MAAM,OAAuB,IAAI;AACjD,QAAM,EAAE,OAAO,cAAc,IAAI,qBAAqB,8BAA8B;AAEpF,QAAM,UAAU,MAAM;AACpB,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,kBAAkB;AAGjC,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,aAAS,YAAY,OAAmB;AACtC,UACE,QAAQ,WACR,CAAC,QAAQ,QAAQ,SAAS,MAAM,MAAc,KAC9C,UAAU,WACV,CAAC,UAAU,QAAQ,SAAS,MAAM,MAAc,GAChD;AACA,gBAAQ,KAAK;AACb,wBAAgB,KAAK;AAAA,MACvB;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,WAAW;AAClD,WAAO,MAAM,SAAS,oBAAoB,aAAa,WAAW;AAAA,EACpE,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,aAAS,cAAc,OAAsB;AAC3C,UAAI,MAAM,QAAQ,UAAU;AAC1B,YAAI,cAAc;AAChB,0BAAgB,KAAK;AAAA,QACvB,OAAO;AACL,kBAAQ,KAAK;AACb,oBAAU,SAAS,MAAM;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,aAAa;AAClD,WAAO,MAAM,SAAS,oBAAoB,WAAW,aAAa;AAAA,EACpE,GAAG,CAAC,MAAM,YAAY,CAAC;AAEvB,QAAM,oBAAoB,MAAM;AAC9B,aAAS,SAAS,UAAU,MAAM;AAAA,EACpC;AAEA,QAAM,qBAAqB,OAAO,WAAmB;AACnD,QAAI;AACF,YAAM,MAAM,oBAAoB;AAAA,QAC9B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,MACjC,CAAC;AACD,aAAO,SAAS,OAAO;AAAA,IACzB,QAAQ;AAAA,IAAC;AAAA,EACX;AAIA,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,gBAAgB;AAEtB,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,SAAoE;AACnE,UAAI,KAAK,YAAY,KAAK,MAAO,QAAO,EAAE,KAAK,UAAU,KAAK,KAAK;AACnE,UAAI,KAAK,SAAU,QAAO,EAAE,KAAK,UAAU,KAAK,EAAE;AAClD,UAAI,KAAK,SAAS,KAAK,MAAM,SAAS,GAAG,EAAG,QAAO,EAAE,KAAK,OAAO,KAAK,EAAE;AACxE,aAAO,KAAK,SAAS,KAAK;AAAA,IAC5B;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,MAAM;AACJ,YAAM,QAAoD,CAAC,EAAE,IAAI,kBAAkB,CAAC;AACpF,UAAI,kBAAmB,OAAM,KAAK,EAAE,IAAI,gBAAgB,CAAC;AACzD,YAAM,KAAK,EAAE,IAAI,gBAAgB,WAAW,KAAK,GAAG,EAAE,IAAI,WAAW,GAAG,EAAE,IAAI,YAAY,WAAW,KAAK,CAAC;AAC3G,aAAO;AAAA,IACT;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,kBAAkB,MAAM;AAAA,IAC5B,MAAM,eAAe,kBAAkB,aAAa;AAAA,IACpD,CAAC,kBAAkB,aAAa;AAAA,EAClC;AACA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,IACA,CAAC,eAAe,aAAa,KAAK;AAAA,EACpC;AAEA,QAAM,qBAAqB,MAAM;AAAA,IAC/B,CAAC,SAAyB;AACxB,YAAM,QAAQ,iBAAiB,IAAI;AACnC,YAAM,OAAO,oBAAoB,KAAK,IAAI;AAC1C,YAAM,QACJ,iCACE;AAAA,4BAAC,UAAK,WAAW,eAAgB,gBAAK;AAAA,QACtC,oBAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA,SAC3C;AAEF,UAAI,KAAK,MAAM;AACb,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAM,KAAK;AAAA,YACX,WAAW;AAAA,YACX,MAAK;AAAA,YACL,qBAAmB,KAAK;AAAA,YACxB,SAAS,MAAM,QAAQ,KAAK;AAAA,YAE3B;AAAA;AAAA,UAPI,KAAK;AAAA,QAQZ;AAAA,MAEJ;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,WAAW;AAAA,UACX,MAAK;AAAA,UACL,qBAAmB,KAAK;AAAA,UACxB,SAAS,MAAM;AACb,iBAAK,UAAU;AACf,oBAAQ,KAAK;AAAA,UACf;AAAA,UAEC;AAAA;AAAA,QAVI,KAAK;AAAA,MAWZ;AAAA,IAEJ;AAAA,IACA,CAAC,eAAe,eAAe,gBAAgB;AAAA,EACjD;AAEA,QAAM,oBAAoB,MAAM;AAAA,IAC9B,CAAC,OAAe;AACd,UAAI,OAAO,mBAAmB;AAC5B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAM;AAAA,YACN,WAAW;AAAA,YACX,MAAK;AAAA,YACL,SAAS,MAAM,QAAQ,KAAK;AAAA,YAE5B;AAAA,kCAAC,OAAI,WAAW,eAAe;AAAA,cAC/B,oBAAC,UAAK,WAAU,mBAAmB,YAAE,iCAAiC,iBAAiB,GAAE;AAAA;AAAA;AAAA,UAPpF;AAAA,QAQP;AAAA,MAEJ;AAEA,UAAI,OAAO,mBAAmB,mBAAmB;AAC/C,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAM;AAAA,YACN,WAAW;AAAA,YACX,MAAK;AAAA,YACL,SAAS,MAAM,QAAQ,KAAK;AAAA,YAE5B;AAAA,kCAAC,QAAK,WAAW,eAAe;AAAA,cAChC,oBAAC,UAAK,WAAU,mBAAmB,YAAE,gCAAgC,0BAA0B,GAAE;AAAA;AAAA;AAAA,UAP5F;AAAA,QAQP;AAAA,MAEJ;AAEA,UAAI,OAAO,gBAAgB;AACzB,YAAI,CAAC,QAAS,QAAO;AACrB,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,GAAG,eAAe,iBAAiB;AAAA,YAC9C,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAW,CAAC,MAAM;AAChB,kBAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,kBAAE,eAAe;AACjB,kCAAkB;AAAA,cACpB;AAAA,YACF;AAAA,YACA,UAAU;AAAA,YAEV;AAAA,mCAAC,UAAK,WAAU,kCACb;AAAA,yBAAS,oBAAC,QAAK,WAAW,eAAe,IAAK,oBAAC,OAAI,WAAW,eAAe;AAAA,gBAC9E,oBAAC,UAAK,WAAU,mBAAmB,YAAE,wBAAwB,OAAO,GAAE;AAAA,iBACxE;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAS;AAAA,kBACT,iBAAiB;AAAA,kBACjB,cAAY,EAAE,wBAAwB,OAAO;AAAA,kBAC7C,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA;AAAA,cACpC;AAAA;AAAA;AAAA,UArBK;AAAA,QAsBP;AAAA,MAEJ;AAEA,UAAI,OAAO,YAAY;AACrB,eACE,qBAAC,SAAa,WAAU,YACtB;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAW;AAAA,cACX,MAAK;AAAA,cACL,SAAS,MAAM,gBAAgB,CAAC,YAAY;AAAA,cAC5C,iBAAe;AAAA,cAEf;AAAA,oCAAC,SAAM,WAAW,eAAe;AAAA,gBACjC,oBAAC,UAAK,WAAU,YAAY,YAAE,2BAA2B,UAAU,GAAE;AAAA,gBACrE,oBAAC,UAAK,WAAU,iCAAiC,uBAAa,aAAa,GAAE;AAAA,gBAC7E;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAW;AAAA,sBACT;AAAA,sBACA,gBAAgB;AAAA,oBAClB;AAAA,oBACA,eAAY;AAAA;AAAA,gBACd;AAAA;AAAA;AAAA,UACF;AAAA,UACC,gBACC,oBAAC,SAAI,WAAU,sDACZ,kBAAQ,IAAI,CAAC,WACZ;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,WAAW;AAAA,gBACT;AAAA,gBACA,WAAW,gBAAgB,gCAAgC;AAAA,cAC7D;AAAA,cACA,SAAS,MAAM,mBAAmB,MAAM;AAAA,cAExC;AAAA,oCAAC,UAAK,WAAU,YAAY,uBAAa,MAAM,GAAE;AAAA,gBAChD,WAAW,iBAAiB,oBAAC,SAAM,WAAU,wCAAuC,eAAY,QAAO;AAAA;AAAA;AAAA,YATnG;AAAA,UAUP,CACD,GACH;AAAA,aAnCM,EAqCV;AAAA,MAEJ;AAEA,UAAI,OAAO,YAAY;AACrB,eACE,oBAAC,UAAc,QAAO,oBAAmB,QAAO,QAC9C;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAW;AAAA,YACX,MAAK;AAAA,YAEL;AAAA,kCAAC,UAAO,WAAW,eAAe;AAAA,cAClC,oBAAC,UAAK,WAAU,6BAA6B,YAAE,sBAAsB,UAAU,GAAE;AAAA;AAAA;AAAA,QACnF,KARS,EASX;AAAA,MAEJ;AAEA,aAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAU,YACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,CAAC,IAAI;AAAA,QAC5B,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,eAAY;AAAA,QACZ,OAAO,SAAS,EAAE,4BAA4B,MAAM;AAAA,QAEpD,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,IAC3B;AAAA,IAEC,QACC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,WAAU;AAAA,QACV,MAAK;AAAA,QACL,eAAY;AAAA,QAGV;AAAA,0BAAe,UACf,qBAAC,SAAI,WAAU,0DACb;AAAA,gCAAC,SAAI,WAAU,wGACb,8BAAC,QAAK,WAAU,UAAS,eAAY,QAAO,GAC9C;AAAA,YACA,qBAAC,SAAI,WAAU,gCACZ;AAAA,4BACC,oBAAC,UAAK,WAAU,0DAA0D,uBAAY,IACpF;AAAA,cACH,QACC,oBAAC,UAAK,WAAW;AAAA,gBACf;AAAA,gBACA,cAAc,0BAA0B;AAAA,cAC1C,GACG,iBACH,IACE;AAAA,cACH,CAAC,eAAe,QACf,oBAAC,UAAK,WAAU,4EACb,YAAE,0BAA0B,WAAW,GAC1C,IACE;AAAA,eACN;AAAA,aACF;AAAA,UAGF,qBAAC,SAAI,WAAU,uBACZ;AAAA,4BAAgB,IAAI,CAAC,SACpB,qBAAC,MAAM,UAAN,EACE;AAAA,mBAAK,YAAY,oBAAC,SAAI,WAAU,uBAAsB,eAAY,QAAO,IAAK;AAAA,cAC9E,KAAK,WAAW,aACZ,KAAK,QAAQ,KAAK,WAAW,KAAK,SAAS,KAAK,WAAW,mBAAmB,IAAI,IAAI,OACvF,kBAAkB,KAAK,EAAE;AAAA,iBAJV,KAAK,EAK1B,CACD;AAAA,YACD;AAAA,cAAC;AAAA;AAAA,gBACC,QAAQ;AAAA,gBACR,SAAS;AAAA;AAAA,YACX;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  function NotificationCountBadge({ count }) {
3
3
  if (count <= 0) return null;
4
- return /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-overline font-medium text-destructive-foreground", children: count > 99 ? "99+" : count });
4
+ return /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-indigo px-1 text-overline font-medium text-accent-indigo-foreground ring-2 ring-background", children: count > 99 ? "99+" : count });
5
5
  }
6
6
  export {
7
7
  NotificationCountBadge
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/notifications/NotificationCountBadge.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\n\nexport type NotificationCountBadgeProps = {\n count: number\n}\n\nexport function NotificationCountBadge({ count }: NotificationCountBadgeProps) {\n if (count <= 0) return null\n return (\n <span className=\"absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-overline font-medium text-destructive-foreground\">\n {count > 99 ? '99+' : count}\n </span>\n )\n}\n"],
5
- "mappings": "AASI;AAHG,SAAS,uBAAuB,EAAE,MAAM,GAAgC;AAC7E,MAAI,SAAS,EAAG,QAAO;AACvB,SACE,oBAAC,UAAK,WAAU,uJACb,kBAAQ,KAAK,QAAQ,OACxB;AAEJ;",
4
+ "sourcesContent": ["import * as React from 'react'\n\nexport type NotificationCountBadgeProps = {\n count: number\n}\n\nexport function NotificationCountBadge({ count }: NotificationCountBadgeProps) {\n if (count <= 0) return null\n return (\n <span className=\"absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-indigo px-1 text-overline font-medium text-accent-indigo-foreground ring-2 ring-background\">\n {count > 99 ? '99+' : count}\n </span>\n )\n}\n"],
5
+ "mappings": "AASI;AAHG,SAAS,uBAAuB,EAAE,MAAM,GAAgC;AAC7E,MAAI,SAAS,EAAG,QAAO;AACvB,SACE,oBAAC,UAAK,WAAU,2LACb,kBAAQ,KAAK,QAAQ,OACxB;AAEJ;",
6
6
  "names": []
7
7
  }