@juspay/svelte-ui-components 2.28.2 → 2.28.3
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/Banner/Banner.svelte +34 -7
- package/dist/Banner/properties.d.ts +2 -0
- package/dist/Breadcrumb/Breadcrumb.svelte +58 -0
- package/dist/Breadcrumb/Breadcrumb.svelte.d.ts +4 -0
- package/dist/Breadcrumb/properties.d.ts +19 -0
- package/dist/Breadcrumb/properties.js +1 -0
- package/dist/Calendar/Calendar.svelte +10 -4
- package/dist/Calendar/properties.d.ts +2 -0
- package/dist/DateRangePicker/DateRangePicker.svelte +627 -0
- package/dist/DateRangePicker/DateRangePicker.svelte.d.ts +4 -0
- package/dist/DateRangePicker/properties.d.ts +74 -0
- package/dist/DateRangePicker/properties.js +1 -0
- package/dist/EmptyState/EmptyState.svelte +3 -1
- package/dist/EmptyState/properties.d.ts +1 -1
- package/dist/FileInput/FileInput.svelte +176 -0
- package/dist/FileInput/FileInput.svelte.d.ts +4 -0
- package/dist/FileInput/properties.d.ts +18 -0
- package/dist/FileInput/properties.js +1 -0
- package/dist/Modal/Modal.svelte +5 -4
- package/dist/Pagination/Pagination.svelte +56 -11
- package/dist/Pagination/properties.d.ts +15 -0
- package/dist/Pill/Pill.svelte +10 -0
- package/dist/Pill/properties.d.ts +7 -0
- package/dist/Select/Select.svelte +69 -29
- package/dist/Select/Select.svelte.d.ts +1 -1
- package/dist/Select/properties.d.ts +8 -1
- package/dist/Sheet/Sheet.svelte +16 -0
- package/dist/Sheet/properties.d.ts +4 -0
- package/dist/Table/Table.svelte +28 -6
- package/dist/Table/properties.d.ts +6 -0
- package/dist/Tabs/Tabs.svelte +52 -8
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
{/if}
|
|
13
13
|
<div class="empty-state-title">{title}</div>
|
|
14
|
-
|
|
14
|
+
{#if typeof description === 'string' && description.length > 0}
|
|
15
|
+
<div class="empty-state-description">{description}</div>
|
|
16
|
+
{/if}
|
|
15
17
|
{#if typeof children === 'function'}
|
|
16
18
|
<div class="empty-state-actions">
|
|
17
19
|
{@render children()}
|
|
@@ -2,9 +2,9 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
export type EmptyStateProperties = MandatoryEmptyStateProperties & OptionalEmptyStateProperties;
|
|
3
3
|
export type MandatoryEmptyStateProperties = {
|
|
4
4
|
title: string;
|
|
5
|
-
description: string;
|
|
6
5
|
};
|
|
7
6
|
export type OptionalEmptyStateProperties = {
|
|
7
|
+
description?: string;
|
|
8
8
|
icon?: Snippet;
|
|
9
9
|
children?: Snippet;
|
|
10
10
|
classes?: string;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FileInputProperties } from './properties';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
trigger,
|
|
6
|
+
accept,
|
|
7
|
+
multiple = false,
|
|
8
|
+
maxSizeBytes,
|
|
9
|
+
disabled = false,
|
|
10
|
+
testId,
|
|
11
|
+
classes,
|
|
12
|
+
onfiles,
|
|
13
|
+
onerror
|
|
14
|
+
}: FileInputProperties = $props();
|
|
15
|
+
|
|
16
|
+
let dragOver = $state(false);
|
|
17
|
+
let inputEl: HTMLInputElement | null = $state(null);
|
|
18
|
+
|
|
19
|
+
function openFilePicker(): void {
|
|
20
|
+
if (!disabled) {
|
|
21
|
+
inputEl?.click();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isAccepted(file: File): boolean {
|
|
26
|
+
if (typeof accept !== 'string' || accept.length === 0) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const tokens = accept.split(',').map((token) => token.trim().toLowerCase());
|
|
30
|
+
const fileName = file.name.toLowerCase();
|
|
31
|
+
const fileMime = file.type.toLowerCase();
|
|
32
|
+
|
|
33
|
+
return tokens.some((token) => {
|
|
34
|
+
if (token.startsWith('.')) {
|
|
35
|
+
return fileName.endsWith(token);
|
|
36
|
+
}
|
|
37
|
+
if (token.endsWith('/*')) {
|
|
38
|
+
const baseType = token.slice(0, -2);
|
|
39
|
+
return fileMime.startsWith(baseType + '/');
|
|
40
|
+
}
|
|
41
|
+
return fileMime === token;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function processFiles(rawFiles: FileList | null): void {
|
|
46
|
+
if (rawFiles === null || rawFiles.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fileArray = Array.from(rawFiles);
|
|
51
|
+
const rejected: string[] = [];
|
|
52
|
+
const accepted: File[] = [];
|
|
53
|
+
|
|
54
|
+
for (const file of fileArray) {
|
|
55
|
+
if (!isAccepted(file)) {
|
|
56
|
+
rejected.push(`"${file.name}" has an unsupported type.`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (typeof maxSizeBytes === 'number' && file.size > maxSizeBytes) {
|
|
60
|
+
const limitMb = (maxSizeBytes / (1024 * 1024)).toFixed(1);
|
|
61
|
+
rejected.push(`"${file.name}" exceeds the ${limitMb} MB limit.`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
accepted.push(file);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (rejected.length > 0) {
|
|
68
|
+
onerror?.(rejected.join(' '));
|
|
69
|
+
}
|
|
70
|
+
if (accepted.length > 0) {
|
|
71
|
+
onfiles?.(accepted);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleInputChange(event: Event): void {
|
|
76
|
+
const target = event.currentTarget;
|
|
77
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
processFiles(target.files);
|
|
81
|
+
target.value = '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleDragOver(event: DragEvent): void {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
if (!disabled) {
|
|
87
|
+
dragOver = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function handleDragLeave(event: DragEvent): void {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
dragOver = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleDrop(event: DragEvent): void {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
dragOver = false;
|
|
99
|
+
if (!disabled) {
|
|
100
|
+
processFiles(event.dataTransfer?.files ?? null);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleKeyDown(event: KeyboardEvent): void {
|
|
105
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
openFilePicker();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<div
|
|
113
|
+
class="file-input {classes ?? ''}"
|
|
114
|
+
class:file-input-dragover={dragOver}
|
|
115
|
+
class:file-input-disabled={disabled}
|
|
116
|
+
data-pw={testId}
|
|
117
|
+
role="button"
|
|
118
|
+
tabindex={disabled ? -1 : 0}
|
|
119
|
+
aria-disabled={disabled}
|
|
120
|
+
ondragover={handleDragOver}
|
|
121
|
+
ondragleave={handleDragLeave}
|
|
122
|
+
ondrop={handleDrop}
|
|
123
|
+
onkeydown={handleKeyDown}
|
|
124
|
+
>
|
|
125
|
+
<input
|
|
126
|
+
bind:this={inputEl}
|
|
127
|
+
type="file"
|
|
128
|
+
class="file-input-hidden"
|
|
129
|
+
{accept}
|
|
130
|
+
{multiple}
|
|
131
|
+
{disabled}
|
|
132
|
+
data-pw={typeof testId === 'string' ? `${testId}-input` : null}
|
|
133
|
+
onchange={handleInputChange}
|
|
134
|
+
tabindex="-1"
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
{@render trigger({ openFilePicker, dragOver, disabled })}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<style>
|
|
142
|
+
.file-input {
|
|
143
|
+
display: var(--file-input-display, inline-flex);
|
|
144
|
+
flex-direction: var(--file-input-flex-direction, column);
|
|
145
|
+
align-items: var(--file-input-align-items, center);
|
|
146
|
+
justify-content: var(--file-input-justify-content, center);
|
|
147
|
+
padding: var(--file-input-padding);
|
|
148
|
+
border: var(--file-input-border);
|
|
149
|
+
border-radius: var(--file-input-radius);
|
|
150
|
+
background: var(--file-input-background);
|
|
151
|
+
gap: var(--file-input-gap);
|
|
152
|
+
text-align: var(--file-input-text-align, center);
|
|
153
|
+
transition: var(--file-input-transition);
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.file-input:focus-visible {
|
|
158
|
+
outline: var(--file-input-focus-outline);
|
|
159
|
+
outline-offset: var(--file-input-focus-outline-offset);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.file-input-dragover {
|
|
163
|
+
background: var(--file-input-dragover-background);
|
|
164
|
+
border-color: var(--file-input-dragover-border-color);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.file-input-disabled {
|
|
168
|
+
opacity: var(--file-input-disabled-opacity, 0.5);
|
|
169
|
+
cursor: var(--file-input-disabled-cursor, not-allowed);
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.file-input-hidden {
|
|
174
|
+
display: none;
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type FileInputSnippetProps = {
|
|
3
|
+
openFilePicker: () => void;
|
|
4
|
+
dragOver: boolean;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type FileInputProperties = {
|
|
8
|
+
/** Content slot — receives { openFilePicker, dragOver, disabled } so consumers build any visual they need. */
|
|
9
|
+
trigger: Snippet<[FileInputSnippetProps]>;
|
|
10
|
+
accept?: string;
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
maxSizeBytes?: number;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
testId?: string;
|
|
15
|
+
classes?: string;
|
|
16
|
+
onfiles?: (files: File[]) => void;
|
|
17
|
+
onerror?: (message: string) => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Modal/Modal.svelte
CHANGED
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function handleOverlayClick(event: MouseEvent) {
|
|
60
|
-
if (event.target
|
|
60
|
+
if (event.target === overlayDiv) {
|
|
61
61
|
debounce(() => {
|
|
62
62
|
onoverlayClick?.();
|
|
63
63
|
});
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
<div class="modal-content {size}">
|
|
111
111
|
{#if (typeof header?.leftImage === 'string' && header.leftImage.length > 0) || (typeof header?.text === 'string' && header.text.length > 0) || (typeof header?.rightImage === 'string' && header.rightImage.length > 0)}
|
|
112
112
|
<div class="header">
|
|
113
|
-
{#if header.leftImage}
|
|
113
|
+
{#if typeof header.leftImage === 'string' && header.leftImage.length > 0}
|
|
114
114
|
<div
|
|
115
115
|
onclick={handleLeftImageClick}
|
|
116
116
|
{onkeydown}
|
|
@@ -121,12 +121,12 @@
|
|
|
121
121
|
<img class="header-left-img" src={header.leftImage} alt="" />
|
|
122
122
|
</div>
|
|
123
123
|
{/if}
|
|
124
|
-
{#if header.text}
|
|
124
|
+
{#if typeof header.text === 'string' && header.text.length > 0}
|
|
125
125
|
<div class="header-text" data-pw={header.testId}>
|
|
126
126
|
{header.text}
|
|
127
127
|
</div>
|
|
128
128
|
{/if}
|
|
129
|
-
{#if header.rightImage}
|
|
129
|
+
{#if typeof header.rightImage === 'string' && header.rightImage.length > 0}
|
|
130
130
|
<div
|
|
131
131
|
role="button"
|
|
132
132
|
tabindex="0"
|
|
@@ -255,6 +255,7 @@
|
|
|
255
255
|
padding: var(--modal-header-padding, 18px 20px);
|
|
256
256
|
border-radius: var(--modal-header-border-radius, 0px);
|
|
257
257
|
border-bottom: var(--modal-header-border-bottom, none);
|
|
258
|
+
align-items: var(--modal-header-align-items, center);
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
.footer-content {
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
disabled = false,
|
|
9
9
|
testId,
|
|
10
10
|
onchange,
|
|
11
|
-
classes
|
|
11
|
+
classes,
|
|
12
|
+
hasMore = false,
|
|
13
|
+
prevButtonTestId,
|
|
14
|
+
nextButtonTestId,
|
|
15
|
+
onLoadMore
|
|
12
16
|
}: PaginationProperties = $props();
|
|
13
17
|
|
|
14
18
|
function generatePages(total: number, current: number, siblings: number): (number | '...')[] {
|
|
@@ -40,24 +44,39 @@
|
|
|
40
44
|
let pages = $derived(generatePages(totalPages, currentPage, siblingCount));
|
|
41
45
|
|
|
42
46
|
function goToPage(page: number): void {
|
|
43
|
-
|
|
47
|
+
const isForwardAllowed = hasMore || page <= totalPages;
|
|
48
|
+
if (disabled || page < 1 || !isForwardAllowed || page === currentPage) {
|
|
44
49
|
return;
|
|
45
50
|
}
|
|
46
51
|
currentPage = page;
|
|
47
52
|
onchange?.(page);
|
|
48
53
|
}
|
|
54
|
+
|
|
55
|
+
let isNextDisabled = $derived(disabled || (!hasMore && currentPage >= totalPages));
|
|
56
|
+
// Cursor mode: on the last known page with more to load, swap the next-button
|
|
57
|
+
// for a load-more CTA.
|
|
58
|
+
let isLoadMore = $derived(hasMore && currentPage >= totalPages);
|
|
59
|
+
|
|
60
|
+
function loadMore(): void {
|
|
61
|
+
if (disabled) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
currentPage = currentPage + 1;
|
|
65
|
+
onLoadMore?.();
|
|
66
|
+
}
|
|
49
67
|
</script>
|
|
50
68
|
|
|
51
69
|
<nav
|
|
52
70
|
class="pagination {classes ?? ''}"
|
|
53
71
|
class:disabled
|
|
54
|
-
data-pw={typeof testId === 'string' ? testId : null}
|
|
72
|
+
data-pw={typeof testId === 'string' && testId.length > 0 ? testId : null}
|
|
55
73
|
>
|
|
56
74
|
<button
|
|
57
75
|
class="page-button prev-button"
|
|
58
76
|
disabled={disabled || currentPage <= 1}
|
|
59
77
|
onclick={() => goToPage(currentPage - 1)}
|
|
60
78
|
aria-label="Previous page"
|
|
79
|
+
data-pw={typeof prevButtonTestId === 'string' ? prevButtonTestId : null}
|
|
61
80
|
>
|
|
62
81
|
‹
|
|
63
82
|
</button>
|
|
@@ -79,14 +98,27 @@
|
|
|
79
98
|
{/if}
|
|
80
99
|
{/each}
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
{#if isLoadMore}
|
|
102
|
+
<button
|
|
103
|
+
class="page-button load-more-button"
|
|
104
|
+
{disabled}
|
|
105
|
+
onclick={loadMore}
|
|
106
|
+
aria-label="Load more"
|
|
107
|
+
data-pw={typeof nextButtonTestId === 'string' ? nextButtonTestId : null}
|
|
108
|
+
>
|
|
109
|
+
›
|
|
110
|
+
</button>
|
|
111
|
+
{:else}
|
|
112
|
+
<button
|
|
113
|
+
class="page-button next-button"
|
|
114
|
+
disabled={isNextDisabled}
|
|
115
|
+
onclick={() => goToPage(currentPage + 1)}
|
|
116
|
+
aria-label="Next page"
|
|
117
|
+
data-pw={typeof nextButtonTestId === 'string' ? nextButtonTestId : null}
|
|
118
|
+
>
|
|
119
|
+
›
|
|
120
|
+
</button>
|
|
121
|
+
{/if}
|
|
90
122
|
</nav>
|
|
91
123
|
|
|
92
124
|
<style>
|
|
@@ -131,6 +163,19 @@
|
|
|
131
163
|
font-weight: var(--pagination-active-font-weight, 600);
|
|
132
164
|
}
|
|
133
165
|
|
|
166
|
+
.load-more-button {
|
|
167
|
+
width: var(--pagination-load-more-width, auto);
|
|
168
|
+
padding: var(--pagination-load-more-padding, 6px 14px);
|
|
169
|
+
color: var(--pagination-load-more-color, #3a4550);
|
|
170
|
+
background: var(--pagination-load-more-background, transparent);
|
|
171
|
+
border-color: var(--pagination-load-more-border-color, #d1d5db);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.load-more-button:hover:not(:disabled) {
|
|
175
|
+
color: var(--pagination-load-more-hover-color, #111827);
|
|
176
|
+
background: var(--pagination-load-more-hover-background, #f3f4f6);
|
|
177
|
+
}
|
|
178
|
+
|
|
134
179
|
.page-button:disabled {
|
|
135
180
|
opacity: var(--pagination-disabled-opacity, 0.5);
|
|
136
181
|
cursor: var(--pagination-disabled-cursor, not-allowed);
|
|
@@ -8,7 +8,22 @@ export type OptionalPaginationProperties = {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
testId?: string;
|
|
10
10
|
classes?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Cursor-pagination hint. When `true`, the next button stays enabled even
|
|
13
|
+
* when `currentPage >= totalPages` — use this when the total page count is
|
|
14
|
+
* not known ahead of time (e.g. cursor-based APIs). `totalPages` takes
|
|
15
|
+
* precedence when both are provided: the next button is enabled only if
|
|
16
|
+
* `hasMore` is `true` OR `currentPage < totalPages`. In cursor mode the
|
|
17
|
+
* next-button becomes a load-more CTA on the last known page (see onLoadMore).
|
|
18
|
+
*/
|
|
19
|
+
hasMore?: boolean;
|
|
20
|
+
/** `data-pw` test-id for the previous-page button. */
|
|
21
|
+
prevButtonTestId?: string;
|
|
22
|
+
/** `data-pw` test-id for the next-page button. */
|
|
23
|
+
nextButtonTestId?: string;
|
|
11
24
|
};
|
|
12
25
|
export type PaginationEventProperties = {
|
|
13
26
|
onchange?: (page: number) => void;
|
|
27
|
+
/** Fired when the load-more CTA is clicked (cursor mode). */
|
|
28
|
+
onLoadMore?: () => void;
|
|
14
29
|
};
|
package/dist/Pill/Pill.svelte
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
disabled = false,
|
|
10
10
|
testId,
|
|
11
11
|
dismissIcon,
|
|
12
|
+
leadingIcon,
|
|
12
13
|
onclick,
|
|
13
14
|
ondismiss,
|
|
14
15
|
classes
|
|
@@ -52,6 +53,9 @@
|
|
|
52
53
|
aria-disabled={interactive && disabled ? true : null}
|
|
53
54
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
54
55
|
>
|
|
56
|
+
{#if typeof leadingIcon === 'function'}
|
|
57
|
+
<span class="pill-leading-icon">{@render leadingIcon()}</span>
|
|
58
|
+
{/if}
|
|
55
59
|
<span class="pill-text">{text}</span>
|
|
56
60
|
{#if dismissible}
|
|
57
61
|
<div class="pill-dismiss">
|
|
@@ -106,6 +110,12 @@
|
|
|
106
110
|
white-space: nowrap;
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
.pill-leading-icon {
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
flex-shrink: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
.pill-dismiss {
|
|
110
120
|
--button-color: transparent;
|
|
111
121
|
--button-border: none;
|
|
@@ -8,6 +8,13 @@ export type OptionalPillProperties = {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
testId?: string;
|
|
10
10
|
dismissIcon?: Snippet;
|
|
11
|
+
/**
|
|
12
|
+
* A Svelte snippet rendered immediately before the text label inside a
|
|
13
|
+
* `<span class="pill-leading-icon">` wrapper. Use for icons, logos, or any
|
|
14
|
+
* inline decoration. The wrapper does not receive `aria-hidden` — leave
|
|
15
|
+
* accessibility attributes on the icon itself.
|
|
16
|
+
*/
|
|
17
|
+
leadingIcon?: Snippet;
|
|
11
18
|
classes?: string;
|
|
12
19
|
};
|
|
13
20
|
export type PillEventProperties = {
|