@propbinder/mobile-design 0.1.16 → 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,11 +1,11 @@
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, forwardRef } 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';
@@ -1815,1108 +1815,576 @@ 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
2776
- *
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
2387
+ * DsMobileContentComponent
2920
2388
  *
2921
2389
  * Main content container for mobile pages with flexible layout options.
2922
2390
  * Provides consistent spacing and layout patterns.
@@ -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'
@@ -9774,40 +9245,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
9774
9245
 
9775
9246
  // Mobile Page Components
9776
9247
 
9777
- /**
9778
- * User service for managing current user data globally
9779
- */
9780
- class UserService {
9781
- // User avatar configuration
9782
- _avatarInitials = signal('LM', ...(ngDevMode ? [{ debugName: "_avatarInitials" }] : []));
9783
- _avatarType = signal('initials', ...(ngDevMode ? [{ debugName: "_avatarType" }] : []));
9784
- _avatarSrc = signal('', ...(ngDevMode ? [{ debugName: "_avatarSrc" }] : []));
9785
- // Readonly computed values
9786
- avatarInitials = this._avatarInitials.asReadonly();
9787
- avatarType = this._avatarType.asReadonly();
9788
- avatarSrc = this._avatarSrc.asReadonly();
9789
- /**
9790
- * Update avatar configuration
9791
- */
9792
- setAvatarInitials(initials) {
9793
- this._avatarInitials.set(initials);
9794
- }
9795
- setAvatarType(type) {
9796
- this._avatarType.set(type);
9797
- }
9798
- setAvatarSrc(src) {
9799
- this._avatarSrc.set(src);
9800
- }
9801
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
9802
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, providedIn: 'root' });
9803
- }
9804
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: UserService, decorators: [{
9805
- type: Injectable,
9806
- args: [{
9807
- providedIn: 'root'
9808
- }]
9809
- }] });
9810
-
9811
9248
  class MobileCommunityPageComponent {
9812
9249
  router;
9813
9250
  route;
@@ -10453,7 +9890,7 @@ class MobileCommunityPageComponent {
10453
9890
  </div>
10454
9891
  </ds-mobile-content>
10455
9892
  </ds-mobile-page-main>
10456
- `, 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"] }] });
10457
9894
  }
10458
9895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileCommunityPageComponent, decorators: [{
10459
9896
  type: Component,
@@ -10982,7 +10419,7 @@ class MobileHandbookPageComponent {
10982
10419
  </ds-mobile-content-section>
10983
10420
  </ds-mobile-content>
10984
10421
  </ds-mobile-page-main>
10985
- `, 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"] }] });
10986
10423
  }
10987
10424
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileHandbookPageComponent, decorators: [{
10988
10425
  type: Component,
@@ -11173,7 +10610,7 @@ class MobileHomePageComponent {
11173
10610
  </ds-mobile-content-section>
11174
10611
  </ds-mobile-content>
11175
10612
  </ds-mobile-page-main>
11176
- `, 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" }] });
11177
10614
  }
11178
10615
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileHomePageComponent, decorators: [{
11179
10616
  type: Component,
@@ -11501,7 +10938,7 @@ class MobileInquiriesPageComponent {
11501
10938
  </div>
11502
10939
  </ds-mobile-content>
11503
10940
  </ds-mobile-page-main>
11504
- `, 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"] }] });
11505
10942
  }
11506
10943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MobileInquiriesPageComponent, decorators: [{
11507
10944
  type: Component,
@@ -11528,600 +10965,1187 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
11528
10965
  </ds-mobile-inline-tabs>
11529
10966
  </div>
11530
10967
 
11531
- <ds-mobile-content>
11532
- <div class="inquiries-container">
11533
- @if (filteredInquiries().length > 0) {
11534
- <div class="inquiry-list-wrapper">
11535
- @for (inquiry of filteredInquiries(); track inquiry.id; let idx = $index) {
11536
- <ds-mobile-interactive-list-item-inquiry
11537
- [title]="inquiry.title"
11538
- [description]="inquiry.description"
11539
- [status]="inquiry.status"
11540
- [timestamp]="inquiry.timestamp"
11541
- [iconName]="getInquiryIcon(inquiry.category)"
11542
- [clickable]="true"
11543
- [showChevron]="false"
11544
- (inquiryClick)="openInquiryDetail(inquiry.id)"
11545
- (longPress)="showInquiryActions(inquiry.id)">
11546
- </ds-mobile-interactive-list-item-inquiry>
11547
-
11548
- }
11549
- </div>
11550
- } @else {
11551
- <!-- Empty state -->
11552
- <div class="empty-state">
11553
- <ds-icon name="remixInboxLine" size="48px" color="tertiary" />
11554
- <h3 class="empty-state-title">Ingen henvendelser endnu</h3>
11555
- <p class="empty-state-description">
11556
- @if (filterStatus() === 'open') {
11557
- Du har ingen åbne henvendelser
11558
- } @else if (filterStatus() === 'closed') {
11559
- Du har ingen lukkede henvendelser
11560
- } @else {
11561
- Du har ikke oprettet nogen henvendelser endnu
11562
- }
11563
- </p>
11564
- </div>
11565
- }
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()" />
11566
11627
  </div>
11567
- </ds-mobile-content>
11568
- </ds-mobile-page-main>
11569
- `, 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"] }]
11570
- }], 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
+ }] } });
11571
11646
 
11572
- class MobileInquiryDetailPageComponent extends MobilePageBase {
11573
- userService;
11574
- navCtrl;
11575
- elementRef;
11576
- ionContent;
11577
- // Platform detection
11578
- platform = inject(Platform);
11579
- // Computed property to check if running on native platform
11580
- isNativePlatform = computed(() => this.platform.is('ios') ||
11581
- this.platform.is('android') ||
11582
- this.platform.is('capacitor'), ...(ngDevMode ? [{ debugName: "isNativePlatform" }] : []));
11583
- inquiryTitle = 'Tørretumbler virker ikke';
11584
- activeTab = signal('activity', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
11585
- tabItems = [
11586
- { id: 'activity', label: 'Aktivitet' },
11587
- { id: 'messages', label: 'Beskeder', badge: 0 },
11588
- { id: 'details', label: 'Detaljer' }
11589
- ];
11590
- activities = [
11591
- {
11592
- id: '1',
11593
- type: 'assignment',
11594
- actor: 'Martin Smith',
11595
- actorInitials: 'MS',
11596
- title: 'er blevet tildelt som din dedikerede tekniker.',
11597
- timestamp: '2 dage siden',
11598
- date: '28. feb 2025',
11599
- iconName: 'remixUserAddLine'
11600
- },
11601
- {
11602
- id: '2',
11603
- type: 'assignment',
11604
- actor: 'Ricki Meihlen',
11605
- actorInitials: 'RM',
11606
- title: 'er blevet tildelt til at håndtere din henvendelse.',
11607
- timestamp: '8 dage siden',
11608
- date: '22. feb 2025',
11609
- iconName: 'remixUserLine'
11610
- },
11611
- {
11612
- id: '3',
11613
- type: 'creation',
11614
- title: 'Henvendelse oprettet',
11615
- timestamp: '8 dage siden',
11616
- date: '22. feb 2025',
11617
- 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';
11618
11674
  }
11619
- ];
11620
- messageThreads = [
11621
- {
11622
- id: '1',
11623
- senderName: 'Ove Hindborg',
11624
- senderAvatar: '',
11625
- senderInitials: 'OH',
11626
- message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11627
- role: 'Sagsbehandler',
11628
- timestamp: '2t siden',
11629
- unread: true
11630
- },
11631
- {
11632
- id: '2',
11633
- senderName: 'Martin Smith',
11634
- senderAvatar: '',
11635
- senderInitials: 'MS',
11636
- message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
11637
- role: 'Tekniker',
11638
- timestamp: '4t siden',
11639
- unread: true
11675
+ else if (secondarySurface === '#660036') {
11676
+ this.currentTheme = 'pka';
11640
11677
  }
11641
- ];
11642
- unreadMessagesCount = computed(() => {
11643
- const count = this.messageThreads.filter(m => m.unread).length;
11644
- // Update badge in tab items
11645
- const messagesTab = this.tabItems.find(t => t.id === 'messages');
11646
- if (messagesTab) {
11647
- messagesTab.badge = count;
11678
+ else if (secondarySurface === '#262424') {
11679
+ this.currentTheme = 'clave';
11680
+ }
11681
+ else {
11682
+ this.currentTheme = 'default';
11648
11683
  }
11649
- return count;
11650
- }, ...(ngDevMode ? [{ debugName: "unreadMessagesCount" }] : []));
11651
- constructor(userService, navCtrl, elementRef) {
11652
- super();
11653
- this.userService = userService;
11654
- this.navCtrl = navCtrl;
11655
- this.elementRef = elementRef;
11656
- // Trigger initial badge update
11657
- this.unreadMessagesCount();
11658
11684
  }
11659
- ngAfterViewInit() {
11660
- // Initial setup if needed
11685
+ /**
11686
+ * Close the modal
11687
+ */
11688
+ close() {
11689
+ this.modalController.dismiss();
11661
11690
  }
11662
- setActiveTab(tabId) {
11663
- 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();
11664
11706
  }
11665
- goBack() {
11666
- 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();
11667
11722
  }
11668
- handleScroll(event) {
11669
- const scrollTop = event.detail.scrollTop;
11670
- const threshold = 160;
11671
- const fadeDistance = 200;
11672
- const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
11673
- const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
11674
- // Show title in fixed header when scrolled past threshold
11675
- if (scrollTop > threshold) {
11676
- header?.classList.add('header-scrolled');
11677
- }
11678
- else {
11679
- header?.classList.remove('header-scrolled');
11680
- }
11681
- // Fade out header-expandable content based on scroll
11682
- if (headerExpandable) {
11683
- const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
11684
- // Calculate opacity (1 to 0)
11685
- const opacity = 1 - fadeProgress;
11686
- // Calculate transform (0px to -20px upward)
11687
- const translateY = fadeProgress * -20;
11688
- // Apply styles
11689
- headerExpandable.style.opacity = opacity.toString();
11690
- headerExpandable.style.transform = `translateY(${translateY}px)`;
11691
- }
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();
11692
11738
  }
11693
- handleRefresh(event) {
11694
- console.log('Pull-to-refresh triggered');
11695
- setTimeout(() => {
11696
- console.log('Refresh complete');
11697
- event.target.complete();
11698
- }, 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();
11699
11754
  }
11700
- openMessage(messageId) {
11701
- console.log('Opening message:', messageId);
11702
- // 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
+ });
11703
11762
  }
11704
- 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 });
11705
- 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: `
11706
- <!-- Fixed header at top -->
11707
- <ion-header>
11708
- <ion-toolbar>
11709
- <div class="header-detail">
11710
- <!-- Back Button -->
11711
- <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11712
- <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11713
- </button>
11714
-
11715
- <!-- Title - fades in on scroll -->
11716
- <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11717
- </div>
11718
- </ion-toolbar>
11719
- </ion-header>
11720
-
11721
- <!-- Content with expandable header -->
11722
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11723
- <!-- Pull to refresh (only on native iOS/Android) -->
11724
- @if (isNativePlatform()) {
11725
- <ion-refresher
11726
- slot="fixed"
11727
- (ionRefresh)="handleRefresh($event)"
11728
- [pullFactor]="0.4"
11729
- [pullMin]="80"
11730
- [pullMax]="240"
11731
- closeDuration="600ms">
11732
- <ion-refresher-content
11733
- pullingIcon="remixArrowDownS"
11734
- refreshingSpinner="lines">
11735
- </ion-refresher-content>
11736
- </ion-refresher>
11737
- }
11738
-
11739
- <!-- Expandable header section (purple background) -->
11740
- <div class="header-expandable">
11741
- <div class="header-expandable-inner">
11742
- <div class="header-expandable__text">
11743
- <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11744
- </div>
11745
-
11746
- <!-- Tabs in header -->
11747
- <ds-mobile-inline-tabs
11748
- [tabs]="tabItems"
11749
- [activeTab]="activeTab()"
11750
- (tabChange)="setActiveTab($event)">
11751
- </ds-mobile-inline-tabs>
11752
- </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>
11753
11783
  </div>
11754
-
11755
- <!-- Content wrapper -->
11756
- <div class="content-wrapper">
11757
- <div class="content-inner">
11758
- <!-- Activity Tab Content -->
11759
- @if (activeTab() === 'activity') {
11760
- <div class="activity-list">
11761
- @for (activity of activities; track activity.id) {
11762
- <div class="activity-item">
11763
- @if (activity.actor) {
11764
- <!-- Avatar with badge for actor activities -->
11765
- <div class="avatar-wrapper">
11766
- <ds-avatar
11767
- [type]="'initials'"
11768
- [initials]="activity.actorInitials || ''"
11769
- size="md" />
11770
-
11771
- <div class="avatar-badge">
11772
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11773
- <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"/>
11774
- <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"/>
11775
- </svg>
11776
- </div>
11777
- </div>
11778
- } @else {
11779
- <!-- Icon wrapper for non-actor activities -->
11780
- <div class="activity-icon-wrapper">
11781
- <ds-icon
11782
- [name]="activity.iconName"
11783
- size="18px"
11784
- color="secondary" />
11785
- </div>
11786
- }
11787
-
11788
- <div class="activity-content">
11789
- <p class="activity-title">
11790
- @if (activity.actor) {
11791
- <span class="actor-name">{{ activity.actor }}</span>
11792
- <span class="activity-text"> {{ activity.title }}</span>
11793
- } @else {
11794
- <span class="actor-name">{{ activity.title }}</span>
11795
- }
11796
- </p>
11797
- @if (activity.description) {
11798
- <p class="activity-description">{{ activity.description }}</p>
11799
- }
11800
- <div class="activity-timestamp">
11801
- <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
11802
- <span>{{ activity.date }}</span>
11803
- </div>
11804
- </div>
11805
- </div>
11806
- }
11807
- </div>
11808
- }
11809
-
11810
- <!-- Messages Tab Content -->
11811
- @if (activeTab() === 'messages') {
11812
- <div class="messages-list">
11813
- @for (message of messageThreads; track message.id) {
11814
- <ds-mobile-interactive-list-item-message
11815
- [senderName]="message.senderName"
11816
- [senderRole]="message.role"
11817
- [message]="message.message"
11818
- [avatarInitials]="message.senderInitials"
11819
- [unread]="message.unread"
11820
- (messageClick)="openMessage(message.id)">
11821
- </ds-mobile-interactive-list-item-message>
11822
- }
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>
11823
11805
  </div>
11824
- }
11806
+ </div>
11825
11807
 
11826
- <!-- Details Tab Content -->
11827
- @if (activeTab() === 'details') {
11828
- <div class="details-list">
11829
- <!-- Assignee -->
11830
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11831
- <div content-leading>
11832
- <ds-avatar
11833
- [size]="'sm'"
11834
- [type]="'initials'"
11835
- [initials]="'R'" />
11836
- </div>
11837
- <div content-main>
11838
- <div class="detail-label">Sagsbehandler</div>
11839
- <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" />
11840
11816
  </div>
11841
- </ds-mobile-list-item-static>
11842
-
11843
- <!-- Technician -->
11844
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11845
- <div content-leading>
11846
- <ds-avatar
11847
- [size]="'sm'"
11817
+ </div>
11818
+ <div class="preview-tile">
11819
+ <h3>Logomark</h3>
11820
+ <div class="logomark-preview">
11821
+ <ds-avatar-with-badge
11848
11822
  [type]="'initials'"
11849
- [initials]="'M'" />
11823
+ [initials]="'KN'"
11824
+ [size]="'md'"
11825
+ [badgePosition]="'bottom-right'"
11826
+ />
11850
11827
  </div>
11851
- <div content-main>
11852
- <div class="detail-label">Tekniker</div>
11853
- <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>
11854
11844
  </div>
11855
- </ds-mobile-list-item-static>
11856
-
11857
- <!-- Title -->
11858
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11859
- <div content-leading>
11860
- <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>
11861
11848
  </div>
11862
- <div content-main>
11863
- <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>
11864
11852
  </div>
11865
- </ds-mobile-list-item-static>
11853
+ </div>
11866
11854
 
11867
- <!-- Description -->
11868
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11869
- <div content-leading>
11870
- <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
+ />
11871
11869
  </div>
11872
- <div content-main>
11873
- <div class="detail-value description-text">
11874
- 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.
11875
- </div>
11876
- <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
+ />
11877
11882
  </div>
11878
- </ds-mobile-list-item-static>
11879
-
11880
- <!-- Photos -->
11881
- <ds-mobile-list-item-static [leadingSize]="'32px'">
11882
- <div content-leading>
11883
- <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
+ />
11884
11897
  </div>
11885
- <div content-main>
11886
- <div class="photo-grid">
11887
- <button class="photo-add">
11888
- <ds-icon name="remixAddLine" size="20px" color="tertiary" />
11889
- </button>
11890
- <!-- Placeholder photos -->
11891
- <div class="photo-item"></div>
11892
- <div class="photo-item"></div>
11893
- <div class="photo-item"></div>
11894
- <div class="photo-item"></div>
11895
- </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
+ />
11896
11910
  </div>
11897
- </ds-mobile-list-item-static>
11911
+ </div>
11898
11912
  </div>
11899
- }
11913
+ </div>
11900
11914
  </div>
11901
- </div>
11902
11915
  </ion-content>
11903
- `, 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"] }] });
11904
11917
  }
11905
- 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: [{
11906
11919
  type: Component,
11907
- args: [{ selector: 'app-mobile-inquiry-detail-page', standalone: true, imports: [
11920
+ args: [{ selector: 'ds-whitelabel-demo-modal', standalone: true, imports: [
11908
11921
  CommonModule,
11909
- IonHeader,
11910
- IonToolbar,
11911
- IonTitle,
11922
+ FormsModule,
11912
11923
  IonContent,
11913
- IonRefresher,
11914
- IonRefresherContent,
11915
- DsIconComponent,
11916
- DsAvatarComponent,
11917
- DsMobileInlineTabsComponent,
11918
- DsMobileListItemStaticComponent,
11919
- DsMobileInteractiveListItemMessageComponent
11920
- ], host: {
11921
- class: 'ion-page'
11922
- }, template: `
11923
- <!-- Fixed header at top -->
11924
- <ion-header>
11925
- <ion-toolbar>
11926
- <div class="header-detail">
11927
- <!-- Back Button -->
11928
- <button class="back-button" (click)="goBack()" [attr.aria-label]="'Go back'">
11929
- <ds-icon name="remixArrowLeftSLine" size="24px" color="white" />
11930
- </button>
11931
-
11932
- <!-- Title - fades in on scroll -->
11933
- <ion-title class="header-detail__title">{{ inquiryTitle }}</ion-title>
11934
- </div>
11935
- </ion-toolbar>
11936
- </ion-header>
11937
-
11938
- <!-- Content with expandable header -->
11939
- <ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
11940
- <!-- Pull to refresh (only on native iOS/Android) -->
11941
- @if (isNativePlatform()) {
11942
- <ion-refresher
11943
- slot="fixed"
11944
- (ionRefresh)="handleRefresh($event)"
11945
- [pullFactor]="0.4"
11946
- [pullMin]="80"
11947
- [pullMax]="240"
11948
- closeDuration="600ms">
11949
- <ion-refresher-content
11950
- pullingIcon="remixArrowDownS"
11951
- refreshingSpinner="lines">
11952
- </ion-refresher-content>
11953
- </ion-refresher>
11954
- }
11955
-
11956
- <!-- Expandable header section (purple background) -->
11957
- <div class="header-expandable">
11958
- <div class="header-expandable-inner">
11959
- <div class="header-expandable__text">
11960
- <h1 class="header-expandable__title">{{ inquiryTitle }}</h1>
11961
- </div>
11962
-
11963
- <!-- Tabs in header -->
11964
- <ds-mobile-inline-tabs
11965
- [tabs]="tabItems"
11966
- [activeTab]="activeTab()"
11967
- (tabChange)="setActiveTab($event)">
11968
- </ds-mobile-inline-tabs>
11969
- </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>
11970
11940
  </div>
11971
-
11972
- <!-- Content wrapper -->
11973
- <div class="content-wrapper">
11974
- <div class="content-inner">
11975
- <!-- Activity Tab Content -->
11976
- @if (activeTab() === 'activity') {
11977
- <div class="activity-list">
11978
- @for (activity of activities; track activity.id) {
11979
- <div class="activity-item">
11980
- @if (activity.actor) {
11981
- <!-- Avatar with badge for actor activities -->
11982
- <div class="avatar-wrapper">
11983
- <ds-avatar
11984
- [type]="'initials'"
11985
- [initials]="activity.actorInitials || ''"
11986
- size="md" />
11987
-
11988
- <div class="avatar-badge">
11989
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 32" fill="none">
11990
- <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"/>
11991
- <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"/>
11992
- </svg>
11993
- </div>
11994
- </div>
11995
- } @else {
11996
- <!-- Icon wrapper for non-actor activities -->
11997
- <div class="activity-icon-wrapper">
11998
- <ds-icon
11999
- [name]="activity.iconName"
12000
- size="18px"
12001
- color="secondary" />
12002
- </div>
12003
- }
12004
-
12005
- <div class="activity-content">
12006
- <p class="activity-title">
12007
- @if (activity.actor) {
12008
- <span class="actor-name">{{ activity.actor }}</span>
12009
- <span class="activity-text"> {{ activity.title }}</span>
12010
- } @else {
12011
- <span class="actor-name">{{ activity.title }}</span>
12012
- }
12013
- </p>
12014
- @if (activity.description) {
12015
- <p class="activity-description">{{ activity.description }}</p>
12016
- }
12017
- <div class="activity-timestamp">
12018
- <ds-icon name="remixCalendarLine" size="12px" color="--color-text-tertiary" />
12019
- <span>{{ activity.date }}</span>
12020
- </div>
12021
- </div>
12022
- </div>
12023
- }
12024
- </div>
12025
- }
12026
-
12027
- <!-- Messages Tab Content -->
12028
- @if (activeTab() === 'messages') {
12029
- <div class="messages-list">
12030
- @for (message of messageThreads; track message.id) {
12031
- <ds-mobile-interactive-list-item-message
12032
- [senderName]="message.senderName"
12033
- [senderRole]="message.role"
12034
- [message]="message.message"
12035
- [avatarInitials]="message.senderInitials"
12036
- [unread]="message.unread"
12037
- (messageClick)="openMessage(message.id)">
12038
- </ds-mobile-interactive-list-item-message>
12039
- }
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>
12040
11962
  </div>
12041
- }
11963
+ </div>
12042
11964
 
12043
- <!-- Details Tab Content -->
12044
- @if (activeTab() === 'details') {
12045
- <div class="details-list">
12046
- <!-- Assignee -->
12047
- <ds-mobile-list-item-static [leadingSize]="'32px'">
12048
- <div content-leading>
12049
- <ds-avatar
12050
- [size]="'sm'"
12051
- [type]="'initials'"
12052
- [initials]="'R'" />
12053
- </div>
12054
- <div content-main>
12055
- <div class="detail-label">Sagsbehandler</div>
12056
- <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" />
12057
11973
  </div>
12058
- </ds-mobile-list-item-static>
12059
-
12060
- <!-- Technician -->
12061
- <ds-mobile-list-item-static [leadingSize]="'32px'">
12062
- <div content-leading>
12063
- <ds-avatar
12064
- [size]="'sm'"
11974
+ </div>
11975
+ <div class="preview-tile">
11976
+ <h3>Logomark</h3>
11977
+ <div class="logomark-preview">
11978
+ <ds-avatar-with-badge
12065
11979
  [type]="'initials'"
12066
- [initials]="'M'" />
11980
+ [initials]="'KN'"
11981
+ [size]="'md'"
11982
+ [badgePosition]="'bottom-right'"
11983
+ />
12067
11984
  </div>
12068
- <div content-main>
12069
- <div class="detail-label">Tekniker</div>
12070
- <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>
12071
11997
  </div>
12072
- </ds-mobile-list-item-static>
12073
-
12074
- <!-- Title -->
12075
- <ds-mobile-list-item-static [leadingSize]="'32px'">
12076
- <div content-leading>
12077
- <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>
12078
12001
  </div>
12079
- <div content-main>
12080
- <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>
12081
12005
  </div>
12082
- </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>
12083
12011
 
12084
- <!-- Description -->
12085
- <ds-mobile-list-item-static [leadingSize]="'32px'">
12086
- <div content-leading>
12087
- <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
+ />
12088
12026
  </div>
12089
- <div content-main>
12090
- <div class="detail-value description-text">
12091
- 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.
12092
- </div>
12093
- <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
+ />
12094
12039
  </div>
12095
- </ds-mobile-list-item-static>
12096
-
12097
- <!-- Photos -->
12098
- <ds-mobile-list-item-static [leadingSize]="'32px'">
12099
- <div content-leading>
12100
- <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
+ />
12101
12054
  </div>
12102
- <div content-main>
12103
- <div class="photo-grid">
12104
- <button class="photo-add">
12105
- <ds-icon name="remixAddLine" size="20px" color="tertiary" />
12106
- </button>
12107
- <!-- Placeholder photos -->
12108
- <div class="photo-item"></div>
12109
- <div class="photo-item"></div>
12110
- <div class="photo-item"></div>
12111
- <div class="photo-item"></div>
12112
- </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
+ />
12113
12067
  </div>
12114
- </ds-mobile-list-item-static>
12068
+ </div>
12115
12069
  </div>
12116
- }
12070
+ </div>
12117
12071
  </div>
12118
- </div>
12119
12072
  </ion-content>
12120
- `, 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"] }]
12121
- }], ctorParameters: () => [{ type: UserService }, { type: i1.NavController }, { type: i0.ElementRef }], propDecorators: { ionContent: [{
12122
- type: ViewChild,
12123
- args: [IonContent]
12124
- }] } });
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 }] });
12125
12149
 
12126
12150
  /**
12127
12151
  * MobileTabsExampleComponent
@@ -12146,6 +12170,8 @@ class MobileTabsExampleComponent {
12146
12170
  // Configure user avatar globally - this is now the single source of truth
12147
12171
  this.userService.setAvatarInitials('LM');
12148
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);
12149
12175
  }
12150
12176
  tabs = [
12151
12177
  {
@@ -12179,7 +12205,9 @@ class MobileTabsExampleComponent {
12179
12205
  ];
12180
12206
  /**
12181
12207
  * Profile menu items configuration.
12182
- * 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.
12183
12211
  */
12184
12212
  profileMenuItems = [
12185
12213
  {