@propbinder/mobile-design 0.1.15 → 0.1.17

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.
@@ -1,16 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, effect, Injectable, inject, Input, Component, input, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, output, ViewChild, ViewEncapsulation, CUSTOM_ELEMENTS_SCHEMA, model, ElementRef, createComponent } from '@angular/core';
2
+ import { signal, computed, effect, Injectable, inject, Input, Component, input, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, output, ViewChild, model, ElementRef, CUSTOM_ELEMENTS_SCHEMA, createComponent, forwardRef, ViewEncapsulation } from '@angular/core';
3
3
  import * as i1$2 from '@angular/common';
4
4
  import { CommonModule, isPlatformBrowser } from '@angular/common';
5
5
  import * as i1$3 from '@angular/router';
6
6
  import { Router, NavigationEnd } from '@angular/router';
7
7
  import * as i1 from '@ionic/angular/standalone';
8
- import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, ModalController, Platform, IonRefresher, IonRefresherContent, IonTabBar, IonTabButton, IonLabel, IonTabs, IonTab, IonSpinner, IonButton } from '@ionic/angular/standalone';
8
+ import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, Platform, ModalController, IonRefresher, IonRefresherContent, IonTabBar, IonTabButton, IonLabel, IonTabs, IonTab, IonSpinner, IonButton } from '@ionic/angular/standalone';
9
9
  import { ImpactStyle, Haptics } from '@capacitor/haptics';
10
10
  import { DsIconButtonComponent, DsIconComponent, DsButtonComponent, DsAvatarComponent, DsShapeIndicatorComponent } from '@propbinder/design-system';
11
11
  import { StatusBar } from '@capacitor/status-bar';
12
12
  import * as i1$1 from '@angular/forms';
13
- import { FormsModule } from '@angular/forms';
13
+ import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
14
14
  import { Keyboard } from '@capacitor/keyboard';
15
15
  import { Camera, CameraSource, CameraResultType } from '@capacitor/camera';
16
16
  import { filter } from 'rxjs/operators';
@@ -1815,1111 +1815,579 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
1815
1815
  }] } });
1816
1816
 
1817
1817
  /**
1818
- * DsAvatarWithBadgeComponent
1819
- *
1820
- * Displays an avatar with a logomark badge overlay.
1821
- * Useful for showing user avatars with organization branding.
1822
- *
1823
- * @example
1824
- * ```html
1825
- * <ds-avatar-with-badge
1826
- * [type]="'initials'"
1827
- * [initials]="'JD'"
1828
- * [size]="'lg'"
1829
- * [badgePosition]="'bottom-right'"
1830
- * />
1831
- * ```
1818
+ * User service for managing current user data globally
1832
1819
  */
1833
- class DsAvatarWithBadgeComponent {
1834
- whitelabelService = inject(WhitelabelService);
1835
- // Avatar props
1836
- type = 'initials';
1837
- size = 'md';
1838
- initials = '';
1839
- src = '';
1840
- iconName = 'remixUser3Fill';
1841
- // Badge props
1842
- showBadge = true;
1843
- badgePosition = 'bottom-right';
1844
- badgeClasses = computed(() => {
1845
- return `avatar-badge avatar-badge--${this.badgePosition} avatar-badge--${this.size}`;
1846
- }, ...(ngDevMode ? [{ debugName: "badgeClasses" }] : []));
1847
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsAvatarWithBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1848
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: DsAvatarWithBadgeComponent, isStandalone: true, selector: "ds-avatar-with-badge", inputs: { type: "type", size: "size", initials: "initials", src: "src", iconName: "iconName", showBadge: "showBadge", badgePosition: "badgePosition" }, ngImport: i0, template: `
1849
- <div class="avatar-badge-container">
1850
- <ds-avatar
1851
- [type]="type"
1852
- [size]="size"
1853
- [initials]="initials"
1854
- [src]="src"
1855
- [iconName]="iconName"
1856
- />
1857
-
1858
- @if (showBadge) {
1859
- <div [class]="badgeClasses()">
1860
- <img [src]="whitelabelService.logoMarkUrl()" [alt]="whitelabelService.logoAlt()" />
1861
- </div>
1862
- }
1863
- </div>
1864
- `, isInline: true, styles: [":host{display:inline-block;position:relative}.avatar-badge-container{position:relative;display:inline-block}.avatar-badge{position:absolute;background:var(--color-brand-secondary, #5d5fef);border-radius:8px;display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge img{width:10px;height:10px;object-fit:contain}.avatar-badge--bottom-right{bottom:-6px;right:-6px}.avatar-badge--bottom-left{bottom:-6px;left:-6px}.avatar-badge--top-right{top:-6px;right:-6px}.avatar-badge--top-left{top:-6px;left:-6px}.avatar-badge--xs{width:16px;height:16px}.avatar-badge--sm{width:18px;height:18px}.avatar-badge--md{width:20px;height:20px}.avatar-badge--lg{width:24px;height:24px}.avatar-badge--xl{width:28px;height:28px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }] });
1820
+ class UserService {
1821
+ // User avatar configuration
1822
+ _avatarInitials = signal('LM', ...(ngDevMode ? [{ debugName: "_avatarInitials" }] : []));
1823
+ _avatarType = signal('initials', ...(ngDevMode ? [{ debugName: "_avatarType" }] : []));
1824
+ _avatarSrc = signal('', ...(ngDevMode ? [{ debugName: "_avatarSrc" }] : []));
1825
+ // Profile menu items configuration
1826
+ _profileMenuItems = signal(undefined, ...(ngDevMode ? [{ debugName: "_profileMenuItems" }] : []));
1827
+ // Readonly computed values
1828
+ avatarInitials = this._avatarInitials.asReadonly();
1829
+ avatarType = this._avatarType.asReadonly();
1830
+ avatarSrc = this._avatarSrc.asReadonly();
1831
+ profileMenuItems = this._profileMenuItems.asReadonly();
1832
+ /**
1833
+ * Update avatar configuration
1834
+ */
1835
+ setAvatarInitials(initials) {
1836
+ this._avatarInitials.set(initials);
1837
+ }
1838
+ setAvatarType(type) {
1839
+ this._avatarType.set(type);
1840
+ }
1841
+ setAvatarSrc(src) {
1842
+ this._avatarSrc.set(src);
1843
+ }
1844
+ /**
1845
+ * Set profile menu items globally.
1846
+ * This will be used by both ds-mobile-tab-bar and ds-mobile-page-main
1847
+ * if they don't receive profileMenuItems as an input.
1848
+ */
1849
+ setProfileMenuItems(items) {
1850
+ this._profileMenuItems.set(items);
1851
+ }
1852
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1853
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, providedIn: 'root' });
1865
1854
  }
1866
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsAvatarWithBadgeComponent, decorators: [{
1867
- type: Component,
1868
- args: [{ selector: 'ds-avatar-with-badge', standalone: true, imports: [CommonModule, DsAvatarComponent], encapsulation: ViewEncapsulation.Emulated, template: `
1869
- <div class="avatar-badge-container">
1870
- <ds-avatar
1871
- [type]="type"
1872
- [size]="size"
1873
- [initials]="initials"
1874
- [src]="src"
1875
- [iconName]="iconName"
1876
- />
1877
-
1878
- @if (showBadge) {
1879
- <div [class]="badgeClasses()">
1880
- <img [src]="whitelabelService.logoMarkUrl()" [alt]="whitelabelService.logoAlt()" />
1881
- </div>
1882
- }
1883
- </div>
1884
- `, styles: [":host{display:inline-block;position:relative}.avatar-badge-container{position:relative;display:inline-block}.avatar-badge{position:absolute;background:var(--color-brand-secondary, #5d5fef);border-radius:8px;display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge img{width:10px;height:10px;object-fit:contain}.avatar-badge--bottom-right{bottom:-6px;right:-6px}.avatar-badge--bottom-left{bottom:-6px;left:-6px}.avatar-badge--top-right{top:-6px;right:-6px}.avatar-badge--top-left{top:-6px;left:-6px}.avatar-badge--xs{width:16px;height:16px}.avatar-badge--sm{width:18px;height:18px}.avatar-badge--md{width:20px;height:20px}.avatar-badge--lg{width:24px;height:24px}.avatar-badge--xl{width:28px;height:28px}\n"] }]
1885
- }], propDecorators: { type: [{
1886
- type: Input
1887
- }], size: [{
1888
- type: Input
1889
- }], initials: [{
1890
- type: Input
1891
- }], src: [{
1892
- type: Input
1893
- }], iconName: [{
1894
- type: Input
1895
- }], showBadge: [{
1896
- type: Input
1897
- }], badgePosition: [{
1898
- type: Input
1899
- }] } });
1855
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, decorators: [{
1856
+ type: Injectable,
1857
+ args: [{
1858
+ providedIn: 'root'
1859
+ }]
1860
+ }] });
1900
1861
 
1901
1862
  /**
1902
- * Whitelabel Demo Modal Component
1863
+ * DsMobilePageMainComponent
1903
1864
  *
1904
- * Demonstrates the whitelabeling system with theme selection, brand colors, and logo previews.
1905
- * Opens as a full-screen modal similar to post details.
1865
+ * A complete mobile page layout for main/tab pages with:
1866
+ * - Fixed header with logomark + title + avatar
1867
+ * - Purple expandable header section (scrolls with content)
1868
+ * - White rounded content wrapper
1869
+ * - Pull-to-refresh support
1870
+ * - Auto scroll title fade-in
1871
+ *
1872
+ * @example
1873
+ * ```html
1874
+ * <!-- Simple page -->
1875
+ * <ds-mobile-page-main
1876
+ * title="Inquiries"
1877
+ * [avatarInitials]="'JD'">
1878
+ * <div class="page-content">
1879
+ * <!-- Your content -->
1880
+ * </div>
1881
+ * </ds-mobile-page-main>
1882
+ *
1883
+ * <!-- Page with custom header content -->
1884
+ * <ds-mobile-page-main
1885
+ * title="Home"
1886
+ * headerTitle="Welcome, Lars"
1887
+ * headerSubtitle="Your rental property at a glance."
1888
+ * [avatarInitials]="'L'">
1889
+ *
1890
+ * <div header-content class="property-tiles">
1891
+ * <!-- Custom header content like tiles -->
1892
+ * </div>
1893
+ *
1894
+ * <div class="page-content">
1895
+ * <!-- Main page content -->
1896
+ * </div>
1897
+ * </ds-mobile-page-main>
1898
+ * ```
1906
1899
  */
1907
- class WhitelabelDemoModalComponent {
1908
- whitelabelService = inject(WhitelabelService);
1900
+ class DsMobilePageMainComponent extends MobilePageBase {
1901
+ elementRef;
1902
+ ionContent;
1903
+ // Platform detection
1904
+ platform = inject(Platform);
1909
1905
  modalController = inject(ModalController);
1910
- // Current active theme
1911
- currentTheme = 'default';
1912
- // Custom color inputs
1913
- customPrimarySurface = '#6B5FF5';
1914
- customPrimaryContent = '#FFFFFF';
1915
- customSecondarySurface = '#221a4c';
1916
- customSecondaryContent = '#FFFFFF';
1917
- ngOnInit() {
1918
- this.updateCustomColorInputs();
1919
- this.detectCurrentTheme();
1920
- }
1906
+ router = inject(Router);
1907
+ userService = inject(UserService);
1908
+ // Computed property to check if running on native platform
1909
+ isNativePlatform = computed(() => this.platform.is('ios') ||
1910
+ this.platform.is('android') ||
1911
+ this.platform.is('capacitor'), ...(ngDevMode ? [{ debugName: "isNativePlatform" }] : []));
1912
+ // Inputs - Title
1913
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : [])); // For fixed header title
1914
+ headerTitle = input('', ...(ngDevMode ? [{ debugName: "headerTitle" }] : [])); // Optional different title for expandable header
1915
+ headerSubtitle = input('', ...(ngDevMode ? [{ debugName: "headerSubtitle" }] : []));
1916
+ // Inputs - Avatar
1917
+ avatarType = input('initials', ...(ngDevMode ? [{ debugName: "avatarType" }] : []));
1918
+ avatarInitials = input('U', ...(ngDevMode ? [{ debugName: "avatarInitials" }] : []));
1919
+ avatarSrc = input('', ...(ngDevMode ? [{ debugName: "avatarSrc" }] : []));
1920
+ avatarIconName = input('remixUser3Line', ...(ngDevMode ? [{ debugName: "avatarIconName" }] : []));
1921
+ // Inputs - Features
1922
+ showRefresh = input(true, ...(ngDevMode ? [{ debugName: "showRefresh" }] : []));
1923
+ showCondensedHeader = input(true, ...(ngDevMode ? [{ debugName: "showCondensedHeader" }] : []));
1924
+ scrollThreshold = input(160, ...(ngDevMode ? [{ debugName: "scrollThreshold" }] : [])); // Pixels to scroll before title appears
1925
+ headerFadeDistance = input(150, ...(ngDevMode ? [{ debugName: "headerFadeDistance" }] : [])); // Distance over which header fades out
1921
1926
  /**
1922
- * Detect the current active theme based on colors
1927
+ * Profile menu action groups to display when avatar is clicked.
1928
+ * If not provided, a default menu will be used (without Whitelabel Demo).
1929
+ *
1930
+ * @example
1931
+ * ```typescript
1932
+ * profileMenuItems: ActionGroup[] = [
1933
+ * {
1934
+ * actions: [
1935
+ * { action: 'profile', title: 'Min profil', icon: 'remixUser3Line' },
1936
+ * { action: 'settings', title: 'Indstillinger', icon: 'remixSettings3Line' }
1937
+ * ]
1938
+ * },
1939
+ * {
1940
+ * actions: [
1941
+ * { action: 'logout', title: 'Log ud', icon: 'remixLogoutBoxLine', destructive: true }
1942
+ * ]
1943
+ * }
1944
+ * ];
1945
+ * ```
1923
1946
  */
1924
- detectCurrentTheme() {
1925
- const secondarySurface = this.whitelabelService.secondarySurface().toUpperCase();
1926
- if (secondarySurface === '#A70923') {
1927
- this.currentTheme = 'cej';
1928
- }
1929
- else if (secondarySurface === '#660036') {
1930
- this.currentTheme = 'pka';
1931
- }
1932
- else if (secondarySurface === '#262424') {
1933
- this.currentTheme = 'clave';
1934
- }
1935
- else {
1936
- this.currentTheme = 'default';
1937
- }
1938
- }
1947
+ profileMenuItems = input(undefined, ...(ngDevMode ? [{ debugName: "profileMenuItems" }] : []));
1948
+ // Outputs
1949
+ avatarClick = output();
1939
1950
  /**
1940
- * Close the modal
1951
+ * Emitted when a profile menu action is selected.
1952
+ * Parent component should handle the action logic (navigation, logout, etc.).
1941
1953
  */
