@propbinder/mobile-design 0.2.48 → 0.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +24 -0
- package/package.json +3 -39
- package/src/animations/page-transitions.ts +165 -0
- package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
- package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
- package/src/components/action-list-item/index.ts +2 -0
- package/src/components/app-icon/ds-app-icon.ts +133 -0
- package/src/components/app-icon/index.ts +2 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
- package/src/components/attachment-preview/index.ts +1 -0
- package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
- package/src/components/avatar-with-badge/index.ts +2 -0
- package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
- package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
- package/src/components/booking-modal/index.ts +4 -0
- package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
- package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
- package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
- package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
- package/src/components/bottom-sheet/index.ts +8 -0
- package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
- package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
- package/src/components/card-inline/index.ts +2 -0
- package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
- package/src/components/card-inline-banner/index.ts +1 -0
- package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
- package/src/components/card-inline-contact/index.ts +1 -0
- package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
- package/src/components/card-inline-file/index.ts +1 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
- package/src/components/chat-modal/index.ts +8 -0
- package/src/components/comment/ds-mobile-comment.ts +568 -0
- package/src/components/comment/index.ts +2 -0
- package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
- package/src/components/contact-list-item/index.ts +2 -0
- package/src/components/content/ds-mobile-content.ts +139 -0
- package/src/components/content/index.ts +2 -0
- package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
- package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
- package/src/components/dropdown/index.ts +2 -0
- package/src/components/ds-mobile-tabs.css +407 -0
- package/src/components/ds-mobile-tabs.ts +216 -0
- package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/fab/ds-mobile-fab.ts +315 -0
- package/src/components/fab/index.ts +1 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
- package/src/components/facility-creation-modal/index.ts +9 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
- package/src/components/facility-detail-modal/index.ts +2 -0
- package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
- package/src/components/file-attachment/index.ts +2 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
- package/src/components/handbook-detail-modal/index.ts +3 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
- package/src/components/handbook-folder/index.ts +4 -0
- package/src/components/header-content/ds-mobile-header-content.ts +222 -0
- package/src/components/header-content/index.ts +2 -0
- package/src/components/illustration/ds-mobile-illustration.ts +124 -0
- package/src/components/illustration/index.ts +2 -0
- package/src/components/index.ts +124 -0
- package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
- package/src/components/inline-photo/index.ts +1 -0
- package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
- package/src/components/inline-tabs/index.ts +2 -0
- package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
- package/src/components/interactive-list-item-booking/index.ts +1 -0
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
- package/src/components/interactive-list-item-inquiry/index.ts +2 -0
- package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
- package/src/components/interactive-list-item-message/index.ts +2 -0
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
- package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
- package/src/components/interactive-list-item-post/index.ts +13 -0
- package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
- package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
- package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
- package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
- package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
- package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
- package/src/components/lightbox/index.ts +22 -0
- package/src/components/list-item/ds-mobile-list-item.ts +603 -0
- package/src/components/list-item/index.ts +2 -0
- package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
- package/src/components/list-item-static/index.ts +2 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
- package/src/components/loader-overlay/index.ts +1 -0
- package/src/components/logo/ds-logo.ts +95 -0
- package/src/components/logo/index.ts +2 -0
- package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
- package/src/components/message-bubble/index.ts +7 -0
- package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
- package/src/components/message-composer/index.ts +7 -0
- package/src/components/modal/ds-mobile-modal.css +163 -0
- package/src/components/modal/ds-mobile-modal.service.ts +329 -0
- package/src/components/modal/index.ts +8 -0
- package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
- package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
- package/src/components/modal-base/index.ts +2 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
- package/src/components/new-inquiry-modal/index.ts +4 -0
- package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
- package/src/components/offline-banner/index.ts +1 -0
- package/src/components/page-details/ds-mobile-page-details.css +83 -0
- package/src/components/page-details/ds-mobile-page-details.ts +282 -0
- package/src/components/page-details/index.ts +2 -0
- package/src/components/page-main/ds-mobile-page-main.css +68 -0
- package/src/components/page-main/ds-mobile-page-main.ts +421 -0
- package/src/components/page-main/index.ts +2 -0
- package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
- package/src/components/post-composer/index.ts +2 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
- package/src/components/post-detail-modal/index.ts +9 -0
- package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
- package/src/components/property-banner/index.ts +2 -0
- package/src/components/section/ds-mobile-section.ts +263 -0
- package/src/components/section/index.ts +2 -0
- package/src/components/shared/directives/index.ts +2 -0
- package/src/components/shared/directives/long-press.directive.ts +212 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/shared/mobile-modal-base.ts +457 -0
- package/src/components/shared/mobile-page-base.ts +204 -0
- package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
- package/src/components/swiper/ds-mobile-swiper.ts +327 -0
- package/src/components/swiper/index.ts +3 -0
- package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
- package/src/components/system-message-banner/index.ts +2 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
- package/src/components/tab-bar/index.ts +2 -0
- package/src/components/tabs/ds-mobile-tabs.css +25 -0
- package/src/components/tabs/ds-mobile-tabs.ts +89 -0
- package/src/components/tabs/index.ts +2 -0
- package/src/components/text-input/ds-text-input.ts +287 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/examples/booking.page.ts +434 -0
- package/src/examples/community.page.ts +776 -0
- package/src/examples/handbook.page.ts +324 -0
- package/src/examples/home.page.ts +347 -0
- package/src/examples/index.ts +12 -0
- package/src/examples/inquiries.example.ts +273 -0
- package/src/examples/inquiry-detail.example.css +189 -0
- package/src/examples/inquiry-detail.example.ts +415 -0
- package/src/examples/mobile-tabs-example.component.ts +208 -0
- package/src/examples/post-create.page.ts +311 -0
- package/src/examples/post-detail.page.ts +296 -0
- package/src/examples/sign-in.page.ts +291 -0
- package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
- package/src/examples/whitelabel-demo-modal.service.ts +77 -0
- package/src/models/index.ts +7 -0
- package/src/models/post.model.ts +41 -0
- package/src/pages/community.page.ts +769 -0
- package/src/pages/handbook.page.ts +388 -0
- package/src/pages/home.page.ts +303 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/inquiries.example.ts +273 -0
- package/src/pages/inquiry-detail.example.css +189 -0
- package/src/pages/inquiry-detail.example.ts +415 -0
- package/src/pages/mobile-tabs-example.component.ts +179 -0
- package/src/pages/post-create.page.ts +311 -0
- package/src/pages/post-detail.page.ts +296 -0
- package/src/pages/sign-in.page.ts +291 -0
- package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
- package/src/pages/whitelabel-demo-modal.service.ts +77 -0
- package/src/public-api.ts +6 -0
- package/src/services/base-modal.service.ts +101 -0
- package/src/services/index.ts +11 -0
- package/src/services/posts.service.ts +542 -0
- package/src/services/tracking-permission.service.ts +88 -0
- package/src/services/user.service.ts +60 -0
- package/src/services/whitelabel.service.ts +675 -0
- package/{styles → src/styles}/ionic.css +25 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +13 -0
- package/fesm2022/propbinder-mobile-design.mjs +0 -26168
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8169
- /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
- /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
- /package/{styles → src/components/shared}/mobile-common.css +0 -0
- /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { Component, input, output, HostListener, ElementRef, ViewChild, AfterViewInit, computed, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { Router } from '@angular/router';
|
|
4
|
+
import { IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, Platform, ModalController } from '@ionic/angular/standalone';
|
|
5
|
+
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
|
6
|
+
import { DsAvatarComponent } from '@propbinder/design-system';
|
|
7
|
+
import { DsLogoComponent } from '../logo/ds-logo';
|
|
8
|
+
import { DsMobileLoaderOverlayComponent } from '../loader-overlay';
|
|
9
|
+
import { MobilePageBase } from '../shared/mobile-page-base';
|
|
10
|
+
import { DsMobileProfileActionsSheetComponent, ActionResult, ActionGroup, Language } from '../bottom-sheet';
|
|
11
|
+
import { disableModalShadowPointerEvents } from '../bottom-sheet/modal-shadow-fix';
|
|
12
|
+
import { UserService } from '../../services/user.service';
|
|
13
|
+
import { WhitelabelDemoModalService } from '../../pages/whitelabel-demo-modal.service';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DsMobilePageMainComponent
|
|
17
|
+
*
|
|
18
|
+
* A complete mobile page layout for main/tab pages with:
|
|
19
|
+
* - Fixed header with logomark + title + avatar
|
|
20
|
+
* - Purple expandable header section (scrolls with content)
|
|
21
|
+
* - White rounded content wrapper
|
|
22
|
+
* - Pull-to-refresh support
|
|
23
|
+
* - Auto scroll title fade-in
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```html
|
|
27
|
+
* <!-- Simple page -->
|
|
28
|
+
* <ds-mobile-page-main
|
|
29
|
+
* title="Inquiries"
|
|
30
|
+
* [avatarInitials]="'JD'">
|
|
31
|
+
* <div class="page-content">
|
|
32
|
+
* <!-- Your content -->
|
|
33
|
+
* </div>
|
|
34
|
+
* </ds-mobile-page-main>
|
|
35
|
+
*
|
|
36
|
+
* <!-- Page with custom header content -->
|
|
37
|
+
* <ds-mobile-page-main
|
|
38
|
+
* title="Home"
|
|
39
|
+
* headerTitle="Welcome, Lars"
|
|
40
|
+
* headerSubtitle="Your rental property at a glance."
|
|
41
|
+
* [avatarInitials]="'L'">
|
|
42
|
+
*
|
|
43
|
+
* <div header-content class="property-tiles">
|
|
44
|
+
* <!-- Custom header content like tiles -->
|
|
45
|
+
* </div>
|
|
46
|
+
*
|
|
47
|
+
* <div class="page-content">
|
|
48
|
+
* <!-- Main page content -->
|
|
49
|
+
* </div>
|
|
50
|
+
* </ds-mobile-page-main>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'ds-mobile-page-main',
|
|
55
|
+
standalone: true,
|
|
56
|
+
imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, DsAvatarComponent, DsLogoComponent, DsMobileLoaderOverlayComponent],
|
|
57
|
+
styleUrls: ['../shared/mobile-page-base.css', './ds-mobile-page-main.css'],
|
|
58
|
+
host: {
|
|
59
|
+
'[style.--content-wrapper-padding]': 'contentPadding()'
|
|
60
|
+
},
|
|
61
|
+
template: `
|
|
62
|
+
<!-- Fixed header at top -->
|
|
63
|
+
<ion-header>
|
|
64
|
+
<ion-toolbar>
|
|
65
|
+
<div class="header-main">
|
|
66
|
+
<!-- Whitelabel logo (full in header, logomark used for app icon/avatars) -->
|
|
67
|
+
<ds-logo variant="full" size="lg" />
|
|
68
|
+
|
|
69
|
+
<!-- Title - fades in on scroll -->
|
|
70
|
+
<ion-title class="header-main__title">{{ title() }}</ion-title>
|
|
71
|
+
|
|
72
|
+
<!-- Avatar -->
|
|
73
|
+
<div class="header-main__actions">
|
|
74
|
+
<ds-avatar
|
|
75
|
+
[size]="'md'"
|
|
76
|
+
[type]="avatarType()"
|
|
77
|
+
[initials]="avatarInitials()"
|
|
78
|
+
[src]="avatarSrc()"
|
|
79
|
+
[iconName]="avatarIconName()"
|
|
80
|
+
(click)="handleAvatarClick()"
|
|
81
|
+
style="cursor: pointer;"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</ion-toolbar>
|
|
86
|
+
</ion-header>
|
|
87
|
+
|
|
88
|
+
<!-- Content with expandable header -->
|
|
89
|
+
<ion-content [scrollEvents]="true" [forceOverscroll]="true" (ionScroll)="handleScroll($event)">
|
|
90
|
+
<!-- Condensed header for Ionic scroll effects -->
|
|
91
|
+
@if (showCondensedHeader()) {
|
|
92
|
+
<ion-header collapse="condense">
|
|
93
|
+
<ion-toolbar>
|
|
94
|
+
<ion-title size="large">{{ title() }}</ion-title>
|
|
95
|
+
</ion-toolbar>
|
|
96
|
+
</ion-header>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
<!-- Pull to refresh (only on native iOS/Android) -->
|
|
100
|
+
@if (showRefresh() && isNativePlatform()) {
|
|
101
|
+
<ion-refresher slot="fixed" (ionRefresh)="handleRefresh($event)" [pullFactor]="0.4" [pullMin]="80" [pullMax]="240" closeDuration="600ms">
|
|
102
|
+
<ion-refresher-content pullingIcon="remixArrowDownS" refreshingSpinner="crescent"> </ion-refresher-content>
|
|
103
|
+
</ion-refresher>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
<!-- Expandable header section (purple background) -->
|
|
107
|
+
<div class="header-expandable">
|
|
108
|
+
<div class="header-expandable-inner">
|
|
109
|
+
<div class="header-expandable__text">
|
|
110
|
+
<h1 class="header-expandable__title">
|
|
111
|
+
{{ headerTitle() || title() }}
|
|
112
|
+
</h1>
|
|
113
|
+
@if (headerSubtitle()) {
|
|
114
|
+
<p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
|
|
115
|
+
}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Slot for custom header content (e.g., property tiles) -->
|
|
119
|
+
<ng-content select="[header-content]"></ng-content>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Content wrapper -->
|
|
124
|
+
<div class="content-wrapper">
|
|
125
|
+
@if (contentLoading()) {
|
|
126
|
+
<ds-mobile-loader-overlay />
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
<!-- Offline indicator slot (appears at top of content) -->
|
|
130
|
+
<ng-content select="[offline-indicator]"></ng-content>
|
|
131
|
+
|
|
132
|
+
<div class="content-inner">
|
|
133
|
+
<!-- Main page content -->
|
|
134
|
+
<ng-content></ng-content>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</ion-content>
|
|
138
|
+
`,
|
|
139
|
+
})
|
|
140
|
+
export class DsMobilePageMainComponent extends MobilePageBase implements AfterViewInit {
|
|
141
|
+
@ViewChild(IonContent) ionContent?: IonContent;
|
|
142
|
+
|
|
143
|
+
// Platform detection
|
|
144
|
+
private platform = inject(Platform);
|
|
145
|
+
private modalController = inject(ModalController);
|
|
146
|
+
private router = inject(Router);
|
|
147
|
+
private userService = inject(UserService);
|
|
148
|
+
private whitelabelDemoModal = inject(WhitelabelDemoModalService);
|
|
149
|
+
|
|
150
|
+
// Computed property to check if running on native platform
|
|
151
|
+
isNativePlatform = computed(() => this.platform.is('ios') || this.platform.is('android') || this.platform.is('capacitor'));
|
|
152
|
+
|
|
153
|
+
// Inputs - Title
|
|
154
|
+
title = input.required<string>(); // For fixed header title
|
|
155
|
+
headerTitle = input<string>(''); // Optional different title for expandable header
|
|
156
|
+
headerSubtitle = input<string>('');
|
|
157
|
+
|
|
158
|
+
// Inputs - Avatar
|
|
159
|
+
avatarType = input<'initials' | 'photo' | 'icon'>('initials');
|
|
160
|
+
avatarInitials = input<string>('U');
|
|
161
|
+
avatarSrc = input<string>('');
|
|
162
|
+
avatarIconName = input<string>('remixUser3Line');
|
|
163
|
+
|
|
164
|
+
// Inputs - Features
|
|
165
|
+
showRefresh = input<boolean>(true);
|
|
166
|
+
showCondensedHeader = input<boolean>(true);
|
|
167
|
+
scrollThreshold = input<number>(160); // Pixels to scroll before title appears
|
|
168
|
+
headerFadeDistance = input<number>(150); // Distance over which header fades out
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Content wrapper padding
|
|
172
|
+
* - '0' (default) - No padding, use ds-mobile-section for content organization
|
|
173
|
+
* - '20px' - Legacy padding for content without sections
|
|
174
|
+
* - Any custom CSS padding value
|
|
175
|
+
*
|
|
176
|
+
* Note: Bottom padding for safe area and tab bar is always preserved
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```html
|
|
180
|
+
* <!-- Default: sections handle padding -->
|
|
181
|
+
* <ds-mobile-page-main title="Home">
|
|
182
|
+
* <ds-mobile-section>...</ds-mobile-section>
|
|
183
|
+
* </ds-mobile-page-main>
|
|
184
|
+
*
|
|
185
|
+
* <!-- Legacy: content without sections -->
|
|
186
|
+
* <ds-mobile-page-main title="Home" contentPadding="20px">
|
|
187
|
+
* <div>Content without sections...</div>
|
|
188
|
+
* </ds-mobile-page-main>
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
contentPadding = input<string>('0');
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Profile menu action groups to display when avatar is clicked.
|
|
195
|
+
* If not provided, a default menu will be used (without Whitelabel Demo).
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* profileMenuItems: ActionGroup[] = [
|
|
200
|
+
* {
|
|
201
|
+
* actions: [
|
|
202
|
+
* { action: 'profile', title: 'Min profil', icon: 'remixUser3Line' },
|
|
203
|
+
* { action: 'settings', title: 'Indstillinger', icon: 'remixSettings3Line' }
|
|
204
|
+
* ]
|
|
205
|
+
* },
|
|
206
|
+
* {
|
|
207
|
+
* actions: [
|
|
208
|
+
* { action: 'logout', title: 'Log ud', icon: 'remixLogoutBoxLine', destructive: true }
|
|
209
|
+
* ]
|
|
210
|
+
* }
|
|
211
|
+
* ];
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
profileMenuItems = input<ActionGroup[] | undefined>(undefined);
|
|
215
|
+
|
|
216
|
+
// Outputs
|
|
217
|
+
avatarClick = output<void>();
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Emitted when a profile menu action is selected.
|
|
221
|
+
* Parent component should handle the action logic (navigation, logout, etc.).
|
|
222
|
+
*/
|
|
223
|
+
profileActionSelected = output<ActionResult>();
|
|
224
|
+
|
|
225
|
+
refresh = output<any>();
|
|
226
|
+
scroll = output<any>();
|
|
227
|
+
|
|
228
|
+
constructor(private elementRef: ElementRef) {
|
|
229
|
+
super();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
ngAfterViewInit(): void {
|
|
233
|
+
// Initialize network monitoring
|
|
234
|
+
this.initNetworkMonitoring(this.isNativePlatform());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Handle avatar click - opens profile actions bottom sheet
|
|
239
|
+
*/
|
|
240
|
+
async handleAvatarClick(): Promise<void> {
|
|
241
|
+
// Emit the event for any parent listeners
|
|
242
|
+
this.avatarClick.emit();
|
|
243
|
+
|
|
244
|
+
// Use input if provided, otherwise fall back to service, otherwise use default menu
|
|
245
|
+
const menuItems = this.profileMenuItems() ||
|
|
246
|
+
this.userService.profileMenuItems() || [
|
|
247
|
+
{
|
|
248
|
+
actions: [
|
|
249
|
+
{
|
|
250
|
+
action: 'profile',
|
|
251
|
+
title: 'Min profil',
|
|
252
|
+
icon: 'remixUser3Line',
|
|
253
|
+
destructive: false,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
action: 'settings',
|
|
257
|
+
title: 'Indstillinger',
|
|
258
|
+
icon: 'remixSettings3Line',
|
|
259
|
+
destructive: false,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
action: 'appearance',
|
|
263
|
+
title: 'Udseende',
|
|
264
|
+
icon: 'remixPaletteLine',
|
|
265
|
+
destructive: false,
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
actions: [
|
|
271
|
+
{
|
|
272
|
+
action: 'language',
|
|
273
|
+
title: 'Sprog',
|
|
274
|
+
subtitle: 'Dansk',
|
|
275
|
+
flagIcon: '/Assets/country-flags/denmark.svg',
|
|
276
|
+
icon: 'remixGlobalLine',
|
|
277
|
+
destructive: false,
|
|
278
|
+
showChevron: true,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
actions: [
|
|
284
|
+
{
|
|
285
|
+
action: 'logout',
|
|
286
|
+
title: 'Log ud',
|
|
287
|
+
icon: 'remixLogoutBoxLine',
|
|
288
|
+
destructive: true,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
// If no menu items configured, just emit and return
|
|
295
|
+
if (!menuItems || menuItems.length === 0) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const sheet = await this.modalController.create({
|
|
300
|
+
component: DsMobileProfileActionsSheetComponent,
|
|
301
|
+
componentProps: {
|
|
302
|
+
actionGroups: menuItems,
|
|
303
|
+
currentLanguage: 'da', // TODO: Get from language service
|
|
304
|
+
availableLanguages: [
|
|
305
|
+
{
|
|
306
|
+
code: 'da',
|
|
307
|
+
nativeName: 'Dansk',
|
|
308
|
+
englishName: 'Danish',
|
|
309
|
+
flagIcon: '/Assets/country-flags/denmark.svg',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
code: 'en',
|
|
313
|
+
nativeName: 'English',
|
|
314
|
+
englishName: 'English',
|
|
315
|
+
flagIcon: '/Assets/country-flags/united kingdom.svg',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
code: 'sv',
|
|
319
|
+
nativeName: 'Svenska',
|
|
320
|
+
englishName: 'Swedish',
|
|
321
|
+
flagIcon: '/Assets/country-flags/sweden.svg',
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
code: 'no',
|
|
325
|
+
nativeName: 'Norsk',
|
|
326
|
+
englishName: 'Norwegian',
|
|
327
|
+
flagIcon: '/Assets/country-flags/norway.svg',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
code: 'de',
|
|
331
|
+
nativeName: 'Deutsch',
|
|
332
|
+
englishName: 'German',
|
|
333
|
+
flagIcon: '/Assets/country-flags/germany.svg',
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
breakpoints: [0, 1],
|
|
338
|
+
initialBreakpoint: 1,
|
|
339
|
+
handle: true,
|
|
340
|
+
cssClass: ['ds-bottom-sheet', 'auto-height'],
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await sheet.present();
|
|
344
|
+
disableModalShadowPointerEvents(sheet);
|
|
345
|
+
|
|
346
|
+
const result = await sheet.onWillDismiss<ActionResult>();
|
|
347
|
+
if (result.data?.action) {
|
|
348
|
+
// Handle appearance/whitelabel-demo action internally (works on all pages)
|
|
349
|
+
if (result.data.action === 'appearance' || result.data.action === 'whitelabel-demo') {
|
|
350
|
+
// Small delay to ensure bottom sheet is fully dismissed
|
|
351
|
+
setTimeout(async () => {
|
|
352
|
+
try {
|
|
353
|
+
await this.whitelabelDemoModal.open();
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Failed to open whitelabel demo modal:', error);
|
|
356
|
+
}
|
|
357
|
+
}, 100);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Emit the selected action to parent (for other actions)
|
|
361
|
+
this.profileActionSelected.emit(result.data);
|
|
362
|
+
// Also notify globally via UserService
|
|
363
|
+
this.userService.notifyProfileAction(result.data);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Handle scroll events
|
|
369
|
+
* - Shows title in fixed header when scrolled past threshold
|
|
370
|
+
* - Fades out expandable header content based on scroll position
|
|
371
|
+
* - Emits scroll event for custom handling
|
|
372
|
+
*/
|
|
373
|
+
handleScroll(event: any): void {
|
|
374
|
+
const scrollTop = event.detail.scrollTop;
|
|
375
|
+
const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
|
|
376
|
+
const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
|
|
377
|
+
|
|
378
|
+
// Show title in fixed header when scrolled past threshold
|
|
379
|
+
if (scrollTop > this.scrollThreshold()) {
|
|
380
|
+
header?.classList.add('header-scrolled');
|
|
381
|
+
} else {
|
|
382
|
+
header?.classList.remove('header-scrolled');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Fade out header-expandable content based on scroll
|
|
386
|
+
if (headerExpandable) {
|
|
387
|
+
const fadeDistance = this.headerFadeDistance();
|
|
388
|
+
const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
|
|
389
|
+
|
|
390
|
+
// Calculate opacity (1 to 0)
|
|
391
|
+
const opacity = 1 - fadeProgress;
|
|
392
|
+
|
|
393
|
+
// Calculate transform (0px to -20px upward)
|
|
394
|
+
const translateY = fadeProgress * -20;
|
|
395
|
+
|
|
396
|
+
// Apply styles
|
|
397
|
+
headerExpandable.style.opacity = opacity.toString();
|
|
398
|
+
headerExpandable.style.transform = `translateY(${translateY}px)`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.scroll.emit(event);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Handle pull-to-refresh
|
|
406
|
+
* Emits refresh event - parent should call event.target.complete()
|
|
407
|
+
*/
|
|
408
|
+
async handleRefresh(event: any): Promise<void> {
|
|
409
|
+
// Haptic feedback for pull-to-refresh
|
|
410
|
+
try {
|
|
411
|
+
await Haptics.impact({ style: ImpactStyle.Medium });
|
|
412
|
+
} catch {
|
|
413
|
+
// Fallback to Web Vibration API if Capacitor Haptics is not available
|
|
414
|
+
if ('vibrate' in navigator) {
|
|
415
|
+
navigator.vibrate(50);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this.refresh.emit(event);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Component, input, output } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { DsAvatarComponent } from '@propbinder/design-system';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DsMobilePostComposerComponent
|
|
7
|
+
*
|
|
8
|
+
* A "fake" input composer for creating new posts in the community feed.
|
|
9
|
+
* Features a user avatar, placeholder input, and post button.
|
|
10
|
+
* Clicking opens the full post creation modal/page.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <ds-mobile-post-composer
|
|
15
|
+
* [avatarInitials]="'LM'"
|
|
16
|
+
* [avatarType]="'photo'"
|
|
17
|
+
* [avatarSrc]="'...'"
|
|
18
|
+
* (composerClick)="openPostCreator()">
|
|
19
|
+
* </ds-mobile-post-composer>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'ds-mobile-post-composer',
|
|
24
|
+
standalone: true,
|
|
25
|
+
imports: [CommonModule, DsAvatarComponent],
|
|
26
|
+
host: {
|
|
27
|
+
'(click)': 'handleClick()'
|
|
28
|
+
},
|
|
29
|
+
styles: [`
|
|
30
|
+
:host {
|
|
31
|
+
display: block;
|
|
32
|
+
max-width: 640px;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
transition: all 0.2s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.composer-container {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 12px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.composer-input-wrapper {
|
|
44
|
+
flex: 1;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.composer-input {
|
|
49
|
+
width: 100%;
|
|
50
|
+
background: rgba(var(--header-content-color-rgb, 255, 255, 255), 0.1);
|
|
51
|
+
border: none;
|
|
52
|
+
border-radius: 24px;
|
|
53
|
+
padding: 10px 16px;
|
|
54
|
+
font-family: 'Brockmann', sans-serif;
|
|
55
|
+
font-size: var(--font-size-sm);
|
|
56
|
+
font-weight: 400;
|
|
57
|
+
line-height: 20px;
|
|
58
|
+
letter-spacing: -0.3px;
|
|
59
|
+
color: rgba(var(--header-content-color-rgb, 255, 255, 255), 0.75);
|
|
60
|
+
outline: none;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
transition: all 0.2s ease;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
user-select: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.composer-input::placeholder {
|
|
68
|
+
color: rgba(var(--header-content-color-rgb, 255, 255, 255), 0.75);
|
|
69
|
+
opacity: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Hover effects for desktop */
|
|
73
|
+
@media (hover: hover) {
|
|
74
|
+
:host:hover .composer-input {
|
|
75
|
+
opacity: 0.8;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`],
|
|
79
|
+
template: `
|
|
80
|
+
<div class="composer-container">
|
|
81
|
+
<ds-avatar
|
|
82
|
+
[initials]="avatarInitials()"
|
|
83
|
+
[type]="avatarType()"
|
|
84
|
+
[src]="avatarSrc()"
|
|
85
|
+
[iconName]="avatarIconName()"
|
|
86
|
+
size="md" />
|
|
87
|
+
|
|
88
|
+
<div class="composer-input-wrapper">
|
|
89
|
+
<input
|
|
90
|
+
type="text"
|
|
91
|
+
class="composer-input"
|
|
92
|
+
[placeholder]="placeholder()"
|
|
93
|
+
readonly
|
|
94
|
+
tabindex="-1"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
`
|
|
99
|
+
})
|
|
100
|
+
export class DsMobilePostComposerComponent {
|
|
101
|
+
/**
|
|
102
|
+
* Avatar initials (for initials type)
|
|
103
|
+
*/
|
|
104
|
+
avatarInitials = input<string>('');
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Avatar type
|
|
108
|
+
*/
|
|
109
|
+
avatarType = input<'initials' | 'photo' | 'icon'>('initials');
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Avatar photo source (for photo type)
|
|
113
|
+
*/
|
|
114
|
+
avatarSrc = input<string>('');
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Icon name (for icon type avatars)
|
|
118
|
+
*/
|
|
119
|
+
avatarIconName = input<string>('remixUser3Fill');
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Placeholder text for the input
|
|
123
|
+
*/
|
|
124
|
+
placeholder = input<string>("Hvad er nyt?");
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Text for the post button
|
|
128
|
+
*/
|
|
129
|
+
buttonText = input<string>('Slå op');
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Emits when the composer is clicked
|
|
133
|
+
*/
|
|
134
|
+
composerClick = output<void>();
|
|
135
|
+
|
|
136
|
+
handleClick(): void {
|
|
137
|
+
this.composerClick.emit();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|