@propbinder/mobile-design 0.1.22 → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@propbinder/mobile-design",
3
- "version": "0.1.22",
3
+ "version": "0.2.00",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.3.0 || ^21.0.0",
6
6
  "@angular/core": "^20.3.0 || ^21.0.0"
package/styles/ionic.css CHANGED
@@ -631,6 +631,58 @@ ion-app ion-router-outlet.ion-page-hidden {
631
631
  }
632
632
  }
633
633
 
634
+ /* ============================================
635
+ Chat Modal Styles
636
+ ============================================ */
637
+
638
+ .ds-chat-modal {
639
+ --background: var(--color-background-neutral-primary, #ffffff);
640
+ --width: 100%;
641
+ --max-width: 640px;
642
+ --height: 100dvh; /* Full viewport height - content top offset creates gap */
643
+ --border-radius: 16px 16px 0 0;
644
+ }
645
+
646
+ .ds-chat-modal::part(content) {
647
+ border-radius: 16px 16px 0 0;
648
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
649
+ background: var(--color-background-neutral-primary, #ffffff);
650
+ max-width: 640px;
651
+ margin: 0 auto;
652
+ /* Use top positioning instead of margin-top to work with fixed positioning */
653
+ top: var(--app-sheet-top-offset) !important;
654
+ height: calc(100% - var(--app-sheet-top-offset)) !important;
655
+ max-height: calc(100vh - var(--app-sheet-top-offset)) !important;
656
+ }
657
+
658
+ .ds-chat-modal::part(backdrop) {
659
+ background: rgba(0, 0, 0, 0.4);
660
+ backdrop-filter: blur(4px);
661
+ }
662
+
663
+ .ds-chat-modal ion-content {
664
+ --background: var(--color-background-neutral-primary, #ffffff);
665
+ }
666
+
667
+ /* Dark mode support */
668
+ @media (prefers-color-scheme: dark) {
669
+ .ds-chat-modal {
670
+ --background: var(--color-background-neutral-primary-dark, #1a1a1a);
671
+ }
672
+
673
+ .ds-chat-modal::part(content) {
674
+ background: var(--color-background-neutral-primary-dark, #1a1a1a);
675
+ }
676
+
677
+ .ds-chat-modal::part(backdrop) {
678
+ background: rgba(0, 0, 0, 0.6);
679
+ }
680
+
681
+ .ds-chat-modal ion-content {
682
+ --background: var(--color-background-neutral-primary-dark, #1a1a1a);
683
+ }
684
+ }
685
+
634
686
  /* ============================================
635
687
  Handbook Detail Modal Styles
636
688
  ============================================ */
@@ -690,9 +742,8 @@ ion-app ion-router-outlet.ion-page-hidden {
690
742
  --background: var(--color-background-neutral-primary, #ffffff);
691
743
  --width: 100%;
692
744
  --max-width: 640px;
693
- --height: 100dvh;
745
+ --height: 100dvh; /* Full viewport height - content top offset creates gap */
694
746
  --border-radius: 16px 16px 0 0;
695
- margin-top: var(--app-sheet-top-offset);
696
747
  }
697
748
 
698
749
  .ds-whitelabel-demo-modal::part(content) {
@@ -701,6 +752,10 @@ ion-app ion-router-outlet.ion-page-hidden {
701
752
  background: var(--color-background-neutral-primary, #ffffff);
702
753
  max-width: 640px;
703
754
  margin: 0 auto;
755
+ /* Use top positioning instead of margin-top to work with fixed positioning */
756
+ top: var(--app-sheet-top-offset) !important;
757
+ height: calc(100% - var(--app-sheet-top-offset)) !important;
758
+ max-height: calc(100vh - var(--app-sheet-top-offset)) !important;
704
759
  }
705
760
 
706
761
  .ds-whitelabel-demo-modal::part(backdrop) {
@@ -712,3 +767,22 @@ ion-app ion-router-outlet.ion-page-hidden {
712
767
  --background: #ffffff;
713
768
  }
714
769
 
770
+ /* Dark mode support */
771
+ @media (prefers-color-scheme: dark) {
772
+ .ds-whitelabel-demo-modal {
773
+ --background: var(--color-background-neutral-primary-dark, #1a1a1a);
774
+ }
775
+
776
+ .ds-whitelabel-demo-modal::part(content) {
777
+ background: var(--color-background-neutral-primary-dark, #1a1a1a);
778
+ }
779
+
780
+ .ds-whitelabel-demo-modal::part(backdrop) {
781
+ background: rgba(0, 0, 0, 0.6);
782
+ }
783
+
784
+ .ds-whitelabel-demo-modal ion-content {
785
+ --background: var(--color-background-neutral-primary-dark, #1a1a1a);
786
+ }
787
+ }
788
+
@@ -5,44 +5,30 @@
5
5
  ============================================ */
6
6
 
7
7
  /* ============================================
8
- ION-CONTENT
8
+ HOST
9
9
  ============================================ */
10
10
 
11
- ion-content {
12
- --background: transparent;
13
- --padding-top: 0;
14
- --padding-start: 0;
15
- --padding-end: 0;
16
- --padding-bottom: 0;
17
- border-radius: 24px 24px 0 0;
18
- overflow: hidden;
19
- }
20
-
21
- ion-content::part(scroll) {
22
- -webkit-overflow-scrolling: touch;
23
- overscroll-behavior-y: none;
24
- }
25
-
26
- /* Desktop/Tablet adjustments */
27
- @media (min-width: 768px) {
28
- ion-content {
29
- border-radius: 16px 16px 0 0;
30
- }
11
+ :host {
12
+ display: flex;
13
+ flex-direction: column;
14
+ align-items: center;
15
+ height: 100%;
31
16
  }
32
17
 
33
18
  /* ============================================
34
- ION-HEADER
19
+ SHARED ION-HEADER STYLES
35
20
  ============================================ */
36
21
 
37
- ion-header {
38
- background: transparent;
22
+ :host ion-header {
23
+ background: var(--color-brand-secondary);
39
24
  box-shadow: none;
40
25
  height: 72px;
41
26
  min-height: 72px;
27
+ margin-top: var(--app-header-top-offset);
42
28
  }
43
29
 
44
- ion-header ion-toolbar {
45
- --background: transparent;
30
+ :host ion-header ion-toolbar {
31
+ --background: var(--color-brand-secondary);
46
32
  --border-width: 0;
47
33
  --box-shadow: none;
48
34
  --padding-top: 0;
@@ -55,158 +41,239 @@ ion-header ion-toolbar {
55
41
  padding: 0;
56
42
  }
57
43
 
58
- /* Hide header on desktop when using ds-mobile-tabs top bar */
59
44
  @media (min-width: 768px) {
60
- ion-header {
61
- display: none;
62
- height: auto;
45
+ :host ion-header {
46
+ height: 88px;
47
+ min-height: 88px;
63
48
  }
64
- }
65
-
66
- /* ============================================
67
- REFRESHER
68
- ============================================ */
69
49
 
70
- ion-refresher {
71
- z-index: 0;
50
+ :host ion-header ion-toolbar {
51
+ --min-height: 88px;
52
+ height: 88px;
53
+ min-height: 88px;
54
+ }
72
55
  }
73
56
 
74
- ion-refresher-content {
75
- --color: white;
57
+ /* Hide header on desktop when using ds-mobile-tabs top bar */
58
+ /* Note: This only applies to components using ds-mobile-page-main/ds-mobile-page-details */
59
+ /* Pages that need the header visible on desktop should override this */
60
+ @media (min-width: 768px) {
61
+ :host ion-header {
62
+ display: none;
63
+ height: auto;
64
+ }
76
65
  }
77
66
 
78
67
  /* ============================================
79
- HEADER VARIANTS
68
+ SHARED HEADER CONTAINER PATTERNS
80
69
  ============================================ */
81
70
 
82
- .header-home {
71
+ /* Common flex container pattern for header content */
72
+ :host .header-main,
73
+ :host .header-details,
74
+ .header-details {
83
75
  display: flex;
84
76
  align-items: center;
85
77
  justify-content: space-between;
86
- padding: 0 20px;
87
78
  background: var(--color-brand-secondary);
88
- height: 72px;
79
+ position: relative;
89
80
  }
90
81
 
91
- .header-home__title {
82
+ /* Common centered title pattern */
83
+ :host .header-main__title,
84
+ :host .header-details .header-title,
85
+ .header-details .header-title {
92
86
  position: absolute;
93
87
  left: 50%;
94
- transform: translateX(-50%) translateY(-100%);
88
+ transform: translateX(-50%);
95
89
  font-size: var(--font-size-base);
96
90
  font-weight: 600;
97
91
  color: white;
98
- opacity: 0;
99
- transition: transform 0.6s ease, opacity 0.6s ease;
100
92
  margin: 0;
101
93
  padding: 0;
102
94
  --color: white;
103
95
  }
104
96
 
105
- .header-home__actions {
97
+ /* Scroll behavior for header-details title (hidden initially, shows on scroll) */
98
+ .header-details .header-title {
99
+ transform: translateX(-50%) translateY(-100%);
100
+ opacity: 0 !important;
101
+ pointer-events: none;
102
+ transition: transform 0.2s ease, opacity 0.2s ease !important;
103
+ }
104
+
105
+ /* Show title when scrolled */
106
+ .header-scrolled .header-details .header-title {
107
+ opacity: 1 !important;
108
+ pointer-events: auto;
109
+ transform: translateX(-50%) translateY(0);
110
+ }
111
+
112
+ /* Back button styles for detail pages */
113
+ :host .header-details .back-button,
114
+ .header-details .back-button {
115
+ background: none;
116
+ border: none;
117
+ padding: 0;
106
118
  display: flex;
107
119
  align-items: center;
108
- gap: 8px;
120
+ justify-content: center;
121
+ cursor: pointer;
122
+ color: white;
123
+ transition: opacity var(--transition-duration-fast, 0.2s) var(--ease-smooth, ease);
124
+ z-index: 10;
125
+ position: relative;
109
126
  }
110
127
 
111
- .logomark {
112
- height: 28px;
113
- width: auto;
114
- flex-shrink: 0;
128
+ :host .header-details .back-button:hover,
129
+ .header-details .back-button:hover {
130
+ opacity: 0.8;
115
131
  }
116
132
 
117
- /* Condensed header - hidden by default on mobile */
118
- ion-header[collapse="condense"] {
119
- display: none;
133
+ :host .header-details .back-button:active,
134
+ .header-details .back-button:active {
135
+ opacity: 0.6;
120
136
  }
121
137
 
122
- /* Show title in top header when scrolled past condensed header */
123
- .header-scrolled .header-home__title {
124
- opacity: 1;
125
- transform: translateX(-50%) translateY(0);
138
+ /* ============================================
139
+ SHARED ION-CONTENT STYLES
140
+ ============================================ */
141
+
142
+ :host ion-content {
143
+ --background: var(--color-brand-secondary);
144
+ --padding-top: 0;
145
+ --padding-start: 0;
146
+ --padding-end: 0;
147
+ --padding-bottom: 0;
148
+ border-radius: 24px 24px 0 0;
149
+ overflow: hidden;
150
+ }
151
+
152
+ /* Make ion-content scroll area a flex container */
153
+ :host ion-content::part(scroll) {
154
+ display: flex;
155
+ flex-direction: column;
156
+ -webkit-overflow-scrolling: touch;
157
+ /* Note: Do NOT set overscroll-behavior-y here as it prevents ion-refresher from working */
158
+ /* overscroll-behavior on ion-app is sufficient to block native browser pull-to-refresh */
159
+ }
160
+
161
+ /* iOS-specific: White background for bottom overshoot */
162
+ .plt-ios :host ion-content {
163
+ --background: var(--color-background-neutral-primary);
126
164
  }
127
165
 
166
+ /* Desktop only: remove bottom radius */
128
167
  @media (min-width: 768px) {
129
- .header-home {
130
- padding: 16px 24px;
131
- }
132
-
133
- .logomark {
134
- height: 32px;
168
+ :host ion-content {
169
+ border-radius: 24px 24px 0 0;
135
170
  }
171
+ }
136
172
 
137
- /* Hide title on desktop - not needed */
138
- .header-home__title {
139
- display: none;
140
- }
173
+ /* ============================================
174
+ CONDENSED HEADER
175
+ ============================================ */
141
176
 
142
- /* Hide condensed header on desktop */
143
- ion-header[collapse="condense"] {
177
+ :host ion-header[collapse="condense"] {
178
+ display: none;
179
+ }
180
+
181
+ @media (min-width: 768px) {
182
+ :host ion-header[collapse="condense"] {
144
183
  display: none;
145
184
  }
146
185
  }
147
186
 
148
187
  /* ============================================
149
- EXPANDABLE HEADER
188
+ REFRESHER
150
189
  ============================================ */
151
190
 
152
- .header-expandable {
153
- background: var(--color-brand-secondary);
154
- padding: 24px 16px;
155
- color: var(--header-content-color, white);
156
- position: sticky;
157
- top: 0;
158
- z-index: 10;
191
+ :host ion-refresher {
192
+ z-index: 0;
159
193
  }
160
194
 
161
- .header-expandable-inner {
195
+ :host ion-refresher-content {
196
+ --color: white;
197
+ }
198
+
199
+ /* ============================================
200
+ SHARED CONTENT WRAPPER STYLES
201
+ ============================================ */
202
+
203
+ :host .content-wrapper {
204
+ width: 100%;
205
+ position: relative;
206
+ z-index: 20;
207
+ /* Flex child that grows to fill available space */
208
+ flex: 1;
162
209
  display: flex;
163
210
  flex-direction: column;
164
- gap: 16px;
211
+ background: var(--color-background-neutral-primary);
212
+ border-radius: 24px 24px 0 0;
213
+
214
+ /* Fixed 20px horizontal padding globally */
215
+ padding: 20px;
216
+ /* Add bottom padding on mobile to account for fixed tab bar */
217
+ padding-bottom: calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom, 0px));
218
+ }
219
+
220
+ :host .content-inner {
221
+ /* Visual styling - background, border-radius, and shadow extend */
222
+ transform: translateZ(0);
223
+ will-change: transform;
224
+ isolation: isolate;
225
+ /* Extend white background below for iOS overshoot using box-shadow */
226
+ box-shadow: 0 200vh 0 0 var(--color-background-neutral-primary);
227
+
228
+ /* Grow to fill parent flex container */
229
+ flex: 1;
230
+
231
+ /* Mobile-first: max-width 640px, centered */
165
232
  max-width: 640px;
166
233
  margin: 0 auto;
234
+ width: 100%;
167
235
  }
168
236
 
169
237
  @media (min-width: 768px) {
170
- .header-expandable {
171
- padding: 32px var(--content-padding-md);
238
+ :host .content-wrapper {
239
+ padding: 32px 20px;
172
240
  }
173
241
  }
174
242
 
175
- @media (min-width: 992px) {
176
- .header-expandable {
177
- padding-left: var(--content-padding-lg);
178
- padding-right: var(--content-padding-lg);
179
- }
180
- }
243
+ /* ============================================
244
+ EXPANDABLE HEADER (for main pages only)
245
+ ============================================ */
181
246
 
182
- @media (min-width: 1440px) {
183
- .header-expandable {
184
- padding-left: var(--content-padding-xl);
185
- padding-right: var(--content-padding-xl);
186
- }
247
+ :host .header-expandable {
248
+ background: var(--color-brand-secondary);
249
+ padding: 32px 20px 24px 20px;
250
+ color: var(--header-content-color, white);
251
+ position: sticky;
252
+ top: 0;
253
+ z-index: 5;
254
+ transition: opacity 0.1s ease-out, transform 0.1s ease-out;
187
255
  }
188
256
 
189
- @media (min-width: 1768px) {
190
- .header-expandable {
191
- padding-left: var(--content-padding-2xl);
192
- padding-right: var(--content-padding-2xl);
193
- }
257
+ :host .header-expandable-inner {
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: 20px;
261
+ max-width: 640px;
262
+ margin: 0 auto;
194
263
  }
195
264
 
196
- @media (min-width: 1920px) {
197
- .header-expandable {
198
- padding-left: var(--content-padding-3xl);
199
- padding-right: var(--content-padding-3xl);
200
- }
265
+ :host .header-expandable__text {
266
+ margin-bottom: 0;
201
267
  }
202
268
 
203
- .header-expandable__text {
204
- display: flex;
205
- flex-direction: column;
206
- gap: 4px;
269
+ :host .header-expandable__title {
270
+ font-size: var(--font-size-2xl);
271
+ font-weight: 600;
272
+ color: var(--header-content-color, white);
273
+ margin: 0;
207
274
  }
208
275
 
209
- .header-expandable__subtitle {
276
+ :host .header-expandable__subtitle {
210
277
  font-size: var(--font-size-sm);
211
278
  font-weight: 400;
212
279
  color: var(--header-content-color, white);
@@ -215,68 +282,43 @@ ion-header[collapse="condense"] {
215
282
  }
216
283
 
217
284
  @media (min-width: 768px) {
218
- .header-expandable__subtitle {
219
- font-size: var(--font-size-base);
285
+ :host .header-expandable {
286
+ padding: 48px 20px 32px 20px;
220
287
  }
221
- }
222
-
223
- /* ============================================
224
- CONTENT WRAPPER
225
- ============================================ */
226
-
227
- .content-wrapper {
228
- position: relative;
229
- z-index: 10;
230
- background: var(--color-background-neutral-primary);
231
- border-radius: 24px 24px 0 0;
232
- padding: 0;
233
- }
234
288
 
235
- @media (min-width: 768px) {
236
- .content-wrapper {
237
- border-radius: 16px 16px 0 0;
238
- width: 100%;
289
+ :host .header-expandable__title {
290
+ font-size: var(--font-size-3xl);
239
291
  }
240
- }
241
-
242
- .content-inner {
243
- padding: 20px 16px;
244
- }
245
292
 
246
- @media (min-width: 768px) {
247
- .content-inner {
248
- padding: 32px var(--content-padding-md);
249
- max-width: calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));
250
- margin: 0 auto;
251
- width: 100%;
293
+ :host .header-expandable__subtitle {
294
+ font-size: var(--font-size-base);
252
295
  }
253
296
  }
254
297
 
255
298
  @media (min-width: 992px) {
256
- .content-inner {
257
- padding: 32px var(--content-padding-lg);
258
- max-width: calc(var(--content-max-width-md) + (var(--content-padding-md) * 2));
299
+ :host .header-expandable {
300
+ padding-left: var(--content-padding-lg);
301
+ padding-right: var(--content-padding-lg);
259
302
  }
260
303
  }
261
304
 
262
305
  @media (min-width: 1440px) {
263
- .content-inner {
264
- padding: 32px var(--content-padding-lg);
265
- max-width: calc(var(--content-max-width-lg) + (var(--content-padding-lg) * 2));
306
+ :host .header-expandable {
307
+ padding-left: var(--content-padding-xl);
308
+ padding-right: var(--content-padding-xl);
266
309
  }
267
310
  }
268
311
 
269
312
  @media (min-width: 1768px) {
270
- .content-inner {
271
- /* Keep xl max-width, only increase padding */
272
- padding: 32px var(--content-padding-2xl);
313
+ :host .header-expandable {
314
+ padding-left: var(--content-padding-2xl);
315
+ padding-right: var(--content-padding-2xl);
273
316
  }
274
317
  }
275
318
 
276
319
  @media (min-width: 1920px) {
277
- .content-inner {
278
- /* Keep xl max-width, only increase padding */
279
- padding: 32px var(--content-padding-3xl);
320
+ :host .header-expandable {
321
+ padding-left: var(--content-padding-3xl);
322
+ padding-right: var(--content-padding-3xl);
280
323
  }
281
324
  }
282
-