@campfire-interactive/shell-header 0.2.1 → 0.3.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/index.d.ts +30 -2
- package/dist/index.js +128 -49
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ interface ShellUser {
|
|
|
16
16
|
email: string;
|
|
17
17
|
avatarUrl?: string;
|
|
18
18
|
}
|
|
19
|
+
interface LocaleOption {
|
|
20
|
+
/** BCP-47-ish language code, e.g. "en", "de". */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Human-readable label shown in the dropdown, e.g. "English", "Deutsch". */
|
|
23
|
+
label: string;
|
|
24
|
+
}
|
|
19
25
|
/**
|
|
20
26
|
* Subset of the platform-notifications `Notification` shape that the bell UI
|
|
21
27
|
* actually renders. Hosts typically map their fetched items (e.g., from
|
|
@@ -61,13 +67,35 @@ interface ShellHeaderProps {
|
|
|
61
67
|
onNotificationItemClick?: (item: NotificationItem) => void;
|
|
62
68
|
/** Called when logout is clicked */
|
|
63
69
|
onLogout?: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Current user locale (e.g. "en", "de"). When provided alongside
|
|
72
|
+
* `supportedLocales` and `onLocaleChange`, a locale dropdown renders
|
|
73
|
+
* in the right-side cluster. Omitting any of the three hides it.
|
|
74
|
+
*/
|
|
75
|
+
locale?: string;
|
|
76
|
+
/** Locales the user can pick from. */
|
|
77
|
+
supportedLocales?: ReadonlyArray<LocaleOption>;
|
|
78
|
+
/**
|
|
79
|
+
* Called when the user picks a different locale. Consumer is responsible
|
|
80
|
+
* for persisting (e.g. via identity-client.me.updatePreferences) and for
|
|
81
|
+
* refreshing the JWT / reloading the page so other parts of the UI
|
|
82
|
+
* pick up the change.
|
|
83
|
+
*/
|
|
84
|
+
onLocaleChange?: (locale: string) => void | Promise<void>;
|
|
64
85
|
/** Fixed width for the app brand area (e.g., to align with a sidebar below). */
|
|
65
86
|
brandWidth?: number;
|
|
66
87
|
/** Optional content in the left/center area (filters, search, breadcrumbs, etc.) */
|
|
67
88
|
children?: React.ReactNode;
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
declare function ShellHeader({ appId, user, authorizedApps, notificationCount, onNotificationClick, notifications, onMarkRead, onMarkAllRead, onNotificationItemClick, onLogout, brandWidth, children, }: ShellHeaderProps): react_jsx_runtime.JSX.Element;
|
|
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;
|
|
92
|
+
|
|
93
|
+
interface LocaleSwitcherProps {
|
|
94
|
+
currentLocale: string;
|
|
95
|
+
supportedLocales: ReadonlyArray<LocaleOption>;
|
|
96
|
+
onLocaleChange: (locale: string) => void | Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
declare function LocaleSwitcher({ currentLocale, supportedLocales, onLocaleChange }: LocaleSwitcherProps): react_jsx_runtime.JSX.Element;
|
|
71
99
|
|
|
72
100
|
declare const appCatalog: AppDefinition[];
|
|
73
101
|
/**
|
|
@@ -78,4 +106,4 @@ declare const appCatalog: AppDefinition[];
|
|
|
78
106
|
*/
|
|
79
107
|
declare function getAppUrl(app: AppDefinition): string;
|
|
80
108
|
|
|
81
|
-
export { type AppDefinition, type NotificationItem, ShellHeader, type ShellHeaderProps, type ShellUser, appCatalog, getAppUrl };
|
|
109
|
+
export { type AppDefinition, type LocaleOption, LocaleSwitcher, type NotificationItem, ShellHeader, type ShellHeaderProps, type ShellUser, appCatalog, getAppUrl };
|
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-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-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 = [
|
|
@@ -34,7 +34,8 @@ var appCatalog = [
|
|
|
34
34
|
{ id: "pim", name: "Price Index", icon: "BarChart3", letter: "P", color: "#f97316", localPort: 3300 },
|
|
35
35
|
{ id: "cpq", name: "CPQ", icon: "Calculator", letter: "C", color: "#4f46e5", localPort: 3400 },
|
|
36
36
|
{ id: "omsf", name: "OMSF", icon: "FileText", letter: "O", color: "#0ea5e9", localPort: 3500 },
|
|
37
|
-
{ id: "identity", name: "Identity", icon: "Shield", letter: "I", color: "#8b5cf6", localPort: 3600 }
|
|
37
|
+
{ id: "identity", name: "Identity", icon: "Shield", letter: "I", color: "#8b5cf6", localPort: 3600 },
|
|
38
|
+
{ id: "bom", name: "BOM", icon: "Network", letter: "B", color: "#0d9488", localPort: 3700 }
|
|
38
39
|
];
|
|
39
40
|
function getAppUrl(app) {
|
|
40
41
|
const host = typeof window !== "undefined" ? window.location.hostname : "";
|
|
@@ -62,7 +63,8 @@ import {
|
|
|
62
63
|
Shield,
|
|
63
64
|
GripHorizontal,
|
|
64
65
|
Bell,
|
|
65
|
-
LogOut
|
|
66
|
+
LogOut,
|
|
67
|
+
Globe
|
|
66
68
|
} from "lucide-react";
|
|
67
69
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
68
70
|
var iconMap = {
|
|
@@ -141,9 +143,73 @@ function AppSwitcher({ currentAppId, authorizedApps }) {
|
|
|
141
143
|
] });
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
// src/
|
|
145
|
-
import {
|
|
146
|
+
// src/LocaleSwitcher.tsx
|
|
147
|
+
import { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
146
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
|
+
// 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";
|
|
147
213
|
function NotificationBell({
|
|
148
214
|
count = 0,
|
|
149
215
|
onClick,
|
|
@@ -153,9 +219,9 @@ function NotificationBell({
|
|
|
153
219
|
onItemClick
|
|
154
220
|
}) {
|
|
155
221
|
const hasDropdown = items !== void 0;
|
|
156
|
-
const [open, setOpen] =
|
|
157
|
-
const ref =
|
|
158
|
-
|
|
222
|
+
const [open, setOpen] = useState3(false);
|
|
223
|
+
const ref = useRef3(null);
|
|
224
|
+
useEffect3(() => {
|
|
159
225
|
function handleClickOutside(e) {
|
|
160
226
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
161
227
|
setOpen(false);
|
|
@@ -164,7 +230,7 @@ function NotificationBell({
|
|
|
164
230
|
if (open) document.addEventListener("mousedown", handleClickOutside);
|
|
165
231
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
166
232
|
}, [open]);
|
|
167
|
-
|
|
233
|
+
useEffect3(() => {
|
|
168
234
|
function handleEsc(e) {
|
|
169
235
|
if (e.key === "Escape") setOpen(false);
|
|
170
236
|
}
|
|
@@ -184,8 +250,8 @@ function NotificationBell({
|
|
|
184
250
|
setOpen(false);
|
|
185
251
|
};
|
|
186
252
|
const hasUnread = items?.some((i) => i.readAt === null) ?? false;
|
|
187
|
-
return /* @__PURE__ */
|
|
188
|
-
/* @__PURE__ */
|
|
253
|
+
return /* @__PURE__ */ jsxs4("div", { className: "cfi-sh-notif", ref, children: [
|
|
254
|
+
/* @__PURE__ */ jsxs4(
|
|
189
255
|
"button",
|
|
190
256
|
{
|
|
191
257
|
className: "cfi-sh-icon-btn",
|
|
@@ -194,15 +260,15 @@ function NotificationBell({
|
|
|
194
260
|
"aria-haspopup": hasDropdown ? "true" : void 0,
|
|
195
261
|
"aria-label": "Notifications",
|
|
196
262
|
children: [
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
-
count > 0 && /* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ jsx4(Bell, { size: 18 }),
|
|
264
|
+
count > 0 && /* @__PURE__ */ jsx4("span", { className: "cfi-sh-badge", children: count > 99 ? "99+" : count })
|
|
199
265
|
]
|
|
200
266
|
}
|
|
201
267
|
),
|
|
202
|
-
hasDropdown && open && /* @__PURE__ */
|
|
203
|
-
/* @__PURE__ */
|
|
204
|
-
/* @__PURE__ */
|
|
205
|
-
hasUnread && onMarkAllRead && /* @__PURE__ */
|
|
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
272
|
"button",
|
|
207
273
|
{
|
|
208
274
|
className: "cfi-sh-notif-mark-all",
|
|
@@ -212,18 +278,18 @@ function NotificationBell({
|
|
|
212
278
|
}
|
|
213
279
|
)
|
|
214
280
|
] }),
|
|
215
|
-
/* @__PURE__ */
|
|
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(
|
|
216
282
|
"button",
|
|
217
283
|
{
|
|
218
284
|
className: `cfi-sh-notif-item ${n.readAt === null ? "cfi-sh-notif-item-unread" : ""}`,
|
|
219
285
|
onClick: () => handleItemClick(n),
|
|
220
286
|
type: "button",
|
|
221
287
|
children: [
|
|
222
|
-
n.readAt === null ? /* @__PURE__ */
|
|
223
|
-
/* @__PURE__ */
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
/* @__PURE__ */
|
|
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) })
|
|
227
293
|
] })
|
|
228
294
|
]
|
|
229
295
|
},
|
|
@@ -247,12 +313,12 @@ function formatRelative(iso) {
|
|
|
247
313
|
}
|
|
248
314
|
|
|
249
315
|
// src/UserMenu.tsx
|
|
250
|
-
import { useState as
|
|
251
|
-
import { jsx as
|
|
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";
|
|
252
318
|
function UserMenu({ user, onLogout, color }) {
|
|
253
|
-
const [open, setOpen] =
|
|
254
|
-
const ref =
|
|
255
|
-
|
|
319
|
+
const [open, setOpen] = useState4(false);
|
|
320
|
+
const ref = useRef4(null);
|
|
321
|
+
useEffect4(() => {
|
|
256
322
|
function handleClickOutside(e) {
|
|
257
323
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
258
324
|
setOpen(false);
|
|
@@ -262,8 +328,8 @@ function UserMenu({ user, onLogout, color }) {
|
|
|
262
328
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
263
329
|
}, [open]);
|
|
264
330
|
const initials = user.name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
|
|
265
|
-
return /* @__PURE__ */
|
|
266
|
-
/* @__PURE__ */
|
|
331
|
+
return /* @__PURE__ */ jsxs5("div", { className: "cfi-sh-user-menu", ref, children: [
|
|
332
|
+
/* @__PURE__ */ jsx5(
|
|
267
333
|
"button",
|
|
268
334
|
{
|
|
269
335
|
className: "cfi-sh-avatar-btn",
|
|
@@ -271,16 +337,16 @@ function UserMenu({ user, onLogout, color }) {
|
|
|
271
337
|
onClick: () => setOpen(!open),
|
|
272
338
|
"aria-expanded": open,
|
|
273
339
|
"aria-haspopup": "true",
|
|
274
|
-
children: user.avatarUrl ? /* @__PURE__ */
|
|
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 })
|
|
275
341
|
}
|
|
276
342
|
),
|
|
277
|
-
open && /* @__PURE__ */
|
|
278
|
-
/* @__PURE__ */
|
|
279
|
-
/* @__PURE__ */
|
|
280
|
-
/* @__PURE__ */
|
|
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 })
|
|
281
347
|
] }),
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
/* @__PURE__ */
|
|
348
|
+
/* @__PURE__ */ jsx5("div", { className: "cfi-sh-divider" }),
|
|
349
|
+
/* @__PURE__ */ jsxs5(
|
|
284
350
|
"button",
|
|
285
351
|
{
|
|
286
352
|
className: "cfi-sh-menu-item",
|
|
@@ -289,8 +355,8 @@ function UserMenu({ user, onLogout, color }) {
|
|
|
289
355
|
onLogout?.();
|
|
290
356
|
},
|
|
291
357
|
children: [
|
|
292
|
-
/* @__PURE__ */
|
|
293
|
-
/* @__PURE__ */
|
|
358
|
+
/* @__PURE__ */ jsx5(LogOut, { size: 14 }),
|
|
359
|
+
/* @__PURE__ */ jsx5("span", { children: "Sign out" })
|
|
294
360
|
]
|
|
295
361
|
}
|
|
296
362
|
)
|
|
@@ -299,7 +365,7 @@ function UserMenu({ user, onLogout, color }) {
|
|
|
299
365
|
}
|
|
300
366
|
|
|
301
367
|
// src/ShellHeader.tsx
|
|
302
|
-
import { jsx as
|
|
368
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
303
369
|
function ShellHeader({
|
|
304
370
|
appId,
|
|
305
371
|
user,
|
|
@@ -311,21 +377,33 @@ function ShellHeader({
|
|
|
311
377
|
onMarkAllRead,
|
|
312
378
|
onNotificationItemClick,
|
|
313
379
|
onLogout,
|
|
380
|
+
locale,
|
|
381
|
+
supportedLocales,
|
|
382
|
+
onLocaleChange,
|
|
314
383
|
brandWidth,
|
|
315
384
|
children
|
|
316
385
|
}) {
|
|
317
386
|
const currentApp = appCatalog.find((a) => a.id === appId);
|
|
318
387
|
const brandStyle = brandWidth ? { width: `${brandWidth}px`, flexShrink: 0, boxSizing: "border-box" } : void 0;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
/* @__PURE__ */
|
|
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 })
|
|
324
394
|
] }),
|
|
325
395
|
children
|
|
326
396
|
] }),
|
|
327
|
-
/* @__PURE__ */
|
|
328
|
-
/* @__PURE__ */
|
|
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(
|
|
329
407
|
NotificationBell,
|
|
330
408
|
{
|
|
331
409
|
count: notificationCount,
|
|
@@ -336,12 +414,13 @@ function ShellHeader({
|
|
|
336
414
|
onItemClick: onNotificationItemClick
|
|
337
415
|
}
|
|
338
416
|
),
|
|
339
|
-
/* @__PURE__ */
|
|
340
|
-
/* @__PURE__ */
|
|
417
|
+
/* @__PURE__ */ jsx6(AppSwitcher, { currentAppId: appId, authorizedApps }),
|
|
418
|
+
/* @__PURE__ */ jsx6(UserMenu, { user, onLogout, color: currentApp?.color })
|
|
341
419
|
] })
|
|
342
420
|
] });
|
|
343
421
|
}
|
|
344
422
|
export {
|
|
423
|
+
LocaleSwitcher,
|
|
345
424
|
ShellHeader,
|
|
346
425
|
appCatalog,
|
|
347
426
|
getAppUrl
|