1942
- close() {
1943
- this.modalController.dismiss();
1954
+ profileActionSelected = output();
1955
+ refresh = output();
1956
+ scroll = output();
1957
+ constructor(elementRef) {
1958
+ super();
1959
+ this.elementRef = elementRef;
1944
1960
  }
1945
- applyDefaultTheme() {
1946
- this.currentTheme = 'default';
1947
- this.whitelabelService.updateConfig({
1948
- logoUrl: '/Assets/logos/propbinder-logomark.svg',
1949
- logoMarkUrl: '/Assets/logos/propbinder-logomark.svg',
1950
- logoAlt: 'Propbinder',
1951
- logoHeight: 28,
1952
- primarySurface: '#6B5FF5',
1953
- primaryContent: '#FFFFFF',
1954
- secondarySurface: '#221a4c',
1955
- secondaryContent: '#FFFFFF',
1956
- organizationName: 'Propbinder',
1957
- organizationId: 'default'
1958
- });
1959
- this.updateCustomColorInputs();
1961
+ ngAfterViewInit() {
1962
+ // Initial setup if needed
1960
1963
  }
1961
- applyCejTheme() {
1962
- this.currentTheme = 'cej';
1963
- this.whitelabelService.updateConfig({
1964
- logoUrl: '/Assets/logos/cej-logo.png',
1965
- logoMarkUrl: '/Assets/logos/cej-logo.png',
1966
- logoAlt: 'CEJ',
1967
- logoHeight: 36,
1968
- primarySurface: '#FFFFFF',
1969
- primaryContent: '#A70923',
1970
- secondarySurface: '#A70923',
1971
- secondaryContent: '#FFFFFF',
1972
- organizationName: 'CEJ',
1973
- organizationId: 'cej'
1974
- });
1975
- this.updateCustomColorInputs();
1976
- }
1977
- applyPkaTheme() {
1978
- this.currentTheme = 'pka';
1979
- this.whitelabelService.updateConfig({
1980
- logoUrl: '/Assets/logos/pka-logo.svg',
1981
- logoMarkUrl: '/Assets/logos/pka-logo.svg',
1982
- logoAlt: 'PKA',
1983
- logoHeight: 24,
1984
- primarySurface: '#CC006C',
1985
- primaryContent: '#FFFFFF',
1986
- secondarySurface: '#660036',
1987
- secondaryContent: '#FFFFFF',
1988
- organizationName: 'PKA',
1989
- organizationId: 'pka'
1990
- });
1991
- this.updateCustomColorInputs();
1992
- }
1993
- applyClaveTheme() {
1994
- this.currentTheme = 'clave';
1995
- this.whitelabelService.updateConfig({
1996
- logoUrl: '/Assets/logos/clave-logo.svg',
1997
- logoMarkUrl: '/Assets/logos/clave-logo.svg',
1998
- logoAlt: 'Clave',
1999
- logoHeight: 24,
2000
- primarySurface: '#868764',
2001
- primaryContent: '#262424',
2002
- secondarySurface: '#262424',
2003
- secondaryContent: '#FFFFFF',
2004
- organizationName: 'Clave',
2005
- organizationId: 'clave'
1964
+ /**
1965
+ * Handle avatar click - opens profile actions bottom sheet
1966
+ */
1967
+ async handleAvatarClick() {
1968
+ console.log('Avatar clicked - opening profile bottom sheet');
1969
+ // Emit the event for any parent listeners
1970
+ this.avatarClick.emit();
1971
+ // Use input if provided, otherwise fall back to service, otherwise use default menu
1972
+ const menuItems = this.profileMenuItems() || this.userService.profileMenuItems() || [
1973
+ {
1974
+ actions: [
1975
+ {
1976
+ action: 'profile',
1977
+ title: 'Min profil',
1978
+ icon: 'remixUser3Line',
1979
+ destructive: false
1980
+ },
1981
+ {
1982
+ action: 'settings',
1983
+ title: 'Indstillinger',
1984
+ icon: 'remixSettings3Line',
1985
+ destructive: false
1986
+ }
1987
+ ]
1988
+ },
1989
+ {
1990
+ actions: [
1991
+ {
1992
+ action: 'logout',
1993
+ title: 'Log ud',
1994
+ icon: 'remixLogoutBoxLine',
1995
+ destructive: true
1996
+ }
1997
+ ]
1998
+ }
1999
+ ];
2000
+ // If no menu items configured, just emit and return
2001
+ if (!menuItems || menuItems.length === 0) {
2002
+ return;
2003
+ }
2004
+ const sheet = await this.modalController.create({
2005
+ component: DsMobileActionsBottomSheetComponent,
2006
+ componentProps: {
2007
+ customActionGroups: menuItems
2008
+ },
2009
+ // Auto-height: no breakpoints, no handle
2010
+ cssClass: 'ds-bottom-sheet auto-height'
2006
2011
  });
2007
- this.updateCustomColorInputs();
2012
+ await sheet.present();
2013
+ const result = await sheet.onWillDismiss();
2014
+ if (result.data?.action) {
2015
+ // Emit the selected action to parent
2016
+ this.profileActionSelected.emit(result.data);
2017
+ }
2008
2018
  }
2009
- applyCustomColors() {
2010
- this.whitelabelService.updateColors({
2011
- primarySurface: this.customPrimarySurface,
2012
- primaryContent: this.customPrimaryContent,
2013
- secondarySurface: this.customSecondarySurface,
2014
- secondaryContent: this.customSecondaryContent
2015
- });
2019
+ /**
2020
+ * Handle scroll events
2021
+ * - Shows title in fixed header when scrolled past threshold
2022
+ * - Fades out expandable header content based on scroll position
2023
+ * - Emits scroll event for custom handling
2024
+ */
2025
+ handleScroll(event) {
2026
+ const scrollTop = event.detail.scrollTop;
2027
+ const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
2028
+ const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
2029
+ // Show title in fixed header when scrolled past threshold
2030
+ if (scrollTop > this.scrollThreshold()) {
2031
+ header?.classList.add('header-scrolled');
2032
+ }
2033
+ else {
2034
+ header?.classList.remove('header-scrolled');
2035
+ }
2036
+ // Fade out header-expandable content based on scroll
2037
+ if (headerExpandable) {
2038
+ const fadeDistance = this.headerFadeDistance();
2039
+ const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
2040
+ // Calculate opacity (1 to 0)
2041
+ const opacity = 1 - fadeProgress;
2042
+ // Calculate transform (0px to -20px upward)
2043
+ const translateY = fadeProgress * -20;
2044
+ // Apply styles
2045
+ headerExpandable.style.opacity = opacity.toString();
2046
+ headerExpandable.style.transform = `translateY(${translateY}px)`;
2047
+ }
2048
+ this.scroll.emit(event);
2016
2049
  }
2017
- updateCustomColorInputs() {
2018
- this.customPrimarySurface = this.whitelabelService.primarySurface();
2019
- this.customPrimaryContent = this.whitelabelService.primaryContent();
2020
- this.customSecondarySurface = this.whitelabelService.secondarySurface();
2021
- this.customSecondaryContent = this.whitelabelService.secondaryContent();
2050
+ /**
2051
+ * Handle pull-to-refresh
2052
+ * Emits refresh event - parent should call event.target.complete()
2053
+ */
2054
+ async handleRefresh(event) {
2055
+ // Haptic feedback for pull-to-refresh
2056
+ try {
2057
+ await Haptics.impact({ style: ImpactStyle.Medium });
2058
+ }
2059
+ catch {
2060
+ // Fallback to Web Vibration API if Capacitor Haptics is not available
2061
+ if ('vibrate' in navigator) {
2062
+ navigator.vibrate(50);
2063
+ }
2064
+ }
2065
+ this.refresh.emit(event);
2022
2066
  }
2023
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2024
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: WhitelabelDemoModalComponent, isStandalone: true, selector: "ds-whitelabel-demo-modal", ngImport: i0, template: `
2025
- <!-- Fixed Header -->
2026
- <div class="modal-header">
2027
- <div class="header-content">
2028
- <span class="header-title">Whitelabel</span>
2029
- <ds-icon-button
2030
- icon="remixCloseLine"
2031
- variant="secondary"
2032
- size="lg"
2033
- (click)="close()"
2034
- class="close-button"
2035
- aria-label="Close">
2036
- </ds-icon-button>
2037
- </div>
2038
- </div>
2039
-
2040
- <!-- Scrollable Content -->
2041
- <ion-content [scrollY]="true" class="modal-content">
2042
- <div class="demo-container">
2043
- <!-- Theme Selection -->
2044
- <div class="demo-section">
2045
- <h2>Theme</h2>
2046
- <div class="theme-buttons">
2047
- <button class="theme-btn" (click)="applyDefaultTheme()" [class.active]="currentTheme === 'default'">
2048
- Propbinder
2049
- </button>
2050
- <button class="theme-btn" (click)="applyCejTheme()" [class.active]="currentTheme === 'cej'">
2051
- CEJ
2052
- </button>
2053
- <button class="theme-btn" (click)="applyPkaTheme()" [class.active]="currentTheme === 'pka'">
2054
- PKA
2055
- </button>
2056
- <button class="theme-btn" (click)="applyClaveTheme()" [class.active]="currentTheme === 'clave'">
2057
- Clave
2058
- </button>
2059
- </div>
2060
- </div>
2067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageMainComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null }, profileMenuItems: { classPropertyName: "profileMenuItems", publicName: "profileMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", refresh: "refresh", scroll: "scroll" }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
2069
+ <!-- Fixed header at top -->
2070
+ <ion-header>
2071
+ <ion-toolbar>
2072
+ <div class="header-home">
2073
+ <!-- Whitelabel Logomark -->
2074
+ <ds-logo variant="mark" size="lg" />
2061
2075
 
2062
- <!-- Logo & Logomark Preview -->
2063
- <div class="demo-section">
2064
- <h2>Logo Preview</h2>
2065
- <div class="preview-grid">
2066
- <div class="preview-tile">
2067
- <h3>Logo</h3>
2068
- <div class="logo-preview">
2069
- <ds-logo variant="full" />
2070
- </div>
2071
- </div>
2072
- <div class="preview-tile">
2073
- <h3>Logomark</h3>
2074
- <div class="logomark-preview">
2075
- <ds-avatar-with-badge
2076
- [type]="'initials'"
2077
- [initials]="'KN'"
2078
- [size]="'md'"
2079
- [badgePosition]="'bottom-right'"
2080
- />
2081
- </div>
2082
- </div>
2083
- </div>
2076
+ <!-- Title - fades in on scroll -->
2077
+ <ion-title class="header-home__title">{{ title() }}</ion-title>
2078
+
2079
+ <!-- Avatar -->
2080
+ <div class="header-home__actions">
2081
+ <ds-avatar
2082
+ [size]="'md'"
2083
+ [type]="avatarType()"
2084
+ [initials]="avatarInitials()"
2085
+ [src]="avatarSrc()"
2086
+ [iconName]="avatarIconName()"
2087
+ (click)="handleAvatarClick()"
2088
+ style="cursor: pointer;"
2089
+ />
2090
+ </div>
2091
+ </div>
2092
+ </ion-toolbar>
2093
+ </ion-header>
2094
+
2095
+ <!-- Content with expandable header -->
2096
+ <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
2097
+ <!-- Condensed header for Ionic scroll effects -->
2098
+ @if (showCondensedHeader()) {
2099
+ <ion-header collapse="condense">
2100
+ <ion-toolbar>
2101
+ <ion-title size="large">{{ title() }}</ion-title>
2102
+ </ion-toolbar>
2103
+ </ion-header>
2104
+ }
2105
+
2106
+ <!-- Pull to refresh (only on native iOS/Android) -->
2107
+ @if (showRefresh() && isNativePlatform()) {
2108
+ <ion-refresher
2109
+ slot="fixed"
2110
+ (ionRefresh)="handleRefresh($event)"
2111
+ [pullFactor]="0.4"
2112
+ [pullMin]="80"
2113
+ [pullMax]="240"
2114
+ closeDuration="600ms">
2115
+ <ion-refresher-content
2116
+ pullingIcon="remixArrowDownS"
2117
+ refreshingSpinner="lines">
2118
+ </ion-refresher-content>
2119
+ </ion-refresher>
2120
+ }
2121
+
2122
+ <!-- Expandable header section (purple background) -->
2123
+ <div class="header-expandable">
2124
+ <div class="header-expandable-inner">
2125
+ <div class="header-expandable__text">
2126
+ <h1 class="header-expandable__title">{{ headerTitle() || title() }}</h1>
2127
+ @if (headerSubtitle()) {
2128
+ <p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
2129
+ }
2084
2130
  </div>
2085
2131
 
2086
- <!-- Brand Colors -->
2087
- <div class="demo-section">
2088
- <h2>Brand Colors</h2>
2089
- <div class="color-section">
2090
- <div class="color-swatches">
2091
- <div class="swatch swatch--primary-surface">
2092
- <div class="swatch-label">Primary Surface</div>
2093
- <div class="swatch-value">{{ whitelabelService.primarySurface() }}</div>
2094
- </div>
2095
- <div class="swatch swatch--primary-content">
2096
- <div class="swatch-label">Primary Content</div>
2097
- <div class="swatch-value">{{ whitelabelService.primaryContent() }}</div>
2098
- </div>
2099
- <div class="swatch swatch--secondary-surface">
2100
- <div class="swatch-label">Secondary Surface</div>
2101
- <div class="swatch-value">{{ whitelabelService.secondarySurface() }}</div>
2102
- </div>
2103
- <div class="swatch swatch--secondary-content">
2104
- <div class="swatch-label">Secondary Content</div>
2105
- <div class="swatch-value">{{ whitelabelService.secondaryContent() }}</div>
2106
- </div>
2107
- </div>
2108
-
2109
- <div class="color-inputs">
2110
- <div class="color-group-label">Primary</div>
2111
- <div class="color-row">
2112
- <label>Surface</label>
2113
- <input
2114
- type="color"
2115
- [(ngModel)]="customPrimarySurface"
2116
- (change)="applyCustomColors()"
2117
- />
2118
- <input
2119
- type="text"
2120
- [(ngModel)]="customPrimarySurface"
2121
- (change)="applyCustomColors()"
2122
- />
2123
- </div>
2124
- <div class="color-row">
2125
- <label>Content</label>
2126
- <input
2127
- type="color"
2128
- [(ngModel)]="customPrimaryContent"
2129
- (change)="applyCustomColors()"
2130
- />
2131
- <input
2132
- type="text"
2133
- [(ngModel)]="customPrimaryContent"
2134
- (change)="applyCustomColors()"
2135
- />
2136
- </div>
2137
-
2138
- <div class="color-group-label">Secondary</div>
2139
- <div class="color-row">
2140
- <label>Surface</label>
2141
- <input
2142
- type="color"
2143
- [(ngModel)]="customSecondarySurface"
2144
- (change)="applyCustomColors()"
2145
- />
2146
- <input
2147
- type="text"
2148
- [(ngModel)]="customSecondarySurface"
2149
- (change)="applyCustomColors()"
2150
- />
2151
- </div>
2152
- <div class="color-row">
2153
- <label>Content</label>
2154
- <input
2155
- type="color"
2156
- [(ngModel)]="customSecondaryContent"
2157
- (change)="applyCustomColors()"
2158
- />
2159
- <input
2160
- type="text"
2161
- [(ngModel)]="customSecondaryContent"
2162
- (change)="applyCustomColors()"
2163
- />
2164
- </div>
2165
- </div>
2166
- </div>
2167
- </div>
2132
+ <!-- Slot for custom header content (e.g., property tiles) -->
2133
+ <ng-content select="[header-content]"></ng-content>
2134
+ </div>
2135
+ </div>
2136
+
2137
+ <!-- Content wrapper -->
2138
+ <div class="content-wrapper">
2139
+ <div class="content-inner">
2140
+ <!-- Main page content -->
2141
+ <ng-content></ng-content>
2168
2142
  </div>
2143
+ </div>
2169
2144
  </ion-content>
2170
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%;background:var(--color-background-neutral-primary, #ffffff)}.modal-header{flex-shrink:0;background:var(--color-background-neutral-primary, #ffffff);border-bottom:1px solid var(--border-color-default, #e0e0e0);padding:0 16px}ion-content,.modal-content{--background: #ffffff;flex:1;width:100%}.header-content{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px}.header-title{font-family:Brockmann,sans-serif;font-size:17px;font-weight:600;color:var(--color-text-primary, #1a1a1a);flex:1}.close-button{flex-shrink:0;border-radius:50%}.close-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important;background:var(--color-background-neutral-secondary, #f5f5f5)!important;color:var(--color-text-primary, #1a1a1a)!important}.demo-container{padding:20px;max-width:600px;margin:0 auto;width:100%}.demo-section{margin-bottom:32px}.demo-section h2{margin-bottom:16px;font-size:18px;font-weight:600;color:#333}.theme-buttons{display:flex;gap:12px;flex-wrap:wrap}.theme-btn{padding:8px 16px;border-radius:8px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .2s ease;background:#e0e0e0;color:#333}.theme-btn:active{transform:scale(.98)}.theme-btn.active{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.logo-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:var(--color-brand-secondary);border-radius:12px;min-height:80px}.logomark-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:#fff;border:1px solid #e0e0e0;border-radius:12px;min-height:80px}.preview-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.preview-tile{background:#fff;border-radius:12px}.preview-tile h3{font-size:13px;font-weight:600;color:#666;margin-top:0;margin-bottom:12px}.color-section{background:#fff;border-radius:12px}.color-swatches{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}.swatch{padding:16px;border-radius:8px;text-align:center}.swatch-label{font-size:12px;font-weight:600;margin-bottom:4px}.swatch-value{font-size:11px;opacity:.8;font-family:monospace}.swatch--primary-surface{background:var(--color-primary-surface);color:var(--color-primary-content)}.swatch--primary-content{background:var(--color-primary-content);color:var(--color-primary-surface);border:1px solid #e0e0e0}.swatch--secondary-surface{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.swatch--secondary-content{background:var(--color-secondary-content);color:var(--color-secondary-surface);border:1px solid #e0e0e0}.color-inputs{display:flex;flex-direction:column;gap:12px}.color-group-label{font-size:13px;font-weight:600;color:#333;margin-top:8px}.color-row{display:flex;align-items:center;gap:12px}.color-row label{min-width:70px;font-size:13px;color:#666}.color-row input[type=color]{width:40px;height:32px;border:none;border-radius:6px;cursor:pointer;padding:0}.color-row input[type=text]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px}@supports (padding: env(safe-area-inset-bottom)){.demo-container{padding-bottom:calc(20px + env(safe-area-inset-bottom))}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsAvatarWithBadgeComponent, selector: "ds-avatar-with-badge", inputs: ["type", "size", "initials", "src", "iconName", "showBadge", "badgePosition"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
2145
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%}:host ion-header{background:var(--color-brand-secondary);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){ion-header{display:none;height:auto}}.header-home{display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:var(--color-brand-secondary);height:72px}.header-home__title{position:absolute;left:50%;transform:translate(-50%) translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0;transition:transform .6s ease,opacity .6s ease;margin:0;padding:0;--color: white}.header-scrolled .header-home__title{opacity:1;transform:translate(-50%) translateY(0)}.header-home__actions{display:flex;align-items:center;gap:8px}.header-home__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}.logomark{height:28px;width:auto;flex-shrink:0}@media (min-width: 768px){.header-home{padding:16px 24px}.logomark{height:32px}.header-home__title{display:none}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px;overflow:hidden}ion-content::part(scroll){display:flex;flex-direction:column}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-header[collapse=condense]{display:none}@media (min-width: 768px){ion-header[collapse=condense]{display:none}}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}.header-expandable__text{margin-bottom:0}.header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}.header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){.header-expandable{padding:48px var(--content-padding-md)}.header-expandable__title{font-size:var(--font-size-3xl)}.header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{position:relative;z-index:20;padding:0;flex:1;display:flex;flex-direction:column}@media (min-width: 768px){.content-wrapper{width:100%}}.content-inner{background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary);flex:1;padding:24px 20px 32px;padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom,0px))}@media (min-width: 768px){.content-inner{border-radius:16px 16px 0 0;padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.content-inner{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.content-inner{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.content-inner{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.content-inner{padding:32px var(--content-padding-3xl)!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
2171
2146
  }
2172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalComponent, decorators: [{
2147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageMainComponent, decorators: [{
2173
2148
  type: Component,
2174
- args: [{ selector: 'ds-whitelabel-demo-modal', standalone: true, imports: [
2149
+ args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [
2175
2150
  CommonModule,
2176
- FormsModule,
2151
+ IonHeader,
2152
+ IonToolbar,
2153
+ IonTitle,
2177
2154
  IonContent,
2178
- DsLogoComponent,
2179
- DsAvatarWithBadgeComponent,
2180
- DsIconButtonComponent
2181
- ], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
2182
- <!-- Fixed Header -->
2183
- <div class="modal-header">
2184
- <div class="header-content">
2185
- <span class="header-title">Whitelabel</span>
2186
- <ds-icon-button
2187
- icon="remixCloseLine"
2188
- variant="secondary"
2189
- size="lg"
2190
- (click)="close()"
2191
- class="close-button"
2192
- aria-label="Close">
2193
- </ds-icon-button>
2194
- </div>
2195
- </div>
2196
-
2197
- <!-- Scrollable Content -->
2198
- <ion-content [scrollY]="true" class="modal-content">
2199
- <div class="demo-container">
2200
- <!-- Theme Selection -->
2201
- <div class="demo-section">
2202
- <h2>Theme</h2>
2203
- <div class="theme-buttons">
2204
- <button class="theme-btn" (click)="applyDefaultTheme()" [class.active]="currentTheme === 'default'">
2205
- Propbinder
2206
- </button>
2207
- <button class="theme-btn" (click)="applyCejTheme()" [class.active]="currentTheme === 'cej'">
2208
- CEJ
2209
- </button>
2210
- <button class="theme-btn" (click)="applyPkaTheme()" [class.active]="currentTheme === 'pka'">
2211
- PKA
2212
- </button>
2213
- <button class="theme-btn" (click)="applyClaveTheme()" [class.active]="currentTheme === 'clave'">
2214
- Clave
2215
- </button>
2216
- </div>
2217
- </div>
2155
+ IonRefresher,
2156
+ IonRefresherContent,
2157
+ DsAvatarComponent,
2158
+ DsLogoComponent
2159
+ ], template: `
2160
+ <!-- Fixed header at top -->
2161
+ <ion-header>
2162
+ <ion-toolbar>
2163
+ <div class="header-home">
2164
+ <!-- Whitelabel Logomark -->
2165
+ <ds-logo variant="mark" size="lg" />
2218
2166
 
2219
- <!-- Logo & Logomark Preview -->
2220
- <div class="demo-section">
2221
- <h2>Logo Preview</h2>
2222
- <div class="preview-grid">
2223
- <div class="preview-tile">
2224
- <h3>Logo</h3>
2225
- <div class="logo-preview">
2226
- <ds-logo variant="full" />
2227
- </div>
2228
- </div>
2229
- <div class="preview-tile">
2230
- <h3>Logomark</h3>
2231
- <div class="logomark-preview">
2232
- <ds-avatar-with-badge
2233
- [type]="'initials'"
2234
- [initials]="'KN'"
2235
- [size]="'md'"
2236
- [badgePosition]="'bottom-right'"
2237
- />
2238
- </div>
2239
- </div>
2240
- </div>
2167
+ <!-- Title - fades in on scroll -->
2168
+ <ion-title class="header-home__title">{{ title() }}</ion-title>
2169
+
2170
+ <!-- Avatar -->
2171
+ <div class="header-home__actions">
2172
+ <ds-avatar
2173
+ [size]="'md'"
2174
+ [type]="avatarType()"
2175
+ [initials]="avatarInitials()"
2176
+ [src]="avatarSrc()"
2177
+ [iconName]="avatarIconName()"
2178
+ (click)="handleAvatarClick()"
2179
+ style="cursor: pointer;"
2180
+ />
2181
+ </div>
2182
+ </div>
2183
+ </ion-toolbar>
2184
+ </ion-header>
2185
+
2186
+ <!-- Content with expandable header -->
2187
+ <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
2188
+ <!-- Condensed header for Ionic scroll effects -->
2189
+ @if (showCondensedHeader()) {
2190
+ <ion-header collapse="condense">
2191
+ <ion-toolbar>
2192
+ <ion-title size="large">{{ title() }}</ion-title>
2193
+ </ion-toolbar>
2194
+ </ion-header>
2195
+ }
2196
+
2197
+ <!-- Pull to refresh (only on native iOS/Android) -->
2198
+ @if (showRefresh() && isNativePlatform()) {
2199
+ <ion-refresher
2200
+ slot="fixed"
2201
+ (ionRefresh)="handleRefresh($event)"
2202
+ [pullFactor]="0.4"
2203
+ [pullMin]="80"
2204
+ [pullMax]="240"
2205
+ closeDuration="600ms">
2206
+ <ion-refresher-content
2207
+ pullingIcon="remixArrowDownS"
2208
+ refreshingSpinner="lines">
2209
+ </ion-refresher-content>
2210
+ </ion-refresher>
2211
+ }
2212
+
2213
+ <!-- Expandable header section (purple background) -->
2214
+ <div class="header-expandable">
2215
+ <div class="header-expandable-inner">
2216
+ <div class="header-expandable__text">
2217
+ <h1 class="header-expandable__title">{{ headerTitle() || title() }}</h1>
2218
+ @if (headerSubtitle()) {
2219
+ <p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
2220
+ }
2241
2221
  </div>
2242
2222
 
2243
- <!-- Brand Colors -->
2244
- <div class="demo-section">
2245
- <h2>Brand Colors</h2>
2246
- <div class="color-section">
2247
- <div class="color-swatches">
2248
- <div class="swatch swatch--primary-surface">
2249
- <div class="swatch-label">Primary Surface</div>
2250
- <div class="swatch-value">{{ whitelabelService.primarySurface() }}</div>
2251
- </div>
2252
- <div class="swatch swatch--primary-content">
2253
- <div class="swatch-label">Primary Content</div>
2254
- <div class="swatch-value">{{ whitelabelService.primaryContent() }}</div>
2255
- </div>
2256
- <div class="swatch swatch--secondary-surface">
2257
- <div class="swatch-label">Secondary Surface</div>
2258
- <div class="swatch-value">{{ whitelabelService.secondarySurface() }}</div>
2259
- </div>
2260
- <div class="swatch swatch--secondary-content">
2261
- <div class="swatch-label">Secondary Content</div>
2262
- <div class="swatch-value">{{ whitelabelService.secondaryContent() }}</div>
2263
- </div>
2264
- </div>
2265
-
2266
- <div class="color-inputs">
2267
- <div class="color-group-label">Primary</div>
2268
- <div class="color-row">
2269
- <label>Surface</label>
2270
- <input
2271
- type="color"
2272
- [(ngModel)]="customPrimarySurface"
2273
- (change)="applyCustomColors()"
2274
- />
2275
- <input
2276
- type="text"
2277
- [(ngModel)]="customPrimarySurface"
2278
- (change)="applyCustomColors()"
2279
- />
2280
- </div>
2281
- <div class="color-row">
2282
- <label>Content</label>
2283
- <input
2284
- type="color"
2285
- [(ngModel)]="customPrimaryContent"
2286
- (change)="applyCustomColors()"
2287
- />
2288
- <input
2289
- type="text"
2290
- [(ngModel)]="customPrimaryContent"
2291
- (change)="applyCustomColors()"
2292
- />
2293
- </div>
2294
-
2295
- <div class="color-group-label">Secondary</div>
2296
- <div class="color-row">
2297
- <label>Surface</label>
2298
- <input
2299
- type="color"
2300
- [(ngModel)]="customSecondarySurface"
2301
- (change)="applyCustomColors()"
2302
- />
2303
- <input
2304
- type="text"
2305
- [(ngModel)]="customSecondarySurface"
2306
- (change)="applyCustomColors()"
2307
- />
2308
- </div>
2309
- <div class="color-row">
2310
- <label>Content</label>
2311
- <input
2312
- type="color"
2313
- [(ngModel)]="customSecondaryContent"
2314
- (change)="applyCustomColors()"
2315
- />
2316
- <input
2317
- type="text"
2318
- [(ngModel)]="customSecondaryContent"
2319
- (change)="applyCustomColors()"
2320
- />
2321
- </div>
2322
- </div>
2323
- </div>
2324
- </div>
2223
+ <!-- Slot for custom header content (e.g., property tiles) -->
2224
+ <ng-content select="[header-content]"></ng-content>
2225
+ </div>
2226
+ </div>
2227
+
2228
+ <!-- Content wrapper -->
2229
+ <div class="content-wrapper">
2230
+ <div class="content-inner">
2231
+ <!-- Main page content -->
2232
+ <ng-content></ng-content>
2325
2233
  </div>
2234
+ </div>
2326
2235
  </ion-content>
2327
- `, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%;background:var(--color-background-neutral-primary, #ffffff)}.modal-header{flex-shrink:0;background:var(--color-background-neutral-primary, #ffffff);border-bottom:1px solid var(--border-color-default, #e0e0e0);padding:0 16px}ion-content,.modal-content{--background: #ffffff;flex:1;width:100%}.header-content{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px}.header-title{font-family:Brockmann,sans-serif;font-size:17px;font-weight:600;color:var(--color-text-primary, #1a1a1a);flex:1}.close-button{flex-shrink:0;border-radius:50%}.close-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important;background:var(--color-background-neutral-secondary, #f5f5f5)!important;color:var(--color-text-primary, #1a1a1a)!important}.demo-container{padding:20px;max-width:600px;margin:0 auto;width:100%}.demo-section{margin-bottom:32px}.demo-section h2{margin-bottom:16px;font-size:18px;font-weight:600;color:#333}.theme-buttons{display:flex;gap:12px;flex-wrap:wrap}.theme-btn{padding:8px 16px;border-radius:8px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .2s ease;background:#e0e0e0;color:#333}.theme-btn:active{transform:scale(.98)}.theme-btn.active{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.logo-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:var(--color-brand-secondary);border-radius:12px;min-height:80px}.logomark-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:#fff;border:1px solid #e0e0e0;border-radius:12px;min-height:80px}.preview-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.preview-tile{background:#fff;border-radius:12px}.preview-tile h3{font-size:13px;font-weight:600;color:#666;margin-top:0;margin-bottom:12px}.color-section{background:#fff;border-radius:12px}.color-swatches{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}.swatch{padding:16px;border-radius:8px;text-align:center}.swatch-label{font-size:12px;font-weight:600;margin-bottom:4px}.swatch-value{font-size:11px;opacity:.8;font-family:monospace}.swatch--primary-surface{background:var(--color-primary-surface);color:var(--color-primary-content)}.swatch--primary-content{background:var(--color-primary-content);color:var(--color-primary-surface);border:1px solid #e0e0e0}.swatch--secondary-surface{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.swatch--secondary-content{background:var(--color-secondary-content);color:var(--color-secondary-surface);border:1px solid #e0e0e0}.color-inputs{display:flex;flex-direction:column;gap:12px}.color-group-label{font-size:13px;font-weight:600;color:#333;margin-top:8px}.color-row{display:flex;align-items:center;gap:12px}.color-row label{min-width:70px;font-size:13px;color:#666}.color-row input[type=color]{width:40px;height:32px;border:none;border-radius:6px;cursor:pointer;padding:0}.color-row input[type=text]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px}@supports (padding: env(safe-area-inset-bottom)){.demo-container{padding-bottom:calc(20px + env(safe-area-inset-bottom))}}\n"] }]
2328
- }] });
2329
-
2330
- /**
2331
- * WhitelabelDemoModalService
2332
- *
2333
- * Service for displaying the whitelabel demo in a full-screen modal.
2334
- * Built on Ionic's modal system with native gestures and animations.
2335
- *
2336
- * @example
2337
- * ```typescript
2338
- * constructor(private whitelabelModal: WhitelabelDemoModalService) {}
2339
- *
2340
- * async openDemo() {
2341
- * await this.whitelabelModal.open();
2342
- * }
2343
- * ```
2344
- */
2345
- class WhitelabelDemoModalService {
2346
- modalController;
2347
- constructor(modalController) {
2348
- this.modalController = modalController;
2349
- }
2350
- /**
2351
- * Open the whitelabel demo modal
2352
- *
2353
- * @returns Promise that resolves when the modal is presented
2354
- */
2355
- async open() {
2356
- try {
2357
- console.log('[WhitelabelDemoModal] Opening...');
2358
- const modal = await this.modalController.create({
2359
- component: WhitelabelDemoModalComponent,
2360
- cssClass: 'ds-whitelabel-demo-modal',
2361
- mode: 'ios',
2362
- presentingElement: document.querySelector('ion-router-outlet') || undefined,
2363
- backdropDismiss: true,
2364
- showBackdrop: true,
2365
- animated: true,
2366
- keyboardClose: true
2367
- });
2368
- console.log('[WhitelabelDemoModal] Modal created, presenting...');
2369
- await modal.present();
2370
- console.log('[WhitelabelDemoModal] Modal presented');
2371
- }
2372
- catch (error) {
2373
- console.error('[WhitelabelDemoModal] Error opening modal:', error);
2374
- throw error;
2375
- }
2376
- }
2377
- /**
2378
- * Close the currently open whitelabel demo modal
2379
- *
2380
- * @param data Optional data to pass back when dismissing
2381
- * @returns Promise that resolves when the modal is dismissed
2382
- */
2383
- async close(data) {
2384
- return this.modalController.dismiss(data);
2385
- }
2386
- /**
2387
- * Get the top-most modal if one exists
2388
- *
2389
- * @returns Promise that resolves to the modal element or undefined
2390
- */
2391
- async getTop() {
2392
- return this.modalController.getTop();
2393
- }
2394
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable });
2395
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, providedIn: 'root' });
2396
- }
2397
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, decorators: [{
2398
- type: Injectable,
2399
- args: [{
2400
- providedIn: 'root'
2401
- }]
2402
- }], ctorParameters: () => [{ type: i1.ModalController }] });
2236
+ `, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%}:host ion-header{background:var(--color-brand-secondary);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){ion-header{display:none;height:auto}}.header-home{display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:var(--color-brand-secondary);height:72px}.header-home__title{position:absolute;left:50%;transform:translate(-50%) translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0;transition:transform .6s ease,opacity .6s ease;margin:0;padding:0;--color: white}.header-scrolled .header-home__title{opacity:1;transform:translate(-50%) translateY(0)}.header-home__actions{display:flex;align-items:center;gap:8px}.header-home__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}.logomark{height:28px;width:auto;flex-shrink:0}@media (min-width: 768px){.header-home{padding:16px 24px}.logomark{height:32px}.header-home__title{display:none}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px;overflow:hidden}ion-content::part(scroll){display:flex;flex-direction:column}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-header[collapse=condense]{display:none}@media (min-width: 768px){ion-header[collapse=condense]{display:none}}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}.header-expandable__text{margin-bottom:0}.header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}.header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){.header-expandable{padding:48px var(--content-padding-md)}.header-expandable__title{font-size:var(--font-size-3xl)}.header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{position:relative;z-index:20;padding:0;flex:1;display:flex;flex-direction:column}@media (min-width: 768px){.content-wrapper{width:100%}}.content-inner{background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary);flex:1;padding:24px 20px 32px;padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom,0px))}@media (min-width: 768px){.content-inner{border-radius:16px 16px 0 0;padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.content-inner{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.content-inner{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.content-inner{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.content-inner{padding:32px var(--content-padding-3xl)!important}}\n"] }]
2237
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { ionContent: [{
2238
+ type: ViewChild,
2239
+ args: [IonContent]
2240
+ }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], profileMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "profileMenuItems", required: false }] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], profileActionSelected: [{ type: i0.Output, args: ["profileActionSelected"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
2403
2241
 
2404
2242
  /**
2405
- * DsMobilePageMainComponent
2243
+ * DsMobilePageDetailsComponent
2406
2244
  *
2407
- * A complete mobile page layout for main/tab pages with:
2408
- * - Fixed header with logomark + title + avatar
2409
- * - Purple expandable header section (scrolls with content)
2410
- * - White rounded content wrapper
2411
- * - Pull-to-refresh support
2412
- * - Auto scroll title fade-in
2245
+ * A mobile page layout for detail/drill-down pages with:
2246
+ * - Back button header (mobile + desktop variants)
2247
+ * - White background content area
2248
+ * - Responsive padding
2413
2249
  *
2414
2250
  * @example
2415
2251
  * ```html
2416
- * <!-- Simple page -->
2417
- * <ds-mobile-page-main
2418
- * title="Inquiries"
2419
- * [avatarInitials]="'JD'">
2252
+ * <!-- Simple detail page -->
2253
+ * <ds-mobile-page-details
2254
+ * title="Property Details"
2255
+ * (back)="goBack()">
2420
2256
  * <div class="page-content">
2421
2257
  * <!-- Your content -->
2422
2258
  * </div>
2423
- * </ds-mobile-page-main>
2424
- *
2425
- * <!-- Page with custom header content -->
2426
- * <ds-mobile-page-main
2427
- * title="Home"
2428
- * headerTitle="Welcome, Lars"
2429
- * headerSubtitle="Your rental property at a glance."
2430
- * [avatarInitials]="'L'">
2431
- *
2432
- * <div header-content class="property-tiles">
2433
- * <!-- Custom header content like tiles -->
2434
- * </div>
2259
+ * </ds-mobile-page-details>
2435
2260
  *
2261
+ * <!-- With default back route -->
2262
+ * <ds-mobile-page-details
2263
+ * title="Invoice Details"
2264
+ * backRoute="/invoices">
2436
2265
  * <div class="page-content">
2437
- * <!-- Main page content -->
2266
+ * <!-- Your content -->
2438
2267
  * </div>
2439
- * </ds-mobile-page-main>
2268
+ * </ds-mobile-page-details>
2440
2269
  * ```
2441
2270
  */
2442
- class DsMobilePageMainComponent extends MobilePageBase {
2271
+ class DsMobilePageDetailsComponent extends MobilePageBase {
2272
+ navCtrl;
2443
2273
  elementRef;
2444
- ionContent;
2445
- // Platform detection
2446
- platform = inject(Platform);
2447
- modalController = inject(ModalController);
2448
- router = inject(Router);
2449
- whitelabelDemoModal = inject(WhitelabelDemoModalService);
2450
- // Computed property to check if running on native platform
2451
- isNativePlatform = computed(() => this.platform.is('ios') ||
2452
- this.platform.is('android') ||
2453
- this.platform.is('capacitor'), ...(ngDevMode ? [{ debugName: "isNativePlatform" }] : []));
2454
- // Inputs - Title
2455
- title = input.required(...(ngDevMode ? [{ debugName: "title" }] : [])); // For fixed header title
2456
- headerTitle = input('', ...(ngDevMode ? [{ debugName: "headerTitle" }] : [])); // Optional different title for expandable header
2457
- headerSubtitle = input('', ...(ngDevMode ? [{ debugName: "headerSubtitle" }] : []));
2458
- // Inputs - Avatar
2459
- avatarType = input('initials', ...(ngDevMode ? [{ debugName: "avatarType" }] : []));
2460
- avatarInitials = input('U', ...(ngDevMode ? [{ debugName: "avatarInitials" }] : []));
2461
- avatarSrc = input('', ...(ngDevMode ? [{ debugName: "avatarSrc" }] : []));
2462
- avatarIconName = input('remixUser3Line', ...(ngDevMode ? [{ debugName: "avatarIconName" }] : []));
2463
- // Inputs - Features
2464
- showRefresh = input(true, ...(ngDevMode ? [{ debugName: "showRefresh" }] : []));
2465
- showCondensedHeader = input(true, ...(ngDevMode ? [{ debugName: "showCondensedHeader" }] : []));
2466
- scrollThreshold = input(160, ...(ngDevMode ? [{ debugName: "scrollThreshold" }] : [])); // Pixels to scroll before title appears
2467
- headerFadeDistance = input(150, ...(ngDevMode ? [{ debugName: "headerFadeDistance" }] : [])); // Distance over which header fades out
2274
+ // Inputs
2275
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
2276
+ backRoute = input('', ...(ngDevMode ? [{ debugName: "backRoute" }] : [])); // Optional default back route
2468
2277
  // Outputs
2469
- avatarClick = output();
2470
- refresh = output();
2471
- scroll = output();
2472
- constructor(elementRef) {
2278
+ back = output();
2279
+ constructor(navCtrl, elementRef) {
2473
2280
  super();
2281
+ this.navCtrl = navCtrl;
2474
2282
  this.elementRef = elementRef;
2475
2283
  }
2476
- ngAfterViewInit() {
2477
- // Initial setup if needed
2478
- }
2479
2284
  /**
2480
- * Handle avatar click - opens profile actions bottom sheet
2285
+ * Handle back navigation
2286
+ *
2287
+ * By default, navigates using the provided backRoute or browser back.
2288
+ * Parent components can listen to the (back) event to override this behavior.
2289
+ *
2290
+ * @example
2291
+ * ```html
2292
+ * <!-- Default behavior: uses backRoute or browser back -->
2293
+ * <ds-mobile-page-details
2294
+ * title="Details"
2295
+ * backRoute="/home">
2296
+ * </ds-mobile-page-details>
2297
+ *
2298
+ * <!-- Custom behavior: parent handles navigation -->
2299
+ * <ds-mobile-page-details
2300
+ * title="Details"
2301
+ * (back)="customBackHandler()">
2302
+ * </ds-mobile-page-details>
2303
+ * ```
2481
2304
  */
2482
- async handleAvatarClick() {
2483
- console.log('Avatar clicked - opening profile bottom sheet');
2484
- // Emit the event for any parent listeners
2485
- this.avatarClick.emit();
2486
- const sheet = await this.modalController.create({
2487
- component: DsMobileActionsBottomSheetComponent,
2488
- componentProps: {
2489
- customActionGroups: [
2490
- {
2491
- actions: [
2492
- {
2493
- action: 'profile',
2494
- title: 'Min profil',
2495
- icon: 'remixUser3Line',
2496
- destructive: false
2497
- },
2498
- {
2499
- action: 'settings',
2500
- title: 'Indstillinger',
2501
- icon: 'remixSettings3Line',
2502
- destructive: false
2503
- },
2504
- {
2505
- action: 'whitelabel-demo',
2506
- title: 'Whitelabel Demo',
2507
- icon: 'remixPaletteLine',
2508
- destructive: false
2509
- }
2510
- ]
2511
- },
2512
- {
2513
- actions: [
2514
- {
2515
- action: 'logout',
2516
- title: 'Log ud',
2517
- icon: 'remixLogoutBoxLine',
2518
- destructive: true
2519
- }
2520
- ]
2521
- }
2522
- ]
2523
- },
2524
- // Auto-height: no breakpoints, no handle
2525
- cssClass: 'ds-bottom-sheet auto-height'
2526
- });
2527
- await sheet.present();
2528
- const result = await sheet.onWillDismiss();
2529
- if (result.data?.action) {
2530
- console.log('Profile action selected:', result.data.action);
2531
- switch (result.data.action) {
2532
- case 'logout':
2533
- console.log('Logging out...');
2534
- // TODO: Implement logout logic
2535
- break;
2536
- case 'profile':
2537
- console.log('Opening profile...');
2538
- // TODO: Navigate to profile page
2539
- break;
2540
- case 'settings':
2541
- console.log('Opening settings...');
2542
- // TODO: Navigate to settings page
2543
- break;
2544
- case 'whitelabel-demo':
2545
- console.log('Whitelabel demo selected');
2546
- await this.whitelabelDemoModal.open();
2547
- break;
2548
- }
2549
- }
2550
- }
2551
- /**
2552
- * Handle scroll events
2553
- * - Shows title in fixed header when scrolled past threshold
2554
- * - Fades out expandable header content based on scroll position
2555
- * - Emits scroll event for custom handling
2556
- */
2557
- handleScroll(event) {
2558
- const scrollTop = event.detail.scrollTop;
2559
- const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
2560
- const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
2561
- // Show title in fixed header when scrolled past threshold
2562
- if (scrollTop > this.scrollThreshold()) {
2563
- header?.classList.add('header-scrolled');
2305
+ handleBack() {
2306
+ // Add class to trigger reverse animation
2307
+ this.elementRef.nativeElement.classList.add('navigating-back');
2308
+ // Emit event for parent to optionally handle
2309
+ this.back.emit();
2310
+ // Default behavior: navigate using backRoute or browser back
2311
+ if (this.backRoute()) {
2312
+ this.navCtrl.navigateBack(this.backRoute());
2564
2313
  }
2565
2314
  else {
2566
- header?.classList.remove('header-scrolled');
2567
- }
2568
- // Fade out header-expandable content based on scroll
2569
- if (headerExpandable) {
2570
- const fadeDistance = this.headerFadeDistance();
2571
- const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
2572
- // Calculate opacity (1 to 0)
2573
- const opacity = 1 - fadeProgress;
2574
- // Calculate transform (0px to -20px upward)
2575
- const translateY = fadeProgress * -20;
2576
- // Apply styles
2577
- headerExpandable.style.opacity = opacity.toString();
2578
- headerExpandable.style.transform = `translateY(${translateY}px)`;
2579
- }
2580
- this.scroll.emit(event);
2581
- }
2582
- /**
2583
- * Handle pull-to-refresh
2584
- * Emits refresh event - parent should call event.target.complete()
2585
- */
2586
- async handleRefresh(event) {
2587
- // Haptic feedback for pull-to-refresh
2588
- try {
2589
- await Haptics.impact({ style: ImpactStyle.Medium });
2590
- }
2591
- catch {
2592
- // Fallback to Web Vibration API if Capacitor Haptics is not available
2593
- if ('vibrate' in navigator) {
2594
- navigator.vibrate(50);
2595
- }
2315
+ this.navCtrl.back();
2596
2316
  }
2597
- this.refresh.emit(event);
2598
2317
  }
2599
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageMainComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2600
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { avatarClick: "avatarClick", refresh: "refresh", scroll: "scroll" }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
2601
- <!-- Fixed header at top -->
2318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageDetailsComponent, deps: [{ token: i1.NavController }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.14", type: DsMobilePageDetailsComponent, isStandalone: true, selector: "ds-mobile-page-details", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, backRoute: { classPropertyName: "backRoute", publicName: "backRoute", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back" }, usesInheritance: true, ngImport: i0, template: `
2320
+ <!-- Mobile header - hidden on desktop -->
2602
2321
  <ion-header>
2603
2322
  <ion-toolbar>
2604
- <div class="header-home">
2605
- <!-- Whitelabel Logomark -->
2606
- <ds-logo variant="mark" size="lg" />
2607
-
2608
- <!-- Title - fades in on scroll -->
2609
- <ion-title class="header-home__title">{{ title() }}</ion-title>
2610
-
2611
- <!-- Avatar -->
2612
- <div class="header-home__actions">
2613
- <ds-avatar
2614
- [size]="'md'"
2615
- [type]="avatarType()"
2616
- [initials]="avatarInitials()"
2617
- [src]="avatarSrc()"
2618
- [iconName]="avatarIconName()"
2619
- (click)="handleAvatarClick()"
2620
- style="cursor: pointer;"
2621
- />
2622
- </div>
2323
+ <div class="header-back">
2324
+ <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2325
+ <ds-icon name="remixArrowLeftLine" size="24px" />
2326
+ </button>
2327
+ <h1 class="header-title">{{ title() }}</h1>
2623
2328
  </div>
2624
2329
  </ion-toolbar>
2625
2330
  </ion-header>
2626
2331
 
2627
- <!-- Content with expandable header -->
2628
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
2629
- <!-- Condensed header for Ionic scroll effects -->
2630
- @if (showCondensedHeader()) {
2631
- <ion-header collapse="condense">
2632
- <ion-toolbar>
2633
- <ion-title size="large">{{ title() }}</ion-title>
2634
- </ion-toolbar>
2635
- </ion-header>
2636
- }
2637
-
2638
- <!-- Pull to refresh (only on native iOS/Android) -->
2639
- @if (showRefresh() && isNativePlatform()) {
2640
- <ion-refresher
2641
- slot="fixed"
2642
- (ionRefresh)="handleRefresh($event)"
2643
- [pullFactor]="0.4"
2644
- [pullMin]="80"
2645
- [pullMax]="240"
2646
- closeDuration="600ms">
2647
- <ion-refresher-content
2648
- pullingIcon="remixArrowDownS"
2649
- refreshingSpinner="lines">
2650
- </ion-refresher-content>
2651
- </ion-refresher>
2652
- }
2653
-
2654
- <!-- Expandable header section (purple background) -->
2655
- <div class="header-expandable">
2656
- <div class="header-expandable-inner">
2657
- <div class="header-expandable__text">
2658
- <h1 class="header-expandable__title">{{ headerTitle() || title() }}</h1>
2659
- @if (headerSubtitle()) {
2660
- <p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
2661
- }
2662
- </div>
2663
-
2664
- <!-- Slot for custom header content (e.g., property tiles) -->
2665
- <ng-content select="[header-content]"></ng-content>
2666
- </div>
2332
+ <ion-content>
2333
+ <!-- Desktop header above content -->
2334
+ <div class="desktop-header">
2335
+ <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2336
+ <ds-icon name="remixArrowLeftLine" size="24px" />
2337
+ </button>
2338
+ <h1>{{ title() }}</h1>
2667
2339
  </div>
2668
2340
 
2669
- <!-- Content wrapper -->
2670
- <div class="content-wrapper">
2671
- <div class="content-inner">
2672
- <!-- Main page content -->
2673
- <ng-content></ng-content>
2674
- </div>
2341
+ <!-- Content area -->
2342
+ <div class="detail-content">
2343
+ <ng-content></ng-content>
2675
2344
  </div>
2676
2345
  </ion-content>
2677
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%}:host ion-header{background:var(--color-brand-secondary);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){ion-header{display:none;height:auto}}.header-home{display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:var(--color-brand-secondary);height:72px}.header-home__title{position:absolute;left:50%;transform:translate(-50%) translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0;transition:transform .6s ease,opacity .6s ease;margin:0;padding:0;--color: white}.header-scrolled .header-home__title{opacity:1;transform:translate(-50%) translateY(0)}.header-home__actions{display:flex;align-items:center;gap:8px}.header-home__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}.logomark{height:28px;width:auto;flex-shrink:0}@media (min-width: 768px){.header-home{padding:16px 24px}.logomark{height:32px}.header-home__title{display:none}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px;overflow:hidden}ion-content::part(scroll){display:flex;flex-direction:column}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-header[collapse=condense]{display:none}@media (min-width: 768px){ion-header[collapse=condense]{display:none}}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}.header-expandable__text{margin-bottom:0}.header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}.header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){.header-expandable{padding:48px var(--content-padding-md)}.header-expandable__title{font-size:var(--font-size-3xl)}.header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{position:relative;z-index:20;padding:0;flex:1;display:flex;flex-direction:column}@media (min-width: 768px){.content-wrapper{width:100%}}.content-inner{background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary);flex:1;padding:24px 20px 32px;padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom,0px))}@media (min-width: 768px){.content-inner{border-radius:16px 16px 0 0;padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.content-inner{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.content-inner{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.content-inner{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.content-inner{padding:32px var(--content-padding-3xl)!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
2346
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%}ion-header{background:var(--color-brand-secondary);box-shadow:none;height:64px}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 64px;height:100%;min-height:64px;padding:0}ion-header ion-toolbar::part(native){height:100%;min-height:64px;padding:0}@media (min-width: 768px){ion-header{display:none}}.header-back{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--color-brand-secondary);position:relative;height:100%;min-height:64px}.header-back .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;transition:opacity var(--transition-duration-fast) var(--ease-smooth);z-index:10}.header-back .back-button:hover{opacity:.8}.header-back .back-button:active{opacity:.6}.header-back .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:#fff;margin:0}.desktop-header{display:none}@media (min-width: 768px){.desktop-header{display:flex;align-items:center;gap:16px;padding:32px var(--content-padding-md) 24px var(--content-padding-md);max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}.desktop-header .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-color-default-primary);transition:opacity var(--transition-duration-fast) var(--ease-smooth)}.desktop-header .back-button:hover{opacity:.8}.desktop-header .back-button:active{opacity:.6}.desktop-header h1{font-size:var(--font-size-2xl);font-weight:600;margin:0;color:var(--text-color-default-primary)}}@media (min-width: 992px){.desktop-header{max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.desktop-header{max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.desktop-header{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.desktop-header{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-content{--background: var(--color-background-neutral-primary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;overflow:hidden}@media (max-width: 767px){ion-content{border-radius:24px 24px 36px 36px;animation:bottomRadiusOut .6s ease .3s forwards}:host(.navigating-back) ion-content{border-radius:24px 24px 0 0;animation:bottomRadiusIn .8s cubic-bezier(.36,.66,.04,1) forwards!important}}@keyframes bottomRadiusOut{0%{border-radius:24px 24px 36px 36px}to{border-radius:24px 24px 0 0}}@keyframes bottomRadiusIn{0%{border-radius:24px 24px 0 0}to{border-radius:24px 24px 36px 36px}}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0;animation:none}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;overscroll-behavior-y:none}.detail-content{padding:24px 20px 32px}@media (min-width: 768px){.detail-content{padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.detail-content{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.detail-content{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.detail-content{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.detail-content{padding:32px var(--content-padding-3xl)!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }] });
2678
2347
  }
2679
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageMainComponent, decorators: [{
2348
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageDetailsComponent, decorators: [{
2680
2349
  type: Component,
2681
- args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [
2350
+ args: [{ selector: 'ds-mobile-page-details', standalone: true, imports: [
2682
2351
  CommonModule,
2683
2352
  IonHeader,
2684
2353
  IonToolbar,
2685
- IonTitle,
2686
2354
  IonContent,
2687
- IonRefresher,
2688
- IonRefresherContent,
2689
- DsAvatarComponent,
2690
- DsLogoComponent
2355
+ DsIconComponent
2691
2356
  ], template: `
2692
- <!-- Fixed header at top -->
2357
+ <!-- Mobile header - hidden on desktop -->
2693
2358
  <ion-header>
2694
2359
  <ion-toolbar>
2695
- <div class="header-home">
2696
- <!-- Whitelabel Logomark -->
2697
- <ds-logo variant="mark" size="lg" />
2698
-
2699
- <!-- Title - fades in on scroll -->
2700
- <ion-title class="header-home__title">{{ title() }}</ion-title>
2701
-
2702
- <!-- Avatar -->
2703
- <div class="header-home__actions">
2704
- <ds-avatar
2705
- [size]="'md'"
2706
- [type]="avatarType()"
2707
- [initials]="avatarInitials()"
2708
- [src]="avatarSrc()"
2709
- [iconName]="avatarIconName()"
2710
- (click)="handleAvatarClick()"
2711
- style="cursor: pointer;"
2712
- />
2713
- </div>
2360
+ <div class="header-back">
2361
+ <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2362
+ <ds-icon name="remixArrowLeftLine" size="24px" />
2363
+ </button>
2364
+ <h1 class="header-title">{{ title() }}</h1>
2714
2365
  </div>
2715
2366
  </ion-toolbar>
2716
2367
  </ion-header>
2717
2368
 
2718
- <!-- Content with expandable header -->
2719
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
2720
- <!-- Condensed header for Ionic scroll effects -->
2721
- @if (showCondensedHeader()) {
2722
- <ion-header collapse="condense">
2723
- <ion-toolbar>
2724
- <ion-title size="large">{{ title() }}</ion-title>
2725
- </ion-toolbar>
2726
- </ion-header>
2727
- }
2728
-
2729
- <!-- Pull to refresh (only on native iOS/Android) -->
2730
- @if (showRefresh() && isNativePlatform()) {
2731
- <ion-refresher
2732
- slot="fixed"
2733
- (ionRefresh)="handleRefresh($event)"
2734
- [pullFactor]="0.4"
2735
- [pullMin]="80"
2736
- [pullMax]="240"
2737
- closeDuration="600ms">
2738
- <ion-refresher-content
2739
- pullingIcon="remixArrowDownS"
2740
- refreshingSpinner="lines">
2741
- </ion-refresher-content>
2742
- </ion-refresher>
2743
- }
2744
-
2745
- <!-- Expandable header section (purple background) -->
2746
- <div class="header-expandable">
2747
- <div class="header-expandable-inner">
2748
- <div class="header-expandable__text">
2749
- <h1 class="header-expandable__title">{{ headerTitle() || title() }}</h1>
2750
- @if (headerSubtitle()) {
2751
- <p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
2752
- }
2753
- </div>
2754
-
2755
- <!-- Slot for custom header content (e.g., property tiles) -->
2756
- <ng-content select="[header-content]"></ng-content>
2757
- </div>
2369
+ <ion-content>
2370
+ <!-- Desktop header above content -->
2371
+ <div class="desktop-header">
2372
+ <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2373
+ <ds-icon name="remixArrowLeftLine" size="24px" />
2374
+ </button>
2375
+ <h1>{{ title() }}</h1>
2758
2376
  </div>
2759
2377
 
2760
- <!-- Content wrapper -->
2761
- <div class="content-wrapper">
2762
- <div class="content-inner">
2763
- <!-- Main page content -->
2764
- <ng-content></ng-content>
2765
- </div>
2378
+ <!-- Content area -->
2379
+ <div class="detail-content">
2380
+ <ng-content></ng-content>
2766
2381
  </div>
2767
2382
  </ion-content>
2768
- `, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%}:host ion-header{background:var(--color-brand-secondary);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){ion-header{display:none;height:auto}}.header-home{display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:var(--color-brand-secondary);height:72px}.header-home__title{position:absolute;left:50%;transform:translate(-50%) translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0;transition:transform .6s ease,opacity .6s ease;margin:0;padding:0;--color: white}.header-scrolled .header-home__title{opacity:1;transform:translate(-50%) translateY(0)}.header-home__actions{display:flex;align-items:center;gap:8px}.header-home__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}.logomark{height:28px;width:auto;flex-shrink:0}@media (min-width: 768px){.header-home{padding:16px 24px}.logomark{height:32px}.header-home__title{display:none}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px;overflow:hidden}ion-content::part(scroll){display:flex;flex-direction:column}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-header[collapse=condense]{display:none}@media (min-width: 768px){ion-header[collapse=condense]{display:none}}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}.header-expandable__text{margin-bottom:0}.header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}.header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){.header-expandable{padding:48px var(--content-padding-md)}.header-expandable__title{font-size:var(--font-size-3xl)}.header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{position:relative;z-index:20;padding:0;flex:1;display:flex;flex-direction:column}@media (min-width: 768px){.content-wrapper{width:100%}}.content-inner{background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary);flex:1;padding:24px 20px 32px;padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom,0px))}@media (min-width: 768px){.content-inner{border-radius:16px 16px 0 0;padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.content-inner{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.content-inner{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.content-inner{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.content-inner{padding:32px var(--content-padding-3xl)!important}}\n"] }]
2769
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { ionContent: [{
2770
- type: ViewChild,
2771
- args: [IonContent]
2772
- }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
2383
+ `, styles: [":host{display:flex;flex-direction:column;height:100%}ion-header{background:var(--color-brand-secondary);box-shadow:none;height:64px}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 64px;height:100%;min-height:64px;padding:0}ion-header ion-toolbar::part(native){height:100%;min-height:64px;padding:0}@media (min-width: 768px){ion-header{display:none}}.header-back{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--color-brand-secondary);position:relative;height:100%;min-height:64px}.header-back .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;transition:opacity var(--transition-duration-fast) var(--ease-smooth);z-index:10}.header-back .back-button:hover{opacity:.8}.header-back .back-button:active{opacity:.6}.header-back .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:#fff;margin:0}.desktop-header{display:none}@media (min-width: 768px){.desktop-header{display:flex;align-items:center;gap:16px;padding:32px var(--content-padding-md) 24px var(--content-padding-md);max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}.desktop-header .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-color-default-primary);transition:opacity var(--transition-duration-fast) var(--ease-smooth)}.desktop-header .back-button:hover{opacity:.8}.desktop-header .back-button:active{opacity:.6}.desktop-header h1{font-size:var(--font-size-2xl);font-weight:600;margin:0;color:var(--text-color-default-primary)}}@media (min-width: 992px){.desktop-header{max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.desktop-header{max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.desktop-header{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.desktop-header{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-content{--background: var(--color-background-neutral-primary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;overflow:hidden}@media (max-width: 767px){ion-content{border-radius:24px 24px 36px 36px;animation:bottomRadiusOut .6s ease .3s forwards}:host(.navigating-back) ion-content{border-radius:24px 24px 0 0;animation:bottomRadiusIn .8s cubic-bezier(.36,.66,.04,1) forwards!important}}@keyframes bottomRadiusOut{0%{border-radius:24px 24px 36px 36px}to{border-radius:24px 24px 0 0}}@keyframes bottomRadiusIn{0%{border-radius:24px 24px 0 0}to{border-radius:24px 24px 36px 36px}}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0;animation:none}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;overscroll-behavior-y:none}.detail-content{padding:24px 20px 32px}@media (min-width: 768px){.detail-content{padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.detail-content{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.detail-content{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.detail-content{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.detail-content{padding:32px var(--content-padding-3xl)!important}}\n"] }]
2384
+ }], ctorParameters: () => [{ type: i1.NavController }, { type: i0.ElementRef }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], backRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "backRoute", required: false }] }], back: [{ type: i0.Output, args: ["back"] }] } });
2773
2385
 
