picocrank 1.25.2 → 1.27.0
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/eslint.config.js +75 -0
- package/package.json +17 -4
- package/router.js +74 -0
- package/styles/dark-theme.css +20 -11
- package/styles/picocrank-extensions.css +123 -30
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +10 -24
- package/supplemental-themes/dracula-alucard/theme.css +10 -24
- package/supplemental-themes/gruvbox-dark-light/theme.css +10 -24
- package/supplemental-themes/waffles/theme.css +2 -16
- package/vite.config.js +5 -0
- package/vue/components/Breadcrumbs.vue +192 -45
- package/vue/components/Calendar.vue +162 -145
- package/vue/components/DangerZone.vue +186 -0
- package/vue/components/FormField.spec.js +350 -0
- package/vue/components/FormField.vue +236 -32
- package/vue/components/FormLayout.spec.js +56 -0
- package/vue/components/Header.vue +22 -6
- package/vue/components/Login.vue +152 -41
- package/vue/components/NavigationGrid.vue +26 -180
- package/vue/components/NotificationBlock.vue +120 -0
- package/vue/components/NotificationPopup.vue +44 -41
- package/vue/components/Pagination.vue +5 -4
- package/vue/components/ReadOnlyTextArea.vue +1 -1
- package/vue/components/SidebarNavLink.vue +3 -3
- package/vue/components/Table.vue +107 -79
- package/vue/components/TableColumnOptionsPopover.vue +9 -0
- package/vue/components/Tabs.spec.js +101 -0
- package/vue/components/Tabs.vue +455 -81
- package/vue/components/TopBar.vue +2 -2
- package/vue/components/form-validation.spec.js +126 -0
- package/vue/composables/tableColumnPriorities.js +33 -0
- package/vue/composables/tableFilters.js +1 -1
- package/vue/data/navSectionHubs.js +196 -0
- package/vue/data/routeBreadcrumbs.js +90 -0
- package/vue/examples/Admin.vue +12 -12
- package/vue/examples/App.vue +27 -36
- package/vue/examples/CalendarExample.vue +32 -22
- package/vue/examples/ExampleHeaderThemeSwitcher.vue +1 -1
- package/vue/examples/FormExample.vue +76 -48
- package/vue/examples/Login.vue +5 -3
- package/vue/examples/LoginExample.vue +99 -15
- package/vue/examples/NavSectionHub.vue +36 -0
- package/vue/examples/PageStructure.vue +15 -12
- package/vue/examples/StatusExample.vue +248 -129
- package/vue/examples/TableExample.vue +72 -71
- package/vue/examples/TabsExample.vue +236 -257
- package/vue/examples/ThemePreview.vue +21 -23
- package/vue/examples/ViewItem.vue +32 -5
- package/vue/examples/Welcome.vue +3 -40
- package/vue/test/formTestUtils.js +279 -0
- package/vue/test/setup.js +5 -0
package/vue/components/Tabs.vue
CHANGED
|
@@ -1,112 +1,361 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="tabs-container"
|
|
4
|
+
:class="{
|
|
5
|
+
'tabs-vertical': orientation === 'vertical',
|
|
6
|
+
'tabs-flush': !padding,
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
9
|
+
<div class="tabs-header">
|
|
10
|
+
<div
|
|
11
|
+
class="tabs-header-scroll"
|
|
12
|
+
role="tablist"
|
|
13
|
+
:aria-orientation="orientation"
|
|
14
|
+
:aria-labelledby="tablistLabelledBy || undefined"
|
|
15
|
+
@keydown="onTabListKeydown"
|
|
13
16
|
>
|
|
14
|
-
<
|
|
15
|
-
v-
|
|
16
|
-
:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
<button
|
|
18
|
+
v-for="(tab, index) in tabs"
|
|
19
|
+
:key="getTabKey(tab, index)"
|
|
20
|
+
:ref="(el) => setTabRef(getTabKey(tab, index), el)"
|
|
21
|
+
:class="['tab-button', { active: isTabSelected(tab, index), disabled: isTabDisabled(tab) }]"
|
|
22
|
+
:disabled="isTabDisabled(tab)"
|
|
23
|
+
:aria-selected="isTabSelected(tab, index) ? 'true' : 'false'"
|
|
24
|
+
:aria-disabled="isTabDisabled(tab) ? 'true' : undefined"
|
|
25
|
+
:aria-controls="`tab-panel-${getTabKey(tab, index)}`"
|
|
26
|
+
:id="`tab-${getTabKey(tab, index)}`"
|
|
27
|
+
:tabindex="tabTabIndex(tab, index)"
|
|
28
|
+
:title="tab.disabledTitle || undefined"
|
|
29
|
+
role="tab"
|
|
30
|
+
type="button"
|
|
31
|
+
@click="onTabClick(getTabKey(tab, index))"
|
|
32
|
+
>
|
|
33
|
+
<HugeiconsIcon
|
|
34
|
+
v-if="tab.icon"
|
|
35
|
+
:icon="tab.icon"
|
|
36
|
+
width="1em"
|
|
37
|
+
height="1em"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
/>
|
|
40
|
+
<span>{{ tab.label }}</span>
|
|
41
|
+
<slot :name="`tab-${getTabKey(tab, index)}-badge`" />
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
23
44
|
</div>
|
|
24
45
|
<div class="tabs-content">
|
|
25
46
|
<div
|
|
26
47
|
v-for="(tab, index) in tabs"
|
|
27
|
-
:key="tab
|
|
28
|
-
:id="`tab-panel-${tab
|
|
29
|
-
:class="['tab-panel', { active:
|
|
48
|
+
:key="`panel-${getTabKey(tab, index)}`"
|
|
49
|
+
:id="`tab-panel-${getTabKey(tab, index)}`"
|
|
50
|
+
:class="['tab-panel', { active: isTabSelected(tab, index) }]"
|
|
30
51
|
role="tabpanel"
|
|
31
|
-
:aria-labelledby="`tab-${tab
|
|
32
|
-
|
|
52
|
+
:aria-labelledby="`tab-${getTabKey(tab, index)}`"
|
|
53
|
+
:hidden="!isTabSelected(tab, index)"
|
|
54
|
+
:tabindex="isTabSelected(tab, index) ? 0 : -1"
|
|
33
55
|
>
|
|
34
|
-
<slot :name="`tab-${tab
|
|
56
|
+
<slot :name="`tab-${getTabKey(tab, index)}`" :tab="tab" />
|
|
35
57
|
</div>
|
|
36
58
|
</div>
|
|
37
59
|
</div>
|
|
38
60
|
</template>
|
|
39
61
|
|
|
40
62
|
<script setup>
|
|
41
|
-
import {
|
|
42
|
-
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
63
|
+
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
64
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
43
65
|
|
|
44
66
|
const props = defineProps({
|
|
45
67
|
tabs: {
|
|
46
68
|
type: Array,
|
|
47
69
|
required: true,
|
|
48
70
|
validator: (tabs) => {
|
|
49
|
-
return tabs.every(tab =>
|
|
50
|
-
typeof tab === 'object'
|
|
51
|
-
(tab.id !== undefined || true)
|
|
52
|
-
typeof tab.label === 'string'
|
|
53
|
-
)
|
|
54
|
-
}
|
|
71
|
+
return tabs.every(tab =>
|
|
72
|
+
typeof tab === 'object'
|
|
73
|
+
&& (tab.id !== undefined || true)
|
|
74
|
+
&& typeof tab.label === 'string',
|
|
75
|
+
)
|
|
76
|
+
},
|
|
55
77
|
},
|
|
56
78
|
defaultTab: {
|
|
57
79
|
type: [String, Number],
|
|
58
|
-
default: null
|
|
80
|
+
default: null,
|
|
81
|
+
},
|
|
82
|
+
/** APG activation: `auto` selects on focus; `manual` requires Space/Enter. */
|
|
83
|
+
activation: {
|
|
84
|
+
type: String,
|
|
85
|
+
default: 'auto',
|
|
86
|
+
validator: (value) => ['auto', 'manual'].includes(value),
|
|
87
|
+
},
|
|
88
|
+
/** Optional id of an element that labels the tablist. */
|
|
89
|
+
tablistLabelledBy: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: '',
|
|
92
|
+
},
|
|
93
|
+
/** Tab strip layout: `horizontal` (default) or `vertical`. */
|
|
94
|
+
orientation: {
|
|
95
|
+
type: String,
|
|
96
|
+
default: 'horizontal',
|
|
97
|
+
validator: (value) => ['horizontal', 'vertical'].includes(value),
|
|
98
|
+
},
|
|
99
|
+
/** When true, panels use full inset padding; default false keeps dividers flush. */
|
|
100
|
+
padding: {
|
|
101
|
+
type: Boolean,
|
|
102
|
+
default: false,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const emit = defineEmits(['tab-change'])
|
|
107
|
+
|
|
108
|
+
const tabRefs = new Map()
|
|
109
|
+
|
|
110
|
+
const activeTabId = ref(null)
|
|
111
|
+
const focusedTabId = ref(null)
|
|
112
|
+
|
|
113
|
+
function isTabDisabled(tab) {
|
|
114
|
+
return tab.disabled === true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getFirstEnabledIndex() {
|
|
118
|
+
return props.tabs.findIndex((tab) => !isTabDisabled(tab))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getLastEnabledIndex() {
|
|
122
|
+
for (let index = props.tabs.length - 1; index >= 0; index -= 1) {
|
|
123
|
+
if (!isTabDisabled(props.tabs[index])) {
|
|
124
|
+
return index
|
|
125
|
+
}
|
|
59
126
|
}
|
|
60
|
-
|
|
127
|
+
return -1
|
|
128
|
+
}
|
|
61
129
|
|
|
62
|
-
|
|
130
|
+
function getFirstEnabledTabKey() {
|
|
131
|
+
const index = getFirstEnabledIndex()
|
|
132
|
+
if (index < 0) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
return getTabKey(props.tabs[index], index)
|
|
136
|
+
}
|
|
63
137
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
138
|
+
function resolveSelectableTabId(tabKey) {
|
|
139
|
+
if (tabKey !== null && tabKey !== undefined) {
|
|
140
|
+
const index = props.tabs.findIndex((tab, i) => getTabKey(tab, i) === tabKey)
|
|
141
|
+
if (index >= 0 && !isTabDisabled(props.tabs[index])) {
|
|
142
|
+
return tabKey
|
|
143
|
+
}
|
|
68
144
|
}
|
|
69
|
-
|
|
70
|
-
|
|
145
|
+
return getFirstEnabledTabKey()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function tabTabIndex(tab, index) {
|
|
149
|
+
if (isTabDisabled(tab)) {
|
|
150
|
+
return -1
|
|
71
151
|
}
|
|
72
|
-
return
|
|
152
|
+
return isTabFocused(tab, index) ? 0 : -1
|
|
73
153
|
}
|
|
74
154
|
|
|
75
|
-
|
|
155
|
+
function findNextEnabledIndex(fromIndex, step) {
|
|
156
|
+
const length = props.tabs.length
|
|
157
|
+
if (length === 0) {
|
|
158
|
+
return fromIndex
|
|
159
|
+
}
|
|
76
160
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
161
|
+
for (let offset = 1; offset <= length; offset += 1) {
|
|
162
|
+
const index = (fromIndex + step * offset + length * 100) % length
|
|
163
|
+
if (!isTabDisabled(props.tabs[index])) {
|
|
164
|
+
return index
|
|
165
|
+
}
|
|
82
166
|
}
|
|
167
|
+
|
|
168
|
+
return fromIndex
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getTabKey(tab, index) {
|
|
172
|
+
return tab.id ?? index
|
|
83
173
|
}
|
|
84
174
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
175
|
+
function setTabRef(tabKey, el) {
|
|
176
|
+
if (el) {
|
|
177
|
+
tabRefs.set(tabKey, el)
|
|
178
|
+
return
|
|
89
179
|
}
|
|
180
|
+
tabRefs.delete(tabKey)
|
|
90
181
|
}
|
|
91
182
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
183
|
+
function tabIndexForKey(tabKey) {
|
|
184
|
+
return props.tabs.findIndex((tab, index) => getTabKey(tab, index) === tabKey)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isTabSelected(tab, index) {
|
|
188
|
+
return activeTabId.value === getTabKey(tab, index)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isTabFocused(tab, index) {
|
|
192
|
+
return focusedTabId.value === getTabKey(tab, index)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function focusTab(tabKey) {
|
|
196
|
+
tabRefs.get(tabKey)?.focus()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function selectTab(tabKey, { emitChange = true } = {}) {
|
|
200
|
+
const index = tabIndexForKey(tabKey)
|
|
201
|
+
if (index >= 0 && isTabDisabled(props.tabs[index])) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (activeTabId.value !== tabKey) {
|
|
206
|
+
activeTabId.value = tabKey
|
|
207
|
+
if (emitChange) {
|
|
208
|
+
const activeTab = index >= 0 ? props.tabs[index] : null
|
|
209
|
+
emit('tab-change', activeTab, tabKey)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
focusedTabId.value = tabKey
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onTabClick(tabKey) {
|
|
216
|
+
selectTab(tabKey)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function moveFocusToIndex(index, { activate = props.activation === 'auto' } = {}) {
|
|
220
|
+
if (props.tabs.length === 0) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (index < 0 || index >= props.tabs.length || isTabDisabled(props.tabs[index])) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const tabKey = getTabKey(props.tabs[index], index)
|
|
229
|
+
focusedTabId.value = tabKey
|
|
230
|
+
|
|
231
|
+
if (activate) {
|
|
232
|
+
selectTab(tabKey)
|
|
96
233
|
}
|
|
97
|
-
});
|
|
98
234
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
235
|
+
nextTick(() => focusTab(tabKey))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function moveFocusByStep(fromIndex, step, { activate = props.activation === 'auto' } = {}) {
|
|
239
|
+
const nextIndex = findNextEnabledIndex(fromIndex, step)
|
|
240
|
+
if (nextIndex === fromIndex && isTabDisabled(props.tabs[fromIndex])) {
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
moveFocusToIndex(nextIndex, { activate })
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tabKeyFromElement(tabEl) {
|
|
247
|
+
const suffix = tabEl.id.slice('tab-'.length)
|
|
248
|
+
const index = props.tabs.findIndex((tab, i) => String(getTabKey(tab, i)) === suffix)
|
|
249
|
+
if (index >= 0) {
|
|
250
|
+
return getTabKey(props.tabs[index], index)
|
|
103
251
|
}
|
|
104
|
-
|
|
252
|
+
return suffix
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function onTabListKeydown(event) {
|
|
256
|
+
const tabEl = event.target?.closest?.('[role="tab"]')
|
|
257
|
+
if (!tabEl || !tabEl.id?.startsWith('tab-')) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const tabKey = tabKeyFromElement(tabEl)
|
|
262
|
+
const index = tabIndexForKey(tabKey)
|
|
263
|
+
if (index < 0) {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const isVertical = props.orientation === 'vertical'
|
|
268
|
+
|
|
269
|
+
switch (event.key) {
|
|
270
|
+
case 'ArrowRight':
|
|
271
|
+
if (!isVertical) {
|
|
272
|
+
event.preventDefault()
|
|
273
|
+
moveFocusByStep(index, 1)
|
|
274
|
+
}
|
|
275
|
+
break
|
|
276
|
+
case 'ArrowLeft':
|
|
277
|
+
if (!isVertical) {
|
|
278
|
+
event.preventDefault()
|
|
279
|
+
moveFocusByStep(index, -1)
|
|
280
|
+
}
|
|
281
|
+
break
|
|
282
|
+
case 'ArrowDown':
|
|
283
|
+
if (isVertical) {
|
|
284
|
+
event.preventDefault()
|
|
285
|
+
moveFocusByStep(index, 1)
|
|
286
|
+
}
|
|
287
|
+
break
|
|
288
|
+
case 'ArrowUp':
|
|
289
|
+
if (isVertical) {
|
|
290
|
+
event.preventDefault()
|
|
291
|
+
moveFocusByStep(index, -1)
|
|
292
|
+
}
|
|
293
|
+
break
|
|
294
|
+
case 'Home':
|
|
295
|
+
event.preventDefault()
|
|
296
|
+
moveFocusToIndex(getFirstEnabledIndex())
|
|
297
|
+
break
|
|
298
|
+
case 'End':
|
|
299
|
+
event.preventDefault()
|
|
300
|
+
moveFocusToIndex(getLastEnabledIndex())
|
|
301
|
+
break
|
|
302
|
+
case ' ':
|
|
303
|
+
case 'Enter':
|
|
304
|
+
if (props.activation === 'manual') {
|
|
305
|
+
event.preventDefault()
|
|
306
|
+
selectTab(tabKey)
|
|
307
|
+
}
|
|
308
|
+
break
|
|
309
|
+
default:
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function setActiveTab(tabKey) {
|
|
315
|
+
const index = tabIndexForKey(tabKey)
|
|
316
|
+
if (index >= 0) {
|
|
317
|
+
selectTab(tabKey)
|
|
318
|
+
nextTick(() => focusTab(tabKey))
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function syncTabIdsAfterListChange() {
|
|
323
|
+
if (props.tabs.length === 0) {
|
|
324
|
+
activeTabId.value = null
|
|
325
|
+
focusedTabId.value = null
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const activeIndex = tabIndexForKey(activeTabId.value)
|
|
330
|
+
const activeIsValid = activeIndex >= 0 && !isTabDisabled(props.tabs[activeIndex])
|
|
331
|
+
if (!activeIsValid) {
|
|
332
|
+
const firstKey = getFirstEnabledTabKey()
|
|
333
|
+
activeTabId.value = firstKey
|
|
334
|
+
focusedTabId.value = firstKey
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const focusedIndex = tabIndexForKey(focusedTabId.value)
|
|
339
|
+
const focusedIsValid = focusedIndex >= 0 && !isTabDisabled(props.tabs[focusedIndex])
|
|
340
|
+
if (!focusedIsValid) {
|
|
341
|
+
focusedTabId.value = activeTabId.value
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
onMounted(() => {
|
|
346
|
+
activeTabId.value = resolveSelectableTabId(props.defaultTab)
|
|
347
|
+
focusedTabId.value = activeTabId.value
|
|
348
|
+
syncTabIdsAfterListChange()
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
watch(() => props.tabs, () => {
|
|
352
|
+
syncTabIdsAfterListChange()
|
|
353
|
+
}, { deep: true })
|
|
105
354
|
|
|
106
355
|
defineExpose({
|
|
107
356
|
setActiveTab,
|
|
108
|
-
activeTabId
|
|
109
|
-
})
|
|
357
|
+
activeTabId,
|
|
358
|
+
})
|
|
110
359
|
</script>
|
|
111
360
|
|
|
112
361
|
<style scoped>
|
|
@@ -114,27 +363,55 @@ defineExpose({
|
|
|
114
363
|
width: 100%;
|
|
115
364
|
}
|
|
116
365
|
|
|
366
|
+
.tabs-container.tabs-vertical {
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: stretch;
|
|
369
|
+
gap: 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
117
372
|
.tabs-header {
|
|
373
|
+
border-bottom: 1px solid var(--tab-border);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.tabs-vertical .tabs-header {
|
|
377
|
+
flex-shrink: 0;
|
|
378
|
+
border-bottom: none;
|
|
379
|
+
border-right: 1px solid var(--tab-border);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.tabs-header-scroll {
|
|
118
383
|
display: flex;
|
|
119
|
-
border-bottom: 2px solid var(--tab-border);
|
|
120
384
|
gap: 0.25rem;
|
|
121
385
|
overflow-x: auto;
|
|
122
386
|
scrollbar-width: thin;
|
|
123
387
|
}
|
|
124
388
|
|
|
125
|
-
.tabs-header::-webkit-scrollbar {
|
|
389
|
+
.tabs-header-scroll::-webkit-scrollbar {
|
|
126
390
|
height: 4px;
|
|
127
391
|
}
|
|
128
392
|
|
|
129
|
-
.tabs-header::-webkit-scrollbar-track {
|
|
393
|
+
.tabs-header-scroll::-webkit-scrollbar-track {
|
|
130
394
|
background: transparent;
|
|
131
395
|
}
|
|
132
396
|
|
|
133
|
-
.tabs-header::-webkit-scrollbar-thumb {
|
|
397
|
+
.tabs-header-scroll::-webkit-scrollbar-thumb {
|
|
134
398
|
background: var(--tab-border);
|
|
135
399
|
border-radius: 2px;
|
|
136
400
|
}
|
|
137
401
|
|
|
402
|
+
.tabs-vertical .tabs-header-scroll {
|
|
403
|
+
flex-direction: column;
|
|
404
|
+
overflow-x: hidden;
|
|
405
|
+
overflow-y: auto;
|
|
406
|
+
min-width: 10rem;
|
|
407
|
+
max-height: 24rem;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.tabs-vertical .tabs-header-scroll::-webkit-scrollbar {
|
|
411
|
+
width: 4px;
|
|
412
|
+
height: auto;
|
|
413
|
+
}
|
|
414
|
+
|
|
138
415
|
.tab-button {
|
|
139
416
|
display: flex;
|
|
140
417
|
align-items: center;
|
|
@@ -142,33 +419,108 @@ defineExpose({
|
|
|
142
419
|
padding: 0.75rem 1.25rem;
|
|
143
420
|
background: transparent;
|
|
144
421
|
border: none;
|
|
145
|
-
border-bottom:
|
|
146
|
-
border-radius: 0;
|
|
422
|
+
border-bottom: 1px solid transparent;
|
|
423
|
+
border-radius: 0.4em 0.4em 0 0;
|
|
147
424
|
cursor: pointer;
|
|
148
425
|
font-size: 1em;
|
|
149
426
|
color: var(--tab-fg);
|
|
150
|
-
transition: all 0.2s ease;
|
|
151
427
|
white-space: nowrap;
|
|
152
428
|
position: relative;
|
|
153
429
|
}
|
|
154
430
|
|
|
155
|
-
.tab-button:hover {
|
|
431
|
+
.tab-button:hover:not(.active):not(:disabled) {
|
|
156
432
|
color: var(--tab-hover-fg);
|
|
157
433
|
background-color: var(--tab-hover-bg);
|
|
158
434
|
}
|
|
159
435
|
|
|
160
436
|
.tab-button.active {
|
|
437
|
+
background-color: var(--tab-active-bg);
|
|
161
438
|
color: var(--tab-active-fg);
|
|
162
|
-
border-bottom-color:
|
|
163
|
-
font-weight: bold;
|
|
439
|
+
border-bottom-color: transparent;
|
|
164
440
|
}
|
|
165
441
|
|
|
166
|
-
.tab-button:
|
|
167
|
-
|
|
168
|
-
|
|
442
|
+
.tab-button:disabled,
|
|
443
|
+
.tab-button.disabled {
|
|
444
|
+
cursor: not-allowed;
|
|
445
|
+
color: var(--disabled-text-color);
|
|
446
|
+
opacity: 0.72;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.tab-button:disabled:hover,
|
|
450
|
+
.tab-button.disabled:hover {
|
|
451
|
+
color: var(--disabled-text-color);
|
|
452
|
+
background-color: transparent;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.tab-button:disabled.active,
|
|
456
|
+
.tab-button.disabled.active {
|
|
457
|
+
background-color: var(--tab-active-bg);
|
|
458
|
+
color: var(--disabled-text-color);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.tabs-vertical .tab-button {
|
|
462
|
+
width: 100%;
|
|
463
|
+
justify-content: flex-start;
|
|
464
|
+
border-bottom: none;
|
|
465
|
+
border-right: 1px solid transparent;
|
|
466
|
+
border-radius: 0.4em 0 0 0.4em;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.tabs-vertical .tab-button.active {
|
|
470
|
+
border-right-color: transparent;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.tabs-vertical .tabs-content {
|
|
474
|
+
flex: 1;
|
|
475
|
+
min-width: 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.tabs-flush .tab-panel {
|
|
479
|
+
padding: 0;
|
|
480
|
+
padding-top: 1.5rem;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.tabs-vertical.tabs-flush .tab-panel {
|
|
484
|
+
padding-top: 0;
|
|
485
|
+
padding-left: 0.75rem;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.tabs-vertical.tabs-flush .tabs-header {
|
|
489
|
+
display: flex;
|
|
490
|
+
flex-direction: column;
|
|
491
|
+
align-self: stretch;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.tabs-vertical.tabs-flush .tabs-header-scroll {
|
|
495
|
+
flex: 1;
|
|
496
|
+
max-height: none;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.tabs-vertical.tabs-flush .tab-button {
|
|
169
500
|
border-radius: 0;
|
|
170
501
|
}
|
|
171
502
|
|
|
503
|
+
.tab-button.active:hover,
|
|
504
|
+
.tab-button.active:focus-visible {
|
|
505
|
+
background-color: var(--tab-active-hover-bg);
|
|
506
|
+
color: var(--tab-active-fg);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.tab-button:focus {
|
|
510
|
+
outline: none;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.tab-button:focus-visible {
|
|
514
|
+
outline: none;
|
|
515
|
+
box-shadow: inset 0 0 0 2px var(--focus-outline-color);
|
|
516
|
+
z-index: 1;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.tab-button:hover:not(.active):not(:disabled):focus-visible {
|
|
520
|
+
color: var(--tab-hover-fg);
|
|
521
|
+
background-color: var(--tab-hover-bg);
|
|
522
|
+
}
|
|
523
|
+
|
|
172
524
|
.tabs-content {
|
|
173
525
|
width: 100%;
|
|
174
526
|
min-height: 200px;
|
|
@@ -177,11 +529,23 @@ defineExpose({
|
|
|
177
529
|
.tab-panel {
|
|
178
530
|
padding-block: 1.5rem;
|
|
179
531
|
padding-inline: 0.75rem;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.tab-panel:focus {
|
|
535
|
+
outline: none;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.tab-panel:focus-visible {
|
|
539
|
+
outline: 2px solid var(--focus-outline-color);
|
|
540
|
+
outline-offset: 2px;
|
|
541
|
+
border-radius: 0.4em;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.tab-panel[hidden] {
|
|
180
545
|
display: none;
|
|
181
546
|
}
|
|
182
547
|
|
|
183
548
|
.tab-panel.active {
|
|
184
|
-
display: block;
|
|
185
549
|
animation: fadeIn 0.2s ease-in;
|
|
186
550
|
}
|
|
187
551
|
|
|
@@ -198,7 +562,7 @@ defineExpose({
|
|
|
198
562
|
|
|
199
563
|
/* Responsive */
|
|
200
564
|
@media (max-width: 768px) {
|
|
201
|
-
.tabs-header {
|
|
565
|
+
.tabs-header-scroll {
|
|
202
566
|
gap: 0;
|
|
203
567
|
}
|
|
204
568
|
|
|
@@ -211,5 +575,15 @@ defineExpose({
|
|
|
211
575
|
padding-block: 1rem;
|
|
212
576
|
padding-inline: 0.5rem;
|
|
213
577
|
}
|
|
578
|
+
|
|
579
|
+
.tabs-flush .tab-panel {
|
|
580
|
+
padding: 0;
|
|
581
|
+
padding-top: 1rem;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.tabs-vertical.tabs-flush .tab-panel {
|
|
585
|
+
padding-top: 0;
|
|
586
|
+
padding-left: 0.5rem;
|
|
587
|
+
}
|
|
214
588
|
}
|
|
215
589
|
</style>
|
|
@@ -97,7 +97,7 @@ function handleLinkClick(callback = null) {
|
|
|
97
97
|
padding: 0.5em 0.75em;
|
|
98
98
|
border-radius: 0.25em;
|
|
99
99
|
text-decoration: none;
|
|
100
|
-
color: var(--header-fg-color
|
|
100
|
+
color: var(--header-fg-color);
|
|
101
101
|
transition: background-color 0.15s ease;
|
|
102
102
|
white-space: nowrap;
|
|
103
103
|
border: 0;
|
|
@@ -124,7 +124,7 @@ function handleLinkClick(callback = null) {
|
|
|
124
124
|
|
|
125
125
|
html[data-theme="dark"] {
|
|
126
126
|
.topbar-link {
|
|
127
|
-
color: var(--header-fg-color
|
|
127
|
+
color: var(--header-fg-color);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
.separator {
|