@propbinder/mobile-design 0.2.48 → 0.2.50
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/ng-package.json +24 -0
- package/package.json +3 -39
- package/src/animations/page-transitions.ts +165 -0
- package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
- package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
- package/src/components/action-list-item/index.ts +2 -0
- package/src/components/app-icon/ds-app-icon.ts +133 -0
- package/src/components/app-icon/index.ts +2 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
- package/src/components/attachment-preview/index.ts +1 -0
- package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
- package/src/components/avatar-with-badge/index.ts +2 -0
- package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
- package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
- package/src/components/booking-modal/index.ts +4 -0
- package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
- package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
- package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
- package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
- package/src/components/bottom-sheet/index.ts +8 -0
- package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
- package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
- package/src/components/card-inline/index.ts +2 -0
- package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
- package/src/components/card-inline-banner/index.ts +1 -0
- package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
- package/src/components/card-inline-contact/index.ts +1 -0
- package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
- package/src/components/card-inline-file/index.ts +1 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
- package/src/components/chat-modal/index.ts +8 -0
- package/src/components/comment/ds-mobile-comment.ts +568 -0
- package/src/components/comment/index.ts +2 -0
- package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
- package/src/components/contact-list-item/index.ts +2 -0
- package/src/components/content/ds-mobile-content.ts +139 -0
- package/src/components/content/index.ts +2 -0
- package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
- package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
- package/src/components/dropdown/index.ts +2 -0
- package/src/components/ds-mobile-tabs.css +407 -0
- package/src/components/ds-mobile-tabs.ts +216 -0
- package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/fab/ds-mobile-fab.ts +315 -0
- package/src/components/fab/index.ts +1 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
- package/src/components/facility-creation-modal/index.ts +9 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
- package/src/components/facility-detail-modal/index.ts +2 -0
- package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
- package/src/components/file-attachment/index.ts +2 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
- package/src/components/handbook-detail-modal/index.ts +3 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
- package/src/components/handbook-folder/index.ts +4 -0
- package/src/components/header-content/ds-mobile-header-content.ts +222 -0
- package/src/components/header-content/index.ts +2 -0
- package/src/components/illustration/ds-mobile-illustration.ts +124 -0
- package/src/components/illustration/index.ts +2 -0
- package/src/components/index.ts +124 -0
- package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
- package/src/components/inline-photo/index.ts +1 -0
- package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
- package/src/components/inline-tabs/index.ts +2 -0
- package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
- package/src/components/interactive-list-item-booking/index.ts +1 -0
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
- package/src/components/interactive-list-item-inquiry/index.ts +2 -0
- package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
- package/src/components/interactive-list-item-message/index.ts +2 -0
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
- package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
- package/src/components/interactive-list-item-post/index.ts +13 -0
- package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
- package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
- package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
- package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
- package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
- package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
- package/src/components/lightbox/index.ts +22 -0
- package/src/components/list-item/ds-mobile-list-item.ts +603 -0
- package/src/components/list-item/index.ts +2 -0
- package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
- package/src/components/list-item-static/index.ts +2 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
- package/src/components/loader-overlay/index.ts +1 -0
- package/src/components/logo/ds-logo.ts +95 -0
- package/src/components/logo/index.ts +2 -0
- package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
- package/src/components/message-bubble/index.ts +7 -0
- package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
- package/src/components/message-composer/index.ts +7 -0
- package/src/components/modal/ds-mobile-modal.css +163 -0
- package/src/components/modal/ds-mobile-modal.service.ts +329 -0
- package/src/components/modal/index.ts +8 -0
- package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
- package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
- package/src/components/modal-base/index.ts +2 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
- package/src/components/new-inquiry-modal/index.ts +4 -0
- package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
- package/src/components/offline-banner/index.ts +1 -0
- package/src/components/page-details/ds-mobile-page-details.css +83 -0
- package/src/components/page-details/ds-mobile-page-details.ts +282 -0
- package/src/components/page-details/index.ts +2 -0
- package/src/components/page-main/ds-mobile-page-main.css +68 -0
- package/src/components/page-main/ds-mobile-page-main.ts +421 -0
- package/src/components/page-main/index.ts +2 -0
- package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
- package/src/components/post-composer/index.ts +2 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
- package/src/components/post-detail-modal/index.ts +9 -0
- package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
- package/src/components/property-banner/index.ts +2 -0
- package/src/components/section/ds-mobile-section.ts +263 -0
- package/src/components/section/index.ts +2 -0
- package/src/components/shared/directives/index.ts +2 -0
- package/src/components/shared/directives/long-press.directive.ts +212 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/shared/mobile-modal-base.ts +457 -0
- package/src/components/shared/mobile-page-base.ts +204 -0
- package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
- package/src/components/swiper/ds-mobile-swiper.ts +327 -0
- package/src/components/swiper/index.ts +3 -0
- package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
- package/src/components/system-message-banner/index.ts +2 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
- package/src/components/tab-bar/index.ts +2 -0
- package/src/components/tabs/ds-mobile-tabs.css +25 -0
- package/src/components/tabs/ds-mobile-tabs.ts +89 -0
- package/src/components/tabs/index.ts +2 -0
- package/src/components/text-input/ds-text-input.ts +287 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/examples/booking.page.ts +434 -0
- package/src/examples/community.page.ts +776 -0
- package/src/examples/handbook.page.ts +324 -0
- package/src/examples/home.page.ts +347 -0
- package/src/examples/index.ts +12 -0
- package/src/examples/inquiries.example.ts +273 -0
- package/src/examples/inquiry-detail.example.css +189 -0
- package/src/examples/inquiry-detail.example.ts +415 -0
- package/src/examples/mobile-tabs-example.component.ts +208 -0
- package/src/examples/post-create.page.ts +311 -0
- package/src/examples/post-detail.page.ts +296 -0
- package/src/examples/sign-in.page.ts +291 -0
- package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
- package/src/examples/whitelabel-demo-modal.service.ts +77 -0
- package/src/models/index.ts +7 -0
- package/src/models/post.model.ts +41 -0
- package/src/pages/community.page.ts +769 -0
- package/src/pages/handbook.page.ts +388 -0
- package/src/pages/home.page.ts +303 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/inquiries.example.ts +273 -0
- package/src/pages/inquiry-detail.example.css +189 -0
- package/src/pages/inquiry-detail.example.ts +415 -0
- package/src/pages/mobile-tabs-example.component.ts +179 -0
- package/src/pages/post-create.page.ts +311 -0
- package/src/pages/post-detail.page.ts +296 -0
- package/src/pages/sign-in.page.ts +291 -0
- package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
- package/src/pages/whitelabel-demo-modal.service.ts +77 -0
- package/src/public-api.ts +6 -0
- package/src/services/base-modal.service.ts +101 -0
- package/src/services/index.ts +11 -0
- package/src/services/posts.service.ts +542 -0
- package/src/services/tracking-permission.service.ts +88 -0
- package/src/services/user.service.ts +60 -0
- package/src/services/whitelabel.service.ts +675 -0
- package/{styles → src/styles}/ionic.css +25 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +13 -0
- package/fesm2022/propbinder-mobile-design.mjs +0 -26168
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8169
- /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
- /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
- /package/{styles → src/components/shared}/mobile-common.css +0 -0
- /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { Injectable, signal, effect, computed } from '@angular/core';
|
|
2
|
+
import { StatusBar, Style } from '@capacitor/status-bar';
|
|
3
|
+
|
|
4
|
+
export interface WhitelabelConfig {
|
|
5
|
+
// Logo assets
|
|
6
|
+
logoUrl: string; // Full logo for header (typically horizontal)
|
|
7
|
+
logoMarkUrl: string; // Compact logo mark for avatars/badges
|
|
8
|
+
logoAlt: string; // Alt text for accessibility
|
|
9
|
+
logoSize: 'sm' | 'md' | 'lg' | 'xl'; // Logo size in header (sm: 24px, md: 28px, lg: 32px, xl: 36px)
|
|
10
|
+
|
|
11
|
+
// Logo dimensions (optional, for optimization)
|
|
12
|
+
logoWidth?: number;
|
|
13
|
+
logoHeight?: number;
|
|
14
|
+
logoMarkWidth?: number;
|
|
15
|
+
logoMarkHeight?: number;
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// APP ICON (app icons, logo badges)
|
|
19
|
+
// ============================================
|
|
20
|
+
appIconSurface: string; // App icon background fill
|
|
21
|
+
appIconContent: string; // Logomark color on app icon
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// ACTIONS & SELECTIONS (buttons, FABs, active tabs, selected items)
|
|
25
|
+
// ============================================
|
|
26
|
+
accent: string; // Main accent color (button bg, active tab icon)
|
|
27
|
+
onAccent: string; // Content on accent-colored surfaces (button text/icon)
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// HEADER/NAVIGATION (includes ion-header and header-expandable)
|
|
31
|
+
// ============================================
|
|
32
|
+
headerSurface: string; // Header background
|
|
33
|
+
headerContent: string; // Header text/icons
|
|
34
|
+
headerAccent: string; // Accent elements in header (e.g., subtle overlay)
|
|
35
|
+
onHeaderAccent: string; // Content on header accent elements
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// SIGN-IN PAGE CUSTOMIZATION
|
|
39
|
+
// ============================================
|
|
40
|
+
showCityIllustration: boolean; // Show/hide city illustration on sign-in page
|
|
41
|
+
|
|
42
|
+
// Sign-in background
|
|
43
|
+
signInBgType: 'solid' | 'gradient'; // Background type
|
|
44
|
+
signInBgSolid: string; // Solid background color
|
|
45
|
+
signInBgGradientStart: string; // Gradient start color (top)
|
|
46
|
+
signInBgGradientEnd: string; // Gradient end color (bottom)
|
|
47
|
+
|
|
48
|
+
// Sign-in content color
|
|
49
|
+
signInContentColor: string; // Text color on sign-in page (headings, body text, links)
|
|
50
|
+
|
|
51
|
+
// Organization info
|
|
52
|
+
organizationName: string;
|
|
53
|
+
organizationId: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_CONFIG: WhitelabelConfig = {
|
|
57
|
+
logoUrl: '/Assets/logos/propbinder-logomark.svg',
|
|
58
|
+
logoMarkUrl: '/Assets/logos/propbinder-logomark.svg',
|
|
59
|
+
logoAlt: 'Propbinder',
|
|
60
|
+
logoSize: 'md', // Propbinder default: md (28px mobile, 32px desktop)
|
|
61
|
+
|
|
62
|
+
// App icon (brand identity)
|
|
63
|
+
appIconSurface: '#6B5FF5', // Purple
|
|
64
|
+
appIconContent: '#FFFFFF', // White
|
|
65
|
+
|
|
66
|
+
// Accent (buttons, FABs, active tabs)
|
|
67
|
+
accent: '#6B5FF5', // Purple
|
|
68
|
+
onAccent: '#FFFFFF', // White
|
|
69
|
+
|
|
70
|
+
// Header/navigation
|
|
71
|
+
headerSurface: '#221a4c', // Dark purple
|
|
72
|
+
headerContent: '#FFFFFF', // White
|
|
73
|
+
headerAccent: '#6B5FF5', // Purple accent (matches Propbinder theme)
|
|
74
|
+
onHeaderAccent: '#FFFFFF', // White
|
|
75
|
+
|
|
76
|
+
// Sign-in page
|
|
77
|
+
showCityIllustration: true, // Show city illustration by default
|
|
78
|
+
signInBgType: 'gradient', // Use gradient by default
|
|
79
|
+
signInBgSolid: '#D6C7FF', // Fallback solid color
|
|
80
|
+
signInBgGradientStart: '#D6C7FF', // Gradient top color
|
|
81
|
+
signInBgGradientEnd: '#8A9BFF', // Gradient bottom color
|
|
82
|
+
signInContentColor: '#1a1a1a', // Default dark text color
|
|
83
|
+
|
|
84
|
+
organizationName: 'Propbinder',
|
|
85
|
+
organizationId: 'default',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* WhitelabelService
|
|
90
|
+
*
|
|
91
|
+
* Manages whitelabel configuration including logos and brand colors.
|
|
92
|
+
* Automatically updates CSS custom properties when colors change.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* Initialize with custom config:
|
|
96
|
+
* ```typescript
|
|
97
|
+
* whitelabelService.initialize({
|
|
98
|
+
* logoUrl: '/Assets/logos/acme-logo.svg',
|
|
99
|
+
* logoMarkUrl: '/Assets/logos/acme-mark.svg',
|
|
100
|
+
* accent: '#2563eb',
|
|
101
|
+
* onAccent: '#ffffff',
|
|
102
|
+
* headerSurface: '#1e40af',
|
|
103
|
+
* headerContent: '#ffffff',
|
|
104
|
+
* organizationName: 'Acme Corp'
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Load from API:
|
|
109
|
+
* ```typescript
|
|
110
|
+
* await whitelabelService.loadFromApi('acme-corp');
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
@Injectable({
|
|
114
|
+
providedIn: 'root',
|
|
115
|
+
})
|
|
116
|
+
export class WhitelabelService {
|
|
117
|
+
private _config = signal<WhitelabelConfig>(DEFAULT_CONFIG);
|
|
118
|
+
|
|
119
|
+
// Readonly computed signals for accessing config values
|
|
120
|
+
readonly logoUrl = computed(() => this._config().logoUrl);
|
|
121
|
+
readonly logoMarkUrl = computed(() => this._config().logoMarkUrl);
|
|
122
|
+
readonly logoAlt = computed(() => this._config().logoAlt);
|
|
123
|
+
readonly logoSize = computed(() => this._config().logoSize);
|
|
124
|
+
readonly logoHeight = computed(() => {
|
|
125
|
+
const customHeight = this._config().logoHeight;
|
|
126
|
+
const size = this._config().logoSize;
|
|
127
|
+
|
|
128
|
+
// If custom height is explicitly specified, always use it
|
|
129
|
+
if (customHeight !== undefined) {
|
|
130
|
+
return customHeight;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Otherwise calculate height based on logoSize
|
|
134
|
+
switch (size) {
|
|
135
|
+
case 'sm':
|
|
136
|
+
return 24;
|
|
137
|
+
case 'md':
|
|
138
|
+
return 28;
|
|
139
|
+
case 'lg':
|
|
140
|
+
return 32;
|
|
141
|
+
case 'xl':
|
|
142
|
+
return 36;
|
|
143
|
+
default:
|
|
144
|
+
return 28;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
readonly appIconSurface = computed(() => this._config().appIconSurface);
|
|
148
|
+
readonly appIconContent = computed(() => this._config().appIconContent);
|
|
149
|
+
readonly accent = computed(() => this._config().accent);
|
|
150
|
+
readonly onAccent = computed(() => this._config().onAccent);
|
|
151
|
+
readonly headerSurface = computed(() => this._config().headerSurface);
|
|
152
|
+
readonly headerContent = computed(() => this._config().headerContent);
|
|
153
|
+
readonly headerAccent = computed(() => this._config().headerAccent);
|
|
154
|
+
readonly onHeaderAccent = computed(() => this._config().onHeaderAccent);
|
|
155
|
+
readonly showCityIllustration = computed(() => this._config().showCityIllustration);
|
|
156
|
+
readonly signInBgType = computed(() => this._config().signInBgType);
|
|
157
|
+
readonly signInBgSolid = computed(() => this._config().signInBgSolid);
|
|
158
|
+
readonly signInBgGradientStart = computed(() => this._config().signInBgGradientStart);
|
|
159
|
+
readonly signInBgGradientEnd = computed(() => this._config().signInBgGradientEnd);
|
|
160
|
+
readonly signInContentColor = computed(() => this._config().signInContentColor);
|
|
161
|
+
readonly organizationName = computed(() => this._config().organizationName);
|
|
162
|
+
readonly organizationId = computed(() => this._config().organizationId);
|
|
163
|
+
|
|
164
|
+
// Computed background style for sign-in page
|
|
165
|
+
readonly signInBgStyle = computed(() => {
|
|
166
|
+
const config = this._config();
|
|
167
|
+
if (config.signInBgType === 'solid') {
|
|
168
|
+
return config.signInBgSolid;
|
|
169
|
+
} else {
|
|
170
|
+
return `linear-gradient(180deg, ${config.signInBgGradientStart} 0%, ${config.signInBgGradientEnd} 100%)`;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Full config accessor
|
|
175
|
+
readonly config = this._config.asReadonly();
|
|
176
|
+
|
|
177
|
+
constructor() {
|
|
178
|
+
// Apply default colors on initialization
|
|
179
|
+
this.applyColors(DEFAULT_CONFIG);
|
|
180
|
+
|
|
181
|
+
// Watch for config changes and update CSS custom properties
|
|
182
|
+
effect(() => {
|
|
183
|
+
const config = this._config();
|
|
184
|
+
this.applyColors(config);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Listen for modal dismissals and re-apply status bar
|
|
188
|
+
// This fixes the issue where Ionic restores cached status bar state
|
|
189
|
+
this.setupModalDismissListener();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Setup global listener for modal dismiss events
|
|
194
|
+
* Re-applies status bar after modals close to override Ionic's cached state restoration
|
|
195
|
+
*/
|
|
196
|
+
private setupModalDismissListener(): void {
|
|
197
|
+
if (typeof document === 'undefined') return;
|
|
198
|
+
|
|
199
|
+
document.addEventListener('ionModalDidDismiss', () => {
|
|
200
|
+
// Small delay to let Ionic's restoration happen first
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
// Check if we're on the sign-in page
|
|
203
|
+
const isOnSignInPage = document.querySelector('app-sign-in') !== null;
|
|
204
|
+
|
|
205
|
+
if (isOnSignInPage) {
|
|
206
|
+
// On sign-in page - use sign-in background color
|
|
207
|
+
const config = this._config();
|
|
208
|
+
const backgroundColor = config.signInBgType === 'gradient' ? config.signInBgGradientStart : config.signInBgSolid;
|
|
209
|
+
const style = this.getStatusBarStyleForColor(backgroundColor);
|
|
210
|
+
|
|
211
|
+
StatusBar.setBackgroundColor({ color: backgroundColor }).catch(() => {});
|
|
212
|
+
StatusBar.setStyle({ style }).catch(() => {});
|
|
213
|
+
} else {
|
|
214
|
+
// On regular page - use header color
|
|
215
|
+
const config = this._config();
|
|
216
|
+
const headerColor = config.headerSurface;
|
|
217
|
+
const style = this.getStatusBarStyleForColor(headerColor);
|
|
218
|
+
|
|
219
|
+
StatusBar.setStyle({ style }).catch(() => {});
|
|
220
|
+
}
|
|
221
|
+
}, 50);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Initialize whitelabel configuration
|
|
227
|
+
* Call this early in app initialization (app.config.ts or app.component.ts)
|
|
228
|
+
*/
|
|
229
|
+
initialize(config: Partial<WhitelabelConfig>) {
|
|
230
|
+
this._config.update((current) => ({
|
|
231
|
+
...current,
|
|
232
|
+
...config,
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Load whitelabel config from API
|
|
238
|
+
* Typically called on app startup based on subdomain, user tenant, etc.
|
|
239
|
+
*
|
|
240
|
+
* @param organizationId - The organization identifier (subdomain, tenant ID, etc.)
|
|
241
|
+
*/
|
|
242
|
+
async loadFromApi(organizationId?: string): Promise<void> {
|
|
243
|
+
try {
|
|
244
|
+
// Example API call structure
|
|
245
|
+
// const response = await fetch(`/api/whitelabel/${organizationId || 'default'}`);
|
|
246
|
+
// const config = await response.json();
|
|
247
|
+
// this.initialize(config);
|
|
248
|
+
|
|
249
|
+
//console.log('Loading whitelabel config from API for:', organizationId);
|
|
250
|
+
|
|
251
|
+
// Example: Different configs for different organizations
|
|
252
|
+
if (organizationId === 'demo-client') {
|
|
253
|
+
this.initialize({
|
|
254
|
+
logoUrl: '/Assets/logos/demo-logo.svg',
|
|
255
|
+
logoMarkUrl: '/Assets/logos/demo-mark.svg',
|
|
256
|
+
logoAlt: 'Demo Client',
|
|
257
|
+
appIconSurface: '#2563eb',
|
|
258
|
+
appIconContent: '#FFFFFF',
|
|
259
|
+
accent: '#2563eb',
|
|
260
|
+
onAccent: '#FFFFFF',
|
|
261
|
+
headerSurface: '#1e40af',
|
|
262
|
+
headerContent: '#FFFFFF',
|
|
263
|
+
headerAccent: 'rgba(255, 255, 255, 0.15)',
|
|
264
|
+
onHeaderAccent: '#FFFFFF',
|
|
265
|
+
organizationName: 'Demo Client',
|
|
266
|
+
organizationId: 'demo-client',
|
|
267
|
+
});
|
|
268
|
+
} else if (organizationId === 'cobblestone') {
|
|
269
|
+
this.initialize({
|
|
270
|
+
logoUrl: '/Assets/logos/cobblestone-logo.svg',
|
|
271
|
+
logoMarkUrl: '/Assets/logos/cobblestone-logomark.svg',
|
|
272
|
+
logoAlt: 'Cobblestone',
|
|
273
|
+
logoSize: 'sm',
|
|
274
|
+
appIconSurface: '#2C3E50',
|
|
275
|
+
appIconContent: '#FFFFFF',
|
|
276
|
+
accent: '#3498DB',
|
|
277
|
+
onAccent: '#FFFFFF',
|
|
278
|
+
headerSurface: '#2C3E50',
|
|
279
|
+
headerContent: '#FFFFFF',
|
|
280
|
+
headerAccent: '#3498DB',
|
|
281
|
+
onHeaderAccent: '#FFFFFF',
|
|
282
|
+
showCityIllustration: false,
|
|
283
|
+
signInBgType: 'gradient',
|
|
284
|
+
signInBgSolid: '#E8EEF2',
|
|
285
|
+
signInBgGradientStart: '#E8EEF2',
|
|
286
|
+
signInBgGradientEnd: '#BDC3C7',
|
|
287
|
+
signInContentColor: '#1a1a1a',
|
|
288
|
+
organizationName: 'Cobblestone',
|
|
289
|
+
organizationId: 'cobblestone',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Add more organization-specific configs as needed
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Failed to load whitelabel config:', error);
|
|
295
|
+
// Fallback to defaults already set
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Update config dynamically (e.g., when user switches organizations)
|
|
301
|
+
*/
|
|
302
|
+
updateConfig(updates: Partial<WhitelabelConfig>) {
|
|
303
|
+
this.initialize(updates);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Update only the brand colors
|
|
308
|
+
*/
|
|
309
|
+
updateColors(colors: {
|
|
310
|
+
appIconSurface?: string;
|
|
311
|
+
appIconContent?: string;
|
|
312
|
+
accent?: string;
|
|
313
|
+
onAccent?: string;
|
|
314
|
+
headerSurface?: string;
|
|
315
|
+
headerContent?: string;
|
|
316
|
+
headerAccent?: string;
|
|
317
|
+
onHeaderAccent?: string;
|
|
318
|
+
}) {
|
|
319
|
+
this._config.update((current) => ({
|
|
320
|
+
...current,
|
|
321
|
+
...colors,
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Reset to default configuration
|
|
327
|
+
*/
|
|
328
|
+
resetToDefault() {
|
|
329
|
+
this._config.set(DEFAULT_CONFIG);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Convert hex color to RGB values
|
|
334
|
+
*/
|
|
335
|
+
private hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
|
336
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
337
|
+
return result
|
|
338
|
+
? {
|
|
339
|
+
r: parseInt(result[1], 16),
|
|
340
|
+
g: parseInt(result[2], 16),
|
|
341
|
+
b: parseInt(result[3], 16),
|
|
342
|
+
}
|
|
343
|
+
: null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Calculate relative luminance of a color (WCAG standard)
|
|
348
|
+
* Returns a value between 0 (darkest) and 1 (lightest)
|
|
349
|
+
*/
|
|
350
|
+
private getRelativeLuminance(color: string): number {
|
|
351
|
+
const rgb = this.hexToRgb(color);
|
|
352
|
+
if (!rgb) return 0;
|
|
353
|
+
|
|
354
|
+
const rsRGB = rgb.r / 255;
|
|
355
|
+
const gsRGB = rgb.g / 255;
|
|
356
|
+
const bsRGB = rgb.b / 255;
|
|
357
|
+
|
|
358
|
+
const r = rsRGB <= 0.03928 ? rsRGB / 12.92 : Math.pow((rsRGB + 0.055) / 1.055, 2.4);
|
|
359
|
+
const g = gsRGB <= 0.03928 ? gsRGB / 12.92 : Math.pow((gsRGB + 0.055) / 1.055, 2.4);
|
|
360
|
+
const b = bsRGB <= 0.03928 ? bsRGB / 12.92 : Math.pow((bsRGB + 0.055) / 1.055, 2.4);
|
|
361
|
+
|
|
362
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Determine if a color is light (needs dark status bar content)
|
|
367
|
+
*/
|
|
368
|
+
private isColorLight(color: string): boolean {
|
|
369
|
+
const luminance = this.getRelativeLuminance(color);
|
|
370
|
+
return luminance > 0.5;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get the appropriate status bar style for a background color
|
|
375
|
+
* Public method for use by sign-in page
|
|
376
|
+
*/
|
|
377
|
+
getStatusBarStyleForColor(color: string): Style {
|
|
378
|
+
// Style.Dark = black icons (for light backgrounds)
|
|
379
|
+
// Style.Light = white icons (for dark backgrounds)
|
|
380
|
+
return this.isColorLight(color) ? Style.Light : Style.Dark;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Generate a hover state color by applying a black overlay
|
|
385
|
+
* This simulates the effect of overlaying #000000 with 10% opacity
|
|
386
|
+
*
|
|
387
|
+
* @param baseColor - Hex color to overlay on (e.g., '#6B5FF5')
|
|
388
|
+
* @param overlayColor - Overlay color (default: '#000000' for darkening)
|
|
389
|
+
* @param overlayAlpha - Opacity of overlay (0-1, default: 0.1 = 10%)
|
|
390
|
+
* @returns Hex color with overlay applied
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* generateHoverColor('#6B5FF5') // Returns darker purple for hover state
|
|
394
|
+
* generateHoverColor('#FF0000', '#FFFFFF', 0.2) // Lighten red by 20%
|
|
395
|
+
*/
|
|
396
|
+
private generateHoverColor(baseColor: string, overlayColor: string = '#000000', overlayAlpha: number = 0.1): string {
|
|
397
|
+
const base = this.hexToRgb(baseColor);
|
|
398
|
+
const overlay = this.hexToRgb(overlayColor);
|
|
399
|
+
|
|
400
|
+
if (!base || !overlay) return baseColor;
|
|
401
|
+
|
|
402
|
+
// Alpha blending formula: result = overlay * alpha + base * (1 - alpha)
|
|
403
|
+
const r = Math.round(overlay.r * overlayAlpha + base.r * (1 - overlayAlpha));
|
|
404
|
+
const g = Math.round(overlay.g * overlayAlpha + base.g * (1 - overlayAlpha));
|
|
405
|
+
const b = Math.round(overlay.b * overlayAlpha + base.b * (1 - overlayAlpha));
|
|
406
|
+
|
|
407
|
+
// Convert to hex and ensure 2-digit format
|
|
408
|
+
const toHex = (n: number) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
|
|
409
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Generate an active/pressed state color (darker than hover)
|
|
414
|
+
* Uses 20% black overlay for a more pronounced pressed effect
|
|
415
|
+
*
|
|
416
|
+
* @param baseColor - Hex color to overlay on
|
|
417
|
+
* @returns Hex color for active/pressed state
|
|
418
|
+
*/
|
|
419
|
+
private generateActiveColor(baseColor: string): string {
|
|
420
|
+
return this.generateHoverColor(baseColor, '#000000', 0.2); // 20% darker
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Apply colors to CSS custom properties and native StatusBar
|
|
425
|
+
* This updates the actual CSS variables used throughout the app
|
|
426
|
+
* and the native status bar color on mobile devices
|
|
427
|
+
*/
|
|
428
|
+
private applyColors(config: WhitelabelConfig) {
|
|
429
|
+
if (typeof document !== 'undefined') {
|
|
430
|
+
const { appIconSurface, appIconContent, accent, onAccent, headerSurface, headerContent, headerAccent, onHeaderAccent, signInContentColor } = config;
|
|
431
|
+
const root = document.documentElement;
|
|
432
|
+
const body = document.body;
|
|
433
|
+
const ionApp = document.querySelector('ion-app');
|
|
434
|
+
|
|
435
|
+
// Generate hover and active state colors for accent
|
|
436
|
+
const accentHover = this.generateHoverColor(accent);
|
|
437
|
+
const accentActive = this.generateActiveColor(accent);
|
|
438
|
+
const headerAccentHover = this.generateHoverColor(headerAccent);
|
|
439
|
+
const headerAccentActive = this.generateActiveColor(headerAccent);
|
|
440
|
+
|
|
441
|
+
// Generate RGB values for Ionic color system
|
|
442
|
+
const accentRgb = this.hexToRgb(accent);
|
|
443
|
+
const accentRgbString = accentRgb ? `${accentRgb.r}, ${accentRgb.g}, ${accentRgb.b}` : '107, 95, 245';
|
|
444
|
+
const onAccentRgb = this.hexToRgb(onAccent);
|
|
445
|
+
const onAccentRgbString = onAccentRgb ? `${onAccentRgb.r}, ${onAccentRgb.g}, ${onAccentRgb.b}` : '255, 255, 255';
|
|
446
|
+
|
|
447
|
+
// ============================================
|
|
448
|
+
// APP ICON COLORS
|
|
449
|
+
// ============================================
|
|
450
|
+
root.style.setProperty('--color-app-icon-surface', appIconSurface);
|
|
451
|
+
root.style.setProperty('--color-app-icon-content', appIconContent);
|
|
452
|
+
body.style.setProperty('--color-app-icon-surface', appIconSurface);
|
|
453
|
+
body.style.setProperty('--color-app-icon-content', appIconContent);
|
|
454
|
+
|
|
455
|
+
// ============================================
|
|
456
|
+
// ACCENT COLORS (buttons, FABs, active tabs, selections)
|
|
457
|
+
// ============================================
|
|
458
|
+
|
|
459
|
+
// Base color
|
|
460
|
+
root.style.setProperty('--color-accent', accent);
|
|
461
|
+
root.style.setProperty('--color-on-accent', onAccent);
|
|
462
|
+
body.style.setProperty('--color-accent', accent);
|
|
463
|
+
body.style.setProperty('--color-on-accent', onAccent);
|
|
464
|
+
|
|
465
|
+
// Hover state (10% darker)
|
|
466
|
+
root.style.setProperty('--color-accent-hover', accentHover);
|
|
467
|
+
body.style.setProperty('--color-accent-hover', accentHover);
|
|
468
|
+
|
|
469
|
+
// Active/pressed state (20% darker)
|
|
470
|
+
root.style.setProperty('--color-accent-active', accentActive);
|
|
471
|
+
body.style.setProperty('--color-accent-active', accentActive);
|
|
472
|
+
|
|
473
|
+
// Legacy aliases for backward compatibility
|
|
474
|
+
root.style.setProperty('--color-background-brand', accent);
|
|
475
|
+
root.style.setProperty('--color-brand-base', accent);
|
|
476
|
+
root.style.setProperty('--color-primary-surface', accent);
|
|
477
|
+
root.style.setProperty('--color-primary-content', onAccent);
|
|
478
|
+
root.style.setProperty('--color-brand-base-hover', accentHover);
|
|
479
|
+
root.style.setProperty('--color-primary-surface-hover', accentHover);
|
|
480
|
+
root.style.setProperty('--color-brand-base-active', accentActive);
|
|
481
|
+
root.style.setProperty('--color-primary-surface-active', accentActive);
|
|
482
|
+
|
|
483
|
+
body.style.setProperty('--color-background-brand', accent);
|
|
484
|
+
body.style.setProperty('--color-brand-base', accent);
|
|
485
|
+
body.style.setProperty('--color-primary-surface', accent);
|
|
486
|
+
body.style.setProperty('--color-primary-content', onAccent);
|
|
487
|
+
body.style.setProperty('--color-brand-base-hover', accentHover);
|
|
488
|
+
body.style.setProperty('--color-primary-surface-hover', accentHover);
|
|
489
|
+
body.style.setProperty('--color-brand-base-active', accentActive);
|
|
490
|
+
body.style.setProperty('--color-primary-surface-active', accentActive);
|
|
491
|
+
|
|
492
|
+
if (ionApp) {
|
|
493
|
+
(ionApp as HTMLElement).style.setProperty('--color-accent', accent);
|
|
494
|
+
(ionApp as HTMLElement).style.setProperty('--color-on-accent', onAccent);
|
|
495
|
+
(ionApp as HTMLElement).style.setProperty('--color-accent-hover', accentHover);
|
|
496
|
+
(ionApp as HTMLElement).style.setProperty('--color-accent-active', accentActive);
|
|
497
|
+
(ionApp as HTMLElement).style.setProperty('--color-background-brand', accent);
|
|
498
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-base', accent);
|
|
499
|
+
(ionApp as HTMLElement).style.setProperty('--color-primary-surface', accent);
|
|
500
|
+
(ionApp as HTMLElement).style.setProperty('--color-primary-content', onAccent);
|
|
501
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-base-hover', accentHover);
|
|
502
|
+
(ionApp as HTMLElement).style.setProperty('--color-primary-surface-hover', accentHover);
|
|
503
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-base-active', accentActive);
|
|
504
|
+
(ionApp as HTMLElement).style.setProperty('--color-primary-surface-active', accentActive);
|
|
505
|
+
(ionApp as HTMLElement).style.setProperty('--color-selected', accent);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Update tab button selected color directly
|
|
509
|
+
// CSS variable inheritance doesn't always work dynamically with Ionic components
|
|
510
|
+
document.querySelectorAll('ion-tab-button').forEach((tabButton) => {
|
|
511
|
+
(tabButton as HTMLElement).style.setProperty('--color-selected', accent);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// ============================================
|
|
515
|
+
// IONIC COLOR SYSTEM
|
|
516
|
+
// ============================================
|
|
517
|
+
// Update Ionic's primary color system with RGB values
|
|
518
|
+
root.style.setProperty('--ion-color-primary', accent);
|
|
519
|
+
root.style.setProperty('--ion-color-primary-rgb', accentRgbString);
|
|
520
|
+
root.style.setProperty('--ion-color-primary-contrast', onAccent);
|
|
521
|
+
root.style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
|
|
522
|
+
root.style.setProperty('--ion-color-primary-shade', accentHover);
|
|
523
|
+
root.style.setProperty('--ion-color-primary-tint', accent);
|
|
524
|
+
|
|
525
|
+
body.style.setProperty('--ion-color-primary', accent);
|
|
526
|
+
body.style.setProperty('--ion-color-primary-rgb', accentRgbString);
|
|
527
|
+
body.style.setProperty('--ion-color-primary-contrast', onAccent);
|
|
528
|
+
body.style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
|
|
529
|
+
body.style.setProperty('--ion-color-primary-shade', accentHover);
|
|
530
|
+
body.style.setProperty('--ion-color-primary-tint', accent);
|
|
531
|
+
|
|
532
|
+
if (ionApp) {
|
|
533
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary', accent);
|
|
534
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary-rgb', accentRgbString);
|
|
535
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary-contrast', onAccent);
|
|
536
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary-contrast-rgb', onAccentRgbString);
|
|
537
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary-shade', accentHover);
|
|
538
|
+
(ionApp as HTMLElement).style.setProperty('--ion-color-primary-tint', accent);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ============================================
|
|
542
|
+
// HEADER COLORS (navigation bar, header-expandable)
|
|
543
|
+
// ============================================
|
|
544
|
+
|
|
545
|
+
// Base colors
|
|
546
|
+
root.style.setProperty('--color-header-surface', headerSurface);
|
|
547
|
+
root.style.setProperty('--color-header-content', headerContent);
|
|
548
|
+
root.style.setProperty('--color-header-accent', headerAccent);
|
|
549
|
+
root.style.setProperty('--color-on-header-accent', onHeaderAccent);
|
|
550
|
+
body.style.setProperty('--color-header-surface', headerSurface);
|
|
551
|
+
body.style.setProperty('--color-header-content', headerContent);
|
|
552
|
+
body.style.setProperty('--color-header-accent', headerAccent);
|
|
553
|
+
body.style.setProperty('--color-on-header-accent', onHeaderAccent);
|
|
554
|
+
|
|
555
|
+
// Hover/active states for header accent
|
|
556
|
+
root.style.setProperty('--color-header-accent-hover', headerAccentHover);
|
|
557
|
+
root.style.setProperty('--color-header-accent-active', headerAccentActive);
|
|
558
|
+
body.style.setProperty('--color-header-accent-hover', headerAccentHover);
|
|
559
|
+
body.style.setProperty('--color-header-accent-active', headerAccentActive);
|
|
560
|
+
|
|
561
|
+
// Legacy aliases for backward compatibility
|
|
562
|
+
root.style.setProperty('--color-brand-secondary', headerSurface);
|
|
563
|
+
root.style.setProperty('--color-secondary-surface', headerSurface);
|
|
564
|
+
root.style.setProperty('--color-secondary-content', headerContent);
|
|
565
|
+
root.style.setProperty('--header-content-color', headerContent);
|
|
566
|
+
root.style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
|
|
567
|
+
root.style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
|
|
568
|
+
root.style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
|
|
569
|
+
root.style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
|
|
570
|
+
|
|
571
|
+
body.style.setProperty('--color-brand-secondary', headerSurface);
|
|
572
|
+
body.style.setProperty('--color-secondary-surface', headerSurface);
|
|
573
|
+
body.style.setProperty('--color-secondary-content', headerContent);
|
|
574
|
+
body.style.setProperty('--header-content-color', headerContent);
|
|
575
|
+
body.style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
|
|
576
|
+
body.style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
|
|
577
|
+
body.style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
|
|
578
|
+
body.style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
|
|
579
|
+
|
|
580
|
+
if (ionApp) {
|
|
581
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-surface', headerSurface);
|
|
582
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-content', headerContent);
|
|
583
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-accent', headerAccent);
|
|
584
|
+
(ionApp as HTMLElement).style.setProperty('--color-on-header-accent', onHeaderAccent);
|
|
585
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-accent-hover', headerAccentHover);
|
|
586
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-accent-active', headerAccentActive);
|
|
587
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-secondary', headerSurface);
|
|
588
|
+
(ionApp as HTMLElement).style.setProperty('--color-secondary-surface', headerSurface);
|
|
589
|
+
(ionApp as HTMLElement).style.setProperty('--color-secondary-content', headerContent);
|
|
590
|
+
(ionApp as HTMLElement).style.setProperty('--header-content-color', headerContent);
|
|
591
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-secondary-hover', this.generateHoverColor(headerSurface));
|
|
592
|
+
(ionApp as HTMLElement).style.setProperty('--color-secondary-surface-hover', this.generateHoverColor(headerSurface));
|
|
593
|
+
(ionApp as HTMLElement).style.setProperty('--color-brand-secondary-active', this.generateActiveColor(headerSurface));
|
|
594
|
+
(ionApp as HTMLElement).style.setProperty('--color-secondary-surface-active', this.generateActiveColor(headerSurface));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Also set RGB values for use with rgba() opacity variations
|
|
598
|
+
const rgbContent = this.hexToRgb(headerContent);
|
|
599
|
+
if (rgbContent) {
|
|
600
|
+
const rgbValue = `${rgbContent.r}, ${rgbContent.g}, ${rgbContent.b}`;
|
|
601
|
+
root.style.setProperty('--header-content-color-rgb', rgbValue);
|
|
602
|
+
root.style.setProperty('--color-secondary-content-rgb', rgbValue);
|
|
603
|
+
root.style.setProperty('--color-header-content-rgb', rgbValue);
|
|
604
|
+
body.style.setProperty('--header-content-color-rgb', rgbValue);
|
|
605
|
+
body.style.setProperty('--color-secondary-content-rgb', rgbValue);
|
|
606
|
+
body.style.setProperty('--color-header-content-rgb', rgbValue);
|
|
607
|
+
if (ionApp) {
|
|
608
|
+
(ionApp as HTMLElement).style.setProperty('--header-content-color-rgb', rgbValue);
|
|
609
|
+
(ionApp as HTMLElement).style.setProperty('--color-secondary-content-rgb', rgbValue);
|
|
610
|
+
(ionApp as HTMLElement).style.setProperty('--color-header-content-rgb', rgbValue);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const rgbOnHeaderAccent = this.hexToRgb(onHeaderAccent);
|
|
615
|
+
if (rgbOnHeaderAccent) {
|
|
616
|
+
const rgbValue = `${rgbOnHeaderAccent.r}, ${rgbOnHeaderAccent.g}, ${rgbOnHeaderAccent.b}`;
|
|
617
|
+
root.style.setProperty('--color-on-header-accent-rgb', rgbValue);
|
|
618
|
+
body.style.setProperty('--color-on-header-accent-rgb', rgbValue);
|
|
619
|
+
if (ionApp) {
|
|
620
|
+
(ionApp as HTMLElement).style.setProperty('--color-on-header-accent-rgb', rgbValue);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Update theme-color meta tag for browser chrome/status bar (PWA/iOS)
|
|
625
|
+
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
|
|
626
|
+
if (metaThemeColor) {
|
|
627
|
+
metaThemeColor.setAttribute('content', headerSurface);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ============================================
|
|
631
|
+
// SIGN-IN PAGE COLORS
|
|
632
|
+
// ============================================
|
|
633
|
+
root.style.setProperty('--color-signin-content', signInContentColor);
|
|
634
|
+
body.style.setProperty('--color-signin-content', signInContentColor);
|
|
635
|
+
if (ionApp) {
|
|
636
|
+
(ionApp as HTMLElement).style.setProperty('--color-signin-content', signInContentColor);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Update native StatusBar color (Capacitor - Android only, iOS ignores this)
|
|
640
|
+
this.updateNativeStatusBar(headerSurface);
|
|
641
|
+
|
|
642
|
+
// console.log('Applied whitelabel colors:', {
|
|
643
|
+
// appIconSurface,
|
|
644
|
+
// appIconContent,
|
|
645
|
+
// accent,
|
|
646
|
+
// accentHover,
|
|
647
|
+
// accentActive,
|
|
648
|
+
// onAccent,
|
|
649
|
+
// headerSurface,
|
|
650
|
+
// headerContent,
|
|
651
|
+
// headerAccent,
|
|
652
|
+
// headerAccentHover,
|
|
653
|
+
// headerAccentActive,
|
|
654
|
+
// onHeaderAccent,
|
|
655
|
+
// signInContentColor
|
|
656
|
+
// });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Update the native status bar color AND style
|
|
662
|
+
* Sets background color (Android) and content style (iOS/Android)
|
|
663
|
+
*/
|
|
664
|
+
private async updateNativeStatusBar(color: string): Promise<void> {
|
|
665
|
+
try {
|
|
666
|
+
await StatusBar.setBackgroundColor({ color });
|
|
667
|
+
|
|
668
|
+
// Calculate and set appropriate style for status bar content
|
|
669
|
+
const style = this.getStatusBarStyleForColor(color);
|
|
670
|
+
await StatusBar.setStyle({ style });
|
|
671
|
+
} catch (e) {
|
|
672
|
+
// StatusBar API not available (web browser) or failed
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|