2774
2386
  /**
2775
- * DsMobilePageDetailsComponent
2387
+ * DsMobileContentComponent
2776
2388
  *
2777
- * A mobile page layout for detail/drill-down pages with:
2778
- * - Back button header (mobile + desktop variants)
2779
- * - White background content area
2780
- * - Responsive padding
2781
- *
2782
- * @example
2783
- * ```html
2784
- * <!-- Simple detail page -->
2785
- * <ds-mobile-page-details
2786
- * title="Property Details"
2787
- * (back)="goBack()">
2788
- * <div class="page-content">
2789
- * <!-- Your content -->
2790
- * </div>
2791
- * </ds-mobile-page-details>
2792
- *
2793
- * <!-- With default back route -->
2794
- * <ds-mobile-page-details
2795
- * title="Invoice Details"
2796
- * backRoute="/invoices">
2797
- * <div class="page-content">
2798
- * <!-- Your content -->
2799
- * </div>
2800
- * </ds-mobile-page-details>
2801
- * ```
2802
- */
2803
- class DsMobilePageDetailsComponent extends MobilePageBase {
2804
- navCtrl;
2805
- elementRef;
2806
- // Inputs
2807
- title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
2808
- backRoute = input('', ...(ngDevMode ? [{ debugName: "backRoute" }] : [])); // Optional default back route
2809
- // Outputs
2810
- back = output();
2811
- constructor(navCtrl, elementRef) {
2812
- super();
2813
- this.navCtrl = navCtrl;
2814
- this.elementRef = elementRef;
2815
- }
2816
- /**
2817
- * Handle back navigation
2818
- *
2819
- * By default, navigates using the provided backRoute or browser back.
2820
- * Parent components can listen to the (back) event to override this behavior.
2821
- *
2822
- * @example
2823
- * ```html
2824
- * <!-- Default behavior: uses backRoute or browser back -->
2825
- * <ds-mobile-page-details
2826
- * title="Details"
2827
- * backRoute="/home">
2828
- * </ds-mobile-page-details>
2829
- *
2830
- * <!-- Custom behavior: parent handles navigation -->
2831
- * <ds-mobile-page-details
2832
- * title="Details"
2833
- * (back)="customBackHandler()">
2834
- * </ds-mobile-page-details>
2835
- * ```
2836
- */
2837
- handleBack() {
2838
- // Add class to trigger reverse animation
2839
- this.elementRef.nativeElement.classList.add('navigating-back');
2840
- // Emit event for parent to optionally handle
2841
- this.back.emit();
2842
- // Default behavior: navigate using backRoute or browser back
2843
- if (this.backRoute()) {
2844
- this.navCtrl.navigateBack(this.backRoute());
2845
- }
2846
- else {
2847
- this.navCtrl.back();
2848
- }
2849
- }
2850
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageDetailsComponent, deps: [{ token: i1.NavController }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2851
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.14", type: DsMobilePageDetailsComponent, isStandalone: true, selector: "ds-mobile-page-details", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, backRoute: { classPropertyName: "backRoute", publicName: "backRoute", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back" }, usesInheritance: true, ngImport: i0, template: `
2852
- <!-- Mobile header - hidden on desktop -->
2853
- <ion-header>
2854
- <ion-toolbar>
2855
- <div class="header-back">
2856
- <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2857
- <ds-icon name="remixArrowLeftLine" size="24px" />
2858
- </button>
2859
- <h1 class="header-title">{{ title() }}</h1>
2860
- </div>
2861
- </ion-toolbar>
2862
- </ion-header>
2863
-
2864
- <ion-content>
2865
- <!-- Desktop header above content -->
2866
- <div class="desktop-header">
2867
- <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2868
- <ds-icon name="remixArrowLeftLine" size="24px" />
2869
- </button>
2870
- <h1>{{ title() }}</h1>
2871
- </div>
2872
-
2873
- <!-- Content area -->
2874
- <div class="detail-content">
2875
- <ng-content></ng-content>
2876
- </div>
2877
- </ion-content>
2878
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%}ion-header{background:var(--color-brand-secondary);box-shadow:none;height:64px}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 64px;height:100%;min-height:64px;padding:0}ion-header ion-toolbar::part(native){height:100%;min-height:64px;padding:0}@media (min-width: 768px){ion-header{display:none}}.header-back{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--color-brand-secondary);position:relative;height:100%;min-height:64px}.header-back .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;transition:opacity var(--transition-duration-fast) var(--ease-smooth);z-index:10}.header-back .back-button:hover{opacity:.8}.header-back .back-button:active{opacity:.6}.header-back .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:#fff;margin:0}.desktop-header{display:none}@media (min-width: 768px){.desktop-header{display:flex;align-items:center;gap:16px;padding:32px var(--content-padding-md) 24px var(--content-padding-md);max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}.desktop-header .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-color-default-primary);transition:opacity var(--transition-duration-fast) var(--ease-smooth)}.desktop-header .back-button:hover{opacity:.8}.desktop-header .back-button:active{opacity:.6}.desktop-header h1{font-size:var(--font-size-2xl);font-weight:600;margin:0;color:var(--text-color-default-primary)}}@media (min-width: 992px){.desktop-header{max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.desktop-header{max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.desktop-header{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.desktop-header{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-content{--background: var(--color-background-neutral-primary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;overflow:hidden}@media (max-width: 767px){ion-content{border-radius:24px 24px 36px 36px;animation:bottomRadiusOut .6s ease .3s forwards}:host(.navigating-back) ion-content{border-radius:24px 24px 0 0;animation:bottomRadiusIn .8s cubic-bezier(.36,.66,.04,1) forwards!important}}@keyframes bottomRadiusOut{0%{border-radius:24px 24px 36px 36px}to{border-radius:24px 24px 0 0}}@keyframes bottomRadiusIn{0%{border-radius:24px 24px 0 0}to{border-radius:24px 24px 36px 36px}}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0;animation:none}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;overscroll-behavior-y:none}.detail-content{padding:24px 20px 32px}@media (min-width: 768px){.detail-content{padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.detail-content{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.detail-content{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.detail-content{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.detail-content{padding:32px var(--content-padding-3xl)!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }] });
2879
- }
2880
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsMobilePageDetailsComponent, decorators: [{
2881
- type: Component,
2882
- args: [{ selector: 'ds-mobile-page-details', standalone: true, imports: [
2883
- CommonModule,
2884
- IonHeader,
2885
- IonToolbar,
2886
- IonContent,
2887
- DsIconComponent
2888
- ], template: `
2889
- <!-- Mobile header - hidden on desktop -->
2890
- <ion-header>
2891
- <ion-toolbar>
2892
- <div class="header-back">
2893
- <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2894
- <ds-icon name="remixArrowLeftLine" size="24px" />
2895
- </button>
2896
- <h1 class="header-title">{{ title() }}</h1>
2897
- </div>
2898
- </ion-toolbar>
2899
- </ion-header>
2900
-
2901
- <ion-content>
2902
- <!-- Desktop header above content -->
2903
- <div class="desktop-header">
2904
- <button class="back-button" (click)="handleBack()" [attr.aria-label]="'Go back'">
2905
- <ds-icon name="remixArrowLeftLine" size="24px" />
2906
- </button>
2907
- <h1>{{ title() }}</h1>
2908
- </div>
2909
-
2910
- <!-- Content area -->
2911
- <div class="detail-content">
2912
- <ng-content></ng-content>
2913
- </div>
2914
- </ion-content>
2915
- `, styles: [":host{display:flex;flex-direction:column;height:100%}ion-header{background:var(--color-brand-secondary);box-shadow:none;height:64px}ion-header ion-toolbar{--background: var(--color-brand-secondary);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 64px;height:100%;min-height:64px;padding:0}ion-header ion-toolbar::part(native){height:100%;min-height:64px;padding:0}@media (min-width: 768px){ion-header{display:none}}.header-back{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--color-brand-secondary);position:relative;height:100%;min-height:64px}.header-back .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;transition:opacity var(--transition-duration-fast) var(--ease-smooth);z-index:10}.header-back .back-button:hover{opacity:.8}.header-back .back-button:active{opacity:.6}.header-back .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:#fff;margin:0}.desktop-header{display:none}@media (min-width: 768px){.desktop-header{display:flex;align-items:center;gap:16px;padding:32px var(--content-padding-md) 24px var(--content-padding-md);max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}.desktop-header .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-color-default-primary);transition:opacity var(--transition-duration-fast) var(--ease-smooth)}.desktop-header .back-button:hover{opacity:.8}.desktop-header .back-button:active{opacity:.6}.desktop-header h1{font-size:var(--font-size-2xl);font-weight:600;margin:0;color:var(--text-color-default-primary)}}@media (min-width: 992px){.desktop-header{max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.desktop-header{max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.desktop-header{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.desktop-header{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-content{--background: var(--color-background-neutral-primary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;overflow:hidden}@media (max-width: 767px){ion-content{border-radius:24px 24px 36px 36px;animation:bottomRadiusOut .6s ease .3s forwards}:host(.navigating-back) ion-content{border-radius:24px 24px 0 0;animation:bottomRadiusIn .8s cubic-bezier(.36,.66,.04,1) forwards!important}}@keyframes bottomRadiusOut{0%{border-radius:24px 24px 36px 36px}to{border-radius:24px 24px 0 0}}@keyframes bottomRadiusIn{0%{border-radius:24px 24px 0 0}to{border-radius:24px 24px 36px 36px}}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0;animation:none}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;overscroll-behavior-y:none}.detail-content{padding:24px 20px 32px}@media (min-width: 768px){.detail-content{padding:32px var(--content-padding-md)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));margin:0 auto;width:100%}}@media (min-width: 992px){.detail-content{padding:32px var(--content-padding-lg)!important;max-width:calc(var(--content-max-width-md) + (var(--content-padding-md) * 2))}}@media (min-width: 1440px){.detail-content{padding:32px var(--content-padding-xl)!important;max-width:calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2))}}@media (min-width: 1768px){.detail-content{padding:32px var(--content-padding-2xl)!important}}@media (min-width: 1920px){.detail-content{padding:32px var(--content-padding-3xl)!important}}\n"] }]
2916
- }], ctorParameters: () => [{ type: i1.NavController }, { type: i0.ElementRef }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], backRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "backRoute", required: false }] }], back: [{ type: i0.Output, args: ["back"] }] } });
2917
-
2918
- /**
2919
- * DsMobileContentComponent
2920
- *
2921
- * Main content container for mobile pages with flexible layout options.
2922
- * Provides consistent spacing and layout patterns.
2389
+ * Main content container for mobile pages with flexible layout options.
2390
+ * Provides consistent spacing and layout patterns.
2923
2391
  *
2924
2392
  * @example
2925
2393
  * ```html
@@ -4720,6 +4188,7 @@ class DsMobileTabBarComponent {
4720
4188
  routerSubscription;
4721
4189
  router;
4722
4190
  modalController = inject(ModalController);
4191
+ userService = inject(UserService);
4723
4192
  constructor(elementRef) {
4724
4193
  this.elementRef = elementRef;
4725
4194
  // Inject Router optionally
@@ -5168,15 +4637,17 @@ class DsMobileTabBarComponent {
5168
4637
  async handleAvatarClick() {
5169
4638
  // Emit the basic click event (for backwards compatibility)
5170
4639
  this.avatarClick.emit();
4640
+ // Use input if provided, otherwise fall back to service
4641
+ const menuItems = this.profileMenuItems || this.userService.profileMenuItems();
5171
4642
  // If no menu items configured, just emit and return
5172
- if (!this.profileMenuItems || this.profileMenuItems.length === 0) {
4643
+ if (!menuItems || menuItems.length === 0) {
5173
4644
  return;
5174
4645
  }
5175
4646
  // Open the bottom sheet with configured menu items
5176
4647
  const sheet = await this.modalController.create({
5177
4648
  component: DsMobileActionsBottomSheetComponent,
5178
4649
  componentProps: {
5179
- customActionGroups: this.profileMenuItems
4650
+ customActionGroups: menuItems
5180
4651
  },
5181
4652
  // Auto-height: no breakpoints, no handle
5182
4653
  cssClass: 'ds-bottom-sheet auto-height'
@@ -9565,41 +9036,214 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
9565
9036
  args: ['click']
9566
9037
  }] } });
9567
9038
 
9568
- // Mobile Page Components
9569
-
9570
9039
  /**
9571
- * User service for managing current user data globally
9040
+ * DsTextInputComponent
9041
+ *
9042
+ * Mobile-first text input field component following the design system.
9043
+ * Supports email, phone, text, and other input types.
9044
+ *
9045
+ * Features:
9046
+ * - All design system states (default, hover, focus, error, disabled)
9047
+ * - Validation error state with destructive border color
9048
+ * - Automatic error clearing when input becomes valid (configurable)
9049
+ * - Built-in validation based on input type or custom validator function
9050
+ * - Accessible with proper ARIA attributes
9051
+ * - ControlValueAccessor for Angular forms integration
9052
+ *
9053
+ * @example
9054
+ * ```html
9055
+ * <!-- Basic usage -->
9056
+ * <ds-text-input
9057
+ * type="email"
9058
+ * placeholder="Enter your email"
9059
+ * [(ngModel)]="email">
9060
+ * </ds-text-input>
9061
+ *
9062
+ * <!-- With validation error and auto-clear -->
9063
+ * <ds-text-input
9064
+ * type="email"
9065
+ * placeholder="Enter your email"
9066
+ * [hasError]="emailInvalid"
9067
+ * errorMessage="Please enter a valid email"
9068
+ * [autoClearError]="true"
9069
+ * (errorCleared)="emailInvalid = false"
9070
+ * [(ngModel)]="email">
9071
+ * </ds-text-input>
9072
+ *
9073
+ * <!-- With custom validator -->
9074
+ * <ds-text-input
9075
+ * type="text"
9076
+ * placeholder="Enter phone number"
9077
+ * [validator]="phoneValidator"
9078
+ * [hasError]="phoneInvalid"
9079
+ * (errorCleared)="phoneInvalid = false"
9080
+ * [(ngModel)]="phone">
9081
+ * </ds-text-input>
9082
+ * ```
9572
9083
  */
9573
- class UserService {
9574
- // User avatar configuration
9575
- _avatarInitials = signal('LM', ...(ngDevMode ? [{ debugName: "_avatarInitials" }] : []));
9576
- _avatarType = signal('initials', ...(ngDevMode ? [{ debugName: "_avatarType" }] : []));
9577
- _avatarSrc = signal('', ...(ngDevMode ? [{ debugName: "_avatarSrc" }] : []));
9578
- // Readonly computed values
9579
- avatarInitials = this._avatarInitials.asReadonly();
9580
- avatarType = this._avatarType.asReadonly();
9581
- avatarSrc = this._avatarSrc.asReadonly();
9084
+ class DsTextInputComponent {
9085
+ // Input properties
9086
+ type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : []));
9087
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
9088
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9089
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
9090
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
9091
+ hasError = input(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
9092
+ errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
9093
+ autocomplete = input('', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
9094
+ inputmode = input(undefined, ...(ngDevMode ? [{ debugName: "inputmode" }] : []));
9095
+ autoClearError = input(true, ...(ngDevMode ? [{ debugName: "autoClearError" }] : []));
9096
+ validator = input(null, ...(ngDevMode ? [{ debugName: "validator" }] : []));
9097
+ // Output events
9098
+ valueChange = output();
9099
+ blur = output();
9100
+ focus = output();
9101
+ errorCleared = output();
9102
+ // Internal state
9103
+ _value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : []));
9104
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
9105
+ // Generate unique ID for accessibility
9106
+ inputId = `ds-text-input-${Math.random().toString(36).substring(2, 9)}`;
9107
+ // ControlValueAccessor implementation
9108
+ onChange = (value) => { };
9109
+ onTouched = () => { };
9110
+ onInput(event) {
9111
+ const target = event.target;
9112
+ const newValue = target.value;
9113
+ this._value.set(newValue);
9114
+ this.onChange(newValue);
9115
+ this.valueChange.emit(newValue);
9116
+ // Auto-clear error if input becomes valid
9117
+ if (this.autoClearError() && this.hasError()) {
9118
+ const isValid = this.validateInput(newValue);
9119
+ if (isValid) {
9120
+ this.errorCleared.emit();
9121
+ }
9122
+ }
9123
+ }
9582
9124
  /**
9583
- * Update avatar configuration
9125
+ * Validates the input value based on type or custom validator
9584
9126
  */
9585
- setAvatarInitials(initials) {
9586
- this._avatarInitials.set(initials);
9127
+ validateInput(value) {
9128
+ // Use custom validator if provided
9129
+ const customValidator = this.validator();
9130
+ if (customValidator) {
9131
+ return customValidator(value);
9132
+ }
9133
+ // Use built-in validation based on input type
9134
+ const inputType = this.type();
9135
+ const inputElement = document.createElement('input');
9136
+ inputElement.type = inputType;
9137
+ inputElement.value = value;
9138
+ // For email type, use HTML5 validation
9139
+ if (inputType === 'email') {
9140
+ return inputElement.validity.valid;
9141
+ }
9142
+ // For required fields, check if value exists
9143
+ if (this.required() && !value.trim()) {
9144
+ return false;
9145
+ }
9146
+ // Default: valid if HTML5 validation passes
9147
+ return inputElement.validity.valid;
9587
9148
  }
9588
- setAvatarType(type) {
9589
- this._avatarType.set(type);
9149
+ onBlur() {
9150
+ this.onTouched();
9590
9151
  }
9591
- setAvatarSrc(src) {
9592
- this._avatarSrc.set(src);
9152
+ onFocus() {
9153
+ // Focus event can be emitted if needed
9593
9154
  }
9594
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
9595
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, providedIn: 'root' });
9155
+ // ControlValueAccessor methods
9156
+ writeValue(value) {
9157
+ this._value.set(value || '');
9158
+ }
9159
+ registerOnChange(fn) {
9160
+ this.onChange = fn;
9161
+ }
9162
+ registerOnTouched(fn) {
9163
+ this.onTouched = fn;
9164
+ }
9165
+ setDisabledState(isDisabled) {
9166
+ // Angular forms will handle this via the disabled input
9167
+ }
9168
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsTextInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9169
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: DsTextInputComponent, isStandalone: true, selector: "ds-text-input", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hasError: { classPropertyName: "hasError", publicName: "hasError", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, autoClearError: { classPropertyName: "autoClearError", publicName: "autoClearError", isSignal: true, isRequired: false, transformFunction: null }, validator: { classPropertyName: "validator", publicName: "validator", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", errorCleared: "errorCleared" }, providers: [
9170
+ {
9171
+ provide: NG_VALUE_ACCESSOR,
9172
+ useExisting: forwardRef(() => DsTextInputComponent),
9173
+ multi: true
9174
+ }
9175
+ ], ngImport: i0, template: `
9176
+ <div class="text-input-wrapper">
9177
+ <input
9178
+ [type]="type()"
9179
+ [placeholder]="placeholder()"
9180
+ [value]="value()"
9181
+ [disabled]="disabled()"
9182
+ [readonly]="readonly()"
9183
+ [required]="required()"
9184
+ [autocomplete]="autocomplete() || null"
9185
+ [attr.inputmode]="inputmode() || null"
9186
+ [class.error]="hasError()"
9187
+ [attr.aria-invalid]="hasError()"
9188
+ [attr.aria-describedby]="hasError() && errorMessage() ? 'error-' + inputId : null"
9189
+ class="text-input"
9190
+ (input)="onInput($event)"
9191
+ (blur)="onBlur()"
9192
+ (focus)="onFocus()"
9193
+ [id]="inputId">
9194
+
9195
+ @if (hasError() && errorMessage()) {
9196
+ <div
9197
+ class="error-message"
9198
+ [id]="'error-' + inputId"
9199
+ role="alert">
9200
+ {{ errorMessage() }}
9201
+ </div>
9202
+ }
9203
+ </div>
9204
+ `, isInline: true, styles: [":host{display:block;width:100%}.text-input-wrapper{position:relative;width:100%}.text-input{width:100%;height:48px;padding:0 16px;box-sizing:border-box;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-size:var(--font-size-base);font-weight:400;line-height:1.4;color:var(--text-color-default-primary);background-color:var(--color-background-neutral-primary);border:1px solid var(--border-color-default);border-radius:8px;transition:border-color var(--transition-duration-fast) var(--ease-smooth),background-color var(--transition-duration-fast) var(--ease-smooth),box-shadow var(--transition-duration-fast) var(--ease-smooth);outline:none;-webkit-appearance:none;appearance:none}.text-input::placeholder{color:var(--text-color-default-tertiary)}.text-input:hover:not(:disabled):not(:focus){border-color:var(--border-color-default);background-color:var(--color-background-neutral-primary-hover)}.text-input:focus{border-color:var(--color-brand-base);background-color:var(--color-background-neutral-primary);box-shadow:0 0 0 3px var(--outline-color-default)}.text-input.error{border-color:var(--color-destructive-base)}.text-input.error:focus{border-color:var(--color-destructive-base);box-shadow:0 0 0 3px #dc26261a}.text-input:disabled{background-color:var(--color-background-neutral-disabled);border-color:var(--border-color-default);color:var(--text-color-default-disabled);cursor:not-allowed}.text-input:disabled::placeholder{color:var(--text-color-default-disabled)}.error-message{margin-top:8px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-destructive-base)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
9596
9205
  }
9597
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, decorators: [{
9598
- type: Injectable,
9599
- args: [{
9600
- providedIn: 'root'
9601
- }]
9602
- }] });
9206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsTextInputComponent, decorators: [{
9207
+ type: Component,
9208
+ args: [{ selector: 'ds-text-input', standalone: true, imports: [CommonModule, FormsModule], providers: [
9209
+ {
9210
+ provide: NG_VALUE_ACCESSOR,
9211
+ useExisting: forwardRef(() => DsTextInputComponent),
9212
+ multi: true
9213
+ }
9214
+ ], template: `
9215
+ <div class="text-input-wrapper">
9216
+ <input
9217
+ [type]="type()"
9218
+ [placeholder]="placeholder()"
9219
+ [value]="value()"
9220
+ [disabled]="disabled()"
9221
+ [readonly]="readonly()"
9222
+ [required]="required()"
9223
+ [autocomplete]="autocomplete() || null"
9224
+ [attr.inputmode]="inputmode() || null"
9225
+ [class.error]="hasError()"
9226
+ [attr.aria-invalid]="hasError()"
9227
+ [attr.aria-describedby]="hasError() && errorMessage() ? 'error-' + inputId : null"
9228
+ class="text-input"
9229
+ (input)="onInput($event)"
9230
+ (blur)="onBlur()"
9231
+ (focus)="onFocus()"
9232
+ [id]="inputId">
9233
+
9234
+ @if (hasError() && errorMessage()) {
9235
+ <div
9236
+ class="error-message"
9237
+ [id]="'error-' + inputId"
9238
+ role="alert">
9239
+ {{ errorMessage() }}
9240
+ </div>
9241
+ }
9242
+ </div>
9243
+ `, styles: [":host{display:block;width:100%}.text-input-wrapper{position:relative;width:100%}.text-input{width:100%;height:48px;padding:0 16px;box-sizing:border-box;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-size:var(--font-size-base);font-weight:400;line-height:1.4;color:var(--text-color-default-primary);background-color:var(--color-background-neutral-primary);border:1px solid var(--border-color-default);border-radius:8px;transition:border-color var(--transition-duration-fast) var(--ease-smooth),background-color var(--transition-duration-fast) var(--ease-smooth),box-shadow var(--transition-duration-fast) var(--ease-smooth);outline:none;-webkit-appearance:none;appearance:none}.text-input::placeholder{color:var(--text-color-default-tertiary)}.text-input:hover:not(:disabled):not(:focus){border-color:var(--border-color-default);background-color:var(--color-background-neutral-primary-hover)}.text-input:focus{border-color:var(--color-brand-base);background-color:var(--color-background-neutral-primary);box-shadow:0 0 0 3px var(--outline-color-default)}.text-input.error{border-color:var(--color-destructive-base)}.text-input.error:focus{border-color:var(--color-destructive-base);box-shadow:0 0 0 3px #dc26261a}.text-input:disabled{background-color:var(--color-background-neutral-disabled);border-color:var(--border-color-default);color:var(--text-color-default-disabled);cursor:not-allowed}.text-input:disabled::placeholder{color:var(--text-color-default-disabled)}.error-message{margin-top:8px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-destructive-base)}\n"] }]
9244
+ }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hasError: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasError", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], autoClearError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoClearError", required: false }] }], validator: [{ type: i0.Input, args: [{ isSignal: true, alias: "validator", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], blur: [{ type: i0.Output, args: ["blur"] }], focus: [{ type: i0.Output, args: ["focus"] }], errorCleared: [{ type: i0.Output, args: ["errorCleared"] }] } });
9245
+
9246
+ // Mobile Page Components
9603
9247
 
