@propbinder/mobile-design 0.2.48 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26168
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8169
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,8 @@
1
+ export {
2
+ DsMobileChatModalComponent,
3
+ type ChatModalData,
4
+ type ChatMessage,
5
+ type ChatParticipant
6
+ } from './ds-mobile-chat-modal';
7
+ export { DsMobileChatModalService } from './ds-mobile-chat-modal.service';
8
+
@@ -0,0 +1,568 @@
1
+ import { Component, input, output, signal, computed, inject, PLATFORM_ID, effect } from '@angular/core';
2
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
3
+ import { DsAvatarComponent } from '@propbinder/design-system';
4
+ import { DsIconComponent } from '@propbinder/design-system';
5
+ import { DsIconButtonComponent } from '@propbinder/design-system';
6
+ import { Haptics, ImpactStyle } from '@capacitor/haptics';
7
+
8
+ /**
9
+ * DsMobileCommentComponent
10
+ *
11
+ * Individual comment component for post discussions.
12
+ * Displays user comments with avatar, content, and like action.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <ds-mobile-comment
17
+ * [authorName]="'John Doe'"
18
+ * [authorRole]="'Tenant'"
19
+ * [timestamp]="'1h ago'"
20
+ * [avatarInitials]="'JD'"
21
+ * [content]="'Great post!'">
22
+ * </ds-mobile-comment>
23
+ * ```
24
+ */
25
+ @Component({
26
+ selector: 'ds-mobile-comment',
27
+ standalone: true,
28
+ imports: [CommonModule, DsAvatarComponent, DsIconComponent, DsIconButtonComponent],
29
+ styleUrls: ['../shared/mobile-common.css'],
30
+ host: {
31
+ '[class.clickable]': 'clickable()',
32
+ '(click)': 'handleCommentClick($event)',
33
+ '(touchstart)': 'handleTouchStart($event)',
34
+ '(touchend)': 'handleTouchEnd($event)',
35
+ '(touchmove)': 'handleTouchMove($event)',
36
+ '(contextmenu)': 'handleContextMenu($event)',
37
+ },
38
+ styles: [
39
+ `
40
+ :host {
41
+ display: flex;
42
+ gap: 12px;
43
+ padding: 8px;
44
+ position: relative;
45
+ border-radius: 16px;
46
+ transition: all 0.2s ease;
47
+ background: var(--color-background-primary, #ffffff);
48
+ margin-bottom: 8px;
49
+ margin-left: -8px;
50
+ margin-right: -8px;
51
+ }
52
+
53
+ :host:last-child {
54
+ margin-bottom: 0;
55
+ }
56
+
57
+ :host::after {
58
+ content: '';
59
+ position: absolute;
60
+ bottom: -4px;
61
+ /* Align with comment content: padding (8px) + avatar (32px) + gap (12px) */
62
+ left: 44px;
63
+ /* Align with comment content right edge: padding (8px) from right */
64
+ right: 8px;
65
+ height: 1px;
66
+ background: var(--border-color-default);
67
+ }
68
+
69
+ :host:last-child::after {
70
+ display: none;
71
+ }
72
+
73
+ :host.clickable {
74
+ cursor: pointer;
75
+ }
76
+
77
+ :host.clickable:active {
78
+ background: var(--color-background-neutral-primary-hover, #f5f5f5);
79
+ }
80
+
81
+ .avatar-wrapper {
82
+ position: relative;
83
+ display: flex;
84
+ align-items: flex-start;
85
+ justify-content: center;
86
+ flex-shrink: 0;
87
+ }
88
+
89
+ .comment-content {
90
+ flex: 1;
91
+ min-width: 0;
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 4px;
95
+ }
96
+
97
+ .comment-header {
98
+ display: flex;
99
+ align-items: baseline;
100
+ gap: 6px;
101
+ flex-wrap: wrap;
102
+ }
103
+
104
+ /* Wrapper for like and more actions */
105
+ .header-actions {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 4px;
109
+ margin-left: auto;
110
+ }
111
+
112
+ /* Desktop more actions button - using ds-icon-button */
113
+ .desktop-more-button::ng-deep button {
114
+ border-radius: 50% !important;
115
+ }
116
+
117
+ /* Author styles imported from mobile-common.css */
118
+
119
+ .action-like {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 2px;
123
+ color: var(--color-text-secondary, #737373);
124
+ cursor: pointer;
125
+ transition: color 0.2s ease;
126
+ user-select: none;
127
+ -webkit-tap-highlight-color: transparent;
128
+ font-family: 'Brockmann', sans-serif;
129
+ font-size: var(--font-size-xs);
130
+ font-weight: 500;
131
+ line-height: 18px;
132
+ }
133
+
134
+ .like-count {
135
+ opacity: 1;
136
+ }
137
+
138
+ .like-count.hidden {
139
+ opacity: 0;
140
+ }
141
+
142
+ .action-like.active {
143
+ color: #f91880;
144
+ }
145
+
146
+ .icon-wrapper {
147
+ position: relative;
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ }
152
+
153
+ .icon-pulse {
154
+ position: absolute;
155
+ top: 50%;
156
+ left: 50%;
157
+ transform: translate(-50%, -50%);
158
+ opacity: 0;
159
+ pointer-events: none;
160
+ }
161
+
162
+ .icon-pulse.animating {
163
+ animation: pulse 0.4s cubic-bezier(0.4, 0, 0.2, 1);
164
+ }
165
+
166
+ @keyframes pulse {
167
+ 0% {
168
+ transform: translate(-50%, -50%) scale(1);
169
+ opacity: 0.8;
170
+ }
171
+ 100% {
172
+ transform: translate(-50%, -50%) scale(2.5);
173
+ opacity: 0;
174
+ }
175
+ }
176
+
177
+ .comment-text {
178
+ font-family: 'Brockmann', sans-serif;
179
+ font-size: var(--font-size-sm);
180
+ font-weight: 400;
181
+ line-height: 22px;
182
+ letter-spacing: -0.3px;
183
+ color: var(--color-text-primary, #1a1a1a);
184
+ white-space: pre-wrap;
185
+ word-wrap: break-word;
186
+ }
187
+
188
+ .comment-text ::ng-deep .mention {
189
+ color: var(--color-accent, #6b5ff5) !important;
190
+ font-weight: 600;
191
+ }
192
+
193
+ .comment-actions {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 12px;
197
+ margin-top: 4px;
198
+ }
199
+
200
+ .action-reply {
201
+ font-family: 'Brockmann', sans-serif;
202
+ font-size: var(--font-size-sm);
203
+ font-weight: 500;
204
+ line-height: 18px;
205
+ color: var(--color-text-secondary, #737373);
206
+ cursor: pointer;
207
+ user-select: none;
208
+ -webkit-tap-highlight-color: transparent;
209
+ transition: color 0.2s ease;
210
+ }
211
+
212
+ .action-reply:hover {
213
+ color: var(--color-text-primary, #1a1a1a);
214
+ }
215
+
216
+ .action-edit {
217
+ font-family: 'Brockmann', sans-serif;
218
+ font-size: var(--font-size-sm);
219
+ font-weight: 500;
220
+ line-height: 18px;
221
+ color: var(--color-text-secondary, #737373);
222
+ cursor: pointer;
223
+ user-select: none;
224
+ -webkit-tap-highlight-color: transparent;
225
+ transition: color 0.2s ease;
226
+ }
227
+
228
+ .action-edit:hover {
229
+ color: var(--color-text-primary, #1a1a1a);
230
+ }
231
+ `,
232
+ ],
233
+ template: `
234
+ <div class="avatar-wrapper">
235
+ <ds-avatar [initials]="avatarInitials()" [type]="avatarType()" size="sm" />
236
+ </div>
237
+
238
+ <div class="comment-content">
239
+ <div class="comment-header">
240
+ <span class="author-name">{{ authorName() }}</span>
241
+ <span class="author-meta">{{ authorRole() }} · {{ timestamp() }}</span>
242
+
243
+ <!-- Wrapper for like and more actions -->
244
+ <div class="header-actions">
245
+ <div class="action-like" [class.active]="internalIsLiked()" (click)="toggleLike()">
246
+ <span class="like-count" [class.hidden]="internalLikeCount() === 0">{{ internalLikeCount() }}</span>
247
+ <div class="icon-wrapper">
248
+ <ds-icon class="icon-pulse" [class.animating]="isPulsing()" [name]="internalIsLiked() ? 'remixHeart3Fill' : 'remixHeart3Line'" size="16px" />
249
+ <ds-icon [name]="internalIsLiked() ? 'remixHeart3Fill' : 'remixHeart3Line'" size="16px" />
250
+ </div>
251
+ </div>
252
+
253
+ @if (shouldShowMoreButton()) {
254
+ <ds-icon-button class="desktop-more-button" icon="remixMoreFill" variant="secondary" size="sm" (clicked)="handleMoreButtonClick($event)" aria-label="More options">
255
+ </ds-icon-button>
256
+ }
257
+ </div>
258
+ </div>
259
+
260
+ <div class="comment-text" [innerHTML]="formattedContent()"></div>
261
+
262
+ <div class="comment-actions">
263
+ <div class="action-reply" (click)="handleReply()">{{ replyLabel() }}</div>
264
+ @if (isOwnComment()) {
265
+ <div class="action-edit" (click)="handleEdit()">{{ editLabel() }}</div>
266
+ }
267
+ </div>
268
+ </div>
269
+ `,
270
+ })
271
+ export class DsMobileCommentComponent {
272
+ private platformId = inject(PLATFORM_ID);
273
+
274
+ /**
275
+ * Detect if viewport is desktop size
276
+ * Use viewport width for breakpoint detection (show button on tablet and above)
277
+ */
278
+ isDesktop = signal<boolean>(false);
279
+
280
+ constructor() {
281
+ if (isPlatformBrowser(this.platformId)) {
282
+ // Show button on tablet breakpoint and above (768px+)
283
+ const isDesktopViewport = window.innerWidth >= 768;
284
+
285
+ console.log('[Comment] Desktop detection:', {
286
+ innerWidth: window.innerWidth,
287
+ isDesktopViewport
288
+ });
289
+
290
+ this.isDesktop.set(isDesktopViewport);
291
+
292
+ // Listen for window resize to update detection
293
+ window.addEventListener('resize', () => {
294
+ const newIsDesktop = window.innerWidth >= 768;
295
+ if (newIsDesktop !== this.isDesktop()) {
296
+ console.log('[Comment] Viewport changed, updating desktop detection:', newIsDesktop);
297
+ this.isDesktop.set(newIsDesktop);
298
+ }
299
+ });
300
+ }
301
+
302
+ // Sync input signals with internal signals
303
+ effect(() => {
304
+ this.internalIsLiked.set(this.isLiked());
305
+ });
306
+
307
+ effect(() => {
308
+ this.internalLikeCount.set(this.likeCount());
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Author's display name
314
+ */
315
+ authorName = input.required<string>();
316
+
317
+ /**
318
+ * Author's role (e.g., "Tenant", "Property Manager")
319
+ */
320
+ authorRole = input.required<string>();
321
+
322
+ /**
323
+ * Timestamp text (e.g., "1h ago", "2d ago")
324
+ */
325
+ timestamp = input.required<string>();
326
+
327
+ /**
328
+ * Comment content text
329
+ */
330
+ content = input.required<string>();
331
+
332
+ /**
333
+ * Avatar initials
334
+ */
335
+ avatarInitials = input<string>('');
336
+
337
+ /**
338
+ * Avatar type
339
+ */
340
+ avatarType = input<'initials' | 'photo' | 'icon'>('initials');
341
+
342
+ /**
343
+ * Whether the comment is clickable
344
+ */
345
+ clickable = input<boolean>(false);
346
+
347
+ /**
348
+ * Unified toggle for contextual actions (more button + long press).
349
+ * - `true` — more button visible on **all** breakpoints, long press enabled
350
+ * - `false` — more button hidden, long press suppressed
351
+ * - `undefined` (default) — falls back to desktop-only button
352
+ */
353
+ moreActions = input<boolean | undefined>(undefined);
354
+
355
+ shouldShowMoreButton = computed(() => {
356
+ const ma = this.moreActions();
357
+ if (ma !== undefined) return ma;
358
+ return this.isDesktop();
359
+ });
360
+
361
+ /**
362
+ * Label for the reply action button
363
+ */
364
+ replyLabel = input<string>('Svar');
365
+
366
+ /**
367
+ * Label for the edit action button
368
+ */
369
+ editLabel = input<string>('Rediger');
370
+
371
+ /**
372
+ * Whether this comment belongs to the current user
373
+ */
374
+ isOwnComment = input<boolean>(false);
375
+
376
+ /**
377
+ * Whether the comment is liked by current user
378
+ */
379
+ isLiked = input<boolean>(false);
380
+
381
+ /**
382
+ * Emits when like is toggled (after UI is opdateret)
383
+ */
384
+ likeToggled = output<{ active: boolean; count: number }>();
385
+
386
+ /**
387
+ * Number of likes
388
+ */
389
+ likeCount = input<number>(0);
390
+
391
+ /**
392
+ * Internal signal to track like state for UI updates
393
+ */
394
+ internalIsLiked = signal<boolean>(false);
395
+
396
+ /**
397
+ * Internal signal to track like count for UI updates
398
+ */
399
+ internalLikeCount = signal<number>(0);
400
+
401
+ /**
402
+ * Signal to control pulse animation
403
+ */
404
+ isPulsing = signal(false);
405
+
406
+ /**
407
+ * Computed property to format content with @mentions
408
+ */
409
+ formattedContent = computed(() => {
410
+ const text = this.content();
411
+ // Replace @mentions with styled spans
412
+ // Matches @FirstName or @FirstName LastName (max 2 words)
413
+ return text.replace(/@([A-Za-z]+(?:\s+[A-Za-z]+)?)\b/g, '<span class="mention">@$1</span>');
414
+ });
415
+
416
+ /**
417
+ * Emits when the comment card is clicked (if clickable)
418
+ */
419
+ commentClick = output<void>();
420
+
421
+ /**
422
+ * Emits when reply is clicked
423
+ */
424
+ replyClick = output<void>();
425
+
426
+ /**
427
+ * Emits when edit is clicked
428
+ */
429
+ editClick = output<void>();
430
+
431
+ /**
432
+ * Emits when the comment is long-pressed
433
+ */
434
+ longPress = output<void>();
435
+
436
+ /**
437
+ * Long press tracking
438
+ */
439
+ private longPressTimer: any = null;
440
+ private longPressTriggered = false;
441
+ private touchStartX = 0;
442
+ private touchStartY = 0;
443
+ private readonly LONG_PRESS_DURATION = 500; // ms
444
+ private readonly MOVE_THRESHOLD = 10; // px
445
+
446
+ handleCommentClick(event: Event): void {
447
+ // Only emit if clickable and not clicking on action buttons
448
+ if (this.clickable() && !(event.target as HTMLElement).closest('.comment-actions')) {
449
+ this.commentClick.emit();
450
+ }
451
+ }
452
+
453
+ async toggleLike(): Promise<void> {
454
+ const newLiked = !this.internalIsLiked();
455
+ this.internalIsLiked.set(newLiked);
456
+
457
+ const newCount = newLiked ? this.internalLikeCount() + 1 : this.internalLikeCount() - 1;
458
+ this.internalLikeCount.set(Math.max(0, newCount));
459
+
460
+ // Emit the like toggled event
461
+ this.likeToggled.emit({ active: newLiked, count: Math.max(0, newCount) });
462
+
463
+ // Trigger pulse animation only when liking
464
+ if (newLiked) {
465
+ this.isPulsing.set(true);
466
+ setTimeout(() => this.isPulsing.set(false), 400);
467
+ }
468
+
469
+ // Haptic feedback for like/unlike
470
+ try {
471
+ await Haptics.impact({ style: ImpactStyle.Light });
472
+ } catch {
473
+ // Fallback to Web Vibration API if Capacitor Haptics is not available
474
+ if ('vibrate' in navigator) {
475
+ navigator.vibrate(50);
476
+ }
477
+ }
478
+ }
479
+
480
+ handleReply(): void {
481
+ this.replyClick.emit();
482
+ }
483
+
484
+ handleEdit(): void {
485
+ this.editClick.emit();
486
+ }
487
+
488
+ /**
489
+ * Handle touch start for long press detection
490
+ */
491
+ handleTouchStart(event: TouchEvent): void {
492
+ if (this.moreActions() === false) return;
493
+ this.longPressTriggered = false;
494
+ this.touchStartX = event.touches[0].clientX;
495
+ this.touchStartY = event.touches[0].clientY;
496
+
497
+ // Start long press timer
498
+ this.longPressTimer = setTimeout(async () => {
499
+ this.longPressTriggered = true;
500
+ this.longPress.emit();
501
+
502
+ // Haptic feedback for long press
503
+ try {
504
+ await Haptics.impact({ style: ImpactStyle.Medium });
505
+ } catch {
506
+ // Fallback to Web Vibration API if Capacitor Haptics is not available
507
+ if ('vibrate' in navigator) {
508
+ navigator.vibrate(50);
509
+ }
510
+ }
511
+ }, this.LONG_PRESS_DURATION);
512
+ }
513
+
514
+ /**
515
+ * Handle touch end to clear long press timer
516
+ */
517
+ handleTouchEnd(event: TouchEvent): void {
518
+ if (this.longPressTimer) {
519
+ clearTimeout(this.longPressTimer);
520
+ this.longPressTimer = null;
521
+ }
522
+
523
+ // Prevent normal click if long press was triggered
524
+ if (this.longPressTriggered) {
525
+ event.preventDefault();
526
+ event.stopPropagation();
527
+ this.longPressTriggered = false;
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Handle touch move to cancel long press if moved too much
533
+ */
534
+ handleTouchMove(event: TouchEvent): void {
535
+ if (!this.longPressTimer) return;
536
+
537
+ const touch = event.touches[0];
538
+ const deltaX = Math.abs(touch.clientX - this.touchStartX);
539
+ const deltaY = Math.abs(touch.clientY - this.touchStartY);
540
+
541
+ // Cancel long press if moved too far
542
+ if (deltaX > this.MOVE_THRESHOLD || deltaY > this.MOVE_THRESHOLD) {
543
+ clearTimeout(this.longPressTimer);
544
+ this.longPressTimer = null;
545
+ this.longPressTriggered = false;
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Handle context menu (right-click on desktop) to trigger long press action
551
+ */
552
+ handleContextMenu(event: Event): void {
553
+ if (this.moreActions() === false) return;
554
+ event.preventDefault();
555
+ this.longPress.emit();
556
+ }
557
+
558
+ /**
559
+ * Handle desktop more button click
560
+ * Stops propagation and triggers long press action
561
+ */
562
+ handleMoreButtonClick(event: Event): void {
563
+ console.log('[Comment] Desktop more button clicked');
564
+ event.stopPropagation();
565
+ event.preventDefault();
566
+ this.longPress.emit();
567
+ }
568
+ }
@@ -0,0 +1,2 @@
1
+ export { DsMobileCommentComponent } from './ds-mobile-comment';
2
+