@rokkit/ui 1.0.0-next.99 → 1.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.
- package/README.md +159 -1
- package/package.json +52 -53
- package/src/MarkdownRenderer.svelte +106 -0
- package/src/components/AlertList.svelte +59 -0
- package/src/components/Avatar.svelte +68 -0
- package/src/components/Badge.svelte +57 -0
- package/src/components/BreadCrumbs.svelte +94 -0
- package/src/components/Button.svelte +91 -0
- package/src/components/ButtonGroup.svelte +9 -0
- package/src/components/Card.svelte +71 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +189 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/Divider.svelte +24 -0
- package/src/components/Dropdown.svelte +202 -0
- package/src/components/FloatingAction.svelte +367 -0
- package/src/components/FloatingNavigation.svelte +256 -0
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +25 -0
- package/src/components/LazyTree.svelte +171 -0
- package/src/components/List.svelte +224 -0
- package/src/components/Menu.svelte +270 -0
- package/src/components/Message.svelte +75 -0
- package/src/components/MultiSelect.svelte +367 -0
- package/src/components/NavContent.svelte +42 -0
- package/src/components/PaletteManager.svelte +360 -0
- package/src/components/Pill.svelte +83 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +346 -0
- package/src/components/Rating.svelte +115 -0
- package/src/components/ResponsiveGrid.svelte +34 -0
- package/src/components/Reveal.svelte +46 -0
- package/src/components/SearchFilter.svelte +88 -0
- package/src/components/Select.svelte +424 -0
- package/src/components/Shine.svelte +87 -0
- package/src/components/Stack.svelte +38 -0
- package/src/components/StatusList.svelte +18 -0
- package/src/components/Stepper.svelte +174 -0
- package/src/components/Swatch.svelte +102 -0
- package/src/components/Switch.svelte +71 -0
- package/src/components/Table.svelte +245 -0
- package/src/components/Tabs.svelte +202 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +93 -0
- package/src/components/Toolbar.svelte +306 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tooltip.svelte +121 -0
- package/src/components/Tree.svelte +143 -0
- package/src/components/UploadFileStatus.svelte +102 -0
- package/src/components/UploadProgress.svelte +127 -0
- package/src/components/UploadTarget.svelte +126 -0
- package/src/components/index.ts +50 -0
- package/src/index.ts +61 -0
- package/src/markdown-plugin.ts +12 -0
- package/src/types/button.ts +86 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +123 -0
- package/src/types/floating-navigation.ts +80 -0
- package/src/types/index.ts +58 -0
- package/src/types/list.ts +200 -0
- package/src/types/menu.ts +95 -0
- package/src/types/palette.ts +160 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +176 -0
- package/src/types/switch.ts +68 -0
- package/src/types/table.ts +219 -0
- package/src/types/tabs.ts +103 -0
- package/src/types/timeline.ts +53 -0
- package/src/types/toggle.ts +68 -0
- package/src/types/toolbar.ts +167 -0
- package/src/types/tooltip.ts +21 -0
- package/src/types/tree.ts +250 -0
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +71 -0
- package/src/utils/palette.ts +618 -0
- package/src/utils/shiki.ts +114 -0
- package/src/utils/upload.js +145 -0
- package/LICENSE +0 -21
- package/src/index.js +0 -8
- package/src/wrappers.js +0 -2
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MultiSelect — Trigger + dropdown with List-style flatView content.
|
|
4
|
+
*
|
|
5
|
+
* Same architecture as Select but with toggle selection (dropdown stays open),
|
|
6
|
+
* checkbox indicators, and tags display in trigger.
|
|
7
|
+
*
|
|
8
|
+
* Data attributes:
|
|
9
|
+
* data-select / data-multiselect — root container (both for theme compat)
|
|
10
|
+
* data-select-trigger — trigger button
|
|
11
|
+
* data-select-value — selected value display area
|
|
12
|
+
* data-select-tags — tags container
|
|
13
|
+
* data-select-tag — individual tag
|
|
14
|
+
* data-select-tag-text — tag text
|
|
15
|
+
* data-select-tag-remove — tag remove button
|
|
16
|
+
* data-select-count — count indicator when exceeding maxDisplay
|
|
17
|
+
* data-select-placeholder — placeholder text
|
|
18
|
+
* data-select-arrow — dropdown arrow icon
|
|
19
|
+
* data-select-dropdown — dropdown container
|
|
20
|
+
* data-select-option — option items
|
|
21
|
+
* data-select-checkbox — checkbox indicator
|
|
22
|
+
* data-select-group-label — group header (non-interactive)
|
|
23
|
+
* data-select-group-icon — icon inside group label
|
|
24
|
+
* data-select-divider — divider between groups
|
|
25
|
+
* data-path, data-selected, data-checked, data-disabled, data-open, data-size
|
|
26
|
+
*/
|
|
27
|
+
// @ts-nocheck
|
|
28
|
+
import type { ProxyItem } from '@rokkit/states'
|
|
29
|
+
import { Wrapper, ProxyTree, messages } from '@rokkit/states'
|
|
30
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
31
|
+
import { Navigator, Trigger } from '@rokkit/actions'
|
|
32
|
+
import { DEFAULT_STATE_ICONS, resolveSnippet, ITEM_SNIPPET, GROUP_SNIPPET } from '@rokkit/core'
|
|
33
|
+
import ItemContent from './ItemContent.svelte'
|
|
34
|
+
|
|
35
|
+
interface MultiSelectIcons {
|
|
36
|
+
opened?: string
|
|
37
|
+
closed?: string
|
|
38
|
+
checked?: string
|
|
39
|
+
remove?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
items = [],
|
|
44
|
+
fields = {},
|
|
45
|
+
value = $bindable<unknown[]>([]),
|
|
46
|
+
selected = $bindable<unknown[]>([]),
|
|
47
|
+
placeholder = messages.select,
|
|
48
|
+
size = 'md',
|
|
49
|
+
disabled = false,
|
|
50
|
+
maxDisplay = 3,
|
|
51
|
+
align = 'start',
|
|
52
|
+
direction = 'down',
|
|
53
|
+
icons: userIcons = {} as MultiSelectIcons,
|
|
54
|
+
onchange,
|
|
55
|
+
class: className = '',
|
|
56
|
+
...snippets
|
|
57
|
+
}: {
|
|
58
|
+
items?: unknown[]
|
|
59
|
+
fields?: Record<string, string>
|
|
60
|
+
value?: unknown[]
|
|
61
|
+
selected?: unknown[]
|
|
62
|
+
placeholder?: string
|
|
63
|
+
size?: string
|
|
64
|
+
disabled?: boolean
|
|
65
|
+
maxDisplay?: number
|
|
66
|
+
align?: 'start' | 'end'
|
|
67
|
+
direction?: 'up' | 'down'
|
|
68
|
+
icons?: MultiSelectIcons
|
|
69
|
+
onchange?: (values: unknown[], items: unknown[]) => void
|
|
70
|
+
class?: string
|
|
71
|
+
[key: string]: unknown
|
|
72
|
+
} = $props()
|
|
73
|
+
|
|
74
|
+
const icons = $derived({
|
|
75
|
+
...DEFAULT_STATE_ICONS.selector,
|
|
76
|
+
...DEFAULT_STATE_ICONS.checkbox,
|
|
77
|
+
...DEFAULT_STATE_ICONS.action,
|
|
78
|
+
...userIcons
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// ─── Dropdown state ───────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
let isOpen = $state(false)
|
|
84
|
+
let selectRef = $state<HTMLElement | null>(null)
|
|
85
|
+
let triggerRef = $state<HTMLElement | null>(null)
|
|
86
|
+
let dropdownRef = $state<HTMLElement | null>(null)
|
|
87
|
+
|
|
88
|
+
// ─── Pre-process items ────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const childrenField = $derived(fields?.children || 'children')
|
|
91
|
+
|
|
92
|
+
// Force groups expanded + disabled (non-navigable labels)
|
|
93
|
+
const processedItems = $derived(
|
|
94
|
+
items.map((item) => {
|
|
95
|
+
const children = item[childrenField]
|
|
96
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
97
|
+
return { ...item, expanded: true, disabled: true }
|
|
98
|
+
}
|
|
99
|
+
return item
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// ─── Wrapper ──────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function handleSelect(extractedValue: unknown, proxy: ProxyItem) {
|
|
106
|
+
if (proxy.disabled) return
|
|
107
|
+
toggleItemSelection(extractedValue)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const proxyTree = $derived(new ProxyTree(processedItems, fields))
|
|
111
|
+
const wrapper = $derived(new Wrapper(proxyTree, { onselect: handleSelect }))
|
|
112
|
+
|
|
113
|
+
// Override cancel/blur to close dropdown
|
|
114
|
+
$effect(() => {
|
|
115
|
+
const w = wrapper
|
|
116
|
+
w.cancel = () => {
|
|
117
|
+
isOpen = false
|
|
118
|
+
triggerRef?.focus()
|
|
119
|
+
}
|
|
120
|
+
w.blur = () => {
|
|
121
|
+
isOpen = false
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// When wrapper recreates while open, focus first item
|
|
126
|
+
$effect(() => {
|
|
127
|
+
const _w = wrapper
|
|
128
|
+
if (isOpen) _w.first(null)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// ─── Selection logic ──────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
function isItemSelected(extractedValue: unknown): boolean {
|
|
134
|
+
return (value ?? []).some((v) => v === extractedValue)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildSelectedItems(newValues: unknown[]): unknown[] {
|
|
138
|
+
const result: unknown[] = []
|
|
139
|
+
for (const [, proxy] of wrapper.lookup) {
|
|
140
|
+
if (isLeafWithValue(proxy, newValues)) result.push(proxy.original)
|
|
141
|
+
}
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function applySelection(newValues: unknown[]) {
|
|
146
|
+
const newItems = buildSelectedItems(newValues)
|
|
147
|
+
value = newValues
|
|
148
|
+
selected = newItems
|
|
149
|
+
onchange?.(newValues, newItems)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toggleItemSelection(extractedValue: unknown) {
|
|
153
|
+
const currentValues = value ?? []
|
|
154
|
+
const alreadySelected = currentValues.some((v) => v === extractedValue)
|
|
155
|
+
const newValues = alreadySelected
|
|
156
|
+
? currentValues.filter((v) => v !== extractedValue)
|
|
157
|
+
: [...currentValues, extractedValue]
|
|
158
|
+
applySelection(newValues)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function removeTag(extractedValue: unknown) {
|
|
162
|
+
const newValues = (value ?? []).filter((v) => v !== extractedValue)
|
|
163
|
+
applySelection(newValues)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── Selected items for tags display ──────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
function isLeafWithValue(proxy: ProxyItem, vals: unknown[]): boolean {
|
|
169
|
+
return !proxy.hasChildren && vals.some((v) => v === proxy.value)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function computeSelectedProxies(): ProxyItem[] {
|
|
173
|
+
const vals = value ?? []
|
|
174
|
+
if (vals.length === 0) return []
|
|
175
|
+
const result: ProxyItem[] = []
|
|
176
|
+
for (const [, proxy] of wrapper.lookup) {
|
|
177
|
+
if (isLeafWithValue(proxy, vals)) result.push(proxy)
|
|
178
|
+
}
|
|
179
|
+
return result
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const selectedProxies = $derived.by(computeSelectedProxies)
|
|
183
|
+
|
|
184
|
+
// ─── Trigger action ───────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
$effect(() => {
|
|
187
|
+
if (!triggerRef || !selectRef || disabled) return
|
|
188
|
+
const t = new Trigger(triggerRef, selectRef, {
|
|
189
|
+
isOpen: () => isOpen,
|
|
190
|
+
onopen: () => {
|
|
191
|
+
isOpen = true
|
|
192
|
+
requestAnimationFrame(() => wrapper.first(null))
|
|
193
|
+
},
|
|
194
|
+
onclose: () => {
|
|
195
|
+
isOpen = false
|
|
196
|
+
},
|
|
197
|
+
onlast: () => requestAnimationFrame(() => wrapper.last(null))
|
|
198
|
+
})
|
|
199
|
+
return () => t.destroy()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// ─── Navigator on dropdown ────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
$effect(() => {
|
|
205
|
+
if (!isOpen || !dropdownRef) return
|
|
206
|
+
const dir = getComputedStyle(dropdownRef).direction || 'ltr'
|
|
207
|
+
const nav = new Navigator(dropdownRef, wrapper, { dir, containScroll: true })
|
|
208
|
+
return () => nav.destroy()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// DOM focus sync
|
|
212
|
+
$effect(() => {
|
|
213
|
+
const key = wrapper.focusedKey
|
|
214
|
+
if (!isOpen || !dropdownRef || !key) return
|
|
215
|
+
requestAnimationFrame(() => {
|
|
216
|
+
const target = dropdownRef?.querySelector(`[data-path="${key}"]`) as HTMLElement | null
|
|
217
|
+
if (target && target !== document.activeElement) {
|
|
218
|
+
target.focus()
|
|
219
|
+
target.scrollIntoView?.({ block: 'nearest' })
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
const groupDividers = $derived.by(() => {
|
|
227
|
+
const set = new SvelteSet<string>()
|
|
228
|
+
let foundFirst = false
|
|
229
|
+
for (const node of wrapper.flatView) {
|
|
230
|
+
if (node.hasChildren) {
|
|
231
|
+
if (foundFirst) set.add(node.key)
|
|
232
|
+
foundFirst = true
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return set
|
|
236
|
+
})
|
|
237
|
+
</script>
|
|
238
|
+
|
|
239
|
+
{#snippet defaultOptionContent(proxy: ProxyItem)}
|
|
240
|
+
<ItemContent {proxy} />
|
|
241
|
+
{/snippet}
|
|
242
|
+
|
|
243
|
+
{#snippet defaultGroupContent(proxy: ProxyItem)}
|
|
244
|
+
{#if proxy.get('icon')}
|
|
245
|
+
<span data-select-group-icon class={proxy.get('icon')} aria-hidden="true"></span>
|
|
246
|
+
{/if}
|
|
247
|
+
<span>{proxy.label}</span>
|
|
248
|
+
{/snippet}
|
|
249
|
+
|
|
250
|
+
<div
|
|
251
|
+
bind:this={selectRef}
|
|
252
|
+
data-select
|
|
253
|
+
data-multiselect
|
|
254
|
+
data-open={isOpen || undefined}
|
|
255
|
+
data-size={size}
|
|
256
|
+
data-disabled={disabled || undefined}
|
|
257
|
+
data-align={align}
|
|
258
|
+
data-direction={direction}
|
|
259
|
+
class={className || undefined}
|
|
260
|
+
>
|
|
261
|
+
<button
|
|
262
|
+
bind:this={triggerRef}
|
|
263
|
+
type="button"
|
|
264
|
+
data-select-trigger
|
|
265
|
+
{disabled}
|
|
266
|
+
aria-haspopup="listbox"
|
|
267
|
+
aria-expanded={isOpen}
|
|
268
|
+
>
|
|
269
|
+
<span data-select-value>
|
|
270
|
+
{#if selectedProxies.length > 0}
|
|
271
|
+
{#if selectedProxies.length <= maxDisplay}
|
|
272
|
+
<span data-select-tags>
|
|
273
|
+
{#each selectedProxies as proxy (proxy.value)}
|
|
274
|
+
<span data-select-tag>
|
|
275
|
+
<span data-select-tag-text>{proxy.label}</span>
|
|
276
|
+
<span
|
|
277
|
+
role="button"
|
|
278
|
+
tabindex="0"
|
|
279
|
+
data-select-tag-remove
|
|
280
|
+
aria-label="Remove {proxy.label}"
|
|
281
|
+
onclick={(e) => {
|
|
282
|
+
e.stopPropagation()
|
|
283
|
+
removeTag(proxy.value)
|
|
284
|
+
}}
|
|
285
|
+
onkeydown={(e) => {
|
|
286
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
287
|
+
e.preventDefault()
|
|
288
|
+
e.stopPropagation()
|
|
289
|
+
removeTag(proxy.value)
|
|
290
|
+
}
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
<span class={icons.remove} aria-hidden="true"></span>
|
|
294
|
+
</span>
|
|
295
|
+
</span>
|
|
296
|
+
{/each}
|
|
297
|
+
</span>
|
|
298
|
+
{:else}
|
|
299
|
+
<span data-select-count>{selectedProxies.length} selected</span>
|
|
300
|
+
{/if}
|
|
301
|
+
{:else}
|
|
302
|
+
<span data-select-placeholder>{placeholder}</span>
|
|
303
|
+
{/if}
|
|
304
|
+
</span>
|
|
305
|
+
<span data-select-arrow class={icons.opened} aria-hidden="true"></span>
|
|
306
|
+
</button>
|
|
307
|
+
|
|
308
|
+
{#if isOpen}
|
|
309
|
+
<div
|
|
310
|
+
bind:this={dropdownRef}
|
|
311
|
+
data-select-dropdown
|
|
312
|
+
role="listbox"
|
|
313
|
+
aria-multiselectable="true"
|
|
314
|
+
aria-orientation="vertical"
|
|
315
|
+
>
|
|
316
|
+
{#each wrapper.flatView as node (node.key)}
|
|
317
|
+
{@const proxy = node.proxy}
|
|
318
|
+
{@const sel = !node.hasChildren && isItemSelected(proxy.value)}
|
|
319
|
+
{@const content = resolveSnippet(
|
|
320
|
+
snippets as Record<string, unknown>,
|
|
321
|
+
proxy,
|
|
322
|
+
node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET
|
|
323
|
+
)}
|
|
324
|
+
|
|
325
|
+
{#if node.type === 'separator'}
|
|
326
|
+
<hr data-select-separator />
|
|
327
|
+
{:else if node.hasChildren}
|
|
328
|
+
{#if groupDividers.has(node.key)}
|
|
329
|
+
<div data-select-divider></div>
|
|
330
|
+
{/if}
|
|
331
|
+
<div data-select-group-label role="presentation">
|
|
332
|
+
{#if content}
|
|
333
|
+
{@render content(proxy)}
|
|
334
|
+
{:else}
|
|
335
|
+
{@render defaultGroupContent(proxy)}
|
|
336
|
+
{/if}
|
|
337
|
+
</div>
|
|
338
|
+
{:else}
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
data-select-option
|
|
342
|
+
data-path={node.key}
|
|
343
|
+
data-level={node.level}
|
|
344
|
+
data-selected={sel || undefined}
|
|
345
|
+
data-disabled={proxy.disabled || undefined}
|
|
346
|
+
role="option"
|
|
347
|
+
aria-selected={sel}
|
|
348
|
+
aria-label={proxy.label}
|
|
349
|
+
disabled={proxy.disabled || disabled}
|
|
350
|
+
tabindex="-1"
|
|
351
|
+
>
|
|
352
|
+
<span data-select-checkbox data-checked={sel || undefined}>
|
|
353
|
+
{#if sel}
|
|
354
|
+
<span class={icons.checked} aria-hidden="true"></span>
|
|
355
|
+
{/if}
|
|
356
|
+
</span>
|
|
357
|
+
{#if content}
|
|
358
|
+
{@render content(proxy)}
|
|
359
|
+
{:else}
|
|
360
|
+
{@render defaultOptionContent(proxy)}
|
|
361
|
+
{/if}
|
|
362
|
+
</button>
|
|
363
|
+
{/if}
|
|
364
|
+
{/each}
|
|
365
|
+
</div>
|
|
366
|
+
{/if}
|
|
367
|
+
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
|
|
4
|
+
interface NavContentProps {
|
|
5
|
+
/** Layout orientation */
|
|
6
|
+
orientation?: 'horizontal' | 'vertical'
|
|
7
|
+
/** Nav panel width (horizontal) or height (vertical) */
|
|
8
|
+
navSize?: string
|
|
9
|
+
/** Collapse nav on small screens */
|
|
10
|
+
collapsible?: boolean
|
|
11
|
+
/** Nav panel snippet */
|
|
12
|
+
nav: Snippet
|
|
13
|
+
/** Content panel snippet */
|
|
14
|
+
content: Snippet
|
|
15
|
+
/** Additional CSS class */
|
|
16
|
+
class?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
orientation = 'horizontal',
|
|
21
|
+
navSize = '280px',
|
|
22
|
+
collapsible = true,
|
|
23
|
+
nav,
|
|
24
|
+
content,
|
|
25
|
+
class: className = ''
|
|
26
|
+
}: NavContentProps = $props()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div
|
|
30
|
+
data-nav-content
|
|
31
|
+
data-orientation={orientation}
|
|
32
|
+
data-collapsible={collapsible || undefined}
|
|
33
|
+
class={className || undefined}
|
|
34
|
+
style:--nav-size={navSize}
|
|
35
|
+
>
|
|
36
|
+
<div data-nav-content-nav>
|
|
37
|
+
{@render nav()}
|
|
38
|
+
</div>
|
|
39
|
+
<div data-nav-content-main>
|
|
40
|
+
{@render content()}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|