9604
9248
  class MobileCommunityPageComponent {
9605
9249
  router;
@@ -10246,7 +9890,7 @@ class MobileCommunityPageComponent {
10246
9890
  </div>
10247
9891
  </ds-mobile-content>
10248
9892
  </ds-mobile-page-main>
10249
- `, isInline: true, styles: [".post-feed{display:flex;flex-direction:column;max-width:640px}.post-list-wrapper{display:flex;flex-direction:column}.pinned-posts-section{margin:-12px -12px 12px;padding:0 12px 12px;box-shadow:var(--box-shadow-sm);border-radius:16px;border:1px solid var(--border-color-default)}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance"], outputs: ["avatarClick", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "clickable"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout"], outputs: ["fileClick"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "author", "maxVisible"], outputs: ["photoClick"] }] });
9893
+ `, isInline: true, styles: [".post-feed{display:flex;flex-direction:column;max-width:640px}.post-list-wrapper{display:flex;flex-direction:column}.pinned-posts-section{margin:-12px -12px 12px;padding:0 12px 12px;box-shadow:var(--box-shadow-sm);border-radius:16px;border:1px solid var(--border-color-default)}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "clickable"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout"], outputs: ["fileClick"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "author", "maxVisible"], outputs: ["photoClick"] }] });
10250
9894
  }
10251
9895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileCommunityPageComponent, decorators: [{
10252
9896
  type: Component,
@@ -10775,7 +10419,7 @@ class MobileHandbookPageComponent {
10775
10419
  </ds-mobile-content-section>
10776
10420
  </ds-mobile-content>
10777
10421
  </ds-mobile-page-main>
10778
- `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance"], outputs: ["avatarClick", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileContentSectionComponent, selector: "ds-mobile-content-section" }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "iconName", "itemCount", "label", "items", "loading", "error"] }] });
10422
+ `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileContentSectionComponent, selector: "ds-mobile-content-section" }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "iconName", "itemCount", "label", "items", "loading", "error"] }] });
10779
10423
  }
10780
10424
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileHandbookPageComponent, decorators: [{
10781
10425
  type: Component,
@@ -10966,7 +10610,7 @@ class MobileHomePageComponent {
10966
10610
  </ds-mobile-content-section>
10967
10611
  </ds-mobile-content>
10968
10612
  </ds-mobile-page-main>
10969
- `, isInline: true, styles: [".grey-box{height:120px;border-radius:12px;background:var(--color-background-neutral-tertiary);flex:1}.grey-box.clickable{background:var(--color-background-brand);cursor:pointer;transition:transform var(--transition-duration-fast) var(--ease-smooth)}.grey-box.clickable:active{transform:scale(.98)}\n"], dependencies: [{ kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance"], outputs: ["avatarClick", "refresh", "scroll"] }, { kind: "component", type: DsMobileHeaderContentComponent, selector: "ds-mobile-header-content" }, { kind: "component", type: DsMobileHeaderContentTileComponent, selector: "ds-mobile-header-content-tile" }, { kind: "component", type: TileIconComponent, selector: "tile-icon" }, { kind: "component", type: TileContentComponent, selector: "tile-content" }, { kind: "component", type: TileLabelComponent, selector: "tile-label" }, { kind: "component", type: TileValueComponent, selector: "tile-value" }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileContentSectionComponent, selector: "ds-mobile-content-section" }, { kind: "component", type: SectionHeaderComponent, selector: "section-header", inputs: ["width"] }, { kind: "component", type: ContentRowComponent, selector: "content-row" }] });
10613
+ `, isInline: true, styles: [".grey-box{height:120px;border-radius:12px;background:var(--color-background-neutral-tertiary);flex:1}.grey-box.clickable{background:var(--color-background-brand);cursor:pointer;transition:transform var(--transition-duration-fast) var(--ease-smooth)}.grey-box.clickable:active{transform:scale(.98)}\n"], dependencies: [{ kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileHeaderContentComponent, selector: "ds-mobile-header-content" }, { kind: "component", type: DsMobileHeaderContentTileComponent, selector: "ds-mobile-header-content-tile" }, { kind: "component", type: TileIconComponent, selector: "tile-icon" }, { kind: "component", type: TileContentComponent, selector: "tile-content" }, { kind: "component", type: TileLabelComponent, selector: "tile-label" }, { kind: "component", type: TileValueComponent, selector: "tile-value" }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileContentSectionComponent, selector: "ds-mobile-content-section" }, { kind: "component", type: SectionHeaderComponent, selector: "section-header", inputs: ["width"] }, { kind: "component", type: ContentRowComponent, selector: "content-row" }] });
10970
10614
  }
