@makolabs/ripple 3.0.2 → 3.0.4
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/ai/ai-types.d.ts +7 -0
- package/dist/charts/Chart.svelte +18 -37
- package/dist/charts/chart-types.d.ts +7 -0
- package/dist/drawer/Drawer.svelte +29 -36
- package/dist/drawer/drawer.js +2 -2
- package/dist/elements/combobox/ComboBox.svelte +6 -6
- package/dist/elements/dropdown/Select.svelte +6 -8
- package/dist/elements/popover/Popover.svelte +1 -1
- package/dist/elements/progress/Progress.svelte +18 -5
- package/dist/elements/progress/progress-types.d.ts +8 -0
- package/dist/file-browser/FileBrowser.svelte +6 -3
- package/dist/filters/CompactFilters.svelte +78 -68
- package/dist/filters/filter-types.d.ts +11 -0
- package/dist/forms/DateRange.svelte +31 -20
- package/dist/forms/NumberInput.svelte +6 -2
- package/dist/forms/Tags.svelte +9 -4
- package/dist/forms/Toggle.svelte +8 -6
- package/dist/forms/calendar/Calendar.svelte +8 -2
- package/dist/forms/calendar/calendar-types.d.ts +9 -0
- package/dist/forms/date-picker/DatePicker.svelte +8 -1
- package/dist/forms/form-types.d.ts +23 -0
- package/dist/forms/month-picker/MonthPicker.svelte +12 -3
- package/dist/forms/month-picker/month-picker-types.d.ts +7 -0
- package/dist/header/Breadcrumbs.svelte +19 -5
- package/dist/header/header-types.d.ts +7 -0
- package/dist/layout/activity-list/ActivityList.svelte +29 -7
- package/dist/layout/activity-list/activity-list-types.d.ts +8 -0
- package/dist/layout/card/MetricCard.svelte +34 -8
- package/dist/layout/card/RankedCard.svelte +34 -10
- package/dist/layout/card/card-types.d.ts +8 -0
- package/dist/layout/card/ranked-card.d.ts +10 -0
- package/dist/layout/table/Table.svelte +5 -0
- package/dist/layout/table/table-types.d.ts +8 -0
- package/dist/modal/modal.js +4 -4
- package/dist/pipeline/Pipeline.svelte +7 -3
- package/dist/pipeline/pipeline-types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../helper/testid.js';
|
|
3
4
|
import { Size } from '../variants.js';
|
|
4
5
|
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { DateRangeProps } from '../index.js';
|
|
@@ -23,7 +24,8 @@
|
|
|
23
24
|
size = Size.MD,
|
|
24
25
|
id,
|
|
25
26
|
name,
|
|
26
|
-
onselect
|
|
27
|
+
onselect,
|
|
28
|
+
testId
|
|
27
29
|
}: DateRangeProps = $props();
|
|
28
30
|
|
|
29
31
|
const tokens = $derived(formSizeTokens[size]);
|
|
@@ -249,7 +251,11 @@
|
|
|
249
251
|
<input type="hidden" name={`${name}[format]`} value={format} />
|
|
250
252
|
<input type="hidden" name={`${name}`} value={getValue()} />
|
|
251
253
|
|
|
252
|
-
<div
|
|
254
|
+
<div
|
|
255
|
+
class={cn('relative block w-full', className)}
|
|
256
|
+
bind:this={datePickerRef}
|
|
257
|
+
data-testid={buildTestId('date-range', undefined, testId)}
|
|
258
|
+
>
|
|
253
259
|
<div class="relative">
|
|
254
260
|
<button
|
|
255
261
|
{id}
|
|
@@ -268,6 +274,7 @@
|
|
|
268
274
|
: 'focus-visible:border-primary-500 focus-visible:ring-primary-500 hover:border-default-400 focus-visible:ring-2'
|
|
269
275
|
)}
|
|
270
276
|
onclick={toggleDatepicker}
|
|
277
|
+
data-testid={buildTestId('date-range', 'trigger', testId)}
|
|
271
278
|
aria-haspopup="true"
|
|
272
279
|
aria-expanded={isOpen}
|
|
273
280
|
aria-describedby={errors?.length ? `${id}-errors` : undefined}
|
|
@@ -299,6 +306,7 @@
|
|
|
299
306
|
'right-8'
|
|
300
307
|
)}
|
|
301
308
|
onclick={clearDates}
|
|
309
|
+
data-testid={buildTestId('date-range', 'clear', testId)}
|
|
302
310
|
aria-label="Clear dates"
|
|
303
311
|
>
|
|
304
312
|
<svg class={cn(tokens.iconSize)} viewBox="0 0 20 20" fill="currentColor">
|
|
@@ -313,7 +321,7 @@
|
|
|
313
321
|
</div>
|
|
314
322
|
|
|
315
323
|
{#if errors?.length}
|
|
316
|
-
<div id="{id}-errors">
|
|
324
|
+
<div id="{id}-errors" data-testid={buildTestId('date-range', 'errors', testId)}>
|
|
317
325
|
{#each errors as error, i (i)}
|
|
318
326
|
<p class="text-danger-600 mt-1 text-sm">{error}</p>
|
|
319
327
|
{/each}
|
|
@@ -325,6 +333,7 @@
|
|
|
325
333
|
<div
|
|
326
334
|
bind:this={calendarRef}
|
|
327
335
|
class="ring-opacity-5 ring-default-300 absolute z-10 mt-1 w-full origin-top-left rounded-md bg-white p-4 shadow-lg ring-1 focus:outline-none"
|
|
336
|
+
data-testid={buildTestId('date-range', 'panel', testId)}
|
|
328
337
|
transition:fly={{ y: -8, duration: 300, easing: quintOut }}
|
|
329
338
|
>
|
|
330
339
|
{@render calendarContent()}
|
|
@@ -333,24 +342,26 @@
|
|
|
333
342
|
{/if}
|
|
334
343
|
|
|
335
344
|
{#if isOpen && isMobile}
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<div class="
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
<Portal target={datePickerRef}>
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
class="fixed inset-0 z-[9998] bg-black/40 backdrop-blur-sm"
|
|
349
|
+
aria-label="Close"
|
|
350
|
+
onclick={() => (isOpen = false)}
|
|
351
|
+
></button>
|
|
352
|
+
<div
|
|
353
|
+
class="fixed inset-x-0 bottom-0 z-[9999] flex max-h-[85vh] min-h-48 flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl"
|
|
354
|
+
transition:fly={{ y: 300, duration: 200, easing: quintOut }}
|
|
355
|
+
bind:this={calendarRef}
|
|
356
|
+
>
|
|
357
|
+
<div class="flex justify-center py-2">
|
|
358
|
+
<div class="bg-default-300 h-1 w-8 rounded-full"></div>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="flex-1 cursor-pointer overflow-y-auto p-4">
|
|
361
|
+
{@render calendarContent()}
|
|
362
|
+
</div>
|
|
352
363
|
</div>
|
|
353
|
-
</
|
|
364
|
+
</Portal>
|
|
354
365
|
{/if}
|
|
355
366
|
</div>
|
|
356
367
|
|
|
@@ -154,6 +154,7 @@
|
|
|
154
154
|
<button
|
|
155
155
|
type="button"
|
|
156
156
|
class="hover:bg-default-100 flex items-center gap-1 rounded px-1"
|
|
157
|
+
data-testid={buildTestId('numberinput', 'unit', testId)}
|
|
157
158
|
onclick={handleUnitToggle}
|
|
158
159
|
{disabled}
|
|
159
160
|
>
|
|
@@ -173,7 +174,10 @@
|
|
|
173
174
|
{:else if unit}
|
|
174
175
|
<!-- Static unit label — no chevron, not clickable. Matches the
|
|
175
176
|
field's text size so it sits inline. -->
|
|
176
|
-
<span
|
|
177
|
+
<span
|
|
178
|
+
class={cn('text-default-500 flex items-center gap-1 pr-2', tokens.text)}
|
|
179
|
+
data-testid={buildTestId('numberinput', 'unit', testId)}
|
|
180
|
+
>
|
|
177
181
|
{#if selectedOption?.icon}
|
|
178
182
|
{@const Icon = selectedOption.icon}
|
|
179
183
|
<Icon />
|
|
@@ -183,7 +187,7 @@
|
|
|
183
187
|
{/if}
|
|
184
188
|
|
|
185
189
|
{#if showUnitDropdown && hasMultipleUnits}
|
|
186
|
-
<div class={dropdownClass}>
|
|
190
|
+
<div class={dropdownClass} data-testid={buildTestId('numberinput', 'unit-dropdown', testId)}>
|
|
187
191
|
{#each units as unitOption (unitOption.value)}
|
|
188
192
|
<button
|
|
189
193
|
type="button"
|
package/dist/forms/Tags.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Badge from '../elements/badge/Badge.svelte';
|
|
3
3
|
import { Size } from '../variants.js';
|
|
4
4
|
import { cn } from '../helper/cls.js';
|
|
5
|
+
import { buildTestId } from '../helper/testid.js';
|
|
5
6
|
import { formSizeTokens } from './form-size.js';
|
|
6
7
|
import { fade } from 'svelte/transition';
|
|
7
8
|
import { flip } from 'svelte/animate';
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
class: className = '',
|
|
19
20
|
suggestions = [],
|
|
20
21
|
onaddtag: onAddTag,
|
|
21
|
-
onremovetag: onRemoveTag
|
|
22
|
+
onremovetag: onRemoveTag,
|
|
23
|
+
testId
|
|
22
24
|
}: TagsProps = $props();
|
|
23
25
|
|
|
24
26
|
let inputValue = $state('');
|
|
@@ -154,16 +156,17 @@
|
|
|
154
156
|
);
|
|
155
157
|
</script>
|
|
156
158
|
|
|
157
|
-
<div class="space-y-1">
|
|
159
|
+
<div class="space-y-1" data-testid={buildTestId('tags', undefined, testId)}>
|
|
158
160
|
{#if label}
|
|
159
161
|
<label for={name} class="text-default-700 block text-sm font-medium">{label}</label>
|
|
160
162
|
{/if}
|
|
161
163
|
<div class={containerClass} onfocusout={handleFocusOut}>
|
|
162
|
-
{#each value as tag (tag)}
|
|
164
|
+
{#each value as tag, index (tag)}
|
|
163
165
|
<div
|
|
164
166
|
class="inline-flex"
|
|
165
167
|
transition:fade={{ duration: 250, easing: quintOut }}
|
|
166
168
|
animate:flip={{ duration: 300, easing: quintOut }}
|
|
169
|
+
data-testid={buildTestId('tags', 'tag', testId, index)}
|
|
167
170
|
>
|
|
168
171
|
<Badge size={chipSize} color="info" onclose={() => handleTagRemoval(tag)} class="shadow-xs">
|
|
169
172
|
{tag}
|
|
@@ -184,10 +187,11 @@
|
|
|
184
187
|
autocomplete="off"
|
|
185
188
|
onkeydown={handleKeydown}
|
|
186
189
|
onfocus={handleFocusIn}
|
|
190
|
+
data-testid={buildTestId('tags', 'input', testId)}
|
|
187
191
|
/>
|
|
188
192
|
|
|
189
193
|
{#if showSuggestions && filteredSuggestions.length > 0}
|
|
190
|
-
<div class={suggestionClass}>
|
|
194
|
+
<div class={suggestionClass} data-testid={buildTestId('tags', 'suggestions', testId)}>
|
|
191
195
|
{#each filteredSuggestions as suggestion, i (suggestion)}
|
|
192
196
|
<button
|
|
193
197
|
type="button"
|
|
@@ -196,6 +200,7 @@
|
|
|
196
200
|
onmouseover={() => handleSuggestionHover(i)}
|
|
197
201
|
onfocus={() => handleSuggestionHover(i)}
|
|
198
202
|
tabindex="-1"
|
|
203
|
+
data-testid={buildTestId('tags', 'suggestion', testId, i)}
|
|
199
204
|
>
|
|
200
205
|
{suggestion}
|
|
201
206
|
</button>
|
package/dist/forms/Toggle.svelte
CHANGED
|
@@ -68,15 +68,17 @@
|
|
|
68
68
|
)[size]
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
+
// On-position = track_width − thumb_width − 2px margin.
|
|
72
|
+
// Off-position = 2px margin (translate-x-0.5).
|
|
71
73
|
const thumbPosition = $derived(
|
|
72
74
|
(
|
|
73
75
|
{
|
|
74
|
-
[Size.XS]: value ? 'translate-x-
|
|
75
|
-
[Size.SM]: value ? 'translate-x-
|
|
76
|
-
[Size.MD]: value ? 'translate-x-
|
|
77
|
-
[Size.LG]: value ? 'translate-x-
|
|
78
|
-
[Size.XL]: value ? 'translate-x-
|
|
79
|
-
[Size.XXL]: value ? 'translate-x-
|
|
76
|
+
[Size.XS]: value ? 'translate-x-4' : 'translate-x-0.5',
|
|
77
|
+
[Size.SM]: value ? 'translate-x-[18px]' : 'translate-x-0.5',
|
|
78
|
+
[Size.MD]: value ? 'translate-x-[22px]' : 'translate-x-0.5',
|
|
79
|
+
[Size.LG]: value ? 'translate-x-[26px]' : 'translate-x-0.5',
|
|
80
|
+
[Size.XL]: value ? 'translate-x-[30px]' : 'translate-x-0.5',
|
|
81
|
+
[Size.XXL]: value ? 'translate-x-[30px]' : 'translate-x-0.5'
|
|
80
82
|
} satisfies Record<VariantSizes, string>
|
|
81
83
|
)[size]
|
|
82
84
|
);
|
|
@@ -230,7 +230,6 @@
|
|
|
230
230
|
class={cn(
|
|
231
231
|
'inline-block bg-white select-none',
|
|
232
232
|
'border-default-200 rounded-lg border shadow-xs',
|
|
233
|
-
'max-sm:w-full max-sm:rounded-none max-sm:border-0 max-sm:shadow-none',
|
|
234
233
|
density.panel,
|
|
235
234
|
density.padding,
|
|
236
235
|
className
|
|
@@ -247,6 +246,7 @@
|
|
|
247
246
|
density.navBtn
|
|
248
247
|
)}
|
|
249
248
|
aria-label="Previous month"
|
|
249
|
+
data-testid={buildTestId('calendar', 'prev-month', testId)}
|
|
250
250
|
{disabled}
|
|
251
251
|
>
|
|
252
252
|
<svg class={density.navIcon} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
@@ -257,7 +257,10 @@
|
|
|
257
257
|
/>
|
|
258
258
|
</svg>
|
|
259
259
|
</button>
|
|
260
|
-
<span
|
|
260
|
+
<span
|
|
261
|
+
class={cn('text-default-800 font-semibold', density.monthText)}
|
|
262
|
+
data-testid={buildTestId('calendar', 'month-label', testId)}>{monthLabel}</span
|
|
263
|
+
>
|
|
261
264
|
<button
|
|
262
265
|
type="button"
|
|
263
266
|
onclick={nextMonth}
|
|
@@ -266,6 +269,7 @@
|
|
|
266
269
|
density.navBtn
|
|
267
270
|
)}
|
|
268
271
|
aria-label="Next month"
|
|
272
|
+
data-testid={buildTestId('calendar', 'next-month', testId)}
|
|
269
273
|
{disabled}
|
|
270
274
|
>
|
|
271
275
|
<svg class={density.navIcon} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
@@ -284,6 +288,7 @@
|
|
|
284
288
|
'text-default-400 mb-1 grid grid-cols-7 gap-0.5 text-center font-medium',
|
|
285
289
|
density.dayHeaderText
|
|
286
290
|
)}
|
|
291
|
+
data-testid={buildTestId('calendar', 'day-headers', testId)}
|
|
287
292
|
>
|
|
288
293
|
{#each dayHeaders() as d (d)}
|
|
289
294
|
<div>{d}</div>
|
|
@@ -298,6 +303,7 @@
|
|
|
298
303
|
disabled={cell.disabled}
|
|
299
304
|
aria-pressed={cell.isSelected}
|
|
300
305
|
aria-label={cell.date.toLocaleDateString()}
|
|
306
|
+
data-testid={buildTestId('calendar', 'day', testId, cell.date.getDate())}
|
|
301
307
|
class={cn(
|
|
302
308
|
'relative flex items-center justify-center rounded transition-colors',
|
|
303
309
|
density.cell,
|
|
@@ -50,5 +50,14 @@ export type CalendarProps = {
|
|
|
50
50
|
from: Date | null;
|
|
51
51
|
to: Date | null;
|
|
52
52
|
}) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
55
|
+
* - `{testId}-calendar` — root wrapper
|
|
56
|
+
* - `{testId}-calendar-prev-month` — prev button
|
|
57
|
+
* - `{testId}-calendar-next-month` — next button
|
|
58
|
+
* - `{testId}-calendar-month-label` — month/year header
|
|
59
|
+
* - `{testId}-calendar-day-headers` — weekday row
|
|
60
|
+
* - `{testId}-calendar-day-{dayNumber}` — each day cell
|
|
61
|
+
*/
|
|
53
62
|
testId?: string;
|
|
54
63
|
};
|
|
@@ -127,7 +127,14 @@
|
|
|
127
127
|
</button>
|
|
128
128
|
|
|
129
129
|
{#snippet content()}
|
|
130
|
-
<Calendar
|
|
130
|
+
<Calendar
|
|
131
|
+
{value}
|
|
132
|
+
{minDate}
|
|
133
|
+
{maxDate}
|
|
134
|
+
{size}
|
|
135
|
+
class="max-sm:w-full max-sm:rounded-none max-sm:border-0 max-sm:shadow-none"
|
|
136
|
+
onselect={(d) => handleSelect(d as Date)}
|
|
137
|
+
/>
|
|
131
138
|
{/snippet}
|
|
132
139
|
</Popover>
|
|
133
140
|
|
|
@@ -280,6 +280,13 @@ export type NumberInputProps = {
|
|
|
280
280
|
* BCP 47 locale used for thousands-separator formatting. @default 'en-US'
|
|
281
281
|
*/
|
|
282
282
|
locale?: string;
|
|
283
|
+
/**
|
|
284
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
285
|
+
* - `{testId}-numberinput` — root input
|
|
286
|
+
* - `{testId}-numberinput-label` — label
|
|
287
|
+
* - `{testId}-numberinput-unit` — unit label/button
|
|
288
|
+
* - `{testId}-numberinput-unit-dropdown` — unit dropdown
|
|
289
|
+
*/
|
|
283
290
|
testId?: string;
|
|
284
291
|
};
|
|
285
292
|
/**
|
|
@@ -334,6 +341,14 @@ export interface DateRangeProps {
|
|
|
334
341
|
}) => void;
|
|
335
342
|
id?: string;
|
|
336
343
|
name?: string;
|
|
344
|
+
/**
|
|
345
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
346
|
+
* - `{testId}-date-range` — root wrapper
|
|
347
|
+
* - `{testId}-date-range-trigger` — trigger button
|
|
348
|
+
* - `{testId}-date-range-clear` — clear button
|
|
349
|
+
* - `{testId}-date-range-errors` — error container
|
|
350
|
+
* - `{testId}-date-range-panel` — calendar panel
|
|
351
|
+
*/
|
|
337
352
|
testId?: string;
|
|
338
353
|
}
|
|
339
354
|
/**
|
|
@@ -379,6 +394,14 @@ export type TagsProps = {
|
|
|
379
394
|
onaddtag?: (tag: string) => void;
|
|
380
395
|
/** Fires when a tag is removed (via × or backspace). */
|
|
381
396
|
onremovetag?: (tag: string) => void;
|
|
397
|
+
/**
|
|
398
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
399
|
+
* - `{testId}-tags` — root wrapper
|
|
400
|
+
* - `{testId}-tags-input` — text input
|
|
401
|
+
* - `{testId}-tags-tag-{i}` — each tag badge
|
|
402
|
+
* - `{testId}-tags-suggestions` — dropdown
|
|
403
|
+
* - `{testId}-tags-suggestion-{i}` — each suggestion
|
|
404
|
+
*/
|
|
382
405
|
testId?: string;
|
|
383
406
|
};
|
|
384
407
|
/** Selection mode for `<Slider>`. */
|
|
@@ -118,7 +118,10 @@
|
|
|
118
118
|
|
|
119
119
|
function setThisMonth() {
|
|
120
120
|
const now = new Date();
|
|
121
|
-
|
|
121
|
+
const d = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
122
|
+
if (minDate && d < new Date(minDate.getFullYear(), minDate.getMonth(), 1)) return;
|
|
123
|
+
if (maxDate && d > new Date(maxDate.getFullYear(), maxDate.getMonth(), 1)) return;
|
|
124
|
+
value = d;
|
|
122
125
|
onselect?.(value);
|
|
123
126
|
open = false;
|
|
124
127
|
}
|
|
@@ -135,14 +138,18 @@
|
|
|
135
138
|
});
|
|
136
139
|
</script>
|
|
137
140
|
|
|
138
|
-
<div class={cn('w-full', className)}>
|
|
141
|
+
<div class={cn('w-full', className)} data-testid={buildTestId('month-picker', 'root', testId)}>
|
|
139
142
|
{#if label}
|
|
140
143
|
<label for={id} class="text-default-700 mb-1 block text-sm font-medium">
|
|
141
144
|
{label}
|
|
142
145
|
</label>
|
|
143
146
|
{/if}
|
|
144
147
|
|
|
145
|
-
<input
|
|
148
|
+
<input
|
|
149
|
+
type="hidden"
|
|
150
|
+
{name}
|
|
151
|
+
value={value ? `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}` : ''}
|
|
152
|
+
/>
|
|
146
153
|
|
|
147
154
|
<Popover trigger="manual" bind:open placement="bottom" {disabled}>
|
|
148
155
|
<button
|
|
@@ -239,6 +246,7 @@
|
|
|
239
246
|
yearSelected ? 'bg-primary-50 text-primary-700' : 'bg-default-50 text-default-800'
|
|
240
247
|
)}
|
|
241
248
|
data-year={year}
|
|
249
|
+
data-testid={buildTestId('month-picker', 'year', testId, year)}
|
|
242
250
|
>
|
|
243
251
|
{year}
|
|
244
252
|
</div>
|
|
@@ -251,6 +259,7 @@
|
|
|
251
259
|
<button
|
|
252
260
|
type="button"
|
|
253
261
|
data-month={m}
|
|
262
|
+
data-testid={buildTestId('month-picker', 'month', testId, `${year}-${m}`)}
|
|
254
263
|
data-selected={sel || undefined}
|
|
255
264
|
data-current={cur || undefined}
|
|
256
265
|
onclick={() => entry && pick(entry)}
|
|
@@ -18,5 +18,12 @@ export type MonthPickerProps = {
|
|
|
18
18
|
errors?: string[];
|
|
19
19
|
class?: ClassValue;
|
|
20
20
|
onselect?: (value: Date | null) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
23
|
+
* - `{testId}-month-picker-root` — root wrapper
|
|
24
|
+
* - `{testId}-month-picker` — trigger button
|
|
25
|
+
* - `{testId}-month-picker-year-{year}` — year header
|
|
26
|
+
* - `{testId}-month-picker-month-{year}-{month}` — month cell
|
|
27
|
+
*/
|
|
21
28
|
testId?: string;
|
|
22
29
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../helper/testid.js';
|
|
3
4
|
import { breadcrumbs } from './breadcrumbs.js';
|
|
4
5
|
import type { BreadcrumbsProps } from '../index.js';
|
|
5
6
|
import { resolve } from '$app/paths';
|
|
@@ -14,7 +15,8 @@
|
|
|
14
15
|
listClass = '',
|
|
15
16
|
itemClass = '',
|
|
16
17
|
separatorClass = '',
|
|
17
|
-
wrapperClass = ''
|
|
18
|
+
wrapperClass = '',
|
|
19
|
+
testId
|
|
18
20
|
}: BreadcrumbsProps = $props();
|
|
19
21
|
|
|
20
22
|
const { base, list, item, separator, wrapper } = $derived(
|
|
@@ -33,10 +35,14 @@
|
|
|
33
35
|
const wrapperClasses = $derived(cn(wrapper(), wrapperClass));
|
|
34
36
|
</script>
|
|
35
37
|
|
|
36
|
-
<nav
|
|
38
|
+
<nav
|
|
39
|
+
class={baseClass}
|
|
40
|
+
aria-label="Breadcrumb"
|
|
41
|
+
data-testid={buildTestId('breadcrumbs', undefined, testId)}
|
|
42
|
+
>
|
|
37
43
|
<ol role="list" class={listClasses}>
|
|
38
44
|
{#each items as breadcrumbItem, index (index)}
|
|
39
|
-
<li>
|
|
45
|
+
<li data-testid={buildTestId('breadcrumbs', 'item', testId, index)}>
|
|
40
46
|
<div class={wrapperClasses}>
|
|
41
47
|
{#if index > 0}
|
|
42
48
|
<span class={separatorClasses} aria-hidden="true">
|
|
@@ -58,7 +64,11 @@
|
|
|
58
64
|
</span>
|
|
59
65
|
{/if}
|
|
60
66
|
{#if breadcrumbItem.current}
|
|
61
|
-
<span
|
|
67
|
+
<span
|
|
68
|
+
class={activeItemClasses}
|
|
69
|
+
aria-current="page"
|
|
70
|
+
data-testid={buildTestId('breadcrumbs', 'current', testId, index)}
|
|
71
|
+
>
|
|
62
72
|
{#if breadcrumbItem.icon}
|
|
63
73
|
{@const ItemIcon = breadcrumbItem.icon}
|
|
64
74
|
<ItemIcon class="size-4 flex-shrink-0" />
|
|
@@ -66,7 +76,11 @@
|
|
|
66
76
|
{breadcrumbItem.label}
|
|
67
77
|
</span>
|
|
68
78
|
{:else}
|
|
69
|
-
<a
|
|
79
|
+
<a
|
|
80
|
+
href={resolve(breadcrumbItem.href as `/`)}
|
|
81
|
+
class={itemClasses}
|
|
82
|
+
data-testid={buildTestId('breadcrumbs', 'link', testId, index)}
|
|
83
|
+
>
|
|
70
84
|
{#if breadcrumbItem.icon}
|
|
71
85
|
{@const ItemIcon = breadcrumbItem.icon}
|
|
72
86
|
<ItemIcon class="size-4 flex-shrink-0" />
|
|
@@ -49,6 +49,13 @@ export type BreadcrumbsProps = {
|
|
|
49
49
|
separatorClass?: ClassValue;
|
|
50
50
|
/** Classes on the inner item wrapper (icon + label). */
|
|
51
51
|
wrapperClass?: ClassValue;
|
|
52
|
+
/**
|
|
53
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
54
|
+
* - `{testId}-breadcrumbs` — root nav
|
|
55
|
+
* - `{testId}-breadcrumbs-item-{i}` — each item
|
|
56
|
+
* - `{testId}-breadcrumbs-link-{i}` — link element
|
|
57
|
+
* - `{testId}-breadcrumbs-current-{i}` — current page
|
|
58
|
+
*/
|
|
52
59
|
testId?: string;
|
|
53
60
|
};
|
|
54
61
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../../helper/testid.js';
|
|
3
4
|
import {
|
|
4
5
|
activityList,
|
|
5
6
|
iconCircleClasses,
|
|
@@ -25,7 +26,8 @@
|
|
|
25
26
|
itemClass = '',
|
|
26
27
|
onitemclick,
|
|
27
28
|
children,
|
|
28
|
-
customContent
|
|
29
|
+
customContent,
|
|
30
|
+
testId
|
|
29
31
|
}: ActivityListProps = $props();
|
|
30
32
|
|
|
31
33
|
const {
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
const contentClasses = $derived(cn(content(), contentClass, timeline && 'relative divide-y-0'));
|
|
55
57
|
</script>
|
|
56
58
|
|
|
57
|
-
<div class={baseClass}>
|
|
59
|
+
<div class={baseClass} data-testid={buildTestId('activity-list', undefined, testId)}>
|
|
58
60
|
{#if title}
|
|
59
61
|
<div class={headerClasses}>
|
|
60
62
|
<h2 class={titleClasses}>{title}</h2>
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
<div
|
|
86
88
|
class={cn(item(), highlightClass, itemClass)}
|
|
87
89
|
data-activity-row=""
|
|
90
|
+
data-testid={buildTestId('activity-list', 'item', testId, index)}
|
|
88
91
|
data-activity-highlighted={activityItem.highlighted ? '' : undefined}
|
|
89
92
|
>
|
|
90
93
|
{#if accentBarColor}
|
|
@@ -113,9 +116,13 @@
|
|
|
113
116
|
data-activity-comment=""
|
|
114
117
|
>
|
|
115
118
|
<div class="mb-1 flex justify-between gap-x-4">
|
|
116
|
-
<div
|
|
119
|
+
<div
|
|
120
|
+
class="flex flex-wrap items-center gap-2"
|
|
121
|
+
data-testid={buildTestId('activity-list', 'badges', testId, index)}
|
|
122
|
+
>
|
|
117
123
|
<button
|
|
118
124
|
class="text-default-900 cursor-pointer text-sm font-medium"
|
|
125
|
+
data-testid={buildTestId('activity-list', 'title', testId, index)}
|
|
119
126
|
onclick={() => onitemclick?.(activityItem, index)}
|
|
120
127
|
>
|
|
121
128
|
{activityItem.title}
|
|
@@ -140,7 +147,11 @@
|
|
|
140
147
|
{/if}
|
|
141
148
|
</div>
|
|
142
149
|
{#if activityItem.timestamp}
|
|
143
|
-
<div
|
|
150
|
+
<div
|
|
151
|
+
class={itemTimestamp()}
|
|
152
|
+
data-activity-timestamp=""
|
|
153
|
+
data-testid={buildTestId('activity-list', 'timestamp', testId, index)}
|
|
154
|
+
>
|
|
144
155
|
{activityItem.timestamp}
|
|
145
156
|
</div>
|
|
146
157
|
{/if}
|
|
@@ -166,8 +177,15 @@
|
|
|
166
177
|
{:else}
|
|
167
178
|
<div class={itemContent()}>
|
|
168
179
|
<div class={itemMain()}>
|
|
169
|
-
<div
|
|
170
|
-
|
|
180
|
+
<div
|
|
181
|
+
class={itemHeader()}
|
|
182
|
+
data-testid={buildTestId('activity-list', 'badges', testId, index)}
|
|
183
|
+
>
|
|
184
|
+
<button
|
|
185
|
+
class={itemTitle()}
|
|
186
|
+
data-testid={buildTestId('activity-list', 'title', testId, index)}
|
|
187
|
+
onclick={() => onitemclick?.(activityItem, index)}
|
|
188
|
+
>
|
|
171
189
|
{activityItem.title}
|
|
172
190
|
</button>
|
|
173
191
|
{#if activityItem.badges}
|
|
@@ -197,7 +215,11 @@
|
|
|
197
215
|
</div>
|
|
198
216
|
|
|
199
217
|
{#if activityItem.timestamp}
|
|
200
|
-
<div
|
|
218
|
+
<div
|
|
219
|
+
class={itemTimestamp()}
|
|
220
|
+
data-activity-timestamp=""
|
|
221
|
+
data-testid={buildTestId('activity-list', 'timestamp', testId, index)}
|
|
222
|
+
>
|
|
201
223
|
{activityItem.timestamp}
|
|
202
224
|
</div>
|
|
203
225
|
{/if}
|
|
@@ -145,5 +145,13 @@ export type ActivityListProps = {
|
|
|
145
145
|
* `custom: true` continue to use the default rendering.
|
|
146
146
|
*/
|
|
147
147
|
customContent?: Snippet<[ActivityItem, number]>;
|
|
148
|
+
/**
|
|
149
|
+
* Test ID prefix. When set, the component emits these selectors:
|
|
150
|
+
* - `{testId}-activity-list` — root wrapper
|
|
151
|
+
* - `{testId}-activity-list-item-{i}` — each item
|
|
152
|
+
* - `{testId}-activity-list-title-{i}` — item title
|
|
153
|
+
* - `{testId}-activity-list-timestamp-{i}` — timestamp
|
|
154
|
+
* - `{testId}-activity-list-badges-{i}` — badges container
|
|
155
|
+
*/
|
|
148
156
|
testId?: string;
|
|
149
157
|
};
|