@vialiq/flux-ui 0.18.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.
@@ -0,0 +1,6 @@
1
+
2
+ @mixin vi-focus-ring($color-token, $glow-token) {
3
+ outline: var(--vi-border-width-base, 2px) solid var(--vi-focus-ring-color, #{$color-token});
4
+ outline-offset: 0;
5
+ box-shadow: 0 0 0 3px var(--vi-focus-ring-glow, #{$glow-token});
6
+ }
@@ -17,3 +17,8 @@
17
17
  @forward 'animation';
18
18
  @forward 'switch';
19
19
  @forward 'select';
20
+ @forward 'link';
21
+ @forward 'focus';
22
+ @forward 'tabs';
23
+ @forward 'tab';
24
+ @forward 'tab-panel';
@@ -0,0 +1,76 @@
1
+ @use '../styles/variables' as tokens;
2
+ @use './focus' as *;
3
+
4
+ @mixin vi-link-styles {
5
+ color: var(--vi-link-color, #{tokens.$link-color});
6
+ text-decoration-thickness: 1px;
7
+ text-underline-offset: var(--vi-link-underline-offset, 2px);
8
+ cursor: pointer;
9
+ transition: color 0.2s ease, text-decoration-color 0.2s ease;
10
+ outline: none;
11
+ text-decoration: underline;
12
+
13
+ &.underline-none {
14
+ text-decoration: none;
15
+ }
16
+
17
+ &.underline-hover {
18
+ text-decoration: none;
19
+ &:hover {
20
+ text-decoration: underline;
21
+ }
22
+ }
23
+
24
+ &.size-inherit {
25
+ font-size: inherit;
26
+ }
27
+
28
+ &.size-sm {
29
+ font-size: var(--vi-font-size-sm, #{tokens.$font-size-sm});
30
+ }
31
+
32
+ &.size-md {
33
+ font-size: var(--vi-font-size-base, #{tokens.$font-size-base});
34
+ }
35
+
36
+ &.size-lg {
37
+ font-size: var(--vi-font-size-lg, #{tokens.$font-size-lg});
38
+ }
39
+
40
+ &.variant-secondary {
41
+ --vi-link-color: var(--vi-link-color-secondary, #{tokens.$link-color-secondary});
42
+ --vi-link-color-hover: var(--vi-link-color-secondary-hover, #{tokens.$color-grey-800});
43
+ --vi-link-color-active: var(--vi-link-color-secondary-active, #{tokens.$color-grey-900});
44
+ --vi-link-color-visited: var(--vi-link-color-secondary-visited, #{tokens.$color-grey-700});
45
+ }
46
+
47
+ &.variant-muted {
48
+ --vi-link-color: var(--vi-link-color-muted, #{tokens.$link-color-muted});
49
+ --vi-link-color-hover: var(--vi-link-color-muted-hover, #{tokens.$color-grey-600});
50
+ --vi-link-color-active: var(--vi-link-color-muted-active, #{tokens.$color-grey-700});
51
+ --vi-link-color-visited: var(--vi-link-color-muted-visited, #{tokens.$color-grey-500});
52
+ }
53
+
54
+ &:visited {
55
+ color: var(--vi-link-color-visited, #{tokens.$link-color-visited});
56
+ }
57
+
58
+ &:hover {
59
+ color: var(--vi-link-color-hover, #{tokens.$link-color-hover});
60
+ }
61
+
62
+ &:active {
63
+ color: var(--vi-link-color-active, #{tokens.$link-color-active});
64
+ }
65
+
66
+ &:focus-visible {
67
+ @include vi-focus-ring(tokens.$link-color, transparent);
68
+ }
69
+
70
+ &[aria-disabled="true"], &.disabled {
71
+ color: var(--vi-link-color-disabled, #{tokens.$link-color-disabled});
72
+ pointer-events: none;
73
+ cursor: not-allowed;
74
+ text-decoration: none;
75
+ }
76
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vialiq/flux-ui",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -425,3 +425,12 @@ $modal-max-width-sm: var(--vi-modal-max-width-sm, 480px);
425
425
  $modal-max-width-md: var(--vi-modal-max-width-md, 640px);
426
426
  $modal-max-width-lg: var(--vi-modal-max-width-lg, 800px);
427
427
  $modal-max-width-xl: var(--vi-modal-max-width-xl, 960px);
428
+
429
+
430
+ $link-color: var(--vi-link-color, $color-primary);
431
+ $link-color-hover: var(--vi-link-color-hover, $color-blue-800);
432
+ $link-color-active: var(--vi-link-color-active, $color-blue-900);
433
+ $link-color-visited: var(--vi-link-color-visited, $color-purple-600);
434
+ $link-color-disabled: var(--vi-link-color-disabled, $color-grey-400);
435
+ $link-color-secondary:var(--vi-link-color-secondary,$color-grey-700);
436
+ $link-color-muted: var(--vi-link-color-muted, $color-grey-500);