@onsvisual/svelte-components 1.1.56 → 1.1.58

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.
Files changed (28) hide show
  1. package/dist/components/BarChart/BarChart.stories.svelte +1 -1
  2. package/dist/components/ChangeStat/ChangeStat.stories.svelte +67 -0
  3. package/dist/components/ChangeStat/ChangeStat.stories.svelte.d.ts +23 -0
  4. package/dist/components/ChangeStat/ChangeStat.svelte +167 -0
  5. package/dist/components/ChangeStat/ChangeStat.svelte.d.ts +33 -0
  6. package/dist/components/ChangeStat/docs/component.md +14 -0
  7. package/dist/components/Chart/Chart.stories.svelte +1 -1
  8. package/dist/components/ColumnChart/ColumnChart.stories.svelte +1 -1
  9. package/dist/components/DataCard/DataCard.stories.svelte +1 -1
  10. package/dist/components/DotPlotChart/DotPlotChart.stories.svelte +1 -1
  11. package/dist/components/Dropdown/Dropdown.svelte +10 -3
  12. package/dist/components/Dropdown/Dropdown.svelte.d.ts +2 -0
  13. package/dist/components/Input/Input.stories.svelte +2 -0
  14. package/dist/components/Input/docs/component.md +163 -0
  15. package/dist/components/LineChart/LineChart.stories.svelte +1 -1
  16. package/dist/components/ScatterChart/ScatterChart.stories.svelte +1 -1
  17. package/dist/components/Select/Select.svelte +9 -0
  18. package/dist/components/Select/Select.svelte.d.ts +2 -0
  19. package/dist/components/Table/Table.stories.svelte +1 -1
  20. package/dist/components/Toolbar/ToolbarIcon.svelte +5 -0
  21. package/dist/components/UpdateStatus/UpdateStatus.stories.svelte +52 -0
  22. package/dist/components/UpdateStatus/UpdateStatus.stories.svelte.d.ts +23 -0
  23. package/dist/components/UpdateStatus/UpdateStatus.svelte +91 -0
  24. package/dist/components/UpdateStatus/UpdateStatus.svelte.d.ts +35 -0
  25. package/dist/components/UpdateStatus/docs/component.md +13 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +2 -0
  28. package/package.json +1 -1
@@ -6,7 +6,7 @@
6
6
  import componentDocs from "./docs/component.md?raw";
7
7
 
