@onsvisual/svelte-components 1.1.55 → 1.1.57
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/components/BarChart/BarChart.stories.svelte +1 -1
- package/dist/components/ChangeStat/ChangeStat.stories.svelte +67 -0
- package/dist/components/ChangeStat/ChangeStat.stories.svelte.d.ts +23 -0
- package/dist/components/ChangeStat/ChangeStat.svelte +167 -0
- package/dist/components/ChangeStat/ChangeStat.svelte.d.ts +33 -0
- package/dist/components/ChangeStat/docs/component.md +14 -0
- package/dist/components/Chart/Chart.stories.svelte +1 -1
- package/dist/components/ColumnChart/ColumnChart.stories.svelte +1 -1
- package/dist/components/DataCard/DataCard.stories.svelte +1 -1
- package/dist/components/DotPlotChart/DotPlotChart.stories.svelte +1 -1
- package/dist/components/Grid/Grid.stories.svelte +20 -0
- package/dist/components/Input/Input.stories.svelte +2 -0
- package/dist/components/Input/docs/component.md +163 -0
- package/dist/components/LineChart/LineChart.stories.svelte +1 -1
- package/dist/components/ScatterChart/ScatterChart.stories.svelte +1 -1
- package/dist/components/Table/Table.stories.svelte +1 -1
- package/dist/components/Toolbar/ToolbarIcon.svelte +5 -0
- package/dist/components/UpdateStatus/UpdateStatus.stories.svelte +52 -0
- package/dist/components/UpdateStatus/UpdateStatus.stories.svelte.d.ts +23 -0
- package/dist/components/UpdateStatus/UpdateStatus.svelte +91 -0
- package/dist/components/UpdateStatus/UpdateStatus.svelte.d.ts +35 -0
- package/dist/components/UpdateStatus/docs/component.md +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
|
+
import { withComponentDocs } from "../../js/withParams.js";
|
|
4
|
+
import ChangeStat from "./ChangeStat.svelte";
|
|
5
|
+
import componentDocs from "./docs/component.md?raw";
|
|
6
|
+
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: "Data visualisation/ChangeStat",
|
|
9
|
+
component: ChangeStat,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
argTypes: {
|
|
12
|
+
direction: {
|
|
13
|
+
control: { type: "select" }
|
|
14
|
+
},
|
|
15
|
+
tone: {
|
|
16
|
+
control: { type: "select" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
parameters: withComponentDocs(componentDocs)
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#snippet template(args)}
|
|
24
|
+
<div style:padding="12px">
|
|
25
|
+
<ChangeStat {...args} />
|
|
26
|
+
</div>
|
|
27
|
+
{/snippet}
|
|
28
|
+
|
|
29
|
+
<Story
|
|
30
|
+
name="Lower"
|
|
31
|
+
args={{
|
|
32
|
+
value: "7%",
|
|
33
|
+
direction: "down",
|
|
34
|
+
context: "than last year"
|
|
35
|
+
}}
|
|
36
|
+
{template}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<Story
|
|
40
|
+
name="Higher"
|
|
41
|
+
args={{
|
|
42
|
+
value: "1.3pp",
|
|
43
|
+
direction: "up",
|
|
44
|
+
context: "than the previous quarter"
|
|
45
|
+
}}
|
|
46
|
+
{template}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<Story
|
|
50
|
+
name="No change"
|
|
51
|
+
args={{
|
|
52
|
+
label: "No change",
|
|
53
|
+
direction: "neutral"
|
|
54
|
+
}}
|
|
55
|
+
{template}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<Story
|
|
59
|
+
name="Fewer"
|
|
60
|
+
args={{
|
|
61
|
+
value: "69 (0.7%)",
|
|
62
|
+
label: "fewer",
|
|
63
|
+
direction: "down",
|
|
64
|
+
tone: "positive"
|
|
65
|
+
}}
|
|
66
|
+
{template}
|
|
67
|
+
/>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} ChangeStatProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} ChangeStatEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} ChangeStatSlots */
|
|
4
|
+
export default class ChangeStat extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: never;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type ChangeStatProps = typeof __propDef.props;
|
|
11
|
+
export type ChangeStatEvents = typeof __propDef.events;
|
|
12
|
+
export type ChangeStatSlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponentTyped } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
[x: string]: never;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* Highlighted change value, for example "7%" or "1.3pp"
|
|
4
|
+
* @type {string|number|null}
|
|
5
|
+
*/
|
|
6
|
+
export let value = null;
|
|
7
|
+
/**
|
|
8
|
+
* Direction of change
|
|
9
|
+
* @type {"up"|"down"|"neutral"}
|
|
10
|
+
*/
|
|
11
|
+
export let direction = "neutral";
|
|
12
|
+
/**
|
|
13
|
+
* Optional override for the descriptive label
|
|
14
|
+
* @type {string|null}
|
|
15
|
+
*/
|
|
16
|
+
export let label = null;
|
|
17
|
+
/**
|
|
18
|
+
* Optional trailing context, for example "than last year"
|
|
19
|
+
* @type {string|null}
|
|
20
|
+
*/
|
|
21
|
+
export let context = null;
|
|
22
|
+
/**
|
|
23
|
+
* Optional colour treatment
|
|
24
|
+
* @type {"default"|"positive"|"negative"|"subtle"}
|
|
25
|
+
*/
|
|
26
|
+
export let tone = "default";
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Set an additional CSS class for the component
|
|
29
|
+
* @type {string|null}
|
|
30
|
+
*/
|
|
31
|
+
export let cls = null;
|
|
32
|
+
|
|
33
|
+
const directionLabels = {
|
|
34
|
+
up: "higher",
|
|
35
|
+
down: "lower",
|
|
36
|
+
neutral: "unchanged"
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
$: hasValue = value !== null && value !== undefined && `${value}`.trim().length > 0;
|
|
40
|
+
$: resolvedLabel = label || directionLabels[direction] || "";
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<p
|
|
44
|
+
class="ons-change-stat ons-u-fs-r {cls}"
|
|
45
|
+
class:ons-change-stat--direction-up={direction === "up"}
|
|
46
|
+
class:ons-change-stat--direction-down={direction === "down"}
|
|
47
|
+
class:ons-change-stat--direction-neutral={direction === "neutral"}
|
|
48
|
+
class:ons-change-stat--tone-positive={tone === "positive"}
|
|
49
|
+
class:ons-change-stat--tone-negative={tone === "negative"}
|
|
50
|
+
class:ons-change-stat--tone-subtle={tone === "subtle"}
|
|
51
|
+
>
|
|
52
|
+
<span class="ons-change-stat__leading">
|
|
53
|
+
{#if direction === "neutral" && hasValue}
|
|
54
|
+
<svg
|
|
55
|
+
class="ons-change-stat__icon"
|
|
56
|
+
width="14"
|
|
57
|
+
height="16"
|
|
58
|
+
viewBox="0 0 12 14"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
>
|
|
61
|
+
<path d="M2 7H10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
62
|
+
</svg>
|
|
63
|
+
{:else if direction !== "neutral"}
|
|
64
|
+
<svg
|
|
65
|
+
class="ons-change-stat__icon"
|
|
66
|
+
class:ons-change-stat__icon--down={direction === "down"}
|
|
67
|
+
width="14"
|
|
68
|
+
height="16"
|
|
69
|
+
viewBox="0 0 12 14"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
>
|
|
72
|
+
<path
|
|
73
|
+
d="M5.5 1.5L5.5 12.5M5.5 1.5L9.5 5.5M5.5 1.5L1.5 5.5"
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
stroke-width="2"
|
|
77
|
+
stroke-linecap="round"
|
|
78
|
+
stroke-linejoin="round"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
{/if}
|
|
82
|
+
{#if hasValue}
|
|
83
|
+
<span class="ons-change-stat__value">{value}</span>
|
|
84
|
+
{/if}
|
|
85
|
+
<span>{resolvedLabel}</span>
|
|
86
|
+
</span>
|
|
87
|
+
{#if context}
|
|
88
|
+
<span class="ons-change-stat__context">{context}</span>
|
|
89
|
+
{/if}
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
.ons-change-stat {
|
|
94
|
+
--ons-change-stat-accent: #c14e01;
|
|
95
|
+
--ons-change-stat-background: rgb(247, 223, 207);
|
|
96
|
+
--ons-change-stat-context: var(--ons-color-text-light);
|
|
97
|
+
--ons-change-stat-text: var(--ons-color-text);
|
|
98
|
+
align-items: center;
|
|
99
|
+
border-radius: 0.125rem;
|
|
100
|
+
background: var(--ons-change-stat-background);
|
|
101
|
+
color: var(--ons-change-stat-text);
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
flex-wrap: wrap;
|
|
104
|
+
gap: 0.125rem 0.25rem;
|
|
105
|
+
margin: 0;
|
|
106
|
+
padding: 0.625rem 0.875rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ons-change-stat__leading {
|
|
110
|
+
align-items: center;
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: 0.375rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ons-change-stat__icon {
|
|
117
|
+
color: var(--ons-change-stat-accent);
|
|
118
|
+
display: block;
|
|
119
|
+
flex: 0 0 auto;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ons-change-stat__icon--down {
|
|
123
|
+
transform: rotate(180deg);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ons-change-stat__value {
|
|
127
|
+
font-size: 1.75rem;
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
line-height: 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ons-change-stat__context {
|
|
133
|
+
color: var(--ons-change-stat-context);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.ons-change-stat--tone-positive {
|
|
137
|
+
--ons-change-stat-accent: #0b6a0b;
|
|
138
|
+
--ons-change-stat-background: rgb(207, 225, 198);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ons-change-stat--tone-negative {
|
|
142
|
+
--ons-change-stat-accent: var(--ons-color-errors, #d4351c);
|
|
143
|
+
--ons-change-stat-background: rgb(247, 223, 207);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ons-change-stat--tone-subtle {
|
|
147
|
+
--ons-change-stat-accent: var(--ons-color-text);
|
|
148
|
+
--ons-change-stat-background: var(--ons-color-grey-10, #f3f2f1);
|
|
149
|
+
--ons-change-stat-context: var(--ons-color-text);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ons-change-stat--direction-up {
|
|
153
|
+
--ons-change-stat-accent: #c14e01;
|
|
154
|
+
--ons-change-stat-background: rgb(247, 223, 207);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.ons-change-stat--direction-down {
|
|
158
|
+
--ons-change-stat-accent: #4f4b96;
|
|
159
|
+
--ons-change-stat-background: rgb(221, 218, 242);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.ons-change-stat--direction-neutral {
|
|
163
|
+
--ons-change-stat-accent: var(--ons-color-text);
|
|
164
|
+
--ons-change-stat-background: rgb(230, 230, 230);
|
|
165
|
+
--ons-change-stat-context: var(--ons-color-text);
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} ChangeStatProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} ChangeStatEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} ChangeStatSlots */
|
|
4
|
+
export default class ChangeStat extends SvelteComponentTyped<{
|
|
5
|
+
cls?: string | null | undefined;
|
|
6
|
+
label?: string | null | undefined;
|
|
7
|
+
value?: string | number | null | undefined;
|
|
8
|
+
direction?: "up" | "down" | "neutral" | undefined;
|
|
9
|
+
context?: string | null | undefined;
|
|
10
|
+
tone?: "default" | "negative" | "positive" | "subtle" | undefined;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}> {
|
|
14
|
+
}
|
|
15
|
+
export type ChangeStatProps = typeof __propDef.props;
|
|
16
|
+
export type ChangeStatEvents = typeof __propDef.events;
|
|
17
|
+
export type ChangeStatSlots = typeof __propDef.slots;
|
|
18
|
+
import { SvelteComponentTyped } from "svelte";
|
|
19
|
+
declare const __propDef: {
|
|
20
|
+
props: {
|
|
21
|
+
cls?: string | null | undefined;
|
|
22
|
+
label?: string | null | undefined;
|
|
23
|
+
value?: string | number | null | undefined;
|
|
24
|
+
direction?: "up" | "down" | "neutral" | undefined;
|
|
25
|
+
context?: string | null | undefined;
|
|
26
|
+
tone?: "default" | "negative" | "positive" | "subtle" | undefined;
|
|
27
|
+
};
|
|
28
|
+
events: {
|
|
29
|
+
[evt: string]: CustomEvent<any>;
|
|
30
|
+
};
|
|
31
|
+
slots: {};
|
|
32
|
+
};
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Shows a short directional stat such as `7% lower`, `1.3pp higher`, or `69 (0.7%) fewer`.
|
|
2
|
+
|
|
3
|
+
The component separates **direction** from **tone**, so visual styling can reflect whether a change is good or bad without assuming that higher is always positive.
|
|
4
|
+
|
|
5
|
+
<!-- prettier-ignore -->
|
|
6
|
+
```html
|
|
7
|
+
<script>
|
|
8
|
+
import { ChangeStat } from "@onsvisual/svelte-components";
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<ChangeStat value="7%" direction="down" />
|
|
12
|
+
<ChangeStat value="1.3pp" direction="up" />
|
|
13
|
+
<ChangeStat value="69 (0.7%)" label="fewer" direction="down" tone="positive" />
|
|
14
|
+
```
|
|
@@ -33,6 +33,22 @@
|
|
|
33
33
|
</Grid>
|
|
34
34
|
{/snippet}
|
|
35
35
|
|
|
36
|
+
{#snippet threeColumnsTemplate()}
|
|
37
|
+
<Grid colWidth="medium">
|
|
38
|
+
<GridCell><div class="grid-cell">Grid cell one</div></GridCell>
|
|
39
|
+
<GridCell><div class="grid-cell">Grid cell two</div></GridCell>
|
|
40
|
+
<GridCell><div class="grid-cell">Grid cell three</div></GridCell>
|
|
41
|
+
</Grid>
|
|
42
|
+
{/snippet}
|
|
43
|
+
|
|
44
|
+
{#snippet threeColumnsClassTemplate()}
|
|
45
|
+
<Grid>
|
|
46
|
+
<GridCell cls="ons-col-4@m"><div class="grid-cell">Grid cell one</div></GridCell>
|
|
47
|
+
<GridCell cls="ons-col-4@m"><div class="grid-cell">Grid cell two</div></GridCell>
|
|
48
|
+
<GridCell cls="ons-col-4@m"><div class="grid-cell">Grid cell three</div></GridCell>
|
|
49
|
+
</Grid>
|
|
50
|
+
{/snippet}
|
|
51
|
+
|
|
36
52
|
<Story name="Default" args={{}} {template} />
|
|
37
53
|
|
|
38
54
|
<Story name="Wide columns" args={{ colWidth: "wide" }} {template} />
|
|
@@ -41,6 +57,10 @@
|
|
|
41
57
|
|
|
42
58
|
<Story name="Full width columns" args={{ colWidth: "full" }} {template} />
|
|
43
59
|
|
|
60
|
+
<Story name="Three columns with medium column width" args={{}} template={threeColumnsTemplate} />
|
|
61
|
+
|
|
62
|
+
<Story name="Three columns with GridCell class override" args={{}} template={threeColumnsClassTemplate} />
|
|
63
|
+
|
|
44
64
|
<style>
|
|
45
65
|
.grid-cell {
|
|
46
66
|
background: var(--ons-color-grey-10);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
A standard `<input>` element with Svelte bindings and events.
|
|
2
2
|
|
|
3
|
+
## Basic Usage
|
|
4
|
+
|
|
3
5
|
<!-- prettier-ignore -->
|
|
4
6
|
```html
|
|
5
7
|
<script>
|
|
@@ -14,3 +16,164 @@ A standard `<input>` element with Svelte bindings and events.
|
|
|
14
16
|
|
|
15
17
|
<Input bind:value on:change="{handleChange}" />
|
|
16
18
|
```
|
|
19
|
+
|
|
20
|
+
## Using with Autocomplete/Lookup Functions
|
|
21
|
+
|
|
22
|
+
The Input component can be paired with a lookup function to provide autocomplete functionality or data enrichment. Here are common patterns:
|
|
23
|
+
|
|
24
|
+
### Postcode Lookup Example
|
|
25
|
+
|
|
26
|
+
Use the `on:input` or `on:blur` events to trigger async lookups (e.g., postcode validation/enrichment via postcodes.io):
|
|
27
|
+
|
|
28
|
+
<!-- prettier-ignore -->
|
|
29
|
+
```html
|
|
30
|
+
<script>
|
|
31
|
+
import { Input } from "@onsvisual/svelte-components";
|
|
32
|
+
|
|
33
|
+
let postcode = "";
|
|
34
|
+
let postcodeData = null;
|
|
35
|
+
let isLoading = false;
|
|
36
|
+
|
|
37
|
+
// Function to lookup postcode data
|
|
38
|
+
const lookupPostcode = async (inputValue) => {
|
|
39
|
+
if (!inputValue || inputValue.length < 2) return;
|
|
40
|
+
|
|
41
|
+
isLoading = true;
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(`https://api.postcodes.io/postcodes/${encodeURIComponent(inputValue)}`);
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
|
|
46
|
+
if (data.result) {
|
|
47
|
+
postcodeData = data.result;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error("Postcode lookup failed:", error);
|
|
51
|
+
} finally {
|
|
52
|
+
isLoading = false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Trigger lookup on blur or after debounced input
|
|
57
|
+
const handleChange = (e) => {
|
|
58
|
+
lookupPostcode(e.detail.value);
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<Input
|
|
63
|
+
id="postcode"
|
|
64
|
+
label="Enter postcode"
|
|
65
|
+
bind:value={postcode}
|
|
66
|
+
placeholder="e.g., SW1A 1AA"
|
|
67
|
+
on:change={handleChange}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
{#if isLoading}
|
|
71
|
+
<p>Looking up postcode...</p>
|
|
72
|
+
{/if}
|
|
73
|
+
|
|
74
|
+
{#if postcodeData}
|
|
75
|
+
<div>
|
|
76
|
+
<p><strong>Area:</strong> {postcodeData.admin_district}</p>
|
|
77
|
+
<p><strong>Region:</strong> {postcodeData.region}</p>
|
|
78
|
+
<p><strong>Country:</strong> {postcodeData.country}</p>
|
|
79
|
+
</div>
|
|
80
|
+
{/if}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Debounced Input with Autocomplete Suggestions
|
|
84
|
+
|
|
85
|
+
For real-time autocomplete without overwhelming API calls, use a debounce function:
|
|
86
|
+
|
|
87
|
+
<!-- prettier-ignore -->
|
|
88
|
+
```html
|
|
89
|
+
<script>
|
|
90
|
+
import { Input } from "@onsvisual/svelte-components";
|
|
91
|
+
|
|
92
|
+
let searchValue = "";
|
|
93
|
+
let suggestions = [];
|
|
94
|
+
let debounceTimer;
|
|
95
|
+
|
|
96
|
+
// Generic autocomplete lookup function
|
|
97
|
+
const fetchSuggestions = async (query) => {
|
|
98
|
+
if (!query || query.length < 2) {
|
|
99
|
+
suggestions = [];
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
suggestions = data.results || [];
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("Autocomplete fetch failed:", error);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Debounced handler to avoid excessive API calls
|
|
113
|
+
const handleInput = (e) => {
|
|
114
|
+
clearTimeout(debounceTimer);
|
|
115
|
+
debounceTimer = setTimeout(() => {
|
|
116
|
+
fetchSuggestions(e.detail.value);
|
|
117
|
+
}, 300); // Wait 300ms after user stops typing
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const selectSuggestion = (suggestion) => {
|
|
121
|
+
searchValue = suggestion;
|
|
122
|
+
suggestions = [];
|
|
123
|
+
};
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<Input
|
|
127
|
+
id="search"
|
|
128
|
+
label="Search"
|
|
129
|
+
bind:value={searchValue}
|
|
130
|
+
placeholder="Start typing..."
|
|
131
|
+
on:input={handleInput}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
{#if suggestions.length > 0}
|
|
135
|
+
<ul class="suggestions">
|
|
136
|
+
{#each suggestions as item}
|
|
137
|
+
<li>
|
|
138
|
+
<button on:click={() => selectSuggestion(item.name)}>
|
|
139
|
+
{item.name}
|
|
140
|
+
</button>
|
|
141
|
+
</li>
|
|
142
|
+
{/each}
|
|
143
|
+
</ul>
|
|
144
|
+
{/if}
|
|
145
|
+
|
|
146
|
+
<style>
|
|
147
|
+
.suggestions {
|
|
148
|
+
list-style: none;
|
|
149
|
+
padding: 0;
|
|
150
|
+
border: 1px solid #ccc;
|
|
151
|
+
margin: 4px 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.suggestions li button {
|
|
155
|
+
width: 100%;
|
|
156
|
+
padding: 8px;
|
|
157
|
+
text-align: left;
|
|
158
|
+
border: none;
|
|
159
|
+
background: none;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.suggestions li button:hover {
|
|
164
|
+
background-color: #f0f0f0;
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Events
|
|
170
|
+
|
|
171
|
+
The Input component dispatches three events:
|
|
172
|
+
|
|
173
|
+
- **`on:input`** - Fired on each keystroke (useful for real-time feedback/autocomplete)
|
|
174
|
+
- **`on:change`** - Fired when the input loses focus (useful for validation or lookups)
|
|
175
|
+
- **`on:blur`** - Fired when the input loses focus
|
|
176
|
+
|
|
177
|
+
Each event detail contains:
|
|
178
|
+
- `value` - The current input value
|
|
179
|
+
- `e` - The native DOM event
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
selected: "M24 14a10 10 0 1 1-20 0 10 10 0 0 1 20 0",
|
|
51
51
|
d: "m28.708 27.293-6.26-6.258a11.014 11.014 0 1 0-1.413 1.414l6.258 6.258a1 1 0 0 0 1.415-1.415M5 14a9 9 0 1 1 9 9 9.01 9.01 0 0 1-9-9"
|
|
52
52
|
},
|
|
53
|
+
save: {
|
|
54
|
+
selected:
|
|
55
|
+
"M22.11,5H7.44c-1.36,0-2.44,1.1-2.44,2.44v17.11c0,1.34,1.09,2.44,2.44,2.44h17.11c1.34,0,2.44-1.1,2.44-2.44v-14.67l-4.89-4.89Z",
|
|
56
|
+
d: "M27.71,9.18l-4.89-4.89c-.19-.19-.44-.29-.71-.29H7.44c-1.9,0-3.44,1.54-3.44,3.44v17.11c0,1.9,1.54,3.44,3.44,3.44h17.11c1.9,0,3.44-1.54,3.44-3.44v-14.67c0-.27-.11-.52-.29-.71ZM18.67,6v4.78h-8.26v-4.78h8.26ZM26,24.56c0,.8-.65,1.44-1.44,1.44H7.44c-.8,0-1.44-.65-1.44-1.44V7.44c0-.8.65-1.44,1.44-1.44h.96v5.78c0,.55.45,1,1,1h10.26c.55,0,1-.45,1-1v-5.78h1.03l4.3,4.3v14.25ZM16,15.65c-2.57,0-4.67,2.09-4.67,4.67s2.09,4.67,4.67,4.67,4.67-2.09,4.67-4.67-2.09-4.67-4.67-4.67ZM16,22.98c-1.47,0-2.67-1.2-2.67-2.67s1.2-2.67,2.67-2.67,2.67,1.2,2.67,2.67-1.2,2.67-2.67,2.67Z"
|
|
57
|
+
},
|
|
53
58
|
download: {
|
|
54
59
|
selected: "M27 6v20H5V6a2 2 0 0 1 2-2h18a2 2 0 0 1 2 2",
|
|
55
60
|
d: "M28 18v8a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-8a1 1 0 1 1 2 0v7h20v-7a1 1 0 0 1 2 0m-12.707.707a1 1 0 0 0 1.415 0l5-5a1 1 0 0 0-1.415-1.415L17 15.587V4a1 1 0 0 0-2 0v11.586l-3.293-3.293a1 1 0 1 0-1.415 1.415z"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
|
+
import { withComponentDocs } from "../../js/withParams.js";
|
|
4
|
+
import UpdateStatus from "./UpdateStatus.svelte";
|
|
5
|
+
import componentDocs from "./docs/component.md?raw";
|
|
6
|
+
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: "Data visualisation/UpdateStatus",
|
|
9
|
+
component: UpdateStatus,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
parameters: withComponentDocs(componentDocs)
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#snippet template(args)}
|
|
16
|
+
<div style:padding="12px">
|
|
17
|
+
<UpdateStatus {...args} />
|
|
18
|
+
</div>
|
|
19
|
+
{/snippet}
|
|
20
|
+
|
|
21
|
+
<Story
|
|
22
|
+
name="Recent flag"
|
|
23
|
+
args={{
|
|
24
|
+
recent: true
|
|
25
|
+
}}
|
|
26
|
+
{template}
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<Story
|
|
30
|
+
name="Last updated date"
|
|
31
|
+
args={{
|
|
32
|
+
date: "1 September 2026"
|
|
33
|
+
}}
|
|
34
|
+
{template}
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<Story
|
|
38
|
+
name="Custom recent label"
|
|
39
|
+
args={{
|
|
40
|
+
recent: true,
|
|
41
|
+
recentLabel: "Updated recently"
|
|
42
|
+
}}
|
|
43
|
+
{template}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<Story
|
|
47
|
+
name="Missing date fallback"
|
|
48
|
+
args={{
|
|
49
|
+
date: null
|
|
50
|
+
}}
|
|
51
|
+
{template}
|
|
52
|
+
/>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} UpdateStatusProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} UpdateStatusEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} UpdateStatusSlots */
|
|
4
|
+
export default class UpdateStatus extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: never;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type UpdateStatusProps = typeof __propDef.props;
|
|
11
|
+
export type UpdateStatusEvents = typeof __propDef.events;
|
|
12
|
+
export type UpdateStatusSlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponentTyped } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
[x: string]: never;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* Last updated date or label to display when the content is not recent
|
|
4
|
+
* @type {string|null}
|
|
5
|
+
*/
|
|
6
|
+
export let date = null;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the content has been updated recently
|
|
9
|
+
* @type {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export let recent = false;
|
|
12
|
+
/**
|
|
13
|
+
* Label used when recent is true
|
|
14
|
+
* @type {string}
|
|
15
|
+
*/
|
|
16
|
+
export let recentLabel = "Updated this week";
|
|
17
|
+
/**
|
|
18
|
+
* Prefix used when showing the date
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
export let prefix = "Last updated:";
|
|
22
|
+
/**
|
|
23
|
+
* Message shown when no date is available
|
|
24
|
+
* @type {string}
|
|
25
|
+
*/
|
|
26
|
+
export let missingLabel = "No update date provided";
|
|
27
|
+
/**
|
|
28
|
+
* Whether to show the status dot for recent updates
|
|
29
|
+
* @type {boolean}
|
|
30
|
+
*/
|
|
31
|
+
export let showRecentDot = true;
|
|
32
|
+
/**
|
|
33
|
+
* Optional: Set an additional CSS class for the component
|
|
34
|
+
* @type {string|null}
|
|
35
|
+
*/
|
|
36
|
+
export let cls = null;
|
|
37
|
+
|
|
38
|
+
$: hasDate = typeof date === "string" ? date.trim().length > 0 : date != null;
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div class="ons-update-status-container {cls}">
|
|
42
|
+
<p
|
|
43
|
+
class="ons-update-status ons-u-fs-s"
|
|
44
|
+
class:ons-update-status--recent={recent}
|
|
45
|
+
class:ons-update-status--missing={!recent && !hasDate}
|
|
46
|
+
>
|
|
47
|
+
{#if recent}
|
|
48
|
+
{#if showRecentDot}
|
|
49
|
+
<span class="ons-update-status__dot" aria-hidden="true"></span>
|
|
50
|
+
{/if}
|
|
51
|
+
<span>{recentLabel}</span>
|
|
52
|
+
{:else if !hasDate}
|
|
53
|
+
{missingLabel}
|
|
54
|
+
{:else}
|
|
55
|
+
{prefix} {date}
|
|
56
|
+
{/if}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.ons-update-status-container {
|
|
62
|
+
display: flex;
|
|
63
|
+
justify-content: flex-end;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ons-update-status {
|
|
67
|
+
color: var(--ons-color-text-light);
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.ons-update-status--recent {
|
|
72
|
+
align-items: center;
|
|
73
|
+
color: var(--ons-color-text);
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
gap: 0.375rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ons-update-status--missing {
|
|
79
|
+
color: var(--ons-color-errors, #d4351c);
|
|
80
|
+
text-align: right;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ons-update-status__dot {
|
|
84
|
+
background: #69e073;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
display: inline-block;
|
|
87
|
+
flex: 0 0 auto;
|
|
88
|
+
height: 0.625rem;
|
|
89
|
+
width: 0.625rem;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} UpdateStatusProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} UpdateStatusEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} UpdateStatusSlots */
|
|
4
|
+
export default class UpdateStatus extends SvelteComponentTyped<{
|
|
5
|
+
cls?: string | null | undefined;
|
|
6
|
+
prefix?: string | undefined;
|
|
7
|
+
date?: string | null | undefined;
|
|
8
|
+
recent?: boolean | undefined;
|
|
9
|
+
recentLabel?: string | undefined;
|
|
10
|
+
missingLabel?: string | undefined;
|
|
11
|
+
showRecentDot?: boolean | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
[evt: string]: CustomEvent<any>;
|
|
14
|
+
}, {}> {
|
|
15
|
+
}
|
|
16
|
+
export type UpdateStatusProps = typeof __propDef.props;
|
|
17
|
+
export type UpdateStatusEvents = typeof __propDef.events;
|
|
18
|
+
export type UpdateStatusSlots = typeof __propDef.slots;
|
|
19
|
+
import { SvelteComponentTyped } from "svelte";
|
|
20
|
+
declare const __propDef: {
|
|
21
|
+
props: {
|
|
22
|
+
cls?: string | null | undefined;
|
|
23
|
+
prefix?: string | undefined;
|
|
24
|
+
date?: string | null | undefined;
|
|
25
|
+
recent?: boolean | undefined;
|
|
26
|
+
recentLabel?: string | undefined;
|
|
27
|
+
missingLabel?: string | undefined;
|
|
28
|
+
showRecentDot?: boolean | undefined;
|
|
29
|
+
};
|
|
30
|
+
events: {
|
|
31
|
+
[evt: string]: CustomEvent<any>;
|
|
32
|
+
};
|
|
33
|
+
slots: {};
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Shows a short status message for recently updated content, or falls back to a standard "Last updated" label.
|
|
2
|
+
|
|
3
|
+
Useful for dashboard cards and article metadata where the recency logic is handled outside the component.
|
|
4
|
+
|
|
5
|
+
<!-- prettier-ignore -->
|
|
6
|
+
```html
|
|
7
|
+
<script>
|
|
8
|
+
import { UpdateStatus } from "@onsvisual/svelte-components";
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<UpdateStatus recent />
|
|
12
|
+
<UpdateStatus date="1 September 2026" />
|
|
13
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -56,7 +56,9 @@ export { default as Document } from "./components/DocumentList/Document.svelte";
|
|
|
56
56
|
export { default as ErrorPage } from "./components/ErrorPage/ErrorPage.svelte";
|
|
57
57
|
export { default as Image } from "./components/Image/Image.svelte";
|
|
58
58
|
export { default as Notice } from "./components/Notice/Notice.svelte";
|
|
59
|
+
export { default as UpdateStatus } from "./components/UpdateStatus/UpdateStatus.svelte";
|
|
59
60
|
export { default as ShareButtons } from "./components/ShareButtons/ShareButtons.svelte";
|
|
61
|
+
export { default as ChangeStat } from "./components/ChangeStat/ChangeStat.svelte";
|
|
60
62
|
export { default as Summary } from "./components/Summary/Summary.svelte";
|
|
61
63
|
export { default as Timeline } from "./components/Timeline/Timeline.svelte";
|
|
62
64
|
export { default as TimelineItem } from "./components/Timeline/TimelineItem.svelte";
|
package/dist/index.js
CHANGED
|
@@ -77,7 +77,9 @@ export { default as Document } from "./components/DocumentList/Document.svelte";
|
|
|
77
77
|
export { default as ErrorPage } from "./components/ErrorPage/ErrorPage.svelte";
|
|
78
78
|
export { default as Image } from "./components/Image/Image.svelte";
|
|
79
79
|
export { default as Notice } from "./components/Notice/Notice.svelte";
|
|
80
|
+
export { default as UpdateStatus } from "./components/UpdateStatus/UpdateStatus.svelte";
|
|
80
81
|
export { default as ShareButtons } from "./components/ShareButtons/ShareButtons.svelte";
|
|
82
|
+
export { default as ChangeStat } from "./components/ChangeStat/ChangeStat.svelte";
|
|
81
83
|
export { default as Summary } from "./components/Summary/Summary.svelte";
|
|
82
84
|
export { default as Timeline } from "./components/Timeline/Timeline.svelte";
|
|
83
85
|
export { default as TimelineItem } from "./components/Timeline/TimelineItem.svelte";
|