@propbinder/mobile-design 0.2.50 → 0.2.53

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.
Files changed (221) hide show
  1. package/fesm2022/propbinder-mobile-design.mjs +26206 -0
  2. package/fesm2022/propbinder-mobile-design.mjs.map +1 -0
  3. package/index.d.ts +8193 -0
  4. package/package.json +39 -3
  5. package/ng-package.json +0 -24
  6. package/src/animations/page-transitions.ts +0 -165
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +0 -102
  8. package/src/components/action-list-item/index.ts +0 -2
  9. package/src/components/app-icon/ds-app-icon.ts +0 -133
  10. package/src/components/app-icon/index.ts +0 -2
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +0 -139
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +0 -164
  13. package/src/components/attachment-preview/index.ts +0 -1
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +0 -142
  15. package/src/components/avatar-with-badge/index.ts +0 -2
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +0 -71
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +0 -121
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +0 -598
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +0 -161
  20. package/src/components/booking-modal/index.ts +0 -4
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +0 -266
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +0 -146
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +0 -156
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +0 -101
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +0 -169
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +0 -211
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +0 -578
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +0 -614
  29. package/src/components/bottom-sheet/index.ts +0 -8
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +0 -42
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +0 -301
  32. package/src/components/card-inline/index.ts +0 -2
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +0 -118
  34. package/src/components/card-inline-banner/index.ts +0 -1
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +0 -120
  36. package/src/components/card-inline-contact/index.ts +0 -1
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +0 -141
  38. package/src/components/card-inline-file/index.ts +0 -1
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +0 -159
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +0 -105
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +0 -918
  42. package/src/components/chat-modal/index.ts +0 -8
  43. package/src/components/comment/ds-mobile-comment.ts +0 -568
  44. package/src/components/comment/index.ts +0 -2
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +0 -182
  46. package/src/components/contact-list-item/index.ts +0 -2
  47. package/src/components/content/ds-mobile-content.ts +0 -139
  48. package/src/components/content/index.ts +0 -2
  49. package/src/components/dropdown/ds-mobile-dropdown.css +0 -199
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +0 -340
  51. package/src/components/dropdown/index.ts +0 -2
  52. package/src/components/ds-mobile-tabs.css +0 -407
  53. package/src/components/ds-mobile-tabs.ts +0 -216
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +0 -120
  55. package/src/components/empty-state/index.ts +0 -2
  56. package/src/components/fab/ds-mobile-fab.ts +0 -315
  57. package/src/components/fab/index.ts +0 -1
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +0 -121
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +0 -189
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +0 -135
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +0 -656
  62. package/src/components/facility-creation-modal/index.ts +0 -9
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +0 -105
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +0 -188
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +0 -460
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +0 -134
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +0 -69
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +0 -379
  69. package/src/components/facility-detail-modal/index.ts +0 -2
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +0 -164
  71. package/src/components/file-attachment/index.ts +0 -2
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +0 -214
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +0 -84
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +0 -424
  75. package/src/components/handbook-detail-modal/index.ts +0 -3
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +0 -175
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +0 -533
  78. package/src/components/handbook-folder/index.ts +0 -4
  79. package/src/components/header-content/ds-mobile-header-content.ts +0 -222
  80. package/src/components/header-content/index.ts +0 -2
  81. package/src/components/illustration/ds-mobile-illustration.ts +0 -124
  82. package/src/components/illustration/index.ts +0 -2
  83. package/src/components/index.ts +0 -124
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +0 -361
  85. package/src/components/inline-photo/index.ts +0 -1
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +0 -132
  87. package/src/components/inline-tabs/index.ts +0 -2
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +0 -350
  89. package/src/components/interactive-list-item-booking/index.ts +0 -1
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +0 -321
  91. package/src/components/interactive-list-item-inquiry/index.ts +0 -2
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +0 -237
  93. package/src/components/interactive-list-item-message/index.ts +0 -2
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +0 -549
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +0 -124
  96. package/src/components/interactive-list-item-post/index.ts +0 -13
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +0 -315
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +0 -202
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +0 -484
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +0 -377
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +0 -374
  102. package/src/components/lightbox/ds-mobile-lightbox.css +0 -587
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +0 -296
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +0 -529
  105. package/src/components/lightbox/index.ts +0 -22
  106. package/src/components/list-item/ds-mobile-list-item.ts +0 -603
  107. package/src/components/list-item/index.ts +0 -2
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +0 -133
  109. package/src/components/list-item-static/index.ts +0 -2
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +0 -49
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +0 -77
  112. package/src/components/loader-overlay/index.ts +0 -1
  113. package/src/components/logo/ds-logo.ts +0 -95
  114. package/src/components/logo/index.ts +0 -2
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +0 -633
  116. package/src/components/message-bubble/index.ts +0 -7
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +0 -1146
  118. package/src/components/message-composer/index.ts +0 -7
  119. package/src/components/modal/ds-mobile-modal.css +0 -163
  120. package/src/components/modal/ds-mobile-modal.service.ts +0 -329
  121. package/src/components/modal/index.ts +0 -8
  122. package/src/components/modal-base/ds-mobile-modal-base.css +0 -378
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +0 -261
  124. package/src/components/modal-base/index.ts +0 -2
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +0 -112
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +0 -93
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +0 -442
  128. package/src/components/new-inquiry-modal/index.ts +0 -4
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +0 -135
  130. package/src/components/offline-banner/index.ts +0 -1
  131. package/src/components/page-details/ds-mobile-page-details.css +0 -83
  132. package/src/components/page-details/ds-mobile-page-details.ts +0 -282
  133. package/src/components/page-details/index.ts +0 -2
  134. package/src/components/page-main/ds-mobile-page-main.css +0 -68
  135. package/src/components/page-main/ds-mobile-page-main.ts +0 -421
  136. package/src/components/page-main/index.ts +0 -2
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +0 -140
  138. package/src/components/post-composer/index.ts +0 -2
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +0 -390
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +0 -108
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +0 -722
  142. package/src/components/post-detail-modal/index.ts +0 -9
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +0 -95
  144. package/src/components/property-banner/index.ts +0 -2
  145. package/src/components/section/ds-mobile-section.ts +0 -263
  146. package/src/components/section/index.ts +0 -2
  147. package/src/components/shared/directives/index.ts +0 -2
  148. package/src/components/shared/directives/long-press.directive.ts +0 -212
  149. package/src/components/shared/index.ts +0 -3
  150. package/src/components/shared/mobile-modal-base.ts +0 -457
  151. package/src/components/shared/mobile-page-base.ts +0 -204
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +0 -160
  153. package/src/components/swiper/ds-mobile-swiper.ts +0 -327
  154. package/src/components/swiper/index.ts +0 -3
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +0 -129
  156. package/src/components/system-message-banner/index.ts +0 -2
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +0 -533
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +0 -735
  159. package/src/components/tab-bar/index.ts +0 -2
  160. package/src/components/tabs/ds-mobile-tabs.css +0 -25
  161. package/src/components/tabs/ds-mobile-tabs.ts +0 -89
  162. package/src/components/tabs/index.ts +0 -2
  163. package/src/components/text-input/ds-text-input.ts +0 -287
  164. package/src/components/text-input/index.ts +0 -2
  165. package/src/examples/booking.page.ts +0 -434
  166. package/src/examples/community.page.ts +0 -776
  167. package/src/examples/handbook.page.ts +0 -324
  168. package/src/examples/home.page.ts +0 -347
  169. package/src/examples/index.ts +0 -12
  170. package/src/examples/inquiries.example.ts +0 -273
  171. package/src/examples/inquiry-detail.example.css +0 -189
  172. package/src/examples/inquiry-detail.example.ts +0 -415
  173. package/src/examples/mobile-tabs-example.component.ts +0 -208
  174. package/src/examples/post-create.page.ts +0 -311
  175. package/src/examples/post-detail.page.ts +0 -296
  176. package/src/examples/sign-in.page.ts +0 -291
  177. package/src/examples/whitelabel-demo-modal.component.ts +0 -1094
  178. package/src/examples/whitelabel-demo-modal.service.ts +0 -77
  179. package/src/models/index.ts +0 -7
  180. package/src/models/post.model.ts +0 -41
  181. package/src/pages/community.page.ts +0 -769
  182. package/src/pages/handbook.page.ts +0 -388
  183. package/src/pages/home.page.ts +0 -303
  184. package/src/pages/index.ts +0 -11
  185. package/src/pages/inquiries.example.ts +0 -273
  186. package/src/pages/inquiry-detail.example.css +0 -189
  187. package/src/pages/inquiry-detail.example.ts +0 -415
  188. package/src/pages/mobile-tabs-example.component.ts +0 -179
  189. package/src/pages/post-create.page.ts +0 -311
  190. package/src/pages/post-detail.page.ts +0 -296
  191. package/src/pages/sign-in.page.ts +0 -291
  192. package/src/pages/whitelabel-demo-modal.component.ts +0 -1094
  193. package/src/pages/whitelabel-demo-modal.service.ts +0 -77
  194. package/src/public-api.ts +0 -6
  195. package/src/services/base-modal.service.ts +0 -101
  196. package/src/services/index.ts +0 -11
  197. package/src/services/posts.service.ts +0 -542
  198. package/src/services/tracking-permission.service.ts +0 -88
  199. package/src/services/user.service.ts +0 -60
  200. package/src/services/whitelabel.service.ts +0 -675
  201. package/tsconfig.lib.json +0 -17
  202. package/tsconfig.lib.prod.json +0 -9
  203. package/tsconfig.spec.json +0 -13
  204. /package/{src/assets → assets}/fonts/Brockmann-Bold.otf +0 -0
  205. /package/{src/assets → assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  206. /package/{src/assets → assets}/fonts/Brockmann-Medium.otf +0 -0
  207. /package/{src/assets → assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  208. /package/{src/assets → assets}/fonts/Brockmann-Regular.otf +0 -0
  209. /package/{src/assets → assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  210. /package/{src/assets → assets}/fonts/Brockmann-SemiBold.otf +0 -0
  211. /package/{src/assets → assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  212. /package/{src/assets → assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  213. /package/{src/assets → assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  214. /package/{src/assets → assets}/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  215. /package/{src/assets → assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  216. /package/{src/assets → assets}/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  217. /package/{src/assets → assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  218. /package/{src/assets → assets}/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  219. /package/{src/styles → styles}/ionic.css +0 -0
  220. /package/{src/components/shared → styles}/mobile-common.css +0 -0
  221. /package/{src/components/shared → styles}/mobile-page-base.css +0 -0
@@ -1,598 +0,0 @@
1
- import { Component, Input, signal, computed, CUSTOM_ELEMENTS_SCHEMA, ViewChild, AfterViewInit } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
- import { ModalController } from '@ionic/angular/standalone';
4
- import { DsIconComponent, DsButtonComponent, DsDatepickerComponent } from '@propbinder/design-system';
5
- import { DsMobileModalBaseComponent } from '../modal-base/ds-mobile-modal-base';
6
- import { DsMobileSectionComponent } from '../section';
7
- import { DsMobileSwiperComponent } from '../swiper/ds-mobile-swiper';
8
-
9
- /**
10
- * Date option interface for date selection
11
- */
12
- export interface DateOption {
13
- id: string;
14
- dayName: string; // "Tue"
15
- date: string; // "19"
16
- monthName: string; // "Nov"
17
- fullDate: Date;
18
- state: 'default' | 'selected' | 'disabled';
19
- }
20
-
21
- /**
22
- * Time slot interface for time selection
23
- */
24
- export interface TimeSlot {
25
- id: string;
26
- startTime: string; // "14:00"
27
- endTime: string; // "16:00"
28
- state: 'default' | 'selected' | 'disabled';
29
- }
30
-
31
- /**
32
- * Booking result returned when user confirms booking
33
- */
34
- export interface BookingResult {
35
- facilityId: string;
36
- facilityTitle: string;
37
- selectedDate: DateOption;
38
- selectedTimeSlot: TimeSlot;
39
- timestamp: Date;
40
- }
41
-
42
- /**
43
- * DsMobileBookingModalComponent
44
- *
45
- * Full-screen modal for booking facilities with date and time selection.
46
- * Features swipeable date selection and vertical time slot list.
47
- *
48
- * @example
49
- * ```typescript
50
- * const modal = await modalController.create({
51
- * component: DsMobileBookingModalComponent,
52
- * componentProps: {
53
- * facilityId: 'facility-1',
54
- * facilityTitle: 'Boremaskinen'
55
- * }
56
- * });
57
- * await modal.present();
58
- * const result = await modal.onWillDismiss<BookingResult>();
59
- * ```
60
- */
61
- @Component({
62
- selector: 'ds-mobile-booking-modal',
63
- standalone: true,
64
- imports: [
65
- CommonModule,
66
- DsMobileModalBaseComponent,
67
- DsMobileSectionComponent,
68
- DsMobileSwiperComponent,
69
- DsIconComponent,
70
- DsButtonComponent,
71
- DsDatepickerComponent
72
- ],
73
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
74
- styles: [`
75
- /* Date item styles */
76
- .date-item {
77
- display: flex;
78
- flex-direction: column;
79
- align-items: center;
80
- justify-content: center;
81
- width: 80px;
82
- padding: 12px 8px;
83
- border-radius: 12px;
84
- border: 1px solid var(--color-border, #e5e5e5);
85
- background: var(--color-surface-primary, #ffffff);
86
- cursor: pointer;
87
- transition: all 200ms ease;
88
- -webkit-tap-highlight-color: transparent;
89
- gap: 4px;
90
- }
91
-
92
- .date-item .day-name {
93
- font-family: 'Brockmann', sans-serif;
94
- font-size: var(--font-size-xs, 12px);
95
- font-weight: 400;
96
- color: var(--text-color-default-secondary, #71727a);
97
- text-transform: uppercase;
98
- }
99
-
100
- .date-item .date-number {
101
- font-family: 'Brockmann', sans-serif;
102
- font-size: var(--font-size-xl, 20px);
103
- font-weight: 600;
104
- color: var(--text-color-default-primary, #202227);
105
- }
106
-
107
- .date-item .month-name {
108
- font-family: 'Brockmann', sans-serif;
109
- font-size: var(--font-size-xs, 12px);
110
- font-weight: 400;
111
- color: var(--text-color-default-secondary, #71727a);
112
- text-transform: capitalize;
113
- }
114
-
115
- /* Selected state */
116
- .date-item.selected {
117
- background: var(--color-accent, #5d5fef);
118
- border-color: var(--color-accent, #5d5fef);
119
- }
120
-
121
- .date-item.selected .day-name,
122
- .date-item.selected .date-number,
123
- .date-item.selected .month-name {
124
- color: #ffffff;
125
- }
126
-
127
- /* Disabled state */
128
- .date-item.disabled {
129
- background: var(--color-background-neutral-secondary, #f5f5f5);
130
- border-color: var(--color-border, #e5e5e5);
131
- cursor: not-allowed;
132
- opacity: 0.6;
133
- }
134
-
135
- .date-item.disabled .day-name,
136
- .date-item.disabled .date-number,
137
- .date-item.disabled .month-name {
138
- color: var(--text-color-default-tertiary, #9a9aa2);
139
- }
140
-
141
- /* Hover effect for non-disabled items */
142
- .date-item:not(.disabled):not(.selected):hover {
143
- border-color: var(--color-accent, #5d5fef);
144
- background: var(--color-background-neutral-secondary, #f5f5f5);
145
- }
146
-
147
- /* Time slots list */
148
- .time-slots-list {
149
- display: flex;
150
- flex-direction: column;
151
- gap: 8px;
152
- }
153
-
154
- .time-slot-item {
155
- display: flex;
156
- align-items: center;
157
- justify-content: space-between;
158
- width: 100%;
159
- padding: 16px 20px;
160
- border-radius: 12px;
161
- border: 1px solid var(--color-border, #e5e5e5);
162
- background: var(--color-surface-primary, #ffffff);
163
- cursor: pointer;
164
- transition: all 200ms ease;
165
- -webkit-tap-highlight-color: transparent;
166
- }
167
-
168
- .time-slot-item span {
169
- font-family: 'Brockmann', sans-serif;
170
- font-size: var(--font-size-base, 16px);
171
- font-weight: 400;
172
- color: var(--text-color-default-primary, #202227);
173
- }
174
-
175
- /* Selected state */
176
- .time-slot-item.selected {
177
- background: var(--color-accent, #5d5fef);
178
- border-color: var(--color-accent, #5d5fef);
179
- }
180
-
181
- .time-slot-item.selected span {
182
- color: #ffffff;
183
- font-weight: 500;
184
- }
185
-
186
- /* Disabled state */
187
- .time-slot-item.disabled {
188
- background: var(--color-background-neutral-secondary, #f5f5f5);
189
- border-color: var(--color-border, #e5e5e5);
190
- cursor: not-allowed;
191
- opacity: 0.6;
192
- }
193
-
194
- .time-slot-item.disabled span {
195
- color: var(--text-color-default-tertiary, #9a9aa2);
196
- }
197
-
198
- /* Hover effect for non-disabled items */
199
- .time-slot-item:not(.disabled):not(.selected):hover {
200
- border-color: var(--color-accent, #5d5fef);
201
- background: var(--color-background-neutral-secondary, #f5f5f5);
202
- }
203
-
204
- /* Fixed bottom button */
205
- .booking-confirm-action {
206
- width: 100%;
207
- padding: 16px 20px;
208
- background: var(--color-surface-primary, #ffffff);
209
- border-top: 1px solid var(--color-border, #e5e5e5);
210
- box-sizing: border-box;
211
- }
212
-
213
- .booking-confirm-action ::ng-deep ds-button {
214
- display: block;
215
- width: 100%;
216
- }
217
-
218
- .booking-confirm-action ::ng-deep ds-button button {
219
- width: 100%;
220
- border-radius: 100px;
221
- height: 48px;
222
- }
223
-
224
- /* Swiper slide adjustments */
225
- ::ng-deep .date-swiper .swiper-slide {
226
- width: auto !important;
227
- }
228
-
229
- /* Styled to match ds-mobile-section's .section-link */
230
- .calendar-link {
231
- display: flex;
232
- align-items: center;
233
- gap: 2px;
234
- font-family: 'Brockmann', sans-serif;
235
- font-size: var(--font-size-sm);
236
- font-weight: 500;
237
- color: var(--color-accent, #5d5fef);
238
- cursor: pointer;
239
- white-space: nowrap;
240
- user-select: none;
241
- line-height: 1;
242
- }
243
-
244
- /* Ensure the CDK overlay renders above the Ionic modal stack */
245
- ::ng-deep .cdk-overlay-container {
246
- z-index: 20001;
247
- }
248
- `],
249
- template: `
250
- <ds-mobile-modal-base
251
- headerTitle="Hvornår skal det være?"
252
- [showCloseButton]="true"
253
- [hasFixedBottom]="true"
254
- (closeClicked)="handleClose()">
255
-
256
- <!-- Date Section -->
257
- <ds-mobile-section headline="Vælg dato">
258
- <ds-datepicker
259
- header-action
260
- [isDateDisabled]="dateDisabledFn()"
261
- (dateChange)="jumpToDate($event)">
262
- <span class="calendar-link">
263
- {{ selectFromCalendarText }}
264
- <ds-icon name="remixArrowRightSLine" size="16px" />
265
- </span>
266
- </ds-datepicker>
267
-
268
- <ds-mobile-swiper
269
- class="date-swiper"
270
- [slideWidth]="'auto'"
271
- [gap]="12">
272
- @for (date of dateOptions(); track date.id) {
273
- <div class="swiper-slide">
274
- <button
275
- class="date-item"
276
- [class.selected]="date.state === 'selected'"
277
- [class.disabled]="date.state === 'disabled'"
278
- [disabled]="date.state === 'disabled'"
279
- (click)="selectDate(date)">
280
- <span class="day-name">{{ date.dayName }}</span>
281
- <span class="date-number">{{ date.date }}</span>
282
- <span class="month-name">{{ date.monthName }}</span>
283
- </button>
284
- </div>
285
- }
286
- </ds-mobile-swiper>
287
- </ds-mobile-section>
288
-
289
- <!-- Time Section -->
290
- <ds-mobile-section headline="Vælg tidspunkt">
291
- <div class="time-slots-list">
292
- @for (slot of timeSlots(); track slot.id) {
293
- <button
294
- class="time-slot-item"
295
- [class.selected]="slot.state === 'selected'"
296
- [class.disabled]="slot.state === 'disabled'"
297
- [disabled]="slot.state === 'disabled'"
298
- (click)="selectTime(slot)">
299
- <span>{{ slot.startTime }} - {{ slot.endTime }}</span>
300
- @if (slot.state === 'selected') {
301
- <ds-icon name="remixCheckLine" size="20px" color="#ffffff" />
302
- }
303
- </button>
304
- }
305
- </div>
306
- </ds-mobile-section>
307
-
308
- <!-- Fixed Bottom Button -->
309
- <div fixed-bottom class="booking-confirm-action">
310
- <ds-button
311
- size="lg"
312
- variant="primary"
313
- [fullWidth]="true"
314
- [disabled]="!canConfirm()"
315
- [loading]="isConfirming()"
316
- (clicked)="handleConfirm()">
317
- {{ confirmBookingText }}
318
- </ds-button>
319
- </div>
320
- </ds-mobile-modal-base>
321
- `
322
- })
323
- export class DsMobileBookingModalComponent implements AfterViewInit {
324
- @Input() facilityId!: string;
325
- @Input() facilityTitle!: string;
326
- /**
327
- * Number of days ahead available for booking selection.
328
- * Defaults to 60 (2 months). Override via componentProps when opening the modal.
329
- */
330
- @Input() daysAhead: number = 60;
331
-
332
- @Input() selectFromCalendarText: string = 'Vælg fra kalender';
333
- @Input() confirmBookingText: string = 'Bekræft booking';
334
- @Input() availableDates?: DateOption[];
335
- @Input() availableTimeSlots?: Record<string, TimeSlot[]>;
336
-
337
- @ViewChild(DsMobileSwiperComponent) swiperComponent?: DsMobileSwiperComponent;
338
-
339
- // Signals for reactive state management
340
- dateOptions = signal<DateOption[]>([]);
341
- timeSlots = signal<TimeSlot[]>([]);
342
-
343
- selectedDate = signal<DateOption | null>(null);
344
- selectedTimeSlot = signal<TimeSlot | null>(null);
345
- isConfirming = signal<boolean>(false);
346
-
347
- // Computed property for button state
348
- canConfirm = computed(() => {
349
- return this.selectedDate() !== null && this.selectedTimeSlot() !== null && !this.isConfirming();
350
- });
351
-
352
- constructor(private modalController: ModalController) {
353
- this.generateMockData();
354
- }
355
-
356
- /**
357
- * After view init - force swiper update to fix initial positioning
358
- */
359
- ngAfterViewInit(): void {
360
- // Use a slightly longer timeout to ensure swiper is fully initialized
361
- setTimeout(() => {
362
- if (this.swiperComponent && (this.swiperComponent as any).swiperInstance) {
363
- const swiperInstance = (this.swiperComponent as any).swiperInstance;
364
- // Update swiper to recalculate positions
365
- swiperInstance.update();
366
- // Slide to first slide to ensure proper centering
367
- swiperInstance.slideTo(0, 0);
368
- }
369
- }, 150);
370
- }
371
-
372
- /**
373
- * Returns true if the given date should be disabled in both the swiper and the datepicker.
374
- * Weekends are disabled. Index-based mock disabling (i===3, i===7) is swiper-only and
375
- * not representable as a date rule, so it is intentionally excluded here.
376
- */
377
- private isDateUnavailable(date: Date): boolean {
378
- return date.getDay() === 0 || date.getDay() === 6;
379
- }
380
-
381
- /**
382
- * Computed signal that returns a fresh disabled-date function whenever dateOptions()
383
- * changes. Returning a new function reference causes the datepicker's own isDateDisabled
384
- * input to update, which triggers its internal computed to re-run and re-render all
385
- * calendar cells with the correct disabled state.
386
- * The disabledSet is pre-built once per computation for O(1) per-cell lookups.
387
- */
388
- dateDisabledFn = computed(() => {
389
- const disabledSet = new Set(
390
- this.dateOptions()
391
- .filter(opt => opt.state === 'disabled')
392
- .map(opt => opt.fullDate.toDateString())
393
- );
394
-
395
- return (date: Date): boolean => {
396
- const today = new Date(); today.setHours(0, 0, 0, 0);
397
- const max = new Date(today); max.setDate(today.getDate() + this.daysAhead - 1);
398
- const d = new Date(date); d.setHours(0, 0, 0, 0);
399
- if (d < today || d > max) return true;
400
- return disabledSet.has(date.toDateString());
401
- };
402
- });
403
-
404
- /**
405
- * Generate mock date and time data or use provided available data
406
- */
407
- private generateMockData(): void {
408
- if (this.availableDates && this.availableDates.length > 0) {
409
- this.dateOptions.set(this.availableDates);
410
-
411
- const firstAvailableDate = this.availableDates.find(d => d.state !== 'disabled') || this.availableDates[0];
412
- if (firstAvailableDate) {
413
- // Ensure strictly one selected
414
- const updatedDates = this.availableDates.map(date => ({
415
- ...date,
416
- state: date.id === firstAvailableDate.id ? 'selected' as const :
417
- (date.state === 'disabled' ? 'disabled' as const : 'default' as const)
418
- }));
419
- this.dateOptions.set(updatedDates);
420
-
421
- const selectedRef = updatedDates.find(d => d.id === firstAvailableDate.id);
422
- if (selectedRef) {
423
- this.selectedDate.set(selectedRef);
424
- this.generateTimeSlots(selectedRef.fullDate, selectedRef.date);
425
- }
426
- }
427
- return;
428
- }
429
-
430
- const dates: DateOption[] = [];
431
- const today = new Date();
432
-
433
- const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
434
- const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
435
-
436
- let firstAvailableDate: DateOption | null = null;
437
-
438
- for (let i = 0; i < this.daysAhead; i++) {
439
- const date = new Date(today);
440
- date.setDate(today.getDate() + i);
441
-
442
- const isDisabled = this.isDateUnavailable(date) || (i === 3) || (i === 7);
443
-
444
- const dateOption: DateOption = {
445
- id: `date-${i}`,
446
- dayName: dayNames[date.getDay()],
447
- date: date.getDate().toString(),
448
- monthName: monthNames[date.getMonth()],
449
- fullDate: date,
450
- state: isDisabled ? 'disabled' : 'default'
451
- };
452
-
453
- // Track the first available (non-disabled) date
454
- if (!isDisabled && !firstAvailableDate) {
455
- firstAvailableDate = dateOption;
456
- dateOption.state = 'selected'; // Set as selected
457
- }
458
-
459
- dates.push(dateOption);
460
- }
461
-
462
- this.dateOptions.set(dates);
463
-
464
- // Set the first available date as selected and generate time slots for it
465
- if (firstAvailableDate) {
466
- this.selectedDate.set(firstAvailableDate);
467
- this.generateTimeSlots(firstAvailableDate.fullDate, firstAvailableDate.date);
468
- } else {
469
- // Fallback to today if no available dates
470
- this.generateTimeSlots(today);
471
- }
472
- }
473
-
474
- /**
475
- * Generate time slots based on selected date and dynamic available timeslots
476
- */
477
- private generateTimeSlots(date: Date, dateStringKey?: string): void {
478
- if (this.availableTimeSlots && dateStringKey && this.availableTimeSlots[dateStringKey]) {
479
- this.timeSlots.set(this.availableTimeSlots[dateStringKey]);
480
- return;
481
- }
482
-
483
- const slots: TimeSlot[] = [];
484
- const currentHour = new Date().getHours();
485
- const isToday = date.toDateString() === new Date().toDateString();
486
-
487
- // Generate time slots from 08:00 to 20:00 in 2-hour increments
488
- const startHour = 8;
489
- const endHour = 20;
490
-
491
- for (let hour = startHour; hour < endHour; hour += 2) {
492
- const startTime = `${hour.toString().padStart(2, '0')}:00`;
493
- const endTime = `${(hour + 2).toString().padStart(2, '0')}:00`;
494
-
495
- // Disable past times if today is selected
496
- const isPast = isToday && hour < currentHour;
497
- // Randomly disable some slots for demo purposes
498
- const isRandomlyDisabled = Math.random() > 0.7;
499
- const isDisabled = isPast || isRandomlyDisabled;
500
-
501
- slots.push({
502
- id: `slot-${hour}`,
503
- startTime,
504
- endTime,
505
- state: isDisabled ? 'disabled' : 'default'
506
- });
507
- }
508
-
509
- this.timeSlots.set(slots);
510
- }
511
-
512
- /**
513
- * Handle date selection
514
- */
515
- selectDate(selectedDate: DateOption): void {
516
- if (selectedDate.state === 'disabled') return;
517
-
518
- // Update date options
519
- const updatedDates = this.dateOptions().map(date => ({
520
- ...date,
521
- state: date.id === selectedDate.id ? 'selected' as const :
522
- (date.state === 'disabled' ? 'disabled' as const : 'default' as const)
523
- }));
524
-
525
- this.dateOptions.set(updatedDates);
526
- this.selectedDate.set(selectedDate);
527
-
528
- // Regenerate time slots for the selected date
529
- this.generateTimeSlots(selectedDate.fullDate, selectedDate.date);
530
-
531
- // Reset time selection
532
- this.selectedTimeSlot.set(null);
533
- }
534
-
535
- /**
536
- * Handle time slot selection
537
- */
538
- selectTime(selectedSlot: TimeSlot): void {
539
- if (selectedSlot.state === 'disabled') return;
540
-
541
- // Update time slots
542
- const updatedSlots = this.timeSlots().map(slot => ({
543
- ...slot,
544
- state: slot.id === selectedSlot.id ? 'selected' as const :
545
- (slot.state === 'disabled' ? 'disabled' as const : 'default' as const)
546
- }));
547
-
548
- this.timeSlots.set(updatedSlots);
549
- this.selectedTimeSlot.set(selectedSlot);
550
- }
551
-
552
- /**
553
- * Called when the datepicker overlay emits a date selection.
554
- * Finds the matching DateOption in the swiper, selects it, and auto-scrolls to it.
555
- */
556
- jumpToDate(date: Date | null): void {
557
- if (!date) return;
558
- const options = this.dateOptions();
559
- const idx = options.findIndex(
560
- opt => opt.fullDate.toDateString() === date.toDateString()
561
- );
562
- if (idx === -1) return;
563
- const option = options[idx];
564
- if (option.state === 'disabled') return;
565
- this.selectDate(option);
566
- this.swiperComponent?.slideTo(idx);
567
- }
568
-
569
- /**
570
- * Handle confirm button click
571
- */
572
- async handleConfirm(): Promise<void> {
573
- if (!this.canConfirm()) return;
574
-
575
- // Set loading state
576
- this.isConfirming.set(true);
577
-
578
- // Simulate booking API call with 2 second delay
579
- await new Promise(resolve => setTimeout(resolve, 2000));
580
-
581
- const result: BookingResult = {
582
- facilityId: this.facilityId,
583
- facilityTitle: this.facilityTitle,
584
- selectedDate: this.selectedDate()!,
585
- selectedTimeSlot: this.selectedTimeSlot()!,
586
- timestamp: new Date()
587
- };
588
-
589
- await this.modalController.dismiss(result, 'confirm');
590
- }
591
-
592
- /**
593
- * Handle close button click
594
- */
595
- async handleClose(): Promise<void> {
596
- await this.modalController.dismiss(null, 'cancel');
597
- }
598
- }