@propbinder/mobile-design 0.0.1 → 0.0.2

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 (123) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +12 -39
  3. package/src/animations/page-transitions.ts +86 -0
  4. package/src/assets/fonts/Brockmann-Bold.otf +0 -0
  5. package/src/assets/fonts/Brockmann-BoldItalic.otf +0 -0
  6. package/src/assets/fonts/Brockmann-Medium.otf +0 -0
  7. package/src/assets/fonts/Brockmann-MediumItalic.otf +0 -0
  8. package/src/assets/fonts/Brockmann-Regular.otf +0 -0
  9. package/src/assets/fonts/Brockmann-RegularItalic.otf +0 -0
  10. package/src/assets/fonts/Brockmann-SemiBold.otf +0 -0
  11. package/src/assets/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  12. package/src/assets/fonts/Brockmann_desktop_license.pdf +0 -0
  13. package/src/assets/fonts/brockmann-medium-webfont.woff2 +0 -0
  14. package/src/assets/fonts/brockmann-regular-webfont.woff2 +0 -0
  15. package/src/assets/fonts/brockmann-semibold-webfont.woff2 +0 -0
  16. package/src/components/action-list-item/ds-mobile-action-list-item.ts +83 -0
  17. package/src/components/action-list-item/index.ts +2 -0
  18. package/src/components/app-layout/ds-mobile-app-layout.css +343 -0
  19. package/src/components/app-layout/ds-mobile-app-layout.ts +271 -0
  20. package/src/components/app-layout/index.ts +2 -0
  21. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +130 -0
  22. package/src/components/avatar-with-badge/index.ts +2 -0
  23. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +273 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +110 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +167 -0
  26. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +656 -0
  27. package/src/components/bottom-sheet/index.ts +3 -0
  28. package/src/components/comment/ds-mobile-comment.ts +516 -0
  29. package/src/components/comment/index.ts +2 -0
  30. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  31. package/src/components/contact-list-item/index.ts +2 -0
  32. package/src/components/content/ds-mobile-content.ts +158 -0
  33. package/src/components/content/index.ts +2 -0
  34. package/src/components/ds-mobile-tabs.css +372 -0
  35. package/src/components/ds-mobile-tabs.ts +217 -0
  36. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  37. package/src/components/file-attachment/index.ts +2 -0
  38. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +98 -0
  39. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +514 -0
  40. package/src/components/handbook-detail-modal/index.ts +3 -0
  41. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +130 -0
  42. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +444 -0
  43. package/src/components/handbook-folder/index.ts +4 -0
  44. package/src/components/header-content/ds-mobile-header-content.ts +211 -0
  45. package/src/components/header-content/index.ts +2 -0
  46. package/src/components/index.ts +45 -0
  47. package/src/components/inline-photo/ds-mobile-inline-photo.ts +269 -0
  48. package/src/components/inline-photo/index.ts +1 -0
  49. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.css +60 -0
  50. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +280 -0
  51. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  52. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +197 -0
  53. package/src/components/interactive-list-item-message/index.ts +2 -0
  54. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.css +70 -0
  55. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +594 -0
  56. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  57. package/src/components/interactive-list-item-post/index.ts +13 -0
  58. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +331 -0
  59. package/src/components/lightbox/ds-mobile-lightbox-header.ts +173 -0
  60. package/src/components/lightbox/ds-mobile-lightbox-image.ts +464 -0
  61. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +375 -0
  62. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  63. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  64. package/src/components/lightbox/ds-mobile-lightbox.service.ts +293 -0
  65. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  66. package/src/components/lightbox/index.ts +22 -0
  67. package/src/components/list-item/ds-mobile-list-item.ts +499 -0
  68. package/src/components/list-item/index.ts +2 -0
  69. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  70. package/src/components/list-item-static/index.ts +2 -0
  71. package/src/components/logo/ds-logo.ts +85 -0
  72. package/src/components/logo/index.ts +2 -0
  73. package/src/components/modal/ds-mobile-modal.css +163 -0
  74. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  75. package/src/components/modal/index.ts +8 -0
  76. package/src/components/page-details/ds-mobile-page-details.css +285 -0
  77. package/src/components/page-details/ds-mobile-page-details.ts +128 -0
  78. package/src/components/page-details/index.ts +2 -0
  79. package/src/components/page-main/ds-mobile-page-main.css +346 -0
  80. package/src/components/page-main/ds-mobile-page-main.ts +331 -0
  81. package/src/components/page-main/index.ts +2 -0
  82. package/src/components/post-card/ds-mobile-post-card.ts +685 -0
  83. package/src/components/post-card/ds-mobile-post-pdf-attachment.ts +124 -0
  84. package/src/components/post-card/index.ts +11 -0
  85. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  86. package/src/components/post-composer/index.ts +2 -0
  87. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +104 -0
  88. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +1273 -0
  89. package/src/components/post-detail-modal/index.ts +9 -0
  90. package/src/components/shared/directives/index.ts +2 -0
  91. package/src/components/shared/directives/long-press.directive.ts +208 -0
  92. package/src/components/shared/index.ts +3 -0
  93. package/src/components/shared/mobile-common.css +94 -0
  94. package/src/components/shared/mobile-page-base.css +315 -0
  95. package/src/components/shared/mobile-page-base.ts +70 -0
  96. package/src/components/swiper/ds-mobile-swiper.ts +123 -0
  97. package/src/components/swiper/index.ts +2 -0
  98. package/src/components/tab-bar/ds-mobile-tab-bar.ts +132 -0
  99. package/src/components/tab-bar/index.ts +2 -0
  100. package/src/components/tabs/ds-mobile-tabs.css +405 -0
  101. package/src/components/tabs/ds-mobile-tabs.ts +204 -0
  102. package/src/components/tabs/index.ts +2 -0
  103. package/src/pages/community.page.ts +768 -0
  104. package/src/pages/handbook.page.ts +298 -0
  105. package/src/pages/home.page.ts +192 -0
  106. package/src/pages/index.ts +9 -0
  107. package/src/pages/inquiries.example.ts +212 -0
  108. package/src/pages/inquiry-detail.example.css +434 -0
  109. package/src/pages/inquiry-detail.example.ts +416 -0
  110. package/src/pages/mobile-tabs-example.component.ts +146 -0
  111. package/src/pages/post-create.page.ts +311 -0
  112. package/src/pages/post-detail.page.ts +295 -0
  113. package/src/pages/whitelabel-demo.page.ts +548 -0
  114. package/src/public-api.ts +5 -0
  115. package/src/services/user.service.ts +35 -0
  116. package/src/services/whitelabel.service.ts +171 -0
  117. package/src/styles/ionic.css +673 -0
  118. package/tsconfig.lib.json +17 -0
  119. package/tsconfig.lib.prod.json +9 -0
  120. package/tsconfig.spec.json +13 -0
  121. package/fesm2022/propbinder-mobile-design.mjs +0 -8294
  122. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  123. package/index.d.ts +0 -2860
