@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.
- 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 -26168
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8169
- /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,160 @@
|
|
|
1
|
+
import { Component, ContentChild, AfterContentInit, ChangeDetectorRef } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { DsIconButtonComponent } from '@propbinder/design-system';
|
|
4
|
+
import { DsMobileSwiperComponent } from './ds-mobile-swiper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DsMobileSwiperWithNavComponent
|
|
8
|
+
*
|
|
9
|
+
* Wrapper component that adds navigation buttons to a ds-mobile-swiper.
|
|
10
|
+
* Automatically handles button states (disabled at start/end) and mobile visibility.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Automatic prev/next navigation buttons
|
|
14
|
+
* - Auto-hide buttons when there's only one slide
|
|
15
|
+
* - Auto-disable buttons at start/end of swiper
|
|
16
|
+
* - Hidden on mobile devices (< 768px)
|
|
17
|
+
* - No pointer-events blocking using display: contents
|
|
18
|
+
* - Self-contained navigation logic
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```html
|
|
22
|
+
* <ds-mobile-swiper-with-nav>
|
|
23
|
+
* <ds-mobile-swiper [slideWidth]="'100%'" [gap]="32">
|
|
24
|
+
* <div class="swiper-slide">Slide 1</div>
|
|
25
|
+
* <div class="swiper-slide">Slide 2</div>
|
|
26
|
+
* </ds-mobile-swiper>
|
|
27
|
+
* </ds-mobile-swiper-with-nav>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'ds-mobile-swiper-with-nav',
|
|
32
|
+
standalone: true,
|
|
33
|
+
imports: [CommonModule, DsIconButtonComponent],
|
|
34
|
+
styles: [`
|
|
35
|
+
:host {
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.swiper-nav-wrapper {
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Use display: contents to avoid blocking pointer events */
|
|
44
|
+
.swiper-nav-buttons {
|
|
45
|
+
display: contents;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.swiper-nav-button {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 50%;
|
|
51
|
+
transform: translateY(-50%);
|
|
52
|
+
z-index: 10;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.swiper-nav-button:first-child {
|
|
56
|
+
left: -48px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.swiper-nav-button:last-child {
|
|
60
|
+
right: -48px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Force buttons to be perfectly round */
|
|
64
|
+
::ng-deep .swiper-nav-button button {
|
|
65
|
+
border-radius: 50% !important;
|
|
66
|
+
width: 48px !important;
|
|
67
|
+
height: 48px !important;
|
|
68
|
+
padding: 0 !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Hide on mobile */
|
|
72
|
+
@media (max-width: 767px) {
|
|
73
|
+
.swiper-nav-button {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`],
|
|
78
|
+
template: `
|
|
79
|
+
<div class="swiper-nav-wrapper">
|
|
80
|
+
<ng-content></ng-content>
|
|
81
|
+
|
|
82
|
+
@if (shouldShowNavButtons()) {
|
|
83
|
+
<div class="swiper-nav-buttons">
|
|
84
|
+
<ds-icon-button
|
|
85
|
+
class="swiper-nav-button"
|
|
86
|
+
icon="remixArrowLeftSLine"
|
|
87
|
+
variant="ghost"
|
|
88
|
+
size="sm"
|
|
89
|
+
[disabled]="isFirstSlide()"
|
|
90
|
+
(clicked)="slidePrev()"
|
|
91
|
+
aria-label="Previous"
|
|
92
|
+
/>
|
|
93
|
+
<ds-icon-button
|
|
94
|
+
class="swiper-nav-button"
|
|
95
|
+
icon="remixArrowRightSLine"
|
|
96
|
+
variant="ghost"
|
|
97
|
+
size="sm"
|
|
98
|
+
[disabled]="isLastSlide()"
|
|
99
|
+
(clicked)="slideNext()"
|
|
100
|
+
aria-label="Next"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
}
|
|
104
|
+
</div>
|
|
105
|
+
`
|
|
106
|
+
})
|
|
107
|
+
export class DsMobileSwiperWithNavComponent implements AfterContentInit {
|
|
108
|
+
@ContentChild(DsMobileSwiperComponent) swiper?: DsMobileSwiperComponent;
|
|
109
|
+
|
|
110
|
+
constructor(private cdr: ChangeDetectorRef) {}
|
|
111
|
+
|
|
112
|
+
ngAfterContentInit(): void {
|
|
113
|
+
// Trigger change detection when swiper is initialized
|
|
114
|
+
// This ensures button states are correct on initial render
|
|
115
|
+
if (this.swiper) {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
this.cdr.detectChanges();
|
|
118
|
+
}, 200);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Navigate to previous slide
|
|
124
|
+
*/
|
|
125
|
+
slidePrev(): void {
|
|
126
|
+
this.swiper?.slidePrev();
|
|
127
|
+
this.cdr.detectChanges();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Navigate to next slide
|
|
132
|
+
*/
|
|
133
|
+
slideNext(): void {
|
|
134
|
+
this.swiper?.slideNext();
|
|
135
|
+
this.cdr.detectChanges();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if at first slide
|
|
140
|
+
*/
|
|
141
|
+
isFirstSlide(): boolean {
|
|
142
|
+
return this.swiper?.isBeginning() ?? true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if at last slide
|
|
147
|
+
*/
|
|
148
|
+
isLastSlide(): boolean {
|
|
149
|
+
return this.swiper?.isEnd() ?? true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if navigation buttons should be shown
|
|
154
|
+
* Hide buttons if there's only one slide
|
|
155
|
+
*/
|
|
156
|
+
shouldShowNavButtons(): boolean {
|
|
157
|
+
const slideCount = this.swiper?.getSlideCount() ?? 0;
|
|
158
|
+
return slideCount > 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
AfterViewInit,
|
|
5
|
+
OnDestroy,
|
|
6
|
+
ElementRef,
|
|
7
|
+
ViewChild,
|
|
8
|
+
CUSTOM_ELEMENTS_SCHEMA
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { CommonModule } from '@angular/common';
|
|
11
|
+
import Swiper from 'swiper';
|
|
12
|
+
import { Pagination } from 'swiper/modules';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* DsMobileSwiperComponent
|
|
16
|
+
*
|
|
17
|
+
* A reusable swiper/carousel component with configurable child width and spacing.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - First slide is left-aligned
|
|
21
|
+
* - Middle slides are centered when active
|
|
22
|
+
* - Last slide is right-aligned
|
|
23
|
+
* - Configurable slide width and gap
|
|
24
|
+
* - Content projection via ng-content
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```html
|
|
28
|
+
* <ds-mobile-swiper [slideWidth]="'75vw'" [gap]="16">
|
|
29
|
+
* <div class="swiper-slide">Slide 1</div>
|
|
30
|
+
* <div class="swiper-slide">Slide 2</div>
|
|
31
|
+
* <div class="swiper-slide">Slide 3</div>
|
|
32
|
+
* </ds-mobile-swiper>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
@Component({
|
|
36
|
+
selector: 'ds-mobile-swiper',
|
|
37
|
+
standalone: true,
|
|
38
|
+
imports: [CommonModule],
|
|
39
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
40
|
+
template: `
|
|
41
|
+
<div class="swiper-container" #swiperContainer>
|
|
42
|
+
<div class="swiper-wrapper">
|
|
43
|
+
<ng-content></ng-content>
|
|
44
|
+
</div>
|
|
45
|
+
@if (pagination()) {
|
|
46
|
+
<div class="swiper-pagination"></div>
|
|
47
|
+
}
|
|
48
|
+
</div>
|
|
49
|
+
`,
|
|
50
|
+
styles: [`
|
|
51
|
+
:host {
|
|
52
|
+
display: block;
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.swiper-container {
|
|
57
|
+
width: 100%;
|
|
58
|
+
position: relative;
|
|
59
|
+
overflow: visible;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.swiper-wrapper {
|
|
63
|
+
display: flex;
|
|
64
|
+
box-sizing: content-box;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:host ::ng-deep .swiper-slide {
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
height: auto;
|
|
70
|
+
position: relative;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: flex-start;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
transition: opacity 300ms ease, transform 300ms ease;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Hide all slides by default when progressive opacity is enabled */
|
|
78
|
+
:host(.progressive-opacity) ::ng-deep .swiper-slide {
|
|
79
|
+
opacity: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Show first slide immediately */
|
|
83
|
+
:host(.progressive-opacity) ::ng-deep .swiper-slide:first-child {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Pagination styles */
|
|
88
|
+
.swiper-pagination {
|
|
89
|
+
position: relative;
|
|
90
|
+
text-align: center;
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
margin-top: 20px;
|
|
94
|
+
margin-bottom: -16px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:host ::ng-deep .swiper-pagination-bullet {
|
|
98
|
+
width: 6px;
|
|
99
|
+
height: 6px;
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
background: var(--color-accent);
|
|
102
|
+
opacity: 0.25;
|
|
103
|
+
transition: all 0.3s ease;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:host ::ng-deep .swiper-pagination-bullet-active {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
background: var(--color-accent);
|
|
110
|
+
width: 20px;
|
|
111
|
+
border-radius: 3px;
|
|
112
|
+
}
|
|
113
|
+
`]
|
|
114
|
+
})
|
|
115
|
+
export class DsMobileSwiperComponent implements AfterViewInit, OnDestroy {
|
|
116
|
+
/**
|
|
117
|
+
* Width of each slide (e.g., '75vw', '300px', '80%')
|
|
118
|
+
*/
|
|
119
|
+
slideWidth = input<string>('75vw');
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Gap between slides in pixels
|
|
123
|
+
*/
|
|
124
|
+
gap = input<number>(16);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Enable pagination dots
|
|
128
|
+
*/
|
|
129
|
+
pagination = input<boolean>(false);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Enable auto height - container adapts to active slide's height
|
|
133
|
+
*/
|
|
134
|
+
autoHeight = input<boolean>(false);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Enable progressive opacity based on slide position
|
|
138
|
+
* Slides fade in/out smoothly as they move toward/away from center
|
|
139
|
+
*/
|
|
140
|
+
progressiveOpacity = input<boolean>(false);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Enable progressive scale based on slide position
|
|
144
|
+
* Slides scale down smoothly as they move away from center
|
|
145
|
+
*/
|
|
146
|
+
progressiveScale = input<boolean>(false);
|
|
147
|
+
|
|
148
|
+
@ViewChild('swiperContainer', { static: false }) swiperContainer!: ElementRef;
|
|
149
|
+
|
|
150
|
+
private swiperInstance: Swiper | null = null;
|
|
151
|
+
|
|
152
|
+
constructor(private elementRef: ElementRef) {}
|
|
153
|
+
|
|
154
|
+
ngAfterViewInit(): void {
|
|
155
|
+
// Add progressive-opacity class to host if enabled
|
|
156
|
+
if (this.progressiveOpacity()) {
|
|
157
|
+
this.elementRef.nativeElement.classList.add('progressive-opacity');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.initializeSwiper();
|
|
162
|
+
}, 100);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private initializeSwiper(): void {
|
|
166
|
+
if (!this.swiperContainer) return;
|
|
167
|
+
|
|
168
|
+
// Apply slide width to all slides
|
|
169
|
+
const slides = this.swiperContainer.nativeElement.querySelectorAll('.swiper-slide');
|
|
170
|
+
slides.forEach((slide: HTMLElement, index: number) => {
|
|
171
|
+
slide.style.width = this.slideWidth();
|
|
172
|
+
|
|
173
|
+
// Set initial opacity BEFORE Swiper initializes to prevent flash of inactive slides
|
|
174
|
+
if (this.progressiveOpacity()) {
|
|
175
|
+
// Hide all slides except the first one (active slide)
|
|
176
|
+
slide.style.opacity = index === 0 ? '1' : '0';
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const config: any = {
|
|
181
|
+
slidesPerView: 'auto',
|
|
182
|
+
spaceBetween: this.gap(),
|
|
183
|
+
centeredSlides: true,
|
|
184
|
+
centeredSlidesBounds: true,
|
|
185
|
+
speed: 300,
|
|
186
|
+
resistance: true,
|
|
187
|
+
resistanceRatio: 0.85,
|
|
188
|
+
autoHeight: this.autoHeight(),
|
|
189
|
+
watchSlidesProgress: this.progressiveOpacity() || this.progressiveScale(), // Enable progress tracking
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Configure event handlers
|
|
193
|
+
config.on = {};
|
|
194
|
+
|
|
195
|
+
// Configure autoHeight animation
|
|
196
|
+
if (this.autoHeight()) {
|
|
197
|
+
config.autoHeight = true;
|
|
198
|
+
// The height transition will use the speed value (300ms)
|
|
199
|
+
config.on.slideChangeTransitionStart = () => {
|
|
200
|
+
// Height transition happens automatically
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Configure progressive effects (opacity and/or scale)
|
|
205
|
+
if (this.progressiveOpacity() || this.progressiveScale()) {
|
|
206
|
+
config.on.setTranslate = () => {
|
|
207
|
+
if (!this.swiperInstance) return;
|
|
208
|
+
|
|
209
|
+
this.swiperInstance.slides.forEach((slideEl: any) => {
|
|
210
|
+
const progress = slideEl.progress || 0;
|
|
211
|
+
const absProgress = Math.abs(progress);
|
|
212
|
+
|
|
213
|
+
// Progressive opacity with sharper cutoff
|
|
214
|
+
if (this.progressiveOpacity()) {
|
|
215
|
+
// Make opacity drop off more aggressively
|
|
216
|
+
// Slides with absProgress > 0.5 will be completely hidden
|
|
217
|
+
let opacity;
|
|
218
|
+
if (absProgress > 0.5) {
|
|
219
|
+
opacity = 0;
|
|
220
|
+
} else {
|
|
221
|
+
opacity = 1 - (absProgress * 2); // 2x multiplier for faster fade
|
|
222
|
+
}
|
|
223
|
+
slideEl.style.opacity = Math.max(opacity, 0).toString();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Progressive scale
|
|
227
|
+
if (this.progressiveScale()) {
|
|
228
|
+
// Scale from 1 (center) to 0.9 (edges)
|
|
229
|
+
const minScale = 0.9;
|
|
230
|
+
const scale = 1 - (absProgress * (1 - minScale));
|
|
231
|
+
slideEl.style.transform = `scale(${Math.max(scale, minScale)})`;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Also update on init
|
|
237
|
+
config.on.init = () => {
|
|
238
|
+
if (!this.swiperInstance) return;
|
|
239
|
+
|
|
240
|
+
this.swiperInstance.slides.forEach((slideEl: any) => {
|
|
241
|
+
const progress = slideEl.progress || 0;
|
|
242
|
+
const absProgress = Math.abs(progress);
|
|
243
|
+
|
|
244
|
+
// Set initial opacity with sharper cutoff
|
|
245
|
+
if (this.progressiveOpacity()) {
|
|
246
|
+
let opacity;
|
|
247
|
+
if (absProgress > 0.5) {
|
|
248
|
+
opacity = 0;
|
|
249
|
+
} else {
|
|
250
|
+
opacity = 1 - (absProgress * 2);
|
|
251
|
+
}
|
|
252
|
+
slideEl.style.opacity = Math.max(opacity, 0).toString();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Set initial scale
|
|
256
|
+
if (this.progressiveScale()) {
|
|
257
|
+
const minScale = 0.9;
|
|
258
|
+
const scale = 1 - (absProgress * (1 - minScale));
|
|
259
|
+
slideEl.style.transform = `scale(${Math.max(scale, minScale)})`;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Add pagination if enabled
|
|
266
|
+
if (this.pagination()) {
|
|
267
|
+
config.modules = [Pagination];
|
|
268
|
+
config.pagination = {
|
|
269
|
+
el: '.swiper-pagination',
|
|
270
|
+
clickable: true,
|
|
271
|
+
dynamicBullets: false,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.swiperInstance = new Swiper(this.swiperContainer.nativeElement, config);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Navigate to previous slide
|
|
280
|
+
*/
|
|
281
|
+
slidePrev(): void {
|
|
282
|
+
this.swiperInstance?.slidePrev();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Navigate to next slide
|
|
287
|
+
*/
|
|
288
|
+
slideNext(): void {
|
|
289
|
+
this.swiperInstance?.slideNext();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Navigate to a specific slide by index
|
|
294
|
+
*/
|
|
295
|
+
slideTo(index: number, speed = 300): void {
|
|
296
|
+
this.swiperInstance?.slideTo(index, speed);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Check if at the beginning
|
|
301
|
+
*/
|
|
302
|
+
isBeginning(): boolean {
|
|
303
|
+
return this.swiperInstance?.isBeginning ?? true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if at the end
|
|
308
|
+
*/
|
|
309
|
+
isEnd(): boolean {
|
|
310
|
+
return this.swiperInstance?.isEnd ?? true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get the total number of slides
|
|
315
|
+
*/
|
|
316
|
+
getSlideCount(): number {
|
|
317
|
+
return this.swiperInstance?.slides?.length ?? 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
ngOnDestroy(): void {
|
|
321
|
+
if (this.swiperInstance) {
|
|
322
|
+
this.swiperInstance.destroy();
|
|
323
|
+
this.swiperInstance = null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DsMobileSystemMessageBannerComponent
|
|
6
|
+
*
|
|
7
|
+
* Full-width centered banner component for displaying system messages in chat conversations.
|
|
8
|
+
* Uses the same text styling as message bubbles for consistency.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Full-width centered layout
|
|
12
|
+
* - Subtle background with theming support
|
|
13
|
+
* - Same typography as message bubbles
|
|
14
|
+
* - Optional icon support
|
|
15
|
+
*
|
|
16
|
+
* Common use cases:
|
|
17
|
+
* - Inquiry status updates ("Your inquiry has been assigned to...")
|
|
18
|
+
* - System notifications ("This inquiry is marked as resolved")
|
|
19
|
+
* - Auto-replies ("We aim to respond within 24 hours...")
|
|
20
|
+
* - Time/date separators
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <!-- Simple system message -->
|
|
25
|
+
* <ds-mobile-system-message-banner
|
|
26
|
+
* [message]="'Ricki Meihlen har overtaget din henvendelse og vil kontakte dig snart.'">
|
|
27
|
+
* </ds-mobile-system-message-banner>
|
|
28
|
+
*
|
|
29
|
+
* <!-- With icon -->
|
|
30
|
+
* <ds-mobile-system-message-banner
|
|
31
|
+
* [message]="'Vi bestræber os på at svare inden for 24 timer på hverdage.'"
|
|
32
|
+
* [iconName]="'remixInformationLine'">
|
|
33
|
+
* </ds-mobile-system-message-banner>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
@Component({
|
|
37
|
+
selector: 'ds-mobile-system-message-banner',
|
|
38
|
+
standalone: true,
|
|
39
|
+
imports: [CommonModule],
|
|
40
|
+
host: {
|
|
41
|
+
'[class.after-timestamp]': 'afterTimestamp()'
|
|
42
|
+
},
|
|
43
|
+
styles: [`
|
|
44
|
+
:host {
|
|
45
|
+
display: block;
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding: 12px 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:host(.after-timestamp) {
|
|
51
|
+
padding-top: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.system-message-container {
|
|
55
|
+
display: flex;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
align-items: center;
|
|
58
|
+
width: 100%;
|
|
59
|
+
padding: 0 16px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.system-message-content {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
gap: 8px;
|
|
67
|
+
padding: 8px 16px;
|
|
68
|
+
background: var(--color-background-neutral-secondary, #f5f5f5);
|
|
69
|
+
border-radius: 16px;
|
|
70
|
+
max-width: 85%;
|
|
71
|
+
text-align: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.system-message-icon {
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
width: 16px;
|
|
77
|
+
height: 16px;
|
|
78
|
+
color: var(--color-text-tertiary, #a0a0a0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.system-message-text {
|
|
82
|
+
font-family: 'Brockmann', sans-serif;
|
|
83
|
+
font-size: var(--font-size-sm);
|
|
84
|
+
font-weight: 400;
|
|
85
|
+
line-height: 20px;
|
|
86
|
+
letter-spacing: -0.3px;
|
|
87
|
+
color: var(--color-text-secondary, #666666);
|
|
88
|
+
margin: 0;
|
|
89
|
+
}
|
|
90
|
+
`],
|
|
91
|
+
template: `
|
|
92
|
+
<div class="system-message-container">
|
|
93
|
+
<div class="system-message-content">
|
|
94
|
+
@if (iconName()) {
|
|
95
|
+
<span class="system-message-icon">
|
|
96
|
+
<!-- Icon slot - you can add ds-icon component here if needed -->
|
|
97
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
98
|
+
<path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM11 7h2v2h-2V7zm0 4h2v6h-2v-6z"/>
|
|
99
|
+
</svg>
|
|
100
|
+
</span>
|
|
101
|
+
}
|
|
102
|
+
<p class="system-message-text">{{ message() }}</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
`
|
|
106
|
+
})
|
|
107
|
+
export class DsMobileSystemMessageBannerComponent {
|
|
108
|
+
/**
|
|
109
|
+
* System message text to display
|
|
110
|
+
*/
|
|
111
|
+
message = input.required<string>();
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Optional icon name (currently using inline SVG for info icon)
|
|
115
|
+
* Can be extended to support full icon library integration
|
|
116
|
+
*/
|
|
117
|
+
iconName = input<string>('');
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether this system message appears directly after a timestamp
|
|
121
|
+
* When true, removes top padding to reduce spacing
|
|
122
|
+
*/
|
|
123
|
+
afterTimestamp = input<boolean>(false);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|