10971
10615
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileHomePageComponent, decorators: [{
10972
10616
  type: Component,
@@ -11294,7 +10938,7 @@ class MobileInquiriesPageComponent {
11294
10938
  </div>
11295
10939
  </ds-mobile-content>
11296
10940
  </ds-mobile-page-main>
11297
- `, isInline: true, styles: [".inquiries-container{display:flex;flex-direction:column;max-width:640px}.inquiry-list-wrapper{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance"], outputs: ["avatarClick", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "clickable", "showChevron"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }] });
10941
+ `, isInline: true, styles: [".inquiries-container{display:flex;flex-direction:column;max-width:640px}.inquiry-list-wrapper{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileContentComponent, selector: "ds-mobile-content", inputs: ["layout"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "clickable", "showChevron"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }] });
11298
10942
  }
11299
10943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiriesPageComponent, decorators: [{
11300
10944
  type: Component,
@@ -11321,600 +10965,1187 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
11321
10965
  </ds-mobile-inline-tabs>
11322
10966
  </div>
11323
10967
 
11324
- <ds-mobile-content>
11325
- <div class="inquiries-container">
11326
- @if (filteredInquiries().length > 0) {
11327
- <div class="inquiry-list-wrapper">
11328
- @for (inquiry of filteredInquiries(); track inquiry.id; let idx = $index) {
11329
- <ds-mobile-interactive-list-item-inquiry
11330
- [title]="inquiry.title"
11331
- [description]="inquiry.description"
11332
- [status]="inquiry.status"
11333
- [timestamp]="inquiry.timestamp"
11334
- [iconName]="getInquiryIcon(inquiry.category)"
11335
- [clickable]="true"
11336
- [showChevron]="false"
11337
- (inquiryClick)="openInquiryDetail(inquiry.id)"
11338
- (longPress)="showInquiryActions(inquiry.id)">
11339
- </ds-mobile-interactive-list-item-inquiry>
11340
-
11341
- }
11342
- </div>
11343
- } @else {
11344
- <!-- Empty state -->
11345
- <div class="empty-state">
11346
- <ds-icon name="remixInboxLine" size="48px" color="tertiary" />
11347
- <h3 class="empty-state-title">Ingen henvendelser endnu</h3>
11348
- <p class="empty-state-description">
11349
- @if (filterStatus() === 'open') {
11350
- Du har ingen åbne henvendelser
11351
- } @else if (filterStatus() === 'closed') {
11352
- Du har ingen lukkede henvendelser
11353
- } @else {
11354
- Du har ikke oprettet nogen henvendelser endnu
11355
- }
11356
- </p>
11357
- </div>
11358
- }
10968
+ <ds-mobile-content>
10969
+ <div class="inquiries-container">
10970
+ @if (filteredInquiries().length > 0) {
10971
+ <div class="inquiry-list-wrapper">
10972
+ @for (inquiry of filteredInquiries(); track inquiry.id; let idx = $index) {
10973
+ <ds-mobile-interactive-list-item-inquiry
10974
+ [title]="inquiry.title"
10975
+ [description]="inquiry.description"
10976
+ [status]="inquiry.status"
10977
+ [timestamp]="inquiry.timestamp"
10978
+ [iconName]="getInquiryIcon(inquiry.category)"
10979
+ [clickable]="true"
10980
+ [showChevron]="false"
10981
+ (inquiryClick)="openInquiryDetail(inquiry.id)"
10982
+ (longPress)="showInquiryActions(inquiry.id)">
10983
+ </ds-mobile-interactive-list-item-inquiry>
10984
+
10985
+ }
10986
+ </div>
10987
+ } @else {
10988
+ <!-- Empty state -->
10989
+ <div class="empty-state">
10990
+ <ds-icon name="remixInboxLine" size="48px" color="tertiary" />
10991
+ <h3 class="empty-state-title">Ingen henvendelser endnu</h3>
10992
+ <p class="empty-state-description">
10993
+ @if (filterStatus() === 'open') {
10994
+ Du har ingen åbne henvendelser
10995
+ } @else if (filterStatus() === 'closed') {
10996
+ Du har ingen lukkede henvendelser
10997
+ } @else {
10998
+ Du har ikke oprettet nogen henvendelser endnu
10999
+ }
11000
+ </p>
11001
+ </div>
11002
+ }
11003
+ </div>
11004
+ </ds-mobile-content>
11005
+ </ds-mobile-page-main>
11006
+ `, styles: [".inquiries-container{display:flex;flex-direction:column;max-width:640px}.inquiry-list-wrapper{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"] }]
11007
+ }], ctorParameters: () => [{ type: UserService }, { type: i1.NavController }] });
11008
+
11009
+ class MobileInquiryDetailPageComponent extends MobilePageBase {
11010
+ userService;
11011
+ navCtrl;
11012
+ elementRef;
11013
+ ionContent;
11014
+ // Platform detection
11015
+ platform = inject(Platform);
11016
+ // Computed property to check if running on native platform
11017
+ isNativePlatform = computed(() => this.platform.is('ios') ||
11018
+ this.platform.is('android') ||
11019
+ this.platform.is('capacitor'), ...(ngDevMode ? [{ debugName: "isNativePlatform" }] : []));
11020
+ inquiryTitle = 'Tørretumbler virker ikke';
11021
+ activeTab = signal('activity', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
11022
+ tabItems = [
11023
+ { id: 'activity', label: 'Aktivitet' },
11024
+ { id: 'messages', label: 'Beskeder', badge: 0 },
11025
+ { id: 'details', label: 'Detaljer' }
11026
+ ];
11027
+ activities = [
11028
+ {
11029
+ id: '1',
11030
+ type: 'assignment',
11031
+ actor: 'Martin Smith',
11032
+ actorInitials: 'MS',
11033
+ title: 'er blevet tildelt som din dedikerede tekniker.',
11034
+ timestamp: '2 dage siden',
11035
+ date: '28. feb 2025',
11036
+ iconName: 'remixUserAddLine'
11037
+ },
11038
+ {
11039
+ id: '2',
11040
+ type: 'assignment',
11041
+ actor: 'Ricki Meihlen',
11042
+ actorInitials: 'RM',
11043
+ title: 'er blevet tildelt til at håndtere din henvendelse.',
11044
+ timestamp: '8 dage siden',
11045
+ date: '22. feb 2025',
11046
+ iconName: 'remixUserLine'
11047
+ },
11048
+ {
11049
+ id: '3',
11050
+ type: 'creation',
11051
+ title: 'Henvendelse oprettet',
11052
+ timestamp: '8 dage siden',
11053
+ date: '22. feb 2025',
11054
+ iconName: 'remixAddCircleLine'
11055
+ }
11056
+ ];
11057
+ messageThreads = [
11058
+ {
11059
+ id: '1',
11060
+ senderName: 'Ove Hindborg',
11061
+ senderAvatar: '',
11062
+ senderInitials: 'OH',
11063
+ message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11064
+ role: 'Sagsbehandler',
11065
+ timestamp: '2t siden',
11066
+ unread: true
11067
+ },
11068
+ {
11069
+ id: '2',
11070
+ senderName: 'Martin Smith',
11071
+ senderAvatar: '',
11072
+ senderInitials: 'MS',
11073
+ message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11074
+ role: 'Tekniker',
11075
+ timestamp: '4t siden',
11076
+ unread: true
11077
+ }
11078
+ ];
11079
+ unreadMessagesCount = computed(() => {
11080
+ const count = this.messageThreads.filter(m => m.unread).length;
11081
+ // Update badge in tab items
11082
+ const messagesTab = this.tabItems.find(t => t.id === 'messages');
11083
+ if (messagesTab) {
11084
+ messagesTab.badge = count;
11085
+ }
11086
+ return count;
11087
+ }, ...(ngDevMode ? [{ debugName: "unreadMessagesCount" }] : []));
11088
+ constructor(userService, navCtrl, elementRef) {
11089
+ super();
11090
+ this.userService = userService;
11091
+ this.navCtrl = navCtrl;
11092
+ this.elementRef = elementRef;
11093
+ // Trigger initial badge update
11094
+ this.unreadMessagesCount();
11095
+ }
11096
+ ngAfterViewInit() {
11097
+ // Initial setup if needed
11098
+ }
11099
+ setActiveTab(tabId) {
11100
+ this.activeTab.set(tabId);
11101
+ }
11102
+ goBack() {
11103
+ this.navCtrl.back({ animation: customBackTransition });
11104
+ }
11105
+ handleScroll(event) {
11106
+ const scrollTop = event.detail.scrollTop;
11107
+ const threshold = 160;
11108
+ const fadeDistance = 200;
11109
+ const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
11110
+ const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
11111
+ // Show title in fixed header when scrolled past threshold
11112
+ if (scrollTop > threshold) {
11113
+ header?.classList.add('header-scrolled');
11114
+ }
11115
+ else {
11116
+ header?.classList.remove('header-scrolled');
11117
+ }
11118
+ // Fade out header-expandable content based on scroll
11119
+ if (headerExpandable) {
11120
+ const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
11121
+ // Calculate opacity (1 to 0)
11122
+ const opacity = 1 - fadeProgress;
11123
+ // Calculate transform (0px to -20px upward)
11124
+ const translateY = fadeProgress * -20;
11125
+ // Apply styles
11126
+ headerExpandable.style.opacity = opacity.toString();
11127
+ headerExpandable.style.transform = `translateY(${translateY}px)`;
11128
+ }
11129
+ }
11130
+ handleRefresh(event) {
11131
+ console.log('Pull-to-refresh triggered');
11132
+ setTimeout(() => {
11133
+ console.log('Refresh complete');
11134
+ event.target.complete();
11135
+ }, 1000);
11136
+ }
11137
+ openMessage(messageId) {
11138
+ console.log('Opening message:', messageId);
11139
+ // Navigate to message thread detail
11140
+ }
11141
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiryDetailPageComponent, deps: [{ token: UserService }, { token: i1.NavController }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
11142
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: MobileInquiryDetailPageComponent, isStandalone: true, selector: "app-mobile-inquiry-detail-page", host: { classAttribute: "ion-page" }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
11143
+ <!-- Fixed header at top -->
11144
+ <ion-header>
11145
+ <ion-toolbar>
11146
+ <div class="header-detail">
11147
+ <!-- Back Button -->
11148
+ <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11149
+ <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11150
+ </button>
11151
+
11152
+ <!-- Title - fades in on scroll -->
11153
+ <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11154
+ </div>
11155
+ </ion-toolbar>
11156
+ </ion-header>
11157
+
11158
+ <!-- Content with expandable header -->
11159
+ <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11160
+ <!-- Pull to refresh (only on native iOS/Android) -->
11161
+ @if (isNativePlatform()) {
11162
+ <ion-refresher
11163
+ slot="fixed"
11164
+ (ionRefresh)="handleRefresh($event)"
11165
+ [pullFactor]="0.4"
11166
+ [pullMin]="80"
11167
+ [pullMax]="240"
11168
+ closeDuration="600ms">
11169
+ <ion-refresher-content
11170
+ pullingIcon="remixArrowDownS"
11171
+ refreshingSpinner="lines">
11172
+ </ion-refresher-content>
11173
+ </ion-refresher>
11174
+ }
11175
+
11176
+ <!-- Expandable header section (purple background) -->
11177
+ <div class="header-expandable">
11178
+ <div class="header-expandable-inner">
11179
+ <div class="header-expandable__text">
11180
+ <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11181
+ </div>
11182
+
11183
+ <!-- Tabs in header -->
11184
+ <ds-mobile-inline-tabs
11185
+ [tabs]="tabItems"
11186
+ [activeTab]="activeTab()"
11187
+ (tabChange)="setActiveTab($event)">
11188
+ </ds-mobile-inline-tabs>
11189
+ </div>
11190
+ </div>
11191
+
11192
+ <!-- Content wrapper -->
11193
+ <div class="content-wrapper">
11194
+ <div class="content-inner">
11195
+ <!-- Activity Tab Content -->
11196
+ @if (activeTab() === 'activity') {
11197
+ <div class="activity-list">
11198
+ @for (activity of activities; track activity.id) {
11199
+ <div class="activity-item">
11200
+ @if (activity.actor) {
11201
+ <!-- Avatar with badge for actor activities -->
11202
+ <div class="avatar-wrapper">
11203
+ <ds-avatar
11204
+ [type]="'initials'"
11205
+ [initials]="activity.actorInitials || ''"
11206
+ size="md" />
11207
+
11208
+ <div class="avatar-badge">
11209
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11210
+ <path d="M33.9862 5.51709H23.1724V8.82743H26.0413C26.2841 8.82743 26.4827 9.02606 26.4827 9.26881V12.7998C26.4827 13.0426 26.2841 13.2412 26.0413 13.2412H23.1724V14.3447H26.0413C26.2841 14.3447 26.4827 14.5433 26.4827 14.7861V18.3171C26.4827 18.5598 26.2841 18.7585 26.0413 18.7585H23.1724V19.8619H26.0413C26.2841 19.8619 26.4827 20.0605 26.4827 20.3033V23.8343C26.4827 24.0771 26.2841 24.2757 26.0413 24.2757H23.1724V26.2619C23.1724 26.7496 23.0267 27.2043 22.7773 27.5861H27.5862L32 31.9999V27.5861H33.9862C34.7167 27.5861 35.3103 26.9924 35.3103 26.2619V6.84123C35.3103 6.11075 34.7167 5.51709 33.9862 5.51709ZM32 23.8343C32 24.0771 31.8013 24.2757 31.5586 24.2757H28.0276C27.7848 24.2757 27.5862 24.0771 27.5862 23.8343V20.3033C27.5862 20.0605 27.7848 19.8619 28.0276 19.8619H31.5586C31.8013 19.8619 32 20.0605 32 20.3033V23.8343ZM32 18.3171C32 18.5598 31.8013 18.7585 31.5586 18.7585H28.0276C27.7848 18.7585 27.5862 18.5598 27.5862 18.3171V14.7861C27.5862 14.5433 27.7848 14.3447 28.0276 14.3447H31.5586C31.8013 14.3447 32 14.5433 32 14.7861V18.3171ZM32 12.7998C32 13.0426 31.8013 13.2412 31.5586 13.2412H28.0276C27.7848 13.2412 27.5862 13.0426 27.5862 12.7998V9.26881C27.5862 9.02606 27.7848 8.82743 28.0276 8.82743H31.5586C31.8013 8.82743 32 9.02606 32 9.26881V12.7998Z" fill="white"/>
11211
+ <path d="M20.7448 0H1.32414C0.593655 0 0 0.593655 0 1.32414V26.2621C0 26.9926 0.593655 27.5862 1.32414 27.5862H3.31034V32L7.72414 27.5862H20.7448C21.4753 27.5862 22.069 26.9926 22.069 26.2621V1.32414C22.069 0.593655 21.4753 0 20.7448 0ZM7.72414 23.8345C7.72414 24.0772 7.52552 24.2759 7.28276 24.2759H3.75172C3.50897 24.2759 3.31034 24.0772 3.31034 23.8345V20.3034C3.31034 20.0607 3.50897 19.8621 3.75172 19.8621H7.28276C7.52552 19.8621 7.72414 20.0607 7.72414 20.3034V23.8345ZM7.72414 18.3172C7.72414 18.56 7.52552 18.7586 7.28276 18.7586H3.75172C3.50897 18.7586 3.31034 18.56 3.31034 18.3172V14.7862C3.31034 14.5434 3.50897 14.3448 3.75172 14.3448H7.28276C7.52552 14.3448 7.72414 14.5434 7.72414 14.7862V18.3172ZM7.72414 12.8C7.72414 13.0428 7.52552 13.2414 7.28276 13.2414H3.75172C3.50897 13.2414 3.31034 13.0428 3.31034 12.8V9.26897C3.31034 9.02621 3.50897 8.82759 3.75172 8.82759H7.28276C7.52552 8.82759 7.72414 9.02621 7.72414 9.26897V12.8ZM7.72414 7.28276C7.72414 7.52552 7.52552 7.72414 7.28276 7.72414H3.75172C3.50897 7.72414 3.31034 7.52552 3.31034 7.28276V3.75172C3.31034 3.50897 3.50897 3.31034 3.75172 3.31034H7.28276C7.52552 3.31034 7.72414 3.50897 7.72414 3.75172V7.28276ZM13.2414 23.8345C13.2414 24.0772 13.0428 24.2759 12.8 24.2759H9.26897C9.02621 24.2759 8.82759 24.0772 8.82759 23.8345V20.3034C8.82759 20.0607 9.02621 19.8621 9.26897 19.8621H12.8C13.0428 19.8621 13.2414 20.0607 13.2414 20.3034V23.8345ZM13.2414 18.3172C13.2414 18.56 13.0428 18.7586 12.8 18.7586H9.26897C9.02621 18.7586 8.82759 18.56 8.82759 18.3172V14.7862C8.82759 14.5434 9.02621 14.3448 9.26897 14.3448H12.8C13.0428 14.3448 13.2414 14.5434 13.2414 14.7862V18.3172ZM13.2414 12.8C13.2414 13.0428 13.0428 13.2414 12.8 13.2414H9.26897C9.02621 13.2414 8.82759 13.0428 8.82759 12.8V9.26897C8.82759 9.02621 9.02621 8.82759 9.26897 8.82759H12.8C13.0428 8.82759 13.2414 9.02621 13.2414 9.26897V12.8ZM13.2414 6.84138V7.28276C13.2414 7.52552 13.0428 7.72414 12.8 7.72414H9.26897C9.02621 7.72414 8.82759 7.52552 8.82759 7.28276V3.75172C8.82759 3.50897 9.02621 3.31034 9.26897 3.31034H12.8C13.0428 3.31034 13.2414 3.50897 13.2414 3.75172V6.84138ZM18.7586 23.8345C18.7586 24.0772 18.56 24.2759 18.3172 24.2759H14.7862C14.5434 24.2759 14.3448 24.0772 14.3448 23.8345V20.3034C14.3448 20.0607 14.5434 19.8621 14.7862 19.8621H18.3172C18.56 19.8621 18.7586 20.0607 18.7586 20.3034V23.8345ZM18.7586 18.3172C18.7586 18.56 18.56 18.7586 18.3172 18.7586H14.7862C14.5434 18.7586 14.3448 18.56 14.3448 18.3172V14.7862C14.3448 14.5434 14.5434 14.3448 14.7862 14.3448H18.3172C18.56 14.3448 18.7586 14.5434 18.7586 14.7862V18.3172ZM18.7586 12.8C18.7586 13.0428 18.56 13.2414 18.3172 13.2414H14.7862C14.5434 13.2414 14.3448 13.0428 14.3448 12.8V9.26897C14.3448 9.02621 14.5434 8.82759 14.7862 8.82759H18.3172C18.56 8.82759 18.7586 9.02621 18.7586 9.26897V12.8ZM18.7586 5.51724V7.28276C18.7586 7.52552 18.56 7.72414 18.3172 7.72414H14.7862C14.5434 7.72414 14.3448 7.52552 14.3448 7.28276V3.75172C14.3448 3.50897 14.5434 3.31034 14.7862 3.31034H18.3172C18.56 3.31034 18.7586 3.50897 18.7586 3.75172V5.51724Z" fill="white"/>
11212
+ </svg>
11213
+ </div>
11214
+ </div>
11215
+ } @else {
11216
+ <!-- Icon wrapper for non-actor activities -->
11217
+ <div class="activity-icon-wrapper">
11218
+ <ds-icon
11219
+ [name]="activity.iconName"
11220
+ size="18px"
11221
+ color="secondary" />
11222
+ </div>
11223
+ }
11224
+
11225
+ <div class="activity-content">
11226
+ <p class="activity-title">
11227
+ @if (activity.actor) {
11228
+ <span class="actor-name">{{ activity.actor }}</span>
11229
+ <span class="activity-text"> {{ activity.title }}</span>
11230
+ } @else {
11231
+ <span class="actor-name">{{ activity.title }}</span>
11232
+ }
11233
+ </p>
11234
+ @if (activity.description) {
11235
+ <p class="activity-description">{{ activity.description }}</p>
11236
+ }
11237
+ <div class="activity-timestamp">
11238
+ <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
11239
+ <span>{{ activity.date }}</span>
11240
+ </div>
11241
+ </div>
11242
+ </div>
11243
+ }
11244
+ </div>
11245
+ }
11246
+
11247
+ <!-- Messages Tab Content -->
11248
+ @if (activeTab() === 'messages') {
11249
+ <div class="messages-list">
11250
+ @for (message of messageThreads; track message.id) {
11251
+ <ds-mobile-interactive-list-item-message
11252
+ [senderName]="message.senderName"
11253
+ [senderRole]="message.role"
11254
+ [message]="message.message"
11255
+ [avatarInitials]="message.senderInitials"
11256
+ [unread]="message.unread"
11257
+ (messageClick)="openMessage(message.id)">
11258
+ </ds-mobile-interactive-list-item-message>
11259
+ }
11260
+ </div>
11261
+ }
11262
+
11263
+ <!-- Details Tab Content -->
11264
+ @if (activeTab() === 'details') {
11265
+ <div class="details-list">
11266
+ <!-- Assignee -->
11267
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11268
+ <div content-leading>
11269
+ <ds-avatar
11270
+ [size]="'sm'"
11271
+ [type]="'initials'"
11272
+ [initials]="'R'" />
11273
+ </div>
11274
+ <div content-main>
11275
+ <div class="detail-label">Sagsbehandler</div>
11276
+ <div class="detail-value">Ricki Meihlen</div>
11277
+ </div>
11278
+ </ds-mobile-list-item-static>
11279
+
11280
+ <!-- Technician -->
11281
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11282
+ <div content-leading>
11283
+ <ds-avatar
11284
+ [size]="'sm'"
11285
+ [type]="'initials'"
11286
+ [initials]="'M'" />
11287
+ </div>
11288
+ <div content-main>
11289
+ <div class="detail-label">Tekniker</div>
11290
+ <div class="detail-value">Martin Smith</div>
11291
+ </div>
11292
+ </ds-mobile-list-item-static>
11293
+
11294
+ <!-- Title -->
11295
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11296
+ <div content-leading>
11297
+ <ds-icon name="remixTextBlock" size="20px" color="tertiary" />
11298
+ </div>
11299
+ <div content-main>
11300
+ <div class="detail-value">{{ inquiryTitle }}</div>
11301
+ </div>
11302
+ </ds-mobile-list-item-static>
11303
+
11304
+ <!-- Description -->
11305
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11306
+ <div content-leading>
11307
+ <ds-icon name="remixAlignLeft" size="20px" color="tertiary" />
11308
+ </div>
11309
+ <div content-main>
11310
+ <div class="detail-value description-text">
11311
+ I de sidste tre dage har vi oplevet vedvarende problemer med tørretumbleren i vores lejlighed. På trods af at vi følger betjeningsvejledningen, fejler maskinen konsekvent i at fuldføre sine tørrecyklusser.
11312
+ </div>
11313
+ <div class="detail-tag">Husholdningsapparater</div>
11314
+ </div>
11315
+ </ds-mobile-list-item-static>
11316
+
11317
+ <!-- Photos -->
11318
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11319
+ <div content-leading>
11320
+ <ds-icon name="remixCameraLine" size="20px" color="tertiary" />
11321
+ </div>
11322
+ <div content-main>
11323
+ <div class="photo-grid">
11324
+ <button class="photo-add">
11325
+ <ds-icon name="remixAddLine" size="20px" color="tertiary" />
11326
+ </button>
11327
+ <!-- Placeholder photos -->
11328
+ <div class="photo-item"></div>
11329
+ <div class="photo-item"></div>
11330
+ <div class="photo-item"></div>
11331
+ <div class="photo-item"></div>
11332
+ </div>
11333
+ </div>
11334
+ </ds-mobile-list-item-static>
11335
+ </div>
11336
+ }
11337
+ </div>
11338
+ </div>
11339
+ </ion-content>
11340
+ `, isInline: true, styles: ["ion-header{--background: var(--color-brand-secondary, #5d5fef);height:72px;min-height:72px}ion-toolbar{--background: var(--color-brand-secondary, #5d5fef);--color: white;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:100%;min-height:72px}ion-toolbar ion-title{transition:transform .2s ease,opacity .2s ease!important}.header-detail{display:flex;align-items:center;gap:12px;padding:0 20px;height:100%}.back-button{width:36px;height:36px;border-radius:50%;background:#ffffff1a;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s ease;color:#fff;padding:0;flex-shrink:0}.back-button:hover,.back-button:active{background:#ffffff26}.header-detail__title{position:absolute;left:64px;transform:translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important;margin:0;padding:0;--color: white;text-align:left!important}.header-scrolled .header-detail__title{opacity:1!important;pointer-events:auto;transform:translateY(0)}@media (min-width: 768px){.header-detail{padding:16px 24px}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px}.header-expandable__text{margin-bottom:0}@media (min-width: 768px){.header-expandable{padding:40px var(--content-padding-md) 32px var(--content-padding-md)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{background:var(--color-background-neutral-primary, white);border-radius:24px 24px 0 0;flex:1 1 auto;min-height:100%;position:relative;z-index:10;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary)}.content-inner{padding:24px 20px 40px}@media (min-width: 768px){.content-inner{padding:32px}}.activity-list{display:flex;flex-direction:column;gap:32px}.activity-item{display:flex;gap:12px;position:relative}.activity-item:after{content:\"\";position:absolute;bottom:-16px;left:44px;right:8px;height:1px;background:var(--border-color-default, #e5e5e5)}.activity-item:last-child:after{display:none}.activity-icon-wrapper{width:32px;height:32px;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:32px;height:32px}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.messages-list{display:flex;flex-direction:column;gap:8px;margin:-8px}.details-list{display:flex;flex-direction:column;gap:20px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-value.description-text{padding:8px 0}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:flex;gap:8px;margin-top:8px;overflow-x:auto}.photo-add{width:80px;height:80px;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}.photo-item{width:80px;height:80px;border-radius:12px;background:var(--color-background-neutral-secondary, #e5e5e5);flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileListItemStaticComponent, selector: "ds-mobile-list-item-static", inputs: ["leadingSize"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "clickable"], outputs: ["messageClick", "longPress"] }] });
11341
+ }
11342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiryDetailPageComponent, decorators: [{
11343
+ type: Component,
11344
+ args: [{ selector: 'app-mobile-inquiry-detail-page', standalone: true, imports: [
11345
+ CommonModule,
11346
+ IonHeader,
11347
+ IonToolbar,
11348
+ IonTitle,
11349
+ IonContent,
11350
+ IonRefresher,
11351
+ IonRefresherContent,
11352
+ DsIconComponent,
11353
+ DsAvatarComponent,
11354
+ DsMobileInlineTabsComponent,
11355
+ DsMobileListItemStaticComponent,
11356
+ DsMobileInteractiveListItemMessageComponent
11357
+ ], host: {
11358
+ class: 'ion-page'
11359
+ }, template: `
11360
+ <!-- Fixed header at top -->
11361
+ <ion-header>
11362
+ <ion-toolbar>
11363
+ <div class="header-detail">
11364
+ <!-- Back Button -->
11365
+ <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11366
+ <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11367
+ </button>
11368
+
11369
+ <!-- Title - fades in on scroll -->
11370
+ <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11371
+ </div>
11372
+ </ion-toolbar>
11373
+ </ion-header>
11374
+
11375
+ <!-- Content with expandable header -->
11376
+ <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11377
+ <!-- Pull to refresh (only on native iOS/Android) -->
11378
+ @if (isNativePlatform()) {
11379
+ <ion-refresher
11380
+ slot="fixed"
11381
+ (ionRefresh)="handleRefresh($event)"
11382
+ [pullFactor]="0.4"
11383
+ [pullMin]="80"
11384
+ [pullMax]="240"
11385
+ closeDuration="600ms">
11386
+ <ion-refresher-content
11387
+ pullingIcon="remixArrowDownS"
11388
+ refreshingSpinner="lines">
11389
+ </ion-refresher-content>
11390
+ </ion-refresher>
11391
+ }
11392
+
11393
+ <!-- Expandable header section (purple background) -->
11394
+ <div class="header-expandable">
11395
+ <div class="header-expandable-inner">
11396
+ <div class="header-expandable__text">
11397
+ <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11398
+ </div>
11399
+
11400
+ <!-- Tabs in header -->
11401
+ <ds-mobile-inline-tabs
11402
+ [tabs]="tabItems"
11403
+ [activeTab]="activeTab()"
11404
+ (tabChange)="setActiveTab($event)">
11405
+ </ds-mobile-inline-tabs>
11406
+ </div>
11407
+ </div>
11408
+
11409
+ <!-- Content wrapper -->
11410
+ <div class="content-wrapper">
11411
+ <div class="content-inner">
11412
+ <!-- Activity Tab Content -->
11413
+ @if (activeTab() === 'activity') {
11414
+ <div class="activity-list">
11415
+ @for (activity of activities; track activity.id) {
11416
+ <div class="activity-item">
11417
+ @if (activity.actor) {
11418
+ <!-- Avatar with badge for actor activities -->
11419
+ <div class="avatar-wrapper">
11420
+ <ds-avatar
11421
+ [type]="'initials'"
11422
+ [initials]="activity.actorInitials || ''"
11423
+ size="md" />
11424
+
11425
+ <div class="avatar-badge">
11426
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11427
+ <path d="M33.9862 5.51709H23.1724V8.82743H26.0413C26.2841 8.82743 26.4827 9.02606 26.4827 9.26881V12.7998C26.4827 13.0426 26.2841 13.2412 26.0413 13.2412H23.1724V14.3447H26.0413C26.2841 14.3447 26.4827 14.5433 26.4827 14.7861V18.3171C26.4827 18.5598 26.2841 18.7585 26.0413 18.7585H23.1724V19.8619H26.0413C26.2841 19.8619 26.4827 20.0605 26.4827 20.3033V23.8343C26.4827 24.0771 26.2841 24.2757 26.0413 24.2757H23.1724V26.2619C23.1724 26.7496 23.0267 27.2043 22.7773 27.5861H27.5862L32 31.9999V27.5861H33.9862C34.7167 27.5861 35.3103 26.9924 35.3103 26.2619V6.84123C35.3103 6.11075 34.7167 5.51709 33.9862 5.51709ZM32 23.8343C32 24.0771 31.8013 24.2757 31.5586 24.2757H28.0276C27.7848 24.2757 27.5862 24.0771 27.5862 23.8343V20.3033C27.5862 20.0605 27.7848 19.8619 28.0276 19.8619H31.5586C31.8013 19.8619 32 20.0605 32 20.3033V23.8343ZM32 18.3171C32 18.5598 31.8013 18.7585 31.5586 18.7585H28.0276C27.7848 18.7585 27.5862 18.5598 27.5862 18.3171V14.7861C27.5862 14.5433 27.7848 14.3447 28.0276 14.3447H31.5586C31.8013 14.3447 32 14.5433 32 14.7861V18.3171ZM32 12.7998C32 13.0426 31.8013 13.2412 31.5586 13.2412H28.0276C27.7848 13.2412 27.5862 13.0426 27.5862 12.7998V9.26881C27.5862 9.02606 27.7848 8.82743 28.0276 8.82743H31.5586C31.8013 8.82743 32 9.02606 32 9.26881V12.7998Z" fill="white"/>
11428
+ <path d="M20.7448 0H1.32414C0.593655 0 0 0.593655 0 1.32414V26.2621C0 26.9926 0.593655 27.5862 1.32414 27.5862H3.31034V32L7.72414 27.5862H20.7448C21.4753 27.5862 22.069 26.9926 22.069 26.2621V1.32414C22.069 0.593655 21.4753 0 20.7448 0ZM7.72414 23.8345C7.72414 24.0772 7.52552 24.2759 7.28276 24.2759H3.75172C3.50897 24.2759 3.31034 24.0772 3.31034 23.8345V20.3034C3.31034 20.0607 3.50897 19.8621 3.75172 19.8621H7.28276C7.52552 19.8621 7.72414 20.0607 7.72414 20.3034V23.8345ZM7.72414 18.3172C7.72414 18.56 7.52552 18.7586 7.28276 18.7586H3.75172C3.50897 18.7586 3.31034 18.56 3.31034 18.3172V14.7862C3.31034 14.5434 3.50897 14.3448 3.75172 14.3448H7.28276C7.52552 14.3448 7.72414 14.5434 7.72414 14.7862V18.3172ZM7.72414 12.8C7.72414 13.0428 7.52552 13.2414 7.28276 13.2414H3.75172C3.50897 13.2414 3.31034 13.0428 3.31034 12.8V9.26897C3.31034 9.02621 3.50897 8.82759 3.75172 8.82759H7.28276C7.52552 8.82759 7.72414 9.02621 7.72414 9.26897V12.8ZM7.72414 7.28276C7.72414 7.52552 7.52552 7.72414 7.28276 7.72414H3.75172C3.50897 7.72414 3.31034 7.52552 3.31034 7.28276V3.75172C3.31034 3.50897 3.50897 3.31034 3.75172 3.31034H7.28276C7.52552 3.31034 7.72414 3.50897 7.72414 3.75172V7.28276ZM13.2414 23.8345C13.2414 24.0772 13.0428 24.2759 12.8 24.2759H9.26897C9.02621 24.2759 8.82759 24.0772 8.82759 23.8345V20.3034C8.82759 20.0607 9.02621 19.8621 9.26897 19.8621H12.8C13.0428 19.8621 13.2414 20.0607 13.2414 20.3034V23.8345ZM13.2414 18.3172C13.2414 18.56 13.0428 18.7586 12.8 18.7586H9.26897C9.02621 18.7586 8.82759 18.56 8.82759 18.3172V14.7862C8.82759 14.5434 9.02621 14.3448 9.26897 14.3448H12.8C13.0428 14.3448 13.2414 14.5434 13.2414 14.7862V18.3172ZM13.2414 12.8C13.2414 13.0428 13.0428 13.2414 12.8 13.2414H9.26897C9.02621 13.2414 8.82759 13.0428 8.82759 12.8V9.26897C8.82759 9.02621 9.02621 8.82759 9.26897 8.82759H12.8C13.0428 8.82759 13.2414 9.02621 13.2414 9.26897V12.8ZM13.2414 6.84138V7.28276C13.2414 7.52552 13.0428 7.72414 12.8 7.72414H9.26897C9.02621 7.72414 8.82759 7.52552 8.82759 7.28276V3.75172C8.82759 3.50897 9.02621 3.31034 9.26897 3.31034H12.8C13.0428 3.31034 13.2414 3.50897 13.2414 3.75172V6.84138ZM18.7586 23.8345C18.7586 24.0772 18.56 24.2759 18.3172 24.2759H14.7862C14.5434 24.2759 14.3448 24.0772 14.3448 23.8345V20.3034C14.3448 20.0607 14.5434 19.8621 14.7862 19.8621H18.3172C18.56 19.8621 18.7586 20.0607 18.7586 20.3034V23.8345ZM18.7586 18.3172C18.7586 18.56 18.56 18.7586 18.3172 18.7586H14.7862C14.5434 18.7586 14.3448 18.56 14.3448 18.3172V14.7862C14.3448 14.5434 14.5434 14.3448 14.7862 14.3448H18.3172C18.56 14.3448 18.7586 14.5434 18.7586 14.7862V18.3172ZM18.7586 12.8C18.7586 13.0428 18.56 13.2414 18.3172 13.2414H14.7862C14.5434 13.2414 14.3448 13.0428 14.3448 12.8V9.26897C14.3448 9.02621 14.5434 8.82759 14.7862 8.82759H18.3172C18.56 8.82759 18.7586 9.02621 18.7586 9.26897V12.8ZM18.7586 5.51724V7.28276C18.7586 7.52552 18.56 7.72414 18.3172 7.72414H14.7862C14.5434 7.72414 14.3448 7.52552 14.3448 7.28276V3.75172C14.3448 3.50897 14.5434 3.31034 14.7862 3.31034H18.3172C18.56 3.31034 18.7586 3.50897 18.7586 3.75172V5.51724Z" fill="white"/>
11429
+ </svg>
11430
+ </div>
11431
+ </div>
11432
+ } @else {
11433
+ <!-- Icon wrapper for non-actor activities -->
11434
+ <div class="activity-icon-wrapper">
11435
+ <ds-icon
11436
+ [name]="activity.iconName"
11437
+ size="18px"
11438
+ color="secondary" />
11439
+ </div>
11440
+ }
11441
+
11442
+ <div class="activity-content">
11443
+ <p class="activity-title">
11444
+ @if (activity.actor) {
11445
+ <span class="actor-name">{{ activity.actor }}</span>
11446
+ <span class="activity-text"> {{ activity.title }}</span>
11447
+ } @else {
11448
+ <span class="actor-name">{{ activity.title }}</span>
11449
+ }
11450
+ </p>
11451
+ @if (activity.description) {
11452
+ <p class="activity-description">{{ activity.description }}</p>
11453
+ }
11454
+ <div class="activity-timestamp">
11455
+ <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
11456
+ <span>{{ activity.date }}</span>
11457
+ </div>
11458
+ </div>
11459
+ </div>
11460
+ }
11461
+ </div>
11462
+ }
11463
+
11464
+ <!-- Messages Tab Content -->
11465
+ @if (activeTab() === 'messages') {
11466
+ <div class="messages-list">
11467
+ @for (message of messageThreads; track message.id) {
11468
+ <ds-mobile-interactive-list-item-message
11469
+ [senderName]="message.senderName"
11470
+ [senderRole]="message.role"
11471
+ [message]="message.message"
11472
+ [avatarInitials]="message.senderInitials"
11473
+ [unread]="message.unread"
11474
+ (messageClick)="openMessage(message.id)">
11475
+ </ds-mobile-interactive-list-item-message>
11476
+ }
11477
+ </div>
11478
+ }
11479
+
11480
+ <!-- Details Tab Content -->
11481
+ @if (activeTab() === 'details') {
11482
+ <div class="details-list">
11483
+ <!-- Assignee -->
11484
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11485
+ <div content-leading>
11486
+ <ds-avatar
11487
+ [size]="'sm'"
11488
+ [type]="'initials'"
11489
+ [initials]="'R'" />
11490
+ </div>
11491
+ <div content-main>
11492
+ <div class="detail-label">Sagsbehandler</div>
11493
+ <div class="detail-value">Ricki Meihlen</div>
11494
+ </div>
11495
+ </ds-mobile-list-item-static>
11496
+
11497
+ <!-- Technician -->
11498
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11499
+ <div content-leading>
11500
+ <ds-avatar
11501
+ [size]="'sm'"
11502
+ [type]="'initials'"
11503
+ [initials]="'M'" />
11504
+ </div>
11505
+ <div content-main>
11506
+ <div class="detail-label">Tekniker</div>
11507
+ <div class="detail-value">Martin Smith</div>
11508
+ </div>
11509
+ </ds-mobile-list-item-static>
11510
+
11511
+ <!-- Title -->
11512
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11513
+ <div content-leading>
11514
+ <ds-icon name="remixTextBlock" size="20px" color="tertiary" />
11515
+ </div>
11516
+ <div content-main>
11517
+ <div class="detail-value">{{ inquiryTitle }}</div>
11518
+ </div>
11519
+ </ds-mobile-list-item-static>
11520
+
11521
+ <!-- Description -->
11522
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11523
+ <div content-leading>
11524
+ <ds-icon name="remixAlignLeft" size="20px" color="tertiary" />
11525
+ </div>
11526
+ <div content-main>
11527
+ <div class="detail-value description-text">
11528
+ I de sidste tre dage har vi oplevet vedvarende problemer med tørretumbleren i vores lejlighed. På trods af at vi følger betjeningsvejledningen, fejler maskinen konsekvent i at fuldføre sine tørrecyklusser.
11529
+ </div>
11530
+ <div class="detail-tag">Husholdningsapparater</div>
11531
+ </div>
11532
+ </ds-mobile-list-item-static>
11533
+
11534
+ <!-- Photos -->
11535
+ <ds-mobile-list-item-static [leadingSize]="'32px'">
11536
+ <div content-leading>
11537
+ <ds-icon name="remixCameraLine" size="20px" color="tertiary" />
11538
+ </div>
11539
+ <div content-main>
11540
+ <div class="photo-grid">
11541
+ <button class="photo-add">
11542
+ <ds-icon name="remixAddLine" size="20px" color="tertiary" />
11543
+ </button>
11544
+ <!-- Placeholder photos -->
11545
+ <div class="photo-item"></div>
11546
+ <div class="photo-item"></div>
11547
+ <div class="photo-item"></div>
11548
+ <div class="photo-item"></div>
11549
+ </div>
11550
+ </div>
11551
+ </ds-mobile-list-item-static>
11552
+ </div>
11553
+ }
11554
+ </div>
11555
+ </div>
11556
+ </ion-content>
11557
+ `, styles: ["ion-header{--background: var(--color-brand-secondary, #5d5fef);height:72px;min-height:72px}ion-toolbar{--background: var(--color-brand-secondary, #5d5fef);--color: white;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:100%;min-height:72px}ion-toolbar ion-title{transition:transform .2s ease,opacity .2s ease!important}.header-detail{display:flex;align-items:center;gap:12px;padding:0 20px;height:100%}.back-button{width:36px;height:36px;border-radius:50%;background:#ffffff1a;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s ease;color:#fff;padding:0;flex-shrink:0}.back-button:hover,.back-button:active{background:#ffffff26}.header-detail__title{position:absolute;left:64px;transform:translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important;margin:0;padding:0;--color: white;text-align:left!important}.header-scrolled .header-detail__title{opacity:1!important;pointer-events:auto;transform:translateY(0)}@media (min-width: 768px){.header-detail{padding:16px 24px}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px}.header-expandable__text{margin-bottom:0}@media (min-width: 768px){.header-expandable{padding:40px var(--content-padding-md) 32px var(--content-padding-md)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{background:var(--color-background-neutral-primary, white);border-radius:24px 24px 0 0;flex:1 1 auto;min-height:100%;position:relative;z-index:10;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary)}.content-inner{padding:24px 20px 40px}@media (min-width: 768px){.content-inner{padding:32px}}.activity-list{display:flex;flex-direction:column;gap:32px}.activity-item{display:flex;gap:12px;position:relative}.activity-item:after{content:\"\";position:absolute;bottom:-16px;left:44px;right:8px;height:1px;background:var(--border-color-default, #e5e5e5)}.activity-item:last-child:after{display:none}.activity-icon-wrapper{width:32px;height:32px;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:32px;height:32px}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.messages-list{display:flex;flex-direction:column;gap:8px;margin:-8px}.details-list{display:flex;flex-direction:column;gap:20px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-value.description-text{padding:8px 0}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:flex;gap:8px;margin-top:8px;overflow-x:auto}.photo-add{width:80px;height:80px;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}.photo-item{width:80px;height:80px;border-radius:12px;background:var(--color-background-neutral-secondary, #e5e5e5);flex-shrink:0}\n"] }]
11558
+ }], ctorParameters: () => [{ type: UserService }, { type: i1.NavController }, { type: i0.ElementRef }], propDecorators: { ionContent: [{
11559
+ type: ViewChild,
11560
+ args: [IonContent]
11561
+ }] } });
11562
+
11563
+ /**
11564
+ * DsAvatarWithBadgeComponent
11565
+ *
11566
+ * Displays an avatar with a logomark badge overlay.
11567
+ * Useful for showing user avatars with organization branding.
11568
+ *
11569
+ * @example
11570
+ * ```html
11571
+ * <ds-avatar-with-badge
11572
+ * [type]="'initials'"
11573
+ * [initials]="'JD'"
11574
+ * [size]="'lg'"
11575
+ * [badgePosition]="'bottom-right'"
11576
+ * />
11577
+ * ```
11578
+ */
11579
+ class DsAvatarWithBadgeComponent {
11580
+ whitelabelService = inject(WhitelabelService);
11581
+ // Avatar props
11582
+ type = 'initials';
11583
+ size = 'md';
11584
+ initials = '';
11585
+ src = '';
11586
+ iconName = 'remixUser3Fill';
11587
+ // Badge props
11588
+ showBadge = true;
11589
+ badgePosition = 'bottom-right';
11590
+ badgeClasses = computed(() => {
11591
+ return `avatar-badge avatar-badge--${this.badgePosition} avatar-badge--${this.size}`;
11592
+ }, ...(ngDevMode ? [{ debugName: "badgeClasses" }] : []));
11593
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsAvatarWithBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11594
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: DsAvatarWithBadgeComponent, isStandalone: true, selector: "ds-avatar-with-badge", inputs: { type: "type", size: "size", initials: "initials", src: "src", iconName: "iconName", showBadge: "showBadge", badgePosition: "badgePosition" }, ngImport: i0, template: `
11595
+ <div class="avatar-badge-container">
11596
+ <ds-avatar
11597
+ [type]="type"
11598
+ [size]="size"
11599
+ [initials]="initials"
11600
+ [src]="src"
11601
+ [iconName]="iconName"
11602
+ />
11603
+
11604
+ @if (showBadge) {
11605
+ <div [class]="badgeClasses()">
11606
+ <img [src]="whitelabelService.logoMarkUrl()" [alt]="whitelabelService.logoAlt()" />
11607
+ </div>
11608
+ }
11609
+ </div>
11610
+ `, isInline: true, styles: [":host{display:inline-block;position:relative}.avatar-badge-container{position:relative;display:inline-block}.avatar-badge{position:absolute;background:var(--color-brand-secondary, #5d5fef);border-radius:8px;display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge img{width:10px;height:10px;object-fit:contain}.avatar-badge--bottom-right{bottom:-6px;right:-6px}.avatar-badge--bottom-left{bottom:-6px;left:-6px}.avatar-badge--top-right{top:-6px;right:-6px}.avatar-badge--top-left{top:-6px;left:-6px}.avatar-badge--xs{width:16px;height:16px}.avatar-badge--sm{width:18px;height:18px}.avatar-badge--md{width:20px;height:20px}.avatar-badge--lg{width:24px;height:24px}.avatar-badge--xl{width:28px;height:28px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }] });
11611
+ }
11612
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DsAvatarWithBadgeComponent, decorators: [{
11613
+ type: Component,
11614
+ args: [{ selector: 'ds-avatar-with-badge', standalone: true, imports: [CommonModule, DsAvatarComponent], encapsulation: ViewEncapsulation.Emulated, template: `
11615
+ <div class="avatar-badge-container">
11616
+ <ds-avatar
11617
+ [type]="type"
11618
+ [size]="size"
11619
+ [initials]="initials"
11620
+ [src]="src"
11621
+ [iconName]="iconName"
11622
+ />
11623
+
11624
+ @if (showBadge) {
11625
+ <div [class]="badgeClasses()">
11626
+ <img [src]="whitelabelService.logoMarkUrl()" [alt]="whitelabelService.logoAlt()" />
11359
11627
  </div>
11360
- </ds-mobile-content>
11361
- </ds-mobile-page-main>
11362
- `, styles: [".inquiries-container{display:flex;flex-direction:column;max-width:640px}.inquiry-list-wrapper{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"] }]
11363
- }], ctorParameters: () => [{ type: UserService }, { type: i1.NavController }] });
11628
+ }
11629
+ </div>
11630
+ `, styles: [":host{display:inline-block;position:relative}.avatar-badge-container{position:relative;display:inline-block}.avatar-badge{position:absolute;background:var(--color-brand-secondary, #5d5fef);border-radius:8px;display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge img{width:10px;height:10px;object-fit:contain}.avatar-badge--bottom-right{bottom:-6px;right:-6px}.avatar-badge--bottom-left{bottom:-6px;left:-6px}.avatar-badge--top-right{top:-6px;right:-6px}.avatar-badge--top-left{top:-6px;left:-6px}.avatar-badge--xs{width:16px;height:16px}.avatar-badge--sm{width:18px;height:18px}.avatar-badge--md{width:20px;height:20px}.avatar-badge--lg{width:24px;height:24px}.avatar-badge--xl{width:28px;height:28px}\n"] }]
11631
+ }], propDecorators: { type: [{
11632
+ type: Input
11633
+ }], size: [{
11634
+ type: Input
11635
+ }], initials: [{
11636
+ type: Input
11637
+ }], src: [{
11638
+ type: Input
11639
+ }], iconName: [{
11640
+ type: Input
11641
+ }], showBadge: [{
11642
+ type: Input
11643
+ }], badgePosition: [{
11644
+ type: Input
11645
+ }] } });
11364
11646
 
11365
- class MobileInquiryDetailPageComponent extends MobilePageBase {
11366
- userService;
11367
- navCtrl;
11368
- elementRef;
11369
- ionContent;
11370
- // Platform detection
11371
- platform = inject(Platform);
11372
- // Computed property to check if running on native platform
11373
- isNativePlatform = computed(() => this.platform.is('ios') ||
11374
- this.platform.is('android') ||
11375
- this.platform.is('capacitor'), ...(ngDevMode ? [{ debugName: "isNativePlatform" }] : []));
11376
- inquiryTitle = 'Tørretumbler virker ikke';
11377
- activeTab = signal('activity', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
11378
- tabItems = [
11379
- { id: 'activity', label: 'Aktivitet' },
11380
- { id: 'messages', label: 'Beskeder', badge: 0 },
11381
- { id: 'details', label: 'Detaljer' }
11382
- ];
11383
- activities = [
11384
- {
11385
- id: '1',
11386
- type: 'assignment',
11387
- actor: 'Martin Smith',
11388
- actorInitials: 'MS',
11389
- title: 'er blevet tildelt som din dedikerede tekniker.',
11390
- timestamp: '2 dage siden',
11391
- date: '28. feb 2025',
11392
- iconName: 'remixUserAddLine'
11393
- },
11394
- {
11395
- id: '2',
11396
- type: 'assignment',
11397
- actor: 'Ricki Meihlen',
11398
- actorInitials: 'RM',
11399
- title: 'er blevet tildelt til at håndtere din henvendelse.',
11400
- timestamp: '8 dage siden',
11401
- date: '22. feb 2025',
11402
- iconName: 'remixUserLine'
11403
- },
11404
- {
11405
- id: '3',
11406
- type: 'creation',
11407
- title: 'Henvendelse oprettet',
11408
- timestamp: '8 dage siden',
11409
- date: '22. feb 2025',
11410
- iconName: 'remixAddCircleLine'
11647
+ /**
11648
+ * Whitelabel Demo Modal Component
11649
+ *
11650
+ * Demonstrates the whitelabeling system with theme selection, brand colors, and logo previews.
11651
+ * Opens as a full-screen modal similar to post details.
11652
+ */
11653
+ class WhitelabelDemoModalComponent {
11654
+ whitelabelService = inject(WhitelabelService);
11655
+ modalController = inject(ModalController);
11656
+ // Current active theme
11657
+ currentTheme = 'default';
11658
+ // Custom color inputs
11659
+ customPrimarySurface = '#6B5FF5';
11660
+ customPrimaryContent = '#FFFFFF';
11661
+ customSecondarySurface = '#221a4c';
11662
+ customSecondaryContent = '#FFFFFF';
11663
+ ngOnInit() {
11664
+ this.updateCustomColorInputs();
11665
+ this.detectCurrentTheme();
11666
+ }
11667
+ /**
11668
+ * Detect the current active theme based on colors
11669
+ */
11670
+ detectCurrentTheme() {
11671
+ const secondarySurface = this.whitelabelService.secondarySurface().toUpperCase();
11672
+ if (secondarySurface === '#A70923') {
11673
+ this.currentTheme = 'cej';
11411
11674
  }
11412
- ];
11413
- messageThreads = [
11414
- {
11415
- id: '1',
11416
- senderName: 'Ove Hindborg',
11417
- senderAvatar: '',
11418
- senderInitials: 'OH',
11419
- message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11420
- role: 'Sagsbehandler',
11421
- timestamp: '2t siden',
11422
- unread: true
11423
- },
11424
- {
11425
- id: '2',
11426
- senderName: 'Martin Smith',
11427
- senderAvatar: '',
11428
- senderInitials: 'MS',
11429
- message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11430
- role: 'Tekniker',
11431
- timestamp: '4t siden',
11432
- unread: true
11675
+ else if (secondarySurface === '#660036') {
11676
+ this.currentTheme = 'pka';
11433
11677
  }
11434
- ];
11435
- unreadMessagesCount = computed(() => {
11436
- const count = this.messageThreads.filter(m => m.unread).length;
11437
- // Update badge in tab items
11438
- const messagesTab = this.tabItems.find(t => t.id === 'messages');
11439
- if (messagesTab) {
11440
- messagesTab.badge = count;
11678
+ else if (secondarySurface === '#262424') {
11679
+ this.currentTheme = 'clave';
11680
+ }
11681
+ else {
11682
+ this.currentTheme = 'default';
11441
11683
  }
11442
- return count;
11443
- }, ...(ngDevMode ? [{ debugName: "unreadMessagesCount" }] : []));
11444
- constructor(userService, navCtrl, elementRef) {
11445
- super();
11446
- this.userService = userService;
11447
- this.navCtrl = navCtrl;
11448
- this.elementRef = elementRef;
11449
- // Trigger initial badge update
11450
- this.unreadMessagesCount();
11451
11684
  }
11452
- ngAfterViewInit() {
11453
- // Initial setup if needed
11685
+ /**
11686
+ * Close the modal
11687
+ */
11688
+ close() {
11689
+ this.modalController.dismiss();
11454
11690
  }
11455
- setActiveTab(tabId) {
11456
- this.activeTab.set(tabId);
11691
+ applyDefaultTheme() {
11692
+ this.currentTheme = 'default';
11693
+ this.whitelabelService.updateConfig({
11694
+ logoUrl: '/Assets/logos/propbinder-logomark.svg',
11695
+ logoMarkUrl: '/Assets/logos/propbinder-logomark.svg',
11696
+ logoAlt: 'Propbinder',
11697
+ logoHeight: 28,
11698
+ primarySurface: '#6B5FF5',
11699
+ primaryContent: '#FFFFFF',
11700
+ secondarySurface: '#221a4c',
11701
+ secondaryContent: '#FFFFFF',
11702
+ organizationName: 'Propbinder',
11703
+ organizationId: 'default'
11704
+ });
11705
+ this.updateCustomColorInputs();
11457
11706
  }
11458
- goBack() {
11459
- this.navCtrl.back({ animation: customBackTransition });
11707
+ applyCejTheme() {
11708
+ this.currentTheme = 'cej';
11709
+ this.whitelabelService.updateConfig({
11710
+ logoUrl: '/Assets/logos/cej-logo.png',
11711
+ logoMarkUrl: '/Assets/logos/cej-logo.png',
11712
+ logoAlt: 'CEJ',
11713
+ logoHeight: 36,
11714
+ primarySurface: '#FFFFFF',
11715
+ primaryContent: '#A70923',
11716
+ secondarySurface: '#A70923',
11717
+ secondaryContent: '#FFFFFF',
11718
+ organizationName: 'CEJ',
11719
+ organizationId: 'cej'
11720
+ });
11721
+ this.updateCustomColorInputs();
11460
11722
  }
11461
- handleScroll(event) {
11462
- const scrollTop = event.detail.scrollTop;
11463
- const threshold = 160;
11464
- const fadeDistance = 200;
11465
- const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
11466
- const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
11467
- // Show title in fixed header when scrolled past threshold
11468
- if (scrollTop > threshold) {
11469
- header?.classList.add('header-scrolled');
11470
- }
11471
- else {
11472
- header?.classList.remove('header-scrolled');
11473
- }
11474
- // Fade out header-expandable content based on scroll
11475
- if (headerExpandable) {
11476
- const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
11477
- // Calculate opacity (1 to 0)
11478
- const opacity = 1 - fadeProgress;
11479
- // Calculate transform (0px to -20px upward)
11480
- const translateY = fadeProgress * -20;
11481
- // Apply styles
11482
- headerExpandable.style.opacity = opacity.toString();
11483
- headerExpandable.style.transform = `translateY(${translateY}px)`;
11484
- }
11723
+ applyPkaTheme() {
11724
+ this.currentTheme = 'pka';
11725
+ this.whitelabelService.updateConfig({
11726
+ logoUrl: '/Assets/logos/pka-logo.svg',
11727
+ logoMarkUrl: '/Assets/logos/pka-logo.svg',
11728
+ logoAlt: 'PKA',
11729
+ logoHeight: 24,
11730
+ primarySurface: '#CC006C',
11731
+ primaryContent: '#FFFFFF',
11732
+ secondarySurface: '#660036',
11733
+ secondaryContent: '#FFFFFF',
11734
+ organizationName: 'PKA',
11735
+ organizationId: 'pka'
11736
+ });
11737
+ this.updateCustomColorInputs();
11485
11738
  }
11486
- handleRefresh(event) {
11487
- console.log('Pull-to-refresh triggered');
11488
- setTimeout(() => {
11489
- console.log('Refresh complete');
11490
- event.target.complete();
11491
- }, 1000);
11739
+ applyClaveTheme() {
11740
+ this.currentTheme = 'clave';
11741
+ this.whitelabelService.updateConfig({
11742
+ logoUrl: '/Assets/logos/clave-logo.svg',
11743
+ logoMarkUrl: '/Assets/logos/clave-logo.svg',
11744
+ logoAlt: 'Clave',
11745
+ logoHeight: 24,
11746
+ primarySurface: '#868764',
11747
+ primaryContent: '#262424',
11748
+ secondarySurface: '#262424',
11749
+ secondaryContent: '#FFFFFF',
11750
+ organizationName: 'Clave',
11751
+ organizationId: 'clave'
11752
+ });
11753
+ this.updateCustomColorInputs();
11492
11754
  }
11493
- openMessage(messageId) {
11494
- console.log('Opening message:', messageId);
11495
- // Navigate to message thread detail
11755
+ applyCustomColors() {
11756
+ this.whitelabelService.updateColors({
11757
+ primarySurface: this.customPrimarySurface,
11758
+ primaryContent: this.customPrimaryContent,
11759
+ secondarySurface: this.customSecondarySurface,
11760
+ secondaryContent: this.customSecondaryContent
11761
+ });
11496
11762
  }
11497
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiryDetailPageComponent, deps: [{ token: UserService }, { token: i1.NavController }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
11498
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: MobileInquiryDetailPageComponent, isStandalone: true, selector: "app-mobile-inquiry-detail-page", host: { classAttribute: "ion-page" }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
11499
- <!-- Fixed header at top -->
11500
- <ion-header>
11501
- <ion-toolbar>
11502
- <div class="header-detail">
11503
- <!-- Back Button -->
11504
- <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11505
- <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11506
- </button>
11507
-
11508
- <!-- Title - fades in on scroll -->
11509
- <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11510
- </div>
11511
- </ion-toolbar>
11512
- </ion-header>
11513
-
11514
- <!-- Content with expandable header -->
11515
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11516
- <!-- Pull to refresh (only on native iOS/Android) -->
11517
- @if (isNativePlatform()) {
11518
- <ion-refresher
11519
- slot="fixed"
11520
- (ionRefresh)="handleRefresh($event)"
11521
- [pullFactor]="0.4"
11522
- [pullMin]="80"
11523
- [pullMax]="240"
11524
- closeDuration="600ms">
11525
- <ion-refresher-content
11526
- pullingIcon="remixArrowDownS"
11527
- refreshingSpinner="lines">
11528
- </ion-refresher-content>
11529
- </ion-refresher>
11530
- }
11531
-
11532
- <!-- Expandable header section (purple background) -->
11533
- <div class="header-expandable">
11534
- <div class="header-expandable-inner">
11535
- <div class="header-expandable__text">
11536
- <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11537
- </div>
11538
-
11539
- <!-- Tabs in header -->
11540
- <ds-mobile-inline-tabs
11541
- [tabs]="tabItems"
11542
- [activeTab]="activeTab()"
11543
- (tabChange)="setActiveTab($event)">
11544
- </ds-mobile-inline-tabs>
11545
- </div>
11763
+ updateCustomColorInputs() {
11764
+ this.customPrimarySurface = this.whitelabelService.primarySurface();
11765
+ this.customPrimaryContent = this.whitelabelService.primaryContent();
11766
+ this.customSecondarySurface = this.whitelabelService.secondarySurface();
11767
+ this.customSecondaryContent = this.whitelabelService.secondaryContent();
11768
+ }
11769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11770
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: WhitelabelDemoModalComponent, isStandalone: true, selector: "ds-whitelabel-demo-modal", ngImport: i0, template: `
11771
+ <!-- Fixed Header -->
11772
+ <div class="modal-header">
11773
+ <div class="header-content">
11774
+ <span class="header-title">Whitelabel</span>
11775
+ <ds-icon-button
11776
+ icon="remixCloseLine"
11777
+ variant="secondary"
11778
+ size="lg"
11779
+ (click)="close()"
11780
+ class="close-button"
11781
+ aria-label="Close">
11782
+ </ds-icon-button>
11546
11783
  </div>
11547
-
11548
- <!-- Content wrapper -->
11549
- <div class="content-wrapper">
11550
- <div class="content-inner">
11551
- <!-- Activity Tab Content -->
11552
- @if (activeTab() === 'activity') {
11553
- <div class="activity-list">
11554
- @for (activity of activities; track activity.id) {
11555
- <div class="activity-item">
11556
- @if (activity.actor) {
11557
- <!-- Avatar with badge for actor activities -->
11558
- <div class="avatar-wrapper">
11559
- <ds-avatar
11560
- [type]="'initials'"
11561
- [initials]="activity.actorInitials || ''"
11562
- size="md" />
11563
-
11564
- <div class="avatar-badge">
11565
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11566
- <path d="M33.9862 5.51709H23.1724V8.82743H26.0413C26.2841 8.82743 26.4827 9.02606 26.4827 9.26881V12.7998C26.4827 13.0426 26.2841 13.2412 26.0413 13.2412H23.1724V14.3447H26.0413C26.2841 14.3447 26.4827 14.5433 26.4827 14.7861V18.3171C26.4827 18.5598 26.2841 18.7585 26.0413 18.7585H23.1724V19.8619H26.0413C26.2841 19.8619 26.4827 20.0605 26.4827 20.3033V23.8343C26.4827 24.0771 26.2841 24.2757 26.0413 24.2757H23.1724V26.2619C23.1724 26.7496 23.0267 27.2043 22.7773 27.5861H27.5862L32 31.9999V27.5861H33.9862C34.7167 27.5861 35.3103 26.9924 35.3103 26.2619V6.84123C35.3103 6.11075 34.7167 5.51709 33.9862 5.51709ZM32 23.8343C32 24.0771 31.8013 24.2757 31.5586 24.2757H28.0276C27.7848 24.2757 27.5862 24.0771 27.5862 23.8343V20.3033C27.5862 20.0605 27.7848 19.8619 28.0276 19.8619H31.5586C31.8013 19.8619 32 20.0605 32 20.3033V23.8343ZM32 18.3171C32 18.5598 31.8013 18.7585 31.5586 18.7585H28.0276C27.7848 18.7585 27.5862 18.5598 27.5862 18.3171V14.7861C27.5862 14.5433 27.7848 14.3447 28.0276 14.3447H31.5586C31.8013 14.3447 32 14.5433 32 14.7861V18.3171ZM32 12.7998C32 13.0426 31.8013 13.2412 31.5586 13.2412H28.0276C27.7848 13.2412 27.5862 13.0426 27.5862 12.7998V9.26881C27.5862 9.02606 27.7848 8.82743 28.0276 8.82743H31.5586C31.8013 8.82743 32 9.02606 32 9.26881V12.7998Z" fill="white"/>
11567
- <path d="M20.7448 0H1.32414C0.593655 0 0 0.593655 0 1.32414V26.2621C0 26.9926 0.593655 27.5862 1.32414 27.5862H3.31034V32L7.72414 27.5862H20.7448C21.4753 27.5862 22.069 26.9926 22.069 26.2621V1.32414C22.069 0.593655 21.4753 0 20.7448 0ZM7.72414 23.8345C7.72414 24.0772 7.52552 24.2759 7.28276 24.2759H3.75172C3.50897 24.2759 3.31034 24.0772 3.31034 23.8345V20.3034C3.31034 20.0607 3.50897 19.8621 3.75172 19.8621H7.28276C7.52552 19.8621 7.72414 20.0607 7.72414 20.3034V23.8345ZM7.72414 18.3172C7.72414 18.56 7.52552 18.7586 7.28276 18.7586H3.75172C3.50897 18.7586 3.31034 18.56 3.31034 18.3172V14.7862C3.31034 14.5434 3.50897 14.3448 3.75172 14.3448H7.28276C7.52552 14.3448 7.72414 14.5434 7.72414 14.7862V18.3172ZM7.72414 12.8C7.72414 13.0428 7.52552 13.2414 7.28276 13.2414H3.75172C3.50897 13.2414 3.31034 13.0428 3.31034 12.8V9.26897C3.31034 9.02621 3.50897 8.82759 3.75172 8.82759H7.28276C7.52552 8.82759 7.72414 9.02621 7.72414 9.26897V12.8ZM7.72414 7.28276C7.72414 7.52552 7.52552 7.72414 7.28276 7.72414H3.75172C3.50897 7.72414 3.31034 7.52552 3.31034 7.28276V3.75172C3.31034 3.50897 3.50897 3.31034 3.75172 3.31034H7.28276C7.52552 3.31034 7.72414 3.50897 7.72414 3.75172V7.28276ZM13.2414 23.8345C13.2414 24.0772 13.0428 24.2759 12.8 24.2759H9.26897C9.02621 24.2759 8.82759 24.0772 8.82759 23.8345V20.3034C8.82759 20.0607 9.02621 19.8621 9.26897 19.8621H12.8C13.0428 19.8621 13.2414 20.0607 13.2414 20.3034V23.8345ZM13.2414 18.3172C13.2414 18.56 13.0428 18.7586 12.8 18.7586H9.26897C9.02621 18.7586 8.82759 18.56 8.82759 18.3172V14.7862C8.82759 14.5434 9.02621 14.3448 9.26897 14.3448H12.8C13.0428 14.3448 13.2414 14.5434 13.2414 14.7862V18.3172ZM13.2414 12.8C13.2414 13.0428 13.0428 13.2414 12.8 13.2414H9.26897C9.02621 13.2414 8.82759 13.0428 8.82759 12.8V9.26897C8.82759 9.02621 9.02621 8.82759 9.26897 8.82759H12.8C13.0428 8.82759 13.2414 9.02621 13.2414 9.26897V12.8ZM13.2414 6.84138V7.28276C13.2414 7.52552 13.0428 7.72414 12.8 7.72414H9.26897C9.02621 7.72414 8.82759 7.52552 8.82759 7.28276V3.75172C8.82759 3.50897 9.02621 3.31034 9.26897 3.31034H12.8C13.0428 3.31034 13.2414 3.50897 13.2414 3.75172V6.84138ZM18.7586 23.8345C18.7586 24.0772 18.56 24.2759 18.3172 24.2759H14.7862C14.5434 24.2759 14.3448 24.0772 14.3448 23.8345V20.3034C14.3448 20.0607 14.5434 19.8621 14.7862 19.8621H18.3172C18.56 19.8621 18.7586 20.0607 18.7586 20.3034V23.8345ZM18.7586 18.3172C18.7586 18.56 18.56 18.7586 18.3172 18.7586H14.7862C14.5434 18.7586 14.3448 18.56 14.3448 18.3172V14.7862C14.3448 14.5434 14.5434 14.3448 14.7862 14.3448H18.3172C18.56 14.3448 18.7586 14.5434 18.7586 14.7862V18.3172ZM18.7586 12.8C18.7586 13.0428 18.56 13.2414 18.3172 13.2414H14.7862C14.5434 13.2414 14.3448 13.0428 14.3448 12.8V9.26897C14.3448 9.02621 14.5434 8.82759 14.7862 8.82759H18.3172C18.56 8.82759 18.7586 9.02621 18.7586 9.26897V12.8ZM18.7586 5.51724V7.28276C18.7586 7.52552 18.56 7.72414 18.3172 7.72414H14.7862C14.5434 7.72414 14.3448 7.52552 14.3448 7.28276V3.75172C14.3448 3.50897 14.5434 3.31034 14.7862 3.31034H18.3172C18.56 3.31034 18.7586 3.50897 18.7586 3.75172V5.51724Z" fill="white"/>
11568
- </svg>
11569
- </div>
11570
- </div>
11571
- } @else {
11572
- <!-- Icon wrapper for non-actor activities -->
11573
- <div class="activity-icon-wrapper">
11574
- <ds-icon
11575
- [name]="activity.iconName"
11576
- size="18px"
11577
- color="secondary" />
11578
- </div>
11579
- }
11580
-
11581
- <div class="activity-content">
11582
- <p class="activity-title">
11583
- @if (activity.actor) {
11584
- <span class="actor-name">{{ activity.actor }}</span>
11585
- <span class="activity-text"> {{ activity.title }}</span>
11586
- } @else {
11587
- <span class="actor-name">{{ activity.title }}</span>
11588
- }
11589
- </p>
11590
- @if (activity.description) {
11591
- <p class="activity-description">{{ activity.description }}</p>
11592
- }
11593
- <div class="activity-timestamp">
11594
- <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
11595
- <span>{{ activity.date }}</span>
11596
- </div>
11597
- </div>
11598
- </div>
11599
- }
11600
- </div>
11601
- }
11602
-
11603
- <!-- Messages Tab Content -->
11604
- @if (activeTab() === 'messages') {
11605
- <div class="messages-list">
11606
- @for (message of messageThreads; track message.id) {
11607
- <ds-mobile-interactive-list-item-message
11608
- [senderName]="message.senderName"
11609
- [senderRole]="message.role"
11610
- [message]="message.message"
11611
- [avatarInitials]="message.senderInitials"
11612
- [unread]="message.unread"
11613
- (messageClick)="openMessage(message.id)">
11614
- </ds-mobile-interactive-list-item-message>
11615
- }
11784
+ </div>
11785
+
11786
+ <!-- Scrollable Content -->
11787
+ <ion-content [scrollY]="true" class="modal-content">
11788
+ <div class="demo-container">
11789
+ <!-- Theme Selection -->
11790
+ <div class="demo-section">
11791
+ <h2>Theme</h2>
11792
+ <div class="theme-buttons">
11793
+ <button class="theme-btn" (click)="applyDefaultTheme()" [class.active]="currentTheme === 'default'">
11794
+ Propbinder
11795
+ </button>
11796
+ <button class="theme-btn" (click)="applyCejTheme()" [class.active]="currentTheme === 'cej'">
11797
+ CEJ
11798
+ </button>
11799
+ <button class="theme-btn" (click)="applyPkaTheme()" [class.active]="currentTheme === 'pka'">
11800
+ PKA
11801
+ </button>
11802
+ <button class="theme-btn" (click)="applyClaveTheme()" [class.active]="currentTheme === 'clave'">
11803
+ Clave
11804
+ </button>
11616
11805
  </div>
11617
- }
11806
+ </div>
11618
11807
 
11619
- <!-- Details Tab Content -->
11620
- @if (activeTab() === 'details') {
11621
- <div class="details-list">
11622
- <!-- Assignee -->
11623
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11624
- <div content-leading>
11625
- <ds-avatar
11626
- [size]="'sm'"
11627
- [type]="'initials'"
11628
- [initials]="'R'" />
11629
- </div>
11630
- <div content-main>
11631
- <div class="detail-label">Sagsbehandler</div>
11632
- <div class="detail-value">Ricki Meihlen</div>
11808
+ <!-- Logo & Logomark Preview -->
11809
+ <div class="demo-section">
11810
+ <h2>Logo Preview</h2>
11811
+ <div class="preview-grid">
11812
+ <div class="preview-tile">
11813
+ <h3>Logo</h3>
11814
+ <div class="logo-preview">
11815
+ <ds-logo variant="full" />
11633
11816
  </div>
11634
- </ds-mobile-list-item-static>
11635
-
11636
- <!-- Technician -->
11637
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11638
- <div content-leading>
11639
- <ds-avatar
11640
- [size]="'sm'"
11817
+ </div>
11818
+ <div class="preview-tile">
11819
+ <h3>Logomark</h3>
11820
+ <div class="logomark-preview">
11821
+ <ds-avatar-with-badge
11641
11822
  [type]="'initials'"
11642
- [initials]="'M'" />
11823
+ [initials]="'KN'"
11824
+ [size]="'md'"
11825
+ [badgePosition]="'bottom-right'"
11826
+ />
11643
11827
  </div>
11644
- <div content-main>
11645
- <div class="detail-label">Tekniker</div>
11646
- <div class="detail-value">Martin Smith</div>
11828
+ </div>
11829
+ </div>
11830
+ </div>
11831
+
11832
+ <!-- Brand Colors -->
11833
+ <div class="demo-section">
11834
+ <h2>Brand Colors</h2>
11835
+ <div class="color-section">
11836
+ <div class="color-swatches">
11837
+ <div class="swatch swatch--primary-surface">
11838
+ <div class="swatch-label">Primary Surface</div>
11839
+ <div class="swatch-value">{{ whitelabelService.primarySurface() }}</div>
11840
+ </div>
11841
+ <div class="swatch swatch--primary-content">
11842
+ <div class="swatch-label">Primary Content</div>
11843
+ <div class="swatch-value">{{ whitelabelService.primaryContent() }}</div>
11647
11844
  </div>
11648
- </ds-mobile-list-item-static>
11649
-
11650
- <!-- Title -->
11651
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11652
- <div content-leading>
11653
- <ds-icon name="remixTextBlock" size="20px" color="tertiary" />
11845
+ <div class="swatch swatch--secondary-surface">
11846
+ <div class="swatch-label">Secondary Surface</div>
11847
+ <div class="swatch-value">{{ whitelabelService.secondarySurface() }}</div>
11654
11848
  </div>
11655
- <div content-main>
11656
- <div class="detail-value">{{ inquiryTitle }}</div>
11849
+ <div class="swatch swatch--secondary-content">
11850
+ <div class="swatch-label">Secondary Content</div>
11851
+ <div class="swatch-value">{{ whitelabelService.secondaryContent() }}</div>
11657
11852
  </div>
11658
- </ds-mobile-list-item-static>
11853
+ </div>
11659
11854
 
11660
- <!-- Description -->
11661
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11662
- <div content-leading>
11663
- <ds-icon name="remixAlignLeft" size="20px" color="tertiary" />
11855
+ <div class="color-inputs">
11856
+ <div class="color-group-label">Primary</div>
11857
+ <div class="color-row">
11858
+ <label>Surface</label>
11859
+ <input
11860
+ type="color"
11861
+ [(ngModel)]="customPrimarySurface"
11862
+ (change)="applyCustomColors()"
11863
+ />
11864
+ <input
11865
+ type="text"
11866
+ [(ngModel)]="customPrimarySurface"
11867
+ (change)="applyCustomColors()"
11868
+ />
11664
11869
  </div>
11665
- <div content-main>
11666
- <div class="detail-value description-text">
11667
- I de sidste tre dage har vi oplevet vedvarende problemer med tørretumbleren i vores lejlighed. På trods af at vi følger betjeningsvejledningen, fejler maskinen konsekvent i at fuldføre sine tørrecyklusser.
11668
- </div>
11669
- <div class="detail-tag">Husholdningsapparater</div>
11870
+ <div class="color-row">
11871
+ <label>Content</label>
11872
+ <input
11873
+ type="color"
11874
+ [(ngModel)]="customPrimaryContent"
11875
+ (change)="applyCustomColors()"
11876
+ />
11877
+ <input
11878
+ type="text"
11879
+ [(ngModel)]="customPrimaryContent"
11880
+ (change)="applyCustomColors()"
11881
+ />
11670
11882
  </div>
11671
- </ds-mobile-list-item-static>
11672
-
11673
- <!-- Photos -->
11674
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11675
- <div content-leading>
11676
- <ds-icon name="remixCameraLine" size="20px" color="tertiary" />
11883
+
11884
+ <div class="color-group-label">Secondary</div>
11885
+ <div class="color-row">
11886
+ <label>Surface</label>
11887
+ <input
11888
+ type="color"
11889
+ [(ngModel)]="customSecondarySurface"
11890
+ (change)="applyCustomColors()"
11891
+ />
11892
+ <input
11893
+ type="text"
11894
+ [(ngModel)]="customSecondarySurface"
11895
+ (change)="applyCustomColors()"
11896
+ />
11677
11897
  </div>
11678
- <div content-main>
11679
- <div class="photo-grid">
11680
- <button class="photo-add">
11681
- <ds-icon name="remixAddLine" size="20px" color="tertiary" />
11682
- </button>
11683
- <!-- Placeholder photos -->
11684
- <div class="photo-item"></div>
11685
- <div class="photo-item"></div>
11686
- <div class="photo-item"></div>
11687
- <div class="photo-item"></div>
11688
- </div>
11898
+ <div class="color-row">
11899
+ <label>Content</label>
11900
+ <input
11901
+ type="color"
11902
+ [(ngModel)]="customSecondaryContent"
11903
+ (change)="applyCustomColors()"
11904
+ />
11905
+ <input
11906
+ type="text"
11907
+ [(ngModel)]="customSecondaryContent"
11908
+ (change)="applyCustomColors()"
11909
+ />
11689
11910
  </div>
11690
- </ds-mobile-list-item-static>
11911
+ </div>
11691
11912
  </div>
11692
- }
11913
+ </div>
11693
11914
  </div>
11694
- </div>
11695
11915
  </ion-content>
11696
- `, isInline: true, styles: ["ion-header{--background: var(--color-brand-secondary, #5d5fef);height:72px;min-height:72px}ion-toolbar{--background: var(--color-brand-secondary, #5d5fef);--color: white;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:100%;min-height:72px}ion-toolbar ion-title{transition:transform .2s ease,opacity .2s ease!important}.header-detail{display:flex;align-items:center;gap:12px;padding:0 20px;height:100%}.back-button{width:36px;height:36px;border-radius:50%;background:#ffffff1a;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s ease;color:#fff;padding:0;flex-shrink:0}.back-button:hover,.back-button:active{background:#ffffff26}.header-detail__title{position:absolute;left:64px;transform:translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important;margin:0;padding:0;--color: white;text-align:left!important}.header-scrolled .header-detail__title{opacity:1!important;pointer-events:auto;transform:translateY(0)}@media (min-width: 768px){.header-detail{padding:16px 24px}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px}.header-expandable__text{margin-bottom:0}@media (min-width: 768px){.header-expandable{padding:40px var(--content-padding-md) 32px var(--content-padding-md)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{background:var(--color-background-neutral-primary, white);border-radius:24px 24px 0 0;flex:1 1 auto;min-height:100%;position:relative;z-index:10;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary)}.content-inner{padding:24px 20px 40px}@media (min-width: 768px){.content-inner{padding:32px}}.activity-list{display:flex;flex-direction:column;gap:32px}.activity-item{display:flex;gap:12px;position:relative}.activity-item:after{content:\"\";position:absolute;bottom:-16px;left:44px;right:8px;height:1px;background:var(--border-color-default, #e5e5e5)}.activity-item:last-child:after{display:none}.activity-icon-wrapper{width:32px;height:32px;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:32px;height:32px}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.messages-list{display:flex;flex-direction:column;gap:8px;margin:-8px}.details-list{display:flex;flex-direction:column;gap:20px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-value.description-text{padding:8px 0}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:flex;gap:8px;margin-top:8px;overflow-x:auto}.photo-add{width:80px;height:80px;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}.photo-item{width:80px;height:80px;border-radius:12px;background:var(--color-background-neutral-secondary, #e5e5e5);flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileListItemStaticComponent, selector: "ds-mobile-list-item-static", inputs: ["leadingSize"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "clickable"], outputs: ["messageClick", "longPress"] }] });
11916
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%;background:var(--color-background-neutral-primary, #ffffff)}.modal-header{flex-shrink:0;background:var(--color-background-neutral-primary, #ffffff);border-bottom:1px solid var(--border-color-default, #e0e0e0);padding:0 16px}ion-content,.modal-content{--background: #ffffff;flex:1;width:100%}.header-content{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px}.header-title{font-family:Brockmann,sans-serif;font-size:17px;font-weight:600;color:var(--color-text-primary, #1a1a1a);flex:1}.close-button{flex-shrink:0;border-radius:50%}.close-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important;background:var(--color-background-neutral-secondary, #f5f5f5)!important;color:var(--color-text-primary, #1a1a1a)!important}.demo-container{padding:20px;max-width:600px;margin:0 auto;width:100%}.demo-section{margin-bottom:32px}.demo-section h2{margin-bottom:16px;font-size:18px;font-weight:600;color:#333}.theme-buttons{display:flex;gap:12px;flex-wrap:wrap}.theme-btn{padding:8px 16px;border-radius:8px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .2s ease;background:#e0e0e0;color:#333}.theme-btn:active{transform:scale(.98)}.theme-btn.active{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.logo-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:var(--color-brand-secondary);border-radius:12px;min-height:80px}.logomark-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:#fff;border:1px solid #e0e0e0;border-radius:12px;min-height:80px}.preview-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.preview-tile{background:#fff;border-radius:12px}.preview-tile h3{font-size:13px;font-weight:600;color:#666;margin-top:0;margin-bottom:12px}.color-section{background:#fff;border-radius:12px}.color-swatches{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}.swatch{padding:16px;border-radius:8px;text-align:center}.swatch-label{font-size:12px;font-weight:600;margin-bottom:4px}.swatch-value{font-size:11px;opacity:.8;font-family:monospace}.swatch--primary-surface{background:var(--color-primary-surface);color:var(--color-primary-content)}.swatch--primary-content{background:var(--color-primary-content);color:var(--color-primary-surface);border:1px solid #e0e0e0}.swatch--secondary-surface{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.swatch--secondary-content{background:var(--color-secondary-content);color:var(--color-secondary-surface);border:1px solid #e0e0e0}.color-inputs{display:flex;flex-direction:column;gap:12px}.color-group-label{font-size:13px;font-weight:600;color:#333;margin-top:8px}.color-row{display:flex;align-items:center;gap:12px}.color-row label{min-width:70px;font-size:13px;color:#666}.color-row input[type=color]{width:40px;height:32px;border:none;border-radius:6px;cursor:pointer;padding:0}.color-row input[type=text]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px}@supports (padding: env(safe-area-inset-bottom)){.demo-container{padding-bottom:calc(20px + env(safe-area-inset-bottom))}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsAvatarWithBadgeComponent, selector: "ds-avatar-with-badge", inputs: ["type", "size", "initials", "src", "iconName", "showBadge", "badgePosition"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
11697
11917
  }
11698
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiryDetailPageComponent, decorators: [{
11918
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalComponent, decorators: [{
11699
11919
  type: Component,
11700
- args: [{ selector: 'app-mobile-inquiry-detail-page', standalone: true, imports: [
11920
+ args: [{ selector: 'ds-whitelabel-demo-modal', standalone: true, imports: [
11701
11921
  CommonModule,
11702
- IonHeader,
11703
- IonToolbar,
11704
- IonTitle,
11922
+ FormsModule,
11705
11923
  IonContent,
11706
- IonRefresher,
11707
- IonRefresherContent,
11708
- DsIconComponent,
11709
- DsAvatarComponent,
11710
- DsMobileInlineTabsComponent,
11711
- DsMobileListItemStaticComponent,
11712
- DsMobileInteractiveListItemMessageComponent
11713
- ], host: {
11714
- class: 'ion-page'
11715
- }, template: `
11716
- <!-- Fixed header at top -->
11717
- <ion-header>
11718
- <ion-toolbar>
11719
- <div class="header-detail">
11720
- <!-- Back Button -->
11721
- <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11722
- <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11723
- </button>
11724
-
11725
- <!-- Title - fades in on scroll -->
11726
- <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11727
- </div>
11728
- </ion-toolbar>
11729
- </ion-header>
11730
-
11731
- <!-- Content with expandable header -->
11732
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11733
- <!-- Pull to refresh (only on native iOS/Android) -->
11734
- @if (isNativePlatform()) {
11735
- <ion-refresher
11736
- slot="fixed"
11737
- (ionRefresh)="handleRefresh($event)"
11738
- [pullFactor]="0.4"
11739
- [pullMin]="80"
11740
- [pullMax]="240"
11741
- closeDuration="600ms">
11742
- <ion-refresher-content
11743
- pullingIcon="remixArrowDownS"
11744
- refreshingSpinner="lines">
11745
- </ion-refresher-content>
11746
- </ion-refresher>
11747
- }
11748
-
11749
- <!-- Expandable header section (purple background) -->
11750
- <div class="header-expandable">
11751
- <div class="header-expandable-inner">
11752
- <div class="header-expandable__text">
11753
- <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11754
- </div>
11755
-
11756
- <!-- Tabs in header -->
11757
- <ds-mobile-inline-tabs
11758
- [tabs]="tabItems"
11759
- [activeTab]="activeTab()"
11760
- (tabChange)="setActiveTab($event)">
11761
- </ds-mobile-inline-tabs>
11762
- </div>
11924
+ DsLogoComponent,
11925
+ DsAvatarWithBadgeComponent,
11926
+ DsIconButtonComponent
11927
+ ], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
11928
+ <!-- Fixed Header -->
11929
+ <div class="modal-header">
11930
+ <div class="header-content">
11931
+ <span class="header-title">Whitelabel</span>
11932
+ <ds-icon-button
11933
+ icon="remixCloseLine"
11934
+ variant="secondary"
11935
+ size="lg"
11936
+ (click)="close()"
11937
+ class="close-button"
11938
+ aria-label="Close">
11939
+ </ds-icon-button>
11763
11940
  </div>
