@propbinder/mobile-design 0.0.2 → 0.0.21
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/fesm2022/propbinder-mobile-design.mjs +12596 -0
- package/fesm2022/propbinder-mobile-design.mjs.map +1 -0
- package/index.d.ts +3214 -0
- package/package.json +39 -12
- package/ng-package.json +0 -7
- package/src/animations/page-transitions.ts +0 -86
- package/src/assets/fonts/Brockmann-Bold.otf +0 -0
- package/src/assets/fonts/Brockmann-BoldItalic.otf +0 -0
- package/src/assets/fonts/Brockmann-Medium.otf +0 -0
- package/src/assets/fonts/Brockmann-MediumItalic.otf +0 -0
- package/src/assets/fonts/Brockmann-Regular.otf +0 -0
- package/src/assets/fonts/Brockmann-RegularItalic.otf +0 -0
- package/src/assets/fonts/Brockmann-SemiBold.otf +0 -0
- package/src/assets/fonts/Brockmann-SemiBoldItalic.otf +0 -0
- package/src/assets/fonts/Brockmann_desktop_license.pdf +0 -0
- package/src/assets/fonts/brockmann-medium-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-regular-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-semibold-webfont.woff2 +0 -0
- package/src/components/action-list-item/ds-mobile-action-list-item.ts +0 -83
- package/src/components/action-list-item/index.ts +0 -2
- package/src/components/app-layout/ds-mobile-app-layout.css +0 -343
- package/src/components/app-layout/ds-mobile-app-layout.ts +0 -271
- package/src/components/app-layout/index.ts +0 -2
- package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +0 -130
- package/src/components/avatar-with-badge/index.ts +0 -2
- package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +0 -273
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +0 -110
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +0 -167
- package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +0 -656
- package/src/components/bottom-sheet/index.ts +0 -3
- package/src/components/comment/ds-mobile-comment.ts +0 -516
- package/src/components/comment/index.ts +0 -2
- package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +0 -182
- package/src/components/contact-list-item/index.ts +0 -2
- package/src/components/content/ds-mobile-content.ts +0 -158
- package/src/components/content/index.ts +0 -2
- package/src/components/ds-mobile-tabs.css +0 -372
- package/src/components/ds-mobile-tabs.ts +0 -217
- package/src/components/file-attachment/ds-mobile-file-attachment.ts +0 -164
- package/src/components/file-attachment/index.ts +0 -2
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +0 -98
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +0 -514
- package/src/components/handbook-detail-modal/index.ts +0 -3
- package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +0 -130
- package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +0 -444
- package/src/components/handbook-folder/index.ts +0 -4
- package/src/components/header-content/ds-mobile-header-content.ts +0 -211
- package/src/components/header-content/index.ts +0 -2
- package/src/components/index.ts +0 -45
- package/src/components/inline-photo/ds-mobile-inline-photo.ts +0 -269
- package/src/components/inline-photo/index.ts +0 -1
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.css +0 -60
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +0 -280
- package/src/components/interactive-list-item-inquiry/index.ts +0 -2
- package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +0 -197
- package/src/components/interactive-list-item-message/index.ts +0 -2
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.css +0 -70
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +0 -594
- package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +0 -124
- package/src/components/interactive-list-item-post/index.ts +0 -13
- package/src/components/lightbox/ds-mobile-lightbox-footer.ts +0 -331
- package/src/components/lightbox/ds-mobile-lightbox-header.ts +0 -173
- package/src/components/lightbox/ds-mobile-lightbox-image.ts +0 -464
- package/src/components/lightbox/ds-mobile-lightbox-pdf.css +0 -375
- package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +0 -374
- package/src/components/lightbox/ds-mobile-lightbox.css +0 -587
- package/src/components/lightbox/ds-mobile-lightbox.service.ts +0 -293
- package/src/components/lightbox/ds-mobile-lightbox.ts +0 -529
- package/src/components/lightbox/index.ts +0 -22
- package/src/components/list-item/ds-mobile-list-item.ts +0 -499
- package/src/components/list-item/index.ts +0 -2
- package/src/components/list-item-static/ds-mobile-list-item-static.ts +0 -133
- package/src/components/list-item-static/index.ts +0 -2
- package/src/components/logo/ds-logo.ts +0 -85
- package/src/components/logo/index.ts +0 -2
- package/src/components/modal/ds-mobile-modal.css +0 -163
- package/src/components/modal/ds-mobile-modal.service.ts +0 -329
- package/src/components/modal/index.ts +0 -8
- package/src/components/page-details/ds-mobile-page-details.css +0 -285
- package/src/components/page-details/ds-mobile-page-details.ts +0 -128
- package/src/components/page-details/index.ts +0 -2
- package/src/components/page-main/ds-mobile-page-main.css +0 -346
- package/src/components/page-main/ds-mobile-page-main.ts +0 -331
- package/src/components/page-main/index.ts +0 -2
- package/src/components/post-card/ds-mobile-post-card.ts +0 -685
- package/src/components/post-card/ds-mobile-post-pdf-attachment.ts +0 -124
- package/src/components/post-card/index.ts +0 -11
- package/src/components/post-composer/ds-mobile-post-composer.ts +0 -140
- package/src/components/post-composer/index.ts +0 -2
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +0 -104
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +0 -1273
- package/src/components/post-detail-modal/index.ts +0 -9
- package/src/components/shared/directives/index.ts +0 -2
- package/src/components/shared/directives/long-press.directive.ts +0 -208
- package/src/components/shared/index.ts +0 -3
- package/src/components/shared/mobile-common.css +0 -94
- package/src/components/shared/mobile-page-base.css +0 -315
- package/src/components/shared/mobile-page-base.ts +0 -70
- package/src/components/swiper/ds-mobile-swiper.ts +0 -123
- package/src/components/swiper/index.ts +0 -2
- package/src/components/tab-bar/ds-mobile-tab-bar.ts +0 -132
- package/src/components/tab-bar/index.ts +0 -2
- package/src/components/tabs/ds-mobile-tabs.css +0 -405
- package/src/components/tabs/ds-mobile-tabs.ts +0 -204
- package/src/components/tabs/index.ts +0 -2
- package/src/pages/community.page.ts +0 -768
- package/src/pages/handbook.page.ts +0 -298
- package/src/pages/home.page.ts +0 -192
- package/src/pages/index.ts +0 -9
- package/src/pages/inquiries.example.ts +0 -212
- package/src/pages/inquiry-detail.example.css +0 -434
- package/src/pages/inquiry-detail.example.ts +0 -416
- package/src/pages/mobile-tabs-example.component.ts +0 -146
- package/src/pages/post-create.page.ts +0 -311
- package/src/pages/post-detail.page.ts +0 -295
- package/src/pages/whitelabel-demo.page.ts +0 -548
- package/src/public-api.ts +0 -5
- package/src/services/user.service.ts +0 -35
- package/src/services/whitelabel.service.ts +0 -171
- package/src/styles/ionic.css +0 -673
- package/tsconfig.lib.json +0 -17
- package/tsconfig.lib.prod.json +0 -9
- package/tsconfig.spec.json +0 -13
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/* ============================================
|
|
2
|
-
DS MOBILE PAGE MAIN
|
|
3
|
-
Reusable mobile page component for main/tab pages
|
|
4
|
-
============================================ */
|
|
5
|
-
|
|
6
|
-
/* ============================================
|
|
7
|
-
HOST
|
|
8
|
-
============================================ */
|
|
9
|
-
|
|
10
|
-
:host {
|
|
11
|
-
display: flex;
|
|
12
|
-
flex-direction: column;
|
|
13
|
-
align-items: center;
|
|
14
|
-
height: 100%;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* ============================================
|
|
18
|
-
ION-HEADER - Fixed Header at Top
|
|
19
|
-
============================================ */
|
|
20
|
-
|
|
21
|
-
ion-header {
|
|
22
|
-
background: var(--color-brand-secondary);
|
|
23
|
-
box-shadow: none;
|
|
24
|
-
height: 72px;
|
|
25
|
-
min-height: 72px;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
ion-header ion-toolbar {
|
|
29
|
-
--background: var(--color-brand-secondary);
|
|
30
|
-
--border-width: 0;
|
|
31
|
-
--box-shadow: none;
|
|
32
|
-
--padding-top: 0;
|
|
33
|
-
--padding-bottom: 0;
|
|
34
|
-
--padding-start: 0;
|
|
35
|
-
--padding-end: 0;
|
|
36
|
-
--min-height: 72px;
|
|
37
|
-
height: 100%;
|
|
38
|
-
min-height: 72px;
|
|
39
|
-
padding: 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
ion-header ion-toolbar::part(native) {
|
|
43
|
-
height: 100%;
|
|
44
|
-
min-height: 72px;
|
|
45
|
-
padding: 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Hide header on desktop when using ds-mobile-tabs top bar */
|
|
49
|
-
@media (min-width: 768px) {
|
|
50
|
-
ion-header {
|
|
51
|
-
display: none;
|
|
52
|
-
height: auto;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/* ============================================
|
|
57
|
-
HEADER HOME - Logo + Title + Avatar
|
|
58
|
-
============================================ */
|
|
59
|
-
|
|
60
|
-
.header-home {
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
justify-content: space-between;
|
|
64
|
-
padding: 0 20px;
|
|
65
|
-
background: var(--color-brand-secondary);
|
|
66
|
-
position: relative;
|
|
67
|
-
height: 100%;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.header-home__title {
|
|
71
|
-
position: absolute;
|
|
72
|
-
left: 50%;
|
|
73
|
-
transform: translateX(-50%) translateY(-100%);
|
|
74
|
-
font-size: var(--font-size-base);
|
|
75
|
-
font-weight: 600;
|
|
76
|
-
color: white;
|
|
77
|
-
opacity: 0;
|
|
78
|
-
transition: transform 0.6s ease, opacity 0.6s ease;
|
|
79
|
-
margin: 0;
|
|
80
|
-
padding: 0;
|
|
81
|
-
--color: white;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Show title when scrolled */
|
|
85
|
-
.header-scrolled .header-home__title {
|
|
86
|
-
opacity: 1;
|
|
87
|
-
transform: translateX(-50%) translateY(0);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.header-home__actions {
|
|
91
|
-
display: flex;
|
|
92
|
-
align-items: center;
|
|
93
|
-
gap: 8px;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.header-home__actions ds-avatar {
|
|
97
|
-
cursor: pointer;
|
|
98
|
-
-webkit-tap-highlight-color: transparent;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.logomark {
|
|
102
|
-
height: 28px;
|
|
103
|
-
width: auto;
|
|
104
|
-
flex-shrink: 0;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@media (min-width: 768px) {
|
|
108
|
-
.header-home {
|
|
109
|
-
padding: 16px 24px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.logomark {
|
|
113
|
-
height: 32px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* Hide title on desktop - not needed */
|
|
117
|
-
.header-home__title {
|
|
118
|
-
display: none;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/* ============================================
|
|
123
|
-
ION-CONTENT
|
|
124
|
-
============================================ */
|
|
125
|
-
|
|
126
|
-
ion-content {
|
|
127
|
-
--background: var(--color-brand-secondary);
|
|
128
|
-
--padding-top: 0;
|
|
129
|
-
--padding-start: 0;
|
|
130
|
-
--padding-end: 0;
|
|
131
|
-
--padding-bottom: 0;
|
|
132
|
-
border-radius: 24px 24px 24px 24px;
|
|
133
|
-
overflow: hidden;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* Make ion-content scroll area a flex container */
|
|
137
|
-
ion-content::part(scroll) {
|
|
138
|
-
display: flex;
|
|
139
|
-
flex-direction: column;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/* iOS-specific: White background for bottom overshoot */
|
|
143
|
-
.plt-ios ion-content {
|
|
144
|
-
--background: var(--color-background-neutral-primary);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* Desktop only: remove bottom radius */
|
|
148
|
-
@media (min-width: 768px) {
|
|
149
|
-
ion-content {
|
|
150
|
-
border-radius: 24px 24px 0 0;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
ion-content::part(scroll) {
|
|
155
|
-
-webkit-overflow-scrolling: touch;
|
|
156
|
-
/* Note: Do NOT set overscroll-behavior-y here as it prevents ion-refresher from working */
|
|
157
|
-
/* overscroll-behavior on ion-app is sufficient to block native browser pull-to-refresh */
|
|
158
|
-
/* Flex container for content to expand properly */
|
|
159
|
-
display: flex;
|
|
160
|
-
flex-direction: column;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/* ============================================
|
|
164
|
-
CONDENSED HEADER
|
|
165
|
-
============================================ */
|
|
166
|
-
|
|
167
|
-
ion-header[collapse="condense"] {
|
|
168
|
-
display: none;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@media (min-width: 768px) {
|
|
172
|
-
ion-header[collapse="condense"] {
|
|
173
|
-
display: none;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/* ============================================
|
|
178
|
-
REFRESHER
|
|
179
|
-
============================================ */
|
|
180
|
-
|
|
181
|
-
ion-refresher {
|
|
182
|
-
z-index: 0;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
ion-refresher-content {
|
|
186
|
-
--color: white;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/* ============================================
|
|
190
|
-
HEADER EXPANDABLE - Purple Header Section
|
|
191
|
-
============================================ */
|
|
192
|
-
|
|
193
|
-
.header-expandable {
|
|
194
|
-
background: var(--color-brand-secondary);
|
|
195
|
-
padding: 32px 20px 24px 20px;
|
|
196
|
-
color: white;
|
|
197
|
-
position: sticky;
|
|
198
|
-
top: 0;
|
|
199
|
-
z-index: 5;
|
|
200
|
-
transition: opacity 0.1s ease-out, transform 0.1s ease-out;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.header-expandable-inner {
|
|
204
|
-
display: flex;
|
|
205
|
-
flex-direction: column;
|
|
206
|
-
gap: 20px;
|
|
207
|
-
max-width: 640px;
|
|
208
|
-
margin: 0 auto;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.header-expandable__text {
|
|
212
|
-
margin-bottom: 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.header-expandable__title {
|
|
216
|
-
font-size: var(--font-size-2xl);
|
|
217
|
-
font-weight: 600;
|
|
218
|
-
color: white;
|
|
219
|
-
margin: 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.header-expandable__subtitle {
|
|
223
|
-
font-size: var(--font-size-sm);
|
|
224
|
-
font-weight: 400;
|
|
225
|
-
color: white;
|
|
226
|
-
opacity: 0.85;
|
|
227
|
-
margin: 0;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
@media (min-width: 768px) {
|
|
231
|
-
.header-expandable {
|
|
232
|
-
padding: 48px var(--content-padding-md);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.header-expandable__title {
|
|
236
|
-
font-size: var(--font-size-3xl);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.header-expandable__subtitle {
|
|
240
|
-
font-size: var(--font-size-base);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
@media (min-width: 992px) {
|
|
245
|
-
.header-expandable {
|
|
246
|
-
padding-left: var(--content-padding-lg);
|
|
247
|
-
padding-right: var(--content-padding-lg);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@media (min-width: 1440px) {
|
|
252
|
-
.header-expandable {
|
|
253
|
-
padding-left: var(--content-padding-xl);
|
|
254
|
-
padding-right: var(--content-padding-xl);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
@media (min-width: 1768px) {
|
|
259
|
-
.header-expandable {
|
|
260
|
-
padding-left: var(--content-padding-2xl);
|
|
261
|
-
padding-right: var(--content-padding-2xl);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
@media (min-width: 1920px) {
|
|
266
|
-
.header-expandable {
|
|
267
|
-
padding-left: var(--content-padding-3xl);
|
|
268
|
-
padding-right: var(--content-padding-3xl);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* ============================================
|
|
273
|
-
CONTENT WRAPPER - White Rounded Content
|
|
274
|
-
============================================ */
|
|
275
|
-
|
|
276
|
-
.content-wrapper {
|
|
277
|
-
position: relative;
|
|
278
|
-
z-index: 20;
|
|
279
|
-
padding: 0;
|
|
280
|
-
/* Flex child that grows to fill available space */
|
|
281
|
-
flex: 1;
|
|
282
|
-
display: flex;
|
|
283
|
-
flex-direction: column;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
@media (min-width: 768px) {
|
|
287
|
-
.content-wrapper {
|
|
288
|
-
width: 100%;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.content-inner {
|
|
293
|
-
/* Visual styling - background, border-radius, and shadow extend */
|
|
294
|
-
background: var(--color-background-neutral-primary);
|
|
295
|
-
border-radius: 24px 24px 0 0;
|
|
296
|
-
transform: translateZ(0);
|
|
297
|
-
will-change: transform;
|
|
298
|
-
isolation: isolate;
|
|
299
|
-
/* Extend white background below for iOS overshoot using box-shadow */
|
|
300
|
-
box-shadow: 0 200vh 0 0 var(--color-background-neutral-primary);
|
|
301
|
-
|
|
302
|
-
/* Grow to fill parent flex container */
|
|
303
|
-
flex: 1;
|
|
304
|
-
|
|
305
|
-
/* Fixed 20px horizontal padding globally */
|
|
306
|
-
padding: 24px 20px 32px 20px;
|
|
307
|
-
/* Add bottom padding on mobile to account for fixed tab bar */
|
|
308
|
-
padding-bottom: calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom, 0px));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
@media (min-width: 768px) {
|
|
312
|
-
.content-inner {
|
|
313
|
-
border-radius: 16px 16px 0 0;
|
|
314
|
-
padding: 32px var(--content-padding-md) !important; /* Override inline styles on desktop */
|
|
315
|
-
max-width: calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));
|
|
316
|
-
margin: 0 auto;
|
|
317
|
-
width: 100%;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
@media (min-width: 992px) {
|
|
322
|
-
.content-inner {
|
|
323
|
-
padding: 32px var(--content-padding-lg) !important;
|
|
324
|
-
max-width: calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
@media (min-width: 1440px) {
|
|
329
|
-
.content-inner {
|
|
330
|
-
padding: 32px var(--content-padding-xl) !important;
|
|
331
|
-
max-width: calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
@media (min-width: 1768px) {
|
|
336
|
-
.content-inner {
|
|
337
|
-
padding: 32px var(--content-padding-2xl) !important;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
@media (min-width: 1920px) {
|
|
342
|
-
.content-inner {
|
|
343
|
-
padding: 32px var(--content-padding-3xl) !important;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
@@ -1,331 +0,0 @@
|
|
|
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 {
|
|
5
|
-
IonHeader,
|
|
6
|
-
IonToolbar,
|
|
7
|
-
IonTitle,
|
|
8
|
-
IonContent,
|
|
9
|
-
IonRefresher,
|
|
10
|
-
IonRefresherContent,
|
|
11
|
-
Platform,
|
|
12
|
-
ModalController
|
|
13
|
-
} from '@ionic/angular/standalone';
|
|
14
|
-
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
|
15
|
-
import { DsAvatarComponent } from '@propbinder/design-system';
|
|
16
|
-
import { MobilePageBase } from '../shared/mobile-page-base';
|
|
17
|
-
import { DsMobileActionsBottomSheetComponent, ActionResult } from '../bottom-sheet';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* DsMobilePageMainComponent
|
|
21
|
-
*
|
|
22
|
-
* A complete mobile page layout for main/tab pages with:
|
|
23
|
-
* - Fixed header with logomark + title + avatar
|
|
24
|
-
* - Purple expandable header section (scrolls with content)
|
|
25
|
-
* - White rounded content wrapper
|
|
26
|
-
* - Pull-to-refresh support
|
|
27
|
-
* - Auto scroll title fade-in
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```html
|
|
31
|
-
* <!-- Simple page -->
|
|
32
|
-
* <ds-mobile-page-main
|
|
33
|
-
* title="Inquiries"
|
|
34
|
-
* [avatarInitials]="'JD'">
|
|
35
|
-
* <div class="page-content">
|
|
36
|
-
* <!-- Your content -->
|
|
37
|
-
* </div>
|
|
38
|
-
* </ds-mobile-page-main>
|
|
39
|
-
*
|
|
40
|
-
* <!-- Page with custom header content -->
|
|
41
|
-
* <ds-mobile-page-main
|
|
42
|
-
* title="Home"
|
|
43
|
-
* headerTitle="Welcome, Lars"
|
|
44
|
-
* headerSubtitle="Your rental property at a glance."
|
|
45
|
-
* [avatarInitials]="'L'">
|
|
46
|
-
*
|
|
47
|
-
* <div header-content class="property-tiles">
|
|
48
|
-
* <!-- Custom header content like tiles -->
|
|
49
|
-
* </div>
|
|
50
|
-
*
|
|
51
|
-
* <div class="page-content">
|
|
52
|
-
* <!-- Main page content -->
|
|
53
|
-
* </div>
|
|
54
|
-
* </ds-mobile-page-main>
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
@Component({
|
|
58
|
-
selector: 'ds-mobile-page-main',
|
|
59
|
-
standalone: true,
|
|
60
|
-
imports: [
|
|
61
|
-
CommonModule,
|
|
62
|
-
IonHeader,
|
|
63
|
-
IonToolbar,
|
|
64
|
-
IonTitle,
|
|
65
|
-
IonContent,
|
|
66
|
-
IonRefresher,
|
|
67
|
-
IonRefresherContent,
|
|
68
|
-
DsAvatarComponent
|
|
69
|
-
],
|
|
70
|
-
styleUrls: ['./ds-mobile-page-main.css'],
|
|
71
|
-
template: `
|
|
72
|
-
<!-- Fixed header at top -->
|
|
73
|
-
<ion-header>
|
|
74
|
-
<ion-toolbar>
|
|
75
|
-
<div class="header-home">
|
|
76
|
-
<!-- Propbinder Logomark -->
|
|
77
|
-
<svg class="logomark" width="36" height="32" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
78
|
-
<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"/>
|
|
79
|
-
<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"/>
|
|
80
|
-
</svg>
|
|
81
|
-
|
|
82
|
-
<!-- Title - fades in on scroll -->
|
|
83
|
-
<ion-title class="header-home__title">{{ title() }}</ion-title>
|
|
84
|
-
|
|
85
|
-
<!-- Avatar -->
|
|
86
|
-
<div class="header-home__actions">
|
|
87
|
-
<ds-avatar
|
|
88
|
-
[size]="'md'"
|
|
89
|
-
[type]="avatarType()"
|
|
90
|
-
[initials]="avatarInitials()"
|
|
91
|
-
[src]="avatarSrc()"
|
|
92
|
-
[iconName]="avatarIconName()"
|
|
93
|
-
(click)="handleAvatarClick()"
|
|
94
|
-
style="cursor: pointer;"
|
|
95
|
-
/>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</ion-toolbar>
|
|
99
|
-
</ion-header>
|
|
100
|
-
|
|
101
|
-
<!-- Content with expandable header -->
|
|
102
|
-
<ion-content [scrollEvents]="true" (ionScroll)="handleScroll($event)">
|
|
103
|
-
<!-- Condensed header for Ionic scroll effects -->
|
|
104
|
-
@if (showCondensedHeader()) {
|
|
105
|
-
<ion-header collapse="condense">
|
|
106
|
-
<ion-toolbar>
|
|
107
|
-
<ion-title size="large">{{ title() }}</ion-title>
|
|
108
|
-
</ion-toolbar>
|
|
109
|
-
</ion-header>
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
<!-- Pull to refresh (only on native iOS/Android) -->
|
|
113
|
-
@if (showRefresh() && isNativePlatform()) {
|
|
114
|
-
<ion-refresher
|
|
115
|
-
slot="fixed"
|
|
116
|
-
(ionRefresh)="handleRefresh($event)"
|
|
117
|
-
[pullFactor]="0.4"
|
|
118
|
-
[pullMin]="80"
|
|
119
|
-
[pullMax]="240"
|
|
120
|
-
closeDuration="600ms">
|
|
121
|
-
<ion-refresher-content
|
|
122
|
-
pullingIcon="chevron-down-circle-outline"
|
|
123
|
-
refreshingSpinner="lines">
|
|
124
|
-
</ion-refresher-content>
|
|
125
|
-
</ion-refresher>
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
<!-- Expandable header section (purple background) -->
|
|
129
|
-
<div class="header-expandable">
|
|
130
|
-
<div class="header-expandable-inner">
|
|
131
|
-
<div class="header-expandable__text">
|
|
132
|
-
<h1 class="header-expandable__title">{{ headerTitle() || title() }}</h1>
|
|
133
|
-
@if (headerSubtitle()) {
|
|
134
|
-
<p class="header-expandable__subtitle">{{ headerSubtitle() }}</p>
|
|
135
|
-
}
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<!-- Slot for custom header content (e.g., property tiles) -->
|
|
139
|
-
<ng-content select="[header-content]"></ng-content>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<!-- Content wrapper -->
|
|
144
|
-
<div class="content-wrapper">
|
|
145
|
-
<div class="content-inner">
|
|
146
|
-
<!-- Main page content -->
|
|
147
|
-
<ng-content></ng-content>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</ion-content>
|
|
151
|
-
`
|
|
152
|
-
})
|
|
153
|
-
export class DsMobilePageMainComponent extends MobilePageBase implements AfterViewInit {
|
|
154
|
-
@ViewChild(IonContent) ionContent?: IonContent;
|
|
155
|
-
|
|
156
|
-
// Platform detection
|
|
157
|
-
private platform = inject(Platform);
|
|
158
|
-
private modalController = inject(ModalController);
|
|
159
|
-
private router = inject(Router);
|
|
160
|
-
|
|
161
|
-
// Computed property to check if running on native platform
|
|
162
|
-
isNativePlatform = computed(() =>
|
|
163
|
-
this.platform.is('ios') ||
|
|
164
|
-
this.platform.is('android') ||
|
|
165
|
-
this.platform.is('capacitor')
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Inputs - Title
|
|
169
|
-
title = input.required<string>(); // For fixed header title
|
|
170
|
-
headerTitle = input<string>(''); // Optional different title for expandable header
|
|
171
|
-
headerSubtitle = input<string>('');
|
|
172
|
-
|
|
173
|
-
// Inputs - Avatar
|
|
174
|
-
avatarType = input<'initials' | 'photo' | 'icon'>('initials');
|
|
175
|
-
avatarInitials = input<string>('U');
|
|
176
|
-
avatarSrc = input<string>('');
|
|
177
|
-
avatarIconName = input<string>('remixUser3Line');
|
|
178
|
-
|
|
179
|
-
// Inputs - Features
|
|
180
|
-
showRefresh = input<boolean>(true);
|
|
181
|
-
showCondensedHeader = input<boolean>(true);
|
|
182
|
-
scrollThreshold = input<number>(160); // Pixels to scroll before title appears
|
|
183
|
-
headerFadeDistance = input<number>(150); // Distance over which header fades out
|
|
184
|
-
|
|
185
|
-
// Outputs
|
|
186
|
-
avatarClick = output<void>();
|
|
187
|
-
refresh = output<any>();
|
|
188
|
-
scroll = output<any>();
|
|
189
|
-
|
|
190
|
-
constructor(private elementRef: ElementRef) {
|
|
191
|
-
super();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
ngAfterViewInit(): void {
|
|
195
|
-
// Initial setup if needed
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Handle avatar click - opens profile actions bottom sheet
|
|
200
|
-
*/
|
|
201
|
-
async handleAvatarClick(): Promise<void> {
|
|
202
|
-
console.log('Avatar clicked - opening profile bottom sheet');
|
|
203
|
-
|
|
204
|
-
// Emit the event for any parent listeners
|
|
205
|
-
this.avatarClick.emit();
|
|
206
|
-
|
|
207
|
-
const sheet = await this.modalController.create({
|
|
208
|
-
component: DsMobileActionsBottomSheetComponent,
|
|
209
|
-
componentProps: {
|
|
210
|
-
customActionGroups: [
|
|
211
|
-
{
|
|
212
|
-
actions: [
|
|
213
|
-
{
|
|
214
|
-
action: 'profile',
|
|
215
|
-
title: 'Min profil',
|
|
216
|
-
icon: 'remixUser3Line',
|
|
217
|
-
destructive: false
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
action: 'settings',
|
|
221
|
-
title: 'Indstillinger',
|
|
222
|
-
icon: 'remixSettings3Line',
|
|
223
|
-
destructive: false
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
action: 'whitelabel-demo',
|
|
227
|
-
title: 'Whitelabel Demo',
|
|
228
|
-
icon: 'remixPaletteLine',
|
|
229
|
-
destructive: false
|
|
230
|
-
}
|
|
231
|
-
]
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
actions: [
|
|
235
|
-
{
|
|
236
|
-
action: 'logout',
|
|
237
|
-
title: 'Log ud',
|
|
238
|
-
icon: 'remixLogoutBoxLine',
|
|
239
|
-
destructive: true
|
|
240
|
-
}
|
|
241
|
-
]
|
|
242
|
-
}
|
|
243
|
-
]
|
|
244
|
-
},
|
|
245
|
-
// Auto-height: no breakpoints, no handle
|
|
246
|
-
cssClass: 'ds-bottom-sheet auto-height'
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
await sheet.present();
|
|
250
|
-
|
|
251
|
-
const result = await sheet.onWillDismiss<ActionResult>();
|
|
252
|
-
if (result.data?.action) {
|
|
253
|
-
console.log('Profile action selected:', result.data.action);
|
|
254
|
-
|
|
255
|
-
switch (result.data.action) {
|
|
256
|
-
case 'logout':
|
|
257
|
-
console.log('Logging out...');
|
|
258
|
-
// TODO: Implement logout logic
|
|
259
|
-
break;
|
|
260
|
-
case 'profile':
|
|
261
|
-
console.log('Opening profile...');
|
|
262
|
-
// TODO: Navigate to profile page
|
|
263
|
-
break;
|
|
264
|
-
case 'settings':
|
|
265
|
-
console.log('Opening settings...');
|
|
266
|
-
// TODO: Navigate to settings page
|
|
267
|
-
break;
|
|
268
|
-
case 'whitelabel-demo':
|
|
269
|
-
console.log('Opening whitelabel demo...');
|
|
270
|
-
this.router.navigate(['/whitelabel-demo']);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Handle scroll events
|
|
278
|
-
* - Shows title in fixed header when scrolled past threshold
|
|
279
|
-
* - Fades out expandable header content based on scroll position
|
|
280
|
-
* - Emits scroll event for custom handling
|
|
281
|
-
*/
|
|
282
|
-
handleScroll(event: any): void {
|
|
283
|
-
const scrollTop = event.detail.scrollTop;
|
|
284
|
-
const header = this.elementRef.nativeElement.querySelector('ion-header:not([collapse])');
|
|
285
|
-
const headerExpandable = this.elementRef.nativeElement.querySelector('.header-expandable');
|
|
286
|
-
|
|
287
|
-
// Show title in fixed header when scrolled past threshold
|
|
288
|
-
if (scrollTop > this.scrollThreshold()) {
|
|
289
|
-
header?.classList.add('header-scrolled');
|
|
290
|
-
} else {
|
|
291
|
-
header?.classList.remove('header-scrolled');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Fade out header-expandable content based on scroll
|
|
295
|
-
if (headerExpandable) {
|
|
296
|
-
const fadeDistance = this.headerFadeDistance();
|
|
297
|
-
const fadeProgress = Math.min(scrollTop / fadeDistance, 1);
|
|
298
|
-
|
|
299
|
-
// Calculate opacity (1 to 0)
|
|
300
|
-
const opacity = 1 - fadeProgress;
|
|
301
|
-
|
|
302
|
-
// Calculate transform (0px to -20px upward)
|
|
303
|
-
const translateY = fadeProgress * -20;
|
|
304
|
-
|
|
305
|
-
// Apply styles
|
|
306
|
-
headerExpandable.style.opacity = opacity.toString();
|
|
307
|
-
headerExpandable.style.transform = `translateY(${translateY}px)`;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
this.scroll.emit(event);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Handle pull-to-refresh
|
|
315
|
-
* Emits refresh event - parent should call event.target.complete()
|
|
316
|
-
*/
|
|
317
|
-
async handleRefresh(event: any): Promise<void> {
|
|
318
|
-
// Haptic feedback for pull-to-refresh
|
|
319
|
-
try {
|
|
320
|
-
await Haptics.impact({ style: ImpactStyle.Medium });
|
|
321
|
-
} catch {
|
|
322
|
-
// Fallback to Web Vibration API if Capacitor Haptics is not available
|
|
323
|
-
if ('vibrate' in navigator) {
|
|
324
|
-
navigator.vibrate(50);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.refresh.emit(event);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|