@propbinder/mobile-design 0.2.47 → 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 -26136
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8154
- /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,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile Modal Base Styles
|
|
3
|
+
*
|
|
4
|
+
* Shared styles for all mobile modal components.
|
|
5
|
+
* These styles handle the base modal structure, header, loading/error states,
|
|
6
|
+
* and fixed bottom components.
|
|
7
|
+
*
|
|
8
|
+
* Component-specific styles should be defined in each modal's own CSS file.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ============================================
|
|
12
|
+
HOST AND CONTENT
|
|
13
|
+
============================================ */
|
|
14
|
+
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
position: relative;
|
|
18
|
+
height: 100%;
|
|
19
|
+
width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:host(.is-auto-height) {
|
|
23
|
+
height: auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.modal-base-content {
|
|
27
|
+
--background: var(--color-background-neutral-primary, #ffffff);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.modal-base-content.is-auto-height {
|
|
31
|
+
--height: auto;
|
|
32
|
+
height: auto !important;
|
|
33
|
+
flex: 0 0 auto;
|
|
34
|
+
display: block;
|
|
35
|
+
contain: none !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Ensure the scroll part also allows auto height */
|
|
39
|
+
.modal-base-content.is-auto-height::part(scroll) {
|
|
40
|
+
position: relative !important;
|
|
41
|
+
display: block !important;
|
|
42
|
+
height: auto !important;
|
|
43
|
+
overflow: visible !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ============================================
|
|
47
|
+
MODAL WRAPPER
|
|
48
|
+
============================================ */
|
|
49
|
+
|
|
50
|
+
.modal-wrapper {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
width: 100%;
|
|
54
|
+
background: var(--color-background-neutral-primary, #ffffff);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.modal-wrapper.is-auto-height {
|
|
58
|
+
flex: 0 0 auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ============================================
|
|
62
|
+
MODAL HEADER
|
|
63
|
+
============================================ */
|
|
64
|
+
|
|
65
|
+
.modal-header {
|
|
66
|
+
position: sticky;
|
|
67
|
+
top: 0;
|
|
68
|
+
z-index: 10;
|
|
69
|
+
background: var(--color-background-neutral-primary, #ffffff);
|
|
70
|
+
border-bottom: 1px solid var(--border-color-default);
|
|
71
|
+
padding: 16px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.modal-header.no-leading-content {
|
|
75
|
+
padding-left: 20px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.header-content {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: space-between;
|
|
82
|
+
gap: 12px;
|
|
83
|
+
/* No padding needed - StatusBar.setOverlaysWebView(false) handles all spacing */
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Header Leading (Avatar, Icon) */
|
|
87
|
+
.header-leading {
|
|
88
|
+
flex-shrink: 0;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Hide header-leading when empty and adjust spacing */
|
|
94
|
+
.header-leading:empty {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Adjust gap when header-leading is empty */
|
|
99
|
+
.modal-header.no-leading-content .header-content,
|
|
100
|
+
.header-content:has(.header-leading:empty) {
|
|
101
|
+
gap: 16px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Header Main (Title, Meta, Custom Content) */
|
|
105
|
+
.header-main {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
min-width: 0;
|
|
109
|
+
flex: 1;
|
|
110
|
+
gap: 2px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.modal-title {
|
|
114
|
+
font-family: 'Brockmann', sans-serif;
|
|
115
|
+
font-size: var(--font-size-base);
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
line-height: 20px;
|
|
118
|
+
letter-spacing: -0.3px;
|
|
119
|
+
color: var(--color-text-primary, #1a1a1a);
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
text-overflow: ellipsis;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.modal-meta {
|
|
126
|
+
font-family: 'Brockmann', sans-serif;
|
|
127
|
+
font-size: var(--font-size-xs);
|
|
128
|
+
font-weight: 400;
|
|
129
|
+
line-height: 1.2;
|
|
130
|
+
letter-spacing: -0.26px;
|
|
131
|
+
color: var(--color-text-tertiary, #737373);
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 6px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Close Button (in header) */
|
|
138
|
+
.close-button {
|
|
139
|
+
flex-shrink: 0;
|
|
140
|
+
border-radius: 50%;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.close-button::ng-deep button {
|
|
144
|
+
border-radius: 50% !important;
|
|
145
|
+
width: 36px !important;
|
|
146
|
+
height: 36px !important;
|
|
147
|
+
min-width: 36px !important;
|
|
148
|
+
min-height: 36px !important;
|
|
149
|
+
padding: 0 !important;
|
|
150
|
+
display: flex !important;
|
|
151
|
+
align-items: center !important;
|
|
152
|
+
justify-content: center !important;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Close Button (absolute, when header is hidden) */
|
|
156
|
+
.close-button-absolute {
|
|
157
|
+
position: absolute;
|
|
158
|
+
top: 16px;
|
|
159
|
+
right: 16px;
|
|
160
|
+
z-index: 100;
|
|
161
|
+
flex-shrink: 0;
|
|
162
|
+
border-radius: 50%;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.close-button-absolute::ng-deep button {
|
|
166
|
+
border-radius: 50% !important;
|
|
167
|
+
width: 36px !important;
|
|
168
|
+
height: 36px !important;
|
|
169
|
+
min-width: 36px !important;
|
|
170
|
+
min-height: 36px !important;
|
|
171
|
+
padding: 0 !important;
|
|
172
|
+
display: flex !important;
|
|
173
|
+
align-items: center !important;
|
|
174
|
+
justify-content: center !important;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ============================================
|
|
178
|
+
MODAL CONTENT CONTAINER
|
|
179
|
+
============================================ */
|
|
180
|
+
|
|
181
|
+
.modal-content-container {
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
width: 100%;
|
|
185
|
+
max-width: 640px;
|
|
186
|
+
margin: 0 auto;
|
|
187
|
+
flex: 1;
|
|
188
|
+
position: relative;
|
|
189
|
+
/* Dynamic bottom padding that matches fixed bottom height */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Allow auto-height when used as a bottom sheet */
|
|
193
|
+
:host-context(ion-modal.auto-height) .modal-content-container,
|
|
194
|
+
.modal-wrapper.is-auto-height .modal-content-container {
|
|
195
|
+
flex: 0 0 auto;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* No top padding when header is hidden - ds-mobile-section provides spacing */
|
|
199
|
+
.modal-wrapper.headerless .modal-content-container {
|
|
200
|
+
padding-top: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.modal-main-content {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
width: 100%;
|
|
207
|
+
/* No top padding - ds-mobile-section provides its own */
|
|
208
|
+
padding-top: 0;
|
|
209
|
+
padding-left: var(--modal-content-padding, 20px);
|
|
210
|
+
padding-right: var(--modal-content-padding, 20px);
|
|
211
|
+
padding-bottom: var(--modal-content-padding, 20px);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.modal-main-content.content-hidden {
|
|
215
|
+
display: none;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Hide state containers when not active */
|
|
219
|
+
.state-hidden {
|
|
220
|
+
display: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.custom-loading-slot,
|
|
224
|
+
.custom-error-slot {
|
|
225
|
+
width: 100%;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ============================================
|
|
229
|
+
LOADING STATE
|
|
230
|
+
============================================ */
|
|
231
|
+
|
|
232
|
+
.modal-loading-state {
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
align-items: center;
|
|
236
|
+
justify-content: center;
|
|
237
|
+
padding: 60px 20px;
|
|
238
|
+
text-align: center;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.loading-spinner {
|
|
242
|
+
width: 48px;
|
|
243
|
+
height: 48px;
|
|
244
|
+
border: 3px solid var(--color-background-neutral-secondary, #f0f0f0);
|
|
245
|
+
border-top-color: var(--color-primary-base, #2563eb);
|
|
246
|
+
border-radius: 50%;
|
|
247
|
+
animation: spin 1s linear infinite;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@keyframes spin {
|
|
251
|
+
to {
|
|
252
|
+
transform: rotate(360deg);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.loading-text {
|
|
257
|
+
font-family: 'Brockmann', sans-serif;
|
|
258
|
+
font-size: var(--font-size-sm);
|
|
259
|
+
font-weight: 400;
|
|
260
|
+
line-height: 1.4;
|
|
261
|
+
color: var(--color-text-secondary, #737373);
|
|
262
|
+
margin-top: 16px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* ============================================
|
|
266
|
+
ERROR STATE
|
|
267
|
+
============================================ */
|
|
268
|
+
|
|
269
|
+
.modal-error-state {
|
|
270
|
+
display: flex;
|
|
271
|
+
flex-direction: column;
|
|
272
|
+
align-items: center;
|
|
273
|
+
justify-content: center;
|
|
274
|
+
padding: 60px 20px;
|
|
275
|
+
text-align: center;
|
|
276
|
+
gap: 16px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.error-state-title {
|
|
280
|
+
font-family: 'Brockmann', sans-serif;
|
|
281
|
+
font-size: var(--font-size-base);
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
line-height: 1.3;
|
|
284
|
+
color: var(--color-text-primary, #1a1a1a);
|
|
285
|
+
margin: 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.error-state-description {
|
|
289
|
+
font-family: 'Brockmann', sans-serif;
|
|
290
|
+
font-size: var(--font-size-sm);
|
|
291
|
+
font-weight: 400;
|
|
292
|
+
line-height: 1.4;
|
|
293
|
+
color: var(--color-text-secondary, #737373);
|
|
294
|
+
margin: 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ============================================
|
|
298
|
+
FIXED BOTTOM COMPONENT / FOOTER
|
|
299
|
+
============================================ */
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Fixed bottom container for footer actions (e.g., submit buttons, composers)
|
|
303
|
+
*
|
|
304
|
+
* Features:
|
|
305
|
+
* - Automatically slides up with keyboard on iOS/Android (via --keyboard-height)
|
|
306
|
+
* - Synchronized with scroll animation using matching cubic-bezier curve
|
|
307
|
+
* - Respects safe areas with bottom padding
|
|
308
|
+
* - Hidden during loading/error states
|
|
309
|
+
* - Opaque background with box-shadow to cover gap between content and keyboard
|
|
310
|
+
* - Use [footer] or [fixed-bottom] attribute on content
|
|
311
|
+
*
|
|
312
|
+
* Usage:
|
|
313
|
+
* <ds-mobile-modal-base [hasFixedBottom]="true" [enableKeyboardHandling]="true">
|
|
314
|
+
* <div footer>Your footer content</div>
|
|
315
|
+
* </ds-mobile-modal-base>
|
|
316
|
+
*/
|
|
317
|
+
.modal-fixed-bottom {
|
|
318
|
+
position: fixed;
|
|
319
|
+
bottom: 0;
|
|
320
|
+
left: 0;
|
|
321
|
+
right: 0;
|
|
322
|
+
z-index: 1000;
|
|
323
|
+
pointer-events: none;
|
|
324
|
+
/* Background and box-shadow to cover gap between fixed bottom and keyboard */
|
|
325
|
+
background: var(--color-background-neutral-primary, #ffffff);
|
|
326
|
+
box-shadow: 0 300px 0 300px var(--color-background-neutral-primary, #ffffff);
|
|
327
|
+
/* Slide up with keyboard on native apps */
|
|
328
|
+
transform: translateY(calc(-1 * var(--keyboard-height, 0px)));
|
|
329
|
+
/* Use cubic-bezier that matches scroll animation (ease-out cubic) for perfect sync */
|
|
330
|
+
transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
331
|
+
max-width: 100vw;
|
|
332
|
+
/* Same safe-area strategy as tab bar: iOS uses reduced inset with floor */
|
|
333
|
+
padding-bottom: max(8px, calc(var(--app-safe-bottom, 0px) - 24px));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Android gesture navigation keeps full inset for footer controls */
|
|
337
|
+
:host-context(.plt-android) .modal-fixed-bottom {
|
|
338
|
+
padding-bottom: max(8px, var(--app-safe-bottom, 0px));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.modal-fixed-bottom.is-auto-height {
|
|
342
|
+
position: relative;
|
|
343
|
+
bottom: auto;
|
|
344
|
+
left: auto;
|
|
345
|
+
right: auto;
|
|
346
|
+
transform: none !important;
|
|
347
|
+
box-shadow: none;
|
|
348
|
+
z-index: 1;
|
|
349
|
+
background: var(--color-background-neutral-primary, #ffffff);
|
|
350
|
+
padding-bottom: var(--keyboard-height, 0px);
|
|
351
|
+
transition: padding-bottom 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.modal-fixed-bottom > * {
|
|
355
|
+
pointer-events: auto;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.modal-fixed-bottom.bottom-hidden {
|
|
359
|
+
display: none;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* ============================================
|
|
363
|
+
SAFE AREA SUPPORT
|
|
364
|
+
============================================ */
|
|
365
|
+
|
|
366
|
+
/* ============================================
|
|
367
|
+
ACCESSIBILITY: REDUCED MOTION
|
|
368
|
+
============================================ */
|
|
369
|
+
|
|
370
|
+
@media (prefers-reduced-motion: reduce) {
|
|
371
|
+
.modal-fixed-bottom {
|
|
372
|
+
transition: none;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.loading-spinner {
|
|
376
|
+
animation: none;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { Component, ContentChild, ElementRef, CUSTOM_ELEMENTS_SCHEMA, AfterContentInit, ChangeDetectorRef, input, HostBinding, ViewChild, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { IonContent } from '@ionic/angular/standalone';
|
|
4
|
+
import { DsIconButtonComponent, DsIconComponent } from '@propbinder/design-system';
|
|
5
|
+
import { MobileModalBase } from '../shared/mobile-modal-base';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* DsMobileModalBaseComponent
|
|
9
|
+
*
|
|
10
|
+
* Base modal component providing consistent layout and behavior for all modals.
|
|
11
|
+
*
|
|
12
|
+
* **Features:**
|
|
13
|
+
* - Optional header with auto-detection of content
|
|
14
|
+
* - Flexible header with slots for leading content (avatar, icon)
|
|
15
|
+
* - Title and metadata inputs or custom header slot
|
|
16
|
+
* - Default loading and error state templates (with override capability)
|
|
17
|
+
* - Fixed bottom component support (e.g., message composer)
|
|
18
|
+
* - Automatic keyboard handling
|
|
19
|
+
* - Configurable keyboard content behavior (`follow` or `overlay`)
|
|
20
|
+
* - Safe area support
|
|
21
|
+
*
|
|
22
|
+
* **Slot Structure:**
|
|
23
|
+
* - `[header-leading]` - Left side of header (avatar, icon)
|
|
24
|
+
* - `[header-main]` - Custom header content (replaces title/meta)
|
|
25
|
+
* - `[loading-state]` - Custom loading template
|
|
26
|
+
* - `[error-state]` - Custom error template
|
|
27
|
+
* - `[footer]` or `[fixed-bottom]` - Fixed bottom component (slides with keyboard)
|
|
28
|
+
* - Default slot - Main modal content
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```html
|
|
32
|
+
* <!-- Simple title modal -->
|
|
33
|
+
* <ds-mobile-modal-base
|
|
34
|
+
* headerTitle="Settings"
|
|
35
|
+
* closeButtonLabel="Close">
|
|
36
|
+
* <div class="content">...</div>
|
|
37
|
+
* </ds-mobile-modal-base>
|
|
38
|
+
*
|
|
39
|
+
* <!-- Avatar header modal -->
|
|
40
|
+
* <ds-mobile-modal-base
|
|
41
|
+
* headerTitle="John Doe"
|
|
42
|
+
* headerMeta="Tenant · 2h ago"
|
|
43
|
+
* [hasFixedBottom]="true"
|
|
44
|
+
* [enableKeyboardHandling]="true">
|
|
45
|
+
*
|
|
46
|
+
* <ds-avatar header-leading [initials]="'JD'" size="md" />
|
|
47
|
+
*
|
|
48
|
+
* <div class="content">...</div>
|
|
49
|
+
*
|
|
50
|
+
* <div fixed-bottom class="composer">...</div>
|
|
51
|
+
* </ds-mobile-modal-base>
|
|
52
|
+
*
|
|
53
|
+
* <!-- Headerless modal (close button positioned absolutely) -->
|
|
54
|
+
* <ds-mobile-modal-base [showHeader]="false">
|
|
55
|
+
* <div class="full-width-content">...</div>
|
|
56
|
+
* </ds-mobile-modal-base>
|
|
57
|
+
*
|
|
58
|
+
* <!-- Modal with footer actions (slides with keyboard) -->
|
|
59
|
+
* <ds-mobile-modal-base
|
|
60
|
+
* headerTitle="Create Inquiry"
|
|
61
|
+
* [hasFixedBottom]="true"
|
|
62
|
+
* [enableKeyboardHandling]="true"
|
|
63
|
+
* [keyboardContentBehavior]="'overlay'">
|
|
64
|
+
*
|
|
65
|
+
* <div class="content">
|
|
66
|
+
* <input type="text" placeholder="Type something..." />
|
|
67
|
+
* </div>
|
|
68
|
+
*
|
|
69
|
+
* <div footer class="action-bar">
|
|
70
|
+
* <button>Cancel</button>
|
|
71
|
+
* <button>Submit</button>
|
|
72
|
+
* </div>
|
|
73
|
+
* </ds-mobile-modal-base>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
@Component({
|
|
77
|
+
selector: 'ds-mobile-modal-base',
|
|
78
|
+
standalone: true,
|
|
79
|
+
imports: [CommonModule, IonContent, DsIconButtonComponent, DsIconComponent],
|
|
80
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
81
|
+
styleUrls: ['./ds-mobile-modal-base.css'],
|
|
82
|
+
host: {
|
|
83
|
+
'[style.--modal-content-padding]': 'contentPadding()',
|
|
84
|
+
'[class.is-auto-height]': 'isAutoHeight()',
|
|
85
|
+
},
|
|
86
|
+
template: `
|
|
87
|
+
<ion-content
|
|
88
|
+
[fullscreen]="!isAutoHeight()"
|
|
89
|
+
[scrollY]="true"
|
|
90
|
+
[class.is-auto-height]="isAutoHeight()"
|
|
91
|
+
class="modal-base-content"
|
|
92
|
+
[style.--padding-bottom]="contentPadding() || (hasFixedBottom() ? 'var(--fixed-bottom-height)' : '24px')"
|
|
93
|
+
>
|
|
94
|
+
<div class="modal-wrapper" [class.headerless]="!shouldShowHeader()" [class.is-auto-height]="isAutoHeight()">
|
|
95
|
+
<!-- Header (conditional) -->
|
|
96
|
+
@if (shouldShowHeader()) {
|
|
97
|
+
<div class="modal-header" [class.no-leading-content]="!hasHeaderLeadingContent()">
|
|
98
|
+
<div class="header-content">
|
|
99
|
+
<!-- Leading slot (avatar, icon) - always rendered, CSS handles empty state -->
|
|
100
|
+
<div class="header-leading">
|
|
101
|
+
<ng-content select="[header-leading]"></ng-content>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Main (title + meta or custom) -->
|
|
105
|
+
<div class="header-main">
|
|
106
|
+
@if (headerTitle()) {
|
|
107
|
+
<div class="modal-title">{{ headerTitle() }}</div>
|
|
108
|
+
}
|
|
109
|
+
@if (headerMeta()) {
|
|
110
|
+
<div class="modal-meta">{{ headerMeta() }}</div>
|
|
111
|
+
}
|
|
112
|
+
<ng-content select="[header-main]"></ng-content>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Close button (in header) -->
|
|
116
|
+
<ds-icon-button icon="remixCloseLine" variant="secondary" size="lg" (clicked)="close()" class="close-button" [attr.aria-label]="closeButtonLabel()" />
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
<!-- Absolute close button (when header is hidden) -->
|
|
122
|
+
@if (!shouldShowHeader()) {
|
|
123
|
+
<ds-icon-button icon="remixCloseLine" variant="secondary" size="lg" (clicked)="close()" class="close-button-absolute" [attr.aria-label]="closeButtonLabel()" />
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
<!-- Content Container -->
|
|
127
|
+
<div class="modal-content-container">
|
|
128
|
+
<!-- Custom Loading State Slot - always present -->
|
|
129
|
+
<div class="custom-loading-slot" [class.state-hidden]="!(loading() && hasCustomLoadingState)">
|
|
130
|
+
<ng-content select="[loading-state]"></ng-content>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Default Loading State -->
|
|
134
|
+
@if (loading() && !hasCustomLoadingState) {
|
|
135
|
+
<div class="modal-loading-state">
|
|
136
|
+
<div class="loading-spinner"></div>
|
|
137
|
+
<p class="loading-text">Loading...</p>
|
|
138
|
+
</div>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
<!-- Custom Error State Slot - always present -->
|
|
142
|
+
<div class="custom-error-slot" [class.state-hidden]="!(error() && !loading() && hasCustomErrorState)">
|
|
143
|
+
<ng-content select="[error-state]"></ng-content>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- Default Error State -->
|
|
147
|
+
@if (error() && !loading() && !hasCustomErrorState) {
|
|
148
|
+
<div class="modal-error-state">
|
|
149
|
+
<ds-icon name="remixErrorWarningLine" size="48px" [style.color]="'var(--color-destructive-base)'" />
|
|
150
|
+
<h3 class="error-state-title">Error</h3>
|
|
151
|
+
<p class="error-state-description">{{ error() }}</p>
|
|
152
|
+
</div>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
<!-- Main content - always rendered but hidden when loading/error -->
|
|
156
|
+
<div class="modal-main-content" [class.content-hidden]="loading() || !!error()">
|
|
157
|
+
<ng-content></ng-content>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</ion-content>
|
|
162
|
+
|
|
163
|
+
<!-- Fixed Bottom Component / Footer (slides with keyboard) -->
|
|
164
|
+
<div class="modal-fixed-bottom" [class.bottom-hidden]="!hasFixedBottom() || loading() || error()" [class.is-auto-height]="isAutoHeight()">
|
|
165
|
+
<ng-content select="[fixed-bottom]"></ng-content>
|
|
166
|
+
<ng-content select="[footer]"></ng-content>
|
|
167
|
+
</div>
|
|
168
|
+
`,
|
|
169
|
+
})
|
|
170
|
+
export class DsMobileModalBaseComponent extends MobileModalBase implements OnInit, AfterContentInit, OnDestroy {
|
|
171
|
+
/**
|
|
172
|
+
* Reference to ion-content for keyboard handling
|
|
173
|
+
*/
|
|
174
|
+
@ViewChild(IonContent, { read: IonContent }) override ionContent?: IonContent = undefined;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Control header visibility
|
|
178
|
+
* - true: Always show header
|
|
179
|
+
* - false: Never show header (close button becomes absolute)
|
|
180
|
+
* - 'auto' (default): Auto-detect based on header content
|
|
181
|
+
*/
|
|
182
|
+
showHeader = input<boolean | 'auto'>('auto');
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Detect if custom loading state is provided
|
|
186
|
+
*/
|
|
187
|
+
@ContentChild('[loading-state]', { read: ElementRef }) customLoadingState?: ElementRef;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Detect if custom error state is provided
|
|
191
|
+
*/
|
|
192
|
+
@ContentChild('[error-state]', { read: ElementRef }) customErrorState?: ElementRef;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Detect if header leading content is provided
|
|
196
|
+
*/
|
|
197
|
+
@ContentChild('[header-leading]', { read: ElementRef }) headerLeading?: ElementRef;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Detect if header main content is provided
|
|
201
|
+
*/
|
|
202
|
+
@ContentChild('[header-main]', { read: ElementRef }) headerMain?: ElementRef;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Flag to track if content has been initialized
|
|
206
|
+
*/
|
|
207
|
+
hasCustomLoadingState = false;
|
|
208
|
+
hasCustomErrorState = false;
|
|
209
|
+
|
|
210
|
+
constructor(private cdr: ChangeDetectorRef) {
|
|
211
|
+
super();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
override ngOnInit(): void {
|
|
215
|
+
// Call parent to set up keyboard listeners and ResizeObserver
|
|
216
|
+
super.ngOnInit();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
ngAfterContentInit(): void {
|
|
220
|
+
// Check for content after content has been initialized
|
|
221
|
+
this.hasCustomLoadingState = !!this.customLoadingState;
|
|
222
|
+
this.hasCustomErrorState = !!this.customErrorState;
|
|
223
|
+
|
|
224
|
+
// Trigger change detection to update the view
|
|
225
|
+
this.cdr.detectChanges();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
override ngOnDestroy(): void {
|
|
229
|
+
super.ngOnDestroy();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Determine if header should be shown based on showHeader input and content detection
|
|
234
|
+
*/
|
|
235
|
+
shouldShowHeader(): boolean {
|
|
236
|
+
const showHeaderValue = this.showHeader();
|
|
237
|
+
|
|
238
|
+
// Explicit override
|
|
239
|
+
if (showHeaderValue === true) return true;
|
|
240
|
+
if (showHeaderValue === false) return false;
|
|
241
|
+
|
|
242
|
+
// Auto-detect: show header if there's any header content
|
|
243
|
+
return !!(this.headerTitle() || this.headerMeta() || this.hasContentInSlot(this.headerLeading) || this.hasContentInSlot(this.headerMain));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Check whether header-leading slot has actual projected content.
|
|
248
|
+
*/
|
|
249
|
+
hasHeaderLeadingContent(): boolean {
|
|
250
|
+
return this.hasContentInSlot(this.headerLeading);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check if a content child slot has actual content
|
|
255
|
+
*/
|
|
256
|
+
private hasContentInSlot(slot?: ElementRef): boolean {
|
|
257
|
+
if (!slot) return false;
|
|
258
|
+
const element = slot.nativeElement;
|
|
259
|
+
return element && (element.childNodes.length > 0 || element.textContent?.trim().length > 0);
|
|
260
|
+
}
|
|
261
|
+
}
|