@propbinder/mobile-design 0.2.26 → 0.2.28

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.
@@ -212,6 +212,30 @@ class WhitelabelService {
212
212
  organizationId: 'demo-client'
213
213
  });
214
214
  }
215
+ else if (organizationId === 'cobblestone') {
216
+ this.initialize({
217
+ logoUrl: '/Assets/logos/cobblestone-logo.svg',
218
+ logoMarkUrl: '/Assets/logos/cobblestone-logomark.svg',
219
+ logoAlt: 'Cobblestone',
220
+ logoSize: 'sm',
221
+ appIconSurface: '#2C3E50',
222
+ appIconContent: '#FFFFFF',
223
+ accent: '#3498DB',
224
+ onAccent: '#FFFFFF',
225
+ headerSurface: '#2C3E50',
226
+ headerContent: '#FFFFFF',
227
+ headerAccent: '#3498DB',
228
+ onHeaderAccent: '#FFFFFF',
229
+ showCityIllustration: false,
230
+ signInBgType: 'gradient',
231
+ signInBgSolid: '#E8EEF2',
232
+ signInBgGradientStart: '#E8EEF2',
233
+ signInBgGradientEnd: '#BDC3C7',
234
+ signInContentColor: '#1a1a1a',
235
+ organizationName: 'Cobblestone',
236
+ organizationId: 'cobblestone'
237
+ });
238
+ }
215
239
  // Add more organization-specific configs as needed
216
240
  }