11764
-
11765
- <!-- Content wrapper -->
11766
- <div class="content-wrapper">
11767
- <div class="content-inner">
11768
- <!-- Activity Tab Content -->
11769
- @if (activeTab() === 'activity') {
11770
- <div class="activity-list">
11771
- @for (activity of activities; track activity.id) {
11772
- <div class="activity-item">
11773
- @if (activity.actor) {
11774
- <!-- Avatar with badge for actor activities -->
11775
- <div class="avatar-wrapper">
11776
- <ds-avatar
11777
- [type]="'initials'"
11778
- [initials]="activity.actorInitials || ''"
11779
- size="md" />
11780
-
11781
- <div class="avatar-badge">
11782
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11783
- <path d="M33.9862 5.51709H23.1724V8.82743H26.0413C26.2841 8.82743 26.4827 9.02606 26.4827 9.26881V12.7998C26.4827 13.0426 26.2841 13.2412 26.0413 13.2412H23.1724V14.3447H26.0413C26.2841 14.3447 26.4827 14.5433 26.4827 14.7861V18.3171C26.4827 18.5598 26.2841 18.7585 26.0413 18.7585H23.1724V19.8619H26.0413C26.2841 19.8619 26.4827 20.0605 26.4827 20.3033V23.8343C26.4827 24.0771 26.2841 24.2757 26.0413 24.2757H23.1724V26.2619C23.1724 26.7496 23.0267 27.2043 22.7773 27.5861H27.5862L32 31.9999V27.5861H33.9862C34.7167 27.5861 35.3103 26.9924 35.3103 26.2619V6.84123C35.3103 6.11075 34.7167 5.51709 33.9862 5.51709ZM32 23.8343C32 24.0771 31.8013 24.2757 31.5586 24.2757H28.0276C27.7848 24.2757 27.5862 24.0771 27.5862 23.8343V20.3033C27.5862 20.0605 27.7848 19.8619 28.0276 19.8619H31.5586C31.8013 19.8619 32 20.0605 32 20.3033V23.8343ZM32 18.3171C32 18.5598 31.8013 18.7585 31.5586 18.7585H28.0276C27.7848 18.7585 27.5862 18.5598 27.5862 18.3171V14.7861C27.5862 14.5433 27.7848 14.3447 28.0276 14.3447H31.5586C31.8013 14.3447 32 14.5433 32 14.7861V18.3171ZM32 12.7998C32 13.0426 31.8013 13.2412 31.5586 13.2412H28.0276C27.7848 13.2412 27.5862 13.0426 27.5862 12.7998V9.26881C27.5862 9.02606 27.7848 8.82743 28.0276 8.82743H31.5586C31.8013 8.82743 32 9.02606 32 9.26881V12.7998Z" fill="white"/>
11784
- <path d="M20.7448 0H1.32414C0.593655 0 0 0.593655 0 1.32414V26.2621C0 26.9926 0.593655 27.5862 1.32414 27.5862H3.31034V32L7.72414 27.5862H20.7448C21.4753 27.5862 22.069 26.9926 22.069 26.2621V1.32414C22.069 0.593655 21.4753 0 20.7448 0ZM7.72414 23.8345C7.72414 24.0772 7.52552 24.2759 7.28276 24.2759H3.75172C3.50897 24.2759 3.31034 24.0772 3.31034 23.8345V20.3034C3.31034 20.0607 3.50897 19.8621 3.75172 19.8621H7.28276C7.52552 19.8621 7.72414 20.0607 7.72414 20.3034V23.8345ZM7.72414 18.3172C7.72414 18.56 7.52552 18.7586 7.28276 18.7586H3.75172C3.50897 18.7586 3.31034 18.56 3.31034 18.3172V14.7862C3.31034 14.5434 3.50897 14.3448 3.75172 14.3448H7.28276C7.52552 14.3448 7.72414 14.5434 7.72414 14.7862V18.3172ZM7.72414 12.8C7.72414 13.0428 7.52552 13.2414 7.28276 13.2414H3.75172C3.50897 13.2414 3.31034 13.0428 3.31034 12.8V9.26897C3.31034 9.02621 3.50897 8.82759 3.75172 8.82759H7.28276C7.52552 8.82759 7.72414 9.02621 7.72414 9.26897V12.8ZM7.72414 7.28276C7.72414 7.52552 7.52552 7.72414 7.28276 7.72414H3.75172C3.50897 7.72414 3.31034 7.52552 3.31034 7.28276V3.75172C3.31034 3.50897 3.50897 3.31034 3.75172 3.31034H7.28276C7.52552 3.31034 7.72414 3.50897 7.72414 3.75172V7.28276ZM13.2414 23.8345C13.2414 24.0772 13.0428 24.2759 12.8 24.2759H9.26897C9.02621 24.2759 8.82759 24.0772 8.82759 23.8345V20.3034C8.82759 20.0607 9.02621 19.8621 9.26897 19.8621H12.8C13.0428 19.8621 13.2414 20.0607 13.2414 20.3034V23.8345ZM13.2414 18.3172C13.2414 18.56 13.0428 18.7586 12.8 18.7586H9.26897C9.02621 18.7586 8.82759 18.56 8.82759 18.3172V14.7862C8.82759 14.5434 9.02621 14.3448 9.26897 14.3448H12.8C13.0428 14.3448 13.2414 14.5434 13.2414 14.7862V18.3172ZM13.2414 12.8C13.2414 13.0428 13.0428 13.2414 12.8 13.2414H9.26897C9.02621 13.2414 8.82759 13.0428 8.82759 12.8V9.26897C8.82759 9.02621 9.02621 8.82759 9.26897 8.82759H12.8C13.0428 8.82759 13.2414 9.02621 13.2414 9.26897V12.8ZM13.2414 6.84138V7.28276C13.2414 7.52552 13.0428 7.72414 12.8 7.72414H9.26897C9.02621 7.72414 8.82759 7.52552 8.82759 7.28276V3.75172C8.82759 3.50897 9.02621 3.31034 9.26897 3.31034H12.8C13.0428 3.31034 13.2414 3.50897 13.2414 3.75172V6.84138ZM18.7586 23.8345C18.7586 24.0772 18.56 24.2759 18.3172 24.2759H14.7862C14.5434 24.2759 14.3448 24.0772 14.3448 23.8345V20.3034C14.3448 20.0607 14.5434 19.8621 14.7862 19.8621H18.3172C18.56 19.8621 18.7586 20.0607 18.7586 20.3034V23.8345ZM18.7586 18.3172C18.7586 18.56 18.56 18.7586 18.3172 18.7586H14.7862C14.5434 18.7586 14.3448 18.56 14.3448 18.3172V14.7862C14.3448 14.5434 14.5434 14.3448 14.7862 14.3448H18.3172C18.56 14.3448 18.7586 14.5434 18.7586 14.7862V18.3172ZM18.7586 12.8C18.7586 13.0428 18.56 13.2414 18.3172 13.2414H14.7862C14.5434 13.2414 14.3448 13.0428 14.3448 12.8V9.26897C14.3448 9.02621 14.5434 8.82759 14.7862 8.82759H18.3172C18.56 8.82759 18.7586 9.02621 18.7586 9.26897V12.8ZM18.7586 5.51724V7.28276C18.7586 7.52552 18.56 7.72414 18.3172 7.72414H14.7862C14.5434 7.72414 14.3448 7.52552 14.3448 7.28276V3.75172C14.3448 3.50897 14.5434 3.31034 14.7862 3.31034H18.3172C18.56 3.31034 18.7586 3.50897 18.7586 3.75172V5.51724Z" fill="white"/>
11785
- </svg>
11786
- </div>
11787
- </div>
11788
- } @else {
11789
- <!-- Icon wrapper for non-actor activities -->
11790
- <div class="activity-icon-wrapper">
11791
- <ds-icon
11792
- [name]="activity.iconName"
11793
- size="18px"
11794
- color="secondary" />
11795
- </div>
11796
- }
11797
-
11798
- <div class="activity-content">
11799
- <p class="activity-title">
11800
- @if (activity.actor) {
11801
- <span class="actor-name">{{ activity.actor }}</span>
11802
- <span class="activity-text"> {{ activity.title }}</span>
11803
- } @else {
11804
- <span class="actor-name">{{ activity.title }}</span>
11805
- }
11806
- </p>
11807
- @if (activity.description) {
11808
- <p class="activity-description">{{ activity.description }}</p>
11809
- }
11810
- <div class="activity-timestamp">
11811
- <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
11812
- <span>{{ activity.date }}</span>
11813
- </div>
11814
- </div>
11815
- </div>
11816
- }
11817
- </div>
11818
- }
11819
-
11820
- <!-- Messages Tab Content -->
11821
- @if (activeTab() === 'messages') {
11822
- <div class="messages-list">
11823
- @for (message of messageThreads; track message.id) {
11824
- <ds-mobile-interactive-list-item-message
11825
- [senderName]="message.senderName"
11826
- [senderRole]="message.role"
11827
- [message]="message.message"
11828
- [avatarInitials]="message.senderInitials"
11829
- [unread]="message.unread"
11830
- (messageClick)="openMessage(message.id)">
11831
- </ds-mobile-interactive-list-item-message>
11832
- }
11941
+ </div>
11942
+
11943
+ <!-- Scrollable Content -->
11944
+ <ion-content [scrollY]="true" class="modal-content">
11945
+ <div class="demo-container">
11946
+ <!-- Theme Selection -->
11947
+ <div class="demo-section">
11948
+ <h2>Theme</h2>
11949
+ <div class="theme-buttons">
11950
+ <button class="theme-btn" (click)="applyDefaultTheme()" [class.active]="currentTheme === 'default'">
11951
+ Propbinder
11952
+ </button>
11953
+ <button class="theme-btn" (click)="applyCejTheme()" [class.active]="currentTheme === 'cej'">
11954
+ CEJ
11955
+ </button>
11956
+ <button class="theme-btn" (click)="applyPkaTheme()" [class.active]="currentTheme === 'pka'">
11957
+ PKA
11958
+ </button>
11959
+ <button class="theme-btn" (click)="applyClaveTheme()" [class.active]="currentTheme === 'clave'">
11960
+ Clave
11961
+ </button>
11833
11962
  </div>
11834
- }
11963
+ </div>
11835
11964
 
