@juspay/svelte-ui-components 2.99.0 → 2.100.1
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.
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
>
|
|
41
41
|
{#if !hideArrow && direction !== 'neutral'}
|
|
42
42
|
<span class="delta-indicator-arrow delta-arrow-{direction}" aria-hidden="true">
|
|
43
|
-
<svg viewBox="0 0
|
|
43
|
+
<svg viewBox="0 0 24 24"
|
|
44
|
+
><path d="M22 7L13.5 15.5L8.5 10.5L2 17" /><path d="M16 7H22V13" /></svg
|
|
45
|
+
>
|
|
44
46
|
</span>
|
|
45
47
|
{/if}
|
|
46
48
|
<span class="delta-indicator-text">{text}</span>
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
.delta-indicator {
|
|
51
53
|
display: inline-flex;
|
|
52
54
|
align-items: center;
|
|
53
|
-
gap: var(--delta-indicator-gap,
|
|
55
|
+
gap: var(--delta-indicator-gap, 4px);
|
|
54
56
|
font-size: var(--delta-indicator-font-size, 13px);
|
|
55
57
|
font-weight: var(--delta-indicator-font-weight, 600);
|
|
56
58
|
line-height: 1;
|
|
@@ -78,18 +80,25 @@
|
|
|
78
80
|
|
|
79
81
|
.delta-indicator-arrow {
|
|
80
82
|
display: inline-flex;
|
|
81
|
-
width: var(--delta-indicator-arrow-size,
|
|
82
|
-
height: var(--delta-indicator-arrow-size,
|
|
83
|
+
width: var(--delta-indicator-arrow-size, 16px);
|
|
84
|
+
height: var(--delta-indicator-arrow-size, 16px);
|
|
83
85
|
flex-shrink: 0;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
.delta-indicator-arrow svg {
|
|
87
89
|
width: 100%;
|
|
88
90
|
height: 100%;
|
|
89
|
-
fill:
|
|
91
|
+
fill: none;
|
|
92
|
+
stroke: currentColor;
|
|
93
|
+
stroke-width: var(--delta-indicator-arrow-stroke-width, 2);
|
|
94
|
+
stroke-linecap: round;
|
|
95
|
+
stroke-linejoin: round;
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
/* Down-trend mirrors the up-trend glyph vertically (line falling to the
|
|
99
|
+
lower-right, arrowhead at the bottom-right) — the Untitled UI trend-down-01
|
|
100
|
+
shape. A 180deg rotation would instead point down-left. */
|
|
92
101
|
.delta-arrow-down {
|
|
93
|
-
transform:
|
|
102
|
+
transform: scaleY(-1);
|
|
94
103
|
}
|
|
95
104
|
</style>
|
package/dist/Tabs/Tabs.svelte
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
import Img from '../Img/Img.svelte';
|
|
4
4
|
import chevronLeftSvg from '../assets/chevron-left.svg?raw';
|
|
5
5
|
import chevronRightSvg from '../assets/chevron-right.svg?raw';
|
|
6
|
+
import chevronUpSvg from '../assets/chevron-up.svg?raw';
|
|
7
|
+
import chevronDownSvg from '../assets/chevron-down.svg?raw';
|
|
6
8
|
|
|
7
9
|
let {
|
|
8
10
|
items,
|
|
9
11
|
activeIndex = $bindable(0),
|
|
10
12
|
activeKey,
|
|
11
13
|
disabled = false,
|
|
14
|
+
orientation = 'horizontal',
|
|
12
15
|
testId,
|
|
13
16
|
scrollLeftIcon,
|
|
14
17
|
scrollRightIcon,
|
|
@@ -18,6 +21,8 @@
|
|
|
18
21
|
onkeychange
|
|
19
22
|
}: TabsProperties = $props();
|
|
20
23
|
|
|
24
|
+
const isVertical = $derived(orientation === 'vertical');
|
|
25
|
+
|
|
21
26
|
const isObjectMode = $derived(items.length > 0 && typeof items.at(0) === 'object');
|
|
22
27
|
|
|
23
28
|
function toTabItem(item: string | TabItem): TabItem | null {
|
|
@@ -44,16 +49,29 @@
|
|
|
44
49
|
let scrollContainer: HTMLDivElement | null = null;
|
|
45
50
|
let canScrollLeft = $state(false);
|
|
46
51
|
let canScrollRight = $state(false);
|
|
52
|
+
let canScrollUp = $state(false);
|
|
53
|
+
let canScrollDown = $state(false);
|
|
47
54
|
|
|
48
|
-
// Sliding indicator state
|
|
55
|
+
// Sliding indicator state — horizontal tracks left/width, vertical tracks top/height.
|
|
49
56
|
let indicatorLeft = $state(0);
|
|
50
57
|
let indicatorWidth = $state(0);
|
|
58
|
+
let indicatorTop = $state(0);
|
|
59
|
+
let indicatorHeight = $state(0);
|
|
51
60
|
let indicatorReady = $state(false);
|
|
52
61
|
|
|
62
|
+
const showStartArrow = $derived(isVertical ? canScrollUp : canScrollLeft);
|
|
63
|
+
const showEndArrow = $derived(isVertical ? canScrollDown : canScrollRight);
|
|
64
|
+
|
|
53
65
|
function updateOverflow(): void {
|
|
54
66
|
if (scrollContainer === null) {
|
|
55
67
|
return;
|
|
56
68
|
}
|
|
69
|
+
if (isVertical) {
|
|
70
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
|
71
|
+
canScrollUp = scrollTop > 1;
|
|
72
|
+
canScrollDown = scrollTop + clientHeight < scrollHeight - 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
57
75
|
const { scrollLeft, scrollWidth, clientWidth } = scrollContainer;
|
|
58
76
|
canScrollLeft = scrollLeft > 1;
|
|
59
77
|
canScrollRight = scrollLeft + clientWidth < scrollWidth - 1;
|
|
@@ -68,20 +86,35 @@
|
|
|
68
86
|
indicatorReady = false;
|
|
69
87
|
indicatorLeft = 0;
|
|
70
88
|
indicatorWidth = 0;
|
|
89
|
+
indicatorTop = 0;
|
|
90
|
+
indicatorHeight = 0;
|
|
71
91
|
return;
|
|
72
92
|
}
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
if (isVertical) {
|
|
94
|
+
indicatorTop = activeEl.offsetTop;
|
|
95
|
+
indicatorHeight = activeEl.offsetHeight;
|
|
96
|
+
} else {
|
|
97
|
+
indicatorLeft = activeEl.offsetLeft;
|
|
98
|
+
indicatorWidth = activeEl.offsetWidth;
|
|
99
|
+
}
|
|
75
100
|
indicatorReady = true;
|
|
76
101
|
}
|
|
77
102
|
|
|
78
|
-
function scroll(
|
|
103
|
+
function scroll(edge: 'start' | 'end'): void {
|
|
79
104
|
if (scrollContainer === null) {
|
|
80
105
|
return;
|
|
81
106
|
}
|
|
107
|
+
if (isVertical) {
|
|
108
|
+
const amount = scrollContainer.clientHeight * 0.6;
|
|
109
|
+
scrollContainer.scrollBy({
|
|
110
|
+
top: edge === 'start' ? -amount : amount,
|
|
111
|
+
behavior: 'smooth'
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
82
115
|
const amount = scrollContainer.clientWidth * 0.6;
|
|
83
116
|
scrollContainer.scrollBy({
|
|
84
|
-
left:
|
|
117
|
+
left: edge === 'start' ? -amount : amount,
|
|
85
118
|
behavior: 'smooth'
|
|
86
119
|
});
|
|
87
120
|
}
|
|
@@ -154,18 +187,18 @@
|
|
|
154
187
|
);
|
|
155
188
|
</script>
|
|
156
189
|
|
|
157
|
-
<div class={rootClass} class:disabled data-pw={testId}>
|
|
158
|
-
{#if
|
|
190
|
+
<div class={rootClass} class:disabled class:vertical={isVertical} data-pw={testId}>
|
|
191
|
+
{#if showStartArrow}
|
|
159
192
|
<button
|
|
160
|
-
class="tabs-arrow tabs-arrow-
|
|
161
|
-
aria-label=
|
|
162
|
-
onclick={() => scroll('
|
|
193
|
+
class="tabs-arrow tabs-arrow-start"
|
|
194
|
+
aria-label={isVertical ? 'Scroll tabs up' : 'Scroll tabs left'}
|
|
195
|
+
onclick={() => scroll('start')}
|
|
163
196
|
>
|
|
164
197
|
{#if typeof scrollLeftIcon === 'function'}
|
|
165
198
|
{@render scrollLeftIcon()}
|
|
166
199
|
{:else}
|
|
167
200
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
168
|
-
{@html chevronLeftSvg}
|
|
201
|
+
{@html isVertical ? chevronUpSvg : chevronLeftSvg}
|
|
169
202
|
{/if}
|
|
170
203
|
</button>
|
|
171
204
|
{/if}
|
|
@@ -173,6 +206,8 @@
|
|
|
173
206
|
class="tabs-bar"
|
|
174
207
|
class:fade-left={canScrollLeft}
|
|
175
208
|
class:fade-right={canScrollRight}
|
|
209
|
+
class:fade-top={canScrollUp}
|
|
210
|
+
class:fade-bottom={canScrollDown}
|
|
176
211
|
role="tablist"
|
|
177
212
|
{@attach initOverflow}
|
|
178
213
|
onscroll={updateOverflow}
|
|
@@ -180,6 +215,9 @@
|
|
|
180
215
|
{#each items as item, index (isObjectMode ? (toTabItem(item)?.key ?? index) : index)}
|
|
181
216
|
{@const tabItem = toTabItem(item)}
|
|
182
217
|
{@const label = toStringLabel(item)}
|
|
218
|
+
{#if typeof tabItem?.sectionLabel === 'string' && tabItem.sectionLabel.length > 0}
|
|
219
|
+
<div class="tabs-section-label" aria-hidden="true">{tabItem.sectionLabel}</div>
|
|
220
|
+
{/if}
|
|
183
221
|
<div
|
|
184
222
|
class="tabs-item"
|
|
185
223
|
class:active={isActiveItem(index)}
|
|
@@ -215,21 +253,23 @@
|
|
|
215
253
|
<span
|
|
216
254
|
class="tabs-indicator"
|
|
217
255
|
aria-hidden="true"
|
|
218
|
-
style=
|
|
256
|
+
style={isVertical
|
|
257
|
+
? `top: ${indicatorTop}px; height: ${indicatorHeight}px;`
|
|
258
|
+
: `left: ${indicatorLeft}px; width: ${indicatorWidth}px;`}
|
|
219
259
|
></span>
|
|
220
260
|
{/if}
|
|
221
261
|
</div>
|
|
222
|
-
{#if
|
|
262
|
+
{#if showEndArrow}
|
|
223
263
|
<button
|
|
224
|
-
class="tabs-arrow tabs-arrow-
|
|
225
|
-
aria-label=
|
|
226
|
-
onclick={() => scroll('
|
|
264
|
+
class="tabs-arrow tabs-arrow-end"
|
|
265
|
+
aria-label={isVertical ? 'Scroll tabs down' : 'Scroll tabs right'}
|
|
266
|
+
onclick={() => scroll('end')}
|
|
227
267
|
>
|
|
228
268
|
{#if typeof scrollRightIcon === 'function'}
|
|
229
269
|
{@render scrollRightIcon()}
|
|
230
270
|
{:else}
|
|
231
271
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
232
|
-
{@html chevronRightSvg}
|
|
272
|
+
{@html isVertical ? chevronDownSvg : chevronRightSvg}
|
|
233
273
|
{/if}
|
|
234
274
|
</button>
|
|
235
275
|
{/if}
|
|
@@ -376,7 +416,13 @@
|
|
|
376
416
|
margin-left: auto;
|
|
377
417
|
border-radius: 50%;
|
|
378
418
|
flex-shrink: 0;
|
|
379
|
-
background:
|
|
419
|
+
background: transparent;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* Neutral "has activity / configured" dot — blue by default, matching the app's
|
|
423
|
+
--text-color-focus. Maps from a settings menu's Default circle type. */
|
|
424
|
+
.tabs-item-status.status-default {
|
|
425
|
+
background: var(--tabs-item-status-default-color, #1a73e8);
|
|
380
426
|
}
|
|
381
427
|
|
|
382
428
|
.tabs-item-status.status-pending {
|
|
@@ -416,8 +462,96 @@
|
|
|
416
462
|
pointer-events: none;
|
|
417
463
|
}
|
|
418
464
|
|
|
465
|
+
/* ---------- Vertical orientation (nav / menu rail) ---------- */
|
|
466
|
+
.tabs-wrapper.vertical {
|
|
467
|
+
flex-direction: column;
|
|
468
|
+
align-items: stretch;
|
|
469
|
+
max-width: none;
|
|
470
|
+
border-bottom: none;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.tabs-wrapper.vertical .tabs-bar {
|
|
474
|
+
flex-direction: column;
|
|
475
|
+
overflow-x: hidden;
|
|
476
|
+
overflow-y: auto;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.tabs-wrapper.vertical .tabs-item {
|
|
480
|
+
/* Full-width rows: label sits at the leading edge, the status dot's margin-left:auto
|
|
481
|
+
pushes it to the trailing edge (the settings-menu look). */
|
|
482
|
+
width: 100%;
|
|
483
|
+
box-sizing: border-box;
|
|
484
|
+
justify-content: flex-start;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.tabs-bar.fade-top {
|
|
488
|
+
mask-image: linear-gradient(
|
|
489
|
+
to bottom,
|
|
490
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
491
|
+
black var(--tabs-fade-size, 32px)
|
|
492
|
+
);
|
|
493
|
+
-webkit-mask-image: linear-gradient(
|
|
494
|
+
to bottom,
|
|
495
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
496
|
+
black var(--tabs-fade-size, 32px)
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.tabs-bar.fade-bottom {
|
|
501
|
+
mask-image: linear-gradient(
|
|
502
|
+
to top,
|
|
503
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
504
|
+
black var(--tabs-fade-size, 32px)
|
|
505
|
+
);
|
|
506
|
+
-webkit-mask-image: linear-gradient(
|
|
507
|
+
to top,
|
|
508
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
509
|
+
black var(--tabs-fade-size, 32px)
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.tabs-bar.fade-top.fade-bottom {
|
|
514
|
+
mask-image: linear-gradient(
|
|
515
|
+
to bottom,
|
|
516
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
517
|
+
black var(--tabs-fade-size, 32px),
|
|
518
|
+
black calc(100% - var(--tabs-fade-size, 32px)),
|
|
519
|
+
transparent calc(100% - var(--tabs-fade-solid, 8px))
|
|
520
|
+
);
|
|
521
|
+
-webkit-mask-image: linear-gradient(
|
|
522
|
+
to bottom,
|
|
523
|
+
transparent var(--tabs-fade-solid, 8px),
|
|
524
|
+
black var(--tabs-fade-size, 32px),
|
|
525
|
+
black calc(100% - var(--tabs-fade-size, 32px)),
|
|
526
|
+
transparent calc(100% - var(--tabs-fade-solid, 8px))
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.tabs-wrapper.vertical .tabs-indicator {
|
|
531
|
+
top: 0;
|
|
532
|
+
bottom: auto;
|
|
533
|
+
left: 0;
|
|
534
|
+
width: var(--tabs-indicator-height, 2px);
|
|
535
|
+
height: var(--tabs-indicator-height, 2px);
|
|
536
|
+
border-radius: var(--tabs-indicator-border-radius-vertical, 0 2px 2px 0);
|
|
537
|
+
transition: var(--tabs-indicator-transition-vertical, top 0.3s ease, height 0.3s ease);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* Section header rendered above a group of vertical nav items. */
|
|
541
|
+
.tabs-section-label {
|
|
542
|
+
padding: var(--tabs-section-label-padding, 12px 16px 4px);
|
|
543
|
+
font-size: var(--tabs-section-label-font-size, 11px);
|
|
544
|
+
font-weight: var(--tabs-section-label-font-weight, 700);
|
|
545
|
+
letter-spacing: var(--tabs-section-label-letter-spacing, 0.04em);
|
|
546
|
+
text-transform: var(--tabs-section-label-text-transform, uppercase);
|
|
547
|
+
color: var(--tabs-section-label-color, var(--tabs-item-color, #999999));
|
|
548
|
+
white-space: nowrap;
|
|
549
|
+
user-select: none;
|
|
550
|
+
}
|
|
551
|
+
|
|
419
552
|
@media (prefers-reduced-motion: reduce) {
|
|
420
|
-
.tabs-indicator
|
|
553
|
+
.tabs-indicator,
|
|
554
|
+
.tabs-wrapper.vertical .tabs-indicator {
|
|
421
555
|
transition: none;
|
|
422
556
|
}
|
|
423
557
|
}
|
|
@@ -13,9 +13,16 @@ export type TabItem = {
|
|
|
13
13
|
/**
|
|
14
14
|
* Optional status dot rendered after the label — for nav/menu tabs that flag
|
|
15
15
|
* per-item state. `'none'` (default) renders nothing. Colours are themeable via
|
|
16
|
-
* `--tabs-item-status-{pending,error,success}-color`.
|
|
16
|
+
* `--tabs-item-status-{default,pending,error,success}-color`. `'default'` is a
|
|
17
|
+
* neutral highlight dot (blue by default) for "has activity / configured" state.
|
|
17
18
|
*/
|
|
18
|
-
status?: 'none' | 'pending' | 'error' | 'success';
|
|
19
|
+
status?: 'none' | 'default' | 'pending' | 'error' | 'success';
|
|
20
|
+
/**
|
|
21
|
+
* Optional section header rendered ABOVE this item — for grouped vertical nav
|
|
22
|
+
* menus (e.g. a "SETTINGS" / "BODY" divider label). Renders regardless of the
|
|
23
|
+
* `tab` snippet, since it sits outside the item row.
|
|
24
|
+
*/
|
|
25
|
+
sectionLabel?: string;
|
|
19
26
|
};
|
|
20
27
|
export type TabsProperties = MandatoryTabsProperties & OptionalTabsProperties & TabsEventProperties;
|
|
21
28
|
export type MandatoryTabsProperties = {
|
|
@@ -25,6 +32,12 @@ export type OptionalTabsProperties = {
|
|
|
25
32
|
activeIndex?: number;
|
|
26
33
|
activeKey?: string;
|
|
27
34
|
disabled?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Layout axis. `'horizontal'` (default) is the classic tab bar — items in a row,
|
|
37
|
+
* indicator on the bottom edge. `'vertical'` stacks items in a column (a nav/menu
|
|
38
|
+
* rail) — indicator on the leading edge, scroll arrows point up/down.
|
|
39
|
+
*/
|
|
40
|
+
orientation?: 'horizontal' | 'vertical';
|
|
28
41
|
testId?: string;
|
|
29
42
|
scrollLeftIcon?: Snippet;
|
|
30
43
|
scrollRightIcon?: Snippet;
|