@juspay/svelte-ui-components 2.130.1 → 2.131.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,268 @@
1
+ <script lang="ts">
2
+ import type { Action } from 'svelte/action';
3
+ import Loader from '../Loader/Loader.svelte';
4
+ import Button from '../Button/Button.svelte';
5
+ import type { TaskListProperties } from './properties';
6
+
7
+ let { rows, onretry, testId, classes }: TaskListProperties = $props();
8
+
9
+ // Rows appended in one update stagger relative to the batch, not the list start —
10
+ // mirrors ThinkingTrace's growthWatcher so a host that streams rows in gets the
11
+ // same "only the new ones animate in" behaviour.
12
+ let staggerBase = $state(0);
13
+
14
+ const growthWatcher: Action<HTMLElement, number> = (_node, initialCount) => {
15
+ let previousCount = initialCount;
16
+ return {
17
+ update(count: number): void {
18
+ if (count > previousCount) {
19
+ staggerBase = previousCount;
20
+ }
21
+ previousCount = count;
22
+ }
23
+ };
24
+ };
25
+
26
+ const rowDelay = (index: number): string => {
27
+ return `${Math.max(0, index - staggerBase) * 120}ms`;
28
+ };
29
+
30
+ const handleRetry = (index: number): void => {
31
+ onretry?.(index);
32
+ };
33
+ </script>
34
+
35
+ <div
36
+ class="task-list {classes ?? ''}"
37
+ use:growthWatcher={rows.length}
38
+ data-pw={typeof testId === 'string' ? testId : null}
39
+ testID={typeof testId === 'string' ? testId : null}
40
+ >
41
+ {#each rows as row, index (index)}
42
+ <div
43
+ class="task-row"
44
+ class:pending={row.status === 'pending'}
45
+ style:animation-delay={rowDelay(index)}
46
+ data-pw={typeof testId === 'string' ? `${testId}-row-${index}` : null}
47
+ testID={typeof testId === 'string' ? `${testId}-row-${index}` : null}
48
+ >
49
+ <span class="row-glyph">
50
+ {#key row.status}
51
+ <span class="glyph-inner" data-status={row.status} aria-hidden="true">
52
+ {#if row.status === 'pending'}
53
+ <span class="glyph-dot"></span>
54
+ {:else if row.status === 'running'}
55
+ <span class="glyph-spinner">
56
+ <Loader />
57
+ </span>
58
+ {:else if row.status === 'done'}
59
+ <span class="glyph-check">
60
+ <svg
61
+ viewBox="0 0 24 24"
62
+ fill="none"
63
+ stroke="currentColor"
64
+ stroke-width="2.4"
65
+ stroke-linecap="round"
66
+ stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg
67
+ >
68
+ </span>
69
+ {:else if row.status === 'failed'}
70
+ <span class="glyph-x">
71
+ <svg
72
+ viewBox="0 0 24 24"
73
+ fill="none"
74
+ stroke="currentColor"
75
+ stroke-width="2.4"
76
+ stroke-linecap="round"
77
+ stroke-linejoin="round"><path d="M6 6l12 12M18 6 6 18" /></svg
78
+ >
79
+ </span>
80
+ {/if}
81
+ </span>
82
+ {/key}
83
+ </span>
84
+
85
+ <span class="row-text">
86
+ <span class="row-label">{row.label}</span>
87
+ {#if typeof row.secondary === 'string' && row.secondary.length > 0}
88
+ <span class="row-secondary" class:mono={row.mono}>{row.secondary}</span>
89
+ {/if}
90
+ </span>
91
+
92
+ {#if row.status === 'failed' && typeof row.retryLabel === 'string' && row.retryLabel.length > 0}
93
+ <div class="row-retry">
94
+ <Button
95
+ text={row.retryLabel}
96
+ size="sm"
97
+ onclick={() => handleRetry(index)}
98
+ {...typeof testId === 'string' ? { testId: `${testId}-row-${index}-retry` } : {}}
99
+ />
100
+ </div>
101
+ {/if}
102
+ </div>
103
+ {/each}
104
+ </div>
105
+
106
+ <style>
107
+ @keyframes task-list-fade-up {
108
+ from {
109
+ opacity: 0;
110
+ transform: translateY(9px);
111
+ }
112
+ to {
113
+ opacity: 1;
114
+ transform: none;
115
+ }
116
+ }
117
+ @keyframes task-list-glyph-fade {
118
+ from {
119
+ opacity: 0;
120
+ }
121
+ to {
122
+ opacity: 1;
123
+ }
124
+ }
125
+ @keyframes task-list-check-pop {
126
+ from {
127
+ opacity: 0;
128
+ transform: scale(0.5);
129
+ }
130
+ to {
131
+ opacity: 1;
132
+ transform: scale(1);
133
+ }
134
+ }
135
+
136
+ .task-list {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: var(--task-list-row-gap, 10px);
140
+ }
141
+
142
+ .task-row {
143
+ display: flex;
144
+ align-items: baseline;
145
+ gap: var(--task-list-row-inline-gap, 10px);
146
+ animation: task-list-fade-up 320ms var(--task-list-ease, cubic-bezier(0.23, 1, 0.32, 1)) both;
147
+ }
148
+
149
+ .row-glyph {
150
+ display: inline-flex;
151
+ align-self: center;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: var(--task-list-glyph-size, 16px);
155
+ height: var(--task-list-glyph-size, 16px);
156
+ flex-shrink: 0;
157
+ }
158
+
159
+ .glyph-inner {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ animation: task-list-glyph-fade 150ms ease both;
164
+ }
165
+
166
+ .glyph-dot {
167
+ width: 6px;
168
+ height: 6px;
169
+ border-radius: 50%;
170
+ background: var(--task-list-dot-color, #9a9a9a);
171
+ }
172
+
173
+ /* Loader draws its own ring via a rotating gradient rather than a bordered arc, so the old
174
+ track/spinner two-tone maps onto the gradient's start/end stops: --loader-foreground is the
175
+ bright leading edge, --loader-foreground-end the dimmer trailing one. The size vars are
176
+ calc()'d off a single token so before/after stay proportional if a consumer overrides it. */
177
+ .glyph-spinner {
178
+ display: inline-flex;
179
+ --loader-foreground: var(--task-list-spinner-color, #6b6b6b);
180
+ --loader-foreground-end: var(--task-list-spinner-track-color, #dcdcdc);
181
+ --loader-width: var(--task-list-spinner-size, 11px);
182
+ --loader-height: var(--task-list-spinner-size, 11px);
183
+ --loader-before-width: calc(var(--task-list-spinner-size, 11px) * 0.5);
184
+ --loader-before-height: calc(var(--task-list-spinner-size, 11px) * 0.5);
185
+ --loader-after-width: calc(var(--task-list-spinner-size, 11px) * 0.75);
186
+ --loader-after-height: calc(var(--task-list-spinner-size, 11px) * 0.75);
187
+ }
188
+
189
+ .glyph-check,
190
+ .glyph-x {
191
+ display: inline-flex;
192
+ }
193
+ .glyph-check svg,
194
+ .glyph-x svg {
195
+ width: 12px;
196
+ height: 12px;
197
+ }
198
+ .glyph-check {
199
+ color: var(--task-list-done-color, #6b6b6b);
200
+ animation: task-list-check-pop 200ms var(--task-list-ease, cubic-bezier(0.23, 1, 0.32, 1)) both;
201
+ }
202
+ .glyph-x {
203
+ color: var(--task-list-error-color, #c93f38);
204
+ }
205
+
206
+ .row-text {
207
+ flex: 1;
208
+ min-width: 0;
209
+ display: flex;
210
+ align-items: baseline;
211
+ gap: var(--task-list-text-gap, 8px);
212
+ }
213
+ .task-row.pending .row-text {
214
+ opacity: var(--task-list-pending-opacity, 0.55);
215
+ }
216
+
217
+ .row-label {
218
+ font-size: var(--task-list-row-font-size, 0.875rem);
219
+ font-weight: var(--task-list-row-weight, 500);
220
+ color: var(--task-list-row-color, #2b2b2b);
221
+ white-space: nowrap;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ }
225
+
226
+ .row-secondary {
227
+ font-size: var(--task-list-secondary-font-size, 0.75rem);
228
+ color: var(--task-list-secondary-color, #9a9a9a);
229
+ white-space: nowrap;
230
+ }
231
+ .row-secondary.mono {
232
+ font-family: var(--task-list-mono-font, ui-monospace, Menlo, monospace);
233
+ }
234
+
235
+ /* Themes the embedded Button into the compact error-tinted pill via its own custom
236
+ properties — these inherit down into Button's internal rules regardless of the
237
+ component boundary, the same wrapper-div pattern Banner uses for its dismiss button. */
238
+ .row-retry {
239
+ margin-left: auto;
240
+ flex-shrink: 0;
241
+ align-self: center;
242
+ --button-color: var(--task-list-retry-background, rgba(201, 63, 56, 0.12));
243
+ --button-text-color: var(--task-list-retry-color, #c93f38);
244
+ --button-hover-color: var(--task-list-retry-hover-background, rgba(201, 63, 56, 0.2));
245
+ --button-hover-text-color: var(--task-list-retry-color, #c93f38);
246
+ --button-border: none;
247
+ --button-padding: var(--task-list-retry-padding, 3px 10px);
248
+ --button-border-radius: var(--task-list-retry-radius, 999px);
249
+ --button-font-size: var(--task-list-retry-font-size, 0.75rem);
250
+ --button-font-weight: var(--task-list-retry-weight, 600);
251
+ --button-transition: background 150ms ease;
252
+ }
253
+
254
+ @media (prefers-reduced-motion: reduce) {
255
+ .task-row,
256
+ .glyph-inner,
257
+ .glyph-check {
258
+ animation-duration: 0.001s;
259
+ }
260
+ /* Loader's spin lives on its own internally-scoped element, reached here via :global()
261
+ scoped to a descendant of our local .glyph-spinner wrapper — the 3-class specificity
262
+ that produces reliably beats Loader's own 2-class rule regardless of stylesheet order. */
263
+ .glyph-spinner :global(.loader) {
264
+ animation: none;
265
+ -webkit-animation: none;
266
+ }
267
+ }
268
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { TaskListProperties } from './properties';
2
+ declare const TaskList: import("svelte").Component<TaskListProperties, {}, "">;
3
+ type TaskList = ReturnType<typeof TaskList>;
4
+ export default TaskList;
@@ -0,0 +1,24 @@
1
+ export type TaskStatus = 'pending' | 'running' | 'failed' | 'done';
2
+ export type TaskListRow = {
3
+ /** The task/step description. */
4
+ label: string;
5
+ /** A count, file, or command shown after the label. */
6
+ secondary?: string;
7
+ /** Render `secondary` in the mono face (filenames, commands). */
8
+ mono?: boolean;
9
+ /** Current state of the row's status machine. */
10
+ status: TaskStatus;
11
+ /** Failed rows: when set, renders an inline retry button with this text. */
12
+ retryLabel?: string;
13
+ };
14
+ export type TaskListProperties = MandatoryTaskListProperties & OptionalTaskListProperties;
15
+ export type MandatoryTaskListProperties = {
16
+ /** The work-plan rows, host-owned. Append rows or flip `status` in place — the component never invents its own timings. */
17
+ rows: TaskListRow[];
18
+ };
19
+ export type OptionalTaskListProperties = {
20
+ /** Fires when a failed row's retry button is clicked, with that row's index. */
21
+ onretry?: (index: number) => void;
22
+ testId?: string;
23
+ classes?: string;
24
+ };
@@ -0,0 +1 @@
1
+ export {};