@propbinder/mobile-design 0.2.47 → 0.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +24 -0
- package/package.json +3 -39
- package/src/animations/page-transitions.ts +165 -0
- package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
- package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
- package/src/components/action-list-item/index.ts +2 -0
- package/src/components/app-icon/ds-app-icon.ts +133 -0
- package/src/components/app-icon/index.ts +2 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
- package/src/components/attachment-preview/index.ts +1 -0
- package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
- package/src/components/avatar-with-badge/index.ts +2 -0
- package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
- package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
- package/src/components/booking-modal/index.ts +4 -0
- package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
- package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
- package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
- package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
- package/src/components/bottom-sheet/index.ts +8 -0
- package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
- package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
- package/src/components/card-inline/index.ts +2 -0
- package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
- package/src/components/card-inline-banner/index.ts +1 -0
- package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
- package/src/components/card-inline-contact/index.ts +1 -0
- package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
- package/src/components/card-inline-file/index.ts +1 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
- package/src/components/chat-modal/index.ts +8 -0
- package/src/components/comment/ds-mobile-comment.ts +568 -0
- package/src/components/comment/index.ts +2 -0
- package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
- package/src/components/contact-list-item/index.ts +2 -0
- package/src/components/content/ds-mobile-content.ts +139 -0
- package/src/components/content/index.ts +2 -0
- package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
- package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
- package/src/components/dropdown/index.ts +2 -0
- package/src/components/ds-mobile-tabs.css +407 -0
- package/src/components/ds-mobile-tabs.ts +216 -0
- package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/fab/ds-mobile-fab.ts +315 -0
- package/src/components/fab/index.ts +1 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
- package/src/components/facility-creation-modal/index.ts +9 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
- package/src/components/facility-detail-modal/index.ts +2 -0
- package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
- package/src/components/file-attachment/index.ts +2 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
- package/src/components/handbook-detail-modal/index.ts +3 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
- package/src/components/handbook-folder/index.ts +4 -0
- package/src/components/header-content/ds-mobile-header-content.ts +222 -0
- package/src/components/header-content/index.ts +2 -0
- package/src/components/illustration/ds-mobile-illustration.ts +124 -0
- package/src/components/illustration/index.ts +2 -0
- package/src/components/index.ts +124 -0
- package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
- package/src/components/inline-photo/index.ts +1 -0
- package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
- package/src/components/inline-tabs/index.ts +2 -0
- package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
- package/src/components/interactive-list-item-booking/index.ts +1 -0
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
- package/src/components/interactive-list-item-inquiry/index.ts +2 -0
- package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
- package/src/components/interactive-list-item-message/index.ts +2 -0
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
- package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
- package/src/components/interactive-list-item-post/index.ts +13 -0
- package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
- package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
- package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
- package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
- package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
- package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
- package/src/components/lightbox/index.ts +22 -0
- package/src/components/list-item/ds-mobile-list-item.ts +603 -0
- package/src/components/list-item/index.ts +2 -0
- package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
- package/src/components/list-item-static/index.ts +2 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
- package/src/components/loader-overlay/index.ts +1 -0
- package/src/components/logo/ds-logo.ts +95 -0
- package/src/components/logo/index.ts +2 -0
- package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
- package/src/components/message-bubble/index.ts +7 -0
- package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
- package/src/components/message-composer/index.ts +7 -0
- package/src/components/modal/ds-mobile-modal.css +163 -0
- package/src/components/modal/ds-mobile-modal.service.ts +329 -0
- package/src/components/modal/index.ts +8 -0
- package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
- package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
- package/src/components/modal-base/index.ts +2 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
- package/src/components/new-inquiry-modal/index.ts +4 -0
- package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
- package/src/components/offline-banner/index.ts +1 -0
- package/src/components/page-details/ds-mobile-page-details.css +83 -0
- package/src/components/page-details/ds-mobile-page-details.ts +282 -0
- package/src/components/page-details/index.ts +2 -0
- package/src/components/page-main/ds-mobile-page-main.css +68 -0
- package/src/components/page-main/ds-mobile-page-main.ts +421 -0
- package/src/components/page-main/index.ts +2 -0
- package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
- package/src/components/post-composer/index.ts +2 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
- package/src/components/post-detail-modal/index.ts +9 -0
- package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
- package/src/components/property-banner/index.ts +2 -0
- package/src/components/section/ds-mobile-section.ts +263 -0
- package/src/components/section/index.ts +2 -0
- package/src/components/shared/directives/index.ts +2 -0
- package/src/components/shared/directives/long-press.directive.ts +212 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/shared/mobile-modal-base.ts +457 -0
- package/src/components/shared/mobile-page-base.ts +204 -0
- package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
- package/src/components/swiper/ds-mobile-swiper.ts +327 -0
- package/src/components/swiper/index.ts +3 -0
- package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
- package/src/components/system-message-banner/index.ts +2 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
- package/src/components/tab-bar/index.ts +2 -0
- package/src/components/tabs/ds-mobile-tabs.css +25 -0
- package/src/components/tabs/ds-mobile-tabs.ts +89 -0
- package/src/components/tabs/index.ts +2 -0
- package/src/components/text-input/ds-text-input.ts +287 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/examples/booking.page.ts +434 -0
- package/src/examples/community.page.ts +776 -0
- package/src/examples/handbook.page.ts +324 -0
- package/src/examples/home.page.ts +347 -0
- package/src/examples/index.ts +12 -0
- package/src/examples/inquiries.example.ts +273 -0
- package/src/examples/inquiry-detail.example.css +189 -0
- package/src/examples/inquiry-detail.example.ts +415 -0
- package/src/examples/mobile-tabs-example.component.ts +208 -0
- package/src/examples/post-create.page.ts +311 -0
- package/src/examples/post-detail.page.ts +296 -0
- package/src/examples/sign-in.page.ts +291 -0
- package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
- package/src/examples/whitelabel-demo-modal.service.ts +77 -0
- package/src/models/index.ts +7 -0
- package/src/models/post.model.ts +41 -0
- package/src/pages/community.page.ts +769 -0
- package/src/pages/handbook.page.ts +388 -0
- package/src/pages/home.page.ts +303 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/inquiries.example.ts +273 -0
- package/src/pages/inquiry-detail.example.css +189 -0
- package/src/pages/inquiry-detail.example.ts +415 -0
- package/src/pages/mobile-tabs-example.component.ts +179 -0
- package/src/pages/post-create.page.ts +311 -0
- package/src/pages/post-detail.page.ts +296 -0
- package/src/pages/sign-in.page.ts +291 -0
- package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
- package/src/pages/whitelabel-demo-modal.service.ts +77 -0
- package/src/public-api.ts +6 -0
- package/src/services/base-modal.service.ts +101 -0
- package/src/services/index.ts +11 -0
- package/src/services/posts.service.ts +542 -0
- package/src/services/tracking-permission.service.ts +88 -0
- package/src/services/user.service.ts +60 -0
- package/src/services/whitelabel.service.ts +675 -0
- package/{styles → src/styles}/ionic.css +25 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +13 -0
- package/fesm2022/propbinder-mobile-design.mjs +0 -26136
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8154
- /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
- /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
- /package/{styles → src/components/shared}/mobile-common.css +0 -0
- /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
|
@@ -0,0 +1,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
|
+
|