vira 31.5.8 → 31.6.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/dist/elements/index.d.ts
CHANGED
|
@@ -19,4 +19,5 @@ export * from './vira-modal.element.js';
|
|
|
19
19
|
export * from './vira-overflow-switch.element.js';
|
|
20
20
|
export * from './vira-progress.element.js';
|
|
21
21
|
export * from './vira-select.element.js';
|
|
22
|
+
export * from './vira-tabs.element.js';
|
|
22
23
|
export * from './vira-tag.element.js';
|
package/dist/elements/index.js
CHANGED
|
@@ -19,4 +19,5 @@ export * from './vira-modal.element.js';
|
|
|
19
19
|
export * from './vira-overflow-switch.element.js';
|
|
20
20
|
export * from './vira-progress.element.js';
|
|
21
21
|
export * from './vira-select.element.js';
|
|
22
|
+
export * from './vira-tabs.element.js';
|
|
22
23
|
export * from './vira-tag.element.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type FullSpaRoute, type GenericTreePaths, type SpaRouter } from 'spa-router-vir';
|
|
3
|
+
import { type ViraIconSvg } from '../icons/icon-svg.js';
|
|
4
|
+
import { ViraColorVariant } from '../styles/form-variants.js';
|
|
5
|
+
import { type HorizontalAnchor, type PopUpOffset } from './pop-up/vira-pop-up-trigger.element.js';
|
|
6
|
+
/**
|
|
7
|
+
* Controls which edge of the tab the selection indicator bar appears on.
|
|
8
|
+
*
|
|
9
|
+
* @category Internal
|
|
10
|
+
*/
|
|
11
|
+
export declare enum ViraTabsBarDirection {
|
|
12
|
+
Top = "top",
|
|
13
|
+
Bottom = "bottom",
|
|
14
|
+
Left = "left",
|
|
15
|
+
Right = "right"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Controls whether tab icons render above/below or beside the label text.
|
|
19
|
+
*
|
|
20
|
+
* @category Internal
|
|
21
|
+
*/
|
|
22
|
+
export declare enum ViraTabsIconLayout {
|
|
23
|
+
/** Icon renders above (or below) the label. */
|
|
24
|
+
Vertical = "vertical",
|
|
25
|
+
/** Icon renders beside the label. */
|
|
26
|
+
Horizontal = "horizontal"
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A single tab entry for {@link ViraTabs}.
|
|
30
|
+
*
|
|
31
|
+
* @category Internal
|
|
32
|
+
*/
|
|
33
|
+
export type ViraTab = {
|
|
34
|
+
label: string;
|
|
35
|
+
paths: GenericTreePaths;
|
|
36
|
+
} & PartialWithUndefined<{
|
|
37
|
+
icon: Readonly<ViraIconSvg>;
|
|
38
|
+
isHidden: boolean;
|
|
39
|
+
isDisabled: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* A tab bar element that renders an array of tabs with an animated selection indicator.
|
|
43
|
+
*
|
|
44
|
+
* @category Elements
|
|
45
|
+
*/
|
|
46
|
+
export declare const ViraTabs: import("element-vir").DeclarativeElementDefinition<"vira-tabs", {
|
|
47
|
+
tabs: ReadonlyArray<Readonly<ViraTab>>;
|
|
48
|
+
router: Pick<SpaRouter<any, any, any>, "createRouteUrl" | "setRouteOnDirectNavigation">;
|
|
49
|
+
currentRoute: Readonly<FullSpaRoute>;
|
|
50
|
+
} & PartialWithUndefined<{
|
|
51
|
+
/**
|
|
52
|
+
* Which edge of the tab the selection bar appears on.
|
|
53
|
+
*
|
|
54
|
+
* @default ViraTabsBarDirection.Bottom
|
|
55
|
+
*/
|
|
56
|
+
barDirection: ViraTabsBarDirection;
|
|
57
|
+
/**
|
|
58
|
+
* Color variant for the tab selection indicator and active tab text.
|
|
59
|
+
*
|
|
60
|
+
* @default ViraColorVariant.Accent
|
|
61
|
+
*/
|
|
62
|
+
colorVariant: ViraColorVariant.Accent | ViraColorVariant.Plain;
|
|
63
|
+
/**
|
|
64
|
+
* Layout direction for icons relative to their label text.
|
|
65
|
+
*
|
|
66
|
+
* @default ViraTabsIconLayout.Vertical
|
|
67
|
+
*/
|
|
68
|
+
iconLayout: ViraTabsIconLayout;
|
|
69
|
+
/**
|
|
70
|
+
* Horizontal anchor for the dropdown menu. Only used when tabs overflow into a dropdown.
|
|
71
|
+
*
|
|
72
|
+
* @default HorizontalAnchor.Left
|
|
73
|
+
*/
|
|
74
|
+
menuHorizontalAnchor: HorizontalAnchor;
|
|
75
|
+
/** Whether the dropdown trigger is disabled. Only used when tabs overflow into a dropdown. */
|
|
76
|
+
menuIsDisabled: boolean;
|
|
77
|
+
/** Offset for the dropdown pop-up. Only used when tabs overflow into a dropdown. */
|
|
78
|
+
menuPopUpOffset: Readonly<PopUpOffset>;
|
|
79
|
+
}>, {}, {}, "vira-tabs-bar-top" | "vira-tabs-bar-bottom" | "vira-tabs-bar-left" | "vira-tabs-bar-right" | "vira-tabs-color-accent" | "vira-tabs-color-plain" | "vira-tabs-icon-layout-vertical" | "vira-tabs-icon-layout-horizontal", "vira-tabs-active-color" | "vira-tabs-active-hover-color" | "vira-tabs-inactive-color" | "vira-tabs-inactive-hover-color" | "vira-tabs-bar-thickness", readonly [], readonly []>;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { filterMap } from '@augment-vir/common';
|
|
3
|
+
import { classMap, css, html, nothing } from 'element-vir';
|
|
4
|
+
import { routeHasPaths, } from 'spa-router-vir';
|
|
5
|
+
import { createFocusStyles } from '../styles/focus.js';
|
|
6
|
+
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
7
|
+
import { ViraColorVariant } from '../styles/form-variants.js';
|
|
8
|
+
import { noNativeFormStyles, noUserSelect, viraDisabledStyles, viraTheme } from '../styles/index.js';
|
|
9
|
+
import { defineViraElement } from '../util/define-vira-element.js';
|
|
10
|
+
import { renderMenuItemEntries } from '../util/pop-up-helpers.js';
|
|
11
|
+
import { ViraMenuTrigger } from './pop-up/vira-menu-trigger.element.js';
|
|
12
|
+
import { ViraMenuCornerStyle } from './pop-up/vira-menu.element.js';
|
|
13
|
+
import { ViraButton } from './vira-button.element.js';
|
|
14
|
+
import { ViraIcon } from './vira-icon.element.js';
|
|
15
|
+
import { ViraLink } from './vira-link.element.js';
|
|
16
|
+
import { ViraOverflowSwitch } from './vira-overflow-switch.element.js';
|
|
17
|
+
/**
|
|
18
|
+
* Controls which edge of the tab the selection indicator bar appears on.
|
|
19
|
+
*
|
|
20
|
+
* @category Internal
|
|
21
|
+
*/
|
|
22
|
+
export var ViraTabsBarDirection;
|
|
23
|
+
(function (ViraTabsBarDirection) {
|
|
24
|
+
ViraTabsBarDirection["Top"] = "top";
|
|
25
|
+
ViraTabsBarDirection["Bottom"] = "bottom";
|
|
26
|
+
ViraTabsBarDirection["Left"] = "left";
|
|
27
|
+
ViraTabsBarDirection["Right"] = "right";
|
|
28
|
+
})(ViraTabsBarDirection || (ViraTabsBarDirection = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Controls whether tab icons render above/below or beside the label text.
|
|
31
|
+
*
|
|
32
|
+
* @category Internal
|
|
33
|
+
*/
|
|
34
|
+
export var ViraTabsIconLayout;
|
|
35
|
+
(function (ViraTabsIconLayout) {
|
|
36
|
+
/** Icon renders above (or below) the label. */
|
|
37
|
+
ViraTabsIconLayout["Vertical"] = "vertical";
|
|
38
|
+
/** Icon renders beside the label. */
|
|
39
|
+
ViraTabsIconLayout["Horizontal"] = "horizontal";
|
|
40
|
+
})(ViraTabsIconLayout || (ViraTabsIconLayout = {}));
|
|
41
|
+
/**
|
|
42
|
+
* A tab bar element that renders an array of tabs with an animated selection indicator.
|
|
43
|
+
*
|
|
44
|
+
* @category Elements
|
|
45
|
+
*/
|
|
46
|
+
export const ViraTabs = defineViraElement()({
|
|
47
|
+
tagName: 'vira-tabs',
|
|
48
|
+
hostClasses: {
|
|
49
|
+
'vira-tabs-bar-top': ({ inputs }) => inputs.barDirection === ViraTabsBarDirection.Top,
|
|
50
|
+
'vira-tabs-bar-bottom': ({ inputs }) => !inputs.barDirection || inputs.barDirection === ViraTabsBarDirection.Bottom,
|
|
51
|
+
'vira-tabs-bar-left': ({ inputs }) => inputs.barDirection === ViraTabsBarDirection.Left,
|
|
52
|
+
'vira-tabs-bar-right': ({ inputs }) => inputs.barDirection === ViraTabsBarDirection.Right,
|
|
53
|
+
'vira-tabs-color-accent': ({ inputs }) => !inputs.colorVariant || inputs.colorVariant === ViraColorVariant.Accent,
|
|
54
|
+
'vira-tabs-color-plain': ({ inputs }) => inputs.colorVariant === ViraColorVariant.Plain,
|
|
55
|
+
'vira-tabs-icon-layout-vertical': ({ inputs }) => !inputs.iconLayout || inputs.iconLayout === ViraTabsIconLayout.Vertical,
|
|
56
|
+
'vira-tabs-icon-layout-horizontal': ({ inputs }) => inputs.iconLayout === ViraTabsIconLayout.Horizontal,
|
|
57
|
+
},
|
|
58
|
+
cssVars: {
|
|
59
|
+
'vira-tabs-active-color': viraFormCssVars['vira-form-accent-primary-color'].value,
|
|
60
|
+
'vira-tabs-active-hover-color': viraFormCssVars['vira-form-accent-primary-hover-color'].value,
|
|
61
|
+
'vira-tabs-inactive-color': viraTheme.colors['vira-grey-foreground-header'].foreground.value,
|
|
62
|
+
'vira-tabs-inactive-hover-color': viraTheme.colors['vira-grey-foreground-non-body'].foreground.value,
|
|
63
|
+
'vira-tabs-bar-thickness': '3px',
|
|
64
|
+
},
|
|
65
|
+
styles: ({ hostClasses, cssVars }) => {
|
|
66
|
+
return css `
|
|
67
|
+
:host {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
${noUserSelect};
|
|
71
|
+
max-width: 100%;
|
|
72
|
+
max-height: 100%;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.tabs-container {
|
|
76
|
+
display: flex;
|
|
77
|
+
position: relative;
|
|
78
|
+
list-style: none;
|
|
79
|
+
margin: 0;
|
|
80
|
+
padding: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
${hostClasses['vira-tabs-bar-top'].selector},
|
|
84
|
+
${hostClasses['vira-tabs-bar-bottom'].selector} {
|
|
85
|
+
& .tabs-container {
|
|
86
|
+
flex-direction: row;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
${hostClasses['vira-tabs-bar-left'].selector},
|
|
91
|
+
${hostClasses['vira-tabs-bar-right'].selector} {
|
|
92
|
+
& .tabs-container {
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
li {
|
|
98
|
+
${noNativeFormStyles};
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
padding: 8px 16px;
|
|
104
|
+
position: relative;
|
|
105
|
+
color: ${cssVars['vira-tabs-inactive-color'].value};
|
|
106
|
+
font-size: ${viraFormCssVars['vira-form-medium-text-size'].value};
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
${createFocusStyles({
|
|
109
|
+
renderInside: true,
|
|
110
|
+
elementBorderSize: '0',
|
|
111
|
+
})}
|
|
112
|
+
|
|
113
|
+
&::after {
|
|
114
|
+
content: '';
|
|
115
|
+
position: absolute;
|
|
116
|
+
background-color: transparent;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&:hover {
|
|
120
|
+
color: ${cssVars['vira-tabs-inactive-hover-color'].value};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&.selected {
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
color: ${cssVars['vira-tabs-active-color'].value};
|
|
126
|
+
|
|
127
|
+
&::after {
|
|
128
|
+
background-color: ${cssVars['vira-tabs-active-color'].value};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&.disabled {
|
|
133
|
+
${viraDisabledStyles};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
${hostClasses['vira-tabs-bar-bottom'].selector} {
|
|
138
|
+
& li::after {
|
|
139
|
+
bottom: 0;
|
|
140
|
+
left: 0;
|
|
141
|
+
right: 0;
|
|
142
|
+
height: ${cssVars['vira-tabs-bar-thickness'].value};
|
|
143
|
+
border-radius: ${cssVars['vira-tabs-bar-thickness'].value}
|
|
144
|
+
${cssVars['vira-tabs-bar-thickness'].value} 0 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
${hostClasses['vira-tabs-bar-top'].selector} {
|
|
149
|
+
& li::after {
|
|
150
|
+
top: 0;
|
|
151
|
+
left: 0;
|
|
152
|
+
right: 0;
|
|
153
|
+
height: ${cssVars['vira-tabs-bar-thickness'].value};
|
|
154
|
+
border-radius: 0 0 ${cssVars['vira-tabs-bar-thickness'].value}
|
|
155
|
+
${cssVars['vira-tabs-bar-thickness'].value};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
${hostClasses['vira-tabs-bar-left'].selector} {
|
|
160
|
+
& li::after {
|
|
161
|
+
top: 0;
|
|
162
|
+
bottom: 0;
|
|
163
|
+
left: 0;
|
|
164
|
+
width: ${cssVars['vira-tabs-bar-thickness'].value};
|
|
165
|
+
border-radius: 0 ${cssVars['vira-tabs-bar-thickness'].value}
|
|
166
|
+
${cssVars['vira-tabs-bar-thickness'].value} 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
${hostClasses['vira-tabs-bar-right'].selector} {
|
|
171
|
+
& li::after {
|
|
172
|
+
top: 0;
|
|
173
|
+
bottom: 0;
|
|
174
|
+
right: 0;
|
|
175
|
+
width: ${cssVars['vira-tabs-bar-thickness'].value};
|
|
176
|
+
border-radius: ${cssVars['vira-tabs-bar-thickness'].value} 0 0
|
|
177
|
+
${cssVars['vira-tabs-bar-thickness'].value};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
${hostClasses['vira-tabs-color-plain'].selector} {
|
|
182
|
+
${cssVars['vira-tabs-active-color'].name}: ${viraTheme.colors['vira-grey-foreground-small-body'].foreground.value};
|
|
183
|
+
${cssVars['vira-tabs-active-hover-color'].name}: ${viraTheme.colors['vira-grey-foreground-body'].foreground.value};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.tab-content {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
${hostClasses['vira-tabs-icon-layout-vertical'].selector} {
|
|
193
|
+
& .tab-content {
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
gap: 4px;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
${hostClasses['vira-tabs-icon-layout-horizontal'].selector} {
|
|
200
|
+
& .tab-content {
|
|
201
|
+
flex-direction: row;
|
|
202
|
+
gap: 8px;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
${ViraOverflowSwitch} {
|
|
207
|
+
max-width: 100%;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
${ViraLink} {
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
},
|
|
215
|
+
render({ inputs }) {
|
|
216
|
+
const tabs = filterMap(inputs.tabs, (tab) => {
|
|
217
|
+
if (tab.isHidden) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const isSelected = routeHasPaths(inputs.currentRoute, tab.paths);
|
|
221
|
+
const iconTemplate = tab.icon
|
|
222
|
+
? html `
|
|
223
|
+
<${ViraIcon.assign({
|
|
224
|
+
icon: tab.icon,
|
|
225
|
+
})}></${ViraIcon}>
|
|
226
|
+
`
|
|
227
|
+
: nothing;
|
|
228
|
+
const isInert = isSelected || !!tab.isDisabled;
|
|
229
|
+
return html `
|
|
230
|
+
<li
|
|
231
|
+
class=${classMap({
|
|
232
|
+
selected: isSelected,
|
|
233
|
+
disabled: !!tab.isDisabled,
|
|
234
|
+
})}
|
|
235
|
+
role="presentation"
|
|
236
|
+
>
|
|
237
|
+
<${ViraLink.assign({
|
|
238
|
+
route: {
|
|
239
|
+
router: inputs.router,
|
|
240
|
+
route: {
|
|
241
|
+
paths: tab.paths.fullPaths,
|
|
242
|
+
},
|
|
243
|
+
scrollToTop: true,
|
|
244
|
+
},
|
|
245
|
+
disableLinkStyles: true,
|
|
246
|
+
attributePassthrough: {
|
|
247
|
+
a: {
|
|
248
|
+
role: 'tab',
|
|
249
|
+
'aria-selected': String(isSelected),
|
|
250
|
+
'aria-disabled': String(!!tab.isDisabled),
|
|
251
|
+
tabindex: isInert ? '-1' : undefined,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
})}>
|
|
255
|
+
<span class="tab-content">
|
|
256
|
+
${iconTemplate}
|
|
257
|
+
<span class="tab-label">${tab.label}</span>
|
|
258
|
+
</span>
|
|
259
|
+
</${ViraLink}>
|
|
260
|
+
</li>
|
|
261
|
+
`;
|
|
262
|
+
}, check.isTruthy);
|
|
263
|
+
const selectedTab = inputs.tabs.find((tab) => routeHasPaths(inputs.currentRoute, tab.paths));
|
|
264
|
+
const menuItems = renderMenuItemEntries(filterMap(inputs.tabs, (tab) => {
|
|
265
|
+
if (tab.isHidden) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
const isSelected = routeHasPaths(inputs.currentRoute, tab.paths);
|
|
269
|
+
return {
|
|
270
|
+
content: html `
|
|
271
|
+
<${ViraLink.assign({
|
|
272
|
+
route: {
|
|
273
|
+
router: inputs.router,
|
|
274
|
+
route: {
|
|
275
|
+
paths: tab.paths.fullPaths,
|
|
276
|
+
},
|
|
277
|
+
scrollToTop: true,
|
|
278
|
+
},
|
|
279
|
+
disableLinkStyles: true,
|
|
280
|
+
})}>
|
|
281
|
+
${tab.label}
|
|
282
|
+
</${ViraLink}>
|
|
283
|
+
`,
|
|
284
|
+
selected: isSelected,
|
|
285
|
+
disabled: tab.isDisabled,
|
|
286
|
+
};
|
|
287
|
+
}, check.isTruthy));
|
|
288
|
+
return html `
|
|
289
|
+
<${ViraOverflowSwitch.assign({
|
|
290
|
+
automaticallySwitch: true,
|
|
291
|
+
})}>
|
|
292
|
+
<ul
|
|
293
|
+
class="tabs-container"
|
|
294
|
+
role="tablist"
|
|
295
|
+
slot=${ViraOverflowSwitch.slotNames.large}
|
|
296
|
+
>
|
|
297
|
+
${tabs}
|
|
298
|
+
</ul>
|
|
299
|
+
<${ViraMenuTrigger.assign({
|
|
300
|
+
horizontalAnchor: inputs.menuHorizontalAnchor,
|
|
301
|
+
isDisabled: inputs.menuIsDisabled,
|
|
302
|
+
popUpOffset: inputs.menuPopUpOffset,
|
|
303
|
+
menuCornerStyle: ViraMenuCornerStyle.AllRounded,
|
|
304
|
+
})}
|
|
305
|
+
slot=${ViraOverflowSwitch.slotNames.small}
|
|
306
|
+
>
|
|
307
|
+
<${ViraButton.assign({
|
|
308
|
+
text: selectedTab?.label || '',
|
|
309
|
+
showMenuCaret: true,
|
|
310
|
+
colorVariant: ViraColorVariant.Neutral,
|
|
311
|
+
})}
|
|
312
|
+
slot=${ViraMenuTrigger.slotNames.trigger}
|
|
313
|
+
></${ViraButton}>
|
|
314
|
+
${menuItems}
|
|
315
|
+
</${ViraMenuTrigger}>
|
|
316
|
+
</${ViraOverflowSwitch}>
|
|
317
|
+
`;
|
|
318
|
+
},
|
|
319
|
+
});
|