@swan-io/lake 8.11.0 → 8.12.1
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/package.json +1 -1
- package/src/components/Filters.d.ts +11 -2
- package/src/components/Filters.js +77 -92
package/package.json
CHANGED
|
@@ -10,8 +10,11 @@ export type FilterCheckboxDef<T> = {
|
|
|
10
10
|
label: string;
|
|
11
11
|
items: Item<T>[];
|
|
12
12
|
width?: number;
|
|
13
|
-
submitText: string;
|
|
14
13
|
checkAllLabel?: string;
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated
|
|
16
|
+
*/
|
|
17
|
+
submitText?: string;
|
|
15
18
|
};
|
|
16
19
|
export type FilterRadioDef<T> = {
|
|
17
20
|
type: "radio";
|
|
@@ -32,11 +35,17 @@ export type FilterDateDef<Values = unknown> = {
|
|
|
32
35
|
export type FilterInputDef = {
|
|
33
36
|
type: "input";
|
|
34
37
|
label: string;
|
|
35
|
-
submitText: string;
|
|
36
38
|
noValueText: string;
|
|
37
39
|
placeholder?: string;
|
|
38
40
|
validate?: (value: string) => ValidatorResult;
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated
|
|
43
|
+
*/
|
|
44
|
+
submitText?: string;
|
|
39
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated
|
|
48
|
+
*/
|
|
40
49
|
export type FilterBooleanDef = {
|
|
41
50
|
type: "boolean";
|
|
42
51
|
label: string;
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { DatePickerModal, } from "@swan-io/shared-business/src/components/DatePicker";
|
|
3
|
-
import { useForm } from "@swan-io/use-form";
|
|
4
3
|
import dayjs from "dayjs";
|
|
5
4
|
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
5
|
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
7
6
|
import { P, match } from "ts-pattern";
|
|
8
|
-
import { colors, shadows
|
|
7
|
+
import { colors, shadows } from "../constants/design";
|
|
9
8
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
10
9
|
import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
11
10
|
import { usePreviousValue } from "../hooks/usePreviousValue";
|
|
12
|
-
import { isNotNullish } from "../utils/nullish";
|
|
11
|
+
import { isNotNullish, isNullish } from "../utils/nullish";
|
|
13
12
|
import { Box } from "./Box";
|
|
14
13
|
import { FlatList } from "./FlatList";
|
|
15
14
|
import { Icon } from "./Icon";
|
|
16
|
-
import { LakeButton } from "./LakeButton";
|
|
17
15
|
import { LakeCheckbox } from "./LakeCheckbox";
|
|
18
16
|
import { LakeLabel } from "./LakeLabel";
|
|
19
17
|
import { LakeRadio } from "./LakeRadio";
|
|
@@ -51,7 +49,9 @@ const styles = StyleSheet.create({
|
|
|
51
49
|
paddingVertical: 12,
|
|
52
50
|
},
|
|
53
51
|
inputContent: {
|
|
54
|
-
|
|
52
|
+
paddingHorizontal: 24,
|
|
53
|
+
paddingTop: 24,
|
|
54
|
+
paddingBottom: 16,
|
|
55
55
|
},
|
|
56
56
|
radio: {
|
|
57
57
|
display: "flex",
|
|
@@ -66,15 +66,12 @@ const styles = StyleSheet.create({
|
|
|
66
66
|
whiteSpace: "nowrap",
|
|
67
67
|
},
|
|
68
68
|
input: {
|
|
69
|
-
|
|
69
|
+
width: 250,
|
|
70
70
|
},
|
|
71
71
|
value: {
|
|
72
72
|
maxWidth: 130,
|
|
73
73
|
whiteSpace: "nowrap",
|
|
74
74
|
},
|
|
75
|
-
buttonContainer: {
|
|
76
|
-
paddingHorizontal: spacings[24],
|
|
77
|
-
},
|
|
78
75
|
});
|
|
79
76
|
const FilterTag = forwardRef(({ onPress, onPressRemove, label, value = "", isActive }, forwardRef) => {
|
|
80
77
|
const ref = useRef(null);
|
|
@@ -85,107 +82,95 @@ function FilterRadio({ label, items, width, value, onValueChange, onPressRemove,
|
|
|
85
82
|
const inputRef = useRef(null);
|
|
86
83
|
const [visible, { close, toggle }] = useDisclosure(autoOpen);
|
|
87
84
|
const currentValue = useMemo(() => items.find(i => i.value === value), [items, value]);
|
|
88
|
-
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: inputRef, onPressRemove: onPressRemove, isActive: visible, value: currentValue === null || currentValue === void 0 ? void 0 : currentValue.label }), _jsx(Popover, { role: "listbox", matchReferenceWidth: false, onDismiss: close, referenceRef: inputRef, returnFocus: false, visible: visible, children: _jsx(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: inputRef, onPressRemove: onPressRemove, isActive: visible, value: currentValue === null || currentValue === void 0 ? void 0 : currentValue.label }), _jsx(Popover, { role: "listbox", matchReferenceWidth: false, onDismiss: close, referenceRef: inputRef, returnFocus: false, visible: visible, children: _jsx(FlatList, { role: "list", data: items, style: [styles.dropdown, { width }], contentContainerStyle: styles.content, keyExtractor: (_, index) => `filter-item-${index}`, renderItem: ({ item }) => {
|
|
86
|
+
const isSelected = value === item.value;
|
|
87
|
+
return (_jsxs(Pressable, { role: "radio", "aria-checked": isSelected, style: ({ hovered }) => [styles.radio, hovered && styles.itemHovered], onPress: () => {
|
|
88
|
+
onValueChange(item.value);
|
|
89
|
+
close();
|
|
90
|
+
}, children: [_jsx(LakeRadio, { value: isSelected }), _jsx(Space, { width: 12 }), _jsx(Text, { style: styles.itemLabel, children: item.label })] }));
|
|
91
|
+
} }) })] }));
|
|
95
92
|
}
|
|
96
|
-
function FilterCheckbox({ label, items, width, checkAllLabel, value, onValueChange,
|
|
93
|
+
function FilterCheckbox({ label, items, width, checkAllLabel, value, onValueChange, onPressRemove, autoOpen = false, }) {
|
|
97
94
|
const inputRef = useRef(null);
|
|
98
95
|
const [visible, { close, toggle }] = useDisclosure(autoOpen);
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const allChecked = checkAllLabel != null && values.size === items.length;
|
|
96
|
+
const valueSet = useMemo(() => new Set(value), [value]);
|
|
97
|
+
const currentValue = useMemo(() => items.filter(item => valueSet.has(item.value)), [items, valueSet]);
|
|
98
|
+
const allChecked = checkAllLabel != null && valueSet.size === items.length;
|
|
103
99
|
const listItems = useMemo(() => {
|
|
104
100
|
if (checkAllLabel == null) {
|
|
105
101
|
return items;
|
|
106
102
|
}
|
|
107
|
-
const checked =
|
|
103
|
+
const checked = valueSet.size === 0 ? false : valueSet.size === items.length ? true : "mixed";
|
|
108
104
|
const checkAllItem = {
|
|
109
105
|
label: checkAllLabel,
|
|
110
106
|
checked,
|
|
111
107
|
};
|
|
112
108
|
return [checkAllItem, ...items];
|
|
113
|
-
}, [items, checkAllLabel,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
setLocalValue(undefined);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
setLocalValue([...nextValues]);
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
.exhaustive();
|
|
155
|
-
return (_jsxs(Pressable, { role: "radio", "aria-checked": isSelected, style: ({ hovered }) => [styles.radio, hovered && styles.itemHovered], onPress: onPress, children: [_jsx(LakeCheckbox, { value: isSelected }), _jsx(Space, { width: 12 }), _jsx(Text, { style: styles.itemLabel, children: item.label })] }));
|
|
156
|
-
} }), _jsx(Space, { height: 8 }), _jsx(View, { style: styles.buttonContainer, children: _jsx(LakeButton, { color: "current", onPress: save, children: applyButtonLabel }) }), _jsx(Space, { height: 24 })] }) })] }));
|
|
109
|
+
}, [items, checkAllLabel, valueSet]);
|
|
110
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: inputRef, onPressRemove: onPressRemove, isActive: visible, value: allChecked ? checkAllLabel : currentValue.map(item => item.label).join(", ") }), _jsx(Popover, { role: "listbox", matchReferenceWidth: false, onDismiss: close, referenceRef: inputRef, returnFocus: false, visible: visible, children: _jsx(FlatList, { role: "list", data: listItems, style: [styles.dropdown, { width }], contentContainerStyle: styles.content, keyExtractor: (_, index) => `filter-item-${index}`, renderItem: ({ item }) => {
|
|
111
|
+
const isSelected = match(item)
|
|
112
|
+
.with({ checked: P.any }, ({ checked }) => checked)
|
|
113
|
+
.with({ value: P.any }, ({ value }) => valueSet.has(value))
|
|
114
|
+
.exhaustive();
|
|
115
|
+
const onPress = match(item)
|
|
116
|
+
// Check all item
|
|
117
|
+
.with({ checked: P.any }, ({ checked }) => () => {
|
|
118
|
+
if (checked === true) {
|
|
119
|
+
onValueChange(undefined);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
onValueChange(items.map(item => item.value));
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
// Regular item
|
|
126
|
+
.with({ value: P.any }, ({ value }) => () => {
|
|
127
|
+
const nextValues = new Set([...valueSet]);
|
|
128
|
+
if (isSelected === true) {
|
|
129
|
+
nextValues.delete(value);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
nextValues.add(value);
|
|
133
|
+
}
|
|
134
|
+
if (nextValues.size === 0) {
|
|
135
|
+
onValueChange(undefined);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
onValueChange([...nextValues]);
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.exhaustive();
|
|
142
|
+
return (_jsxs(Pressable, { role: "radio", "aria-checked": isSelected, style: ({ hovered }) => [styles.radio, hovered && styles.itemHovered], onPress: onPress, children: [_jsx(LakeCheckbox, { value: isSelected }), _jsx(Space, { width: 12 }), _jsx(Text, { style: styles.itemLabel, children: item.label })] }));
|
|
143
|
+
} }) })] }));
|
|
157
144
|
}
|
|
158
|
-
function FilterDate({ label, initialValue, noValueText, cancelText, submitText, dateFormat, isSelectable, validate,
|
|
145
|
+
function FilterDate({ label, initialValue, noValueText, cancelText, submitText, dateFormat, isSelectable, validate, onValueChange, onPressRemove, autoOpen = false, }) {
|
|
159
146
|
const inputRef = useRef(null);
|
|
160
147
|
const [visible, { close, toggle }] = useDisclosure(autoOpen);
|
|
161
148
|
const value = useMemo(() => (isNotNullish(initialValue) ? dayjs(initialValue).format(dateFormat) : ""), [initialValue, dateFormat]);
|
|
162
149
|
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: inputRef, onPressRemove: onPressRemove, isActive: visible, value: isNotNullish(initialValue) ? dayjs(initialValue).format(dateFormat) : noValueText }), _jsx(DatePickerModal, { visible: visible, format: dateFormat, firstWeekDay: "monday", label: label, cancelLabel: cancelText, confirmLabel: submitText, value: value, isSelectable: isSelectable, validate: validate, onChange: value => {
|
|
163
150
|
const formattedValue = dayjs(value, dateFormat, true).toJSON();
|
|
164
|
-
|
|
151
|
+
onValueChange(formattedValue);
|
|
165
152
|
}, onDismiss: close })] }));
|
|
166
153
|
}
|
|
167
|
-
function FilterInput({ label, initialValue = "", noValueText,
|
|
168
|
-
const inputRef = useRef(null);
|
|
154
|
+
function FilterInput({ label, initialValue = "", noValueText, autoOpen = false, placeholder, validate, onValueChange, onPressRemove, }) {
|
|
169
155
|
const [visible, { close, toggle }] = useDisclosure(autoOpen);
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
};
|
|
188
|
-
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: inputRef, onPressRemove: onPressRemove, isActive: visible, value: value === "" ? noValueText : value }), _jsx(Popover, { role: "listbox", matchReferenceWidth: false, onDismiss: close, referenceRef: inputRef, returnFocus: false, visible: visible, children: _jsxs(View, { style: [styles.dropdown, styles.inputContent], children: [_jsx(Field, { name: "input", children: ({ error, value, onChange }) => (_jsx(LakeLabel, { label: label, render: id => (_jsx(LakeTextInput, { id: id, error: error, style: styles.input, placeholder: placeholder, value: value, onChangeText: onChange, onSubmitEditing: onSubmit })) })) }), _jsx(LakeButton, { size: "small", color: "current", onPress: onSubmit, children: submitText })] }) })] }));
|
|
156
|
+
const tagRef = useRef(null);
|
|
157
|
+
const getValueState = useCallback((inputValue, isInitialValue) => {
|
|
158
|
+
var _a;
|
|
159
|
+
const trimmed = inputValue.trim();
|
|
160
|
+
const error = (_a = validate === null || validate === void 0 ? void 0 : validate(trimmed)) !== null && _a !== void 0 ? _a : undefined;
|
|
161
|
+
const validValue = isNullish(error) ? trimmed : undefined;
|
|
162
|
+
if (!isInitialValue) {
|
|
163
|
+
onValueChange(validValue);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
inputValue,
|
|
167
|
+
validValue,
|
|
168
|
+
error: isInitialValue ? undefined : error,
|
|
169
|
+
};
|
|
170
|
+
}, [validate, onValueChange]);
|
|
171
|
+
const [{ inputValue, validValue, error }, setState] = useState(() => getValueState(initialValue, true));
|
|
172
|
+
const onChangeText = useCallback((value) => setState(getValueState(value, false)), [getValueState]);
|
|
173
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(FilterTag, { label: label, onPress: toggle, ref: tagRef, onPressRemove: onPressRemove, isActive: visible, value: validValue !== null && validValue !== void 0 ? validValue : noValueText }), _jsx(Popover, { role: "listbox", matchReferenceWidth: false, onDismiss: close, referenceRef: tagRef, returnFocus: false, visible: visible, children: _jsx(View, { style: [styles.dropdown, styles.inputContent], children: _jsx(LakeLabel, { label: label, render: id => (_jsx(LakeTextInput, { id: id, value: inputValue, error: error, style: styles.input, placeholder: placeholder, onChangeText: onChangeText })) }) }) })] }));
|
|
189
174
|
}
|
|
190
175
|
function FilterBooleanTag({ children, onAdd, onPressRemove }) {
|
|
191
176
|
useEffect(onAdd, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
@@ -210,15 +195,15 @@ export const FiltersStack = ({ filters, openedFilters, definition, onChangeOpene
|
|
|
210
195
|
onChangeFilters({ ...filters, [filterName]: undefined });
|
|
211
196
|
onChangeOpened(openedFilters.filter(f => f !== filterName));
|
|
212
197
|
}, value: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }) })))
|
|
213
|
-
.with({ type: "checkbox" }, ({ type, label, items, width, checkAllLabel
|
|
198
|
+
.with({ type: "checkbox" }, ({ type, label, items, width, checkAllLabel }) => (_jsx(FilterCheckbox, { label: label, items: items, width: width, checkAllLabel: checkAllLabel, autoOpen: lastOpenedFilter === filterName, value: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
|
|
214
199
|
onChangeFilters({ ...filters, [filterName]: undefined });
|
|
215
200
|
onChangeOpened(openedFilters.filter(f => f !== filterName));
|
|
216
201
|
} })))
|
|
217
|
-
.with({ type: "date" }, ({ type, label, noValueText, cancelText, submitText, dateFormat, isSelectable, validate, }) => (_jsx(FilterDate, { label: label, noValueText: noValueText, cancelText: cancelText, submitText: submitText, dateFormat: dateFormat, autoOpen: lastOpenedFilter === filterName, isSelectable: isSelectable ? date => isSelectable(date, filters) : undefined, validate: validate ? value => validate(value, filters) : undefined, initialValue: getFilterValue(type, filters, filterName),
|
|
202
|
+
.with({ type: "date" }, ({ type, label, noValueText, cancelText, submitText, dateFormat, isSelectable, validate, }) => (_jsx(FilterDate, { label: label, noValueText: noValueText, cancelText: cancelText, submitText: submitText, dateFormat: dateFormat, autoOpen: lastOpenedFilter === filterName, isSelectable: isSelectable ? date => isSelectable(date, filters) : undefined, validate: validate ? value => validate(value, filters) : undefined, initialValue: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
|
|
218
203
|
onChangeFilters({ ...filters, [filterName]: undefined });
|
|
219
204
|
onChangeOpened(openedFilters.filter(f => f !== filterName));
|
|
220
205
|
} })))
|
|
221
|
-
.with({ type: "input" }, ({ type, label, placeholder, noValueText, submitText, validate }) => (_jsx(FilterInput, { label: label, placeholder: placeholder, noValueText: noValueText, submitText: submitText, autoOpen: lastOpenedFilter === filterName, validate: validate, initialValue: getFilterValue(type, filters, filterName),
|
|
206
|
+
.with({ type: "input" }, ({ type, label, placeholder, noValueText, submitText, validate }) => (_jsx(FilterInput, { label: label, placeholder: placeholder, noValueText: noValueText, submitText: submitText, autoOpen: lastOpenedFilter === filterName, validate: validate, initialValue: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
|
|
222
207
|
onChangeFilters({ ...filters, [filterName]: undefined });
|
|
223
208
|
onChangeOpened(openedFilters.filter(f => f !== filterName));
|
|
224
209
|
} })))
|