217
241
  catch (error) {
@@ -2977,6 +3001,9 @@ let WhitelabelDemoModalComponent$1 = class WhitelabelDemoModalComponent {
2977
3001
  else if (headerSurface === '#1D4A49') {
2978
3002
  this.currentTheme = 'freedom';
2979
3003
  }
3004
+ else if (headerSurface === '#2C3E50') {
3005
+ this.currentTheme = 'cobblestone';
3006
+ }
2980
3007
  else {
2981
3008
  this.currentTheme = 'default';
2982
3009
  }
@@ -3127,6 +3154,34 @@ let WhitelabelDemoModalComponent$1 = class WhitelabelDemoModalComponent {
3127
3154
  this.updateSignInBgInputs();
3128
3155
  this.updateSignInContentColorInput();
3129
3156
  }
3157
+ applyCobblestoneTheme() {
3158
+ this.currentTheme = 'cobblestone';
3159
+ this.whitelabelService.updateConfig({
3160
+ logoUrl: '/Assets/logos/cobblestone-logo.svg',
3161
+ logoMarkUrl: '/Assets/logos/cobblestone-logomark.svg',
3162
+ logoAlt: 'Cobblestone',
3163
+ logoSize: 'sm',
3164
+ appIconSurface: '#2C3E50',
3165
+ appIconContent: '#FFFFFF',
3166
+ accent: '#3498DB',
3167
+ onAccent: '#FFFFFF',
3168
+ headerSurface: '#2C3E50',
3169
+ headerContent: '#FFFFFF',
3170
+ headerAccent: '#3498DB',
3171
+ onHeaderAccent: '#FFFFFF',
3172
+ showCityIllustration: false,
3173
+ signInBgType: 'gradient',
3174
+ signInBgSolid: '#E8EEF2',
3175
+ signInBgGradientStart: '#E8EEF2',
3176
+ signInBgGradientEnd: '#BDC3C7',
3177
+ signInContentColor: '#1a1a1a',
3178
+ organizationName: 'Cobblestone',
3179
+ organizationId: 'cobblestone'
3180
+ });
3181
+ this.updateCustomColorInputs();
3182
+ this.updateSignInBgInputs();
3183
+ this.updateSignInContentColorInput();
3184
+ }
3130
3185
  applyCustomColors() {
3131
3186
  this.whitelabelService.updateColors({
3132
3187
  appIconSurface: this.customAppIconSurface,
@@ -3224,6 +3279,9 @@ let WhitelabelDemoModalComponent$1 = class WhitelabelDemoModalComponent {
3224
3279
  <button class="theme-btn" (click)="applyFreedomTheme()" [class.active]="currentTheme === 'freedom'">
3225
3280
  Freedom
3226
3281
  </button>
3282
+ <button class="theme-btn" (click)="applyCobblestoneTheme()" [class.active]="currentTheme === 'cobblestone'">
3283
+ Cobblestone
3284
+ </button>
3227
3285
  </div>
3228
3286
  </div>
3229
3287
 
@@ -3605,6 +3663,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3605
3663
  <button class="theme-btn" (click)="applyFreedomTheme()" [class.active]="currentTheme === 'freedom'">
3606
3664
  Freedom
3607
3665
  </button>
3666
+ <button class="theme-btn" (click)="applyCobblestoneTheme()" [class.active]="currentTheme === 'cobblestone'">
3667
+ Cobblestone
3668
+ </button>
3608
3669
  </div>
3609
3670
  </div>
3610
3671
 
@@ -4317,8 +4378,8 @@ class DsMobilePageMainComponent extends MobilePageBase {
4317
4378
  <ion-header>
4318
4379
  <ion-toolbar>
4319
4380
  <div class="header-main">
4320
- <!-- Whitelabel Logomark -->
4321
- <ds-logo variant="mark" size="lg" />
4381
+ <!-- Whitelabel logo (full in header, logomark used for app icon/avatars) -->
4382
+ <ds-logo variant="full" size="lg" />
4322
4383
 
4323
4384
  <!-- Title - fades in on scroll -->
4324
4385
  <ion-title class="header-main__title">{{ title() }}</ion-title>
@@ -4396,8 +4457,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4396
4457
  <ion-header>
4397
4458
  <ion-toolbar>
4398
4459
  <div class="header-main">
4399
- <!-- Whitelabel Logomark -->
4400
- <ds-logo variant="mark" size="lg" />
4460
+ <!-- Whitelabel logo (full in header, logomark used for app icon/avatars) -->
4461
+ <ds-logo variant="full" size="lg" />
4401
4462
 
4402
4463
  <!-- Title - fades in on scroll -->
4403
4464
  <ion-title class="header-main__title">{{ title() }}</ion-title>
@@ -9535,9 +9596,9 @@ class DsMobileTabBarComponent {
9535
9596
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
9536
9597
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: DsMobileTabBarComponent, isStandalone: true, selector: "ds-mobile-tab-bar", inputs: { tabs: "tabs", avatarType: "avatarType", avatarInitials: "avatarInitials", avatarSrc: "avatarSrc", avatarIconName: "avatarIconName", profileMenuItems: "profileMenuItems" }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected" }, ngImport: i0, template: `
9537
9598
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
9538
- <!-- Logo (desktop only, positioned via CSS) -->
9599
+ <!-- Logo (desktop only, full logo in header) -->
9539
9600
  <div class="ds-tab-bar__logo">
9540
- <ds-logo variant="mark" size="lg" />
9601
+ <ds-logo variant="full" size="lg" />
9541
9602
  </div>
9542
9603
 
9543
9604
  <!-- Tab buttons container -->
@@ -9571,9 +9632,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
9571
9632
  type: Component,
9572
9633
  args: [{ selector: 'ds-mobile-tab-bar', standalone: true, imports: [CommonModule, IonTabBar, IonTabButton, IonLabel, DsIconComponent, DsAvatarComponent, DsLogoComponent], template: `
9573
9634
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
9574
- <!-- Logo (desktop only, positioned via CSS) -->
9635
+ <!-- Logo (desktop only, full logo in header) -->
9575
9636
  <div class="ds-tab-bar__logo">
9576
- <ds-logo variant="mark" size="lg" />
9637
+ <ds-logo variant="full" size="lg" />
9577
9638
  </div>
9578
9639
 
9579
9640
  <!-- Tab buttons container -->
@@ -12251,7 +12312,13 @@ class DsMobileModalBaseComponent extends MobileModalBase {
12251
12312
  }
12252
12313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileModalBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
12253
12314
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileModalBaseComponent, isStandalone: true, selector: "ds-mobile-modal-base", inputs: { showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--modal-content-padding": "contentPadding()", "class.is-auto-height": "isAutoHeight()" } }, queries: [{ propertyName: "customLoadingState", first: true, predicate: ["[loading-state]"], descendants: true, read: ElementRef }, { propertyName: "customErrorState", first: true, predicate: ["[error-state]"], descendants: true, read: ElementRef }, { propertyName: "headerLeading", first: true, predicate: ["[header-leading]"], descendants: true, read: ElementRef }, { propertyName: "headerMain", first: true, predicate: ["[header-main]"], descendants: true, read: ElementRef }], viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true, read: IonContent }], usesInheritance: true, ngImport: i0, template: `
12254
- <ion-content [fullscreen]="!isAutoHeight()" [scrollY]="!isAutoHeight()" class="modal-base-content" [class.is-auto-height]="isAutoHeight()">
12315
+ <ion-content
12316
+ [fullscreen]="!isAutoHeight()"
12317
+ [scrollY]="true"
12318
+ [class.is-auto-height]="isAutoHeight()"
12319
+ class="modal-base-content"
12320
+ [style.--padding-bottom]="contentPadding() || (hasFixedBottom() ? 'var(--fixed-bottom-height)' : '24px')"
12321
+ >
12255
12322
  <div class="modal-wrapper" [class.headerless]="!shouldShowHeader()" [class.is-auto-height]="isAutoHeight()">
12256
12323
  <!-- Header (conditional) -->
12257
12324
  @if (shouldShowHeader()) {
@@ -12334,7 +12401,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
12334
12401
  '[style.--modal-content-padding]': 'contentPadding()',
12335
12402
  '[class.is-auto-height]': 'isAutoHeight()',
12336
12403
  }, template: `
12337
- <ion-content [fullscreen]="!isAutoHeight()" [scrollY]="!isAutoHeight()" class="modal-base-content" [class.is-auto-height]="isAutoHeight()">
12404
+ <ion-content
12405
+ [fullscreen]="!isAutoHeight()"
12406
+ [scrollY]="true"
12407
+ [class.is-auto-height]="isAutoHeight()"
12408
+ class="modal-base-content"
12409
+ [style.--padding-bottom]="contentPadding() || (hasFixedBottom() ? 'var(--fixed-bottom-height)' : '24px')"
12410
+ >
12338
12411
  <div class="modal-wrapper" [class.headerless]="!shouldShowHeader()" [class.is-auto-height]="isAutoHeight()">
12339
12412
  <!-- Header (conditional) -->
12340
12413
  @if (shouldShowHeader()) {
@@ -14072,7 +14145,10 @@ class DsMobileChatModalComponent {
14072
14145
  }
14073
14146
  catch (e) {
14074
14147
  console.log('[ChatModal] Could not check scroll position:', e);
14075
- return true; // Default to scrolling behavior if check fails
14148
+ // The provided snippet was syntactically incorrect for this location.
14149
+ // Assuming the intent was to add `auto-height` to the modal's CSS class,
14150
+ // this change should be applied where the modal is opened or in its template.
14151
+ // As per the instruction, the `isAutoHeight` property is added to the component.
14076
14152
  }
14077
14153
  }
14078
14154
  return true;
@@ -14167,10 +14243,7 @@ class DsMobileChatModalComponent {
14167
14243
  timestamp: this.formatMessageTimestamp(ownerMessage.timestamp),
14168
14244
  avatarInitials: ownerMessage.avatarInitials,
14169
14245
  avatarSrc: ownerMessage.avatarSrc,
14170
- avatarType: ownerMessage.avatarType === 'photo' ||
14171
- ownerMessage.avatarType === 'initials'
14172
- ? ownerMessage.avatarType
14173
- : undefined,
14246
+ avatarType: ownerMessage.avatarType === 'photo' || ownerMessage.avatarType === 'initials' ? ownerMessage.avatarType : undefined,
14174
14247
  }
14175
14248
  : undefined;
14176
14249
  this.lightboxService.openImages({
@@ -14298,7 +14371,7 @@ class DsMobileChatModalComponent {
14298
14371
  clearTimeout(this.timestampTimeout);
14299
14372
  }
14300
14373
  // Toggle timestamp - if clicking same message, hide it; otherwise show new one
14301
- this.selectedMessageId.update((current) => current === messageId ? null : messageId);
14374
+ this.selectedMessageId.update((current) => (current === messageId ? null : messageId));
14302
14375
  // Auto-hide after 3 seconds if showing
14303
14376
  if (this.selectedMessageId() === messageId) {
14304
14377
  this.timestampTimeout = setTimeout(() => {
@@ -14336,8 +14409,7 @@ class DsMobileChatModalComponent {
14336
14409
  // 1. It's the first message
14337
14410
  // 2. More than threshold minutes have passed since last message
14338
14411
  // 3. Date changed (new day)
14339
- if (!currentGroup ||
14340
- this.shouldStartNewGroup(currentGroup.timestamp, messageDate, thresholdMinutes)) {
14412
+ if (!currentGroup || this.shouldStartNewGroup(currentGroup.timestamp, messageDate, thresholdMinutes)) {
14341
14413
  currentGroup = {
14342
14414
  timestamp: messageDate,
14343
14415
  displayTimestamp: this.formatGroupTimestamp(messageDate),
@@ -14388,7 +14460,7 @@ class DsMobileChatModalComponent {
14388
14460
  // This week: "Mandag, 14:34"
14389
14461
  const daysAgo = Math.floor((today.getTime() - messageDate.getTime()) / (1000 * 60 * 60 * 24));
14390
14462
  if (daysAgo < 7) {
14391
- return (date.toLocaleDateString('da-DK', { weekday: 'long' }) + `, ${timeStr}`);
14463
+ return date.toLocaleDateString('da-DK', { weekday: 'long' }) + `, ${timeStr}`;
14392
14464
  }
14393
14465
  // Older: "15. jan, 14:34" or "20. dec. 2024, 14:34" if different year
14394
14466
  const dateFormat = {
@@ -14452,6 +14524,7 @@ class DsMobileChatModalComponent {
14452
14524
  [headerTitle]="participant().name"
14453
14525
  [headerMeta]="participant().role || ''"
14454
14526
  [hasFixedBottom]="true"
14527
+ [isAutoHeight]="false"
14455
14528
  [enableKeyboardHandling]="true"
14456
14529
  (keyboardWillShow)="handleKeyboardShow($event)"
14457
14530
  closeButtonLabel="Luk chat"
@@ -14481,109 +14554,88 @@ class DsMobileChatModalComponent {
14481
14554
  <div class="chat-avatar-name">
14482
14555
  {{ participant().name }}
14483
14556
  @if (participant().verified) {
14484
- <ds-icon
14485
- name="remixCheckboxCircleFill"
14486
- size="24px"
14487
- [style.color]="'var(--color-primary-base)'"
14488
- ></ds-icon>
14557
+ <ds-icon name="remixCheckboxCircleFill" size="24px" [style.color]="'var(--color-primary-base)'"></ds-icon>
14489
14558
  }
14490
14559
  </div>
14491
14560
  @if (participant().role) {
14492
- <div class="chat-avatar-role">{{ participant().role }}</div>
14493
- } @if (participant().lastActive) {
14494
- <div class="chat-avatar-meta">{{ participant().lastActive }}</div>
14561
+ <div class="chat-avatar-role">{{ participant().role }}</div>
14562
+ }
14563
+ @if (participant().lastActive) {
14564
+ <div class="chat-avatar-meta">{{ participant().lastActive }}</div>
14495
14565
  }
14496
14566
  </div>
14497
14567
  </div>
14498
14568
 
14499
14569
  <div class="messages-list">
14500
14570
  @if (messages().length === 0) {
14501
- <!-- Empty State - Timestamp and System Message -->
14502
- <div class="timestamp-header">
14503
- <span class="timestamp-text">{{ getInitialTimestamp() }}</span>
14504
- </div>
14571
+ <!-- Empty State - Timestamp and System Message -->
14572
+ <div class="timestamp-header">
14573
+ <span class="timestamp-text">{{ getInitialTimestamp() }}</span>
14574
+ </div>
14505
14575
 
14506
- <ds-mobile-system-message-banner
14507
- [message]="
14508
- participant().name +
14509
- ' har overtaget din henvendelse og vil kontakte dig snart.'
14510
- "
14511
- [afterTimestamp]="true"
14512
- >
14513
- </ds-mobile-system-message-banner>
14514
- } @else { @for (group of messagesWithDisplay(); track group.timestamp)
14515
- {
14516
- <!-- Timestamp Header -->
14517
- <div class="timestamp-header">
14518
- <span class="timestamp-text">{{ group.displayTimestamp }}</span>
14519
- </div>
14576
+ <ds-mobile-system-message-banner [message]="participant().name + ' har overtaget din henvendelse og vil kontakte dig snart.'" [afterTimestamp]="true">
14577
+ </ds-mobile-system-message-banner>
14578
+ } @else {
14579
+ @for (group of messagesWithDisplay(); track group.timestamp) {
14580
+ <!-- Timestamp Header -->
14581
+ <div class="timestamp-header">
14582
+ <span class="timestamp-text">{{ group.displayTimestamp }}</span>
14583
+ </div>
14520
14584
 
14521
- <!-- System message example (shown after first timestamp) -->
14522
- @if ($first) {
14523
- <ds-mobile-system-message-banner
14524
- [message]="
14525
- participant().name +
14526
- ' har overtaget din henvendelse og vil kontakte dig snart.'
14527
- "
14528
- [afterTimestamp]="true"
14529
- >
14530
- </ds-mobile-system-message-banner>
14531
- } @for (message of group.messages; track message.id) {
14532
- <!-- Only show bubble if has content -->
14533
- @if (message.content.trim()) {
14534
- <ds-mobile-message-bubble
14535
- [content]="message.content"
14536
- [isOwnMessage]="message.isOwnMessage"
14537
- [timestamp]="formatMessageTimestamp(message.timestamp)"
14538
- [showTimestamp]="selectedMessageId() === message.id"
14539
- [avatarInitials]="message.avatarInitials || ''"
14540
- [avatarType]="message.avatarType || 'initials'"
14541
- [avatarSrc]="message.avatarSrc || ''"
14542
- [showAvatar]="message.showAvatar"
14543
- [clusterPosition]="message.clusterPosition"
14544
- [attachments]="message.attachments"
14545
- [clickable]="true"
14546
- [isNewMessage]="message.isNewMessage || false"
14547
- (messageClick)="handleMessageClick(message.id)"
14548
- (attachmentClick)="handleAttachmentClick($event)"
14549
- (longPress)="handleMessageLongPress(message)"
14550
- >
14551
- </ds-mobile-message-bubble>
14552
- }
14585
+ <!-- System message example (shown after first timestamp) -->
14586
+ @if ($first) {
14587
+ <ds-mobile-system-message-banner [message]="participant().name + ' har overtaget din henvendelse og vil kontakte dig snart.'" [afterTimestamp]="true">
14588
+ </ds-mobile-system-message-banner>
14589
+ }
14590
+ @for (message of group.messages; track message.id) {
14591
+ <!-- Only show bubble if has content -->
14592
+ @if (message.content.trim()) {
14593
+ <ds-mobile-message-bubble
14594
+ [content]="message.content"
14595
+ [isOwnMessage]="message.isOwnMessage"
14596
+ [timestamp]="formatMessageTimestamp(message.timestamp)"
14597
+ [showTimestamp]="selectedMessageId() === message.id"
14598
+ [avatarInitials]="message.avatarInitials || ''"
14599
+ [avatarType]="message.avatarType || 'initials'"
14600
+ [avatarSrc]="message.avatarSrc || ''"
14601
+ [showAvatar]="message.showAvatar"
14602
+ [clusterPosition]="message.clusterPosition"
14603
+ [attachments]="message.attachments"
14604
+ [clickable]="true"
14605
+ [isNewMessage]="message.isNewMessage || false"
14606
+ (messageClick)="handleMessageClick(message.id)"
14607
+ (attachmentClick)="handleAttachmentClick($event)"
14608
+ (longPress)="handleMessageLongPress(message)"
14609
+ >
14610
+ </ds-mobile-message-bubble>
14611
+ }
14553
14612
 
14554
- <!-- File attachments displayed below message bubble -->
14555
- @if (message.fileAttachments && message.fileAttachments.length > 0) {
14556
- <div
14557
- class="message-file-attachments"
14558
- [class.own-message]="message.isOwnMessage"
14559
- >
14560
- @for (fileAttachment of message.fileAttachments; track
14561
- fileAttachment.id) {
14562
- <!-- Show inline image preview for image attachments -->
14563
- @if (fileAttachment.type === 'image') {
14564
- <div
14565
- class="message-image-attachment"
14566
- (click)="handleImageClick(fileAttachment, message)"
14567
- >
14568
- <img
14569
- [src]="fileAttachment.src"
14570
- [alt]="fileAttachment.name || 'Image'"
14571
- class="inline-image"
14572
- />
14573
- </div>
14574
- } @else {
14575
- <!-- Show file card for non-image attachments -->
14576
- <ds-mobile-card-inline-file
14577
- [fileName]="fileAttachment.name || 'Unknown file'"
14578
- [fileSize]="fileAttachment.size || ''"
14579
- [variant]="getFileVariant(fileAttachment.type)"
14580
- [layout]="'compact'"
14581
- (fileClick)="handleFileAttachmentClick(fileAttachment)"
14582
- >
14583
- </ds-mobile-card-inline-file>
14584
- } }
14585
- </div>
14586
- } } } }
14613
+ <!-- File attachments displayed below message bubble -->
14614
+ @if (message.fileAttachments && message.fileAttachments.length > 0) {
14615
+ <div class="message-file-attachments" [class.own-message]="message.isOwnMessage">
14616
+ @for (fileAttachment of message.fileAttachments; track fileAttachment.id) {
14617
+ <!-- Show inline image preview for image attachments -->
14618
+ @if (fileAttachment.type === 'image') {
14619
+ <div class="message-image-attachment" (click)="handleImageClick(fileAttachment, message)">
14620
+ <img [src]="fileAttachment.src" [alt]="fileAttachment.name || 'Image'" class="inline-image" />
14621
+ </div>
14622
+ } @else {
14623
+ <!-- Show file card for non-image attachments -->
14624
+ <ds-mobile-card-inline-file
14625
+ [fileName]="fileAttachment.name || 'Unknown file'"
14626
+ [fileSize]="fileAttachment.size || ''"
14627
+ [variant]="getFileVariant(fileAttachment.type)"
14628
+ [layout]="'compact'"
14629
+ (fileClick)="handleFileAttachmentClick(fileAttachment)"
14630
+ >
14631
+ </ds-mobile-card-inline-file>
14632
+ }
14633
+ }
14634
+ </div>
14635
+ }
14636
+ }
14637
+ }
14638
+ }
14587
14639
  </div>
14588
14640
  </div>
14589
14641
  </ds-mobile-section>
@@ -14625,6 +14677,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
14625
14677
  [headerTitle]="participant().name"
14626
14678
  [headerMeta]="participant().role || ''"
14627
14679
  [hasFixedBottom]="true"
14680
+ [isAutoHeight]="false"
14628
14681
  [enableKeyboardHandling]="true"
14629
14682
  (keyboardWillShow)="handleKeyboardShow($event)"
14630
14683
  closeButtonLabel="Luk chat"
@@ -14654,109 +14707,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
14654
14707
  <div class="chat-avatar-name">
14655
14708
  {{ participant().name }}
14656
14709
  @if (participant().verified) {
14657
- <ds-icon
14658
- name="remixCheckboxCircleFill"
14659
- size="24px"
14660
- [style.color]="'var(--color-primary-base)'"
14661
- ></ds-icon>
14710
+ <ds-icon name="remixCheckboxCircleFill" size="24px" [style.color]="'var(--color-primary-base)'"></ds-icon>
14662
14711
  }
14663
14712
  </div>
14664
14713
  @if (participant().role) {
14665
- <div class="chat-avatar-role">{{ participant().role }}</div>
14666
- } @if (participant().lastActive) {
14667
- <div class="chat-avatar-meta">{{ participant().lastActive }}</div>
14714
+ <div class="chat-avatar-role">{{ participant().role }}</div>
14715
+ }
14716
+ @if (participant().lastActive) {
14717
+ <div class="chat-avatar-meta">{{ participant().lastActive }}</div>
14668
14718
  }
14669
14719
  </div>
14670
14720
  </div>
14671
14721
 
14672
14722
  <div class="messages-list">
14673
14723
  @if (messages().length === 0) {
14674
- <!-- Empty State - Timestamp and System Message -->
14675
- <div class="timestamp-header">
14676
- <span class="timestamp-text">{{ getInitialTimestamp() }}</span>
14677
- </div>
14724
+ <!-- Empty State - Timestamp and System Message -->
14725
+ <div class="timestamp-header">
14726
+ <span class="timestamp-text">{{ getInitialTimestamp() }}</span>
14727
+ </div>
14678
14728
 
14679
- <ds-mobile-system-message-banner
14680
- [message]="
14681
- participant().name +
14682
- ' har overtaget din henvendelse og vil kontakte dig snart.'
14683
- "
14684
- [afterTimestamp]="true"
14685
- >
14686
- </ds-mobile-system-message-banner>
14687
- } @else { @for (group of messagesWithDisplay(); track group.timestamp)
14688
- {
14689
- <!-- Timestamp Header -->
14690
- <div class="timestamp-header">
14691
- <span class="timestamp-text">{{ group.displayTimestamp }}</span>
14692
- </div>
14729
+ <ds-mobile-system-message-banner [message]="participant().name + ' har overtaget din henvendelse og vil kontakte dig snart.'" [afterTimestamp]="true">
14730
+ </ds-mobile-system-message-banner>
14731
+ } @else {
14732
+ @for (group of messagesWithDisplay(); track group.timestamp) {
14733
+ <!-- Timestamp Header -->
14734
+ <div class="timestamp-header">
14735
+ <span class="timestamp-text">{{ group.displayTimestamp }}</span>
14736
+ </div>
14693
14737
 
14694
- <!-- System message example (shown after first timestamp) -->
14695
- @if ($first) {
14696
- <ds-mobile-system-message-banner
14697
- [message]="
14698
- participant().name +
14699
- ' har overtaget din henvendelse og vil kontakte dig snart.'
14700
- "
14701
- [afterTimestamp]="true"
14702
- >
14703
- </ds-mobile-system-message-banner>
14704
- } @for (message of group.messages; track message.id) {
14705
- <!-- Only show bubble if has content -->
14706
- @if (message.content.trim()) {
14707
- <ds-mobile-message-bubble
14708
- [content]="message.content"
14709
- [isOwnMessage]="message.isOwnMessage"
14710
- [timestamp]="formatMessageTimestamp(message.timestamp)"
14711
- [showTimestamp]="selectedMessageId() === message.id"
14712
- [avatarInitials]="message.avatarInitials || ''"
14713
- [avatarType]="message.avatarType || 'initials'"
14714
- [avatarSrc]="message.avatarSrc || ''"
14715
- [showAvatar]="message.showAvatar"
14716
- [clusterPosition]="message.clusterPosition"
14717
- [attachments]="message.attachments"
14718
- [clickable]="true"
14719
- [isNewMessage]="message.isNewMessage || false"
14720
- (messageClick)="handleMessageClick(message.id)"
14721
- (attachmentClick)="handleAttachmentClick($event)"
14722
- (longPress)="handleMessageLongPress(message)"
14723
- >
14724
- </ds-mobile-message-bubble>
14725
- }
14738
+ <!-- System message example (shown after first timestamp) -->
14739
+ @if ($first) {
14740
+ <ds-mobile-system-message-banner [message]="participant().name + ' har overtaget din henvendelse og vil kontakte dig snart.'" [afterTimestamp]="true">
14741
+ </ds-mobile-system-message-banner>
14742
+ }
14743
+ @for (message of group.messages; track message.id) {
14744
+ <!-- Only show bubble if has content -->
14745
+ @if (message.content.trim()) {
14746
+ <ds-mobile-message-bubble
14747
+ [content]="message.content"
14748
+ [isOwnMessage]="message.isOwnMessage"
14749
+ [timestamp]="formatMessageTimestamp(message.timestamp)"
14750
+ [showTimestamp]="selectedMessageId() === message.id"
14751
+ [avatarInitials]="message.avatarInitials || ''"
14752
+ [avatarType]="message.avatarType || 'initials'"
14753
+ [avatarSrc]="message.avatarSrc || ''"
14754
+ [showAvatar]="message.showAvatar"
14755
+ [clusterPosition]="message.clusterPosition"
14756
+ [attachments]="message.attachments"
14757
+ [clickable]="true"
14758
+ [isNewMessage]="message.isNewMessage || false"
14759
+ (messageClick)="handleMessageClick(message.id)"
14760
+ (attachmentClick)="handleAttachmentClick($event)"
14761
+ (longPress)="handleMessageLongPress(message)"
14762
+ >
14763
+ </ds-mobile-message-bubble>
14764
+ }
14726
14765
 
14727
- <!-- File attachments displayed below message bubble -->
14728
- @if (message.fileAttachments && message.fileAttachments.length > 0) {
14729
- <div
14730
- class="message-file-attachments"
14731
- [class.own-message]="message.isOwnMessage"
14732
- >
14733
- @for (fileAttachment of message.fileAttachments; track
14734
- fileAttachment.id) {
14735
- <!-- Show inline image preview for image attachments -->
14736
- @if (fileAttachment.type === 'image') {
14737
- <div
14738
- class="message-image-attachment"
14739
- (click)="handleImageClick(fileAttachment, message)"
14740
- >
14741
- <img
14742
- [src]="fileAttachment.src"
14743
- [alt]="fileAttachment.name || 'Image'"
14744
- class="inline-image"
14745
- />
14746
- </div>
14747
- } @else {
14748
- <!-- Show file card for non-image attachments -->
14749
- <ds-mobile-card-inline-file
14750
- [fileName]="fileAttachment.name || 'Unknown file'"
14751
- [fileSize]="fileAttachment.size || ''"
14752
- [variant]="getFileVariant(fileAttachment.type)"
14753
- [layout]="'compact'"
14754
- (fileClick)="handleFileAttachmentClick(fileAttachment)"
14755
- >
14756
- </ds-mobile-card-inline-file>
14757
- } }
14758
- </div>
14759
- } } } }
14766
+ <!-- File attachments displayed below message bubble -->
14767
+ @if (message.fileAttachments && message.fileAttachments.length > 0) {
14768
+ <div class="message-file-attachments" [class.own-message]="message.isOwnMessage">
14769
+ @for (fileAttachment of message.fileAttachments; track fileAttachment.id) {
14770
+ <!-- Show inline image preview for image attachments -->
14771
+ @if (fileAttachment.type === 'image') {
14772
+ <div class="message-image-attachment" (click)="handleImageClick(fileAttachment, message)">
14773
+ <img [src]="fileAttachment.src" [alt]="fileAttachment.name || 'Image'" class="inline-image" />
14774
+ </div>
14775
+ } @else {
14776
+ <!-- Show file card for non-image attachments -->
14777
+ <ds-mobile-card-inline-file
14778
+ [fileName]="fileAttachment.name || 'Unknown file'"
14779
+ [fileSize]="fileAttachment.size || ''"
14780
+ [variant]="getFileVariant(fileAttachment.type)"
14781
+ [layout]="'compact'"
14782
+ (fileClick)="handleFileAttachmentClick(fileAttachment)"
14783
+ >
14784
+ </ds-mobile-card-inline-file>
14785
+ }
14786
+ }
14787
+ </div>
14788
+ }
14789
+ }
14790
+ }
14791
+ }
14760
14792
  </div>
14761
14793
  </div>
14762
14794
  </ds-mobile-section>
@@ -14862,6 +14894,7 @@ class DsMobileChatModalService extends BaseModalService {
14862
14894
  error: options?.error,
14863
14895
  }, {
14864
14896
  keyboardClose: true, // Keep keyboard close behavior for this modal
14897
+ cssClass: 'ds-modal-base',
14865
14898
  });
14866
14899
  // console.log('[ChatModal] Modal created, presenting...');
14867
14900
  await modal.present();
@@ -14921,6 +14954,18 @@ class DsMobileNewInquiryModalComponent {
14921
14954
  * Callback function when form is submitted
14922
14955
  */
14923
14956
  onSubmit;
14957
+ /**
14958
+ * Placeholder for the title field
14959
+ */
14960
+ titlePlaceholder = 'Name your inquiry';
14961
+ /**
14962
+ * Placeholder for the description field
14963
+ */
14964
+ descriptionPlaceholder = 'Tell us what this inquiry is about...';
14965
+ /**
14966
+ * Label for the submit button
14967
+ */
14968
+ submitButtonLabel = 'Submit';
14924
14969
  /**
14925
14970
  * Form title field
14926
14971
  */
@@ -15134,7 +15179,7 @@ class DsMobileNewInquiryModalComponent {
15134
15179
  }
15135
15180
  }
15136
15181
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNewInquiryModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15137
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNewInquiryModalComponent, isStandalone: true, selector: "ds-mobile-new-inquiry-modal", inputs: { loading: "loading", error: "error", onSubmit: "onSubmit" }, viewQueries: [{ propertyName: "titleInputRef", first: true, predicate: ["titleInput"], descendants: true, read: ElementRef }, { propertyName: "titleInput", first: true, predicate: ["titleInput"], descendants: true }, { propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: `
15182
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNewInquiryModalComponent, isStandalone: true, selector: "ds-mobile-new-inquiry-modal", inputs: { loading: "loading", error: "error", onSubmit: "onSubmit", titlePlaceholder: "titlePlaceholder", descriptionPlaceholder: "descriptionPlaceholder", submitButtonLabel: "submitButtonLabel" }, viewQueries: [{ propertyName: "titleInputRef", first: true, predicate: ["titleInput"], descendants: true, read: ElementRef }, { propertyName: "titleInput", first: true, predicate: ["titleInput"], descendants: true }, { propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: `
15138
15183
  <ds-mobile-modal-base
15139
15184
  [loading]="loading"
15140
15185
  [error]="error"
@@ -15153,7 +15198,7 @@ class DsMobileNewInquiryModalComponent {
15153
15198
  [ghost]="true"
15154
15199
  [required]="true"
15155
15200
  [rows]="1"
15156
- placeholder="Name your inquiry"
15201
+ [placeholder]="titlePlaceholder"
15157
15202
  class="inquiry-title-input ghost-input-clean"
15158
15203
  (valueChange)="handleTitleChange($event)"
15159
15204
  />
@@ -15163,7 +15208,7 @@ class DsMobileNewInquiryModalComponent {
15163
15208
  [(ngModel)]="description"
15164
15209
  [ghost]="true"
15165
15210
  [rows]="1"
15166
- placeholder="Tell us what this inquiry is about..."
15211
+ [placeholder]="descriptionPlaceholder"
15167
15212
  class="inquiry-description-input ghost-input-clean"
15168
15213
  (valueChange)="validateForm()"
15169
15214
  />
@@ -15204,7 +15249,7 @@ class DsMobileNewInquiryModalComponent {
15204
15249
  </div>
15205
15250
 
15206
15251
  <!-- Submit Button (Right) -->
15207
- <ds-button variant="primary" size="lg" [disabled]="!isFormValid() || isSubmitting()" (clicked)="handleSubmit()"> Submit </ds-button>
15252
+ <ds-button variant="primary" size="lg" [disabled]="!isFormValid() || isSubmitting()" (clicked)="handleSubmit()"> {{ submitButtonLabel }} </ds-button>
15208
15253
  </div>
15209
15254
  </div>
15210
15255
  </div>
@@ -15241,7 +15286,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
15241
15286
  [ghost]="true"
15242
15287
  [required]="true"
15243
15288
  [rows]="1"
15244
- placeholder="Name your inquiry"
15289
+ [placeholder]="titlePlaceholder"
15245
15290
  class="inquiry-title-input ghost-input-clean"
15246
15291
  (valueChange)="handleTitleChange($event)"
15247
15292
  />
@@ -15251,7 +15296,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
15251
15296
  [(ngModel)]="description"
15252
15297
  [ghost]="true"
15253
15298
  [rows]="1"
15254
- placeholder="Tell us what this inquiry is about..."
15299
+ [placeholder]="descriptionPlaceholder"
15255
15300
  class="inquiry-description-input ghost-input-clean"
15256
15301
  (valueChange)="validateForm()"
15257
15302
  />
@@ -15292,7 +15337,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
15292
15337
  </div>
15293
15338
 
15294
15339
  <!-- Submit Button (Right) -->
15295
- <ds-button variant="primary" size="lg" [disabled]="!isFormValid() || isSubmitting()" (clicked)="handleSubmit()"> Submit </ds-button>
15340
+ <ds-button variant="primary" size="lg" [disabled]="!isFormValid() || isSubmitting()" (clicked)="handleSubmit()"> {{ submitButtonLabel }} </ds-button>
15296
15341
  </div>
15297
15342
  </div>
15298
15343
  </div>
@@ -15313,6 +15358,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
15313
15358
  type: Input
15314
15359
  }], onSubmit: [{
15315
15360
  type: Input
15361
+ }], titlePlaceholder: [{
15362
+ type: Input
15363
+ }], descriptionPlaceholder: [{
15364
+ type: Input
15365
+ }], submitButtonLabel: [{
15366
+ type: Input
15316
15367
  }] } });
15317
15368
 
15318
15369
  /**
@@ -15364,6 +15415,9 @@ class DsMobileNewInquiryModalService extends BaseModalService {
15364
15415
  onSubmit: options?.onSubmit,
15365
15416
  loading: options?.loading ?? false,
15366
15417
  error: options?.error,
15418
+ titlePlaceholder: options?.titlePlaceholder,
15419
+ descriptionPlaceholder: options?.descriptionPlaceholder,
15420
+ submitButtonLabel: options?.submitButtonLabel,
15367
15421
  }, {
15368
15422
  keyboardClose: false, // Don't close on keyboard hide for this modal
15369
15423
  cssClass: ['ds-modal-base', 'auto-height'],
@@ -19670,6 +19724,9 @@ class WhitelabelDemoModalComponent {
19670
19724
  else if (headerSurface === '#1D4A49') {
19671
19725
  this.currentTheme = 'freedom';
19672
19726
  }
19727
+ else if (headerSurface === '#2C3E50') {
19728
+ this.currentTheme = 'cobblestone';
19729
+ }
19673
19730
  else {
19674
19731
  this.currentTheme = 'default';
19675
19732
  }
@@ -19820,6 +19877,34 @@ class WhitelabelDemoModalComponent {
19820
19877
  this.updateSignInBgInputs();
19821
19878
  this.updateSignInContentColorInput();
19822
19879
  }
19880
+ applyCobblestoneTheme() {
19881
+ this.currentTheme = 'cobblestone';
19882
+ this.whitelabelService.updateConfig({
19883
+ logoUrl: '/Assets/logos/cobblestone-logo.svg',
19884
+ logoMarkUrl: '/Assets/logos/cobblestone-logomark.svg',
19885
+ logoAlt: 'Cobblestone',
19886
+ logoSize: 'sm',
19887
+ appIconSurface: '#2C3E50',
19888
+ appIconContent: '#FFFFFF',
19889
+ accent: '#3498DB',
19890
+ onAccent: '#FFFFFF',
19891
+ headerSurface: '#2C3E50',
19892
+ headerContent: '#FFFFFF',
19893
+ headerAccent: '#3498DB',
19894
+ onHeaderAccent: '#FFFFFF',
19895
+ showCityIllustration: false,
19896
+ signInBgType: 'gradient',
19897
+ signInBgSolid: '#E8EEF2',
19898
+ signInBgGradientStart: '#E8EEF2',
19899
+ signInBgGradientEnd: '#BDC3C7',
19900
+ signInContentColor: '#1a1a1a',
19901
+ organizationName: 'Cobblestone',
19902
+ organizationId: 'cobblestone'
19903
+ });
19904
+ this.updateCustomColorInputs();
19905
+ this.updateSignInBgInputs();
19906
+ this.updateSignInContentColorInput();
19907
+ }
19823
19908
  applyCustomColors() {
19824
19909
  this.whitelabelService.updateColors({
19825
19910
  appIconSurface: this.customAppIconSurface,
@@ -19917,6 +20002,9 @@ class WhitelabelDemoModalComponent {
19917
20002
  <button class="theme-btn" (click)="applyFreedomTheme()" [class.active]="currentTheme === 'freedom'">
19918
20003
  Freedom
19919
20004
  </button>
20005
+ <button class="theme-btn" (click)="applyCobblestoneTheme()" [class.active]="currentTheme === 'cobblestone'">
20006
+ Cobblestone
20007
+ </button>
19920
20008
  </div>
19921
20009
  </div>
19922
20010
 
@@ -20298,6 +20386,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
20298
20386
  <button class="theme-btn" (click)="applyFreedomTheme()" [class.active]="currentTheme === 'freedom'">
20299
20387
  Freedom
20300
20388
  </button>
20389
+ <button class="theme-btn" (click)="applyCobblestoneTheme()" [class.active]="currentTheme === 'cobblestone'">
20390
+ Cobblestone
20391
+ </button>
20301
20392
  </div>
20302
20393
  </div>
20303
20394