@developer-overheid-nl/don-register-components 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -43
- package/LICENSE.md +291 -291
- package/README.md +20 -20
- package/package.json +3 -3
- package/src/alert/Alert.tsx +19 -19
- package/src/alert/styles.module.css +3 -3
- package/src/alignBox/AlignBox.tsx +114 -111
- package/src/alignBox/styles.module.css +110 -106
- package/src/amountLabel/AmountLabel.tsx +16 -0
- package/src/amountLabel/styles.module.css +11 -0
- package/src/analytics/PiwikPro.tsx +94 -72
- package/src/analytics/index.ts +1 -1
- package/src/article/Article.tsx +10 -10
- package/src/badgeList/BadgeList.tsx +34 -0
- package/src/badgeList/styles.module.css +37 -0
- package/src/block/Block.tsx +35 -35
- package/src/block/styles.module.css +29 -29
- package/src/button/Button.tsx +3 -3
- package/src/cardAsLink/CardAsLink.tsx +21 -21
- package/src/cardAsLink/styles.module.css +23 -23
- package/src/cardsList/CardsList.tsx +54 -54
- package/src/cardsList/styles.module.css +19 -19
- package/src/client.ts +7 -2
- package/src/container/Container.tsx +10 -10
- package/src/container/styles.module.css +28 -28
- package/src/copyButton/CopyButton.tsx +39 -39
- package/src/copyButton/styles.module.css +13 -13
- package/src/dataBadgeLink/DataBadgeLink.tsx +53 -50
- package/src/dataBadgeLink/styles.module.css +89 -43
- package/src/dataSummary/DataSummary.tsx +22 -22
- package/src/dataSummary/DataSummaryItem.tsx +21 -21
- package/src/dataSummary/styles.module.css +75 -70
- package/src/facetFilters/FacetFilters.tsx +367 -0
- package/src/facetFilters/styles.module.css +3 -0
- package/src/fieldset/FieldSet.tsx +3 -3
- package/src/filters/Filters.tsx +116 -115
- package/src/filters/styles.module.css +26 -26
- package/src/footer/Footer.tsx +76 -76
- package/src/footer/styles.module.css +103 -103
- package/src/formFieldCheckboxGroup/FormFieldCheckboxGroup.tsx +16 -0
- package/src/formFieldCheckboxGroup/styles.module.css +5 -0
- package/src/formFieldCheckboxOption/FormFieldCheckboxOption.tsx +26 -0
- package/src/formFieldCheckboxOption/styles.module.css +22 -0
- package/src/formFieldLabel/FormFieldLabel.tsx +23 -23
- package/src/formFieldRadioGroup/FormFieldRadioGroup.tsx +13 -0
- package/src/formFieldRadioGroup/styles.module.css +5 -0
- package/src/formFieldRadioOption/FormFieldRadioOption.tsx +75 -0
- package/src/formFieldRadioOption/styles.module.css +49 -0
- package/src/formFieldSwitch/FormFieldSwitch.tsx +79 -0
- package/src/formFieldSwitch/styles.module.css +32 -0
- package/src/formFieldTextInput/FormFieldTextInput.tsx +3 -3
- package/src/header/Header.tsx +137 -137
- package/src/header/styles.module.css +71 -71
- package/src/heading/Heading.tsx +15 -10
- package/src/headingGroup/HeadingGroup.tsx +48 -48
- package/src/headingGroup/styles.module.css +3 -3
- package/src/i18n.ts +25 -25
- package/src/iconBadge/IconBadge.tsx +35 -32
- package/src/iconBadge/getAppearance.ts +53 -47
- package/src/iconBadge/styles.module.css +19 -19
- package/src/iconsSprite/Icon.tsx +17 -17
- package/src/iconsSprite/IconsSprite.tsx +5 -5
- package/src/index.ts +107 -80
- package/src/infoButton/InfoButton.tsx +45 -0
- package/src/infoButton/styles.module.css +43 -0
- package/src/link/Link.tsx +10 -10
- package/src/markdown/Markdown.tsx +42 -42
- package/src/overlay/Overlay.tsx +18 -0
- package/src/overlay/styles.module.css +15 -0
- package/src/pagination/Pagination.tsx +267 -144
- package/src/pagination/styles.module.css +28 -13
- package/src/paragraph/Paragraph.tsx +24 -10
- package/src/paragraph/styles.module.css +6 -0
- package/src/pillBadge/PillBadge.examples.tsx +107 -107
- package/src/pillBadge/PillBadge.tsx +51 -51
- package/src/pillBadge/styles.module.css +214 -194
- package/src/readOnlyTextInput/ReadOnlyTextInput.tsx +24 -24
- package/src/readOnlyTextInput/styles.module.css +19 -19
- package/src/scoreBadge/ScoreBadge.tsx +133 -132
- package/src/search/Search.tsx +82 -66
- package/src/search/styles.module.css +39 -39
- package/src/siteLogo/SiteLogo.tsx +24 -24
- package/src/switch/Switch.tsx +86 -0
- package/src/switch/styles.module.css +7 -0
- package/src/toolTip/ToolTip.tsx +55 -0
- package/src/toolTip/styles.module.css +49 -0
- package/src/topNavigation/TopNavigation.tsx +67 -67
- package/src/topNavigation/styles.module.css +54 -54
- package/tsconfig.json +121 -121
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { ChangeEvent } from "react";
|
|
5
|
+
import { type HTMLProps, type PropsWithChildren, useRef } from "react";
|
|
6
|
+
import { I18nextProvider, useTranslation } from "react-i18next";
|
|
7
|
+
import FormFieldCheckboxGroup from "../formFieldCheckboxGroup/FormFieldCheckboxGroup";
|
|
8
|
+
import FormFieldCheckboxOption from "../formFieldCheckboxOption/FormFieldCheckboxOption";
|
|
9
|
+
import FormFieldRadioGroup from "../formFieldRadioGroup/FormFieldRadioGroup";
|
|
10
|
+
import FormFieldRadioOption from "../formFieldRadioOption/FormFieldRadioOption";
|
|
11
|
+
import FormFieldSwitch from "../formFieldSwitch/FormFieldSwitch";
|
|
12
|
+
import Heading, {
|
|
13
|
+
clampHeadingLevel,
|
|
14
|
+
type HeadingProps,
|
|
15
|
+
} from "../heading/Heading";
|
|
16
|
+
import i18n from "../i18n";
|
|
17
|
+
import Paragraph from "../paragraph/Paragraph";
|
|
18
|
+
import ToolTip from "../toolTip/ToolTip";
|
|
19
|
+
import styles from "./styles.module.css";
|
|
20
|
+
|
|
21
|
+
enum FilterType {
|
|
22
|
+
Toggle = "toggle",
|
|
23
|
+
Date = "date",
|
|
24
|
+
Multi = "multi-select",
|
|
25
|
+
Single = "single-select",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SelectOption {
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
description?: string | null;
|
|
32
|
+
count: number;
|
|
33
|
+
selected: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface BaseFilterItems {
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
description: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ToggleFilterData extends BaseFilterItems {
|
|
43
|
+
type: FilterType.Toggle;
|
|
44
|
+
value?: boolean;
|
|
45
|
+
count?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface DateFilterData extends BaseFilterItems {
|
|
49
|
+
type: FilterType.Date;
|
|
50
|
+
value?: string;
|
|
51
|
+
count?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface MultiSelectFilterData extends BaseFilterItems {
|
|
55
|
+
type: FilterType.Multi;
|
|
56
|
+
options: SelectOption[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SingleSelectFilterData extends BaseFilterItems {
|
|
60
|
+
type: FilterType.Single;
|
|
61
|
+
options: SelectOption[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type FilterData =
|
|
65
|
+
| ToggleFilterData
|
|
66
|
+
| DateFilterData
|
|
67
|
+
| MultiSelectFilterData
|
|
68
|
+
| SingleSelectFilterData;
|
|
69
|
+
|
|
70
|
+
type SelectedFilters = Record<string, Array<string | [string, string]>>;
|
|
71
|
+
|
|
72
|
+
type SelectedFiltersMap = Map<
|
|
73
|
+
string | [string, string],
|
|
74
|
+
Array<string | [string, string]>
|
|
75
|
+
>;
|
|
76
|
+
|
|
77
|
+
export interface FacetFiltersProps extends HTMLProps<HTMLDivElement> {
|
|
78
|
+
title?: string;
|
|
79
|
+
startHeadingLevel?: HeadingProps["level"];
|
|
80
|
+
filters: FilterData[] | null | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Callback function called when filter option has changed.
|
|
83
|
+
* @param selectedFilters current selected filters at the moment of change
|
|
84
|
+
* @returns
|
|
85
|
+
*/
|
|
86
|
+
onFilterChange?: (selectedFilters: SelectedFilters) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Types of filters for which the onFilterChange callback should be debounced. Default: ["date", "single-select"]
|
|
89
|
+
*/
|
|
90
|
+
debounceTypes?: Array<FilterType>;
|
|
91
|
+
/**
|
|
92
|
+
* Delay for debouncing the onFilterChange callback, in milliseconds. Default: 600
|
|
93
|
+
*/
|
|
94
|
+
debounceDelay?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @deprecated Use getSelectedFiltersMap, that will return a map with keys as a tuple with key and label
|
|
99
|
+
* @param filters
|
|
100
|
+
* @param withLabels Note: keys have no labels, use the map version
|
|
101
|
+
* @returns object
|
|
102
|
+
*/
|
|
103
|
+
export const getSelectedFilters = (
|
|
104
|
+
filters: FilterData[] | null | undefined,
|
|
105
|
+
withLabels = false,
|
|
106
|
+
): SelectedFilters => {
|
|
107
|
+
return (
|
|
108
|
+
filters?.reduce((acc, filter) => {
|
|
109
|
+
if (filter.type === FilterType.Multi) {
|
|
110
|
+
const keys: (string | [string, string])[] = filter.options
|
|
111
|
+
?.filter((option) => option.selected)
|
|
112
|
+
.map((option) =>
|
|
113
|
+
withLabels ? [option.value, option.label] : option.value,
|
|
114
|
+
);
|
|
115
|
+
if (keys?.length > 0) {
|
|
116
|
+
acc[filter.key] = keys;
|
|
117
|
+
}
|
|
118
|
+
} else if (filter.type === FilterType.Toggle && filter.value) {
|
|
119
|
+
acc[filter.key] = withLabels ? [["true", filter.label]] : ["true"];
|
|
120
|
+
} else if (filter.type === FilterType.Date && filter.value) {
|
|
121
|
+
acc[filter.key] = withLabels
|
|
122
|
+
? [[filter.value, filter.label]]
|
|
123
|
+
: [filter.value];
|
|
124
|
+
} else if (filter.type === FilterType.Single) {
|
|
125
|
+
const selectedOption = filter.options?.find(
|
|
126
|
+
(option) => option.selected,
|
|
127
|
+
);
|
|
128
|
+
if (selectedOption) {
|
|
129
|
+
acc[filter.key] = withLabels
|
|
130
|
+
? [[selectedOption.value, selectedOption.label]]
|
|
131
|
+
: [selectedOption.value];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return acc;
|
|
135
|
+
}, {} as SelectedFilters) ?? {}
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const getSelectedFiltersMap = (
|
|
140
|
+
filters: FilterData[] | null | undefined,
|
|
141
|
+
withLabels = false,
|
|
142
|
+
): SelectedFiltersMap => {
|
|
143
|
+
return (
|
|
144
|
+
filters?.reduce((acc, filter) => {
|
|
145
|
+
if (filter.type === FilterType.Multi) {
|
|
146
|
+
const keys: (string | [string, string])[] = filter.options
|
|
147
|
+
?.filter((option) => option.selected)
|
|
148
|
+
.map((option) =>
|
|
149
|
+
withLabels ? [option.value, option.label] : option.value,
|
|
150
|
+
);
|
|
151
|
+
if (keys?.length > 0) {
|
|
152
|
+
withLabels
|
|
153
|
+
? acc.set([filter.key, filter.label], keys)
|
|
154
|
+
: acc.set(filter.key, keys);
|
|
155
|
+
}
|
|
156
|
+
} else if (filter.type === FilterType.Toggle && filter.value) {
|
|
157
|
+
withLabels
|
|
158
|
+
? acc.set([filter.key, filter.label], [["true", filter.label]])
|
|
159
|
+
: acc.set(filter.key, ["true"]);
|
|
160
|
+
} else if (filter.type === FilterType.Date && filter.value) {
|
|
161
|
+
withLabels
|
|
162
|
+
? acc.set([filter.key, filter.label], [[filter.value, filter.label]])
|
|
163
|
+
: acc.set(filter.key, [filter.value]);
|
|
164
|
+
} else if (filter.type === FilterType.Single) {
|
|
165
|
+
const selectedOption = filter.options?.find(
|
|
166
|
+
(option) => option.selected,
|
|
167
|
+
);
|
|
168
|
+
if (selectedOption) {
|
|
169
|
+
withLabels
|
|
170
|
+
? acc.set(
|
|
171
|
+
[filter.key, filter.label],
|
|
172
|
+
[[selectedOption.value, selectedOption.label]],
|
|
173
|
+
)
|
|
174
|
+
: acc.set(filter.key, [selectedOption.value]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return acc;
|
|
178
|
+
}, new Map() as SelectedFiltersMap) ?? new Map()
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const updateFilters = (
|
|
183
|
+
selectedFilters: SelectedFilters,
|
|
184
|
+
filterKey: string,
|
|
185
|
+
filterValue: string,
|
|
186
|
+
checked: boolean,
|
|
187
|
+
) => {
|
|
188
|
+
const newSelectedFilters = {
|
|
189
|
+
...selectedFilters,
|
|
190
|
+
[filterKey]: checked
|
|
191
|
+
? [...(selectedFilters[filterKey] || []), filterValue]
|
|
192
|
+
: (selectedFilters[filterKey] || []).filter(
|
|
193
|
+
(value) => value !== filterValue,
|
|
194
|
+
),
|
|
195
|
+
};
|
|
196
|
+
return newSelectedFilters;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const debounce = <T extends unknown[]>(
|
|
200
|
+
fn: (...args: T) => void,
|
|
201
|
+
delay: number,
|
|
202
|
+
) => {
|
|
203
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
204
|
+
return (...args: T) => {
|
|
205
|
+
if (timeoutId) {
|
|
206
|
+
clearTimeout(timeoutId);
|
|
207
|
+
}
|
|
208
|
+
timeoutId = setTimeout(() => {
|
|
209
|
+
fn(...args);
|
|
210
|
+
}, delay);
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const FacetFilters = (props: PropsWithChildren<FacetFiltersProps>) => {
|
|
215
|
+
const { t } = useTranslation();
|
|
216
|
+
const thisRef = useRef<HTMLDivElement>(null);
|
|
217
|
+
const {
|
|
218
|
+
title,
|
|
219
|
+
startHeadingLevel = 2,
|
|
220
|
+
filters,
|
|
221
|
+
onFilterChange = () => {},
|
|
222
|
+
debounceTypes = [FilterType.Date, FilterType.Single],
|
|
223
|
+
debounceDelay = 600,
|
|
224
|
+
...restProps
|
|
225
|
+
} = props;
|
|
226
|
+
|
|
227
|
+
const selectedFilters = getSelectedFilters(filters);
|
|
228
|
+
|
|
229
|
+
// Callback function (for React) and CustomEvent (for non-React) to notify about filter changes
|
|
230
|
+
const emitChange = (newSelectedFilters: SelectedFilters) => {
|
|
231
|
+
onFilterChange(newSelectedFilters);
|
|
232
|
+
const customEvent = new CustomEvent("onFilterChange", {
|
|
233
|
+
detail: newSelectedFilters,
|
|
234
|
+
});
|
|
235
|
+
if (thisRef.current) thisRef.current.dispatchEvent(customEvent);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const debouncedEmitChange = useRef(
|
|
239
|
+
debounce(emitChange, debounceDelay),
|
|
240
|
+
).current;
|
|
241
|
+
|
|
242
|
+
const handleChange = (
|
|
243
|
+
event: ChangeEvent<HTMLInputElement>,
|
|
244
|
+
type: FilterType,
|
|
245
|
+
) => {
|
|
246
|
+
const newSelectedFilters = updateFilters(
|
|
247
|
+
selectedFilters,
|
|
248
|
+
event.target.name,
|
|
249
|
+
event.target.value,
|
|
250
|
+
event.target.checked,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (debounceTypes.includes(type)) {
|
|
254
|
+
debouncedEmitChange(newSelectedFilters);
|
|
255
|
+
} else {
|
|
256
|
+
emitChange(newSelectedFilters);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
ref={thisRef}
|
|
263
|
+
className={clsx([styles.facetFilters, props.className])}
|
|
264
|
+
{...restProps}
|
|
265
|
+
>
|
|
266
|
+
{title ? (
|
|
267
|
+
<Heading level={startHeadingLevel} appearanceLevel={3}>
|
|
268
|
+
{title}
|
|
269
|
+
</Heading>
|
|
270
|
+
) : null}
|
|
271
|
+
{/* Temporary filter on `date` types */}
|
|
272
|
+
{filters
|
|
273
|
+
?.filter(({ type }) => type !== "date")
|
|
274
|
+
.map((facet) => (
|
|
275
|
+
<div className={styles.facet} key={facet.key}>
|
|
276
|
+
<Heading
|
|
277
|
+
level={
|
|
278
|
+
title
|
|
279
|
+
? clampHeadingLevel(startHeadingLevel + 1)
|
|
280
|
+
: startHeadingLevel
|
|
281
|
+
}
|
|
282
|
+
appearanceLevel={5}
|
|
283
|
+
>
|
|
284
|
+
{facet.label}
|
|
285
|
+
</Heading>
|
|
286
|
+
<Paragraph id={`${facet.key}-label`} purpose="short">
|
|
287
|
+
{facet.description}
|
|
288
|
+
</Paragraph>
|
|
289
|
+
{facet.type === "toggle" ? (
|
|
290
|
+
<FormFieldSwitch
|
|
291
|
+
label=""
|
|
292
|
+
labelledById={`${facet.key}-label`}
|
|
293
|
+
name={facet.key}
|
|
294
|
+
defaultChecked={facet.value}
|
|
295
|
+
value="true"
|
|
296
|
+
amount={facet.count || 0}
|
|
297
|
+
labels={{
|
|
298
|
+
on: t("components.filter-on"),
|
|
299
|
+
off: t("components.filter-off"),
|
|
300
|
+
}}
|
|
301
|
+
onChange={(event) => handleChange(event, facet.type)}
|
|
302
|
+
/>
|
|
303
|
+
) : null}
|
|
304
|
+
{/* TODO: Date selector
|
|
305
|
+
{facet.type === "date" ? <div>TODO</div> : null}
|
|
306
|
+
*/}
|
|
307
|
+
{facet.type === "multi-select" ? (
|
|
308
|
+
<FormFieldCheckboxGroup>
|
|
309
|
+
{facet.options?.map((option) => (
|
|
310
|
+
<FormFieldCheckboxOption
|
|
311
|
+
key={option.value}
|
|
312
|
+
label={option.label}
|
|
313
|
+
name={facet.key}
|
|
314
|
+
value={option.value}
|
|
315
|
+
defaultChecked={option.selected}
|
|
316
|
+
amount={option.count}
|
|
317
|
+
onChange={(event) => handleChange(event, facet.type)}
|
|
318
|
+
>
|
|
319
|
+
{option.description ? (
|
|
320
|
+
<ToolTip
|
|
321
|
+
text={option.description}
|
|
322
|
+
aria-label={t("components.info-about", {
|
|
323
|
+
subject: option.label,
|
|
324
|
+
})}
|
|
325
|
+
/>
|
|
326
|
+
) : null}
|
|
327
|
+
</FormFieldCheckboxOption>
|
|
328
|
+
))}
|
|
329
|
+
</FormFieldCheckboxGroup>
|
|
330
|
+
) : null}
|
|
331
|
+
{facet.type === "single-select" ? (
|
|
332
|
+
<FormFieldRadioGroup>
|
|
333
|
+
<FormFieldRadioOption
|
|
334
|
+
key="none"
|
|
335
|
+
label={t("components.filter-all")}
|
|
336
|
+
name={facet.key}
|
|
337
|
+
value=""
|
|
338
|
+
onChange={(event) => handleChange(event, facet.type)}
|
|
339
|
+
/>
|
|
340
|
+
{facet.options?.map((option) => (
|
|
341
|
+
<FormFieldRadioOption
|
|
342
|
+
key={option.value}
|
|
343
|
+
label={option.label}
|
|
344
|
+
name={facet.key}
|
|
345
|
+
value={option.value}
|
|
346
|
+
defaultChecked={option.selected}
|
|
347
|
+
amount={option.count}
|
|
348
|
+
onChange={(event) => handleChange(event, facet.type)}
|
|
349
|
+
/>
|
|
350
|
+
))}
|
|
351
|
+
</FormFieldRadioGroup>
|
|
352
|
+
) : null}
|
|
353
|
+
</div>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const TranslatedFacetFilters = (props: FacetFiltersProps) => {
|
|
360
|
+
return (
|
|
361
|
+
<I18nextProvider i18n={i18n}>
|
|
362
|
+
<FacetFilters {...props} />
|
|
363
|
+
</I18nextProvider>
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export default TranslatedFacetFilters;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Fieldset } from "@rijkshuisstijl-community/components-react";
|
|
2
|
-
|
|
3
|
-
export default Fieldset;
|
|
1
|
+
import { Fieldset } from "@rijkshuisstijl-community/components-react";
|
|
2
|
+
|
|
3
|
+
export default Fieldset;
|
package/src/filters/Filters.tsx
CHANGED
|
@@ -1,115 +1,116 @@
|
|
|
1
|
-
// 'use client';
|
|
2
|
-
/* biome-ignore-all lint: TODO: filters needs a refactor anyway */
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Heading,
|
|
6
|
-
LinkList,
|
|
7
|
-
LinkListLink,
|
|
8
|
-
} from "@rijkshuisstijl-community/components-react";
|
|
9
|
-
import clsx from "clsx";
|
|
10
|
-
import type { HTMLProps, PropsWithChildren } from "react";
|
|
11
|
-
import { I18nextProvider, useTranslation } from "react-i18next";
|
|
12
|
-
import Alert from "../alert/Alert";
|
|
13
|
-
import DataBadgeLink from "../dataBadgeLink/DataBadgeLink";
|
|
14
|
-
import i18n from "../i18n";
|
|
15
|
-
import Icon from "../iconsSprite/Icon";
|
|
16
|
-
import styles from "./styles.module.css";
|
|
17
|
-
|
|
18
|
-
export interface FiltersProps extends HTMLProps<HTMLDivElement> {
|
|
19
|
-
routing?: Record<string, any>;
|
|
20
|
-
data: any;
|
|
21
|
-
headers: any;
|
|
22
|
-
error: any;
|
|
23
|
-
status?: number;
|
|
24
|
-
statusText?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const ListLinkIcon = ({ isActive }: { isActive: boolean }) => {
|
|
28
|
-
return (
|
|
29
|
-
<Icon
|
|
30
|
-
className={clsx([styles.icon, isActive && styles.active])}
|
|
31
|
-
name={isActive ? "active" : "delta-naar-rechts-inline"}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
organisations
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
1
|
+
// 'use client';
|
|
2
|
+
/* biome-ignore-all lint: TODO: filters needs a refactor anyway */
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Heading,
|
|
6
|
+
LinkList,
|
|
7
|
+
LinkListLink,
|
|
8
|
+
} from "@rijkshuisstijl-community/components-react";
|
|
9
|
+
import clsx from "clsx";
|
|
10
|
+
import type { HTMLProps, PropsWithChildren } from "react";
|
|
11
|
+
import { I18nextProvider, useTranslation } from "react-i18next";
|
|
12
|
+
import Alert from "../alert/Alert";
|
|
13
|
+
import DataBadgeLink from "../dataBadgeLink/DataBadgeLink";
|
|
14
|
+
import i18n from "../i18n";
|
|
15
|
+
import Icon from "../iconsSprite/Icon";
|
|
16
|
+
import styles from "./styles.module.css";
|
|
17
|
+
|
|
18
|
+
export interface FiltersProps extends HTMLProps<HTMLDivElement> {
|
|
19
|
+
routing?: Record<string, any>;
|
|
20
|
+
data: any;
|
|
21
|
+
headers: any;
|
|
22
|
+
error: any;
|
|
23
|
+
status?: number;
|
|
24
|
+
statusText?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ListLinkIcon = ({ isActive }: { isActive: boolean }) => {
|
|
28
|
+
return (
|
|
29
|
+
<Icon
|
|
30
|
+
className={clsx([styles.icon, isActive && styles.active])}
|
|
31
|
+
name={isActive ? "active" : "delta-naar-rechts-inline"}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Use FacetFilters component. This component will be removed in a later version.
|
|
38
|
+
*/
|
|
39
|
+
const Filters = (props: PropsWithChildren<FiltersProps>) => {
|
|
40
|
+
const { t } = useTranslation();
|
|
41
|
+
const { data, error, status, statusText, className, routing } = props;
|
|
42
|
+
const organisations = data && !data.message ? data : [];
|
|
43
|
+
const currentOrganisation =
|
|
44
|
+
organisations &&
|
|
45
|
+
organisations.find((org: any) => org?.uri === routing?.query?.organisation);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={clsx([styles.filters, className])}>
|
|
49
|
+
{currentOrganisation && (
|
|
50
|
+
<div
|
|
51
|
+
className={clsx("utrecht-badge-list", styles.currentFilter)}
|
|
52
|
+
role="list"
|
|
53
|
+
>
|
|
54
|
+
<Heading level={2} appearanceLevel={3}>
|
|
55
|
+
{t("components.current-filters")}
|
|
56
|
+
</Heading>
|
|
57
|
+
<DataBadgeLink
|
|
58
|
+
role="listitem"
|
|
59
|
+
aria-pressed={null}
|
|
60
|
+
helperText={t("components.remove-filter")}
|
|
61
|
+
icon={<Icon name="kruis-inline" />}
|
|
62
|
+
href={new URL(`../`, routing?.url).toString()}
|
|
63
|
+
>
|
|
64
|
+
{styles.badgeLink}
|
|
65
|
+
{currentOrganisation?.label}
|
|
66
|
+
</DataBadgeLink>
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
<Heading level={2} appearanceLevel={3}>
|
|
70
|
+
{t("components.filter-by", { type: "organisatie" })}
|
|
71
|
+
</Heading>
|
|
72
|
+
<LinkList>
|
|
73
|
+
{organisations &&
|
|
74
|
+
organisations.map((organisation: any) => (
|
|
75
|
+
<LinkListLink
|
|
76
|
+
className={clsx([
|
|
77
|
+
organisation.uri === routing?.query?.organisation &&
|
|
78
|
+
styles.active,
|
|
79
|
+
])}
|
|
80
|
+
key={organisation.uri}
|
|
81
|
+
href={new URL(
|
|
82
|
+
`../?organisation=${organisation.uri}`,
|
|
83
|
+
routing?.url,
|
|
84
|
+
).toString()}
|
|
85
|
+
icon={
|
|
86
|
+
<ListLinkIcon
|
|
87
|
+
isActive={organisation.uri === routing?.query?.organisation}
|
|
88
|
+
/>
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
{organisation.label}
|
|
92
|
+
</LinkListLink>
|
|
93
|
+
))}
|
|
94
|
+
</LinkList>
|
|
95
|
+
|
|
96
|
+
{error && (
|
|
97
|
+
<Alert type="error">
|
|
98
|
+
{(error as unknown as any).message ||
|
|
99
|
+
(error as unknown as any).error_msg ||
|
|
100
|
+
`${status}: ${statusText}` ||
|
|
101
|
+
t("components.fuzz-error")}
|
|
102
|
+
</Alert>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const TranslatedFilters = (props: PropsWithChildren<FiltersProps>) => {
|
|
109
|
+
return (
|
|
110
|
+
<I18nextProvider i18n={i18n}>
|
|
111
|
+
<Filters {...props} />
|
|
112
|
+
</I18nextProvider>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default TranslatedFilters;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
.filters {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
row-gap: var(--rhc-space-200);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.currentFilter {
|
|
8
|
-
flex-direction: column;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.active {
|
|
12
|
-
font-weight: bold;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.icon {
|
|
16
|
-
flex-shrink: 0;
|
|
17
|
-
transition: transform 0.2s ease-in-out;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
:global(.utrecht-link-list__link:hover) .icon:not(.active) {
|
|
21
|
-
transform: translateX(0.125rem);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.icon.active {
|
|
25
|
-
transform: scale(1.5);
|
|
26
|
-
}
|
|
1
|
+
.filters {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
row-gap: var(--rhc-space-200);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.currentFilter {
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.active {
|
|
12
|
+
font-weight: bold;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.icon {
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
transition: transform 0.2s ease-in-out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:global(.utrecht-link-list__link:hover) .icon:not(.active) {
|
|
21
|
+
transform: translateX(0.125rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.icon.active {
|
|
25
|
+
transform: scale(1.5);
|
|
26
|
+
}
|