11836
- <!-- Details Tab Content -->
11837
- @if (activeTab() === 'details') {
11838
- <div class="details-list">
11839
- <!-- Assignee -->
11840
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11841
- <div content-leading>
11842
- <ds-avatar
11843
- [size]="'sm'"
11844
- [type]="'initials'"
11845
- [initials]="'R'" />
11846
- </div>
11847
- <div content-main>
11848
- <div class="detail-label">Sagsbehandler</div>
11849
- <div class="detail-value">Ricki Meihlen</div>
11965
+ <!-- Logo & Logomark Preview -->
11966
+ <div class="demo-section">
11967
+ <h2>Logo Preview</h2>
11968
+ <div class="preview-grid">
11969
+ <div class="preview-tile">
11970
+ <h3>Logo</h3>
11971
+ <div class="logo-preview">
11972
+ <ds-logo variant="full" />
11850
11973
  </div>
11851
- </ds-mobile-list-item-static>
11852
-
11853
- <!-- Technician -->
11854
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11855
- <div content-leading>
11856
- <ds-avatar
11857
- [size]="'sm'"
11974
+ </div>
11975
+ <div class="preview-tile">
11976
+ <h3>Logomark</h3>
11977
+ <div class="logomark-preview">
11978
+ <ds-avatar-with-badge
11858
11979
  [type]="'initials'"
11859
- [initials]="'M'" />
11980
+ [initials]="'KN'"
11981
+ [size]="'md'"
11982
+ [badgePosition]="'bottom-right'"
11983
+ />
11860
11984
  </div>
11861
- <div content-main>
11862
- <div class="detail-label">Tekniker</div>
11863
- <div class="detail-value">Martin Smith</div>
11985
+ </div>
11986
+ </div>
11987
+ </div>
11988
+
11989
+ <!-- Brand Colors -->
11990
+ <div class="demo-section">
11991
+ <h2>Brand Colors</h2>
11992
+ <div class="color-section">
11993
+ <div class="color-swatches">
11994
+ <div class="swatch swatch--primary-surface">
11995
+ <div class="swatch-label">Primary Surface</div>
11996
+ <div class="swatch-value">{{ whitelabelService.primarySurface() }}</div>
11864
11997
  </div>
11865
- </ds-mobile-list-item-static>
11866
-
11867
- <!-- Title -->
11868
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11869
- <div content-leading>
11870
- <ds-icon name="remixTextBlock" size="20px" color="tertiary" />
11998
+ <div class="swatch swatch--primary-content">
11999
+ <div class="swatch-label">Primary Content</div>
12000
+ <div class="swatch-value">{{ whitelabelService.primaryContent() }}</div>
11871
12001
  </div>
11872
- <div content-main>
11873
- <div class="detail-value">{{ inquiryTitle }}</div>
12002
+ <div class="swatch swatch--secondary-surface">
12003
+ <div class="swatch-label">Secondary Surface</div>
12004
+ <div class="swatch-value">{{ whitelabelService.secondarySurface() }}</div>
11874
12005
  </div>
11875
- </ds-mobile-list-item-static>
12006
+ <div class="swatch swatch--secondary-content">
12007
+ <div class="swatch-label">Secondary Content</div>
12008
+ <div class="swatch-value">{{ whitelabelService.secondaryContent() }}</div>
12009
+ </div>
12010
+ </div>
11876
12011
 
11877
- <!-- Description -->
11878
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11879
- <div content-leading>
11880
- <ds-icon name="remixAlignLeft" size="20px" color="tertiary" />
12012
+ <div class="color-inputs">
12013
+ <div class="color-group-label">Primary</div>
12014
+ <div class="color-row">
12015
+ <label>Surface</label>
12016
+ <input
12017
+ type="color"
12018
+ [(ngModel)]="customPrimarySurface"
12019
+ (change)="applyCustomColors()"
12020
+ />
12021
+ <input
12022
+ type="text"
12023
+ [(ngModel)]="customPrimarySurface"
12024
+ (change)="applyCustomColors()"
12025
+ />
11881
12026
  </div>
11882
- <div content-main>
11883
- <div class="detail-value description-text">
11884
- I de sidste tre dage har vi oplevet vedvarende problemer med tørretumbleren i vores lejlighed. På trods af at vi følger betjeningsvejledningen, fejler maskinen konsekvent i at fuldføre sine tørrecyklusser.
11885
- </div>
11886
- <div class="detail-tag">Husholdningsapparater</div>
12027
+ <div class="color-row">
12028
+ <label>Content</label>
12029
+ <input
12030
+ type="color"
12031
+ [(ngModel)]="customPrimaryContent"
12032
+ (change)="applyCustomColors()"
12033
+ />
12034
+ <input
12035
+ type="text"
12036
+ [(ngModel)]="customPrimaryContent"
12037
+ (change)="applyCustomColors()"
12038
+ />
11887
12039
  </div>
11888
- </ds-mobile-list-item-static>
11889
-
11890
- <!-- Photos -->
11891
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11892
- <div content-leading>
11893
- <ds-icon name="remixCameraLine" size="20px" color="tertiary" />
12040
+
12041
+ <div class="color-group-label">Secondary</div>
12042
+ <div class="color-row">
12043
+ <label>Surface</label>
12044
+ <input
12045
+ type="color"
12046
+ [(ngModel)]="customSecondarySurface"
12047
+ (change)="applyCustomColors()"
12048
+ />
12049
+ <input
12050
+ type="text"
12051
+ [(ngModel)]="customSecondarySurface"
12052
+ (change)="applyCustomColors()"
12053
+ />
11894
12054
  </div>
11895
- <div content-main>
11896
- <div class="photo-grid">
11897
- <button class="photo-add">
11898
- <ds-icon name="remixAddLine" size="20px" color="tertiary" />
11899
- </button>
11900
- <!-- Placeholder photos -->
11901
- <div class="photo-item"></div>
11902
- <div class="photo-item"></div>
11903
- <div class="photo-item"></div>
11904
- <div class="photo-item"></div>
11905
- </div>
12055
+ <div class="color-row">
12056
+ <label>Content</label>
12057
+ <input
12058
+ type="color"
12059
+ [(ngModel)]="customSecondaryContent"
12060
+ (change)="applyCustomColors()"
12061
+ />
12062
+ <input
12063
+ type="text"
12064
+ [(ngModel)]="customSecondaryContent"
12065
+ (change)="applyCustomColors()"
12066
+ />
11906
12067
  </div>
11907
- </ds-mobile-list-item-static>
12068
+ </div>
11908
12069
  </div>
11909
- }
12070
+ </div>
11910
12071
  </div>