@@ -0,0 +1,123 @@
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
+
13
+ /**
14
+ * DsMobileSwiperComponent
15
+ *
16
+ * A reusable swiper/carousel component with configurable child width and spacing.
17
+ *
18
+ * Features:
19
+ * - First slide is left-aligned
20
+ * - Middle slides are centered when active
21
+ * - Last slide is right-aligned
22
+ * - Configurable slide width and gap
23
+ * - Content projection via ng-content
24
+ *
25
+ * Usage:
26
+ * ```html
27
+ * <ds-mobile-swiper [slideWidth]="'75vw'" [gap]="16">
28
+ * <div class="swiper-slide">Slide 1</div>
29
+ * <div class="swiper-slide">Slide 2</div>
30
+ * <div class="swiper-slide">Slide 3</div>
31
+ * </ds-mobile-swiper>
32
+ * ```
33
+ */
34
+ @Component({
35
+ selector: 'ds-mobile-swiper',
36
+ standalone: true,
37
+ imports: [CommonModule],
38
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
39
+ template: `
40
+ <div class="swiper-container" #swiperContainer>
41
+ <div class="swiper-wrapper">
42
+ <ng-content></ng-content>
43
+ </div>
44
+ </div>
45
+ `,
46
+ styles: [`
47
+ :host {
48
+ display: block;
49
+ width: 100%;
50
+ }
51
+
52
+ .swiper-container {
53
+ width: 100%;
54
+ position: relative;
55
+ overflow: hidden;
56
+ border-radius: 12px;
57
+ }
58
+
59
+ .swiper-wrapper {
60
+ display: flex;
61
+ transition-property: transform;
62
+ box-sizing: content-box;
63
+ }
64
+
65
+ :host ::ng-deep .swiper-slide {
66
+ flex-shrink: 0;
67
+ height: 100%;
68
+ position: relative;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ }
73
+ `]
74
+ })
75
+ export class DsMobileSwiperComponent implements AfterViewInit, OnDestroy {
76
+ /**
77
+ * Width of each slide (e.g., '75vw', '300px', '80%')
78
+ */
79
+ slideWidth = input<string>('75vw');
80
+
81
+ /**
82
+ * Gap between slides in pixels
83
+ */
84
+ gap = input<number>(16);
85
+
86
+ @ViewChild('swiperContainer', { static: false }) swiperContainer!: ElementRef;
87
+
88
+ private swiperInstance: Swiper | null = null;
89
+
90
+ ngAfterViewInit(): void {
91
+ setTimeout(() => {
92
+ this.initializeSwiper();
93
+ }, 100);
94
+ }
95
+
96
+ private initializeSwiper(): void {
97
+ if (!this.swiperContainer) return;
98
+
99
+ // Apply slide width to all slides
100
+ const slides = this.swiperContainer.nativeElement.querySelectorAll('.swiper-slide');
101
+ slides.forEach((slide: HTMLElement) => {
102
+ slide.style.width = this.slideWidth();
103
+ });
104
+
105
+ this.swiperInstance = new Swiper(this.swiperContainer.nativeElement, {
106
+ slidesPerView: 'auto',
107
+ spaceBetween: this.gap(),
108
+ centeredSlides: true,
109
+ centeredSlidesBounds: true,
110
+ speed: 300,
111
+ resistance: true,
112
+ resistanceRatio: 0.85,
113
+ });
114
+ }
115
+
116
+ ngOnDestroy(): void {
117
+ if (this.swiperInstance) {
118
+ this.swiperInstance.destroy();
119
+ this.swiperInstance = null;
120
+ }
121
+ }
122
+ }
123
+
@@ -0,0 +1,2 @@
1
+ export * from './ds-mobile-swiper';
2
+
@@ -0,0 +1,132 @@
1
+ import { Component, input, output } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ export interface TabItem {
5
+ id: string;
6
+ label: string;
7
+ badge?: number;
8
+ }
9
+
10
+ /**
11
+ * DsMobileTabBarComponent
12
+ *
13
+ * Pill-style tab bar for filtering/switching views
14
+ * Used in the purple header section of pages
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <ds-mobile-tab-bar
19
+ * [tabs]="[
20
+ * { id: 'all', label: 'All' },
21
+ * { id: 'open', label: 'Open' },
22
+ * { id: 'closed', label: 'Closed' }
23
+ * ]"
24
+ * [activeTab]="'all'"
25
+ * (tabChange)="handleTabChange($event)">
26
+ * </ds-mobile-tab-bar>
27
+ * ```
28
+ */
29
+ @Component({
30
+ selector: 'ds-mobile-tab-bar',
31
+ standalone: true,
32
+ imports: [CommonModule],
33
+ styles: [`
34
+ .filter-tabs {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ flex-wrap: wrap;
39
+ height: 40px;
40
+ }
41
+
42
+ .filter-tab {
43
+ min-width: 48px;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ padding: 0px 12px;
48
+ height: 32px;
49
+ border-radius: 20px;
50
+ background: transparent;
51
+ border: none;
52
+ font-family: 'Brockmann', sans-serif;
53
+ font-size: var(--font-size-sm);
54
+ font-weight: 600;
55
+ color: rgba(255, 255, 255, 0.6);
56
+ cursor: pointer;
57
+ transition: all 0.2s ease;
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 6px;
61
+ white-space: nowrap;
62
+ }
63
+
64
+ .filter-tab.active {
65
+ background: var(--color-background-brand, #5d5fef);
66
+ color: white;
67
+ }
68
+
69
+ .filter-tab:hover:not(.active) {
70
+ background: rgba(255, 255, 255, 0.1);
71
+ }
72
+
73
+ .tab-badge {
74
+ min-width: 24px;
75
+ height: 16px;
76
+ padding: 0 6px;
77
+ border-radius: 10px;
78
+ background: rgba(255, 255, 255, 0.2);
79
+ color: white;
80
+ font-family: 'Brockmann', sans-serif;
81
+ font-size: var(--font-size-xs);
82
+ font-weight: 600;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ line-height: 1;
87
+ }
88
+
89
+ .filter-tab.active .tab-badge {
90
+ background: rgba(255, 255, 255, 0.3);
91
+ color: white;
92
+ }
93
+ `],
94
+ template: `
95
+ <div class="filter-tabs">
96
+ @for (tab of tabs(); track tab.id) {
97
+ <button
98
+ class="filter-tab"
99
+ [class.active]="activeTab() === tab.id"
100
+ (click)="handleTabClick(tab.id)"
101
+ [attr.aria-label]="tab.label"
102
+ [attr.aria-selected]="activeTab() === tab.id">
103
+ {{ tab.label }}
104
+ @if (tab.badge && tab.badge > 0) {
105
+ <span class="tab-badge">{{ tab.badge }}</span>
106
+ }
107
+ </button>
108
+ }
109
+ </div>
110
+ `
111
+ })
112
+ export class DsMobileTabBarComponent {
113
+ /**
114
+ * Array of tab items to display
115
+ */
116
+ tabs = input.required<TabItem[]>();
117
+
118
+ /**
119
+ * Currently active tab ID
120
+ */
121
+ activeTab = input.required<string>();
122
+
123
+ /**
124
+ * Emitted when a tab is clicked
125
+ */
126
+ tabChange = output<string>();
127
+
128
+ handleTabClick(tabId: string): void {
129
+ this.tabChange.emit(tabId);
130
+ }
131
+ }
132
+
@@ -0,0 +1,2 @@
1
+ export { DsMobileTabBarComponent, type TabItem } from './ds-mobile-tab-bar';
2
+
@@ -0,0 +1,405 @@
1
+ /* ============================================
2
+ HOST
3
+ ============================================ */
4
+
5
+ :host {
6
+ display: block;
7
+ height: 100vh;
8
+ height: 100dvh; /* Use dynamic viewport height instead of small viewport height */
9
+ }
10
+
11
+ /* Completely disable tooltips on tab buttons */
12
+ ion-tab-button::before,
13
+ ion-tab-button::after {
14
+ content: none !important;
15
+ display: none !important;
16
+ }
17
+
18
+ ion-tab-button[title]::before,
19
+ ion-tab-button[title]::after {
20
+ display: none !important;
21
+ }
22
+
23
+ /* Disable tooltips through shadow DOM */
24
+ ion-tab-button::part(native)::before,
25
+ ion-tab-button::part(native)::after {
26
+ display: none !important;
27
+ }
28
+
29
+ /* ============================================
30
+ ION-TABS BASE
31
+ ============================================ */
32
+
33
+ ion-tabs {
34
+ height: 100%;
35
+ background: var(--color-brand-secondary);
36
+ }
37
+
38
+ /* ============================================
39
+ TAB BAR - MOBILE (BOTTOM)
40
+ ============================================ */
41
+
42
+ .ds-tab-bar {
43
+ --background: var(--color-background-neutral-primary);
44
+ /* No delay when appearing - start immediately */
45
+ transition: transform 0.20s ease-in-out;
46
+ }
47
+
48
+ /* Mobile: Fixed position for slide transitions */
49
+ ion-tab-bar[slot="bottom"] {
50
+ border-top: 1px solid var(--border-color-default);
51
+ padding-top: 8px;
52
+ /* iPhone home indicator safe area is 34px total */
53
+ /* Fallback to 34px for simulator when env variable isn't available */
54
+ padding-bottom: calc(var(--ion-safe-area-bottom, 34px) - 4px);
55
+ }
56
+
57
+ @media (max-width: 767px) {
58
+ ion-tab-bar[slot="bottom"] {
59
+ position: fixed;
60
+ bottom: 0;
61
+ left: 0;
62
+ right: 0;
63
+ z-index: 100;
64
+ }
65
+ }
66
+
67
+ /* Hide tab bar when detail page is active with slide-out animation (Mobile only) */
68
+ @media (max-width: 767px) {
69
+ ion-tabs:has(ds-mobile-page-details) .ds-tab-bar {
70
+ transform: translateY(100%);
71
+ /* Add delay when hiding - wait 650ms so it starts hiding 50ms before page transition ends */
72
+ transition: transform 0.3s ease;
73
+ }
74
+ }
75
+
76
+ /* Mobile: hide logo and avatar, show tab buttons in a row */
77
+ .ds-tab-bar__logo,
78
+ .ds-tab-bar__actions {
79
+ display: none;
80
+ }
81
+
82
+ .ds-tab-bar__tabs {
83
+ display: flex;
84
+ width: 100%;
85
+ justify-content: space-around;
86
+ align-items: center;
87
+ }
88
+
89
+ /* On larger screens or PWA mode, remove the extra Safari toolbar padding */
90
+ @media (min-width: 769px) {
91
+ ion-tab-bar[slot="bottom"] {
92
+ padding-bottom: env(safe-area-inset-bottom, 0px);
93
+ }
94
+ }
95
+
96
+ @media (display-mode: standalone) {
97
+ ion-tab-bar[slot="bottom"] {
98
+ padding-bottom: env(safe-area-inset-bottom, 0px) !important;
99
+ }
100
+ }
101
+
102
+ /* ============================================
103
+ TAB BAR - DESKTOP (TOP)
104
+ ============================================ */
105
+
106
+ @media (min-width: 768px) {
107
+ ion-tab-bar[slot="top"] {
108
+ --background: var(--color-brand-secondary);
109
+ position: relative; /* NOT absolute - part of layout flow */
110
+ display: flex;
111
+ align-items: center;
112
+ padding: 12px 24px;
113
+ height: 64px;
114
+ max-width: none;
115
+ }
116
+
117
+ /* Show logo and avatar on desktop */
118
+ .ds-tab-bar__logo {
119
+ display: flex;
120
+ position: absolute;
121
+ left: 24px;
122
+ align-items: center;
123
+ }
124
+
125
+ .ds-tab-bar__actions {
126
+ display: flex;
127
+ position: absolute;
128
+ right: 24px;
129
+ align-items: center;
130
+ gap: 12px;
131
+ }
132
+
133
+ .ds-tab-bar__tabs {
134
+ display: flex;
135
+ gap: 8px;
136
+ align-items: center;
137
+ max-width: 640px;
138
+ width: 100%;
139
+ margin: 0 auto;
140
+ justify-content: center;
141
+ padding-left: var(--content-padding-md);
142
+ padding-right: var(--content-padding-md);
143
+ }
144
+
145
+ .logomark {
146
+ height: 28px;
147
+ width: auto;
148
+ flex-shrink: 0;
149
+ }
150
+ }
151
+
152
+ @media (min-width: 992px) {
153
+ .ds-tab-bar__tabs {
154
+ max-width: 640px;
155
+ padding-left: var(--content-padding-lg);
156
+ padding-right: var(--content-padding-lg);
157
+ justify-content: center;
158
+ }
159
+ }
160
+
161
+ @media (min-width: 1440px) {
162
+ .ds-tab-bar__tabs {
163
+ max-width: 640px;
164
+ padding-left: var(--content-padding-xl);
165
+ padding-right: var(--content-padding-xl);
166
+ }
167
+ }
168
+
169
+ @media (min-width: 1768px) {
170
+ .ds-tab-bar__tabs {
171
+ max-width: 640px;
172
+ padding-left: var(--content-padding-2xl);
173
+ padding-right: var(--content-padding-2xl);
174
+ }
175
+ }
176
+
177
+ @media (min-width: 1920px) {
178
+ .ds-tab-bar__tabs {
179
+ max-width: 640px;
180
+ padding-left: var(--content-padding-3xl);
181
+ padding-right: var(--content-padding-3xl);
182
+ }
183
+ }
184
+
185
+ /* ============================================
186
+ TAB BUTTONS - MOBILE
187
+ ============================================ */
188
+
189
+ ion-tab-button {
190
+ --color: var(--text-color-default-tertiary);
191
+ --color-selected: var(--color-brand-base);
192
+ display: flex;
193
+ flex-direction: column;
194
+ align-items: center;
195
+ justify-content: center;
196
+ position: relative;
197
+ overflow: visible;
198
+ /* Disable native tooltips */
199
+ pointer-events: auto;
200
+ }
201
+
202
+ /* Remove title attribute to prevent tooltips */
203
+ ion-tab-button::part(native) {
204
+ overflow: visible;
205
+ /* Prevent native tooltips */
206
+ }
207
+
208
+ ion-tab-button[title]::before {
209
+ content: attr(title);
210
+ position: absolute;
211
+ opacity: 0;
212
+ pointer-events: none;
213
+ }
214
+
215
+ /* Custom ripple effect - positioned in center of tab button */
216
+ .tab-icon-ripple {
217
+ position: absolute;
218
+ left: 50%;
219
+ top: 50%;
220
+ width: 40px;
221
+ height: 40px;
222
+ border-radius: 50%;
223
+ background: var(--color-brand-base);
224
+ transform: translate(-50%, -50%) scale(0);
225
+ opacity: 0;
226
+ pointer-events: none;
227
+ transition: all 0.6s cubic-bezier(0.36, 1.2, 0.04, 1.4);
228
+ z-index: 0;
229
+ }
230
+
231
+ /* Trigger ripple on tab selection */
232
+ .tab-selected .tab-icon-ripple {
233
+ transform: translate(-50%, -50%) scale(2);
234
+ opacity: 0.05;
235
+ animation: ripple-fade 0.6s cubic-bezier(0.36, 0.5, 0.04, 1.8) forwards;
236
+ }
237
+
238
+ @keyframes ripple-fade {
239
+ 0% {
240
+ opacity: 0;
241
+ transform: translate(-50%, -50%) scale(0);
242
+ }
243
+ 30% {
244
+ opacity: 0.1;
245
+ }
246
+ 100% {
247
+ opacity: 0;
248
+ transform: translate(-50%, -50%) scale(2);
249
+ }
250
+ }
251
+
252
+ /* Allow ripple to expand beyond button */
253
+ ion-tab-button::part(native) {
254
+ overflow: visible;
255
+ }
256
+
257
+ ion-tab-button ion-ripple-effect {
258
+ color: var(--color-brand-base);
259
+ border-radius: 1000px;
260
+ }
261
+
262
+ ion-tab-button ion-label {
263
+ font-size: var(--font-size-xs);
264
+ font-weight: 400;
265
+ letter-spacing: -0.3px;
266
+ margin-top: 0;
267
+ }
268
+
269
+
270
+ /* ============================================
271
+ TAB ICON ANIMATION
272
+ ============================================ */
273
+
274
+ /* Icon wrapper - relative positioning context */
275
+ .tab-icon-wrapper {
276
+ position: relative;
277
+ width: 24px;
278
+ height: 24px;
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ z-index: 1; /* Above ripple */
283
+ margin-bottom: 4px;
284
+ }
285
+
286
+ /* Both icons positioned absolutely and centered */
287
+ .tab-icon-inactive,
288
+ .tab-icon-active {
289
+ position: absolute;
290
+ left: 50%;
291
+ top: 50%;
292
+ transform: translate(-50%, -50%);
293
+ transition: all 0.8s cubic-bezier(0.36, 1, 0.04, 1);
294
+ }
295
+
296
+ /* Start state (inactive tab) */
297
+ .tab-icon-inactive {
298
+ opacity: 1;
299
+ transform: translate(-50%, -50%) scale(1);
300
+ }
301
+
302
+ .tab-icon-active {
303
+ opacity: 0;
304
+ transform: translate(-50%, calc(-50% - 12px)) scale(0.5);
305
+ }
306
+
307
+ /* End state (active tab) */
308
+ .tab-selected .tab-icon-inactive {
309
+ opacity: 0;
310
+ transform: translate(-50%, -50%) scale(0.5);
311
+ }
312
+
313
+ .tab-selected .tab-icon-active {
314
+ opacity: 1;
315
+ transform: translate(-50%, -50%) scale(1);
316
+ }
317
+
318
+ /* ============================================
319
+ TAB BUTTONS - DESKTOP
320
+ ============================================ */
321
+
322
+ @media (min-width: 768px) {
323
+ .ds-tab-button {
324
+ flex-direction: row;
325
+ height: 40px;
326
+ padding: 0px 16px !important;
327
+ border-radius: 40px;
328
+ transition: all 0.2s ease;
329
+ width: -moz-fit-content;
330
+ width: fit-content;
331
+ min-width: auto;
332
+ flex: 0 0 auto;
333
+ --color: rgba(255, 255, 255, 0.7);
334
+ --color-selected: white;
335
+ background: rgba(255, 255, 255, 0.1);
336
+ position: relative;
337
+ overflow: hidden;
338
+ }
339
+
340
+ /* Desktop: smaller icon wrapper */
341
+ .tab-icon-wrapper {
342
+ width: 20px;
343
+ height: 20px;
344
+ }
345
+
346
+ /* Desktop: smaller ripple */
347
+ .tab-icon-ripple {
348
+ width: 20px;
349
+ height: 20px;
350
+ }
351
+
352
+ /* Ensure native button respects the rounded shape */
353
+ .ds-tab-button::part(native) {
354
+ border-radius: 40px;
355
+ }
356
+
357
+ .ds-tab-button:hover {
358
+ --color: white;
359
+ --color-selected: white;
360
+ }
361
+
362
+ .ds-tab-button.tab-selected {
363
+ background: var(--color-background-brand);
364
+ --color-selected: white;
365
+ }
366
+
367
+ .ds-tab-button .button-native {
368
+ width: auto;
369
+ padding: 0;
370
+ }
371
+
372
+ .ds-tab-button ion-label {
373
+ font-size: var(--font-size-sm);
374
+ font-weight: 500;
375
+ margin: 0;
376
+ color: inherit;
377
+ }
378
+
379
+ .ds-tab-button .tab-icon-wrapper {
380
+ margin-right: 4px;
381
+ margin-bottom: 0px;
382
+ }
383
+
384
+ .ds-tab-button ion-ripple-effect {
385
+ color: rgba(255, 255, 255, 0.3);
386
+ border-radius: 1000px;
387
+ transform: scale(1.5);
388
+ }
389
+ }
390
+
391
+ /* ============================================
392
+ AVATAR STYLING
393
+ ============================================ */
394
+
395
+ @media (min-width: 768px) {
396
+ .ds-tab-bar__actions ds-avatar {
397
+ cursor: pointer;
398
+ transition: transform 0.2s ease;
399
+ }
400
+
401
+ .ds-tab-bar__actions ds-avatar:hover {
402
+ transform: scale(1.05);
403
+ }
404
+ }
405
+