@juspay/svelte-ui-components 2.72.0 → 2.73.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.
- package/dist/DateRangePicker/DateRangePicker.svelte +101 -23
- package/dist/DateRangePicker/properties.d.ts +9 -0
- package/dist/StatCard/StatCard.svelte +170 -0
- package/dist/StatCard/StatCard.svelte.d.ts +4 -0
- package/dist/StatCard/properties.d.ts +27 -0
- package/dist/StatCard/properties.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
presetCheckmark = false,
|
|
31
31
|
showDateInputs = false,
|
|
32
32
|
showTimeSelection = false,
|
|
33
|
+
timeSelectionLayout = 'toggle',
|
|
33
34
|
presetToggle = false,
|
|
34
35
|
placeholder = 'Select date',
|
|
35
36
|
dualMonth,
|
|
@@ -79,6 +80,14 @@
|
|
|
79
80
|
let endTimeDisplay: string = $state('11:59 PM');
|
|
80
81
|
let showTimeRow: boolean = $state(false);
|
|
81
82
|
|
|
83
|
+
// Inline time layout (timeSelectionLayout="inline"): the start/end time inputs
|
|
84
|
+
// render beside their date inputs on the same row, always visible — no clock
|
|
85
|
+
// toggle and no collapsible row. All seeding/validation/fold logic is shared
|
|
86
|
+
// with the default toggle layout (it keys off showTimeSelection, not the layout).
|
|
87
|
+
const isInlineTime: boolean = $derived(
|
|
88
|
+
showTimeSelection && timeSelectionLayout === 'inline' && mode === 'range'
|
|
89
|
+
);
|
|
90
|
+
|
|
82
91
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
83
92
|
|
|
84
93
|
function isBaseDisabledDate(date: Date): boolean {
|
|
@@ -668,32 +677,82 @@
|
|
|
668
677
|
<!-- Calendar area -->
|
|
669
678
|
<div class="drp-calendars">
|
|
670
679
|
{#if (showDateInputs || showTimeSelection) && mode === 'range'}
|
|
671
|
-
<div class="drp-datetime-header">
|
|
680
|
+
<div class="drp-datetime-header" class:drp-datetime-header-inline={isInlineTime}>
|
|
672
681
|
<div class="drp-date-input-row">
|
|
673
|
-
|
|
674
|
-
<
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
682
|
+
{#if isInlineTime}
|
|
683
|
+
<div class="drp-date-time-group">
|
|
684
|
+
<div class="drp-date-input" data-pw={testId ? `${testId}-start-date` : null}>
|
|
685
|
+
<span class="drp-date-input-value">{draftStartDateLabel || 'Start date'}</span
|
|
686
|
+
>
|
|
687
|
+
</div>
|
|
688
|
+
<div
|
|
689
|
+
class="drp-time-input drp-time-input-inline"
|
|
690
|
+
class:drp-time-input-invalid={!isStartTimeValid}
|
|
691
|
+
>
|
|
692
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
693
|
+
<span class="drp-time-input-icon" aria-hidden="true">{@html clockSvg}</span>
|
|
694
|
+
<input
|
|
695
|
+
type="text"
|
|
696
|
+
class="drp-time-field"
|
|
697
|
+
bind:value={startTimeDisplay}
|
|
698
|
+
maxlength="8"
|
|
699
|
+
placeholder="12:00 AM"
|
|
700
|
+
aria-label="Start time"
|
|
701
|
+
aria-invalid={!isStartTimeValid}
|
|
702
|
+
data-pw={testId ? `${testId}-start-time` : null}
|
|
703
|
+
/>
|
|
704
|
+
</div>
|
|
705
|
+
</div>
|
|
706
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
707
|
+
<span class="drp-datetime-arrow" aria-hidden="true">{@html chevronRightSvg}</span>
|
|
708
|
+
<div class="drp-date-time-group">
|
|
709
|
+
<div class="drp-date-input" data-pw={testId ? `${testId}-end-date` : null}>
|
|
710
|
+
<span class="drp-date-input-value">{draftEndDateLabel || 'End date'}</span>
|
|
711
|
+
</div>
|
|
712
|
+
<div
|
|
713
|
+
class="drp-time-input drp-time-input-inline"
|
|
714
|
+
class:drp-time-input-invalid={!isEndTimeValid}
|
|
715
|
+
>
|
|
716
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
717
|
+
<span class="drp-time-input-icon" aria-hidden="true">{@html clockSvg}</span>
|
|
718
|
+
<input
|
|
719
|
+
type="text"
|
|
720
|
+
class="drp-time-field"
|
|
721
|
+
bind:value={endTimeDisplay}
|
|
722
|
+
maxlength="8"
|
|
723
|
+
placeholder="11:59 PM"
|
|
724
|
+
aria-label="End time"
|
|
725
|
+
aria-invalid={!isEndTimeValid}
|
|
726
|
+
data-pw={testId ? `${testId}-end-time` : null}
|
|
727
|
+
/>
|
|
728
|
+
</div>
|
|
729
|
+
</div>
|
|
730
|
+
{:else}
|
|
731
|
+
<div class="drp-date-input" data-pw={testId ? `${testId}-start-date` : null}>
|
|
732
|
+
<span class="drp-date-input-value">{draftStartDateLabel || 'Start date'}</span>
|
|
733
|
+
</div>
|
|
734
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
735
|
+
<span class="drp-datetime-arrow" aria-hidden="true">{@html chevronRightSvg}</span>
|
|
736
|
+
<div class="drp-date-input" data-pw={testId ? `${testId}-end-date` : null}>
|
|
737
|
+
<span class="drp-date-input-value">{draftEndDateLabel || 'End date'}</span>
|
|
738
|
+
</div>
|
|
739
|
+
{#if showTimeSelection}
|
|
740
|
+
<button
|
|
741
|
+
type="button"
|
|
742
|
+
class="drp-time-toggle"
|
|
743
|
+
class:drp-time-toggle-active={showTimeRow}
|
|
744
|
+
aria-label="Toggle time selection"
|
|
745
|
+
aria-pressed={showTimeRow}
|
|
746
|
+
data-pw={testId ? `${testId}-time-toggle` : null}
|
|
747
|
+
onclick={() => (showTimeRow = !showTimeRow)}
|
|
748
|
+
>
|
|
749
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
750
|
+
<span class="drp-time-toggle-icon" aria-hidden="true">{@html clockSvg}</span>
|
|
751
|
+
</button>
|
|
752
|
+
{/if}
|
|
694
753
|
{/if}
|
|
695
754
|
</div>
|
|
696
|
-
{#if showTimeSelection && showTimeRow}
|
|
755
|
+
{#if showTimeSelection && !isInlineTime && showTimeRow}
|
|
697
756
|
<div class="drp-time-input-row">
|
|
698
757
|
<div class="drp-time-input" class:drp-time-input-invalid={!isStartTimeValid}>
|
|
699
758
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
@@ -1207,6 +1266,25 @@
|
|
|
1207
1266
|
color: var(--drp-time-field-placeholder-color, #aaaaaa);
|
|
1208
1267
|
}
|
|
1209
1268
|
|
|
1269
|
+
/* ── Inline time layout (timeSelectionLayout="inline") ── */
|
|
1270
|
+
/* Each date input is paired with its time input on the same row: the date box
|
|
1271
|
+
flexes to fill, the time input takes a fixed compact width beside it. */
|
|
1272
|
+
.drp-date-time-group {
|
|
1273
|
+
display: flex;
|
|
1274
|
+
align-items: center;
|
|
1275
|
+
flex: 1;
|
|
1276
|
+
min-width: 0;
|
|
1277
|
+
gap: var(--drp-datetime-inline-gap, 8px);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
.drp-date-time-group .drp-date-input {
|
|
1281
|
+
flex: 1;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.drp-time-input.drp-time-input-inline {
|
|
1285
|
+
flex: 0 0 var(--drp-time-inline-width, 116px);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1210
1288
|
/* ── Compare calendar slot ── */
|
|
1211
1289
|
.drp-compare-section {
|
|
1212
1290
|
display: flex;
|
|
@@ -15,6 +15,7 @@ export type DateRangePreset = {
|
|
|
15
15
|
};
|
|
16
16
|
export type DateRangePickerMode = 'range' | 'single';
|
|
17
17
|
export type DateRangePickerAlign = 'left' | 'right';
|
|
18
|
+
export type DateRangePickerTimeLayout = 'toggle' | 'inline';
|
|
18
19
|
export type OptionalDateRangePickerProperties = {
|
|
19
20
|
/** Currently selected start of range (bindable). */
|
|
20
21
|
rangeStart?: Date | null;
|
|
@@ -52,6 +53,14 @@ export type OptionalDateRangePickerProperties = {
|
|
|
52
53
|
* Opt-in. Default: false.
|
|
53
54
|
*/
|
|
54
55
|
showTimeSelection?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* How the time-of-day inputs are presented when showTimeSelection is on (range mode):
|
|
58
|
+
* 'toggle' (default) shows a clock button that reveals a collapsible start/end time
|
|
59
|
+
* row below the dates; 'inline' renders each time input beside its date input on the
|
|
60
|
+
* same row, always visible, with no toggle. Has no effect unless showTimeSelection is
|
|
61
|
+
* true. Default: 'toggle'.
|
|
62
|
+
*/
|
|
63
|
+
timeSelectionLayout?: DateRangePickerTimeLayout;
|
|
55
64
|
/**
|
|
56
65
|
* Make presets toggle instead of being one-way: clicking the already-selected
|
|
57
66
|
* preset deselects it and reverts the draft to the committed selection. Lets a
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { StatCardProperties } from './properties';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
title,
|
|
6
|
+
value,
|
|
7
|
+
delta,
|
|
8
|
+
deltaPositive,
|
|
9
|
+
subtitle,
|
|
10
|
+
footer,
|
|
11
|
+
valueSnippet,
|
|
12
|
+
testId,
|
|
13
|
+
classes,
|
|
14
|
+
onclick
|
|
15
|
+
}: StatCardProperties = $props();
|
|
16
|
+
|
|
17
|
+
const isInteractive = $derived(typeof onclick === 'function');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* When `deltaPositive` is explicitly provided use it; otherwise infer from
|
|
21
|
+
* the leading character of `delta` — '+' → positive, '-' → negative,
|
|
22
|
+
* anything else (e.g. '0%') → undefined (neutral, no colour applied).
|
|
23
|
+
*/
|
|
24
|
+
const resolvedDeltaPositive = $derived(
|
|
25
|
+
typeof deltaPositive === 'boolean'
|
|
26
|
+
? deltaPositive
|
|
27
|
+
: typeof delta === 'string' && delta.trim().length > 0
|
|
28
|
+
? delta.trimStart().startsWith('+')
|
|
29
|
+
? true
|
|
30
|
+
: delta.trimStart().startsWith('-')
|
|
31
|
+
? false
|
|
32
|
+
: null
|
|
33
|
+
: null
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const hasDelta = $derived(typeof delta === 'string' && delta.trim().length > 0);
|
|
37
|
+
|
|
38
|
+
const handleKeydown = (event: KeyboardEvent): void => {
|
|
39
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
40
|
+
if (event.key === ' ') {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
}
|
|
43
|
+
if (event.currentTarget instanceof HTMLElement) {
|
|
44
|
+
event.currentTarget.click();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
51
|
+
<div
|
|
52
|
+
class="statcard {classes ?? ''}"
|
|
53
|
+
class:statcard-interactive={isInteractive}
|
|
54
|
+
data-pw={typeof testId === 'string' ? testId : null}
|
|
55
|
+
role={isInteractive ? 'button' : null}
|
|
56
|
+
tabindex={isInteractive ? 0 : null}
|
|
57
|
+
onclick={isInteractive ? onclick : null}
|
|
58
|
+
onkeydown={isInteractive ? handleKeydown : null}
|
|
59
|
+
>
|
|
60
|
+
{#if typeof title === 'string' && title.length > 0}
|
|
61
|
+
<div class="statcard-title">{title}</div>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
<div class="statcard-value-row">
|
|
65
|
+
{#if typeof valueSnippet === 'function'}
|
|
66
|
+
<div class="statcard-value">{@render valueSnippet()}</div>
|
|
67
|
+
{:else if typeof value === 'string' && value.length > 0}
|
|
68
|
+
<div class="statcard-value">{value}</div>
|
|
69
|
+
{/if}
|
|
70
|
+
|
|
71
|
+
{#if hasDelta}
|
|
72
|
+
<div
|
|
73
|
+
class="statcard-delta"
|
|
74
|
+
class:statcard-delta-positive={resolvedDeltaPositive === true}
|
|
75
|
+
class:statcard-delta-negative={resolvedDeltaPositive === false}
|
|
76
|
+
>
|
|
77
|
+
{delta}
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{#if typeof subtitle === 'string' && subtitle.length > 0}
|
|
83
|
+
<div class="statcard-subtitle">{subtitle}</div>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
{#if typeof footer === 'function'}
|
|
87
|
+
<div class="statcard-footer">{@render footer()}</div>
|
|
88
|
+
{/if}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
.statcard {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
gap: var(--statcard-gap, 4px);
|
|
96
|
+
padding: var(--statcard-padding, 16px);
|
|
97
|
+
background: var(--statcard-background, #ffffff);
|
|
98
|
+
border: var(--statcard-border, 1px solid #e5e7eb);
|
|
99
|
+
border-radius: var(--statcard-border-radius, 8px);
|
|
100
|
+
box-shadow: var(--statcard-box-shadow, none);
|
|
101
|
+
width: var(--statcard-width, auto);
|
|
102
|
+
min-width: var(--statcard-min-width, 0);
|
|
103
|
+
max-width: var(--statcard-max-width, none);
|
|
104
|
+
height: var(--statcard-height, auto);
|
|
105
|
+
color: var(--statcard-color, inherit);
|
|
106
|
+
cursor: var(--statcard-cursor, default);
|
|
107
|
+
box-sizing: border-box;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.statcard-interactive {
|
|
111
|
+
cursor: var(--statcard-cursor, pointer);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.statcard-interactive:focus-visible {
|
|
115
|
+
outline: var(--statcard-focus-outline, 2px solid currentColor);
|
|
116
|
+
outline-offset: var(--statcard-focus-outline-offset, 2px);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.statcard-title {
|
|
120
|
+
font-size: var(--statcard-title-font-size, 12px);
|
|
121
|
+
font-weight: var(--statcard-title-font-weight, 500);
|
|
122
|
+
color: var(--statcard-title-color, #6b7280);
|
|
123
|
+
line-height: var(--statcard-title-line-height, 1.4);
|
|
124
|
+
white-space: var(--statcard-title-white-space, nowrap);
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
text-overflow: ellipsis;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.statcard-value-row {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: var(--statcard-value-row-align, baseline);
|
|
132
|
+
gap: var(--statcard-value-row-gap, 8px);
|
|
133
|
+
flex-wrap: wrap;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.statcard-value {
|
|
137
|
+
font-size: var(--statcard-value-font-size, 24px);
|
|
138
|
+
font-weight: var(--statcard-value-font-weight, 600);
|
|
139
|
+
color: var(--statcard-value-color, inherit);
|
|
140
|
+
line-height: var(--statcard-value-line-height, 1.2);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.statcard-delta {
|
|
144
|
+
font-size: var(--statcard-delta-font-size, 13px);
|
|
145
|
+
font-weight: var(--statcard-delta-font-weight, 500);
|
|
146
|
+
color: var(--statcard-delta-color, #6b7280);
|
|
147
|
+
line-height: var(--statcard-delta-line-height, 1.4);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.statcard-delta-positive {
|
|
151
|
+
color: var(--statcard-delta-positive-color, #16a34a);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.statcard-delta-negative {
|
|
155
|
+
color: var(--statcard-delta-negative-color, #dc2626);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.statcard-subtitle {
|
|
159
|
+
font-size: var(--statcard-subtitle-font-size, 12px);
|
|
160
|
+
font-weight: var(--statcard-subtitle-font-weight, 400);
|
|
161
|
+
color: var(--statcard-subtitle-color, #9ca3af);
|
|
162
|
+
line-height: var(--statcard-subtitle-line-height, 1.4);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.statcard-footer {
|
|
166
|
+
margin-top: var(--statcard-footer-margin-top, 8px);
|
|
167
|
+
padding-top: var(--statcard-footer-padding-top, 8px);
|
|
168
|
+
border-top: var(--statcard-footer-border-top, 1px solid #e5e7eb);
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type StatCardProperties = OptionalStatCardProperties & StatCardEventProperties;
|
|
3
|
+
export type MandatoryStatCardProperties = Record<string, never>;
|
|
4
|
+
export type OptionalStatCardProperties = {
|
|
5
|
+
/** Card heading label. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Pre-formatted metric value string (e.g. "₹1.23Cr", "98.4%"). */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** Pre-formatted delta string (e.g. "+12.5%", "-3.2%"). Auto-infers `deltaPositive` from leading sign when `deltaPositive` is not provided. */
|
|
10
|
+
delta?: string;
|
|
11
|
+
/** Overrides automatic sign-based inference for delta colour. `true` = positive (green), `false` = negative (red). */
|
|
12
|
+
deltaPositive?: boolean;
|
|
13
|
+
/** Secondary label rendered below the value row. */
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
/** Snippet rendered in the card footer area. */
|
|
16
|
+
footer?: Snippet;
|
|
17
|
+
/** Replaces the string `value` with a custom snippet for advanced value rendering. */
|
|
18
|
+
valueSnippet?: Snippet;
|
|
19
|
+
/** Renders as `data-pw` on the root element for Playwright test selection. */
|
|
20
|
+
testId?: string;
|
|
21
|
+
/** Extra CSS class names appended to the root element. */
|
|
22
|
+
classes?: string;
|
|
23
|
+
};
|
|
24
|
+
export type StatCardEventProperties = {
|
|
25
|
+
/** Makes the card interactive: adds `role="button"`, `tabindex=0`, and wires click/Enter/Space. */
|
|
26
|
+
onclick?: (event: MouseEvent) => void;
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export { default as BarChart } from './BarChart/BarChart.svelte';
|
|
|
66
66
|
export { default as PieChart } from './PieChart/PieChart.svelte';
|
|
67
67
|
export { default as SankeyChart } from './SankeyChart/SankeyChart.svelte';
|
|
68
68
|
export { default as LottiePlayer } from './LottiePlayer/LottiePlayer.svelte';
|
|
69
|
+
export { default as StatCard } from './StatCard/StatCard.svelte';
|
|
69
70
|
export type * from './Button/properties';
|
|
70
71
|
export type * from './Modal/properties';
|
|
71
72
|
export type * from './Input/properties';
|
|
@@ -128,5 +129,6 @@ export type * from './BarChart/properties';
|
|
|
128
129
|
export type * from './PieChart/properties';
|
|
129
130
|
export type * from './SankeyChart/properties';
|
|
130
131
|
export type * from './LottiePlayer/properties';
|
|
132
|
+
export type * from './StatCard/properties';
|
|
131
133
|
export { validateInput } from './utils';
|
|
132
134
|
export { formatNumberIndian } from './_chart/format';
|
package/dist/index.js
CHANGED
|
@@ -66,5 +66,6 @@ export { default as BarChart } from './BarChart/BarChart.svelte';
|
|
|
66
66
|
export { default as PieChart } from './PieChart/PieChart.svelte';
|
|
67
67
|
export { default as SankeyChart } from './SankeyChart/SankeyChart.svelte';
|
|
68
68
|
export { default as LottiePlayer } from './LottiePlayer/LottiePlayer.svelte';
|
|
69
|
+
export { default as StatCard } from './StatCard/StatCard.svelte';
|
|
69
70
|
export { validateInput } from './utils';
|
|
70
71
|
export { formatNumberIndian } from './_chart/format';
|