@juspay/svelte-ui-components 2.71.0 → 2.73.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.
- package/dist/AreaChart/AreaChart.svelte +80 -6
- package/dist/AreaChart/properties.d.ts +1 -0
- package/dist/LineChart/LineChart.svelte +82 -3
- package/dist/LineChart/properties.d.ts +2 -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/_chart/paths.js +1 -0
- package/dist/_chart/types.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { AreaChartProperties, AreaChartTooltipContext } from './properties';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
3
4
|
import ChartContainer from '../_chart/ChartContainer.svelte';
|
|
4
5
|
import Axis from '../_chart/Axis.svelte';
|
|
5
6
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
@@ -8,17 +9,24 @@
|
|
|
8
9
|
import { computeChartDimensions, computeStackedValues } from '../_chart/geometry';
|
|
9
10
|
import { linePath, areaPath } from '../_chart/paths';
|
|
10
11
|
import { getColor } from '../_chart/colors';
|
|
11
|
-
import { formatNumber } from '../_chart/format';
|
|
12
|
+
import { formatNumber, formatPercent } from '../_chart/format';
|
|
12
13
|
import type { LegendItem, Point } from '../_chart/types';
|
|
13
14
|
|
|
14
15
|
// ── Props ──────────────────────────────────────────────────────
|
|
15
16
|
|
|
17
|
+
// Per-instance ID for SVG gradient <linearGradient id> references. Initialised
|
|
18
|
+
// inside onMount so the value is only ever generated on the client — avoids an
|
|
19
|
+
// SSR hydration mismatch that would occur if Math.random() ran on both server
|
|
20
|
+
// and client and produced different strings.
|
|
21
|
+
let uid = $state('');
|
|
22
|
+
|
|
16
23
|
let {
|
|
17
24
|
series,
|
|
18
25
|
curve = 'monotone',
|
|
19
26
|
stacked = false,
|
|
20
27
|
stackNormalize = false,
|
|
21
28
|
fillOpacity = 0.3,
|
|
29
|
+
gradientFill = false,
|
|
22
30
|
showDots = false,
|
|
23
31
|
showLine = true,
|
|
24
32
|
showValues = false,
|
|
@@ -51,6 +59,10 @@
|
|
|
51
59
|
let mouseX = $state(0);
|
|
52
60
|
let mouseY = $state(0);
|
|
53
61
|
|
|
62
|
+
onMount(() => {
|
|
63
|
+
uid = Math.random().toString(36).slice(2, 9);
|
|
64
|
+
});
|
|
65
|
+
|
|
54
66
|
// ── Layout ─────────────────────────────────────────────────────
|
|
55
67
|
|
|
56
68
|
let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
|
|
@@ -169,8 +181,23 @@
|
|
|
169
181
|
if (tooltipContext === null) {
|
|
170
182
|
return null;
|
|
171
183
|
}
|
|
184
|
+
const title = xTickFormat
|
|
185
|
+
? xTickFormat(tooltipContext.x)
|
|
186
|
+
: `x: ${formatNumber(tooltipContext.x)}`;
|
|
187
|
+
if (stackNormalize) {
|
|
188
|
+
const pi = hovered?.pi ?? 0;
|
|
189
|
+
const columnTotal = series.reduce((sum, s) => sum + Math.max(0, s.data[pi]?.y ?? 0), 0);
|
|
190
|
+
return {
|
|
191
|
+
title,
|
|
192
|
+
items: tooltipContext.points.map((p) => ({
|
|
193
|
+
label: p.name,
|
|
194
|
+
value: formatPercent(Math.max(0, p.y), columnTotal),
|
|
195
|
+
color: p.color
|
|
196
|
+
}))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
172
199
|
return {
|
|
173
|
-
title
|
|
200
|
+
title,
|
|
174
201
|
items: tooltipContext.points.map((p) => ({
|
|
175
202
|
label: p.name,
|
|
176
203
|
value: formatNumber(p.y),
|
|
@@ -273,6 +300,37 @@
|
|
|
273
300
|
{/if}
|
|
274
301
|
|
|
275
302
|
<ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
|
|
303
|
+
{#if gradientFill}
|
|
304
|
+
<!-- <defs> must be a direct child of <svg> (SVG root), not inside a transformed <g>.
|
|
305
|
+
gradientUnits="userSpaceOnUse" with y1/y2 in the inner coordinate space (0..innerHeight)
|
|
306
|
+
correctly spans the full chart height regardless of how thin each band is. -->
|
|
307
|
+
<defs>
|
|
308
|
+
{#each areas as area, si (si)}
|
|
309
|
+
<linearGradient
|
|
310
|
+
id="area-grad-{uid}-{si}"
|
|
311
|
+
x1="0"
|
|
312
|
+
y1="0"
|
|
313
|
+
x2="0"
|
|
314
|
+
y2={dims.innerHeight}
|
|
315
|
+
gradientUnits="userSpaceOnUse"
|
|
316
|
+
>
|
|
317
|
+
<!-- The gradient top stop is fillOpacity + 0.3 (clamped to 1), giving a richer
|
|
318
|
+
anchor at the top that fades to transparent at the bottom. This intentionally
|
|
319
|
+
exceeds the base fillOpacity so that gradient-fill areas appear more vivid
|
|
320
|
+
than their solid-fill counterparts (where fill-opacity equals fillOpacity). -->
|
|
321
|
+
<stop
|
|
322
|
+
offset="0%"
|
|
323
|
+
stop-color={area.color}
|
|
324
|
+
stop-opacity={Math.min(
|
|
325
|
+
(hovered?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
326
|
+
1
|
|
327
|
+
)}
|
|
328
|
+
/>
|
|
329
|
+
<stop offset="100%" stop-color={area.color} stop-opacity={0} />
|
|
330
|
+
</linearGradient>
|
|
331
|
+
{/each}
|
|
332
|
+
</defs>
|
|
333
|
+
{/if}
|
|
276
334
|
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
277
335
|
{#if showYAxis}
|
|
278
336
|
<Axis
|
|
@@ -295,8 +353,8 @@
|
|
|
295
353
|
class="area-fill"
|
|
296
354
|
class:dimmed={hovered !== null && hovered.si !== si}
|
|
297
355
|
d={area.areaD}
|
|
298
|
-
fill={area.color}
|
|
299
|
-
fill-opacity={hovered?.si === si ? fillOpacity + 0.2 : fillOpacity}
|
|
356
|
+
fill={gradientFill ? `url(#area-grad-${uid}-${si})` : area.color}
|
|
357
|
+
fill-opacity={gradientFill ? 1 : hovered?.si === si ? fillOpacity + 0.2 : fillOpacity}
|
|
300
358
|
/>
|
|
301
359
|
{#if showLine}
|
|
302
360
|
<path
|
|
@@ -308,6 +366,16 @@
|
|
|
308
366
|
fill="none"
|
|
309
367
|
/>
|
|
310
368
|
{/if}
|
|
369
|
+
{#if area.points.length === 1 && !showDots}
|
|
370
|
+
<circle
|
|
371
|
+
class="single-point"
|
|
372
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
373
|
+
cx={area.points[0].x}
|
|
374
|
+
cy={area.points[0].y}
|
|
375
|
+
r={6}
|
|
376
|
+
fill={area.color}
|
|
377
|
+
/>
|
|
378
|
+
{/if}
|
|
311
379
|
{#if showDots}
|
|
312
380
|
{#each area.points as point, pi (pi)}
|
|
313
381
|
<circle
|
|
@@ -385,6 +453,12 @@
|
|
|
385
453
|
.area-line.dimmed {
|
|
386
454
|
opacity: var(--areachart-dimmed-opacity, 0.1);
|
|
387
455
|
}
|
|
456
|
+
.single-point {
|
|
457
|
+
pointer-events: none;
|
|
458
|
+
}
|
|
459
|
+
.single-point.dimmed {
|
|
460
|
+
opacity: var(--areachart-dimmed-opacity, 0.1);
|
|
461
|
+
}
|
|
388
462
|
.dot {
|
|
389
463
|
transition:
|
|
390
464
|
r var(--chart-transition-duration, 0.2s) ease,
|
|
@@ -400,9 +474,9 @@
|
|
|
400
474
|
pointer-events: none;
|
|
401
475
|
}
|
|
402
476
|
.hover-line {
|
|
403
|
-
stroke: var(--linechart-hover-line-color, #ccc);
|
|
477
|
+
stroke: var(--areachart-hover-line-color, var(--linechart-hover-line-color, #ccc));
|
|
404
478
|
stroke-width: 1;
|
|
405
|
-
stroke-dasharray: 4 4;
|
|
479
|
+
stroke-dasharray: var(--areachart-hover-line-dash, var(--linechart-hover-line-dash, 4 4));
|
|
406
480
|
pointer-events: none;
|
|
407
481
|
}
|
|
408
482
|
.hover-overlay {
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { LineChartProperties, LineChartTooltipContext } from './properties';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
3
4
|
import ChartContainer from '../_chart/ChartContainer.svelte';
|
|
4
5
|
import Axis from '../_chart/Axis.svelte';
|
|
5
6
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
6
7
|
import Legend from '../_chart/Legend.svelte';
|
|
7
8
|
import { createLinearScale, niceLinearDomain } from '../_chart/scales';
|
|
8
9
|
import { computeChartDimensions } from '../_chart/geometry';
|
|
9
|
-
import { linePath } from '../_chart/paths';
|
|
10
|
+
import { linePath, areaPath } from '../_chart/paths';
|
|
10
11
|
import { getColor } from '../_chart/colors';
|
|
11
12
|
import { formatNumber } from '../_chart/format';
|
|
12
13
|
import type { LegendItem, Point } from '../_chart/types';
|
|
13
14
|
|
|
15
|
+
// Per-instance ID for SVG gradient <linearGradient id> references. Initialised
|
|
16
|
+
// inside onMount so the value is only ever generated on the client — avoids an
|
|
17
|
+
// SSR hydration mismatch that would occur if Math.random() ran on both server
|
|
18
|
+
// and client and produced different strings.
|
|
19
|
+
let uid = $state('');
|
|
20
|
+
|
|
14
21
|
// ── Props ──────────────────────────────────────────────────────
|
|
15
22
|
|
|
16
23
|
let {
|
|
17
24
|
series,
|
|
18
25
|
curve = 'monotone',
|
|
26
|
+
gradientFill = false,
|
|
27
|
+
fillOpacity = 0.3,
|
|
19
28
|
showDots = true,
|
|
20
29
|
showValues = false,
|
|
21
30
|
dotRadius = 4,
|
|
@@ -48,6 +57,10 @@
|
|
|
48
57
|
let mouseX = $state(0);
|
|
49
58
|
let mouseY = $state(0);
|
|
50
59
|
|
|
60
|
+
onMount(() => {
|
|
61
|
+
uid = Math.random().toString(36).slice(2, 9);
|
|
62
|
+
});
|
|
63
|
+
|
|
51
64
|
// ── Layout ─────────────────────────────────────────────────────
|
|
52
65
|
|
|
53
66
|
let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
|
|
@@ -82,7 +95,12 @@
|
|
|
82
95
|
series.map((s, si) => {
|
|
83
96
|
const color = s.color ?? getColor(si);
|
|
84
97
|
const points: Point[] = s.data.map((d) => ({ x: xScale(d.x), y: yScale(d.y) }));
|
|
85
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
color,
|
|
100
|
+
points,
|
|
101
|
+
path: linePath(points, curve),
|
|
102
|
+
areaD: areaPath(points, dims.innerHeight, curve)
|
|
103
|
+
};
|
|
86
104
|
})
|
|
87
105
|
);
|
|
88
106
|
|
|
@@ -123,7 +141,7 @@
|
|
|
123
141
|
return null;
|
|
124
142
|
}
|
|
125
143
|
return {
|
|
126
|
-
title: `x: ${formatNumber(tooltipContext.x)}`,
|
|
144
|
+
title: xTickFormat ? xTickFormat(tooltipContext.x) : `x: ${formatNumber(tooltipContext.x)}`,
|
|
127
145
|
items: tooltipContext.points.map((p) => ({
|
|
128
146
|
label: p.name,
|
|
129
147
|
value: formatNumber(p.y),
|
|
@@ -227,6 +245,34 @@
|
|
|
227
245
|
{/if}
|
|
228
246
|
|
|
229
247
|
<ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
|
|
248
|
+
{#if gradientFill}
|
|
249
|
+
<defs>
|
|
250
|
+
{#each lines as line, si (si)}
|
|
251
|
+
<linearGradient
|
|
252
|
+
id="line-grad-{uid}-{si}"
|
|
253
|
+
x1="0"
|
|
254
|
+
y1="0"
|
|
255
|
+
x2="0"
|
|
256
|
+
y2={dims.innerHeight}
|
|
257
|
+
gradientUnits="userSpaceOnUse"
|
|
258
|
+
>
|
|
259
|
+
<!-- The gradient top stop is fillOpacity + 0.3 (clamped to 1), giving a richer
|
|
260
|
+
anchor at the top that fades to transparent at the bottom. This intentionally
|
|
261
|
+
exceeds the base fillOpacity so that gradient-fill areas appear more vivid
|
|
262
|
+
than a flat solid-fill at fillOpacity alone. -->
|
|
263
|
+
<stop
|
|
264
|
+
offset="0%"
|
|
265
|
+
stop-color={line.color}
|
|
266
|
+
stop-opacity={Math.min(
|
|
267
|
+
(hovered?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
268
|
+
1
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
<stop offset="100%" stop-color={line.color} stop-opacity={0} />
|
|
272
|
+
</linearGradient>
|
|
273
|
+
{/each}
|
|
274
|
+
</defs>
|
|
275
|
+
{/if}
|
|
230
276
|
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
231
277
|
{#if showYAxis}
|
|
232
278
|
<Axis
|
|
@@ -245,6 +291,14 @@
|
|
|
245
291
|
{/if}
|
|
246
292
|
|
|
247
293
|
{#each lines as line, si (si)}
|
|
294
|
+
{#if gradientFill}
|
|
295
|
+
<path
|
|
296
|
+
class="line-area-fill"
|
|
297
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
298
|
+
d={line.areaD}
|
|
299
|
+
fill="url(#line-grad-{uid}-{si})"
|
|
300
|
+
/>
|
|
301
|
+
{/if}
|
|
248
302
|
<path
|
|
249
303
|
class="line-path"
|
|
250
304
|
class:dimmed={hovered !== null && hovered.si !== si}
|
|
@@ -253,6 +307,16 @@
|
|
|
253
307
|
stroke-width={strokeWidth}
|
|
254
308
|
fill="none"
|
|
255
309
|
/>
|
|
310
|
+
{#if line.points.length === 1 && !showDots}
|
|
311
|
+
<circle
|
|
312
|
+
class="single-point"
|
|
313
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
314
|
+
cx={line.points[0].x}
|
|
315
|
+
cy={line.points[0].y}
|
|
316
|
+
r={dotRadius * 1.5}
|
|
317
|
+
fill={line.color}
|
|
318
|
+
/>
|
|
319
|
+
{/if}
|
|
256
320
|
{#if showDots}
|
|
257
321
|
{#each line.points as point, pi (pi)}
|
|
258
322
|
<circle
|
|
@@ -313,6 +377,15 @@
|
|
|
313
377
|
width: 100%;
|
|
314
378
|
position: relative;
|
|
315
379
|
}
|
|
380
|
+
.line-area-fill {
|
|
381
|
+
transition:
|
|
382
|
+
fill-opacity var(--chart-transition-duration, 0.2s) ease,
|
|
383
|
+
opacity var(--chart-transition-duration, 0.2s) ease;
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
}
|
|
386
|
+
.line-area-fill.dimmed {
|
|
387
|
+
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
388
|
+
}
|
|
316
389
|
.line-path {
|
|
317
390
|
transition: opacity var(--chart-transition-duration, 0.2s) ease;
|
|
318
391
|
stroke-linecap: round;
|
|
@@ -322,6 +395,12 @@
|
|
|
322
395
|
.line-path.dimmed {
|
|
323
396
|
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
324
397
|
}
|
|
398
|
+
.single-point {
|
|
399
|
+
pointer-events: none;
|
|
400
|
+
}
|
|
401
|
+
.single-point.dimmed {
|
|
402
|
+
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
403
|
+
}
|
|
325
404
|
.dot {
|
|
326
405
|
transition:
|
|
327
406
|
r var(--chart-transition-duration, 0.2s) ease,
|
|
@@ -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/_chart/paths.js
CHANGED
package/dist/_chart/types.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type BandScale = {
|
|
|
31
31
|
bandwidth: number;
|
|
32
32
|
step: number;
|
|
33
33
|
};
|
|
34
|
-
export type CurveType = 'linear' | 'monotone' | 'step' | 'natural';
|
|
34
|
+
export type CurveType = 'linear' | 'monotone' | 'spline' | 'step' | 'natural';
|
|
35
35
|
export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left';
|
|
36
36
|
export type PieSliceLayout = {
|
|
37
37
|
index: number;
|
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';
|