@communitiesuk/svelte-component-library 0.1.19-beta.33 → 0.1.19-beta.34

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.
@@ -177,7 +177,6 @@
177
177
  {#if topLabel && layout.topLabelY !== null}
178
178
  <text x={0} y={layout.topLabelY + 14} fill="#666" font-size="0.75em">
179
179
  Number of areas
180
- {minX}, {maxX}
181
180
  </text>
182
181
  {/if}
183
182
 
@@ -201,28 +200,14 @@
201
200
 
202
201
  <g transform="translate(0, {layout.chartY})">
203
202
  {#if showYAxis}
204
- <!-- <Axis
205
- bind:axisDomain
206
- bind:ticksArray={ticksDomain}
207
- {chartHeight}
208
- chartWidth={chartWidth - markerRadius * 2}
209
- orientation={{ axis: "x", position: "bottom" }}
210
- range={[markerRadius, chartWidth - markerRadius]}
211
- domain={[xValueFirst, xValueLast]}
212
- {min}
213
- {max}
214
- fontSize={14}
215
- {floor}
216
- {ceiling}
217
- {numberOfTicks}
218
- {polarity}
219
- {showTickMarks}
220
- {showGridlines}
221
- {labelFormatter}
222
- {niceTicks}
223
- {markerRadius}
224
- {distribution}
225
- ></Axis> -->
203
+ {#each [3, 6, 9] as x, i}
204
+ <path
205
+ transform="translate(0, {scaleLinear([0, 9], [height, 0])(x)})"
206
+ d={`M0 0 l${containerWidth - 25} 0`}
207
+ stroke="grey"
208
+ stroke-width={0.25}
209
+ ></path>
210
+ {/each}
226
211
  {/if}
227
212
 
228
213
  {#each bins as bin, i}
@@ -391,6 +391,30 @@
391
391
  fill={colorScale[number]}
392
392
  ></rect></g
393
393
  >{/each}
394
+ {#if showAxis}
395
+ <Axis
396
+ bind:axisDomain
397
+ bind:ticksArray={ticksDomain}
398
+ {chartHeight}
399
+ chartWidth={chartWidth - markerRadius * 2}
400
+ orientation={{ axis: "x", position: "bottom" }}
401
+ range={[markerRadius, chartWidth - markerRadius]}
402
+ domain={[xTickFirst, xTickLast]}
403
+ {min}
404
+ {max}
405
+ fontSize={14}
406
+ {floor}
407
+ {ceiling}
408
+ {numberOfTicks}
409
+ {polarity}
410
+ {showTickMarks}
411
+ {showGridlines}
412
+ {labelFormatter}
413
+ {niceTicks}
414
+ {markerRadius}
415
+ {distribution}
416
+ ></Axis>
417
+ {/if}
394
418
  {#each Object.entries(positionChart.rowData) as [tier, points]}
395
419
  {#each points as rowValue, i}
396
420
  {#if !isNaN(Number(rowValue.value))}
@@ -430,7 +454,7 @@
430
454
  stroke={rowValue.color === "inherit"
431
455
  ? colorScale[segmentIndex(rowValue.value)]
432
456
  : rowValue.color}
433
- stroke-width={rowValue.markerRadius * 5}
457
+ stroke-width={rowValue.markerRadius * 7}
434
458
  opacity={0}
435
459
  pointer-events={rowValue.pointerEvents}
436
460
  ></line>
@@ -455,7 +479,7 @@
455
479
  fill={rowValue.color === "inherit"
456
480
  ? colorScale[segmentIndex(rowValue.value)]
457
481
  : rowValue.color}
458
- stroke-width={3}
482
+ stroke-width={5}
459
483
  opacity={rowValue.opacity}
460
484
  ></circle>
461
485
  <circle
@@ -465,8 +489,8 @@
465
489
  fill={rowValue.color === "inherit"
466
490
  ? colorScale[segmentIndex(rowValue.value)]
467
491
  : rowValue.color}
468
- stroke="black"
469
- stroke-width={1.5}
492
+ stroke="#111"
493
+ stroke-width={3}
470
494
  opacity={rowValue.opacity}
471
495
  ></circle>
472
496
  {/if}
@@ -474,30 +498,7 @@
474
498
  {/if}
475
499
  {/each}
476
500
  {/each}
477
- {#if showAxis}
478
- <Axis
479
- bind:axisDomain
480
- bind:ticksArray={ticksDomain}
481
- {chartHeight}
482
- chartWidth={chartWidth - markerRadius * 2}
483
- orientation={{ axis: "x", position: "bottom" }}
484
- range={[markerRadius, chartWidth - markerRadius]}
485
- domain={[xTickFirst, xTickLast]}
486
- {min}
487
- {max}
488
- fontSize={14}
489
- {floor}
490
- {ceiling}
491
- {numberOfTicks}
492
- {polarity}
493
- {showTickMarks}
494
- {showGridlines}
495
- {labelFormatter}
496
- {niceTicks}
497
- {markerRadius}
498
- {distribution}
499
- ></Axis>
500
- {/if}
501
+
501
502
  {#if showAverage}
502
503
  <g
503
504
  transform="translate({xFunction(chartDomain)(averageValue) +
@@ -2,48 +2,63 @@
2
2
  import Button from "./../../ui/Button.svelte";
3
3
 
4
4
  let {
5
- data = undefined,
6
- metaData = undefined,
5
+ data = [],
6
+ metadata = {},
7
7
  caption = undefined,
8
8
  colourScale = undefined,
9
9
  } = $props();
10
10
 
11
- let localCopyOfData = $derived([...data]);
11
+ let localCopyOfData = $state([...(data ?? [])]);
12
+
13
+ let sortState = $state({
14
+ column: null,
15
+ order: null, // "ascending" | "descending" | null
16
+ });
17
+
18
+ $effect(() => {
19
+ localCopyOfData = [...(data ?? [])];
20
+ });
12
21
 
13
22
  function hasUniqueValues(array, key) {
14
23
  const seen = new Set();
15
24
  for (const obj of array) {
16
25
  if (seen.has(obj[key])) {
17
- return false; // Duplicate found
26
+ return false;
18
27
  }
19
28
  seen.add(obj[key]);
20
29
  }
21
- return true; // All values are unique
30
+ return true;
22
31
  }
23
32
 
24
- let columns = [];
25
-
26
- for (const column in localCopyOfData[0]) {
27
- // create a variable to store whether the key is unique or not
28
- const keyIsUnique = hasUniqueValues(localCopyOfData, column);
29
- // get data type of each column
30
- const columnDataType = typeof localCopyOfData[0][column];
31
- // for each one create an object and push it into the array
32
- const columnObject = {
33
- key: column,
34
- isUnique: keyIsUnique,
35
- dataType: columnDataType,
36
- };
37
- columns.push(columnObject);
38
- }
33
+ let columns = $derived.by(() => {
34
+ if (!data || data.length === 0) return [];
35
+
36
+ const derivedColumns = [];
39
37
 
40
- $inspect("columns array is ", columns);
38
+ for (const column in data[0]) {
39
+ const keyIsUnique = hasUniqueValues(data, column);
40
+ const columnDataType = typeof data[0][column];
41
+
42
+ derivedColumns.push({
43
+ key: column,
44
+ isUnique: keyIsUnique,
45
+ dataType: columnDataType,
46
+ id: metadata[column].order,
47
+ });
48
+ }
49
+
50
+ const orderedColumns = derivedColumns.sort((a, b) => a.id - b.id);
51
+
52
+ return orderedColumns;
53
+ });
41
54
 
42
- const metrics = columns
43
- .filter((column) => column.dataType === "number")
44
- .map((column) => column.key);
55
+ $inspect(columns);
45
56
 
46
- let sortState = $state({ column: "sortedColumn", order: "ascending" });
57
+ const metrics = $derived(
58
+ columns
59
+ .filter((column) => column.dataType === "number")
60
+ .map((column) => column.key),
61
+ );
47
62
 
48
63
  function updateSortState(columnToSort, sortOrder) {
49
64
  sortState.column = columnToSort;
@@ -51,61 +66,63 @@
51
66
  }
52
67
 
53
68
  function sortFunction() {
54
- if (typeof localCopyOfData[0][sortState["column"]] === "number") {
69
+ if (!localCopyOfData.length || !sortState.column || !sortState.order)
70
+ return;
71
+
72
+ const sorted = [...localCopyOfData];
73
+
74
+ if (typeof sorted[0][sortState.column] === "number") {
55
75
  if (sortState.order === "ascending") {
56
- localCopyOfData.sort(
57
- (a, b) => a[sortState.column] - b[sortState.column],
58
- );
76
+ sorted.sort((a, b) => a[sortState.column] - b[sortState.column]);
59
77
  } else {
60
- localCopyOfData.sort(
61
- (a, b) => b[sortState.column] - a[sortState.column],
62
- );
78
+ sorted.sort((a, b) => b[sortState.column] - a[sortState.column]);
63
79
  }
64
80
  }
65
- if (typeof localCopyOfData[0][sortState["column"]] === "string") {
81
+
82
+ if (typeof sorted[0][sortState.column] === "string") {
66
83
  if (sortState.order === "ascending") {
67
- localCopyOfData.sort((a, b) =>
68
- a[sortState["column"]].localeCompare(b[sortState["column"]]),
84
+ sorted.sort((a, b) =>
85
+ a[sortState.column].localeCompare(b[sortState.column]),
69
86
  );
70
87
  } else {
71
- localCopyOfData.sort((a, b) =>
72
- b[sortState["column"]].localeCompare(a[sortState["column"]]),
88
+ sorted.sort((a, b) =>
89
+ b[sortState.column].localeCompare(a[sortState.column]),
73
90
  );
74
91
  }
75
92
  }
76
- }
77
-
78
- $inspect(
79
- sortState,
80
- localCopyOfData[0],
81
- typeof localCopyOfData[0][sortState["column"]],
82
- );
83
93
 
84
- // heat map
85
-
86
- // calculate the min and max of each metric
87
- const minAndMaxValues = {}; // create an empty object to store them in
88
- for (const metric of metrics) {
89
- // get the values
90
- const metricValues = localCopyOfData.map((item) => item[metric]);
91
- const min = Math.min(...metricValues);
92
- const max = Math.max(...metricValues);
93
- // store them
94
- minAndMaxValues[metric] = { min, max };
94
+ localCopyOfData = sorted;
95
95
  }
96
96
 
97
- localCopyOfData = localCopyOfData.map((row) => {
98
- const rowWithNorms = { ...row };
97
+ const minAndMaxValues = $derived.by(() => {
98
+ const result = {};
99
99
 
100
100
  for (const metric of metrics) {
101
- const { min, max } = minAndMaxValues[metric];
102
- const value = row[metric];
103
- const normalisedValue = (value - min) / (max - min);
101
+ const metricValues = localCopyOfData.map((item) => item[metric]);
102
+ const min = Math.min(...metricValues);
103
+ const max = Math.max(...metricValues);
104
104
 
105
- rowWithNorms[`${metric}__normalised`] = normalisedValue;
105
+ result[metric] = { min, max };
106
106
  }
107
107
 
108
- return rowWithNorms;
108
+ return result;
109
+ });
110
+
111
+ const displayRows = $derived.by(() => {
112
+ return localCopyOfData.map((row) => {
113
+ const rowWithNorms = { ...row };
114
+
115
+ for (const metric of metrics) {
116
+ const { min, max } = minAndMaxValues[metric];
117
+ const value = row[metric];
118
+
119
+ const normalisedValue = max === min ? 0.5 : (value - min) / (max - min);
120
+
121
+ rowWithNorms[`${metric}__normalised`] = normalisedValue;
122
+ }
123
+
124
+ return rowWithNorms;
125
+ });
109
126
  });
110
127
 
111
128
  function normToColor(norm) {
@@ -119,6 +136,27 @@
119
136
  }
120
137
 
121
138
  const colorKey = Object.entries({ Good: 1, Ok: 0.5, Bad: 0 });
139
+
140
+ function handleSort(columnKey) {
141
+ let newDirection = "ascending";
142
+
143
+ if (sortState.column === columnKey) {
144
+ if (sortState.order === "ascending") {
145
+ newDirection = "descending";
146
+ } else if (sortState.order === "descending") {
147
+ newDirection = null;
148
+ }
149
+ }
150
+
151
+ if (newDirection === null) {
152
+ updateSortState(null, null);
153
+ localCopyOfData = [...(data ?? [])];
154
+ return;
155
+ }
156
+
157
+ updateSortState(columnKey, newDirection);
158
+ sortFunction();
159
+ }
122
160
  </script>
123
161
 
124
162
  <div class="p-4">
@@ -126,7 +164,10 @@
126
164
  <div class="legend">
127
165
  <div>Colour key:</div>
128
166
  {#each colorKey as key}
129
- <div class="color-keys" style="background-color: {normToColor(key[1])}">
167
+ <div
168
+ class="color-keys"
169
+ style={`background-color: ${normToColor(key[1])}`}
170
+ >
130
171
  {key[0]}
131
172
  </div>
132
173
  {/each}
@@ -135,62 +176,65 @@
135
176
 
136
177
  <div class="table-container">
137
178
  <div id="table-caption" class="sticky-caption">{caption}</div>
179
+
138
180
  <table class="govuk-table" data-module="moj-sortable-table">
139
- <thead class="govuk-table__head"
140
- ><tr class="govuk-table__row">
181
+ <thead class="govuk-table__head">
182
+ <tr class="govuk-table__row">
141
183
  {#each columns as column}
142
184
  <th
143
185
  scope="col"
144
186
  class={`govuk-table__header ${column.dataType === "number" ? "govuk-table__header--numeric" : ""}`}
145
- title={metaData[column.key].explainer}
146
- aria-sort={sortState.column !== column.key
187
+ title={metadata[column.key]?.explainer}
188
+ style={metadata[column.key]?.width
189
+ ? `min-width: ${metadata[column.key].width}`
190
+ : undefined}
191
+ aria-sort={sortState.column !== column.key ||
192
+ sortState.order === null
147
193
  ? "none"
148
- : sortState.column === column.key &&
149
- sortState.order === "descending"
194
+ : sortState.order === "descending"
150
195
  ? "descending"
151
196
  : "ascending"}
152
197
  >
153
198
  <div class="header">
154
199
  <Button
155
- textContent={metaData[column.key].shortLabel}
200
+ textContent={metadata[column.key]?.shortLabel ?? column.key}
156
201
  buttonType={"table header"}
157
- onClickFunction={() => {
158
- const newDirection =
159
- sortState.column === column.key &&
160
- sortState.order === "ascending"
161
- ? "descending"
162
- : "ascending";
163
-
164
- updateSortState(column.key, newDirection);
165
- sortFunction();
166
- }}
202
+ direction={sortState.column === column.key
203
+ ? sortState.order
204
+ : null}
205
+ onClickFunction={() => handleSort(column.key)}
167
206
  ></Button>
168
- </div></th
169
- >
207
+ </div>
208
+ </th>
170
209
  {/each}
171
- </tr></thead
172
- >
210
+ </tr>
211
+ </thead>
212
+
173
213
  <tbody class="govuk-table__body">
174
- {#each localCopyOfData as row}
214
+ {#each displayRows as row}
175
215
  <tr class="govuk-table__row">
176
216
  {#each columns as column}
177
217
  {#if column.dataType === "number"}
178
218
  {#if colourScale === "On"}
179
219
  <td
180
220
  class="govuk-table__cell govuk-table__cell--numeric"
181
- style="background-color: {metaData[column.key].direction ===
182
- 'Higher is better'
183
- ? normToColor(row[column.key + '__normalised'])
184
- : normToColorReverse(row[column.key + '__normalised'])}"
185
- >{row[column.key]}</td
221
+ style={`background-color: ${
222
+ metadata[column.key]?.direction === "Higher is better"
223
+ ? normToColor(row[column.key + "__normalised"])
224
+ : normToColorReverse(row[column.key + "__normalised"])
225
+ }`}
186
226
  >
227
+ {@html row[column.key]}
228
+ </td>
187
229
  {:else}
188
- <td class="govuk-table__cell govuk-table__cell--numeric"
189
- >{row[column.key]}</td
190
- >
230
+ <td class="govuk-table__cell govuk-table__cell--numeric">
231
+ {@html row[column.key]}
232
+ </td>
191
233
  {/if}
192
234
  {:else}
193
- <td class="govuk-table__cell">{row[column.key]}</td>
235
+ <td class="govuk-table__cell">
236
+ {@html row[column.key]}
237
+ </td>
194
238
  {/if}
195
239
  {/each}
196
240
  </tr>
@@ -202,7 +246,7 @@
202
246
 
203
247
  <style>
204
248
  .table-container {
205
- max-height: 85vh;
249
+ max-height: 60vh;
206
250
  overflow-y: auto;
207
251
  overflow-x: scroll;
208
252
  width: 100%;
@@ -228,8 +272,14 @@
228
272
  gap: 20px;
229
273
  margin: 10px;
230
274
  }
275
+
231
276
  .color-keys {
232
277
  border-radius: 10%;
233
278
  padding: 6px;
234
279
  }
280
+
281
+ .header {
282
+ display: flex;
283
+ align-items: center;
284
+ }
235
285
  </style>
@@ -4,14 +4,14 @@ type Table = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  };
6
6
  declare const Table: import("svelte").Component<{
7
- data?: any;
8
- metaData?: any;
7
+ data?: any[];
8
+ metadata?: Record<string, any>;
9
9
  caption?: any;
10
10
  colourScale?: any;
11
11
  }, {}, "">;
12
12
  type $$ComponentProps = {
13
- data?: any;
14
- metaData?: any;
13
+ data?: any[];
14
+ metadata?: Record<string, any>;
15
15
  caption?: any;
16
16
  colourScale?: any;
17
17
  };
@@ -16,6 +16,8 @@
16
16
  minSectionsAllSectionToggle = 2,
17
17
  rememberIsExpandedState = true,
18
18
  headingLevel = "h4",
19
+ useCustomSectionIcon = false,
20
+ useLightweightAccordionStyles = false,
19
21
  }: {
20
22
  sections: {
21
23
  heading: string;
@@ -34,35 +36,35 @@
34
36
  minSectionsAllSectionToggle?: number;
35
37
  rememberIsExpandedState?: boolean;
36
38
  headingLevel?: string;
39
+ useCustomSectionIcon?: boolean;
40
+ useLightweightAccordionStyles?: boolean;
37
41
  } = $props();
38
42
 
39
- //Attempt to ensure that ids are unique by attaching extra characters
43
+ // Attempt to ensure that ids are unique by attaching extra characters
40
44
  let uniqueSections = $derived(
41
- sections.map((section) => {
42
- return {
43
- ...section,
44
- uniqueid:
45
- section.id +
46
- section.heading.slice(1, 3) +
47
- (section?.summary?.slice(0, 2) ?? ""),
48
- };
49
- }),
45
+ sections.map((section) => {
46
+ return {
47
+ ...section,
48
+ uniqueid:
49
+ section.id +
50
+ section.heading.slice(1, 3) +
51
+ (section?.summary?.slice(0, 2) ?? ""),
52
+ };
53
+ }),
50
54
  );
51
55
 
52
56
  let expandedSections = $derived(
53
- new SvelteSet(
54
- uniqueSections
55
- .filter((section) => section.expanded)
56
- .map((section) => section.uniqueid),
57
- ),
57
+ new SvelteSet(
58
+ uniqueSections
59
+ .filter((section) => section.expanded)
60
+ .map((section) => section.uniqueid),
61
+ ),
58
62
  );
59
63
 
60
64
  let allExpanded = $derived(expandedSections.size === sections.length);
61
65
 
62
66
  let ariaLiveValue: "polite" | "off" | "assertive" | null | undefined =
63
- $state("polite");
64
-
65
- // Event handlers
67
+ $state("polite");
66
68
 
67
69
  function toggleSection(uniqueid: string) {
68
70
  if (expandedSections.has(uniqueid)) {
@@ -70,28 +72,25 @@
70
72
  } else {
71
73
  expandedSections.add(uniqueid);
72
74
  }
73
- //Announce the contents change when an accoridion section is expanded
75
+
74
76
  ariaLiveValue = "polite";
75
77
  }
76
78
 
77
79
  function toggleAll() {
78
80
  if (!allExpanded) {
79
81
  uniqueSections.forEach((section) =>
80
- expandedSections.add(section.uniqueid),
82
+ expandedSections.add(section.uniqueid),
81
83
  );
82
84
  } else {
83
85
  expandedSections.clear();
84
86
  }
85
- //Don't announce all of the changes when we open all sections - this gets noisy and the content isn't associated with the label
87
+
86
88
  ariaLiveValue = "off";
87
89
  }
88
90
 
89
- // Only use session storage logic if rememberIsExpandedState is true
90
91
  onMount(() => {
91
92
  if (rememberIsExpandedState) {
92
93
  uniqueSections.forEach((section) => {
93
- // If the section is explicitly expanded, respect that.
94
- // Otherwise, try to restore from session storage.
95
94
  if (section.expanded) {
96
95
  expandedSections.add(section.uniqueid);
97
96
  } else {
@@ -102,7 +101,6 @@
102
101
  }
103
102
  });
104
103
  } else {
105
- // If rememberIsExpandedState is false, just respect the initial `section.expanded` values
106
104
  uniqueSections.forEach((section) => {
107
105
  if (section.expanded) {
108
106
  expandedSections.add(section.uniqueid);
@@ -111,13 +109,12 @@
111
109
  }
112
110
  });
113
111
 
114
- // Effect to update sessionStorage when uniqueSections change
115
112
  $effect(() => {
116
113
  if (rememberIsExpandedState) {
117
114
  uniqueSections.forEach((section) => {
118
115
  sessionStorage.setItem(
119
- section.uniqueid,
120
- expandedSections.has(section.uniqueid).toString(),
116
+ section.uniqueid,
117
+ expandedSections.has(section.uniqueid).toString(),
121
118
  );
122
119
  });
123
120
  }
@@ -125,67 +122,112 @@
125
122
  </script>
126
123
 
127
124
  <div
128
- class="govuk-accordion"
129
- data-module="govuk-accordion"
130
- id="accordion-default"
125
+ class="govuk-accordion"
126
+ class:govuk-accordion--download-style={useLightweightAccordionStyles}
127
+ data-module="govuk-accordion"
128
+ id="accordion-default"
131
129
  >
132
130
  <div
133
- class="govuk-accordion__controls"
134
- hidden={!allSectionToggle ||
131
+ class="govuk-accordion__controls"
132
+ hidden={!allSectionToggle ||
135
133
  uniqueSections.length < minSectionsAllSectionToggle}
134
+ style="display: flex; flex-direction: row"
136
135
  >
137
136
  <button
138
- type="button"
139
- class="govuk-accordion__show-all"
140
- aria-expanded={allExpanded}
141
- onclick={toggleAll}
137
+ type="button"
138
+ class="govuk-accordion__show-all"
139
+ aria-expanded={allExpanded}
140
+ onclick={toggleAll}
142
141
  >
143
142
  <span class="govuk-accordion__show-all-text">
144
143
  {allExpanded ? hideAllSections : showAllSections}
145
144
  </span>
146
145
  <span
147
- class="govuk-accordion-nav__chevron"
148
- class:govuk-accordion-nav__chevron--down={!allExpanded}
146
+ class="govuk-accordion-nav__chevron"
147
+ class:govuk-accordion-nav__chevron--down={!allExpanded}
149
148
  ></span>
150
149
  </button>
151
150
  </div>
152
151
 
153
152
  {#snippet headerContent(section, isExpanded)}
154
153
  <button
155
- type="button"
156
- aria-controls="{section.uniqueid}-content"
157
- id="{section.uniqueid}-button"
158
- class="govuk-accordion__section-button"
159
- aria-expanded={isExpanded}
160
- onclick={() => toggleSection(section.uniqueid)}
161
- aria-label="{section.heading}, {section.summary
154
+ type="button"
155
+ aria-controls="{section.uniqueid}-content"
156
+ id="{section.uniqueid}-button"
157
+ class="govuk-accordion__section-button"
158
+ aria-expanded={isExpanded}
159
+ onclick={() => toggleSection(section.uniqueid)}
160
+ aria-label="{section.heading}, {section.summary
162
161
  ? section.summary + ','
163
162
  : ''} {isExpanded ? hideSectionAriaLabel : showSectionAriaLabel}"
164
163
  >
165
164
  <span class="govuk-accordion__section-heading-text">
166
- <span class="govuk-accordion__section-heading-text-focus"
167
- >{section.heading}</span
168
- >
165
+ <span class="govuk-accordion__section-heading-text-focus">
166
+ {section.heading}
167
+ </span>
169
168
  </span>
170
169
 
171
170
  {#if section.summary}
172
171
  <span
173
- class="govuk-visually-hidden govuk-accordion__section-heading-divider"
174
- >,
172
+ class="govuk-visually-hidden govuk-accordion__section-heading-divider"
173
+ >
174
+ ,
175
175
  </span>
176
176
  <span class="govuk-accordion__section-summary govuk-body">
177
- <span class="govuk-accordion__section-summary-focus"
178
- >{section.summary}</span
179
- >
177
+ <span class="govuk-accordion__section-summary-focus">
178
+ {section.summary}
179
+ </span>
180
180
  </span>
181
181
  {/if}
182
182
 
183
183
  <span class="govuk-accordion__section-toggle" data-nosnippet>
184
184
  <span class="govuk-accordion__section-toggle-focus">
185
- <span
186
- class="govuk-accordion-nav__chevron"
187
- class:govuk-accordion-nav__chevron--down={!isExpanded}
188
- ></span>
185
+ {#if useCustomSectionIcon}
186
+ <svg
187
+ width="30"
188
+ height="30"
189
+ viewBox="0 0 30 30"
190
+ aria-hidden="true"
191
+ class="govuk-accordion__custom-icon"
192
+ >
193
+ <g
194
+ transform="translate({12 + (isExpanded ? 3 : 0)},{15 + (isExpanded ? -3 : 0)}) rotate({isExpanded ? -45 : -135})"
195
+ >
196
+ {#each [-5, 5] as cxcy}
197
+ <circle
198
+ data-role={isExpanded
199
+ ? "button-circle-selected"
200
+ : "button-circle-unselected"}
201
+ cx={cxcy}
202
+ cy={cxcy}
203
+ ></circle>
204
+ {/each}
205
+
206
+ <path
207
+ data-role={isExpanded
208
+ ? "button-path-selected"
209
+ : "button-path-unselected"}
210
+ d="M -5 -5 l0 9.75 m0.25 0.25 l9.75 0"
211
+ ></path>
212
+
213
+ <rect
214
+ data-role={isExpanded
215
+ ? "button-rect-selected"
216
+ : "button-rect-unselected"}
217
+ transform="translate(-6, 4)"
218
+ width="2"
219
+ height="2"
220
+ rx="4px"
221
+ ></rect>
222
+ </g>
223
+ </svg>
224
+ {:else}
225
+ <span
226
+ class="govuk-accordion-nav__chevron"
227
+ class:govuk-accordion-nav__chevron--down={!isExpanded}
228
+ ></span>
229
+ {/if}
230
+
189
231
  <span class="govuk-accordion__section-toggle-text">
190
232
  {isExpanded ? hideSection : showSection}
191
233
  </span>
@@ -197,8 +239,8 @@
197
239
  {#each uniqueSections as section}
198
240
  {@const isExpanded = expandedSections.has(section.uniqueid)}
199
241
  <div
200
- class="govuk-accordion__section"
201
- class:govuk-accordion__section--expanded={isExpanded}
242
+ class="govuk-accordion__section"
243
+ class:govuk-accordion__section--expanded={isExpanded}
202
244
  >
203
245
  <div class="govuk-accordion__section-header">
204
246
  {#if headingLevel.toLowerCase() == "h2"}
@@ -223,13 +265,14 @@
223
265
  </h6>
224
266
  {/if}
225
267
  </div>
268
+
226
269
  <div
227
- id="{section.uniqueid}-content"
228
- class="govuk-accordion__section-content"
229
- aria-live={ariaLiveValue}
230
- hidden={!isExpanded}
231
- role={uniqueSections.length < 6 ? "region" : ""}
232
- aria-labelledby={uniqueSections.length < 6
270
+ id="{section.uniqueid}-content"
271
+ class="govuk-accordion__section-content"
272
+ aria-live={ariaLiveValue}
273
+ hidden={!isExpanded}
274
+ role={uniqueSections.length < 6 ? "region" : ""}
275
+ aria-labelledby={uniqueSections.length < 6
233
276
  ? section.uniqueid + "-button"
234
277
  : ""}
235
278
  >
@@ -242,3 +285,106 @@
242
285
  </div>
243
286
  {/each}
244
287
  </div>
288
+
289
+ <style>
290
+ .govuk-accordion__custom-icon {
291
+ display: inline-block;
292
+ vertical-align: middle;
293
+ flex: 0 0 auto;
294
+ margin-right: 0.35rem;
295
+ }
296
+
297
+ [data-role="button-circle-unselected"] {
298
+ fill: #0B0C0C;
299
+ stroke: #0B0C0C;
300
+ stroke-width: 1.5;
301
+ }
302
+
303
+ [data-role="button-circle-selected"] {
304
+ fill: #0B0C0C;
305
+ stroke: #0B0C0C;
306
+ stroke-width: 1.5;
307
+ }
308
+
309
+ [data-role="button-path-unselected"] {
310
+ stroke: #0B0C0C;
311
+ stroke-width: 2;
312
+ fill: none;
313
+ stroke-linecap: round;
314
+ }
315
+
316
+ [data-role="button-path-selected"] {
317
+ stroke: #0B0C0C;
318
+ stroke-width: 2;
319
+ fill: none;
320
+ stroke-linecap: round;
321
+ }
322
+
323
+ [data-role="button-rect-unselected"] {
324
+ fill: #0B0C0C;
325
+ }
326
+
327
+ [data-role="button-rect-selected"] {
328
+ fill: #0B0C0C;
329
+ }
330
+
331
+ /* Alternative style mode */
332
+ .govuk-accordion--download-style {
333
+ background: #f8f8f8;
334
+ padding: 0.75rem 1rem;
335
+ border: 1px solid #e0e0e0;
336
+ }
337
+
338
+ .govuk-accordion--download-style.govuk-accordion {
339
+ border-bottom: 0;
340
+ }
341
+
342
+ .govuk-accordion--download-style .govuk-accordion__section {
343
+ border-bottom: 1px solid #b1b4b6;
344
+ }
345
+
346
+ .govuk-accordion--download-style .govuk-accordion__section-button {
347
+ display: flex;
348
+ align-items: center;
349
+ gap: 0.5rem;
350
+ width: 100%;
351
+ padding: 1rem 0;
352
+ border: 0;
353
+ }
354
+
355
+ .govuk-accordion--download-style .govuk-accordion__section-button:hover {
356
+ color: #0b0c0c;
357
+ }
358
+
359
+ .govuk-accordion--download-style .govuk-accordion__section-toggle {
360
+ order: -1;
361
+ margin: 0;
362
+ min-width: 1rem;
363
+ display: flex;
364
+ align-items: center;
365
+ }
366
+
367
+ .govuk-accordion--download-style .govuk-accordion__section-toggle-focus {
368
+ display: flex;
369
+ align-items: center;
370
+ }
371
+
372
+ .govuk-accordion--download-style .govuk-accordion__section-toggle-text {
373
+ display: none;
374
+ }
375
+
376
+ .govuk-accordion--download-style .govuk-accordion__section-content .govuk-body {
377
+ padding-top: 0;
378
+ margin-top: 0;
379
+ }
380
+
381
+ .govuk-accordion--download-style .govuk-accordion__section-heading-text {
382
+ margin-bottom: 0;
383
+ }
384
+
385
+ .govuk-accordion--download-style .govuk-hint {
386
+ all: unset;
387
+ display: block;
388
+ margin-bottom: 0.5rem;
389
+ }
390
+ </style>
@@ -17,6 +17,8 @@ type $$ComponentProps = {
17
17
  minSectionsAllSectionToggle?: number;
18
18
  rememberIsExpandedState?: boolean;
19
19
  headingLevel?: string;
20
+ useCustomSectionIcon?: boolean;
21
+ useLightweightAccordionStyles?: boolean;
20
22
  };
21
23
  declare const Accordion: import("svelte").Component<$$ComponentProps, {}, "">;
22
24
  type Accordion = ReturnType<typeof Accordion>;
@@ -7,162 +7,257 @@
7
7
  componentNameProp = undefined,
8
8
  onClickFunction = undefined,
9
9
  noPadding = false,
10
+ direction = null, // "ascending" | "descending" | null
11
+ disabled = false,
10
12
  } = $props();
11
13
 
12
14
  let buttonClass = $derived(
13
- buttonType === "default"
14
- ? "govuk-button"
15
- : buttonType === "secondary"
16
- ? "govuk-button govuk-button--secondary"
17
- : buttonType === "warning"
18
- ? "govuk-button govuk-button--warning"
19
- : buttonType === "dark background"
20
- ? "govuk-button govuk-button--inverse"
21
- : undefined,
15
+ buttonType === "default"
16
+ ? "govuk-button"
17
+ : buttonType === "secondary"
18
+ ? "govuk-button govuk-button--secondary"
19
+ : buttonType === "warning"
20
+ ? "govuk-button govuk-button--warning"
21
+ : buttonType === "dark background"
22
+ ? "govuk-button govuk-button--inverse"
23
+ : undefined,
22
24
  );
23
25
  </script>
24
26
 
25
27
  {#if noPadding}
26
28
  {#if buttonType === "moreInfo"}
27
29
  <div>
28
- <button class="more-info-button" onclick={onClickFunction}>
30
+ <button class="more-info-button" type="button" onclick={onClickFunction}>
29
31
  <Icon kind="info" />
30
32
  </button>
31
33
  </div>
34
+
32
35
  {:else if buttonType === "start"}
33
36
  <a
34
- href={"#"}
35
- role="button"
36
- draggable="false"
37
- class="govuk-button govuk-button--start"
38
- data-module="govuk-button"
39
- onclick={onClickFunction}
37
+ href="#"
38
+ role="button"
39
+ draggable="false"
40
+ class="govuk-button govuk-button--start"
41
+ data-module="govuk-button"
42
+ onclick={onClickFunction}
40
43
  >
41
44
  {textContent}
42
45
  <svg
43
- class="govuk-button__start-icon"
44
- xmlns="http://www.w3.org/2000/svg"
45
- width="17.5"
46
- height="19"
47
- viewBox="0 0 33 40"
48
- aria-hidden="true"
49
- focusable="false"
46
+ class="govuk-button__start-icon"
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ width="17.5"
49
+ height="19"
50
+ viewBox="0 0 33 40"
51
+ aria-hidden="true"
52
+ focusable="false"
50
53
  >
51
54
  <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
52
55
  </svg>
53
56
  </a>
57
+
54
58
  {:else if buttonType === "disabled"}
55
59
  <button
56
- type="submit"
57
- disabled
58
- aria-disabled="true"
59
- class="govuk-button"
60
- data-module="govuk-button"
61
- onclick={onClickFunction}
60
+ type="submit"
61
+ disabled
62
+ aria-disabled="true"
63
+ class="govuk-button"
64
+ data-module="govuk-button"
65
+ onclick={onClickFunction}
62
66
  >
63
67
  {textContent}
64
68
  </button>
69
+
65
70
  {:else if buttonType === "table header"}
66
- <button type="button" class="text-header" onclick={onClickFunction}>
67
- {textContent}
68
- <svg
69
- width="22"
70
- height="22"
71
- focusable="false"
72
- aria-hidden="true"
73
- role="img"
74
- viewBox="0 0 22 22"
75
- fill="currentColor"
76
- xmlns="http://www.w3.org/2000/svg"
77
- >
78
- <path
79
- class="top-triangle"
80
- d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
81
- fill="currentColor"
82
- ></path>
83
- <path
84
- class="bottom-triangle"
85
- d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
86
- fill="currentColor"
87
- ></path>
88
- </svg>
71
+ <button
72
+ type="button"
73
+ class="text-header"
74
+ onclick={onClickFunction}
75
+ disabled={disabled}
76
+ >
77
+ <span>{textContent}</span>
78
+
79
+ <span class="sort-icon" aria-hidden="true">
80
+ {#if direction === "ascending"}
81
+ <svg
82
+ class="sort-arrow-single"
83
+ width="1em"
84
+ height="1em"
85
+ focusable="false"
86
+ role="img"
87
+ viewBox="0 0 22 22"
88
+ fill="currentColor"
89
+ xmlns="http://www.w3.org/2000/svg"
90
+ >
91
+ <path
92
+ d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
93
+ fill="currentColor"
94
+ ></path>
95
+ </svg>
96
+ {:else if direction === "descending"}
97
+ <svg
98
+ class="sort-arrow-single"
99
+ width="1em"
100
+ height="1em"
101
+ focusable="false"
102
+ role="img"
103
+ viewBox="0 0 22 22"
104
+ fill="currentColor"
105
+ xmlns="http://www.w3.org/2000/svg"
106
+ >
107
+ <path
108
+ d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
109
+ fill="currentColor"
110
+ ></path>
111
+ </svg>
112
+ {:else}
113
+ <svg
114
+ class="sort-arrow-both"
115
+ width="1em"
116
+ height="1em"
117
+ focusable="false"
118
+ role="img"
119
+ viewBox="0 0 22 22"
120
+ fill="currentColor"
121
+ xmlns="http://www.w3.org/2000/svg"
122
+ >
123
+ <path
124
+ d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
125
+ fill="currentColor"
126
+ ></path>
127
+ <path
128
+ d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
129
+ fill="currentColor"
130
+ ></path>
131
+ </svg>
132
+ {/if}
133
+ </span>
89
134
  </button>
135
+
90
136
  {:else}
91
137
  <button
92
- type="submit"
93
- class={buttonClass}
94
- data-module="govuk-button"
95
- onclick={onClickFunction}
138
+ type="submit"
139
+ class={buttonClass}
140
+ data-module="govuk-button"
141
+ onclick={onClickFunction}
96
142
  >
97
143
  {textContent}
98
144
  </button>
99
145
  {/if}
146
+
100
147
  {:else}
101
148
  <div class="p-4">
102
149
  {#if buttonType === "start"}
103
150
  <a
104
- href={"#"}
105
- role="button"
106
- draggable="false"
107
- class="govuk-button govuk-button--start"
108
- data-module="govuk-button"
109
- onclick={onClickFunction}
151
+ href="#"
152
+ role="button"
153
+ draggable="false"
154
+ class="govuk-button govuk-button--start"
155
+ data-module="govuk-button"
156
+ onclick={onClickFunction}
110
157
  >
111
158
  {textContent}
112
159
  <svg
113
- class="govuk-button__start-icon"
114
- xmlns="http://www.w3.org/2000/svg"
115
- width="17.5"
116
- height="19"
117
- viewBox="0 0 33 40"
118
- aria-hidden="true"
119
- focusable="false"
160
+ class="govuk-button__start-icon"
161
+ xmlns="http://www.w3.org/2000/svg"
162
+ width="17.5"
163
+ height="19"
164
+ viewBox="0 0 33 40"
165
+ aria-hidden="true"
166
+ focusable="false"
120
167
  >
121
168
  <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
122
169
  </svg>
123
170
  </a>
171
+
124
172
  {:else if buttonType === "disabled"}
125
173
  <button
126
- type="submit"
127
- disabled
128
- aria-disabled="true"
129
- class="govuk-button"
130
- data-module="govuk-button"
131
- onclick={onClickFunction}
174
+ type="submit"
175
+ disabled
176
+ aria-disabled="true"
177
+ class="govuk-button"
178
+ data-module="govuk-button"
179
+ onclick={onClickFunction}
132
180
  >
133
181
  {textContent}
134
182
  </button>
183
+
135
184
  {:else if buttonType === "table header"}
136
- <button type="button" class="text-header" onclick={onClickFunction}>
137
- {textContent}
138
- <svg
139
- width="22"
140
- height="22"
141
- focusable="false"
142
- aria-hidden="true"
143
- role="img"
144
- viewBox="0 0 22 22"
145
- fill="none"
146
- xmlns="http://www.w3.org/2000/svg"
147
- >
148
- <path
149
- class="top-triangle"
150
- d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
151
- fill="currentColor"
152
- ></path>
153
- <path
154
- class="bottom-triangle"
155
- d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
156
- fill="currentColor"
157
- ></path>
158
- </svg>
185
+ <button
186
+ type="button"
187
+ class="text-header"
188
+ onclick={onClickFunction}
189
+ disabled={disabled}
190
+ >
191
+ <span>{textContent}</span>
192
+
193
+ <span class="sort-icon" aria-hidden="true">
194
+ {#if direction === "ascending"}
195
+ <svg
196
+ class="sort-arrow-single"
197
+ width="1em"
198
+ height="1em"
199
+ focusable="false"
200
+ role="img"
201
+ viewBox="0 0 22 22"
202
+ fill="currentColor"
203
+ xmlns="http://www.w3.org/2000/svg"
204
+ >
205
+ <path
206
+ d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
207
+ fill="currentColor"
208
+ ></path>
209
+ </svg>
210
+ {:else if direction === "descending"}
211
+ <svg
212
+ class="sort-arrow-single"
213
+ width="1em"
214
+ height="1em"
215
+ focusable="false"
216
+ role="img"
217
+ viewBox="0 0 22 22"
218
+ fill="currentColor"
219
+ xmlns="http://www.w3.org/2000/svg"
220
+ >
221
+ <path
222
+ d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
223
+ fill="currentColor"
224
+ ></path>
225
+ </svg>
226
+ {:else}
227
+ <svg
228
+ class="sort-arrow-both"
229
+ width="1em"
230
+ height="1em"
231
+ focusable="false"
232
+ role="img"
233
+ viewBox="0 0 22 22"
234
+ fill="currentColor"
235
+ xmlns="http://www.w3.org/2000/svg"
236
+ >
237
+ <path
238
+ d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z"
239
+ fill="currentColor"
240
+ ></path>
241
+ <path
242
+ d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z"
243
+ fill="currentColor"
244
+ ></path>
245
+ </svg>
246
+ {/if}
247
+ </span>
159
248
  </button>
249
+
250
+ {:else if buttonType === "moreInfo"}
251
+ <button class="more-info-button" type="button" onclick={onClickFunction}>
252
+ <Icon kind="info" />
253
+ </button>
254
+
160
255
  {:else}
161
256
  <button
162
- type="button"
163
- class={buttonClass}
164
- data-module="govuk-button"
165
- onclick={onClickFunction}
257
+ type="button"
258
+ class={buttonClass}
259
+ data-module="govuk-button"
260
+ onclick={onClickFunction}
166
261
  >
167
262
  {textContent}
168
263
  </button>
@@ -176,6 +271,26 @@
176
271
  color: #005ea5;
177
272
  }
178
273
 
274
+ .sort-icon {
275
+ display: inline-flex;
276
+ align-items: center;
277
+ justify-content: center;
278
+ }
279
+
280
+ .sort-icon svg {
281
+ display: block;
282
+ }
283
+
284
+ .sort-arrow-single {
285
+ width: 1em;
286
+ height: 1em;
287
+ }
288
+
289
+ .sort-arrow-both {
290
+ width: 1em;
291
+ height: 1em;
292
+ }
293
+
179
294
  .more-info-button {
180
295
  display: flex;
181
296
  flex: none;
@@ -185,11 +300,11 @@
185
300
  cursor: pointer;
186
301
  }
187
302
 
188
- /* For the scale on hover of group parent */
189
303
  :hover .more-info-button {
190
304
  transform: scale(1.25);
191
305
  }
306
+
192
307
  .more-info-button.custom-ring {
193
308
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 1);
194
309
  }
195
- </style>
310
+ </style>
@@ -9,6 +9,8 @@ declare const Button: import("svelte").Component<{
9
9
  componentNameProp?: any;
10
10
  onClickFunction?: any;
11
11
  noPadding?: boolean;
12
+ direction?: any;
13
+ disabled?: boolean;
12
14
  }, {}, "">;
13
15
  type $$ComponentProps = {
14
16
  textContent?: any;
@@ -16,4 +18,6 @@ type $$ComponentProps = {
16
18
  componentNameProp?: any;
17
19
  onClickFunction?: any;
18
20
  noPadding?: boolean;
21
+ direction?: any;
22
+ disabled?: boolean;
19
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communitiesuk/svelte-component-library",
3
- "version": "0.1.19-beta.33",
3
+ "version": "0.1.19-beta.34",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/communitiesuk/mhclg_svelte_component_library.git"