@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,633 @@
1
+ import { Component, input, output, computed } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { DsAvatarComponent } from '@propbinder/design-system';
4
+ import { DsIconComponent } from '@propbinder/design-system';
5
+
6
+ /**
7
+ * Chat attachment interface
8
+ */
9
+ export interface ChatAttachment {
10
+ id: string;
11
+ type: 'image' | 'pdf' | 'file';
12
+ url: string;
13
+ name: string;
14
+ size?: number;
15
+ thumbnail?: string;
16
+ }
17
+
18
+ /**
19
+ * DsMobileMessageBubbleComponent
20
+ *
21
+ * Individual message bubble component for chat conversations.
22
+ * Supports left-aligned (sender) and right-aligned (user) messages with different styling.
23
+ *
24
+ * Features:
25
+ * - Left/right alignment based on sender
26
+ * - Avatar display (left for sender, right for user)
27
+ * - Message content with text wrapping
28
+ * - Timestamp display
29
+ * - Read receipt indicator (for user's messages)
30
+ * - Attachment support
31
+ * - Long press support for actions
32
+ *
33
+ * @example
34
+ * ```html
35
+ * <!-- Sender's message (left-aligned) -->
36
+ * <ds-mobile-message-bubble
37
+ * [isOwnMessage]="false"
38
+ * [senderName]="'Ricki Meihlen'"
39
+ * [content]="'We have received your case...'"
40
+ * [timestamp]="'12:34'"
41
+ * [avatarInitials]="'RM'">
42
+ * </ds-mobile-message-bubble>
43
+ *
44
+ * <!-- User's message (right-aligned) -->
45
+ * <ds-mobile-message-bubble
46
+ * [isOwnMessage]="true"
47
+ * [senderName]="'You'"
48
+ * [content]="'Thank you!'"
49
+ * [timestamp]="'12:35'"
50
+ * [readStatus]="true"
51
+ * [avatarInitials]="'JD'">
52
+ * </ds-mobile-message-bubble>
53
+ * ```
54
+ */
55
+ @Component({
56
+ selector: 'ds-mobile-message-bubble',
57
+ standalone: true,
58
+ imports: [CommonModule, DsAvatarComponent, DsIconComponent],
59
+ host: {
60
+ '[class.is-own-message]': 'isOwnMessage()',
61
+ '[class.has-attachments]': 'attachments() && attachments()!.length > 0',
62
+ '[class.cluster-single]': 'clusterPosition() === "single"',
63
+ '[class.cluster-first]': 'clusterPosition() === "first"',
64
+ '[class.cluster-middle]': 'clusterPosition() === "middle"',
65
+ '[class.cluster-last]': 'clusterPosition() === "last"',
66
+ '[class.tapped]': 'isTapped',
67
+ '[class.is-new-message]': 'isNewMessage()',
68
+ '(touchstart)': 'handleTouchStart($event)',
69
+ '(touchend)': 'handleTouchEnd($event)',
70
+ '(touchmove)': 'handleTouchMove($event)',
71
+ '(contextmenu)': 'handleContextMenu($event)'
72
+ },
73
+ styles: [`
74
+ :host {
75
+ display: flex;
76
+ gap: 8px;
77
+ margin-bottom: 4px;
78
+ width: 100%;
79
+ align-items: flex-end;
80
+ box-sizing: border-box;
81
+ }
82
+
83
+ /* Adjust spacing for clustered messages */
84
+ :host.cluster-single {
85
+ margin-bottom: 12px; /* More space after standalone messages */
86
+ }
87
+
88
+ :host.cluster-first {
89
+ margin-bottom: 2px; /* Tight spacing in cluster */
90
+ }
91
+
92
+ :host.cluster-middle {
93
+ margin-bottom: 2px; /* Tight spacing in cluster */
94
+ }
95
+
96
+ :host.cluster-last {
97
+ margin-bottom: 12px; /* More space after cluster ends */
98
+ }
99
+
100
+ /* Left-aligned (sender's message) */
101
+ :host:not(.is-own-message) {
102
+ justify-content: flex-start !important;
103
+ flex-direction: row !important;
104
+ }
105
+
106
+ /* Right-aligned (user's message) - MUST be right-aligned */
107
+ :host.is-own-message {
108
+ justify-content: flex-end !important;
109
+ flex-direction: row-reverse !important;
110
+ margin-left: auto !important;
111
+ margin-right: 0 !important;
112
+ }
113
+
114
+ .avatar-wrapper {
115
+ flex-shrink: 0;
116
+ display: flex;
117
+ align-items: flex-end;
118
+ width: 32px; /* Reserve space for avatar even when hidden */
119
+ }
120
+
121
+ .avatar-wrapper.hidden {
122
+ visibility: hidden; /* Use visibility instead of display to maintain space */
123
+ }
124
+
125
+ .message-content-wrapper {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 0;
129
+ max-width: 75%;
130
+ min-width: 0;
131
+ }
132
+
133
+ /* Left-aligned message bubble */
134
+ :host:not(.is-own-message) .message-content-wrapper {
135
+ align-items: flex-start;
136
+ }
137
+
138
+ /* Right-aligned message bubble */
139
+ :host.is-own-message .message-content-wrapper {
140
+ align-items: flex-end !important;
141
+ margin-left: auto !important;
142
+ margin-right: 0 !important;
143
+ }
144
+
145
+ .message-bubble {
146
+ padding: 8px 12px;
147
+ border-radius: 20px;
148
+ position: relative;
149
+ word-wrap: break-word;
150
+ white-space: pre-wrap;
151
+ max-width: 100%;
152
+ display: flex;
153
+ flex-direction: column;
154
+ /* Tap animation with spring bounce, including border-radius */
155
+ transition:
156
+ transform var(--spring-bouncy),
157
+ border-radius var(--spring-bouncy);
158
+ will-change: transform, border-radius;
159
+ }
160
+
161
+ /* Scale down on tap */
162
+ :host.tapped .message-bubble {
163
+ transform: scale(0.96);
164
+ }
165
+
166
+ /* Cluster position border radius overrides */
167
+ /* Order: top-left, top-right, bottom-right, bottom-left */
168
+
169
+ /* Single message in cluster - 20px all around (default) */
170
+
171
+ /* Own messages (right side) - tight corners on the RIGHT */
172
+ /* First message in cluster - 20px 20px 4px 20px */
173
+ :host.is-own-message.cluster-first .message-bubble {
174
+ border-radius: 20px 20px 4px 20px;
175
+ }
176
+
177
+ /* Middle message in cluster - 20px 4px 4px 20px */
178
+ :host.is-own-message.cluster-middle .message-bubble {
179
+ border-radius: 20px 4px 4px 20px;
180
+ }
181
+
182
+ /* Last message in cluster - 20px 4px 20px 20px */
183
+ :host.is-own-message.cluster-last .message-bubble {
184
+ border-radius: 20px 4px 20px 20px;
185
+ }
186
+
187
+ /* Sender messages (left side, grey) - tight corners on the LEFT */
188
+ /* First message in cluster - 20px 20px 20px 4px */
189
+ :host:not(.is-own-message).cluster-first .message-bubble {
190
+ border-radius: 20px 20px 20px 4px;
191
+ }
192
+
193
+ /* Middle message in cluster - 4px 20px 20px 4px */
194
+ :host:not(.is-own-message).cluster-middle .message-bubble {
195
+ border-radius: 4px 20px 20px 4px;
196
+ }
197
+
198
+ /* Last message in cluster - 4px 20px 20px 20px */
199
+ :host:not(.is-own-message).cluster-last .message-bubble {
200
+ border-radius: 4px 20px 20px 20px;
201
+ }
202
+
203
+ /* Sender's message - neutral secondary background, no border */
204
+ :host:not(.is-own-message) .message-bubble {
205
+ background: var(--color-background-neutral-secondary, #f5f5f5);
206
+ }
207
+
208
+ /* User's message - brand primary surface background */
209
+ :host.is-own-message .message-bubble {
210
+ background: var(--color-accent, var(--color-accent, #6B5FF5));
211
+ color: var(--color-on-accent, #ffffff);
212
+ }
213
+
214
+ .message-text {
215
+ font-family: 'Brockmann', sans-serif;
216
+ font-size: var(--font-size-sm);
217
+ font-weight: 400;
218
+ line-height: 20px;
219
+ letter-spacing: -0.3px;
220
+ margin: 0;
221
+ }
222
+
223
+ :host:not(.is-own-message) .message-text {
224
+ color: var(--color-text-primary, #1a1a1a);
225
+ }
226
+
227
+ :host.is-own-message .message-text {
228
+ color: var(--color-on-accent, #ffffff);
229
+ }
230
+
231
+ /* Attachments container */
232
+ .attachments {
233
+ display: flex;
234
+ flex-wrap: wrap;
235
+ gap: 8px;
236
+ margin-top: 8px;
237
+ }
238
+
239
+ :host:not(.is-own-message) .attachments {
240
+ justify-content: flex-start;
241
+ }
242
+
243
+ :host.is-own-message .attachments {
244
+ justify-content: flex-end;
245
+ }
246
+
247
+ .attachment-item {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 8px;
251
+ padding: 8px 12px;
252
+ background: var(--color-background-neutral-secondary, #f5f5f5);
253
+ border-radius: 8px;
254
+ border: 1px solid var(--border-color-default, #e5e5e5);
255
+ cursor: pointer;
256
+ transition: background 0.2s ease;
257
+ }
258
+
259
+ :host.is-own-message .attachment-item {
260
+ background: rgba(255, 255, 255, 0.2);
261
+ border-color: rgba(255, 255, 255, 0.3);
262
+ }
263
+
264
+ .attachment-item:active {
265
+ background: var(--color-background-neutral-secondary-hover, #ebebeb);
266
+ }
267
+
268
+ :host.is-own-message .attachment-item:active {
269
+ background: rgba(255, 255, 255, 0.3);
270
+ }
271
+
272
+ .attachment-icon {
273
+ flex-shrink: 0;
274
+ width: 24px;
275
+ height: 24px;
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: center;
279
+ border-radius: 4px;
280
+ background: var(--color-background-neutral-primary, #ffffff);
281
+ }
282
+
283
+ :host.is-own-message .attachment-icon {
284
+ background: rgba(255, 255, 255, 0.3);
285
+ }
286
+
287
+ .attachment-name {
288
+ font-family: 'Brockmann', sans-serif;
289
+ font-size: var(--font-size-xs);
290
+ font-weight: 500;
291
+ line-height: 16px;
292
+ color: var(--color-text-primary, #1a1a1a);
293
+ max-width: 150px;
294
+ overflow: hidden;
295
+ text-overflow: ellipsis;
296
+ white-space: nowrap;
297
+ }
298
+
299
+ :host.is-own-message .attachment-name {
300
+ color: var(--color-text-on-brand, #ffffff);
301
+ }
302
+
303
+ /* Timestamp inside bubble */
304
+ .message-footer {
305
+ display: flex;
306
+ align-items: center;
307
+ gap: 4px;
308
+ align-self: flex-end; /* Always align to right within bubble */
309
+ height: 0;
310
+ margin-top: 0;
311
+ opacity: 0;
312
+ overflow: hidden;
313
+ transform: scale(0.95);
314
+ /* Spring bounce animation using design system variable */
315
+ transition:
316
+ height var(--spring-bouncy),
317
+ margin-top var(--spring-bouncy),
318
+ opacity var(--spring-bouncy),
319
+ transform var(--spring-bouncy);
320
+ }
321
+
322
+ /* Visible state - fade in and expand */
323
+ .message-footer.visible {
324
+ height: 14px;
325
+ margin-top: 4px;
326
+ opacity: 1;
327
+ transform: scale(1);
328
+ }
329
+
330
+ /* Timestamp always right-aligned for both message types */
331
+ :host:not(.is-own-message) .message-footer {
332
+ align-self: flex-end; /* Right-align timestamp for sender's messages */
333
+ }
334
+
335
+ .timestamp {
336
+ font-family: 'Brockmann', sans-serif;
337
+ font-size: 11px;
338
+ font-weight: 400;
339
+ line-height: 14px;
340
+ white-space: nowrap;
341
+ }
342
+
343
+ /* Timestamp color for sender's messages (grey background) */
344
+ :host:not(.is-own-message) .timestamp {
345
+ color: var(--color-text-tertiary, #a0a0a0);
346
+ }
347
+
348
+ /* Timestamp color for user's messages (brand background) */
349
+ :host.is-own-message .timestamp {
350
+ color: var(--color-on-accent, #ffffff);
351
+ opacity: 0.7;
352
+ }
353
+
354
+ /* Long press tracking */
355
+ :host {
356
+ user-select: none;
357
+ -webkit-tap-highlight-color: transparent;
358
+ }
359
+
360
+ /* New message animation - bouncy appearance with scale, opacity, and translateY */
361
+ :host.is-new-message {
362
+ animation: message-appear var(--spring-bouncy) forwards;
363
+ }
364
+
365
+ @keyframes message-appear {
366
+ 0% {
367
+ opacity: 0;
368
+ transform: translateY(20px) scale(0.92);
369
+ }
370
+ 100% {
371
+ opacity: 1;
372
+ transform: translateY(0) scale(1);
373
+ }
374
+ }
375
+ `],
376
+ template: `
377
+ @if (!isOwnMessage()) {
378
+ <div class="avatar-wrapper" [class.hidden]="!showAvatar()">
379
+ <ds-avatar
380
+ [initials]="avatarInitials()"
381
+ [type]="avatarType()"
382
+ [src]="avatarSrc()"
383
+ size="sm" />
384
+ </div>
385
+ }
386
+
387
+ <div class="message-content-wrapper" (click)="handleClick($event)">
388
+ <div class="message-bubble">
389
+ <!-- Only show text if content is not empty -->
390
+ @if (content().trim()) {
391
+ <p class="message-text">{{ content() }}</p>
392
+ }
393
+
394
+ <!-- Attachments -->
395
+ @if (attachments() && attachments()!.length > 0) {
396
+ <div class="attachments">
397
+ @for (attachment of attachments(); track attachment.id) {
398
+ <div
399
+ class="attachment-item"
400
+ (click)="handleAttachmentClick(attachment); $event.stopPropagation()">
401
+ <div class="attachment-icon">
402
+ @if (attachment.type === 'image') {
403
+ <ds-icon name="remixImageLine" size="16px" />
404
+ } @else if (attachment.type === 'pdf') {
405
+ <ds-icon name="remixFilePdfLine" size="16px" />
406
+ } @else {
407
+ <ds-icon name="remixFileLine" size="16px" />
408
+ }
409
+ </div>
410
+ <span class="attachment-name">{{ attachment.name }}</span>
411
+ </div>
412
+ }
413
+ </div>
414
+ }
415
+
416
+ <!-- Timestamp inside bubble, animated on tap -->
417
+ <div class="message-footer" [class.visible]="showTimestamp()">
418
+ <span class="timestamp">{{ timestamp() }}</span>
419
+ </div>
420
+ </div>
421
+ </div>
422
+ `
423
+ })
424
+ export class DsMobileMessageBubbleComponent {
425
+ /**
426
+ * Message content text
427
+ */
428
+ content = input.required<string>();
429
+
430
+ /**
431
+ * Whether this is the current user's message (right-aligned)
432
+ */
433
+ isOwnMessage = input<boolean>(false);
434
+
435
+ /**
436
+ * Sender's name (for display purposes)
437
+ */
438
+ senderName = input<string>('');
439
+
440
+ /**
441
+ * Timestamp text (e.g., "12:34", "08-12-2025 13:18")
442
+ */
443
+ timestamp = input.required<string>();
444
+
445
+ /**
446
+ * Whether to show the timestamp below the bubble
447
+ */
448
+ showTimestamp = input<boolean>(false);
449
+
450
+ /**
451
+ * Avatar initials
452
+ */
453
+ avatarInitials = input<string>('');
454
+
455
+ /**
456
+ * Avatar type
457
+ */
458
+ avatarType = input<'initials' | 'photo' | 'icon'>('initials');
459
+
460
+ /**
461
+ * Avatar photo source (for photo type)
462
+ */
463
+ avatarSrc = input<string>('');
464
+
465
+ /**
466
+ * Whether to show the avatar (for clustering logic)
467
+ */
468
+ showAvatar = input<boolean>(true);
469
+
470
+ /**
471
+ * Cluster position for border radius styling
472
+ */
473
+ clusterPosition = input<'single' | 'first' | 'middle' | 'last'>('single');
474
+
475
+ /**
476
+ * Whether to show read receipt (only for user's messages)
477
+ */
478
+ /**
479
+ * Message attachments
480
+ */
481
+ attachments = input<ChatAttachment[] | undefined>(undefined);
482
+
483
+ /**
484
+ * Whether the message is clickable
485
+ */
486
+ clickable = input<boolean>(false);
487
+
488
+ /**
489
+ * Whether this is a newly sent message (triggers appearance animation)
490
+ */
491
+ isNewMessage = input<boolean>(false);
492
+
493
+ /**
494
+ * Emits when attachment is clicked
495
+ */
496
+ attachmentClick = output<ChatAttachment>();
497
+
498
+ /**
499
+ * Emits when the message is long-pressed
500
+ */
501
+ longPress = output<void>();
502
+
503
+ /**
504
+ * Emits when the message is clicked (to toggle timestamp)
505
+ */
506
+ messageClick = output<void>();
507
+
508
+ /**
509
+ * Long press tracking
510
+ */
511
+ private longPressTimer: any = null;
512
+ private longPressTriggered = false;
513
+ private touchStartX = 0;
514
+ private touchStartY = 0;
515
+ private readonly LONG_PRESS_DURATION = 500; // ms
516
+ private readonly MOVE_THRESHOLD = 10; // px
517
+ private clickStartTime = 0;
518
+
519
+ /**
520
+ * Tap animation state
521
+ */
522
+ isTapped = false;
523
+
524
+ /**
525
+ * Handle attachment click
526
+ */
527
+ handleAttachmentClick(attachment: ChatAttachment): void {
528
+ this.attachmentClick.emit(attachment);
529
+ }
530
+
531
+ /**
532
+ * Handle click (for web/mouse support)
533
+ */
534
+ handleClick(event: MouseEvent): void {
535
+ if (!this.clickable()) return;
536
+
537
+ // Prevent double-firing on touch devices
538
+ // Touch events will be handled by handleTouchEnd
539
+ if ((event as any).sourceCapabilities?.firesTouchEvents) {
540
+ return;
541
+ }
542
+
543
+ // Emit message click to toggle timestamp
544
+ this.messageClick.emit();
545
+ }
546
+
547
+ /**
548
+ * Handle touch start for long press detection
549
+ */
550
+ handleTouchStart(event: TouchEvent): void {
551
+ if (!this.clickable()) return;
552
+
553
+ // Add tap scale animation
554
+ this.isTapped = true;
555
+
556
+ this.longPressTriggered = false;
557
+ this.clickStartTime = Date.now();
558
+ this.touchStartX = event.touches[0].clientX;
559
+ this.touchStartY = event.touches[0].clientY;
560
+
561
+ // Start long press timer
562
+ this.longPressTimer = setTimeout(() => {
563
+ this.longPressTriggered = true;
564
+ this.longPress.emit();
565
+ }, this.LONG_PRESS_DURATION);
566
+ }
567
+
568
+ /**
569
+ * Handle touch end to clear long press timer and detect short tap
570
+ */
571
+ handleTouchEnd(event: TouchEvent): void {
572
+ // Remove tap scale animation
573
+ this.isTapped = false;
574
+
575
+ const pressDuration = Date.now() - this.clickStartTime;
576
+
577
+ if (this.longPressTimer) {
578
+ clearTimeout(this.longPressTimer);
579
+ this.longPressTimer = null;
580
+ }
581
+
582
+ // If long press was triggered, prevent other actions
583
+ if (this.longPressTriggered) {
584
+ event.preventDefault();
585
+ event.stopPropagation();
586
+ this.longPressTriggered = false;
587
+ return;
588
+ }
589
+
590
+ // Short tap (less than long press duration) - show timestamp
591
+ if (this.clickable() && pressDuration < this.LONG_PRESS_DURATION) {
592
+ // Check if moved too much
593
+ const touch = event.changedTouches[0];
594
+ const deltaX = Math.abs(touch.clientX - this.touchStartX);
595
+ const deltaY = Math.abs(touch.clientY - this.touchStartY);
596
+
597
+ if (deltaX <= this.MOVE_THRESHOLD && deltaY <= this.MOVE_THRESHOLD) {
598
+ this.messageClick.emit();
599
+ event.preventDefault();
600
+ }
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Handle touch move to cancel long press if moved too much
606
+ */
607
+ handleTouchMove(event: TouchEvent): void {
608
+ if (!this.longPressTimer) return;
609
+
610
+ const touch = event.touches[0];
611
+ const deltaX = Math.abs(touch.clientX - this.touchStartX);
612
+ const deltaY = Math.abs(touch.clientY - this.touchStartY);
613
+
614
+ // Cancel long press if moved too far
615
+ if (deltaX > this.MOVE_THRESHOLD || deltaY > this.MOVE_THRESHOLD) {
616
+ clearTimeout(this.longPressTimer);
617
+ this.longPressTimer = null;
618
+ this.longPressTriggered = false;
619
+ // Remove tap animation if moved too far
620
+ this.isTapped = false;
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Handle context menu (right-click on desktop) to trigger long press action
626
+ */
627
+ handleContextMenu(event: Event): void {
628
+ if (!this.clickable()) return;
629
+ event.preventDefault();
630
+ this.longPress.emit();
631
+ }
632
+ }
633
+
@@ -0,0 +1,7 @@
1
+ export { DsMobileMessageBubbleComponent, type ChatAttachment } from './ds-mobile-message-bubble';
2
+
3
+
4
+
5
+
6
+
7
+