@swan-io/lake 8.11.0 → 8.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/lake",
3
- "version": "8.11.0",
3
+ "version": "8.12.0",
4
4
  "engines": {
5
5
  "node": ">=20.9.0",
6
6
  "yarn": "^1.22.0"
@@ -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, spacings } from "../constants/design";
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
- padding: 24,
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
- minWidth: 200,
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(View, { style: [styles.dropdown, { width }], children: _jsx(FlatList, { role: "list", data: items, contentContainerStyle: styles.content, keyExtractor: (_, index) => `filter-item-${index}`, renderItem: ({ item }) => {
89
- const isSelected = value === item.value;
90
- return (_jsxs(Pressable, { role: "radio", "aria-checked": isSelected, style: ({ hovered }) => [styles.radio, hovered && styles.itemHovered], onPress: () => {
91
- onValueChange(item.value);
92
- close();
93
- }, children: [_jsx(LakeRadio, { value: isSelected }), _jsx(Space, { width: 12 }), _jsx(Text, { style: styles.itemLabel, children: item.label })] }));
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, applyButtonLabel, onPressRemove, autoOpen = false, }) {
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 [localValue, setLocalValue] = useState(value);
100
- const values = useMemo(() => new Set(localValue), [localValue]);
101
- const currentValue = useMemo(() => items.filter(item => values.has(item.value)), [items, values]);
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 = values.size === 0 ? false : values.size === items.length ? true : "mixed";
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, values]);
114
- const save = useCallback(() => {
115
- onValueChange(localValue);
116
- close();
117
- }, [onValueChange, localValue, close]);
118
- useEffect(() => {
119
- if (!visible) {
120
- setLocalValue(value);
121
- }
122
- }, [visible, value]);
123
- 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: _jsxs(View, { style: [styles.dropdown, { width }], children: [_jsx(FlatList, { role: "list", data: listItems, contentContainerStyle: styles.content, keyExtractor: (_, index) => `filter-item-${index}`, renderItem: ({ item }) => {
124
- const isSelected = match(item)
125
- .with({ checked: P.any }, ({ checked }) => checked)
126
- .with({ value: P.any }, ({ value }) => values.has(value))
127
- .exhaustive();
128
- const onPress = match(item)
129
- // Check all item
130
- .with({ checked: P.any }, ({ checked }) => () => {
131
- if (checked === true) {
132
- setLocalValue(undefined);
133
- }
134
- else {
135
- setLocalValue(items.map(item => item.value));
136
- }
137
- })
138
- // Regular item
139
- .with({ value: P.any }, ({ value }) => () => {
140
- const nextValues = new Set([...values]);
141
- if (isSelected === true) {
142
- nextValues.delete(value);
143
- }
144
- else {
145
- nextValues.add(value);
146
- }
147
- if (nextValues.size === 0) {
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, onSave, onPressRemove, autoOpen = false, }) {
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
- onSave(formattedValue);
151
+ onValueChange(formattedValue);
165
152
  }, onDismiss: close })] }));
166
153
  }
167
- function FilterInput({ label, initialValue = "", noValueText, submitText, autoOpen = false, placeholder, validate, onSave, onPressRemove, }) {
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 [value, setValue] = useState(initialValue);
171
- const { Field, submitForm } = useForm({
172
- input: {
173
- initialValue,
174
- validate,
175
- },
176
- });
177
- const onSubmit = () => {
178
- submitForm({
179
- onSuccess: ({ input }) => {
180
- if (input.isSome()) {
181
- setValue(input.get());
182
- onSave(input.get());
183
- close();
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, submitText }) => (_jsx(FilterCheckbox, { label: label, items: items, width: width, checkAllLabel: checkAllLabel, autoOpen: lastOpenedFilter === filterName, applyButtonLabel: submitText, value: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
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), onSave: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
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), onSave: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
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
  } })))