@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,499 @@
1
+ import { Component, input, output, computed, signal, PLATFORM_ID, inject } from '@angular/core';
2
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
3
+ import { DsMobileLongPressDirective } from '../shared/directives/long-press.directive';
4
+ import { DsIconButtonComponent } from '@propbinder/design-system';
5
+
6
+ /**
7
+ * DsMobileListItemComponent
8
+ *
9
+ * A versatile, reusable list item component for mobile applications.
10
+ * Supports both interactive and non-interactive modes with flexible content projection.
11
+ *
12
+ * Features:
13
+ * - Interactive mode with click and long-press support
14
+ * - Pseudo-element background extends 8px beyond bounds (no negative margins needed)
15
+ * - Flexible content slots (leading, main, trailing)
16
+ * - Optional structured inputs for common use cases (title, subtitle)
17
+ * - Accessibility features (focus states, ARIA attributes)
18
+ * - Disabled and loading states
19
+ *
20
+ * This component serves as the foundation for specialized list item types like posts,
21
+ * notifications, messages, contacts, and other list content.
22
+ *
23
+ * @example
24
+ * ```html
25
+ * <!-- Simple structured usage -->
26
+ * <ds-mobile-list-item
27
+ * title="Document Title"
28
+ * subtitle="Supporting text"
29
+ * [interactive]="true"
30
+ * (itemClick)="handleClick()">
31
+ *
32
+ * <ds-icon content-leading name="document" />
33
+ * </ds-mobile-list-item>
34
+ *
35
+ * <!-- Flexible custom usage -->
36
+ * <ds-mobile-list-item
37
+ * [interactive]="true"
38
+ * (itemClick)="handleClick()"
39
+ * (longPress)="showContextMenu()">
40
+ *
41
+ * <div content-leading>
42
+ * <ds-avatar initials="JD" />
43
+ * </div>
44
+ *
45
+ * <div content-main>
46
+ * <h3>Custom Content</h3>
47
+ * <p>Full control over layout and styling</p>
48
+ * </div>
49
+ *
50
+ * <button content-trailing (click)="handleAction($event)">
51
+ * Action
52
+ * </button>
53
+ * </ds-mobile-list-item>
54
+ *
55
+ * <!-- Non-interactive read-only -->
56
+ * <ds-mobile-list-item
57
+ * title="Read-only Item"
58
+ * subtitle="No interaction">
59
+ * <ds-icon content-leading name="info" />
60
+ * </ds-mobile-list-item>
61
+ * ```
62
+ */
63
+ @Component({
64
+ selector: 'ds-mobile-list-item',
65
+ standalone: true,
66
+ imports: [CommonModule, DsIconButtonComponent],
67
+ hostDirectives: [
68
+ {
69
+ directive: DsMobileLongPressDirective,
70
+ outputs: ['longPress']
71
+ }
72
+ ],
73
+ host: {
74
+ '[class.interactive]': 'interactive() && !disabled()',
75
+ '[class.disabled]': 'disabled()',
76
+ '[class.loading]': 'loading()',
77
+ '[class.no-divider]': '!showDivider()',
78
+ '[class.variant-feed]': 'variant() === "feed"',
79
+ '[class.variant-detail]': 'variant() === "detail"',
80
+ '[class.variant-compact]': 'variant() === "compact"',
81
+ '[attr.role]': 'interactive() ? "button" : null',
82
+ '[attr.tabindex]': 'interactive() && !disabled() ? "0" : null',
83
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
84
+ '[style.--leading-size]': 'leadingSize()',
85
+ '[style.--interactive-offset]': 'interactiveOffset()',
86
+ '[style.--divider-spacing]': 'dividerSpacing()',
87
+ '(click)': 'handleClick($event)',
88
+ '(keydown.enter)': 'handleKeyDown($event)',
89
+ '(keydown.space)': 'handleKeyDown($event)',
90
+ '(longPress)': 'handleLongPress()'
91
+ },
92
+ styles: [`
93
+ :host {
94
+ display: block;
95
+ position: relative;
96
+ padding: var(--item-padding-top, 12px) 0 var(--item-padding-bottom, 12px) 0;
97
+ box-sizing: border-box;
98
+ /* CSS variables defined at host level for use by children and pseudo-elements */
99
+ --leading-size: 32px;
100
+ --content-gap: 12px;
101
+ --interactive-offset: 8px;
102
+ }
103
+
104
+ /* Divider line on host */
105
+ :host::after {
106
+ content: '';
107
+ position: absolute;
108
+ bottom: 0;
109
+ left: calc(var(--leading-size) + var(--content-gap));
110
+ right: 0;
111
+ height: 1px;
112
+ background: var(--border-color-default, #e5e5e5);
113
+ z-index: 1;
114
+ display: var(--divider-display, block);
115
+ }
116
+
117
+ .list-item-inner {
118
+ display: flex;
119
+ flex-direction: row;
120
+ align-items: flex-start;
121
+ gap: var(--content-gap);
122
+ position: relative;
123
+ }
124
+
125
+ /* Pseudo-element for interactive background */
126
+ :host(.interactive) .list-item-inner::before {
127
+ content: '';
128
+ position: absolute;
129
+ top: calc(-1 * var(--interactive-offset));
130
+ left: calc(-1 * var(--interactive-offset));
131
+ right: calc(-1 * var(--interactive-offset));
132
+ bottom: calc(-1 * var(--interactive-offset));
133
+ background: var(--color-background-primary, #ffffff);
134
+ border-radius: 16px;
135
+ z-index: -1;
136
+ pointer-events: none;
137
+ }
138
+
139
+ /* Interactive states */
140
+ :host(.interactive) {
141
+ cursor: pointer;
142
+ }
143
+
144
+ /* Hover state (desktop only) */
145
+ @media (hover: hover) and (pointer: fine) {
146
+ :host(.interactive):hover .list-item-inner::before {
147
+ background: var(--color-background-neutral-primary-hover, #f5f5f5);
148
+ }
149
+ }
150
+
151
+ /* Active state */
152
+ :host(.interactive):active .list-item-inner::before {
153
+ background: var(--color-background-neutral-primary-hover, #f5f5f5);
154
+ }
155
+
156
+ /* Focus visible for keyboard navigation */
157
+ :host(.interactive):focus-visible {
158
+ outline: none;
159
+ }
160
+
161
+ :host(.interactive):focus-visible .list-item-inner::before {
162
+ outline: 2px solid var(--color-brand-primary, #5d5fef);
163
+ outline-offset: 2px;
164
+ }
165
+
166
+ /* Disabled state */
167
+ :host(.disabled) {
168
+ opacity: 0.5;
169
+ pointer-events: none;
170
+ }
171
+
172
+ /* Loading state */
173
+ :host(.loading) {
174
+ pointer-events: none;
175
+ }
176
+
177
+ /* Variants */
178
+ :host(.variant-detail) .list-item-inner {
179
+ padding: 0;
180
+ }
181
+
182
+ :host(.variant-compact) .list-item-inner {
183
+ gap: 8px;
184
+ }
185
+
186
+ /* Content slots */
187
+ .content-leading {
188
+ flex-shrink: 0;
189
+ width: var(--leading-size);
190
+ height: var(--leading-size);
191
+ display: flex;
192
+ align-items: flex-start;
193
+ justify-content: center;
194
+ position: relative;
195
+ z-index: 1;
196
+ }
197
+
198
+ .content-main {
199
+ flex: 1;
200
+ min-width: 0;
201
+ display: flex;
202
+ flex-direction: column;
203
+ gap: 8px;
204
+ position: relative;
205
+ z-index: 1;
206
+ }
207
+
208
+ .content-trailing {
209
+ flex-shrink: 0;
210
+ display: flex;
211
+ align-items: flex-start;
212
+ position: relative;
213
+ z-index: 1;
214
+ }
215
+
216
+ /* Structured content styles */
217
+ .structured-title {
218
+ font-family: 'Brockmann', sans-serif;
219
+ font-size: var(--font-size-sm, 14px);
220
+ font-weight: 600;
221
+ line-height: 20px;
222
+ letter-spacing: -0.3px;
223
+ color: var(--text-color-default-primary, #202227);
224
+ margin: 0;
225
+ white-space: nowrap;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ }
229
+
230
+ .structured-subtitle {
231
+ font-family: 'Brockmann', sans-serif;
232
+ font-size: var(--font-size-sm, 14px);
233
+ font-weight: 400;
234
+ line-height: 20px;
235
+ letter-spacing: -0.3px;
236
+ color: var(--text-color-default-secondary, #545B66);
237
+ margin: 0;
238
+ white-space: nowrap;
239
+ overflow: hidden;
240
+ text-overflow: ellipsis;
241
+ }
242
+
243
+ /* Desktop more actions button - using ds-icon-button */
244
+ .desktop-more-button::ng-deep button {
245
+ border-radius: 50% !important;
246
+ }
247
+ `],
248
+ template: `
249
+ <div class="list-item-inner">
250
+ <div class="content-leading">
251
+ <ng-content select="[content-leading]" />
252
+ </div>
253
+
254
+ <div class="content-main">
255
+ @if (title()) {
256
+ <h3 class="structured-title">{{ title() }}</h3>
257
+ }
258
+ @if (subtitle()) {
259
+ <p class="structured-subtitle">{{ subtitle() }}</p>
260
+ }
261
+
262
+ <ng-content select="[content-main]" />
263
+ <ng-content />
264
+ </div>
265
+
266
+ <div class="content-trailing">
267
+ @if (interactive() && enableLongPress() && showDesktopMoreButton() && isDesktop()) {
268
+ <ds-icon-button
269
+ class="desktop-more-button"
270
+ icon="remixMoreFill"
271
+ variant="secondary"
272
+ size="sm"
273
+ (clicked)="handleMoreButtonClick($event)"
274
+ aria-label="More options">
275
+ </ds-icon-button>
276
+ }
277
+ <ng-content select="[content-trailing]" />
278
+ </div>
279
+ </div>
280
+ `
281
+ })
282
+ export class DsMobileListItemComponent {
283
+ private platformId = inject(PLATFORM_ID);
284
+
285
+ /**
286
+ * Detect if viewport is desktop size
287
+ * Use viewport width for breakpoint detection (show button on tablet and above)
288
+ */
289
+ isDesktop = signal<boolean>(false);
290
+
291
+ constructor() {
292
+ if (isPlatformBrowser(this.platformId)) {
293
+ // Show button on tablet breakpoint and above (768px+)
294
+ const isDesktopViewport = window.innerWidth >= 768;
295
+
296
+ console.log('[ListItem] Desktop detection:', {
297
+ innerWidth: window.innerWidth,
298
+ isDesktopViewport
299
+ });
300
+
301
+ this.isDesktop.set(isDesktopViewport);
302
+
303
+ // Listen for window resize to update detection
304
+ window.addEventListener('resize', () => {
305
+ const newIsDesktop = window.innerWidth >= 768;
306
+ if (newIsDesktop !== this.isDesktop()) {
307
+ console.log('[ListItem] Viewport changed, updating desktop detection:', newIsDesktop);
308
+ this.isDesktop.set(newIsDesktop);
309
+ }
310
+ });
311
+ }
312
+ }
313
+
314
+ /**
315
+ * CSS size value for the leading content area (e.g., '32px', '40px', '48px')
316
+ * Defaults to '32px' for standard list item avatars/icons
317
+ */
318
+ leadingSize = input<string>('32px');
319
+
320
+ /**
321
+ * Display variant
322
+ * - 'feed' - Standard feed display (default)
323
+ * - 'detail' - Full detail view
324
+ * - 'compact' - Compact display for nested/related items
325
+ */
326
+ variant = input<'feed' | 'detail' | 'compact'>('feed');
327
+
328
+ /**
329
+ * Whether the list item is interactive (clickable and long-pressable)
330
+ * When true, adds interactive background, cursor pointer, and touch handlers
331
+ */
332
+ interactive = input<boolean>(false);
333
+
334
+ /**
335
+ * Whether the list item is disabled
336
+ * Disables all interactions and reduces opacity
337
+ */
338
+ disabled = input<boolean>(false);
339
+
340
+ /**
341
+ * Whether the list item is in a loading state
342
+ * Disables interactions but maintains full opacity
343
+ */
344
+ loading = input<boolean>(false);
345
+
346
+ /**
347
+ * Enable long-press interaction when interactive is true
348
+ * Set to false to disable long-press but keep click
349
+ */
350
+ enableLongPress = input<boolean>(true);
351
+
352
+ /**
353
+ * Show "more actions" button on desktop for items with long-press enabled
354
+ * Only visible on desktop (hover: hover) and when enableLongPress is true
355
+ * Clicking this button triggers the same handler as long-press on mobile
356
+ * @default true
357
+ */
358
+ showDesktopMoreButton = input<boolean>(true);
359
+
360
+ /**
361
+ * Offset distance for the interactive background pseudo-element
362
+ * Extends the background beyond the content bounds
363
+ * @default '8px'
364
+ */
365
+ interactiveOffset = input<string>('8px');
366
+
367
+ /**
368
+ * Optional structured title text
369
+ * Provides a simple way to add title without custom markup
370
+ */
371
+ title = input<string>();
372
+
373
+ /**
374
+ * Optional structured subtitle text
375
+ * Provides a simple way to add subtitle without custom markup
376
+ */
377
+ subtitle = input<string>();
378
+
379
+ /**
380
+ * Whether to show the divider line below the list item
381
+ * Automatically hidden on last-child and detail variant
382
+ * @default true
383
+ */
384
+ showDivider = input<boolean>(true);
385
+
386
+ /**
387
+ * Spacing around the divider (top and bottom padding)
388
+ * @default '4px'
389
+ */
390
+ dividerSpacing = input<string>('4px');
391
+
392
+ /**
393
+ * Emits when the list item is clicked (if interactive and not disabled)
394
+ */
395
+ itemClick = output<void>();
396
+
397
+ /**
398
+ * Emits when the desktop more actions button is clicked
399
+ * This is separate from longPress to give more control to parent components
400
+ * Typically, you can use (longPress) for both mobile and desktop actions
401
+ */
402
+ moreButtonClick = output<Event>();
403
+
404
+ /**
405
+ * Track if long press was triggered to prevent click
406
+ */
407
+ private longPressTriggered = false;
408
+
409
+ /**
410
+ * Check if leading content slot has content
411
+ * Always true to maintain consistent layout
412
+ */
413
+ hasLeadingContent = computed(() => true);
414
+
415
+ /**
416
+ * Check if trailing content slot has content
417
+ * Always true to maintain consistent layout
418
+ */
419
+ hasTrailingContent = computed(() => true);
420
+
421
+ /**
422
+ * Handle click events
423
+ */
424
+ handleClick(event: Event): void {
425
+ console.log('[ListItem] Click event fired', {
426
+ interactive: this.interactive(),
427
+ disabled: this.disabled(),
428
+ loading: this.loading(),
429
+ longPressTriggered: this.longPressTriggered,
430
+ target: event.target
431
+ });
432
+
433
+ if (!this.interactive() || this.disabled() || this.loading()) {
434
+ console.log('[ListItem] Click ignored - not interactive or disabled/loading');
435
+ return;
436
+ }
437
+
438
+ // Don't emit click if it came from an interactive child element
439
+ // (but not the host element itself)
440
+ const target = event.target as HTMLElement;
441
+ const closestInteractive = target.closest('button, a, input, select, textarea, [role="button"]');
442
+
443
+ // Check if the interactive element is a child, not the host itself
444
+ if (closestInteractive && closestInteractive !== event.currentTarget) {
445
+ console.log('[ListItem] Click ignored - came from interactive child:', closestInteractive);
446
+ return;
447
+ }
448
+
449
+ if (!this.longPressTriggered) {
450
+ console.log('[ListItem] Emitting itemClick');
451
+ this.itemClick.emit();
452
+ } else {
453
+ console.log('[ListItem] Click ignored - long press was triggered');
454
+ }
455
+
456
+ this.longPressTriggered = false;
457
+ }
458
+
459
+ /**
460
+ * Handle keyboard events (Enter/Space)
461
+ */
462
+ handleKeyDown(event: KeyboardEvent): void {
463
+ if (!this.interactive() || this.disabled() || this.loading()) {
464
+ return;
465
+ }
466
+
467
+ event.preventDefault();
468
+ this.itemClick.emit();
469
+ }
470
+
471
+ /**
472
+ * Handle long press events from the directive
473
+ * Set the flag to prevent the subsequent click event
474
+ */
475
+ handleLongPress(): void {
476
+ this.longPressTriggered = true;
477
+ // Reset the flag after a short delay to allow for the next interaction
478
+ setTimeout(() => {
479
+ this.longPressTriggered = false;
480
+ }, 100);
481
+ }
482
+
483
+ /**
484
+ * Handle desktop more button click
485
+ * Stops propagation to prevent triggering itemClick
486
+ * Emits moreButtonClick for parent components to handle
487
+ */
488
+ handleMoreButtonClick(event: Event): void {
489
+ console.log('[ListItem] Desktop more button clicked');
490
+
491
+ // Stop propagation to prevent triggering itemClick
492
+ event.stopPropagation();
493
+ event.preventDefault();
494
+
495
+ // Emit the more button click event
496
+ this.moreButtonClick.emit(event);
497
+ }
498
+ }
499
+
@@ -0,0 +1,2 @@
1
+ export * from './ds-mobile-list-item';
2
+
@@ -0,0 +1,133 @@
1
+ import { Component, input, computed } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ /**
5
+ * DsMobileListItemStaticComponent
6
+ *
7
+ * A read-only version of the interactive list item component.
8
+ * Used for displaying static information without interaction.
9
+ *
10
+ * This component has the same structure as the interactive list item but without:
11
+ * - Padding
12
+ * - Rounded corners
13
+ * - Hover states
14
+ * - Click interactions
15
+ * - Background fill (transparent)
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <ds-mobile-list-item-static
20
+ * [leadingSize]="'40px'">
21
+ *
22
+ * <div content-leading>
23
+ * <ds-avatar initials="JD" />
24
+ * </div>
25
+ *
26
+ * <div content-main>
27
+ * <h3>Main Content</h3>
28
+ * <p>Supporting text goes here...</p>
29
+ * </div>
30
+ *
31
+ * <div content-trailing>
32
+ * <span>Info</span>
33
+ * </div>
34
+ * </ds-mobile-list-item-static>
35
+ * ```
36
+ */
37
+ @Component({
38
+ selector: 'ds-mobile-list-item-static',
39
+ standalone: true,
40
+ imports: [CommonModule],
41
+ host: {
42
+ '[style.--leading-size]': 'leadingSize()'
43
+ },
44
+ styles: [`
45
+ :host {
46
+ display: flex;
47
+ flex-direction: row;
48
+ align-items: flex-start;
49
+ background: transparent;
50
+ padding: 0;
51
+ gap: 12px;
52
+ position: relative;
53
+ --leading-size: 32px;
54
+ }
55
+
56
+ :host::after {
57
+ content: '';
58
+ position: absolute;
59
+ bottom: -10px;
60
+ left: calc(var(--leading-size) + 12px);
61
+ right: 0;
62
+ height: 1px;
63
+ background: var(--border-color-default);
64
+ }
65
+
66
+ :host:last-child::after {
67
+ display: none;
68
+ }
69
+
70
+ .content-leading {
71
+ flex-shrink: 0;
72
+ width: var(--leading-size);
73
+ min-height: var(--leading-size);
74
+ height: auto;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ }
79
+
80
+ .content-main {
81
+ flex: 1;
82
+ min-width: 0;
83
+ min-height: var(--leading-size);
84
+ display: flex;
85
+ flex-direction: column;
86
+ justify-content: center;
87
+ align-items: flex-start;
88
+ gap: 8px;
89
+ }
90
+
91
+ .content-trailing {
92
+ flex-shrink: 0;
93
+ display: flex;
94
+ align-items: flex-start;
95
+ }
96
+ `],
97
+ template: `
98
+ @if (hasLeadingContent()) {
99
+ <div class="content-leading">
100
+ <ng-content select="[content-leading]" />
101
+ </div>
102
+ }
103
+
104
+ <div class="content-main">
105
+ <ng-content select="[content-main]" />
106
+ <ng-content />
107
+ </div>
108
+
109
+ @if (hasTrailingContent()) {
110
+ <div class="content-trailing">
111
+ <ng-content select="[content-trailing]" />
112
+ </div>
113
+ }
114
+ `
115
+ })
116
+ export class DsMobileListItemStaticComponent {
117
+ /**
118
+ * CSS size value for the leading content area (e.g., '32px', '40px', '48px')
119
+ * Defaults to '32px' for standard list item avatars
120
+ */
121
+ leadingSize = input<string>('32px');
122
+
123
+ /**
124
+ * Check if leading content slot has content
125
+ */
126
+ hasLeadingContent = computed(() => true); // Always render slot container for consistency
127
+
128
+ /**
129
+ * Check if trailing content slot has content
130
+ */
131
+ hasTrailingContent = computed(() => true); // Always render slot container for consistency
132
+ }
133
+
@@ -0,0 +1,2 @@
1
+ export { DsMobileListItemStaticComponent } from './ds-mobile-list-item-static';
2
+