intelliwaketssveltekitv25 1.0.95 → 1.0.98
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/dist/DropDown.stories.d.ts +1 -0
- package/dist/DropDown.svelte +3 -0
- package/dist/DropDown.svelte.d.ts +1 -0
- package/dist/DropDownControl.svelte +3 -0
- package/dist/DropDownControl.svelte.d.ts +1 -0
- package/dist/Functions.d.ts +49 -0
- package/dist/Functions.js +54 -0
- package/dist/ListGroupItems.svelte +6 -8
- package/dist/app.css +1 -1
- package/docs/Home.md +2 -1
- package/docs/ListGroupItems.md +484 -0
- package/docs/MasterDetailLayout.md +557 -0
- package/docs/_Sidebar.md +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# MasterDetailLayout Component
|
|
2
|
+
|
|
3
|
+
**Purpose:** Responsive master-detail pattern with automatic routing integration and breakpoint-based mobile/desktop views
|
|
4
|
+
|
|
5
|
+
**When to Use:**
|
|
6
|
+
- List-detail interfaces (users list → user detail, products → product detail)
|
|
7
|
+
- Navigation sidebars with dynamic content areas
|
|
8
|
+
- Inbox/message viewers, file browsers, or any hierarchical data view
|
|
9
|
+
- When you need automatic mobile/tablet responsive behavior
|
|
10
|
+
- Master list needs to hide on mobile when detail is shown
|
|
11
|
+
|
|
12
|
+
## Key Features
|
|
13
|
+
|
|
14
|
+
- **Automatic Mobile Behavior:** Master list hides when detail opens on smaller screens
|
|
15
|
+
- **PathAnalyzer Integration:** Automatically syncs with SvelteKit routing
|
|
16
|
+
- **Breakpoint Control:** Choose when layout switches from mobile to desktop view
|
|
17
|
+
- **ListGroupItems Integration:** Built-in list navigation with data attributes for testing
|
|
18
|
+
- **Hierarchical Support:** Parent-child relationships in list items
|
|
19
|
+
- **Snippet-Based:** Flexible content slots for header, list, footer, and detail areas
|
|
20
|
+
|
|
21
|
+
## Key Props
|
|
22
|
+
|
|
23
|
+
### Required Props
|
|
24
|
+
|
|
25
|
+
- `pageRoute: string` - Base route for navigation (e.g., 'Users', 'Products'). Used with PathAnalyzer for routing.
|
|
26
|
+
|
|
27
|
+
### Optional Props
|
|
28
|
+
|
|
29
|
+
- `breakAt?: TBreakAt` (default: 'md') - Responsive breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
30
|
+
- `backName?: string | null` - Text for back navigation button on mobile
|
|
31
|
+
- `listItems?: TMasterDetailListGroupItem[] | null` - Array of items for the list
|
|
32
|
+
- `caret?: boolean | 'left' | 'right'` - Show selection indicator arrow
|
|
33
|
+
- `emptyListMessage?: string | null` - Message when list is empty
|
|
34
|
+
- `active?: TFindIsActive` - Filter items: `true` (non-hidden), `false` (hidden), `null` (all)
|
|
35
|
+
- `useValueInHref?: boolean` (default: false) - Append `:value` to generated hrefs
|
|
36
|
+
|
|
37
|
+
### Styling Props
|
|
38
|
+
|
|
39
|
+
- `mdClass?: string` - CSS classes for root container
|
|
40
|
+
- `masterClass?: string` - CSS classes for master (list) section
|
|
41
|
+
- `detailClass?: string` - CSS classes for detail section
|
|
42
|
+
- `wrapText?: boolean` (default: false) - Allow list item text to wrap
|
|
43
|
+
- `ellipses?: boolean` (default: false) - Truncate long text with ellipses
|
|
44
|
+
- `borders?: boolean` (default: false) - Show borders between list items
|
|
45
|
+
- `rounded?: boolean` (default: false) - Use rounded card style
|
|
46
|
+
- `roundedDetailFormatted?: boolean` (default: true) - Apply rounded style to detail when `rounded` is true
|
|
47
|
+
- `roundedDetailFormattedHome?: boolean` - Override rounded detail style for home/no-selection view
|
|
48
|
+
|
|
49
|
+
### Detail Header Props
|
|
50
|
+
|
|
51
|
+
- `detailShowHeader?: boolean | string` (default: false) - Show header in detail area (true = item title, string = custom text)
|
|
52
|
+
- `detailShowHeaderFAProps?: IFAProps | null` - FontAwesome icon props for detail header
|
|
53
|
+
- `noLinkReplace?: boolean` (default: false) - Disable automatic link conversion in DisplayHTML
|
|
54
|
+
|
|
55
|
+
## Snippets
|
|
56
|
+
|
|
57
|
+
- `empty?: Snippet` - Content when list is empty
|
|
58
|
+
- `header?: Snippet` - Master section header (typically title or search)
|
|
59
|
+
- `subheader?: Snippet` - Additional header content below main header
|
|
60
|
+
- `list?: Snippet` - Custom list implementation (replaces default ListGroupItems)
|
|
61
|
+
- `footer?: Snippet` - Master section footer
|
|
62
|
+
- `detail?: Snippet` - Detail/content area (right side on desktop, full screen on mobile)
|
|
63
|
+
|
|
64
|
+
## TMasterDetailListGroupItem Type
|
|
65
|
+
|
|
66
|
+
Extends `TListGroupItem` with additional properties:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
type TMasterDetailListGroupItem = Omit<TListGroupItem, 'subs'> & {
|
|
70
|
+
subs?: TMasterDetailListGroupItem[] // Child items
|
|
71
|
+
roundedDetailFormatted?: boolean // Override rounded style for this item's detail
|
|
72
|
+
detailShowHeader?: boolean | string // Override header visibility for this item
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Common TListGroupItem Properties
|
|
77
|
+
|
|
78
|
+
- `key?: string` - Custom key for testing (recommended)
|
|
79
|
+
- `value?: string` - Item identifier (used for routing and hierarchy)
|
|
80
|
+
- `title?: string | Snippet` - Primary text
|
|
81
|
+
- `sub_title?: string | Snippet` - Secondary text
|
|
82
|
+
- `section?: string` - Section header grouping
|
|
83
|
+
- `href?: string` - Custom navigation path
|
|
84
|
+
- `parent_value?: string` - Parent item value (creates hierarchy)
|
|
85
|
+
- `selected?: boolean` - Item is selected
|
|
86
|
+
- `disabled?: boolean` - Item is disabled
|
|
87
|
+
- `hidden?: boolean` - Item is hidden (respects `active` prop)
|
|
88
|
+
- `faProps?: IFAProps` - FontAwesome icon
|
|
89
|
+
- `icon?: IconDefinition` - FontAwesome icon (alternative)
|
|
90
|
+
- `imageHref?: string` - Image URL
|
|
91
|
+
- `badgeValue?: string | number` - Badge content
|
|
92
|
+
- `badgeColor?: string` - Badge color theme
|
|
93
|
+
- `rightText?: string` - Right-aligned text
|
|
94
|
+
- `linkClick?: () => void` - Custom click handler (overrides href)
|
|
95
|
+
- `hover_title?: string` - Tooltip text
|
|
96
|
+
|
|
97
|
+
## Usage Examples
|
|
98
|
+
|
|
99
|
+
### Basic Master-Detail
|
|
100
|
+
|
|
101
|
+
```svelte
|
|
102
|
+
<script>
|
|
103
|
+
import { MasterDetailLayout } from 'intelliwaketssveltekitv25';
|
|
104
|
+
|
|
105
|
+
const users = [
|
|
106
|
+
{ value: '1', title: 'Alice Johnson', href: 'User/1' },
|
|
107
|
+
{ value: '2', title: 'Bob Smith', href: 'User/2' },
|
|
108
|
+
{ value: '3', title: 'Carol White', href: 'User/3' }
|
|
109
|
+
];
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<MasterDetailLayout
|
|
113
|
+
pageRoute="Users"
|
|
114
|
+
listItems={users}
|
|
115
|
+
backName="Users"
|
|
116
|
+
>
|
|
117
|
+
{#snippet header()}
|
|
118
|
+
<h1 class="text-center p-2">Users</h1>
|
|
119
|
+
{/snippet}
|
|
120
|
+
|
|
121
|
+
{#snippet detail()}
|
|
122
|
+
<slot /> <!-- Child route renders here -->
|
|
123
|
+
{/snippet}
|
|
124
|
+
</MasterDetailLayout>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### With Search and Hierarchical Items
|
|
128
|
+
|
|
129
|
+
```svelte
|
|
130
|
+
<script>
|
|
131
|
+
import { MasterDetailLayout, Search } from 'intelliwaketssveltekitv25';
|
|
132
|
+
import { SearchRows } from '@solidbasisventures/intelliwaketsfoundation';
|
|
133
|
+
|
|
134
|
+
let search = $state('');
|
|
135
|
+
|
|
136
|
+
const allItems = [
|
|
137
|
+
{ value: '1', title: 'Parent Item', href: 'Item/1' },
|
|
138
|
+
{ value: '2', title: 'Child Item A', parent_value: '1', href: 'Item/2' },
|
|
139
|
+
{ value: '3', title: 'Child Item B', parent_value: '1', href: 'Item/3' }
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
let filteredItems = $derived(SearchRows(allItems, search));
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<MasterDetailLayout
|
|
146
|
+
pageRoute="Items"
|
|
147
|
+
listItems={filteredItems}
|
|
148
|
+
breakAt="lg"
|
|
149
|
+
rounded
|
|
150
|
+
caret="right"
|
|
151
|
+
>
|
|
152
|
+
{#snippet header()}
|
|
153
|
+
<div class="p-2">
|
|
154
|
+
<h1 class="text-center">Items</h1>
|
|
155
|
+
<Search bind:value={search} placeholder="Search items..." />
|
|
156
|
+
</div>
|
|
157
|
+
{/snippet}
|
|
158
|
+
|
|
159
|
+
{#snippet detail()}
|
|
160
|
+
<slot />
|
|
161
|
+
{/snippet}
|
|
162
|
+
</MasterDetailLayout>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### With Sections and Custom Keys
|
|
166
|
+
|
|
167
|
+
```svelte
|
|
168
|
+
<script>
|
|
169
|
+
import { MasterDetailLayout } from 'intelliwaketssveltekitv25';
|
|
170
|
+
|
|
171
|
+
const products = [
|
|
172
|
+
{
|
|
173
|
+
key: 'electronics-laptop',
|
|
174
|
+
value: 'laptop',
|
|
175
|
+
title: 'Laptop',
|
|
176
|
+
section: 'Electronics',
|
|
177
|
+
badgeValue: 5,
|
|
178
|
+
href: 'Product/laptop'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: 'electronics-phone',
|
|
182
|
+
value: 'phone',
|
|
183
|
+
title: 'Phone',
|
|
184
|
+
section: 'Electronics',
|
|
185
|
+
badgeValue: 12,
|
|
186
|
+
href: 'Product/phone'
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
key: 'furniture-desk',
|
|
190
|
+
value: 'desk',
|
|
191
|
+
title: 'Desk',
|
|
192
|
+
section: 'Furniture',
|
|
193
|
+
rightText: 'In Stock',
|
|
194
|
+
href: 'Product/desk'
|
|
195
|
+
}
|
|
196
|
+
];
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<MasterDetailLayout
|
|
200
|
+
pageRoute="Products"
|
|
201
|
+
listItems={products}
|
|
202
|
+
detailShowHeader={true}
|
|
203
|
+
borders
|
|
204
|
+
>
|
|
205
|
+
{#snippet header()}
|
|
206
|
+
<h1 class="text-center p-2">Products</h1>
|
|
207
|
+
{/snippet}
|
|
208
|
+
|
|
209
|
+
{#snippet footer()}
|
|
210
|
+
<div class="p-2 text-sm text-gray-500">
|
|
211
|
+
{products.length} products
|
|
212
|
+
</div>
|
|
213
|
+
{/snippet}
|
|
214
|
+
|
|
215
|
+
{#snippet detail()}
|
|
216
|
+
<slot />
|
|
217
|
+
{/snippet}
|
|
218
|
+
</MasterDetailLayout>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## PathAnalyzer Integration
|
|
224
|
+
|
|
225
|
+
MasterDetailLayout automatically creates a `PathAnalyzer` instance to manage navigation state. The PathAnalyzer:
|
|
226
|
+
|
|
227
|
+
- Parses the current SvelteKit route
|
|
228
|
+
- Determines which list item is "open" based on the URL
|
|
229
|
+
- Generates navigation paths for list items
|
|
230
|
+
- Provides `isOpen()` method to check if a path is active
|
|
231
|
+
|
|
232
|
+
### How Item Paths Are Generated
|
|
233
|
+
|
|
234
|
+
If an item doesn't have an explicit `href`, the path is generated:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Without useValueInHref
|
|
238
|
+
ToPascalCase(item.title) // "User Settings" → "UserSettings"
|
|
239
|
+
|
|
240
|
+
// With useValueInHref={true}
|
|
241
|
+
ToPascalCase(item.title) + ':' + item.value // "User Settings" → "UserSettings:123"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Example Routes:**
|
|
245
|
+
- `pageRoute="Users"` + item `{ title: "Alice", value: "1" }` → `/Users/Alice` (or `/Users/Alice:1` with useValueInHref)
|
|
246
|
+
- `pageRoute="Products"` + item `{ href: "Item/special-123" }` → `/Products/Item/special-123`
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Responsive Behavior
|
|
251
|
+
|
|
252
|
+
The `breakAt` prop controls when the layout switches from mobile to desktop:
|
|
253
|
+
|
|
254
|
+
| breakAt | Mobile (stacked) | Desktop (side-by-side) |
|
|
255
|
+
|---------|------------------|------------------------|
|
|
256
|
+
| `sm` | < 640px | ≥ 640px |
|
|
257
|
+
| `md` | < 768px | ≥ 768px (default) |
|
|
258
|
+
| `lg` | < 1024px | ≥ 1024px |
|
|
259
|
+
| `xl` | < 1280px | ≥ 1280px |
|
|
260
|
+
| `2xl` | < 1536px | ≥ 1536px |
|
|
261
|
+
|
|
262
|
+
**Mobile Behavior:**
|
|
263
|
+
- Master list visible when no item selected (URL at base route)
|
|
264
|
+
- Detail area visible when item selected (URL includes item path)
|
|
265
|
+
- Master list hidden when detail shown
|
|
266
|
+
- "Back" button appears (using `backName` prop) via BreakAtManager
|
|
267
|
+
|
|
268
|
+
**Desktop Behavior:**
|
|
269
|
+
- Master and detail visible side-by-side
|
|
270
|
+
- Master list always visible
|
|
271
|
+
- Detail updates without hiding master
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Testing with Playwright
|
|
276
|
+
|
|
277
|
+
Since MasterDetailLayout uses `ListGroupItems` internally, you can use the same testing patterns documented in [ListGroupItems Testing Guide](ListGroupItems).
|
|
278
|
+
|
|
279
|
+
### Locating List Items
|
|
280
|
+
|
|
281
|
+
MasterDetailLayout automatically adds `data-listgroupitem-key` attributes to all list items:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { test, expect } from '@playwright/test';
|
|
285
|
+
import { ListGroupItemKeyCalc } from 'intelliwaketssveltekitv25';
|
|
286
|
+
|
|
287
|
+
test('navigate to user detail', async ({ page }) => {
|
|
288
|
+
await page.goto('/Users');
|
|
289
|
+
|
|
290
|
+
// Using custom key
|
|
291
|
+
await page.locator('[data-listgroupitem-key="user-alice"]').click();
|
|
292
|
+
|
|
293
|
+
// Or using ListGroupItemKeyCalc for auto-generated keys
|
|
294
|
+
const userItem = { value: '1', title: 'Alice Johnson' };
|
|
295
|
+
const key = ListGroupItemKeyCalc(userItem);
|
|
296
|
+
await page.locator(`[data-listgroupitem-key="${key}"]`).click();
|
|
297
|
+
|
|
298
|
+
// Verify navigation
|
|
299
|
+
await expect(page).toHaveURL(/.*Alice/);
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Testing Master-Detail Navigation
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
test('master-detail responsive behavior', async ({ page }) => {
|
|
307
|
+
await page.goto('/Products');
|
|
308
|
+
|
|
309
|
+
// Desktop: Both master and detail visible
|
|
310
|
+
await page.setViewportSize({ width: 1024, height: 768 });
|
|
311
|
+
|
|
312
|
+
await page.locator('[data-listgroupitem-key="electronics-laptop"]').click();
|
|
313
|
+
|
|
314
|
+
await expect(page.locator('.masterDetailMaster')).toBeVisible();
|
|
315
|
+
await expect(page.locator('.masterDetailDetail')).toBeVisible();
|
|
316
|
+
|
|
317
|
+
// Mobile: Only detail visible when item open
|
|
318
|
+
await page.setViewportSize({ width: 375, height: 667 });
|
|
319
|
+
|
|
320
|
+
await expect(page.locator('.masterDetailMaster')).toBeHidden();
|
|
321
|
+
await expect(page.locator('.masterDetailDetail')).toBeVisible();
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Testing List Item Selection
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
test('verify selected item styling', async ({ page }) => {
|
|
329
|
+
await page.goto('/Users/Alice');
|
|
330
|
+
|
|
331
|
+
// Find selected item
|
|
332
|
+
const selectedItem = page.locator('.listGroupItem.selected');
|
|
333
|
+
await expect(selectedItem).toBeVisible();
|
|
334
|
+
|
|
335
|
+
// Verify correct item is selected
|
|
336
|
+
await expect(selectedItem).toHaveAttribute('data-listgroupitem-key', 'user-alice');
|
|
337
|
+
|
|
338
|
+
// Selection indicator should be visible
|
|
339
|
+
await expect(page.locator('.list_group_indicator')).toBeVisible();
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Testing Hierarchical Items
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
test('expand and navigate to child item', async ({ page }) => {
|
|
347
|
+
await page.goto('/Items');
|
|
348
|
+
|
|
349
|
+
// Parent item with children
|
|
350
|
+
const parentItem = page.locator('[data-listgroupitem-key="parent-item"]');
|
|
351
|
+
|
|
352
|
+
// Click expand toggle (first button in the item)
|
|
353
|
+
await parentItem.locator('div[role="button"]').first().click();
|
|
354
|
+
|
|
355
|
+
// Child item becomes visible
|
|
356
|
+
const childItem = page.locator('[data-listgroupitem-key="child-item-a"]');
|
|
357
|
+
await expect(childItem).toBeVisible();
|
|
358
|
+
|
|
359
|
+
// Navigate to child
|
|
360
|
+
await childItem.locator('a').click();
|
|
361
|
+
await expect(page).toHaveURL(/.*ChildItemA/);
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Testing with Custom Keys (Recommended)
|
|
366
|
+
|
|
367
|
+
For more reliable testing, always provide custom `key` properties:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// In your component
|
|
371
|
+
const listItems = [
|
|
372
|
+
{ key: 'user-alice', value: '1', title: 'Alice', href: 'User/1' },
|
|
373
|
+
{ key: 'user-bob', value: '2', title: 'Bob', href: 'User/2' }
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
// In your test
|
|
377
|
+
await page.locator('[data-listgroupitem-key="user-alice"]').click();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Testing Empty State
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
test('display empty message', async ({ page }) => {
|
|
384
|
+
await page.goto('/Products');
|
|
385
|
+
|
|
386
|
+
// When no items
|
|
387
|
+
const emptyMessage = page.locator('.listEmpty');
|
|
388
|
+
await expect(emptyMessage).toContainText('No products available');
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Advanced Patterns
|
|
395
|
+
|
|
396
|
+
### Filter by Hidden State
|
|
397
|
+
|
|
398
|
+
Control which items are visible using the `active` prop:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const items = [
|
|
402
|
+
{ value: '1', title: 'Active User', hidden: false },
|
|
403
|
+
{ value: '2', title: 'Inactive User', hidden: true },
|
|
404
|
+
{ value: '3', title: 'Archived User', hidden: true }
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
// Show only active items (hidden: false)
|
|
408
|
+
<MasterDetailLayout listItems={items} active={true} />
|
|
409
|
+
|
|
410
|
+
// Show only hidden items (hidden: true)
|
|
411
|
+
<MasterDetailLayout listItems={items} active={false} />
|
|
412
|
+
|
|
413
|
+
// Show all items regardless of hidden property
|
|
414
|
+
<MasterDetailLayout listItems={items} active={null} />
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Dynamic Detail Headers
|
|
418
|
+
|
|
419
|
+
Show item-specific headers in the detail area:
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const items = [
|
|
423
|
+
{
|
|
424
|
+
value: '1',
|
|
425
|
+
title: 'Dashboard',
|
|
426
|
+
detailShowHeader: 'Overview Dashboard',
|
|
427
|
+
faProps: { icon: 'chart-line' }
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
value: '2',
|
|
431
|
+
title: 'Settings',
|
|
432
|
+
detailShowHeader: true, // Uses item title
|
|
433
|
+
faProps: { icon: 'cog' }
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
value: '3',
|
|
437
|
+
title: 'Profile',
|
|
438
|
+
detailShowHeader: false // No header
|
|
439
|
+
}
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
<MasterDetailLayout
|
|
443
|
+
listItems={items}
|
|
444
|
+
detailShowHeader={false} // Default for all items
|
|
445
|
+
detailShowHeaderFAProps={{ icon: 'info-circle' }} // Default icon
|
|
446
|
+
/>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Custom List Implementation
|
|
450
|
+
|
|
451
|
+
Replace the default ListGroupItems with your own:
|
|
452
|
+
|
|
453
|
+
```svelte
|
|
454
|
+
<MasterDetailLayout pageRoute="Custom">
|
|
455
|
+
{#snippet list()}
|
|
456
|
+
<ul class="custom-list">
|
|
457
|
+
{#each items as item}
|
|
458
|
+
<li><a href={item.href}>{item.title}</a></li>
|
|
459
|
+
{/each}
|
|
460
|
+
</ul>
|
|
461
|
+
{/snippet}
|
|
462
|
+
|
|
463
|
+
{#snippet detail()}
|
|
464
|
+
<slot />
|
|
465
|
+
{/snippet}
|
|
466
|
+
</MasterDetailLayout>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Common Issues & Solutions
|
|
472
|
+
|
|
473
|
+
### Master List Not Showing on Desktop
|
|
474
|
+
|
|
475
|
+
**Problem:** Master list is hidden even on large screens.
|
|
476
|
+
|
|
477
|
+
**Solution:** Check that an item is not selected (URL is at base route). The master hides when `pathAnalyzer.activePageSlug` exists.
|
|
478
|
+
|
|
479
|
+
### Item Not Getting Selected
|
|
480
|
+
|
|
481
|
+
**Problem:** Clicking an item doesn't show selection styling.
|
|
482
|
+
|
|
483
|
+
**Solution:** Ensure the `href` matches the actual route structure. Use PathAnalyzer's `isOpen()` logic by matching your route params.
|
|
484
|
+
|
|
485
|
+
### Links Not Clickable
|
|
486
|
+
|
|
487
|
+
**Problem:** List items don't navigate when clicked.
|
|
488
|
+
|
|
489
|
+
**Solution:**
|
|
490
|
+
1. Ensure `href` is set on items, or title can be converted to a path
|
|
491
|
+
2. Check that `linkClick` isn't set (it overrides href navigation)
|
|
492
|
+
3. Verify items aren't `disabled`
|
|
493
|
+
|
|
494
|
+
### Children Not Expanding
|
|
495
|
+
|
|
496
|
+
**Problem:** Clicking expand icon doesn't show child items.
|
|
497
|
+
|
|
498
|
+
**Solution:**
|
|
499
|
+
1. Ensure child items have `parent_value` matching parent's `value`
|
|
500
|
+
2. Check that `subsExist` is true or items have `subs` array
|
|
501
|
+
3. Verify children aren't filtered out by `active` prop
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Styling Customization
|
|
506
|
+
|
|
507
|
+
### CSS Classes Used
|
|
508
|
+
|
|
509
|
+
| Class | Element | Purpose |
|
|
510
|
+
|-------|---------|---------|
|
|
511
|
+
| `.masterDetail` | Root container | Grid layout container |
|
|
512
|
+
| `.masterDetailMaster` | Master section | Left sidebar with list |
|
|
513
|
+
| `.masterDetailDetail` | Detail section | Right content area |
|
|
514
|
+
| `.masterDetailDetailWithHeader` | Detail section | Applied when header is shown |
|
|
515
|
+
| `.listGroupItem` | List items | Individual list entries |
|
|
516
|
+
| `.selected` | Active list item | Currently selected item |
|
|
517
|
+
| `.list_group_indicator` | Selection indicator | Animated selection highlight |
|
|
518
|
+
|
|
519
|
+
### Custom Styling Examples
|
|
520
|
+
|
|
521
|
+
```svelte
|
|
522
|
+
<!-- Wider master section -->
|
|
523
|
+
<MasterDetailLayout
|
|
524
|
+
mdClass="md:grid-cols-[400px_1fr]"
|
|
525
|
+
pageRoute="Users"
|
|
526
|
+
{listItems}
|
|
527
|
+
/>
|
|
528
|
+
|
|
529
|
+
<!-- Custom colors -->
|
|
530
|
+
<MasterDetailLayout
|
|
531
|
+
masterClass="bg-blue-50 dark:bg-blue-900"
|
|
532
|
+
detailClass="bg-white dark:bg-slate-800"
|
|
533
|
+
pageRoute="Products"
|
|
534
|
+
{listItems}
|
|
535
|
+
/>
|
|
536
|
+
|
|
537
|
+
<!-- Compact rounded style -->
|
|
538
|
+
<MasterDetailLayout
|
|
539
|
+
rounded
|
|
540
|
+
mdClass="p-1 gap-1"
|
|
541
|
+
pageRoute="Items"
|
|
542
|
+
{listItems}
|
|
543
|
+
/>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Related Components
|
|
549
|
+
|
|
550
|
+
- **[ListGroupItems](ListGroupItems)** - Testing guide for list item data attributes
|
|
551
|
+
- **[PathAnalyzer](Functions#pathanalyzer)** - URL parsing and navigation helper
|
|
552
|
+
- **[Search](Search)** - Integrate search with filtered list items
|
|
553
|
+
- **[TabHref](TabHref)** - Alternative navigation pattern
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
For more details on testing list items with Playwright, see the complete [ListGroupItems Testing Guide](ListGroupItems).
|
package/docs/_Sidebar.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
### Layout & Overlays
|
|
19
19
|
- [Modal](Modal)
|
|
20
|
+
- [MasterDetailLayout](MasterDetailLayout)
|
|
20
21
|
- [TabHeader](TabHeader)
|
|
21
22
|
- [TabHref](TabHref)
|
|
22
23
|
- [SlideDown](SlideDown)
|
|
@@ -35,4 +36,7 @@
|
|
|
35
36
|
- [ImporterLoad](ImporterLoad)
|
|
36
37
|
- [ImporterAnalysis](ImporterAnalysis)
|
|
37
38
|
|
|
39
|
+
### Testing
|
|
40
|
+
- [ListGroupItems](ListGroupItems)
|
|
41
|
+
|
|
38
42
|
---
|