8
8
  const { Story } = defineMeta({
9
- title: "Charts/BarChart",
9
+ title: "Data visualisation/BarChart",
10
10
  component: BarChart,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {
@@ -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
+ ```
@@ -73,7 +73,7 @@
73
73
  ];
74
74
 
75
75
  const { Story } = defineMeta({
76
- title: "Charts/Chart",
76
+ title: "Data visualisation/Chart",
77
77
  component: Chart,
78
78
  tags: ["autodocs"],
79
79
  argTypes: {},
@@ -6,7 +6,7 @@
6
6
  import componentDocs from "./docs/component.md?raw";
7
7
 
8
8
  const { Story } = defineMeta({
9
- title: "Charts/ColumnChart",
9
+ title: "Data visualisation/ColumnChart",
10
10
  component: ColumnChart,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {
@@ -7,7 +7,7 @@
7
7
  import data from "../../demo-data/data-scatter.js";
8
8
 
9
9
  const { Story } = defineMeta({
10
- title: "Charts/DataCard",
10
+ title: "Data visualisation/DataCard",
11
11
  component: DataCard,
12
12
  tags: ["autodocs"],
13
13
  argTypes: {
@@ -6,7 +6,7 @@
6
6
  import componentDocs from "./docs/component.md?raw";
7
7
 
8
8
  const { Story } = defineMeta({
9
- title: "Charts/DotPlotChart",
9
+ title: "Data visualisation/DotPlotChart",
10
10
  component: DotPlotChart,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {},
@@ -23,6 +23,11 @@
23
23
  * @type {boolean}
24
24
  */
25
25
  export let hideLabel = false;
26
+ /**
27
+ * An optional description to help users know what to enter
28
+ * @type {string|null}
29
+ */
30
+ export let description = null;
26
31
  /**
27
32
  * An optional placeholder text
28
33
  * @type {string}
@@ -51,9 +56,11 @@
51
56
  </script>
52
57
 
53
58
  <div class="ons-field {cls}">
54
- {#if label}
55
- <label class="ons-label" for={id} class:ons-u-vh={hideLabel}>{label}</label>
56
- {/if}
59
+ {#if label}<label class="ons-label" for={id} class:ons-u-vh={hideLabel}>{label}</label>{/if}
60
+ {#if description}<span
61
+ id="description-hint"
62
+ class="ons-label__description ons-input--with-description">{description}</span
63
+ >{/if}
57
64
  <select
58
65
  {id}
59
66
  {name}
@@ -8,6 +8,7 @@ export default class Dropdown extends SvelteComponentTyped<{
8
8
  label?: string | null | undefined;
9
9
  name?: string | null | undefined;
10
10
  hideLabel?: boolean | undefined;
11
+ description?: string | null | undefined;
11
12
  value?: object | null | undefined;
12
13
  placeholder?: string | undefined;
13
14
  options?: object[] | undefined;
@@ -31,6 +32,7 @@ declare const __propDef: {
31
32
  label?: string | null | undefined;
32
33
  name?: string | null | undefined;
33
34
  hideLabel?: boolean | undefined;
35
+ description?: string | null | undefined;
34
36
  value?: object | null | undefined;
35
37
  placeholder?: string | undefined;
36
38
  options?: object[] | undefined;
@@ -13,6 +13,7 @@
13
13
  });
14
14
  </script>
15
15
 
16
+
16
17
  {#snippet template(args)}
17
18
  <div style:padding="12px">
18
19
  <Input {...args} />
@@ -71,3 +72,4 @@
71
72
  }}
72
73
  {template}
73
74
  />
75
+
@@ -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
@@ -6,7 +6,7 @@
6
6
  import componentDocs from "./docs/component.md?raw";
7
7
 
8
8
  const { Story } = defineMeta({
9
- title: "Charts/LineChart",
9
+ title: "Data visualisation/LineChart",
10
10
  component: LineChart,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {
@@ -6,7 +6,7 @@
6
6
  import componentDocs from "./docs/component.md?raw";
7
7
 
8
8
  const { Story } = defineMeta({
9
- title: "Charts/ScatterChart",
9
+ title: "Data visualisation/ScatterChart",
10
10
  component: ScatterChart,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {},
@@ -54,6 +54,11 @@
54
54
  * @type {boolean}
55
55
  */
56
56
  export let hideLabel = false;
57
+ /**
58
+ * An optional description to help users know what to enter
59
+ * @type {string|null}
60
+ */
61
+ export let description = null;
57
62
  /**
58
63
  * An optional placeholder text
59
64
  * @type {string}
@@ -217,6 +222,10 @@
217
222
  {:else}
218
223
  <div class="ons-field {cls}">
219
224
  {#if label}<label for={id} class="ons-label" class:ons-u-vh={hideLabel}>{label}</label>{/if}
225
+ {#if description}<span
226
+ id="description-hint"
227
+ class="ons-label__description ons-input--with-description">{description}</span
228
+ >{/if}
220
229
  <div class="ons-autocomplete-wrapper">
221
230
  {#if mounted}
222
231
  <div
@@ -7,6 +7,7 @@ export default class Select extends SvelteComponentTyped<{
7
7
  label?: string | null | undefined;
8
8
  name?: string | undefined;
9
9
  hideLabel?: boolean | undefined;
10
+ description?: string | null | undefined;
10
11
  value?: object | undefined;
11
12
  placeholder?: string | undefined;
12
13
  options?: array;
@@ -38,6 +39,7 @@ declare const __propDef: {
38
39
  label?: string | null | undefined;
39
40
  name?: string | undefined;
40
41
  hideLabel?: boolean | undefined;
42
+ description?: string | null | undefined;
41
43
  value?: object | undefined;
42
44
  placeholder?: string | undefined;
43
45
  options?: array;
@@ -28,7 +28,7 @@
28
28
  ];
29
29
 
30
30
  const { Story } = defineMeta({
31
- title: "Charts/Table",
31
+ title: "Data visualisation/Table",
32
32
  component: Table,
33
33
  tags: ["autodocs"],
34
34
  argTypes: {},
@@ -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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onsvisual/svelte-components",
3
- "version": "1.1.56",
3
+ "version": "1.1.58",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "npm run build:package && npm run build:docs",