11911
- </div>
11912
12072
  </ion-content>
11913
- `, styles: ["ion-header{--background: var(--color-brand-secondary, #5d5fef);height:72px;min-height:72px}ion-toolbar{--background: var(--color-brand-secondary, #5d5fef);--color: white;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:100%;min-height:72px}ion-toolbar ion-title{transition:transform .2s ease,opacity .2s ease!important}.header-detail{display:flex;align-items:center;gap:12px;padding:0 20px;height:100%}.back-button{width:36px;height:36px;border-radius:50%;background:#ffffff1a;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s ease;color:#fff;padding:0;flex-shrink:0}.back-button:hover,.back-button:active{background:#ffffff26}.header-detail__title{position:absolute;left:64px;transform:translateY(-100%);font-size:var(--font-size-base);font-weight:600;color:#fff;opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important;margin:0;padding:0;--color: white;text-align:left!important}.header-scrolled .header-detail__title{opacity:1!important;pointer-events:auto;transform:translateY(0)}@media (min-width: 768px){.header-detail{padding:16px 24px}}ion-content{--background: var(--color-brand-secondary);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}.plt-ios ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){ion-content{border-radius:24px 24px 0 0}}ion-content::part(scroll){-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}ion-refresher{z-index:0}ion-refresher-content{--color: white}.header-expandable{background:var(--color-brand-secondary);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out}.header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px}.header-expandable__text{margin-bottom:0}@media (min-width: 768px){.header-expandable{padding:40px var(--content-padding-md) 32px var(--content-padding-md)}}@media (min-width: 992px){.header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){.header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}.content-wrapper{background:var(--color-background-neutral-primary, white);border-radius:24px 24px 0 0;flex:1 1 auto;min-height:100%;position:relative;z-index:10;box-shadow:0 200vh 0 0 var(--color-background-neutral-primary)}.content-inner{padding:24px 20px 40px}@media (min-width: 768px){.content-inner{padding:32px}}.activity-list{display:flex;flex-direction:column;gap:32px}.activity-item{display:flex;gap:12px;position:relative}.activity-item:after{content:\"\";position:absolute;bottom:-16px;left:44px;right:8px;height:1px;background:var(--border-color-default, #e5e5e5)}.activity-item:last-child:after{display:none}.activity-icon-wrapper{width:32px;height:32px;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:32px;height:32px}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.messages-list{display:flex;flex-direction:column;gap:8px;margin:-8px}.details-list{display:flex;flex-direction:column;gap:20px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-value.description-text{padding:8px 0}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:flex;gap:8px;margin-top:8px;overflow-x:auto}.photo-add{width:80px;height:80px;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}.photo-item{width:80px;height:80px;border-radius:12px;background:var(--color-background-neutral-secondary, #e5e5e5);flex-shrink:0}\n"] }]
11914
- }], ctorParameters: () => [{ type: UserService }, { type: i1.NavController }, { type: i0.ElementRef }], propDecorators: { ionContent: [{
11915
- type: ViewChild,
11916
- args: [IonContent]
11917
- }] } });
12073
+ `, styles: [":host{display:flex;flex-direction:column;height:100%;width:100%;background:var(--color-background-neutral-primary, #ffffff)}.modal-header{flex-shrink:0;background:var(--color-background-neutral-primary, #ffffff);border-bottom:1px solid var(--border-color-default, #e0e0e0);padding:0 16px}ion-content,.modal-content{--background: #ffffff;flex:1;width:100%}.header-content{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px}.header-title{font-family:Brockmann,sans-serif;font-size:17px;font-weight:600;color:var(--color-text-primary, #1a1a1a);flex:1}.close-button{flex-shrink:0;border-radius:50%}.close-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important;background:var(--color-background-neutral-secondary, #f5f5f5)!important;color:var(--color-text-primary, #1a1a1a)!important}.demo-container{padding:20px;max-width:600px;margin:0 auto;width:100%}.demo-section{margin-bottom:32px}.demo-section h2{margin-bottom:16px;font-size:18px;font-weight:600;color:#333}.theme-buttons{display:flex;gap:12px;flex-wrap:wrap}.theme-btn{padding:8px 16px;border-radius:8px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .2s ease;background:#e0e0e0;color:#333}.theme-btn:active{transform:scale(.98)}.theme-btn.active{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.logo-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:var(--color-brand-secondary);border-radius:12px;min-height:80px}.logomark-preview{display:flex;align-items:center;justify-content:center;padding:24px;background:#fff;border:1px solid #e0e0e0;border-radius:12px;min-height:80px}.preview-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.preview-tile{background:#fff;border-radius:12px}.preview-tile h3{font-size:13px;font-weight:600;color:#666;margin-top:0;margin-bottom:12px}.color-section{background:#fff;border-radius:12px}.color-swatches{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}.swatch{padding:16px;border-radius:8px;text-align:center}.swatch-label{font-size:12px;font-weight:600;margin-bottom:4px}.swatch-value{font-size:11px;opacity:.8;font-family:monospace}.swatch--primary-surface{background:var(--color-primary-surface);color:var(--color-primary-content)}.swatch--primary-content{background:var(--color-primary-content);color:var(--color-primary-surface);border:1px solid #e0e0e0}.swatch--secondary-surface{background:var(--color-secondary-surface);color:var(--color-secondary-content)}.swatch--secondary-content{background:var(--color-secondary-content);color:var(--color-secondary-surface);border:1px solid #e0e0e0}.color-inputs{display:flex;flex-direction:column;gap:12px}.color-group-label{font-size:13px;font-weight:600;color:#333;margin-top:8px}.color-row{display:flex;align-items:center;gap:12px}.color-row label{min-width:70px;font-size:13px;color:#666}.color-row input[type=color]{width:40px;height:32px;border:none;border-radius:6px;cursor:pointer;padding:0}.color-row input[type=text]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-family:monospace;font-size:13px}@supports (padding: env(safe-area-inset-bottom)){.demo-container{padding-bottom:calc(20px + env(safe-area-inset-bottom))}}\n"] }]
12074
+ }] });
12075
+
12076
+ /**
12077
+ * WhitelabelDemoModalService
12078
+ *
12079
+ * Service for displaying the whitelabel demo in a full-screen modal.
12080
+ * Built on Ionic's modal system with native gestures and animations.
12081
+ *
12082
+ * @example
12083
+ * ```typescript
12084
+ * constructor(private whitelabelModal: WhitelabelDemoModalService) {}
12085
+ *
12086
+ * async openDemo() {
12087
+ * await this.whitelabelModal.open();
12088
+ * }
12089
+ * ```
12090
+ */
12091
+ class WhitelabelDemoModalService {
12092
+ modalController;
12093
+ constructor(modalController) {
12094
+ this.modalController = modalController;
12095
+ }
12096
+ /**
12097
+ * Open the whitelabel demo modal
12098
+ *
12099
+ * @returns Promise that resolves when the modal is presented
12100
+ */
12101
+ async open() {
12102
+ try {
12103
+ console.log('[WhitelabelDemoModal] Opening...');
12104
+ const modal = await this.modalController.create({
12105
+ component: WhitelabelDemoModalComponent,
12106
+ cssClass: 'ds-whitelabel-demo-modal',
12107
+ mode: 'ios',
12108
+ presentingElement: document.querySelector('ion-router-outlet') || undefined,
12109
+ backdropDismiss: true,
12110
+ showBackdrop: true,
12111
+ animated: true,
12112
+ keyboardClose: true
12113
+ });
12114
+ console.log('[WhitelabelDemoModal] Modal created, presenting...');
12115
+ await modal.present();
12116
+ console.log('[WhitelabelDemoModal] Modal presented');
12117
+ }
12118
+ catch (error) {
12119
+ console.error('[WhitelabelDemoModal] Error opening modal:', error);
12120
+ throw error;
12121
+ }
12122
+ }
12123
+ /**
12124
+ * Close the currently open whitelabel demo modal
12125
+ *
12126
+ * @param data Optional data to pass back when dismissing
12127
+ * @returns Promise that resolves when the modal is dismissed
12128
+ */
12129
+ async close(data) {
12130
+ return this.modalController.dismiss(data);
12131
+ }
12132
+ /**
12133
+ * Get the top-most modal if one exists
12134
+ *
12135
+ * @returns Promise that resolves to the modal element or undefined
12136
+ */
12137
+ async getTop() {
12138
+ return this.modalController.getTop();
12139
+ }
12140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable });
12141
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, providedIn: 'root' });
12142
+ }
12143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: WhitelabelDemoModalService, decorators: [{
12144
+ type: Injectable,
12145
+ args: [{
12146
+ providedIn: 'root'
12147
+ }]
12148
+ }], ctorParameters: () => [{ type: i1.ModalController }] });
11918
12149
 
11919
12150
  /**
11920
12151
  * MobileTabsExampleComponent
@@ -11939,6 +12170,8 @@ class MobileTabsExampleComponent {
11939
12170
  // Configure user avatar globally - this is now the single source of truth
11940
12171
  this.userService.setAvatarInitials('LM');
11941
12172
  this.userService.setAvatarType('initials');
12173
+ // Set profile menu items globally - this will be used by both tab bar and page-main components
12174
+ this.userService.setProfileMenuItems(this.profileMenuItems);
11942
12175
  }
11943
12176
  tabs = [
11944
12177
  {
@@ -11972,7 +12205,9 @@ class MobileTabsExampleComponent {
11972
12205
  ];
11973
12206
  /**
11974
12207
  * Profile menu items configuration.
11975
- * Define once here, and the tab bar component handles opening/closing the menu.
12208
+ * Define once here - this is set globally in UserService in ngOnInit(),
12209
+ * so it will be used by both ds-mobile-tab-bar and ds-mobile-page-main components
12210
+ * throughout the entire application.
11976
12211
  */
11977
12212
  profileMenuItems = [
11978
12213
  {
@@ -13034,5 +13269,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
13034
13269
  * Generated bundle index. Do not edit.
13035
13270
  */
13036
13271
 
13037
- export { ActionCommentComponent, ActionLikeComponent, ContentRowComponent, DsMobileActionsBottomSheetComponent, DsMobileBottomSheetService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileContentSectionComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileLongPressDirective, DsMobileModalService, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobileTabBarComponent, DsMobileTabsComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, SectionHeaderComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, UserService, WhitelabelDemoPage, WhitelabelService, customBackTransition, customPageTransition };
13272
+ export { ActionCommentComponent, ActionLikeComponent, ContentRowComponent, DsMobileActionsBottomSheetComponent, DsMobileBottomSheetService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileContentSectionComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileLongPressDirective, DsMobileModalService, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobileTabBarComponent, DsMobileTabsComponent, DsTextInputComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, SectionHeaderComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, UserService, WhitelabelDemoPage, WhitelabelService, customBackTransition, customPageTransition };
13038
13273
  //# sourceMappingURL=propbinder-mobile-design.mjs.map