@vialiq/flux-ui 0.19.0 → 0.20.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/components/_index.scss +4 -1
- package/components/_tab-panel.scss +19 -0
- package/components/_tab.scss +212 -0
- package/components/_tabs.scss +573 -0
- package/package.json +1 -1
package/components/_index.scss
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@use '@vialiq/flux-ui/styles/variables' as tokens;
|
|
2
|
+
|
|
3
|
+
@layer components {
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// Hide inactive panels; keep them in DOM for lazy content preservation
|
|
7
|
+
|
|
8
|
+
[part='panel'] {
|
|
9
|
+
padding: var(--vi-tab-panel-padding, 16px 0);
|
|
10
|
+
outline: none;
|
|
11
|
+
|
|
12
|
+
&:focus-visible {
|
|
13
|
+
outline: 2px solid var(--vi-tabs-indicator-color, #{tokens.$color-primary});
|
|
14
|
+
outline-offset: 2px;
|
|
15
|
+
border-radius: 2px;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
@use '@vialiq/flux-ui/styles/variables' as tokens;
|
|
2
|
+
|
|
3
|
+
@layer components {
|
|
4
|
+
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// vi-tab — Individual tab button
|
|
7
|
+
//
|
|
8
|
+
// The tab host is TRANSPARENT — the sliding cursor in vi-tabs handles all bg.
|
|
9
|
+
// The tab itself is responsible for: typography, icon, badge, focus ring.
|
|
10
|
+
//
|
|
11
|
+
// Key decisions:
|
|
12
|
+
// • No background on :host — cursor does all surface work
|
|
13
|
+
// • Active: primary color + semibold + slight letterSpacing tightening
|
|
14
|
+
// • Hover: very soft inset highlight (8% opacity), scale-in animation
|
|
15
|
+
// • 48px height (M3 minimum) — tabs need body to feel clickable
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// ── Tab button core ───────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
.vi-tab {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
gap: var(--vi-tab-icon-gap, 6px);
|
|
26
|
+
padding: var(--vi-tab-padding, 0 20px);
|
|
27
|
+
min-width: var(--vi-tab-min-width, 90px);
|
|
28
|
+
max-width: var(--vi-tab-max-width, 240px);
|
|
29
|
+
min-height: var(--vi-tab-height, 44px);
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 100%;
|
|
32
|
+
position: relative;
|
|
33
|
+
z-index: 1; // Above the cursor element
|
|
34
|
+
border: none;
|
|
35
|
+
background: transparent;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
user-select: none;
|
|
39
|
+
-webkit-user-select: none;
|
|
40
|
+
|
|
41
|
+
// Typography — clear nav-link feel, not body text
|
|
42
|
+
font-family: inherit;
|
|
43
|
+
font-size: var(--vi-tab-font-size, 13.5px);
|
|
44
|
+
font-weight: var(--vi-tab-font-weight, 500);
|
|
45
|
+
letter-spacing: 0.015em;
|
|
46
|
+
color: var(--vi-tab-color, #{tokens.$text-secondary});
|
|
47
|
+
|
|
48
|
+
transition:
|
|
49
|
+
color 180ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
50
|
+
transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
51
|
+
|
|
52
|
+
// ── Hover highlight (inset bloom effect) ──────────────────────────────────
|
|
53
|
+
|
|
54
|
+
&::before {
|
|
55
|
+
content: '';
|
|
56
|
+
position: absolute;
|
|
57
|
+
inset: 4px;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
background: currentColor;
|
|
60
|
+
opacity: 0;
|
|
61
|
+
transform: scale(0.85);
|
|
62
|
+
transition:
|
|
63
|
+
opacity 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
64
|
+
transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Hover ──────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
&:not(.vi-tab--disabled):not(.vi-tab--active):hover {
|
|
71
|
+
color: var(--vi-tab-color-hover, #{tokens.$text-primary});
|
|
72
|
+
|
|
73
|
+
&::before {
|
|
74
|
+
opacity: 0.06;
|
|
75
|
+
transform: scale(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Selected ──────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
&.vi-tab--active {
|
|
82
|
+
color: var(--vi-tab-color-active, #{tokens.$color-primary});
|
|
83
|
+
font-weight: var(--vi-tab-font-weight-active, 600);
|
|
84
|
+
letter-spacing: 0.01em;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Focus visible ─────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
&:focus-visible {
|
|
90
|
+
outline: none;
|
|
91
|
+
|
|
92
|
+
&::before {
|
|
93
|
+
outline: 2px solid #{tokens.$color-primary};
|
|
94
|
+
outline-offset: 0;
|
|
95
|
+
opacity: 0.12;
|
|
96
|
+
transform: scale(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Disabled ───────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
&.vi-tab--disabled {
|
|
103
|
+
color: var(--vi-tab-color-disabled, #{tokens.$text-disabled});
|
|
104
|
+
cursor: not-allowed;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
|
|
107
|
+
&::before { display: none; }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Icon ──────────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
.vi-tab__icon {
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
flex-shrink: 0;
|
|
117
|
+
font-size: var(--vi-tab-icon-size, 15px);
|
|
118
|
+
opacity: 0.6;
|
|
119
|
+
position: relative;
|
|
120
|
+
z-index: 1;
|
|
121
|
+
transition: opacity 180ms ease, transform 180ms ease;
|
|
122
|
+
|
|
123
|
+
.vi-tab--active & {
|
|
124
|
+
opacity: 1;
|
|
125
|
+
transform: scale(1.04);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Label ─────────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
.vi-tab__label {
|
|
132
|
+
position: relative;
|
|
133
|
+
z-index: 1;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
text-overflow: ellipsis;
|
|
136
|
+
white-space: nowrap;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Badge ─────────────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
.vi-tab__badge {
|
|
142
|
+
position: relative;
|
|
143
|
+
z-index: 1;
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
min-width: var(--vi-tab-close-size, 18px);
|
|
148
|
+
height: var(--vi-tab-close-size, 18px);
|
|
149
|
+
padding: var(--vi-tab-badge-padding, 0 5px);
|
|
150
|
+
border-radius: 9px;
|
|
151
|
+
font-size: 10px;
|
|
152
|
+
font-weight: 700;
|
|
153
|
+
line-height: 1;
|
|
154
|
+
letter-spacing: 0;
|
|
155
|
+
background: var(--vi-tab-badge-bg, #{tokens.$color-primary});
|
|
156
|
+
color: var(--vi-tab-badge-color, #{tokens.$layer-01});
|
|
157
|
+
flex-shrink: 0;
|
|
158
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
|
|
159
|
+
animation: vi-badge-pop 320ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@keyframes vi-badge-pop {
|
|
163
|
+
from { transform: scale(0.4); opacity: 0; }
|
|
164
|
+
to { transform: scale(1); opacity: 1; }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Close button ──────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
.vi-tab__close {
|
|
170
|
+
position: relative;
|
|
171
|
+
z-index: 2;
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
width: var(--vi-tab-close-size, 18px);
|
|
176
|
+
height: var(--vi-tab-close-size, 18px);
|
|
177
|
+
border: none;
|
|
178
|
+
border-radius: 50%;
|
|
179
|
+
background: transparent;
|
|
180
|
+
color: currentColor;
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
padding: 0;
|
|
183
|
+
margin-left: var(--vi-tab-close-margin-left, 2px);
|
|
184
|
+
flex-shrink: 0;
|
|
185
|
+
opacity: 0; // Hidden until tab is hovered
|
|
186
|
+
transform: scale(0.7);
|
|
187
|
+
transition:
|
|
188
|
+
opacity 150ms ease,
|
|
189
|
+
transform 150ms ease,
|
|
190
|
+
background-color 120ms ease;
|
|
191
|
+
|
|
192
|
+
svg { display: block; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Show close button on tab hover or when the tab is active
|
|
196
|
+
.vi-tab:hover .vi-tab__close,
|
|
197
|
+
.vi-tab--active .vi-tab__close {
|
|
198
|
+
opacity: 0.6;
|
|
199
|
+
transform: scale(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.vi-tab__close:hover {
|
|
203
|
+
opacity: 1 !important;
|
|
204
|
+
background: color-mix(in srgb, #{tokens.$text-primary} 8%, transparent);
|
|
205
|
+
transform: scale(1) !important;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Overflow hidden state (overflow=menu) ─────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
@use '@vialiq/flux-ui/styles/variables' as tokens;
|
|
2
|
+
|
|
3
|
+
@layer components {
|
|
4
|
+
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// vi-tabs — Container, tablist, SLIDING CURSOR, indicator, all variants
|
|
7
|
+
//
|
|
8
|
+
// Architecture:
|
|
9
|
+
// • .vi-tabs__cursor → absolutely positioned, slides between tabs (JS-positioned)
|
|
10
|
+
// Used by: pill, card, enclosed (the "selected surface")
|
|
11
|
+
// • .vi-tabs__indicator → absolutely positioned thin line at bottom/side (JS-positioned)
|
|
12
|
+
// Used by: line, secondary (the "underline signal")
|
|
13
|
+
//
|
|
14
|
+
// The cursor element ALWAYS exists in the DOM. CSS on each variant controls
|
|
15
|
+
// whether it's visible (opacity, border-radius, shadow etc.)
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
.vi-tabs {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: minmax(0, 1fr);
|
|
22
|
+
position: relative;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.vi-tabs__scroll-btn {
|
|
26
|
+
grid-area: 1 / 1;
|
|
27
|
+
align-self: stretch;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
width: 48px; // Wider to accommodate the gradient safely
|
|
32
|
+
z-index: 10;
|
|
33
|
+
border: none;
|
|
34
|
+
color: var(--vi-tabs-text-color, #{tokens.$text-primary});
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
transition: opacity 0.2s;
|
|
37
|
+
|
|
38
|
+
&[disabled] {
|
|
39
|
+
opacity: 0;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:hover, &:focus-visible {
|
|
44
|
+
color: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
45
|
+
outline: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&--left {
|
|
49
|
+
justify-self: start;
|
|
50
|
+
background: linear-gradient(to right, var(--vi-tabs-surface-primary, #{tokens.$layer-01}) 50%, transparent 100%);
|
|
51
|
+
padding-right: 12px; // Push icon slightly left
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&--right {
|
|
55
|
+
justify-self: end;
|
|
56
|
+
background: linear-gradient(to left, var(--vi-tabs-surface-primary, #{tokens.$layer-01}) 50%, transparent 100%);
|
|
57
|
+
padding-left: 12px; // Push icon slightly right
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
// Add Button
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
.vi-tabs__add-btn {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
width: 48px;
|
|
72
|
+
height: 48px;
|
|
73
|
+
flex-shrink: 0;
|
|
74
|
+
background: transparent;
|
|
75
|
+
border: none;
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
color: var(--vi-tabs-add-btn-color, #{tokens.$text-secondary});
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
margin-left: 4px;
|
|
80
|
+
outline: none;
|
|
81
|
+
position: relative;
|
|
82
|
+
transition: background-color 0.2s, color 0.2s;
|
|
83
|
+
order: 9998; // Ensure it stays right before the More menu
|
|
84
|
+
|
|
85
|
+
&::before {
|
|
86
|
+
content: '';
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset: 4px;
|
|
89
|
+
border-radius: 50%;
|
|
90
|
+
background-color: currentColor;
|
|
91
|
+
opacity: 0;
|
|
92
|
+
transition: opacity 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&:hover::before,
|
|
96
|
+
&:focus-visible::before {
|
|
97
|
+
opacity: 0.08;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&:active::before {
|
|
101
|
+
opacity: 0.12;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&:focus-visible {
|
|
105
|
+
border-radius: 4px; // Square focus ring for accessibility
|
|
106
|
+
outline: 2px solid var(--vi-tabs-focus-ring, #{tokens.$color-primary});
|
|
107
|
+
outline-offset: 2px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
svg {
|
|
111
|
+
position: relative; // Above the hover state
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
// Shared tablist base
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
.vi-tabs__tablist {
|
|
120
|
+
grid-area: 1 / 1;
|
|
121
|
+
position: relative;
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: row;
|
|
124
|
+
align-items: stretch;
|
|
125
|
+
gap: 0;
|
|
126
|
+
// overflow: visible so overflow-menu dropdown is not clipped.
|
|
127
|
+
// Each variant overrides this as needed.
|
|
128
|
+
overflow: visible;
|
|
129
|
+
min-width: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
+
// SLIDING CURSOR — the magic that makes this feel premium
|
|
134
|
+
//
|
|
135
|
+
// Positioned absolutely by JS. Each variant styles it differently.
|
|
136
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
.vi-tabs__cursor {
|
|
139
|
+
position: absolute;
|
|
140
|
+
top: 0;
|
|
141
|
+
left: 0;
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
opacity: 0;
|
|
144
|
+
z-index: 0;
|
|
145
|
+
|
|
146
|
+
// Transitions — fast out slow in, matching motion system
|
|
147
|
+
transition:
|
|
148
|
+
left 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
149
|
+
top 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
150
|
+
width 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
151
|
+
height 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
152
|
+
opacity 120ms ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
// BOTTOM-LINE INDICATOR — for line / secondary variants
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
.vi-tabs__indicator {
|
|
160
|
+
position: absolute;
|
|
161
|
+
bottom: 0;
|
|
162
|
+
left: 0;
|
|
163
|
+
height: var(--vi-tabs-indicator-thickness, 2px);
|
|
164
|
+
width: 0;
|
|
165
|
+
background: var(--vi-tabs-indicator-color, #{tokens.$color-primary});
|
|
166
|
+
border-radius: 2px 2px 0 0;
|
|
167
|
+
opacity: 0;
|
|
168
|
+
pointer-events: none;
|
|
169
|
+
z-index: 2;
|
|
170
|
+
|
|
171
|
+
transition:
|
|
172
|
+
left 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
173
|
+
width 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
174
|
+
top 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
175
|
+
height 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
176
|
+
opacity 120ms ease;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
180
|
+
// VARIANT: line (default) — clean underline, minimal strip
|
|
181
|
+
//
|
|
182
|
+
// Design reference: Vercel, Linear, GitHub navigation
|
|
183
|
+
// Strip: barely-there border, no bg. Typography + indicator do the work.
|
|
184
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
.vi-tabs--line {
|
|
187
|
+
.vi-tabs__tablist {
|
|
188
|
+
border-bottom: 1px solid var(--vi-tabs-border-color, #{tokens.$color-grey-200});
|
|
189
|
+
background: transparent;
|
|
190
|
+
padding: 0;
|
|
191
|
+
gap: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Cursor hidden — line variant uses indicator only
|
|
195
|
+
.vi-tabs__cursor {
|
|
196
|
+
display: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Indicator: solid 2px line
|
|
200
|
+
.vi-tabs__indicator {
|
|
201
|
+
bottom: -1px;
|
|
202
|
+
height: 2px;
|
|
203
|
+
background: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
204
|
+
border-radius: 2px 2px 0 0;
|
|
205
|
+
box-shadow: 0 0 0 0 transparent; // no glow on line variant — keep clean
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
210
|
+
// VARIANT: pill — segmented control / HeroUI / shadcn style
|
|
211
|
+
//
|
|
212
|
+
// Design reference: Apple iOS, shadcn/ui, HeroUI
|
|
213
|
+
// The cursor IS the active pill — white card that slides between tabs.
|
|
214
|
+
// Container: inset-shadow tray. Active: white card with shadow.
|
|
215
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
.vi-tabs--pill {
|
|
218
|
+
.vi-tabs__tablist {
|
|
219
|
+
background: var(--vi-tabs-pill-bg, #{tokens.$color-grey-100});
|
|
220
|
+
border: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
221
|
+
border-radius: 10px;
|
|
222
|
+
padding: var(--vi-tabs-spacing-xs, 4px);
|
|
223
|
+
gap: var(--vi-tabs-gap-xs, 2px);
|
|
224
|
+
// Inset shadow creates a "recessed tray" feel
|
|
225
|
+
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.07), inset 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// The sliding white pill cursor
|
|
229
|
+
.vi-tabs__cursor {
|
|
230
|
+
display: block;
|
|
231
|
+
inset: 4px auto; // matches tablist padding
|
|
232
|
+
border-radius: 7px;
|
|
233
|
+
background: var(--vi-tabs-surface-primary, #{tokens.$layer-01});
|
|
234
|
+
// Multi-layer shadow for floating card feel
|
|
235
|
+
box-shadow:
|
|
236
|
+
0 1px 3px rgba(0, 0, 0, 0.10),
|
|
237
|
+
0 1px 2px rgba(0, 0, 0, 0.06),
|
|
238
|
+
0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Indicator hidden — cursor does selection work
|
|
242
|
+
.vi-tabs__indicator {
|
|
243
|
+
display: none;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
248
|
+
// VARIANT: secondary (M3 — sub-hierarchy, thinner indicator)
|
|
249
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
.vi-tabs--secondary {
|
|
252
|
+
.vi-tabs__tablist {
|
|
253
|
+
border-bottom: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
254
|
+
background: transparent;
|
|
255
|
+
padding: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.vi-tabs__cursor { display: none; }
|
|
259
|
+
|
|
260
|
+
.vi-tabs__indicator {
|
|
261
|
+
bottom: -1px;
|
|
262
|
+
height: 2px;
|
|
263
|
+
background: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
264
|
+
border-radius: 2px 2px 0 0;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
// VARIANT: card — browser-tab file-folder aesthetic
|
|
270
|
+
//
|
|
271
|
+
// Design reference: browser tabs, VS Code editor tabs
|
|
272
|
+
// Active tab: white surface merges with content area below.
|
|
273
|
+
// The cursor element becomes the white card + side borders.
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
.vi-tabs--card {
|
|
277
|
+
.vi-tabs__tablist {
|
|
278
|
+
background: var(--vi-tabs-surface-secondary, #{tokens.$layer-02});
|
|
279
|
+
border-bottom: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
280
|
+
padding: var(--vi-tabs-spacing-md-md-none, 8px 8px 0);
|
|
281
|
+
gap: var(--vi-tabs-gap-sm, 4px);
|
|
282
|
+
align-items: flex-end;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// The cursor is the active card surface
|
|
286
|
+
.vi-tabs__cursor {
|
|
287
|
+
$surface-color: var(--vi-tabs-surface-primary, #{tokens.$layer-01});
|
|
288
|
+
display: block;
|
|
289
|
+
border-radius: 8px 8px 0 0;
|
|
290
|
+
background: $surface-color;
|
|
291
|
+
border: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
292
|
+
border-bottom-color: $surface-color; // Merges with content below
|
|
293
|
+
margin-top: 8px;
|
|
294
|
+
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.vi-tabs__indicator { display: none; }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
301
|
+
// VARIANT: enclosed — bordered box with shared header/content border
|
|
302
|
+
//
|
|
303
|
+
// Design reference: Mantine, Chakra UI enclosed tabs
|
|
304
|
+
// The entire component lives in a border box. Active tab has indicator only.
|
|
305
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
.vi-tabs--enclosed {
|
|
308
|
+
border: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
309
|
+
border-radius: 10px;
|
|
310
|
+
overflow: clip; // Enclosed: clip cursor inside rounded border box
|
|
311
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
312
|
+
|
|
313
|
+
.vi-tabs__tablist {
|
|
314
|
+
background: var(--vi-tabs-surface-secondary, #{tokens.$layer-02});
|
|
315
|
+
border-bottom: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
316
|
+
padding: 0;
|
|
317
|
+
gap: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Cursor: subtle active tab highlight within the enclosed strip
|
|
321
|
+
.vi-tabs__cursor {
|
|
322
|
+
display: block;
|
|
323
|
+
top: 0;
|
|
324
|
+
border-radius: 0;
|
|
325
|
+
background: rgba(#{tokens.$color-primary}, 0.06);
|
|
326
|
+
border-right: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
327
|
+
border-left: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Thin indicator at bottom of strip header
|
|
331
|
+
.vi-tabs__indicator {
|
|
332
|
+
bottom: 0;
|
|
333
|
+
height: 3px;
|
|
334
|
+
background: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
335
|
+
box-shadow: 0 0 8px color-mix(in srgb, #{tokens.$color-primary} 50%, transparent);
|
|
336
|
+
border-radius: 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
::slotted(vi-tab) {
|
|
340
|
+
border-right: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border}) !important;
|
|
341
|
+
min-height: 44px !important;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
346
|
+
// ORIENTATION: vertical (sidebar settings pattern)
|
|
347
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
.vi-tabs--vertical {
|
|
350
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
351
|
+
align-items: stretch;
|
|
352
|
+
|
|
353
|
+
.vi-tabs__tablist {
|
|
354
|
+
flex-direction: column;
|
|
355
|
+
align-items: stretch;
|
|
356
|
+
background: var(--vi-tabs-surface-secondary, #{tokens.$layer-02});
|
|
357
|
+
border-right: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
358
|
+
border-bottom: none;
|
|
359
|
+
padding: var(--vi-tabs-spacing-sm-xs, 6px 4px);
|
|
360
|
+
min-width: 168px;
|
|
361
|
+
gap: var(--vi-tabs-gap-xs, 2px);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Cursor becomes the sliding bg highlight in vertical mode
|
|
365
|
+
.vi-tabs__cursor {
|
|
366
|
+
display: block;
|
|
367
|
+
left: 4px !important;
|
|
368
|
+
right: 4px;
|
|
369
|
+
width: calc(100% - 8px) !important; // Override JS width — fills sidebar
|
|
370
|
+
border-radius: 8px;
|
|
371
|
+
background: var(--vi-tabs-surface-primary, #{tokens.$layer-01});
|
|
372
|
+
box-shadow:
|
|
373
|
+
0 1px 3px rgba(0, 0, 0, 0.08),
|
|
374
|
+
0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Vertical indicator — right edge accent line
|
|
378
|
+
.vi-tabs__indicator {
|
|
379
|
+
bottom: auto;
|
|
380
|
+
top: 0;
|
|
381
|
+
left: auto;
|
|
382
|
+
right: 0;
|
|
383
|
+
width: 3px !important;
|
|
384
|
+
height: 0;
|
|
385
|
+
border-radius: 3px 0 0 3px;
|
|
386
|
+
background: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
387
|
+
box-shadow: 0 0 6px color-mix(in srgb, #{tokens.$color-primary} 40%, transparent);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
::slotted(vi-tab) {
|
|
391
|
+
justify-content: flex-start !important;
|
|
392
|
+
min-height: 40px !important;
|
|
393
|
+
text-align: left !important;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
&.vi-tabs--line ::slotted(vi-tab[active]) {
|
|
397
|
+
background: var(--vi-tabs-surface-primary, #{tokens.$layer-01});
|
|
398
|
+
border-radius: 8px;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
403
|
+
// Reduced motion — respect system preferences
|
|
404
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
@media (prefers-reduced-motion: reduce) {
|
|
407
|
+
.vi-tabs__cursor,
|
|
408
|
+
.vi-tabs__indicator {
|
|
409
|
+
transition: opacity 100ms ease;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
414
|
+
// OVERFLOW: scroll
|
|
415
|
+
//
|
|
416
|
+
// Tablist scrolls horizontally. Fade gradients hint at hidden content.
|
|
417
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
.vi-tabs--overflow-scroll .vi-tabs__tablist {
|
|
420
|
+
overflow-x: auto;
|
|
421
|
+
overflow-y: hidden;
|
|
422
|
+
scrollbar-width: none;
|
|
423
|
+
&::-webkit-scrollbar { display: none; }
|
|
424
|
+
|
|
425
|
+
// Premium visual indicator using mask-image to fade tabs at the scroll edges
|
|
426
|
+
mask-image: linear-gradient(to right, transparent 0%, black 24px, black calc(100% - 24px), transparent 100%);
|
|
427
|
+
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 24px, black calc(100% - 24px), transparent 100%);
|
|
428
|
+
|
|
429
|
+
// When at scroll start, hide left fade
|
|
430
|
+
&[data-scroll-start] {
|
|
431
|
+
mask-image: linear-gradient(to right, black 0%, black calc(100% - 24px), transparent 100%);
|
|
432
|
+
-webkit-mask-image: linear-gradient(to right, black 0%, black calc(100% - 24px), transparent 100%);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// When at scroll end, hide right fade
|
|
436
|
+
&[data-scroll-end] {
|
|
437
|
+
mask-image: linear-gradient(to right, transparent 0%, black 24px, black 100%);
|
|
438
|
+
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 24px, black 100%);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// When no scroll possible, no fades
|
|
442
|
+
&[data-scroll-none] {
|
|
443
|
+
mask-image: none;
|
|
444
|
+
-webkit-mask-image: none;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
449
|
+
// OVERFLOW: wrap
|
|
450
|
+
//
|
|
451
|
+
// Tabs wrap to the next line.
|
|
452
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
.vi-tabs--overflow-wrap .vi-tabs__tablist {
|
|
455
|
+
flex-wrap: wrap;
|
|
456
|
+
overflow: visible;
|
|
457
|
+
height: auto;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
461
|
+
// OVERFLOW: menu — "More" button + dropdown
|
|
462
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
// More wrapper sits at end of tablist
|
|
465
|
+
.vi-tabs__more-wrapper {
|
|
466
|
+
position: relative;
|
|
467
|
+
display: inline-flex;
|
|
468
|
+
align-items: stretch;
|
|
469
|
+
flex-shrink: 0;
|
|
470
|
+
margin-left: auto;
|
|
471
|
+
order: 9999; // ensure it sits after visually reordered tabs
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// More trigger button
|
|
475
|
+
.vi-tabs__more-btn {
|
|
476
|
+
display: inline-flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: var(--vi-tabs-gap-sm, 4px);
|
|
479
|
+
padding: var(--vi-tabs-spacing-none-lg, 0 14px);
|
|
480
|
+
min-height: 44px;
|
|
481
|
+
border: none;
|
|
482
|
+
background: transparent;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
font-family: inherit;
|
|
485
|
+
font-size: 13px;
|
|
486
|
+
font-weight: 500;
|
|
487
|
+
color: var(--vi-tabs-text-secondary, #{tokens.$text-secondary});
|
|
488
|
+
border-radius: 6px 6px 0 0;
|
|
489
|
+
transition: color 150ms ease, background-color 150ms ease;
|
|
490
|
+
|
|
491
|
+
&:hover {
|
|
492
|
+
color: var(--vi-tabs-text-primary, #{tokens.$text-primary});
|
|
493
|
+
background: rgba(0, 0, 0, 0.04);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
&--open {
|
|
497
|
+
color: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.vi-tabs__more-chevron {
|
|
502
|
+
flex-shrink: 0;
|
|
503
|
+
transition: transform 200ms ease;
|
|
504
|
+
|
|
505
|
+
.vi-tabs__more-btn--open & {
|
|
506
|
+
transform: rotate(180deg);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// More dropdown menu
|
|
511
|
+
.vi-tabs__more-menu {
|
|
512
|
+
position: absolute;
|
|
513
|
+
top: calc(100% + 4px);
|
|
514
|
+
right: 0;
|
|
515
|
+
z-index: 100;
|
|
516
|
+
min-width: 180px;
|
|
517
|
+
background: var(--vi-tabs-surface-primary, #{tokens.$layer-01});
|
|
518
|
+
border: 1px solid var(--vi-tabs-border-color, #{tokens.$color-border});
|
|
519
|
+
border-radius: 8px;
|
|
520
|
+
box-shadow:
|
|
521
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.10),
|
|
522
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
|
523
|
+
0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
524
|
+
padding: var(--vi-tabs-spacing-xs, 4px);
|
|
525
|
+
animation: vi-menu-appear 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
526
|
+
transform-origin: top right;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
@keyframes vi-menu-appear {
|
|
530
|
+
from { opacity: 0; transform: scale(0.95) translateY(-4px); }
|
|
531
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Individual menu item
|
|
535
|
+
.vi-tabs__more-item {
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: center;
|
|
538
|
+
width: 100%;
|
|
539
|
+
padding: var(--vi-tabs-spacing-md-lg, 8px 12px);
|
|
540
|
+
border: none;
|
|
541
|
+
border-radius: 6px;
|
|
542
|
+
background: transparent;
|
|
543
|
+
font-family: inherit;
|
|
544
|
+
font-size: 13.5px;
|
|
545
|
+
font-weight: 500;
|
|
546
|
+
color: var(--vi-tabs-text-secondary, #{tokens.$text-secondary});
|
|
547
|
+
cursor: pointer;
|
|
548
|
+
text-align: left;
|
|
549
|
+
transition: background-color 100ms ease, color 100ms ease;
|
|
550
|
+
|
|
551
|
+
&:hover:not(:disabled) {
|
|
552
|
+
background: var(--vi-tabs-surface-secondary, #{tokens.$layer-02});
|
|
553
|
+
color: var(--vi-tabs-text-primary, #{tokens.$text-primary});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
&:disabled {
|
|
557
|
+
color: var(--vi-tabs-text-disabled, #{tokens.$text-disabled});
|
|
558
|
+
cursor: not-allowed;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
&--active {
|
|
562
|
+
color: var(--vi-tabs-primary-color, #{tokens.$color-primary});
|
|
563
|
+
font-weight: 600;
|
|
564
|
+
background: color-mix(in srgb, #{tokens.$color-primary} 6%, transparent);
|
|
565
|
+
|
|
566
|
+
&:hover {
|
|
567
|
+
background: color-mix(in srgb, #{tokens.$color-primary} 10%, transparent);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
}
|