intelliwaketssveltekitv25 1.0.82 → 1.0.83
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/README.md +2 -2
- package/dist/app.css +1 -1
- package/docs/DateRangePicker.md +272 -0
- package/docs/DisplayHTML.md +249 -0
- package/docs/DropDown.md +269 -0
- package/docs/Functions.md +796 -0
- package/docs/Home.md +109 -0
- package/docs/Icon.md +203 -0
- package/docs/Importer.md +328 -0
- package/docs/ImporterAnalysis.md +249 -0
- package/docs/ImporterLoad.md +288 -0
- package/docs/InputNumber.md +159 -0
- package/docs/Integration.md +215 -0
- package/docs/Modal.md +207 -0
- package/docs/MultiSelect.md +304 -0
- package/docs/Paginator.md +332 -0
- package/docs/Search.md +364 -0
- package/docs/SlideDown.md +358 -0
- package/docs/Svelte-5-Patterns.md +364 -0
- package/docs/Switch.md +107 -0
- package/docs/TabHeader.md +333 -0
- package/docs/TabHref.md +370 -0
- package/docs/TextArea.md +118 -0
- package/docs/_Sidebar.md +38 -0
- package/llms.txt +113 -0
- package/package.json +3 -2
- package/llm.txt +0 -1635
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# TabHeader Component
|
|
2
|
+
|
|
3
|
+
**Purpose:** Client-side tab navigation with animated indicator
|
|
4
|
+
|
|
5
|
+
**When to Use:**
|
|
6
|
+
- Switch between views without URL changes
|
|
7
|
+
- Local state tab management
|
|
8
|
+
- Modal or component-level tabs
|
|
9
|
+
- Conditional content display
|
|
10
|
+
|
|
11
|
+
## Key Props
|
|
12
|
+
|
|
13
|
+
- `tabItems: ITabItem<T>[]` (required) - Array of tab definitions
|
|
14
|
+
- `currentKey: T | undefined` ($bindable, required) - Active tab key
|
|
15
|
+
- `printAllTabs?: boolean` (default: false) - Print all tabs or just active
|
|
16
|
+
- `rounded?: boolean` (default: false) - Rounded pill style vs underline style
|
|
17
|
+
- `onchange?: (key: T) => void` - Callback when tab changes
|
|
18
|
+
|
|
19
|
+
## ITabItem Interface
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface ITabItem<T> {
|
|
23
|
+
key: T // Unique tab identifier (any type via generics)
|
|
24
|
+
title?: string // Display text (falls back to key if omitted)
|
|
25
|
+
hidden?: boolean // Hide this tab
|
|
26
|
+
disabled?: boolean // Disable interaction
|
|
27
|
+
faProps?: IFAProps // FontAwesome icon
|
|
28
|
+
action?: (key: T) => void // Custom action instead of tab switch
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tab Styles
|
|
33
|
+
|
|
34
|
+
### Default (Underline)
|
|
35
|
+
```
|
|
36
|
+
─────────────────────────
|
|
37
|
+
Tab 1 Tab 2 Tab 3
|
|
38
|
+
══════
|
|
39
|
+
```
|
|
40
|
+
- Animated underline indicator
|
|
41
|
+
- Clean, modern look
|
|
42
|
+
- Default style
|
|
43
|
+
|
|
44
|
+
### Rounded
|
|
45
|
+
```
|
|
46
|
+
┌─────┐ ┌─────┐ ┌─────┐
|
|
47
|
+
│Tab 1│ │Tab 2│ │Tab 3│
|
|
48
|
+
└─────┘ └─────┘ └─────┘
|
|
49
|
+
```
|
|
50
|
+
- Pill-shaped buttons
|
|
51
|
+
- Gray background for active tab
|
|
52
|
+
- Set `rounded={true}`
|
|
53
|
+
|
|
54
|
+
## Usage Examples
|
|
55
|
+
|
|
56
|
+
```svelte
|
|
57
|
+
<script>
|
|
58
|
+
import { TabHeader } from 'intelliwaketssveltekitv25';
|
|
59
|
+
|
|
60
|
+
let activeTab = $state<string>('overview');
|
|
61
|
+
|
|
62
|
+
const tabs = [
|
|
63
|
+
{ key: 'overview', title: 'Overview' },
|
|
64
|
+
{ key: 'details', title: 'Details' },
|
|
65
|
+
{ key: 'settings', title: 'Settings' }
|
|
66
|
+
];
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<!-- Basic tabs -->
|
|
70
|
+
<TabHeader
|
|
71
|
+
tabItems={tabs}
|
|
72
|
+
bind:currentKey={activeTab}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
{#if activeTab === 'overview'}
|
|
76
|
+
<Overview />
|
|
77
|
+
{:else if activeTab === 'details'}
|
|
78
|
+
<Details />
|
|
79
|
+
{:else if activeTab === 'settings'}
|
|
80
|
+
<Settings />
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
<!-- With icons -->
|
|
84
|
+
<script>
|
|
85
|
+
import { faUser, faCog, faChartBar } from '@fortawesome/free-solid-svg-icons';
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<TabHeader
|
|
89
|
+
bind:currentKey={activeTab}
|
|
90
|
+
tabItems={[
|
|
91
|
+
{ key: 'profile', title: 'Profile', faProps: { icon: faUser } },
|
|
92
|
+
{ key: 'settings', title: 'Settings', faProps: { icon: faCog } },
|
|
93
|
+
{ key: 'analytics', title: 'Analytics', faProps: { icon: faChartBar } }
|
|
94
|
+
]}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<!-- Rounded style -->
|
|
98
|
+
<TabHeader
|
|
99
|
+
bind:currentKey={activeTab}
|
|
100
|
+
tabItems={tabs}
|
|
101
|
+
rounded
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<!-- With callback -->
|
|
105
|
+
<TabHeader
|
|
106
|
+
bind:currentKey={activeTab}
|
|
107
|
+
tabItems={tabs}
|
|
108
|
+
onchange={(key) => {
|
|
109
|
+
console.log('Tab changed to:', key);
|
|
110
|
+
trackTabChange(key);
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<!-- Conditional tabs -->
|
|
115
|
+
<script>
|
|
116
|
+
let isPremium = $state(true);
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<TabHeader
|
|
120
|
+
bind:currentKey={activeTab}
|
|
121
|
+
tabItems={[
|
|
122
|
+
{ key: 'basic', title: 'Basic Features' },
|
|
123
|
+
{ key: 'premium', title: 'Premium', hidden: !isPremium },
|
|
124
|
+
{ key: 'admin', title: 'Admin', disabled: !isAdmin }
|
|
125
|
+
]}
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<!-- Custom action (doesn't switch tabs) -->
|
|
129
|
+
<TabHeader
|
|
130
|
+
bind:currentKey={activeTab}
|
|
131
|
+
tabItems={[
|
|
132
|
+
{ key: 'tab1', title: 'Tab 1' },
|
|
133
|
+
{ key: 'tab2', title: 'Tab 2' },
|
|
134
|
+
{
|
|
135
|
+
key: 'refresh',
|
|
136
|
+
title: 'Refresh',
|
|
137
|
+
faProps: { icon: faRefresh },
|
|
138
|
+
action: () => refreshData()
|
|
139
|
+
}
|
|
140
|
+
]}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<!-- TypeScript generics support -->
|
|
144
|
+
<script lang="ts">
|
|
145
|
+
type TabKey = 'home' | 'about' | 'contact';
|
|
146
|
+
let current = $state<TabKey>('home');
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<TabHeader<TabKey>
|
|
150
|
+
bind:currentKey={current}
|
|
151
|
+
tabItems={[
|
|
152
|
+
{ key: 'home', title: 'Home' },
|
|
153
|
+
{ key: 'about', title: 'About' },
|
|
154
|
+
{ key: 'contact', title: 'Contact' }
|
|
155
|
+
]}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Common Patterns
|
|
160
|
+
|
|
161
|
+
### Modal Tabs
|
|
162
|
+
```svelte
|
|
163
|
+
<Modal show={showModal} title="User Settings">
|
|
164
|
+
<TabHeader
|
|
165
|
+
bind:currentKey={modalTab}
|
|
166
|
+
tabItems={[
|
|
167
|
+
{ key: 'general', title: 'General' },
|
|
168
|
+
{ key: 'security', title: 'Security' },
|
|
169
|
+
{ key: 'notifications', title: 'Notifications' }
|
|
170
|
+
]}
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
<div class="mt-4">
|
|
174
|
+
{#if modalTab === 'general'}
|
|
175
|
+
<GeneralSettings />
|
|
176
|
+
{:else if modalTab === 'security'}
|
|
177
|
+
<SecuritySettings />
|
|
178
|
+
{:else}
|
|
179
|
+
<NotificationSettings />
|
|
180
|
+
{/if}
|
|
181
|
+
</div>
|
|
182
|
+
</Modal>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Wizard Steps
|
|
186
|
+
```svelte
|
|
187
|
+
<script>
|
|
188
|
+
let step = $state(1);
|
|
189
|
+
let canProceed = $derived(validateStep(step));
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<TabHeader
|
|
193
|
+
bind:currentKey={step}
|
|
194
|
+
tabItems={[
|
|
195
|
+
{ key: 1, title: 'Step 1: Info' },
|
|
196
|
+
{ key: 2, title: 'Step 2: Details', disabled: step < 2 },
|
|
197
|
+
{ key: 3, title: 'Step 3: Confirm', disabled: step < 3 }
|
|
198
|
+
]}
|
|
199
|
+
/>
|
|
200
|
+
|
|
201
|
+
<button
|
|
202
|
+
disabled={!canProceed}
|
|
203
|
+
onclick={() => step++}
|
|
204
|
+
>
|
|
205
|
+
Next
|
|
206
|
+
</button>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Dashboard Tabs
|
|
210
|
+
```svelte
|
|
211
|
+
<script>
|
|
212
|
+
let dashboardView = $state('stats');
|
|
213
|
+
</script>
|
|
214
|
+
|
|
215
|
+
<TabHeader
|
|
216
|
+
bind:currentKey={dashboardView}
|
|
217
|
+
tabItems={[
|
|
218
|
+
{ key: 'stats', title: 'Statistics', faProps: { icon: faChartLine } },
|
|
219
|
+
{ key: 'users', title: 'Users', faProps: { icon: faUsers } },
|
|
220
|
+
{ key: 'reports', title: 'Reports', faProps: { icon: faFileAlt } }
|
|
221
|
+
]}
|
|
222
|
+
/>
|
|
223
|
+
|
|
224
|
+
{#if dashboardView === 'stats'}
|
|
225
|
+
<StatsPanel />
|
|
226
|
+
{:else if dashboardView === 'users'}
|
|
227
|
+
<UsersPanel />
|
|
228
|
+
{:else}
|
|
229
|
+
<ReportsPanel />
|
|
230
|
+
{/if}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Numbered Tabs
|
|
234
|
+
```svelte
|
|
235
|
+
<TabHeader
|
|
236
|
+
bind:currentKey={activeTab}
|
|
237
|
+
tabItems={[
|
|
238
|
+
{ key: 1, title: '1. Select Items' },
|
|
239
|
+
{ key: 2, title: '2. Configure' },
|
|
240
|
+
{ key: 3, title: '3. Review' },
|
|
241
|
+
{ key: 4, title: '4. Complete' }
|
|
242
|
+
]}
|
|
243
|
+
/>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Auto-Selection Behavior
|
|
247
|
+
|
|
248
|
+
- If `currentKey` is undefined, first visible tab is selected
|
|
249
|
+
- If `currentKey` references hidden tab, switches to first visible
|
|
250
|
+
- If all tabs are hidden, `currentKey` becomes undefined
|
|
251
|
+
|
|
252
|
+
## Print Behavior
|
|
253
|
+
|
|
254
|
+
### Default (`printAllTabs={false}`)
|
|
255
|
+
- Only active tab button visible in print
|
|
256
|
+
- Inactive tabs hidden
|
|
257
|
+
|
|
258
|
+
### With `printAllTabs={true}`
|
|
259
|
+
- All tab buttons visible in print
|
|
260
|
+
- Active tab still highlighted
|
|
261
|
+
|
|
262
|
+
## Animated Indicator
|
|
263
|
+
|
|
264
|
+
The component includes a smooth animated indicator:
|
|
265
|
+
- Slides to active tab position
|
|
266
|
+
- 300ms transition with easing
|
|
267
|
+
- Calculates position based on button width/position
|
|
268
|
+
- Border-bottom indicator (2px)
|
|
269
|
+
- Uses `primary-selected` color
|
|
270
|
+
|
|
271
|
+
## Common Mistakes
|
|
272
|
+
|
|
273
|
+
- ❌ Not using `bind:currentKey` for two-way binding
|
|
274
|
+
✅ Correct: `bind:currentKey={activeTab}`
|
|
275
|
+
|
|
276
|
+
- ❌ Forgetting to handle tab content switching
|
|
277
|
+
✅ Correct: Use `{#if currentKey === 'tab1'}` to show/hide content
|
|
278
|
+
|
|
279
|
+
- ❌ Using same key for multiple tabs
|
|
280
|
+
✅ Correct: Each tab must have unique key
|
|
281
|
+
|
|
282
|
+
- ❌ Not providing title (shows key instead)
|
|
283
|
+
✅ Correct: Set `title` property for display text
|
|
284
|
+
|
|
285
|
+
- ❌ Using TabHeader when TabHref is more appropriate
|
|
286
|
+
✅ Correct: Use TabHref for URL-based navigation
|
|
287
|
+
|
|
288
|
+
## Comparison: TabHeader vs TabHref
|
|
289
|
+
|
|
290
|
+
| Feature | TabHeader | TabHref |
|
|
291
|
+
|---------|-----------|---------|
|
|
292
|
+
| Navigation | Local state | URL-based |
|
|
293
|
+
| Persistence | Session only | URL shareable |
|
|
294
|
+
| Use case | Modal, components | Page navigation |
|
|
295
|
+
| Browser back | No effect | Works naturally |
|
|
296
|
+
| Performance | Instant | Page load |
|
|
297
|
+
|
|
298
|
+
## Related Components
|
|
299
|
+
|
|
300
|
+
- **TabHref** - URL-based tab navigation
|
|
301
|
+
- **Icon** - Used for tab icons
|
|
302
|
+
- **ITabItem** - Tab configuration interface
|
|
303
|
+
|
|
304
|
+
## Props Reference
|
|
305
|
+
|
|
306
|
+
| Prop | Type | Default | Description |
|
|
307
|
+
|------|------|---------|-------------|
|
|
308
|
+
| `tabItems` | `ITabItem<T>[]` | (required) | Tab definitions |
|
|
309
|
+
| `currentKey` | `T \| undefined` | (required) | Active tab ($bindable) |
|
|
310
|
+
| `printAllTabs` | `boolean` | `false` | Show all tabs in print |
|
|
311
|
+
| `rounded` | `boolean` | `false` | Pill style vs underline |
|
|
312
|
+
| `onchange` | `(key: T) => void` | - | Tab change callback |
|
|
313
|
+
|
|
314
|
+
## Styling
|
|
315
|
+
|
|
316
|
+
### Default Style
|
|
317
|
+
- Border bottom: `border-b-2 border-b-slate-200`
|
|
318
|
+
- Active: `border-b-primary-selected`
|
|
319
|
+
- Text: Black for active, `text-slate-600` for inactive
|
|
320
|
+
- Indicator: Animated border-bottom
|
|
321
|
+
|
|
322
|
+
### Rounded Style
|
|
323
|
+
- Background: `bg-gray-200` for active
|
|
324
|
+
- Text: `text-gray-900 font-semibold` for active
|
|
325
|
+
- Padding: `px-2.5`
|
|
326
|
+
- Rounded: `rounded-md`
|
|
327
|
+
|
|
328
|
+
## Accessibility
|
|
329
|
+
|
|
330
|
+
- Keyboard navigable (standard button behavior)
|
|
331
|
+
- Visual indicator for active state
|
|
332
|
+
- Disabled state prevents interaction
|
|
333
|
+
- Print-friendly with visibility controls
|
package/docs/TabHref.md
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# TabHref Component
|
|
2
|
+
|
|
3
|
+
**Purpose:** URL-based tab navigation with SvelteKit routing and session/local storage persistence
|
|
4
|
+
|
|
5
|
+
**When to Use:**
|
|
6
|
+
- Page-level tab navigation with URL sync
|
|
7
|
+
- Shareable tab states via URL
|
|
8
|
+
- Persistent tab selection across page refreshes
|
|
9
|
+
- Master-detail layouts with sub-navigation
|
|
10
|
+
|
|
11
|
+
## Key Props
|
|
12
|
+
|
|
13
|
+
- `tabHrefs: ITabHref[]` (required) - Array of tab definitions
|
|
14
|
+
- `basePath?: string` - Base path for tab navigation (optional)
|
|
15
|
+
- `slug?: string | null` ($bindable) - Current tab slug
|
|
16
|
+
- `storage?: 'local' | 'session' | null` (default: 'session') - Persistence method
|
|
17
|
+
- `saveKey?: string | null` - Custom storage key
|
|
18
|
+
- `noIDRemember?: boolean` (default: false) - Disable ID persistence
|
|
19
|
+
- `noTabAdjustments?: boolean` (default: false) - Skip 'Tab' prefix in URLs
|
|
20
|
+
- `reclickToClose?: boolean` (default: false) - Allow closing active tab
|
|
21
|
+
- `bottomMargin?: boolean` (default: true) - Add bottom margin
|
|
22
|
+
- `consoleLogs?: boolean` (default: false) - Enable debug logging
|
|
23
|
+
- `noResolveHidden?: boolean` (default: false) - Don't auto-redirect from hidden tabs
|
|
24
|
+
- `printAllTabs?: boolean` (default: false) - Print all tabs or just active
|
|
25
|
+
- `rounded?: boolean` (default: false) - Rounded pill style
|
|
26
|
+
|
|
27
|
+
## ITabHref Interface
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface ITabHref {
|
|
31
|
+
key: string // Tab identifier
|
|
32
|
+
title?: string // Display text (falls back to key)
|
|
33
|
+
href?: string // Custom URL (overrides default)
|
|
34
|
+
hidden?: boolean // Hide this tab
|
|
35
|
+
disabled?: boolean // Disable interaction
|
|
36
|
+
isInitial?: boolean // Default tab when none selected
|
|
37
|
+
isOpen?: boolean // Manually control open state
|
|
38
|
+
faProps?: IFAProps // FontAwesome icon
|
|
39
|
+
dataSvelteKitPreloadData?: string // Preload strategy
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Key Features
|
|
44
|
+
|
|
45
|
+
### URL Integration
|
|
46
|
+
- Automatically generates tab URLs: `{basePath}/Tab{key}`
|
|
47
|
+
- Custom hrefs override default URL generation
|
|
48
|
+
- Works with SvelteKit's `goto()` and preloading
|
|
49
|
+
- Clean URLs (removes trailing slashes)
|
|
50
|
+
|
|
51
|
+
### Persistence
|
|
52
|
+
- **Session Storage** (default): Persists within browser session
|
|
53
|
+
- **Local Storage**: Persists across browser sessions
|
|
54
|
+
- **None**: No persistence
|
|
55
|
+
- Remembers last tab per base path
|
|
56
|
+
- Optional ID-based memory (e.g., /Users/123 remembers tab separately from /Users/456)
|
|
57
|
+
|
|
58
|
+
### PathAnalyzer Integration
|
|
59
|
+
Uses `PathAnalyzer` utility for:
|
|
60
|
+
- Route parsing and navigation
|
|
61
|
+
- Predecessor/successor patterns (`~/`)
|
|
62
|
+
- Active state detection
|
|
63
|
+
- Dynamic URL generation
|
|
64
|
+
|
|
65
|
+
## Usage Examples
|
|
66
|
+
|
|
67
|
+
```svelte
|
|
68
|
+
<script>
|
|
69
|
+
import { TabHref } from 'intelliwaketssveltekitv25';
|
|
70
|
+
import { faUser, faCog, faChartBar } from '@fortawesome/free-solid-svg-icons';
|
|
71
|
+
|
|
72
|
+
const tabs = [
|
|
73
|
+
{ key: 'Profile', faProps: { icon: faUser } },
|
|
74
|
+
{ key: 'Settings', faProps: { icon: faCog } },
|
|
75
|
+
{ key: 'Analytics', faProps: { icon: faChartBar } }
|
|
76
|
+
];
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<!-- Basic usage -->
|
|
80
|
+
<TabHref
|
|
81
|
+
tabHrefs={tabs}
|
|
82
|
+
basePath="Dashboard"
|
|
83
|
+
/>
|
|
84
|
+
<!-- Generates: /Dashboard/TabProfile, /Dashboard/TabSettings, /Dashboard/TabAnalytics -->
|
|
85
|
+
|
|
86
|
+
<!-- Custom hrefs -->
|
|
87
|
+
<TabHref
|
|
88
|
+
tabHrefs={[
|
|
89
|
+
{ key: 'Overview', href: '/dashboard' },
|
|
90
|
+
{ key: 'Details', href: '/dashboard/details' },
|
|
91
|
+
{ key: 'History', href: '/dashboard/history' }
|
|
92
|
+
]}
|
|
93
|
+
basePath="Dashboard"
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<!-- With initial tab -->
|
|
97
|
+
<TabHref
|
|
98
|
+
tabHrefs={[
|
|
99
|
+
{ key: 'Info', isInitial: true },
|
|
100
|
+
{ key: 'Details' },
|
|
101
|
+
{ key: 'Settings' }
|
|
102
|
+
]}
|
|
103
|
+
basePath="Product"
|
|
104
|
+
/>
|
|
105
|
+
<!-- If no tab in URL, redirects to /Product/TabInfo -->
|
|
106
|
+
|
|
107
|
+
<!-- Local storage persistence -->
|
|
108
|
+
<TabHref
|
|
109
|
+
tabHrefs={tabs}
|
|
110
|
+
basePath="Settings"
|
|
111
|
+
storage="local"
|
|
112
|
+
/>
|
|
113
|
+
<!-- Persists across browser sessions -->
|
|
114
|
+
|
|
115
|
+
<!-- No persistence -->
|
|
116
|
+
<TabHref
|
|
117
|
+
tabHrefs={tabs}
|
|
118
|
+
basePath="Wizard"
|
|
119
|
+
storage={null}
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<!-- Custom save key -->
|
|
123
|
+
<TabHref
|
|
124
|
+
tabHrefs={tabs}
|
|
125
|
+
basePath="Users"
|
|
126
|
+
saveKey="userDetailTabs"
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
<!-- Rounded style -->
|
|
130
|
+
<TabHref
|
|
131
|
+
tabHrefs={tabs}
|
|
132
|
+
basePath="Profile"
|
|
133
|
+
rounded
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<!-- Without Tab prefix in URLs -->
|
|
137
|
+
<TabHref
|
|
138
|
+
tabHrefs={tabs}
|
|
139
|
+
basePath="Admin"
|
|
140
|
+
noTabAdjustments
|
|
141
|
+
/>
|
|
142
|
+
<!-- Generates: /Admin/Profile, /Admin/Settings (no "Tab" prefix) -->
|
|
143
|
+
|
|
144
|
+
<!-- Bind to slug -->
|
|
145
|
+
<script>
|
|
146
|
+
let currentSlug = $state<string | null>(null);
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<TabHref
|
|
150
|
+
tabHrefs={tabs}
|
|
151
|
+
basePath="Dashboard"
|
|
152
|
+
bind:slug={currentSlug}
|
|
153
|
+
/>
|
|
154
|
+
<div>Current tab: {currentSlug}</div>
|
|
155
|
+
|
|
156
|
+
<!-- Debug mode -->
|
|
157
|
+
<TabHref
|
|
158
|
+
tabHrefs={tabs}
|
|
159
|
+
basePath="Debug"
|
|
160
|
+
consoleLogs
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Common Patterns
|
|
165
|
+
|
|
166
|
+
### User Profile Tabs
|
|
167
|
+
```svelte
|
|
168
|
+
<!-- src/routes/Users/User[user_id=integer]/+page.svelte -->
|
|
169
|
+
<script>
|
|
170
|
+
export let data;
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<h1>{data.user.name}</h1>
|
|
174
|
+
|
|
175
|
+
<TabHref
|
|
176
|
+
tabHrefs={[
|
|
177
|
+
{ key: 'Info', faProps: { icon: faUser } },
|
|
178
|
+
{ key: 'Activity', faProps: { icon: faChartLine } },
|
|
179
|
+
{ key: 'Settings', faProps: { icon: faCog } }
|
|
180
|
+
]}
|
|
181
|
+
basePath="Users/User{data.user.id}"
|
|
182
|
+
storage="session"
|
|
183
|
+
/>
|
|
184
|
+
<!-- URLs: /Users/User123/TabInfo, /Users/User123/TabActivity, etc. -->
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Master-Detail Layout
|
|
188
|
+
```svelte
|
|
189
|
+
<MasterDetailLayout basePath="Products">
|
|
190
|
+
{#snippet list()}
|
|
191
|
+
{#each products as product}
|
|
192
|
+
<ProductCard {product} />
|
|
193
|
+
{/each}
|
|
194
|
+
{/snippet}
|
|
195
|
+
|
|
196
|
+
{#snippet detail()}
|
|
197
|
+
<TabHref
|
|
198
|
+
tabHrefs={[
|
|
199
|
+
{ key: 'Overview', isInitial: true },
|
|
200
|
+
{ key: 'Specs' },
|
|
201
|
+
{ key: 'Reviews' }
|
|
202
|
+
]}
|
|
203
|
+
basePath="Products/Product{selectedId}"
|
|
204
|
+
/>
|
|
205
|
+
{/snippet}
|
|
206
|
+
</MasterDetailLayout>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Conditional Tabs
|
|
210
|
+
```svelte
|
|
211
|
+
<script>
|
|
212
|
+
let isAdmin = $state(false);
|
|
213
|
+
</script>
|
|
214
|
+
|
|
215
|
+
<TabHref
|
|
216
|
+
tabHrefs={[
|
|
217
|
+
{ key: 'Public', title: 'Public Info' },
|
|
218
|
+
{ key: 'Private', title: 'Private Info', hidden: !isAdmin },
|
|
219
|
+
{ key: 'Admin', title: 'Admin Panel', hidden: !isAdmin }
|
|
220
|
+
]}
|
|
221
|
+
basePath="Dashboard"
|
|
222
|
+
/>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Nested Routes
|
|
226
|
+
```svelte
|
|
227
|
+
<!-- Parent: /Settings -->
|
|
228
|
+
<TabHref
|
|
229
|
+
tabHrefs={[
|
|
230
|
+
{ key: 'Profile' },
|
|
231
|
+
{ key: 'Security' },
|
|
232
|
+
{ key: 'Billing' }
|
|
233
|
+
]}
|
|
234
|
+
basePath="Settings"
|
|
235
|
+
/>
|
|
236
|
+
|
|
237
|
+
<!-- Each tab can have its own route file: -->
|
|
238
|
+
<!-- src/routes/Settings/TabProfile/+page.svelte -->
|
|
239
|
+
<!-- src/routes/Settings/TabSecurity/+page.svelte -->
|
|
240
|
+
<!-- src/routes/Settings/TabBilling/+page.svelte -->
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Persistence Behavior
|
|
244
|
+
|
|
245
|
+
### Session Storage (Default)
|
|
246
|
+
```
|
|
247
|
+
Key: Tab{basePath}
|
|
248
|
+
Example: "TabDashboard" → "TabProfile"
|
|
249
|
+
Lifetime: Until tab/window closed
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Local Storage
|
|
253
|
+
```
|
|
254
|
+
Key: Tab{basePath}
|
|
255
|
+
Example: "TabSettings" → "TabSecurity"
|
|
256
|
+
Lifetime: Permanent (until cleared)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### ID Memory (noIDRemember=false)
|
|
260
|
+
```
|
|
261
|
+
Key: IDTab{basePath}
|
|
262
|
+
Example: "IDTabUsers/User" → "123"
|
|
263
|
+
Remembers: Which user ID you were viewing
|
|
264
|
+
Use case: Return to last viewed user on page revisit
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## URL Generation
|
|
268
|
+
|
|
269
|
+
### Default (with Tab prefix)
|
|
270
|
+
```
|
|
271
|
+
basePath="Dashboard"
|
|
272
|
+
key="Profile"
|
|
273
|
+
→ /Dashboard/TabProfile
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Without Tab prefix (noTabAdjustments=true)
|
|
277
|
+
```
|
|
278
|
+
basePath="Admin"
|
|
279
|
+
key="Users"
|
|
280
|
+
→ /Admin/Users
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Custom href
|
|
284
|
+
```
|
|
285
|
+
basePath="Reports"
|
|
286
|
+
key="Sales"
|
|
287
|
+
href="/reports/sales-summary"
|
|
288
|
+
→ /reports/sales-summary
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Auto-Redirect Behavior
|
|
292
|
+
|
|
293
|
+
When landing on base path without tab slug:
|
|
294
|
+
1. Check session/local storage for last tab
|
|
295
|
+
2. Use tab with `isInitial: true`
|
|
296
|
+
3. Fall back to first visible tab
|
|
297
|
+
4. Redirect using `replaceState` (no history entry)
|
|
298
|
+
|
|
299
|
+
## Common Mistakes
|
|
300
|
+
|
|
301
|
+
- ❌ Not matching route structure to tab paths
|
|
302
|
+
✅ Correct: Ensure route files match generated URLs
|
|
303
|
+
|
|
304
|
+
- ❌ Using TabHref for local state tabs
|
|
305
|
+
✅ Correct: Use TabHeader for non-URL tabs
|
|
306
|
+
|
|
307
|
+
- ❌ Not providing basePath correctly
|
|
308
|
+
✅ Correct: Match your SvelteKit route structure
|
|
309
|
+
|
|
310
|
+
- ❌ Conflicting saveKey across different tab sets
|
|
311
|
+
✅ Correct: Use unique saveKey for each TabHref instance
|
|
312
|
+
|
|
313
|
+
- ❌ Expecting immediate tab switch (it uses goto)
|
|
314
|
+
✅ Correct: Navigation is async, uses SvelteKit routing
|
|
315
|
+
|
|
316
|
+
## Comparison: TabHref vs TabHeader
|
|
317
|
+
|
|
318
|
+
| Feature | TabHref | TabHeader |
|
|
319
|
+
|---------|---------|-----------|
|
|
320
|
+
| Navigation | URL-based | State-based |
|
|
321
|
+
| Shareable | ✅ Yes | ❌ No |
|
|
322
|
+
| Browser back | ✅ Works | ❌ No effect |
|
|
323
|
+
| Persistence | Storage + URL | Session only |
|
|
324
|
+
| Speed | Page load | Instant |
|
|
325
|
+
| Use case | Pages | Components |
|
|
326
|
+
|
|
327
|
+
## Related Components
|
|
328
|
+
|
|
329
|
+
- **TabHeader** - Local state tab navigation
|
|
330
|
+
- **PathAnalyzer** - URL parsing utility
|
|
331
|
+
- **MasterDetailLayout** - Common container
|
|
332
|
+
- **Icon** - Tab icons
|
|
333
|
+
|
|
334
|
+
## Props Reference
|
|
335
|
+
|
|
336
|
+
| Prop | Type | Default | Description |
|
|
337
|
+
|------|------|---------|-------------|
|
|
338
|
+
| `tabHrefs` | `ITabHref[]` | (required) | Tab definitions |
|
|
339
|
+
| `basePath` | `string` | - | Base route path |
|
|
340
|
+
| `slug` | `string \| null` | `null` | Current tab slug ($bindable) |
|
|
341
|
+
| `storage` | `'local' \| 'session' \| null` | `'session'` | Persistence method |
|
|
342
|
+
| `saveKey` | `string \| null` | `null` | Custom storage key |
|
|
343
|
+
| `noIDRemember` | `boolean` | `false` | Disable ID persistence |
|
|
344
|
+
| `noTabAdjustments` | `boolean` | `false` | Skip 'Tab' prefix |
|
|
345
|
+
| `reclickToClose` | `boolean` | `false` | Allow closing active |
|
|
346
|
+
| `bottomMargin` | `boolean` | `true` | Add margin-bottom |
|
|
347
|
+
| `consoleLogs` | `boolean` | `false` | Debug logging |
|
|
348
|
+
| `noResolveHidden` | `boolean` | `false` | Skip hidden redirect |
|
|
349
|
+
| `printAllTabs` | `boolean` | `false` | Print behavior |
|
|
350
|
+
| `rounded` | `boolean` | `false` | Rounded style |
|
|
351
|
+
|
|
352
|
+
## Styling
|
|
353
|
+
|
|
354
|
+
Same as TabHeader:
|
|
355
|
+
- Underline style (default)
|
|
356
|
+
- Rounded pill style (rounded=true)
|
|
357
|
+
- Animated indicator
|
|
358
|
+
- `primary-selected` color for active state
|
|
359
|
+
|
|
360
|
+
## Advanced: PathAnalyzer
|
|
361
|
+
|
|
362
|
+
The component uses PathAnalyzer for navigation:
|
|
363
|
+
```typescript
|
|
364
|
+
const analyzer = new PathAnalyzer(page, basePath);
|
|
365
|
+
analyzer.open('TabProfile') // Generate URL
|
|
366
|
+
analyzer.activePageSlug // Current tab slug
|
|
367
|
+
analyzer.isOpen('TabProfile') // Check if active
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
See PathAnalyzer utility documentation for advanced usage.
|