@voila.dev/ui-filter 1.1.9

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/src/types.ts ADDED
@@ -0,0 +1,249 @@
1
+ // The vocabulary every filtered list speaks: a screen *declares* which fields
2
+ // are filterable (`FilterDefinition`), and the editor, the summary chips and the
3
+ // query layer all read the same declaration. Nothing here is domain-aware —
4
+ // labels and copy arrive as props so the package stays free of translations.
5
+
6
+ import type * as React from "react";
7
+
8
+ /** One choice of a `select` filter. */
9
+ export type FilterOption = {
10
+ readonly value: string;
11
+ readonly label: string;
12
+ /** Optional leading glyph (a status dot, a flag, an avatar). */
13
+ readonly icon?: React.ReactNode;
14
+ };
15
+
16
+ type BaseDefinition = {
17
+ /** Identifies the filter in a `FilterValues` record and in the query. */
18
+ readonly key: string;
19
+ readonly label: string;
20
+ /** One line under the label when the field needs explaining. */
21
+ readonly description?: string;
22
+ };
23
+
24
+ /**
25
+ * Fields that can be inverted ("is not …") carry `allowExclusion`. Ranges and
26
+ * booleans don't: "not between 10 and 20" reads worse than two range bounds,
27
+ * and a tri-state boolean already covers its own negation.
28
+ */
29
+ type ExcludableDefinition = BaseDefinition & {
30
+ readonly allowExclusion?: boolean;
31
+ };
32
+
33
+ export type TextFilterDefinition = ExcludableDefinition & {
34
+ readonly kind: "text";
35
+ readonly placeholder?: string;
36
+ };
37
+
38
+ export type NumberFilterDefinition = BaseDefinition & {
39
+ readonly kind: "number";
40
+ readonly min?: number;
41
+ readonly max?: number;
42
+ readonly step?: number;
43
+ /** Trailing unit shown inside the field (e.g. `km`, `%`). */
44
+ readonly unit?: string;
45
+ };
46
+
47
+ export type NumberRangeFilterDefinition = BaseDefinition & {
48
+ readonly kind: "numberRange";
49
+ readonly min?: number;
50
+ readonly max?: number;
51
+ readonly step?: number;
52
+ readonly unit?: string;
53
+ };
54
+
55
+ /** Amounts are integer minor units (cents), the platform's money representation. */
56
+ export type MoneyRangeFilterDefinition = BaseDefinition & {
57
+ readonly kind: "moneyRange";
58
+ readonly currency: string;
59
+ };
60
+
61
+ export type SelectFilterDefinition = ExcludableDefinition & {
62
+ readonly kind: "select";
63
+ readonly options: ReadonlyArray<FilterOption>;
64
+ /** Allow several options at once; the value stays a list either way. */
65
+ readonly multiple?: boolean;
66
+ };
67
+
68
+ /** Bounds are `YYYY-MM-DD`, the value a native date input round-trips. */
69
+ export type DateRangeFilterDefinition = BaseDefinition & {
70
+ readonly kind: "dateRange";
71
+ readonly min?: string;
72
+ readonly max?: string;
73
+ };
74
+
75
+ /** A geocoded place a `geoRadius` filter can centre on. */
76
+ export type PlaceSuggestion = {
77
+ readonly id: string;
78
+ readonly label: string;
79
+ readonly latitude: number;
80
+ readonly longitude: number;
81
+ };
82
+
83
+ /**
84
+ * "Around here, within N km". The package geocodes nothing itself — the screen
85
+ * supplies `searchPlaces`, so the same field works against any address
86
+ * provider.
87
+ */
88
+ export type GeoRadiusFilterDefinition = BaseDefinition & {
89
+ readonly kind: "geoRadius";
90
+ readonly searchPlaces: (
91
+ query: string,
92
+ ) => Promise<ReadonlyArray<PlaceSuggestion>>;
93
+ readonly minKm?: number;
94
+ readonly maxKm?: number;
95
+ readonly stepKm?: number;
96
+ /** Radius applied when a place is first picked. */
97
+ readonly defaultKm?: number;
98
+ };
99
+
100
+ export type BooleanFilterDefinition = BaseDefinition & {
101
+ readonly kind: "boolean";
102
+ /** Labels for the two set states; the third state is "any", i.e. unset. */
103
+ readonly trueLabel: string;
104
+ readonly falseLabel: string;
105
+ };
106
+
107
+ export type FilterDefinition =
108
+ | TextFilterDefinition
109
+ | NumberFilterDefinition
110
+ | NumberRangeFilterDefinition
111
+ | MoneyRangeFilterDefinition
112
+ | SelectFilterDefinition
113
+ | DateRangeFilterDefinition
114
+ | GeoRadiusFilterDefinition
115
+ | BooleanFilterDefinition;
116
+
117
+ export type FilterKind = FilterDefinition["kind"];
118
+
119
+ export type TextFilterValue = {
120
+ readonly kind: "text";
121
+ readonly text: string;
122
+ /** `true` renders and reads as "is not". */
123
+ readonly excluded?: boolean;
124
+ };
125
+
126
+ export type NumberFilterValue = {
127
+ readonly kind: "number";
128
+ readonly number: number;
129
+ };
130
+
131
+ export type NumberRangeFilterValue = {
132
+ readonly kind: "numberRange";
133
+ readonly min?: number;
134
+ readonly max?: number;
135
+ };
136
+
137
+ export type MoneyRangeFilterValue = {
138
+ readonly kind: "moneyRange";
139
+ /** Minor units (cents). */
140
+ readonly min?: number;
141
+ readonly max?: number;
142
+ };
143
+
144
+ export type SelectFilterValue = {
145
+ readonly kind: "select";
146
+ readonly values: ReadonlyArray<string>;
147
+ readonly excluded?: boolean;
148
+ };
149
+
150
+ export type DateRangeFilterValue = {
151
+ readonly kind: "dateRange";
152
+ /** `YYYY-MM-DD`. */
153
+ readonly from?: string;
154
+ readonly to?: string;
155
+ };
156
+
157
+ export type GeoRadiusFilterValue = {
158
+ readonly kind: "geoRadius";
159
+ /** The chosen place, kept for the chip and for re-opening the editor. */
160
+ readonly place: PlaceSuggestion;
161
+ readonly radiusKm: number;
162
+ };
163
+
164
+ export type BooleanFilterValue = {
165
+ readonly kind: "boolean";
166
+ readonly value: boolean;
167
+ };
168
+
169
+ export type FilterValue =
170
+ | TextFilterValue
171
+ | NumberFilterValue
172
+ | NumberRangeFilterValue
173
+ | MoneyRangeFilterValue
174
+ | SelectFilterValue
175
+ | DateRangeFilterValue
176
+ | GeoRadiusFilterValue
177
+ | BooleanFilterValue;
178
+
179
+ /**
180
+ * The applied filters, keyed by definition key. A key is absent when its filter
181
+ * is unset — an empty value is never stored, so `Object.keys` is the active
182
+ * count and the record round-trips to a query string unchanged.
183
+ */
184
+ export type FilterValues = Readonly<Record<string, FilterValue>>;
185
+
186
+ /** Every string the package renders. Defaults are English; pass translations. */
187
+ export type FilterLabels = {
188
+ readonly trigger: string;
189
+ readonly title: string;
190
+ readonly description?: string;
191
+ readonly apply: string;
192
+ /** Empties one field. */
193
+ readonly clear: string;
194
+ /** Empties the whole panel. */
195
+ readonly clearAll: string;
196
+ readonly cancel: string;
197
+ readonly close: string;
198
+ readonly search: string;
199
+ readonly searchPlaceholder: string;
200
+ /** Placeholder of the search box a long option list gets. */
201
+ readonly optionSearchPlaceholder: string;
202
+ readonly is: string;
203
+ readonly isNot: string;
204
+ readonly from: string;
205
+ readonly to: string;
206
+ readonly min: string;
207
+ readonly max: string;
208
+ readonly any: string;
209
+ readonly remove: string;
210
+ /** Chip and field copy for a radius around a place, e.g. "30 km around Nantes". */
211
+ readonly around: (place: string, kilometres: number) => string;
212
+ readonly placePlaceholder: string;
213
+ readonly placeNoResults: string;
214
+ readonly radius: string;
215
+ readonly changePlace: string;
216
+ readonly activeCount: (count: number) => string;
217
+ readonly resultCount: (count: number) => string;
218
+ readonly selectedCount: (count: number) => string;
219
+ };
220
+
221
+ export const defaultFilterLabels: FilterLabels = {
222
+ trigger: "Filters",
223
+ title: "Filters",
224
+ description: undefined,
225
+ apply: "Apply",
226
+ clear: "Clear",
227
+ clearAll: "Clear all",
228
+ cancel: "Cancel",
229
+ close: "Close",
230
+ search: "Search",
231
+ searchPlaceholder: "Search…",
232
+ optionSearchPlaceholder: "Search options…",
233
+ is: "is",
234
+ isNot: "is not",
235
+ from: "From",
236
+ to: "To",
237
+ min: "Min",
238
+ max: "Max",
239
+ any: "Any",
240
+ remove: "Remove",
241
+ around: (place, kilometres) => `${kilometres} km around ${place}`,
242
+ placePlaceholder: "Town, address…",
243
+ placeNoResults: "No place found.",
244
+ radius: "Radius",
245
+ changePlace: "Change",
246
+ activeCount: (count) => `${count} active`,
247
+ resultCount: (count) => `${count} results`,
248
+ selectedCount: (count) => `${count} selected`,
249
+ };