@campfire-interactive/shell-header 0.3.3 → 0.5.1

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/index.d.ts CHANGED
@@ -16,6 +16,16 @@ interface ShellUser {
16
16
  email: string;
17
17
  avatarUrl?: string;
18
18
  }
19
+ /**
20
+ * One of the user's tenant memberships, surfaced in the user-menu tenant
21
+ * section. Hosts typically derive these from the JWT's `data.tenants[]`.
22
+ */
23
+ interface TenantOption {
24
+ id: string;
25
+ name: string;
26
+ /** Human-readable role label ("owner", "admin", "member"). Optional. */
27
+ role?: string;
28
+ }
19
29
  interface LocaleOption {
20
30
  /** BCP-47-ish language code, e.g. "en", "de". */
21
31
  code: string;
@@ -67,6 +77,24 @@ interface ShellHeaderProps {
67
77
  onNotificationItemClick?: (item: NotificationItem) => void;
68
78
  /** Called when logout is clicked */
69
79
  onLogout?: () => void;
80
+ /**
81
+ * The user's currently-active tenant. When provided, the user menu
82
+ * shows an "Acting as: <tenant>" row.
83
+ */
84
+ tenant?: TenantOption;
85
+ /**
86
+ * The user's full membership list. When length > 1, the tenant row
87
+ * expands into a switcher. Single-membership users see only the
88
+ * static "Acting as" row.
89
+ */
90
+ tenants?: ReadonlyArray<TenantOption>;
91
+ /**
92
+ * Called when the user picks a different tenant. Consumer is
93
+ * responsible for calling the auth client's switchTenant, replacing
94
+ * the stored token, and reloading the page so the rest of the UI
95
+ * picks up the new tenant context.
96
+ */
97
+ onTenantSwitch?: (tenantId: string) => void | Promise<void>;
70
98
  /**
71
99
  * Current user locale (e.g. "en", "de"). When provided alongside
72
100
  * `supportedLocales` and `onLocaleChange`, a locale dropdown renders
@@ -88,7 +116,7 @@ interface ShellHeaderProps {
88
116
  children?: React.ReactNode;
89
117
  }
90
118
 
91
- declare function ShellHeader({ appId, user, authorizedApps, notificationCount, onNotificationClick, notifications, onMarkRead, onMarkAllRead, onNotificationItemClick, onLogout, locale, supportedLocales, onLocaleChange, brandWidth, children, }: ShellHeaderProps): react_jsx_runtime.JSX.Element;
119
+ declare function ShellHeader({ appId, user, authorizedApps, notificationCount, onNotificationClick, notifications, onMarkRead, onMarkAllRead, onNotificationItemClick, onLogout, locale, supportedLocales, onLocaleChange, tenant, tenants, onTenantSwitch, brandWidth, children, }: ShellHeaderProps): react_jsx_runtime.JSX.Element;
92
120
 
93
121
  interface LocaleSwitcherProps {
94
122
  currentLocale: string;
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ function styleInject(css, { insertAt } = {}) {
24
24
  }
25
25
 
26
26
  // src/styles.css
27
- styleInject(".cfi-sh-header {\n display: flex;\n align-items: center;\n height: var(--cfi-shell-header-height, 48px);\n padding: 0 var(--cfi-spacing-md, 1rem);\n background: white;\n color: var(--cfi-color-gray-900, #111827);\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n position: relative;\n z-index: 1000;\n}\n.cfi-sh-left {\n display: flex;\n align-items: center;\n flex: 1;\n gap: var(--cfi-spacing-md, 1rem);\n overflow: hidden;\n}\n.cfi-sh-app-brand {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n flex-shrink: 0;\n}\n.cfi-sh-app-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n color: white;\n font-size: 14px;\n font-weight: 700;\n font-family: inherit;\n border-radius: var(--cfi-radius-md, 0.375rem);\n flex-shrink: 0;\n}\n.cfi-sh-app-title {\n font-weight: 600;\n font-size: var(--cfi-font-size-base, 1rem);\n color: var(--cfi-color-gray-900, #111827);\n white-space: nowrap;\n}\n.cfi-sh-right {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n flex-shrink: 0;\n}\n.cfi-sh-icon-btn {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: transparent;\n border: none;\n border-radius: 50%;\n color: var(--cfi-color-gray-600, #4b5563);\n cursor: pointer;\n transition: background 150ms;\n}\n.cfi-sh-icon-btn:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n background: var(--cfi-color-error, #ef4444);\n color: white;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n border-radius: 99px;\n}\n.cfi-sh-app-switcher {\n position: relative;\n}\n.cfi-sh-app-grid {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: var(--cfi-spacing-xs, 0.25rem);\n padding: var(--cfi-spacing-sm, 0.5rem);\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n min-width: 280px;\n}\n.cfi-sh-app-grid-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-xs, 0.25rem);\n border-radius: var(--cfi-radius-md, 0.375rem);\n color: var(--cfi-color-gray-700, #374151);\n text-decoration: none;\n cursor: pointer;\n transition: background 100ms;\n font-family: inherit;\n}\n.cfi-sh-app-grid-item:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-app-grid-item-active {\n background: var(--cfi-color-primary-50, #fff7ed);\n color: var(--cfi-color-primary-700, #c2410c);\n}\n.cfi-sh-app-grid-item-active:hover {\n background: var(--cfi-color-primary-100, #ffedd5);\n}\n.cfi-sh-app-grid-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n}\n.cfi-sh-app-grid-label {\n font-size: var(--cfi-font-size-xs, 0.75rem);\n text-align: center;\n line-height: 1.2;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-sh-user-menu {\n position: relative;\n}\n.cfi-sh-avatar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: var(--cfi-color-primary-600, #ea580c);\n border: none;\n border-radius: 50%;\n cursor: pointer;\n transition: opacity 150ms;\n}\n.cfi-sh-avatar-btn:hover {\n opacity: 0.85;\n}\n.cfi-sh-avatar-img {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n object-fit: cover;\n}\n.cfi-sh-avatar-initials {\n color: white;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n font-family: inherit;\n}\n.cfi-sh-user-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: 200px;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n}\n.cfi-sh-user-header {\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n display: flex;\n flex-direction: column;\n}\n.cfi-sh-user-name {\n font-weight: 500;\n color: var(--cfi-color-gray-900, #111827);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n}\n.cfi-sh-user-email {\n color: var(--cfi-color-gray-500, #6b7280);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n}\n.cfi-sh-divider {\n height: 1px;\n background: var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-sh-menu-item {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n background: none;\n border: none;\n color: var(--cfi-color-gray-700, #374151);\n cursor: pointer;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-menu-item:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-menu-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-sh-locale-switcher {\n position: relative;\n}\n.cfi-sh-locale-btn {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n height: 32px;\n padding: 0 var(--cfi-spacing-sm, 0.5rem);\n background: transparent;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 0.375rem);\n color: var(--cfi-color-gray-700, #374151);\n cursor: pointer;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-locale-btn:hover:not(:disabled) {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-locale-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-sh-locale-label {\n letter-spacing: 0.02em;\n}\n.cfi-sh-locale-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: 180px;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n padding: var(--cfi-spacing-xs, 0.25rem) 0;\n}\n.cfi-sh-locale-dropdown .cfi-sh-menu-item {\n justify-content: space-between;\n}\n.cfi-sh-locale-item-active {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-locale-item-label {\n flex: 1;\n text-align: left;\n}\n.cfi-sh-locale-item-code {\n color: var(--cfi-color-gray-400, #9ca3af);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.02em;\n}\n.cfi-sh-notif {\n position: relative;\n}\n.cfi-sh-notif-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 360px;\n max-height: 480px;\n display: flex;\n flex-direction: column;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n overflow: hidden;\n}\n.cfi-sh-notif-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n flex-shrink: 0;\n}\n.cfi-sh-notif-title {\n font-weight: 600;\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-sh-notif-mark-all {\n background: none;\n border: none;\n color: var(--cfi-color-primary-600, #ea580c);\n cursor: pointer;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-family: inherit;\n padding: 0;\n}\n.cfi-sh-notif-mark-all:hover {\n text-decoration: underline;\n}\n.cfi-sh-notif-list {\n overflow-y: auto;\n flex: 1;\n}\n.cfi-sh-notif-empty {\n padding: var(--cfi-spacing-lg, 1.5rem) var(--cfi-spacing-md, 1rem);\n color: var(--cfi-color-gray-500, #6b7280);\n text-align: center;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n}\n.cfi-sh-notif-item {\n display: flex;\n align-items: flex-start;\n gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n background: none;\n border: none;\n border-bottom: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n text-align: left;\n cursor: pointer;\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-notif-item:hover {\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-sh-notif-item:last-child {\n border-bottom: none;\n}\n.cfi-sh-notif-dot {\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n background: var(--cfi-color-primary-600, #ea580c);\n border-radius: 50%;\n}\n.cfi-sh-notif-dot-placeholder {\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n}\n.cfi-sh-notif-content {\n flex: 1;\n min-width: 0;\n}\n.cfi-sh-notif-item-title {\n font-weight: 500;\n color: var(--cfi-color-gray-900, #111827);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n margin-bottom: 2px;\n}\n.cfi-sh-notif-item-unread .cfi-sh-notif-item-title {\n font-weight: 600;\n}\n.cfi-sh-notif-item-body {\n color: var(--cfi-color-gray-600, #4b5563);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.cfi-sh-notif-item-time {\n color: var(--cfi-color-gray-400, #9ca3af);\n font-size: 11px;\n margin-top: 4px;\n}\n");
27
+ styleInject(".cfi-sh-header {\n display: flex;\n align-items: center;\n height: var(--cfi-shell-header-height, 48px);\n padding: 0 var(--cfi-spacing-md, 1rem);\n background: white;\n color: var(--cfi-color-gray-900, #111827);\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n position: relative;\n z-index: 1000;\n}\n.cfi-sh-left {\n display: flex;\n align-items: center;\n flex: 1;\n gap: var(--cfi-spacing-md, 1rem);\n overflow: hidden;\n}\n.cfi-sh-app-brand {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n flex-shrink: 0;\n}\n.cfi-sh-app-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n color: white;\n font-size: 14px;\n font-weight: 700;\n font-family: inherit;\n border-radius: var(--cfi-radius-md, 0.375rem);\n flex-shrink: 0;\n}\n.cfi-sh-app-title {\n font-weight: 600;\n font-size: var(--cfi-font-size-base, 1rem);\n color: var(--cfi-color-gray-900, #111827);\n white-space: nowrap;\n}\n.cfi-sh-right {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n flex-shrink: 0;\n}\n.cfi-sh-icon-btn {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: transparent;\n border: none;\n border-radius: 50%;\n color: var(--cfi-color-gray-600, #4b5563);\n cursor: pointer;\n transition: background 150ms;\n}\n.cfi-sh-icon-btn:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n background: var(--cfi-color-error, #ef4444);\n color: white;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n border-radius: 99px;\n}\n.cfi-sh-app-switcher {\n position: relative;\n}\n.cfi-sh-app-grid {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: var(--cfi-spacing-xs, 0.25rem);\n padding: var(--cfi-spacing-sm, 0.5rem);\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n min-width: 280px;\n}\n.cfi-sh-app-grid-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-xs, 0.25rem);\n border-radius: var(--cfi-radius-md, 0.375rem);\n color: var(--cfi-color-gray-700, #374151);\n text-decoration: none;\n cursor: pointer;\n transition: background 100ms;\n font-family: inherit;\n}\n.cfi-sh-app-grid-item:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-app-grid-item-active {\n background: var(--cfi-color-primary-50, #fff7ed);\n color: var(--cfi-color-primary-700, #c2410c);\n}\n.cfi-sh-app-grid-item-active:hover {\n background: var(--cfi-color-primary-100, #ffedd5);\n}\n.cfi-sh-app-grid-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n}\n.cfi-sh-app-grid-label {\n font-size: var(--cfi-font-size-xs, 0.75rem);\n text-align: center;\n line-height: 1.2;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-sh-user-menu {\n position: relative;\n}\n.cfi-sh-avatar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: var(--cfi-color-primary-600, #ea580c);\n border: none;\n border-radius: 50%;\n cursor: pointer;\n transition: opacity 150ms;\n}\n.cfi-sh-avatar-btn:hover {\n opacity: 0.85;\n}\n.cfi-sh-avatar-img {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n object-fit: cover;\n}\n.cfi-sh-avatar-initials {\n color: white;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n font-family: inherit;\n}\n.cfi-sh-user-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: 200px;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n}\n.cfi-sh-user-header {\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n display: flex;\n flex-direction: column;\n}\n.cfi-sh-user-name {\n font-weight: 500;\n color: var(--cfi-color-gray-900, #111827);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n}\n.cfi-sh-user-email {\n color: var(--cfi-color-gray-500, #6b7280);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n}\n.cfi-sh-divider {\n height: 1px;\n background: var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-sh-menu-item {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n background: none;\n border: none;\n color: var(--cfi-color-gray-700, #374151);\n cursor: pointer;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-menu-item:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-menu-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-sh-locale-switcher {\n position: relative;\n}\n.cfi-sh-locale-btn {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n height: 32px;\n padding: 0 var(--cfi-spacing-sm, 0.5rem);\n background: transparent;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 0.375rem);\n color: var(--cfi-color-gray-700, #374151);\n cursor: pointer;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-locale-btn:hover:not(:disabled) {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-locale-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-sh-locale-label {\n letter-spacing: 0.02em;\n}\n.cfi-sh-locale-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: 180px;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n padding: var(--cfi-spacing-xs, 0.25rem) 0;\n}\n.cfi-sh-locale-dropdown .cfi-sh-menu-item {\n justify-content: space-between;\n}\n.cfi-sh-locale-item-active {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-sh-locale-item-label {\n flex: 1;\n text-align: left;\n}\n.cfi-sh-locale-item-code {\n color: var(--cfi-color-gray-400, #9ca3af);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.02em;\n}\n.cfi-sh-locale-row {\n justify-content: flex-start;\n}\n.cfi-sh-locale-row-label {\n flex: 1;\n text-align: left;\n}\n.cfi-sh-locale-row-current {\n color: var(--cfi-color-gray-500, #6b7280);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n margin-right: var(--cfi-spacing-xs, 0.25rem);\n}\n.cfi-sh-locale-chevron {\n transition: transform 150ms ease;\n flex-shrink: 0;\n}\n.cfi-sh-locale-chevron-open {\n transform: rotate(180deg);\n}\n.cfi-sh-locale-sublist {\n border-top: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-sh-locale-sub-item {\n padding-left: calc(var(--cfi-spacing-md, 1rem) + var(--cfi-spacing-md, 1rem));\n justify-content: space-between;\n}\n.cfi-sh-tenant-static {\n cursor: default;\n pointer-events: none;\n}\n.cfi-sh-tenant-check {\n color: var(--cfi-color-violet-600, #7c3aed);\n}\n.cfi-sh-notif {\n position: relative;\n}\n.cfi-sh-notif-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 360px;\n max-height: 480px;\n display: flex;\n flex-direction: column;\n background: white;\n border-radius: var(--cfi-radius-lg, 0.5rem);\n box-shadow: var(--cfi-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n z-index: 1001;\n overflow: hidden;\n}\n.cfi-sh-notif-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n flex-shrink: 0;\n}\n.cfi-sh-notif-title {\n font-weight: 600;\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-sh-notif-mark-all {\n background: none;\n border: none;\n color: var(--cfi-color-primary-600, #ea580c);\n cursor: pointer;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-family: inherit;\n padding: 0;\n}\n.cfi-sh-notif-mark-all:hover {\n text-decoration: underline;\n}\n.cfi-sh-notif-list {\n overflow-y: auto;\n flex: 1;\n}\n.cfi-sh-notif-empty {\n padding: var(--cfi-spacing-lg, 1.5rem) var(--cfi-spacing-md, 1rem);\n color: var(--cfi-color-gray-500, #6b7280);\n text-align: center;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n}\n.cfi-sh-notif-item {\n display: flex;\n align-items: flex-start;\n gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n background: none;\n border: none;\n border-bottom: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n text-align: left;\n cursor: pointer;\n font-family: inherit;\n transition: background 100ms;\n}\n.cfi-sh-notif-item:hover {\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-sh-notif-item:last-child {\n border-bottom: none;\n}\n.cfi-sh-notif-dot {\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n background: var(--cfi-color-primary-600, #ea580c);\n border-radius: 50%;\n}\n.cfi-sh-notif-dot-placeholder {\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n margin-top: 6px;\n}\n.cfi-sh-notif-content {\n flex: 1;\n min-width: 0;\n}\n.cfi-sh-notif-item-title {\n font-weight: 500;\n color: var(--cfi-color-gray-900, #111827);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n margin-bottom: 2px;\n}\n.cfi-sh-notif-item-unread .cfi-sh-notif-item-title {\n font-weight: 600;\n}\n.cfi-sh-notif-item-body {\n color: var(--cfi-color-gray-600, #4b5563);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.cfi-sh-notif-item-time {\n color: var(--cfi-color-gray-400, #9ca3af);\n font-size: 11px;\n margin-top: 4px;\n}\n");
28
28
 
29
29
  // src/appCatalog.ts
30
30
  var appCatalog = [
@@ -64,7 +64,9 @@ import {
64
64
  GripHorizontal,
65
65
  Bell,
66
66
  LogOut,
67
- Globe
67
+ Globe,
68
+ ChevronDown,
69
+ Check
68
70
  } from "lucide-react";
69
71
  import { jsx, jsxs } from "react/jsx-runtime";
70
72
  var iconMap = {
@@ -143,73 +145,9 @@ function AppSwitcher({ currentAppId, authorizedApps }) {
143
145
  ] });
144
146
  }
145
147
 
146
- // src/LocaleSwitcher.tsx
147
- import { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
148
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
149
- function LocaleSwitcher({ currentLocale, supportedLocales, onLocaleChange }) {
150
- const [open, setOpen] = useState2(false);
151
- const [updating, setUpdating] = useState2(false);
152
- const ref = useRef2(null);
153
- useEffect2(() => {
154
- function handleClickOutside(e) {
155
- if (ref.current && !ref.current.contains(e.target)) {
156
- setOpen(false);
157
- }
158
- }
159
- if (open) document.addEventListener("mousedown", handleClickOutside);
160
- return () => document.removeEventListener("mousedown", handleClickOutside);
161
- }, [open]);
162
- const current = supportedLocales.find((l) => l.code === currentLocale);
163
- const buttonLabel = (current?.code ?? currentLocale).toUpperCase();
164
- async function handleSelect(code) {
165
- if (code === currentLocale) {
166
- setOpen(false);
167
- return;
168
- }
169
- setUpdating(true);
170
- try {
171
- await onLocaleChange(code);
172
- } finally {
173
- setUpdating(false);
174
- setOpen(false);
175
- }
176
- }
177
- return /* @__PURE__ */ jsxs3("div", { className: "cfi-sh-locale-switcher", ref, children: [
178
- /* @__PURE__ */ jsxs3(
179
- "button",
180
- {
181
- className: "cfi-sh-locale-btn",
182
- onClick: () => setOpen(!open),
183
- "aria-expanded": open,
184
- "aria-haspopup": "true",
185
- "aria-label": `Language: ${current?.label ?? currentLocale}`,
186
- disabled: updating,
187
- title: current?.label ?? currentLocale,
188
- children: [
189
- /* @__PURE__ */ jsx3(Globe, { size: 16 }),
190
- /* @__PURE__ */ jsx3("span", { className: "cfi-sh-locale-label", children: buttonLabel })
191
- ]
192
- }
193
- ),
194
- open && /* @__PURE__ */ jsx3("div", { className: "cfi-sh-locale-dropdown", children: supportedLocales.map((loc) => /* @__PURE__ */ jsxs3(
195
- "button",
196
- {
197
- className: loc.code === currentLocale ? "cfi-sh-menu-item cfi-sh-locale-item-active" : "cfi-sh-menu-item",
198
- onClick: () => handleSelect(loc.code),
199
- disabled: updating,
200
- children: [
201
- /* @__PURE__ */ jsx3("span", { className: "cfi-sh-locale-item-label", children: loc.label }),
202
- /* @__PURE__ */ jsx3("span", { className: "cfi-sh-locale-item-code", children: loc.code.toUpperCase() })
203
- ]
204
- },
205
- loc.code
206
- )) })
207
- ] });
208
- }
209
-
210
148
  // src/NotificationBell.tsx
211
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
212
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
149
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
150
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
213
151
  function NotificationBell({
214
152
  count = 0,
215
153
  onClick,
@@ -219,9 +157,9 @@ function NotificationBell({
219
157
  onItemClick
220
158
  }) {
221
159
  const hasDropdown = items !== void 0;
222
- const [open, setOpen] = useState3(false);
223
- const ref = useRef3(null);
224
- useEffect3(() => {
160
+ const [open, setOpen] = useState2(false);
161
+ const ref = useRef2(null);
162
+ useEffect2(() => {
225
163
  function handleClickOutside(e) {
226
164
  if (ref.current && !ref.current.contains(e.target)) {
227
165
  setOpen(false);
@@ -230,7 +168,7 @@ function NotificationBell({
230
168
  if (open) document.addEventListener("mousedown", handleClickOutside);
231
169
  return () => document.removeEventListener("mousedown", handleClickOutside);
232
170
  }, [open]);
233
- useEffect3(() => {
171
+ useEffect2(() => {
234
172
  function handleEsc(e) {
235
173
  if (e.key === "Escape") setOpen(false);
236
174
  }
@@ -250,8 +188,8 @@ function NotificationBell({
250
188
  setOpen(false);
251
189
  };
252
190
  const hasUnread = items?.some((i) => i.readAt === null) ?? false;
253
- return /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-notif", ref, children: [
254
- /* @__PURE__ */ jsxs4(
191
+ return /* @__PURE__ */ jsxs3("div", { className: "cfi-sh-notif", ref, children: [
192
+ /* @__PURE__ */ jsxs3(
255
193
  "button",
256
194
  {
257
195
  className: "cfi-sh-icon-btn",
@@ -260,15 +198,15 @@ function NotificationBell({
260
198
  "aria-haspopup": hasDropdown ? "true" : void 0,
261
199
  "aria-label": "Notifications",
262
200
  children: [
263
- /* @__PURE__ */ jsx4(Bell, { size: 18 }),
264
- count > 0 && /* @__PURE__ */ jsx4("span", { className: "cfi-sh-badge", children: count > 99 ? "99+" : count })
201
+ /* @__PURE__ */ jsx3(Bell, { size: 18 }),
202
+ count > 0 && /* @__PURE__ */ jsx3("span", { className: "cfi-sh-badge", children: count > 99 ? "99+" : count })
265
203
  ]
266
204
  }
267
205
  ),
268
- hasDropdown && open && /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-notif-dropdown", role: "dialog", "aria-label": "Notifications", children: [
269
- /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-notif-header", children: [
270
- /* @__PURE__ */ jsx4("span", { className: "cfi-sh-notif-title", children: "Notifications" }),
271
- hasUnread && onMarkAllRead && /* @__PURE__ */ jsx4(
206
+ hasDropdown && open && /* @__PURE__ */ jsxs3("div", { className: "cfi-sh-notif-dropdown", role: "dialog", "aria-label": "Notifications", children: [
207
+ /* @__PURE__ */ jsxs3("div", { className: "cfi-sh-notif-header", children: [
208
+ /* @__PURE__ */ jsx3("span", { className: "cfi-sh-notif-title", children: "Notifications" }),
209
+ hasUnread && onMarkAllRead && /* @__PURE__ */ jsx3(
272
210
  "button",
273
211
  {
274
212
  className: "cfi-sh-notif-mark-all",
@@ -278,18 +216,18 @@ function NotificationBell({
278
216
  }
279
217
  )
280
218
  ] }),
281
- /* @__PURE__ */ jsx4("div", { className: "cfi-sh-notif-list", children: items.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "cfi-sh-notif-empty", children: "No notifications yet." }) : items.map((n) => /* @__PURE__ */ jsxs4(
219
+ /* @__PURE__ */ jsx3("div", { className: "cfi-sh-notif-list", children: items.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "cfi-sh-notif-empty", children: "No notifications yet." }) : items.map((n) => /* @__PURE__ */ jsxs3(
282
220
  "button",
283
221
  {
284
222
  className: `cfi-sh-notif-item ${n.readAt === null ? "cfi-sh-notif-item-unread" : ""}`,
285
223
  onClick: () => handleItemClick(n),
286
224
  type: "button",
287
225
  children: [
288
- n.readAt === null ? /* @__PURE__ */ jsx4("span", { className: "cfi-sh-notif-dot", "aria-hidden": "true" }) : /* @__PURE__ */ jsx4("span", { className: "cfi-sh-notif-dot-placeholder", "aria-hidden": "true" }),
289
- /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-notif-content", children: [
290
- /* @__PURE__ */ jsx4("div", { className: "cfi-sh-notif-item-title", children: n.title }),
291
- /* @__PURE__ */ jsx4("div", { className: "cfi-sh-notif-item-body", children: n.body }),
292
- /* @__PURE__ */ jsx4("div", { className: "cfi-sh-notif-item-time", children: formatRelative(n.createdAt) })
226
+ n.readAt === null ? /* @__PURE__ */ jsx3("span", { className: "cfi-sh-notif-dot", "aria-hidden": "true" }) : /* @__PURE__ */ jsx3("span", { className: "cfi-sh-notif-dot-placeholder", "aria-hidden": "true" }),
227
+ /* @__PURE__ */ jsxs3("div", { className: "cfi-sh-notif-content", children: [
228
+ /* @__PURE__ */ jsx3("div", { className: "cfi-sh-notif-item-title", children: n.title }),
229
+ /* @__PURE__ */ jsx3("div", { className: "cfi-sh-notif-item-body", children: n.body }),
230
+ /* @__PURE__ */ jsx3("div", { className: "cfi-sh-notif-item-time", children: formatRelative(n.createdAt) })
293
231
  ] })
294
232
  ]
295
233
  },
@@ -313,23 +251,85 @@ function formatRelative(iso) {
313
251
  }
314
252
 
315
253
  // src/UserMenu.tsx
316
- import { useState as useState4, useRef as useRef4, useEffect as useEffect4 } from "react";
317
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
318
- function UserMenu({ user, onLogout, color }) {
319
- const [open, setOpen] = useState4(false);
320
- const ref = useRef4(null);
321
- useEffect4(() => {
254
+ import { useState as useState3, useRef as useRef3, useEffect as useEffect3 } from "react";
255
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
256
+ function UserMenu({
257
+ user,
258
+ onLogout,
259
+ color,
260
+ locale,
261
+ supportedLocales,
262
+ onLocaleChange,
263
+ tenant,
264
+ tenants,
265
+ onTenantSwitch
266
+ }) {
267
+ const [open, setOpen] = useState3(false);
268
+ const [langExpanded, setLangExpanded] = useState3(false);
269
+ const [tenantExpanded, setTenantExpanded] = useState3(false);
270
+ const [updating, setUpdating] = useState3(false);
271
+ const ref = useRef3(null);
272
+ useEffect3(() => {
322
273
  function handleClickOutside(e) {
323
274
  if (ref.current && !ref.current.contains(e.target)) {
324
275
  setOpen(false);
276
+ setLangExpanded(false);
277
+ setTenantExpanded(false);
325
278
  }
326
279
  }
327
280
  if (open) document.addEventListener("mousedown", handleClickOutside);
328
281
  return () => document.removeEventListener("mousedown", handleClickOutside);
329
282
  }, [open]);
283
+ useEffect3(() => {
284
+ if (!open) {
285
+ setLangExpanded(false);
286
+ setTenantExpanded(false);
287
+ }
288
+ }, [open]);
330
289
  const initials = user.name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
331
- return /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-user-menu", ref, children: [
332
- /* @__PURE__ */ jsx5(
290
+ const showLocale = locale !== void 0 && supportedLocales !== void 0 && supportedLocales.length > 0 && onLocaleChange !== void 0;
291
+ const currentLocale = showLocale ? supportedLocales.find((l) => l.code === locale) : void 0;
292
+ async function handleLocaleSelect(code) {
293
+ if (code === locale || !onLocaleChange) {
294
+ setLangExpanded(false);
295
+ return;
296
+ }
297
+ setUpdating(true);
298
+ try {
299
+ await onLocaleChange(code);
300
+ } finally {
301
+ setUpdating(false);
302
+ setLangExpanded(false);
303
+ setOpen(false);
304
+ }
305
+ }
306
+ const showTenant = tenant !== void 0;
307
+ const canSwitchTenant = showTenant && onTenantSwitch !== void 0 && Array.isArray(tenants) && tenants.length > 1;
308
+ async function handleTenantSelect(tenantId) {
309
+ if (!onTenantSwitch || tenantId === tenant?.id) {
310
+ setTenantExpanded(false);
311
+ return;
312
+ }
313
+ const target = tenants?.find((t) => t.id === tenantId);
314
+ const targetName = target?.name ?? "this tenant";
315
+ const confirmed = window.confirm(
316
+ `Switch to ${targetName}? Other open suite tabs will reload.`
317
+ );
318
+ if (!confirmed) {
319
+ setTenantExpanded(false);
320
+ return;
321
+ }
322
+ setUpdating(true);
323
+ try {
324
+ await onTenantSwitch(tenantId);
325
+ } finally {
326
+ setUpdating(false);
327
+ setTenantExpanded(false);
328
+ setOpen(false);
329
+ }
330
+ }
331
+ return /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-user-menu", ref, children: [
332
+ /* @__PURE__ */ jsx4(
333
333
  "button",
334
334
  {
335
335
  className: "cfi-sh-avatar-btn",
@@ -337,16 +337,97 @@ function UserMenu({ user, onLogout, color }) {
337
337
  onClick: () => setOpen(!open),
338
338
  "aria-expanded": open,
339
339
  "aria-haspopup": "true",
340
- children: user.avatarUrl ? /* @__PURE__ */ jsx5("img", { src: user.avatarUrl, alt: user.name, className: "cfi-sh-avatar-img" }) : /* @__PURE__ */ jsx5("span", { className: "cfi-sh-avatar-initials", children: initials })
340
+ children: user.avatarUrl ? /* @__PURE__ */ jsx4("img", { src: user.avatarUrl, alt: user.name, className: "cfi-sh-avatar-img" }) : /* @__PURE__ */ jsx4("span", { className: "cfi-sh-avatar-initials", children: initials })
341
341
  }
342
342
  ),
343
- open && /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-user-dropdown", children: [
344
- /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-user-header", children: [
345
- /* @__PURE__ */ jsx5("span", { className: "cfi-sh-user-name", children: user.name }),
346
- /* @__PURE__ */ jsx5("span", { className: "cfi-sh-user-email", children: user.email })
343
+ open && /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-user-dropdown", children: [
344
+ /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-user-header", children: [
345
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-user-name", children: user.name }),
346
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-user-email", children: user.email })
347
+ ] }),
348
+ showTenant && /* @__PURE__ */ jsxs4(Fragment, { children: [
349
+ /* @__PURE__ */ jsx4("div", { className: "cfi-sh-divider" }),
350
+ canSwitchTenant ? /* @__PURE__ */ jsxs4(
351
+ "button",
352
+ {
353
+ className: "cfi-sh-menu-item cfi-sh-locale-row",
354
+ onClick: () => setTenantExpanded(!tenantExpanded),
355
+ "aria-expanded": tenantExpanded,
356
+ disabled: updating,
357
+ children: [
358
+ /* @__PURE__ */ jsx4(Building2, { size: 14 }),
359
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-label", children: "Tenant" }),
360
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-current", children: tenant.name }),
361
+ /* @__PURE__ */ jsx4(
362
+ ChevronDown,
363
+ {
364
+ size: 14,
365
+ className: tenantExpanded ? "cfi-sh-locale-chevron cfi-sh-locale-chevron-open" : "cfi-sh-locale-chevron"
366
+ }
367
+ )
368
+ ]
369
+ }
370
+ ) : /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-menu-item cfi-sh-tenant-static", children: [
371
+ /* @__PURE__ */ jsx4(Building2, { size: 14 }),
372
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-label", children: "Tenant" }),
373
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-current", children: tenant.name })
374
+ ] }),
375
+ canSwitchTenant && tenantExpanded && /* @__PURE__ */ jsx4("div", { className: "cfi-sh-locale-sublist", children: tenants.map((t) => {
376
+ const active = t.id === tenant.id;
377
+ return /* @__PURE__ */ jsxs4(
378
+ "button",
379
+ {
380
+ className: active ? "cfi-sh-menu-item cfi-sh-locale-sub-item cfi-sh-locale-item-active" : "cfi-sh-menu-item cfi-sh-locale-sub-item",
381
+ onClick: () => handleTenantSelect(t.id),
382
+ disabled: updating || active,
383
+ children: [
384
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-label", children: t.name }),
385
+ active ? /* @__PURE__ */ jsx4(Check, { size: 14, className: "cfi-sh-tenant-check" }) : t.role ? /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-code", children: t.role }) : null
386
+ ]
387
+ },
388
+ t.id
389
+ );
390
+ }) })
347
391
  ] }),
348
- /* @__PURE__ */ jsx5("div", { className: "cfi-sh-divider" }),
349
- /* @__PURE__ */ jsxs5(
392
+ showLocale && /* @__PURE__ */ jsxs4(Fragment, { children: [
393
+ /* @__PURE__ */ jsx4("div", { className: "cfi-sh-divider" }),
394
+ /* @__PURE__ */ jsxs4(
395
+ "button",
396
+ {
397
+ className: "cfi-sh-menu-item cfi-sh-locale-row",
398
+ onClick: () => setLangExpanded(!langExpanded),
399
+ "aria-expanded": langExpanded,
400
+ disabled: updating,
401
+ children: [
402
+ /* @__PURE__ */ jsx4(Globe, { size: 14 }),
403
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-label", children: "Language" }),
404
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-current", children: currentLocale?.label ?? locale }),
405
+ /* @__PURE__ */ jsx4(
406
+ ChevronDown,
407
+ {
408
+ size: 14,
409
+ className: langExpanded ? "cfi-sh-locale-chevron cfi-sh-locale-chevron-open" : "cfi-sh-locale-chevron"
410
+ }
411
+ )
412
+ ]
413
+ }
414
+ ),
415
+ langExpanded && /* @__PURE__ */ jsx4("div", { className: "cfi-sh-locale-sublist", children: supportedLocales.map((loc) => /* @__PURE__ */ jsxs4(
416
+ "button",
417
+ {
418
+ className: loc.code === locale ? "cfi-sh-menu-item cfi-sh-locale-sub-item cfi-sh-locale-item-active" : "cfi-sh-menu-item cfi-sh-locale-sub-item",
419
+ onClick: () => handleLocaleSelect(loc.code),
420
+ disabled: updating,
421
+ children: [
422
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-label", children: loc.label }),
423
+ /* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-code", children: loc.code.toUpperCase() })
424
+ ]
425
+ },
426
+ loc.code
427
+ )) })
428
+ ] }),
429
+ /* @__PURE__ */ jsx4("div", { className: "cfi-sh-divider" }),
430
+ /* @__PURE__ */ jsxs4(
350
431
  "button",
351
432
  {
352
433
  className: "cfi-sh-menu-item",
@@ -355,8 +436,8 @@ function UserMenu({ user, onLogout, color }) {
355
436
  onLogout?.();
356
437
  },
357
438
  children: [
358
- /* @__PURE__ */ jsx5(LogOut, { size: 14 }),
359
- /* @__PURE__ */ jsx5("span", { children: "Sign out" })
439
+ /* @__PURE__ */ jsx4(LogOut, { size: 14 }),
440
+ /* @__PURE__ */ jsx4("span", { children: "Sign out" })
360
441
  ]
361
442
  }
362
443
  )
@@ -365,7 +446,7 @@ function UserMenu({ user, onLogout, color }) {
365
446
  }
366
447
 
367
448
  // src/ShellHeader.tsx
368
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
449
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
369
450
  function ShellHeader({
370
451
  appId,
371
452
  user,
@@ -380,30 +461,24 @@ function ShellHeader({
380
461
  locale,
381
462
  supportedLocales,
382
463
  onLocaleChange,
464
+ tenant,
465
+ tenants,
466
+ onTenantSwitch,
383
467
  brandWidth,
384
468
  children
385
469
  }) {
386
470
  const currentApp = appCatalog.find((a) => a.id === appId);
387
471
  const brandStyle = brandWidth ? { width: `${brandWidth}px`, flexShrink: 0, boxSizing: "border-box" } : void 0;
388
- const showLocaleSwitcher = locale !== void 0 && supportedLocales !== void 0 && supportedLocales.length > 0 && onLocaleChange !== void 0;
389
- return /* @__PURE__ */ jsxs6("header", { className: "cfi-sh-header", children: [
390
- /* @__PURE__ */ jsxs6("div", { className: "cfi-sh-left", children: [
391
- /* @__PURE__ */ jsxs6("div", { className: "cfi-sh-app-brand", style: brandStyle, children: [
392
- currentApp && /* @__PURE__ */ jsx6("span", { className: "cfi-sh-app-badge", style: { background: currentApp.color }, children: currentApp.letter }),
393
- /* @__PURE__ */ jsx6("span", { className: "cfi-sh-app-title", children: currentApp?.name || appId })
472
+ return /* @__PURE__ */ jsxs5("header", { className: "cfi-sh-header", children: [
473
+ /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-left", children: [
474
+ /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-app-brand", style: brandStyle, children: [
475
+ currentApp && /* @__PURE__ */ jsx5("span", { className: "cfi-sh-app-badge", style: { background: currentApp.color }, children: currentApp.letter }),
476
+ /* @__PURE__ */ jsx5("span", { className: "cfi-sh-app-title", children: currentApp?.name || appId })
394
477
  ] }),
395
478
  children
396
479
  ] }),
397
- /* @__PURE__ */ jsxs6("div", { className: "cfi-sh-right", children: [
398
- showLocaleSwitcher && /* @__PURE__ */ jsx6(
399
- LocaleSwitcher,
400
- {
401
- currentLocale: locale,
402
- supportedLocales,
403
- onLocaleChange
404
- }
405
- ),
406
- /* @__PURE__ */ jsx6(
480
+ /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-right", children: [
481
+ /* @__PURE__ */ jsx5(
407
482
  NotificationBell,
408
483
  {
409
484
  count: notificationCount,
@@ -414,11 +489,88 @@ function ShellHeader({
414
489
  onItemClick: onNotificationItemClick
415
490
  }
416
491
  ),
417
- /* @__PURE__ */ jsx6(AppSwitcher, { currentAppId: appId, authorizedApps }),
418
- /* @__PURE__ */ jsx6(UserMenu, { user, onLogout, color: currentApp?.color })
492
+ /* @__PURE__ */ jsx5(AppSwitcher, { currentAppId: appId, authorizedApps }),
493
+ /* @__PURE__ */ jsx5(
494
+ UserMenu,
495
+ {
496
+ user,
497
+ onLogout,
498
+ color: currentApp?.color,
499
+ locale,
500
+ supportedLocales,
501
+ onLocaleChange,
502
+ tenant,
503
+ tenants,
504
+ onTenantSwitch
505
+ }
506
+ )
419
507
  ] })
420
508
  ] });
421
509
  }
510
+
511
+ // src/LocaleSwitcher.tsx
512
+ import { useState as useState4, useRef as useRef4, useEffect as useEffect4 } from "react";
513
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
514
+ function LocaleSwitcher({ currentLocale, supportedLocales, onLocaleChange }) {
515
+ const [open, setOpen] = useState4(false);
516
+ const [updating, setUpdating] = useState4(false);
517
+ const ref = useRef4(null);
518
+ useEffect4(() => {
519
+ function handleClickOutside(e) {
520
+ if (ref.current && !ref.current.contains(e.target)) {
521
+ setOpen(false);
522
+ }
523
+ }
524
+ if (open) document.addEventListener("mousedown", handleClickOutside);
525
+ return () => document.removeEventListener("mousedown", handleClickOutside);
526
+ }, [open]);
527
+ const current = supportedLocales.find((l) => l.code === currentLocale);
528
+ const buttonLabel = (current?.code ?? currentLocale).toUpperCase();
529
+ async function handleSelect(code) {
530
+ if (code === currentLocale) {
531
+ setOpen(false);
532
+ return;
533
+ }
534
+ setUpdating(true);
535
+ try {
536
+ await onLocaleChange(code);
537
+ } finally {
538
+ setUpdating(false);
539
+ setOpen(false);
540
+ }
541
+ }
542
+ return /* @__PURE__ */ jsxs6("div", { className: "cfi-sh-locale-switcher", ref, children: [
543
+ /* @__PURE__ */ jsxs6(
544
+ "button",
545
+ {
546
+ className: "cfi-sh-locale-btn",
547
+ onClick: () => setOpen(!open),
548
+ "aria-expanded": open,
549
+ "aria-haspopup": "true",
550
+ "aria-label": `Language: ${current?.label ?? currentLocale}`,
551
+ disabled: updating,
552
+ title: current?.label ?? currentLocale,
553
+ children: [
554
+ /* @__PURE__ */ jsx6(Globe, { size: 16 }),
555
+ /* @__PURE__ */ jsx6("span", { className: "cfi-sh-locale-label", children: buttonLabel })
556
+ ]
557
+ }
558
+ ),
559
+ open && /* @__PURE__ */ jsx6("div", { className: "cfi-sh-locale-dropdown", children: supportedLocales.map((loc) => /* @__PURE__ */ jsxs6(
560
+ "button",
561
+ {
562
+ className: loc.code === currentLocale ? "cfi-sh-menu-item cfi-sh-locale-item-active" : "cfi-sh-menu-item",
563
+ onClick: () => handleSelect(loc.code),
564
+ disabled: updating,
565
+ children: [
566
+ /* @__PURE__ */ jsx6("span", { className: "cfi-sh-locale-item-label", children: loc.label }),
567
+ /* @__PURE__ */ jsx6("span", { className: "cfi-sh-locale-item-code", children: loc.code.toUpperCase() })
568
+ ]
569
+ },
570
+ loc.code
571
+ )) })
572
+ ] });
573
+ }
422
574
  export {
423
575
  LocaleSwitcher,
424
576
  ShellHeader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campfire-interactive/shell-header",
3
- "version": "0.3.3",
3
+ "version": "0.5.1",
4
4
  "description": "Shared shell header with app switcher for Campfire Suite",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",