@swan-io/shared-business 12.4.2 → 13.0.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.
Binary file
@@ -1,123 +0,0 @@
1
- import { ValidatorResult } from "@swan-io/use-form";
2
- import { Simplify } from "type-fest";
3
- import { DateFormat } from "../utils/i18n";
4
- import { DatePickerDate } from "./DatePicker";
5
- type Item<T> = {
6
- label: string;
7
- value: T;
8
- };
9
- /**
10
- * @deprecated
11
- */
12
- export type FilterCheckboxDef<T> = {
13
- type: "checkbox";
14
- label: string;
15
- items: Item<T>[];
16
- width?: number;
17
- checkAllLabel?: string;
18
- };
19
- /**
20
- * @deprecated
21
- */
22
- export type FilterRadioDef<T> = {
23
- type: "radio";
24
- label: string;
25
- items: Item<T>[];
26
- width?: number;
27
- };
28
- /**
29
- * @deprecated
30
- */
31
- export type FilterDateDef<Values = unknown> = {
32
- type: "date";
33
- label: string;
34
- cancelText: string;
35
- submitText: string;
36
- noValueText: string;
37
- dateFormat: DateFormat;
38
- isSelectable?: (date: DatePickerDate, filters: Values) => boolean;
39
- validate?: (value: string, filters: Values) => ValidatorResult;
40
- };
41
- /**
42
- * @deprecated
43
- */
44
- export type FilterInputDef = {
45
- type: "input";
46
- label: string;
47
- noValueText: string;
48
- placeholder?: string;
49
- validate?: (value: string) => ValidatorResult;
50
- };
51
- type Filter<T> = FilterCheckboxDef<T> | FilterRadioDef<T> | FilterDateDef | FilterInputDef;
52
- type ExtractFilterValue<T extends Filter<unknown>> = T extends {
53
- type: "checkbox";
54
- } ? T["items"][number]["value"][] | undefined : T extends {
55
- type: "radio";
56
- } ? T["items"][number]["value"] | undefined : string | undefined;
57
- type FiltersDefinition = Record<string, Filter<unknown>>;
58
- /**
59
- * @deprecated
60
- */
61
- export type FiltersState<T extends FiltersDefinition> = Simplify<{
62
- [K in keyof T]: Simplify<ExtractFilterValue<T[K]>>;
63
- }>;
64
- type FiltersStackProps<Definition extends FiltersDefinition, State extends FiltersState<Definition> = FiltersState<Definition>> = {
65
- definition: Definition;
66
- filters: State;
67
- openedFilters: (keyof Definition)[];
68
- onChangeOpened: (value: (keyof Definition)[]) => void;
69
- onChangeFilters: (value: State) => void;
70
- };
71
- /**
72
- * @deprecated
73
- */
74
- export declare const FiltersStack: <T extends FiltersDefinition>({ filters, openedFilters, definition, onChangeOpened, onChangeFilters, }: FiltersStackProps<T>) => import("react/jsx-runtime").JSX.Element | null;
75
- /**
76
- * @deprecated
77
- */
78
- export declare const filter: {
79
- readonly checkbox: <T>(config: {
80
- label: string;
81
- items: Item<T>[];
82
- }) => FilterCheckboxDef<T>;
83
- readonly date: (config: {
84
- label: string;
85
- isSelectable?: (date: DatePickerDate, filters: unknown) => boolean;
86
- validate?: (value: string, filters: unknown) => ValidatorResult;
87
- }) => FilterDateDef;
88
- readonly input: (config: {
89
- label: string;
90
- placeholder?: string;
91
- validate?: (value: string) => ValidatorResult;
92
- }) => FilterInputDef;
93
- readonly radio: <T>(config: {
94
- label: string;
95
- items: Item<T>[];
96
- }) => FilterRadioDef<T>;
97
- };
98
- /**
99
- * @deprecated
100
- */
101
- export declare const useFiltersProps: <Definition extends FiltersDefinition, Filters extends Record<string, unknown>>({ available, filters, filtersDefinition, }: {
102
- available?: ReadonlyArray<keyof Filters>;
103
- filters: Filters;
104
- filtersDefinition: Definition;
105
- }) => {
106
- chooser: {
107
- availableFilters: {
108
- name: keyof Definition;
109
- label: string;
110
- }[];
111
- filters: Filters;
112
- label: string;
113
- onAddFilter: (filter: keyof Filters) => void;
114
- openFilters: (keyof Filters)[];
115
- };
116
- stack: {
117
- definition: Definition;
118
- filters: Filters;
119
- openedFilters: (keyof Filters)[];
120
- onChangeOpened: import("react").Dispatch<import("react").SetStateAction<(keyof Filters)[]>>;
121
- };
122
- };
123
- export {};
@@ -1,280 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Array, Dict, Option } from "@swan-io/boxed";
3
- import { Box } from "@swan-io/lake/src/components/Box";
4
- import { FlatList } from "@swan-io/lake/src/components/FlatList";
5
- import { Icon } from "@swan-io/lake/src/components/Icon";
6
- import { LakeCheckbox } from "@swan-io/lake/src/components/LakeCheckbox";
7
- import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
8
- import { LakeRadio } from "@swan-io/lake/src/components/LakeRadio";
9
- import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
10
- import { Popover } from "@swan-io/lake/src/components/Popover";
11
- import { Space } from "@swan-io/lake/src/components/Space";
12
- import { Stack } from "@swan-io/lake/src/components/Stack";
13
- import { Tag } from "@swan-io/lake/src/components/Tag";
14
- import { colors, shadows } from "@swan-io/lake/src/constants/design";
15
- import { useDisclosure } from "@swan-io/lake/src/hooks/useDisclosure";
16
- import { usePreviousValue } from "@swan-io/lake/src/hooks/usePreviousValue";
17
- import { isNotNullish, isNullish } from "@swan-io/lake/src/utils/nullish";
18
- import dayjs from "dayjs";
19
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
- import { Pressable, StyleSheet, Text, View } from "react-native";
21
- import { P, match } from "ts-pattern";
22
- import { locale, t } from "../utils/i18n";
23
- import { DatePickerModal } from "./DatePicker";
24
- const styles = StyleSheet.create({
25
- container: {
26
- paddingRight: 12,
27
- paddingBottom: 8,
28
- },
29
- shadowed: {
30
- position: "absolute",
31
- opacity: 0,
32
- width: "100%",
33
- height: "100%",
34
- borderRadius: 4,
35
- boxShadow: shadows.tile,
36
- transitionDuration: "150ms",
37
- transitionProperty: "opacity",
38
- },
39
- hovered: {
40
- opacity: 1,
41
- },
42
- dropdown: {
43
- maxHeight: 400,
44
- minWidth: 200,
45
- },
46
- itemHovered: {
47
- backgroundColor: colors.gray[50],
48
- },
49
- content: {
50
- paddingVertical: 12,
51
- },
52
- inputContent: {
53
- paddingHorizontal: 24,
54
- paddingTop: 24,
55
- paddingBottom: 16,
56
- },
57
- radio: {
58
- display: "flex",
59
- flexDirection: "row",
60
- alignItems: "center",
61
- height: 32,
62
- paddingHorizontal: 24,
63
- },
64
- itemLabel: {
65
- textOverflow: "ellipsis",
66
- overflow: "hidden",
67
- whiteSpace: "nowrap",
68
- },
69
- input: {
70
- width: 250,
71
- },
72
- value: {
73
- maxWidth: 130,
74
- whiteSpace: "nowrap",
75
- },
76
- });
77
- const FilterTag = ({ ref, onPress, onPressRemove, label, value = "", isActive }) => (_jsx(Pressable, { ref: ref, onPress: onPress, children: ({ hovered }) => (_jsxs(_Fragment, { children: [_jsx(View, { style: [styles.shadowed, hovered && styles.hovered] }), _jsx(Tag, { label: label, color: "current", onPressRemove: onPressRemove, children: _jsxs(Box, { direction: "row", alignItems: "center", children: [_jsx(Text, { numberOfLines: 1, style: styles.value, children: value }), _jsx(Space, { width: 4 }), _jsx(Icon, { color: colors.current.primary, name: isActive ? "chevron-up-filled" : "chevron-down-filled", size: 16 })] }) })] })) }));
78
- const FilterRadio = ({ label, items, width, value, onValueChange, onPressRemove, autoOpen = false, }) => {
79
- const inputRef = useRef(null);
80
- const [visible, { close, toggle }] = useDisclosure(autoOpen);
81
- const currentValue = useMemo(() => items.find(i => i.value === value), [items, value]);
82
- 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 }) => {
83
- const isSelected = value === item.value;
84
- return (_jsxs(Pressable, { role: "radio", "aria-checked": isSelected, style: ({ hovered }) => [styles.radio, hovered && styles.itemHovered], onPress: () => {
85
- onValueChange(item.value);
86
- close();
87
- }, children: [_jsx(LakeRadio, { value: isSelected }), _jsx(Space, { width: 12 }), _jsx(Text, { style: styles.itemLabel, children: item.label })] }));
88
- } }) })] }));
89
- };
90
- const FilterCheckbox = ({ label, items, width, checkAllLabel, value, onValueChange, onPressRemove, autoOpen = false, }) => {
91
- const inputRef = useRef(null);
92
- const [visible, { close, toggle }] = useDisclosure(autoOpen);
93
- const valueSet = useMemo(() => new Set(value), [value]);
94
- const currentValue = useMemo(() => items.filter(item => valueSet.has(item.value)), [items, valueSet]);
95
- const allChecked = checkAllLabel != null && valueSet.size === items.length;
96
- const listItems = useMemo(() => {
97
- if (checkAllLabel == null) {
98
- return items;
99
- }
100
- const checked = valueSet.size === 0 ? false : valueSet.size === items.length ? true : "mixed";
101
- const checkAllItem = {
102
- label: checkAllLabel,
103
- checked,
104
- };
105
- return [checkAllItem, ...items];
106
- }, [items, checkAllLabel, valueSet]);
107
- 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 }) => {
108
- const isSelected = match(item)
109
- .with({ checked: P.any }, ({ checked }) => checked)
110
- .with({ value: P.any }, ({ value }) => valueSet.has(value))
111
- .exhaustive();
112
- const onPress = match(item)
113
- // Check all item
114
- .with({ checked: P.any }, ({ checked }) => () => {
115
- if (checked === true) {
116
- onValueChange(undefined);
117
- }
118
- else {
119
- onValueChange(items.map(item => item.value));
120
- }
121
- })
122
- // Regular item
123
- .with({ value: P.any }, ({ value }) => () => {
124
- const nextValues = new Set([...valueSet]);
125
- if (isSelected === true) {
126
- nextValues.delete(value);
127
- }
128
- else {
129
- nextValues.add(value);
130
- }
131
- if (nextValues.size === 0) {
132
- onValueChange(undefined);
133
- }
134
- else {
135
- onValueChange([...nextValues]);
136
- }
137
- })
138
- .exhaustive();
139
- 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 })] }));
140
- } }) })] }));
141
- };
142
- const FilterDate = ({ label, initialValue, noValueText, cancelText, submitText, dateFormat, isSelectable, validate, onValueChange, onPressRemove, autoOpen = false, }) => {
143
- const inputRef = useRef(null);
144
- const [visible, { close, toggle }] = useDisclosure(autoOpen);
145
- const value = useMemo(() => (isNotNullish(initialValue) ? dayjs(initialValue).format(dateFormat) : ""), [initialValue, dateFormat]);
146
- 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 => {
147
- const formattedValue = dayjs(value, dateFormat, true).toJSON();
148
- onValueChange(formattedValue);
149
- }, onDismiss: close })] }));
150
- };
151
- const FilterInput = ({ label, initialValue = "", noValueText, autoOpen = false, placeholder, validate, onValueChange, onPressRemove, }) => {
152
- const [visible, { close, toggle }] = useDisclosure(autoOpen);
153
- const tagRef = useRef(null);
154
- const getValueState = useCallback((inputValue, isInitialValue) => {
155
- var _a;
156
- const trimmed = inputValue.trim();
157
- const error = (_a = validate === null || validate === void 0 ? void 0 : validate(trimmed)) !== null && _a !== void 0 ? _a : undefined;
158
- const validValue = isNullish(error) ? trimmed : undefined;
159
- if (!isInitialValue) {
160
- onValueChange(validValue);
161
- }
162
- return {
163
- inputValue,
164
- validValue,
165
- error: isInitialValue ? undefined : error,
166
- };
167
- }, [validate, onValueChange]);
168
- const [{ inputValue, validValue, error }, setState] = useState(() => getValueState(initialValue, true));
169
- const onChangeText = useCallback((value) => setState(getValueState(value, false)), [getValueState]);
170
- 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 })) }) }) })] }));
171
- };
172
- const getFilterValue = (_type, filters, name) => filters[name];
173
- /**
174
- * @deprecated
175
- */
176
- export const FiltersStack = ({ filters, openedFilters, definition, onChangeOpened, onChangeFilters, }) => {
177
- const previousOpened = usePreviousValue(openedFilters);
178
- const lastOpenedFilter = openedFilters.length > previousOpened.length
179
- ? openedFilters[openedFilters.length - 1]
180
- : undefined;
181
- if (openedFilters.length === 0) {
182
- return null;
183
- }
184
- return (_jsx(Stack, { direction: "row", wrap: "wrap", children: openedFilters.map(filterName => {
185
- const filterDefinition = definition[filterName];
186
- if (typeof filterName !== "string" || filterDefinition == null) {
187
- return null;
188
- }
189
- return (_jsx(View, { children: match(filterDefinition)
190
- .with({ type: "radio" }, ({ type, label, items, width }) => (_jsx(FilterRadio, { label: label, items: items, width: width, autoOpen: lastOpenedFilter === filterName, onPressRemove: () => {
191
- onChangeFilters({ ...filters, [filterName]: undefined });
192
- onChangeOpened(openedFilters.filter(f => f !== filterName));
193
- }, value: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }) })))
194
- .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: () => {
195
- onChangeFilters({ ...filters, [filterName]: undefined });
196
- onChangeOpened(openedFilters.filter(f => f !== filterName));
197
- } })))
198
- .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: () => {
199
- onChangeFilters({ ...filters, [filterName]: undefined });
200
- onChangeOpened(openedFilters.filter(f => f !== filterName));
201
- } })))
202
- .with({ type: "input" }, ({ type, label, placeholder, noValueText, validate }) => (_jsx(FilterInput, { label: label, placeholder: placeholder, noValueText: noValueText, autoOpen: lastOpenedFilter === filterName, validate: validate, initialValue: getFilterValue(type, filters, filterName), onValueChange: value => onChangeFilters({ ...filters, [filterName]: value }), onPressRemove: () => {
203
- onChangeFilters({ ...filters, [filterName]: undefined });
204
- onChangeOpened(openedFilters.filter(f => f !== filterName));
205
- } })))
206
- .exhaustive() }, filterName));
207
- }) }));
208
- };
209
- /**
210
- * @deprecated
211
- */
212
- export const filter = {
213
- checkbox: (config) => ({
214
- type: "checkbox",
215
- label: config.label,
216
- items: config.items,
217
- checkAllLabel: t("common.filters.all"),
218
- }),
219
- date: (config) => ({
220
- type: "date",
221
- label: config.label,
222
- cancelText: t("common.filters.cancel"),
223
- submitText: t("common.filters.apply"),
224
- noValueText: t("common.filters.none"),
225
- dateFormat: locale.dateFormat,
226
- isSelectable: config.isSelectable,
227
- validate: config.validate,
228
- }),
229
- input: (config) => ({
230
- type: "input",
231
- label: config.label,
232
- noValueText: t("common.filters.none"),
233
- placeholder: config.placeholder,
234
- validate: config.validate,
235
- }),
236
- radio: (config) => ({
237
- type: "radio",
238
- label: config.label,
239
- items: config.items,
240
- }),
241
- };
242
- /**
243
- * @deprecated
244
- */
245
- export const useFiltersProps = ({ available, filters, filtersDefinition, }) => {
246
- const availableFilters = useMemo(() => {
247
- const availableSet = new Set(isNullish(available) ? Dict.keys(filtersDefinition) : available);
248
- return Array.filterMap(Dict.entries(filtersDefinition), ([name, { label }]) => availableSet.has(name) ? Option.Some({ name, label }) : Option.None());
249
- }, [available, filtersDefinition]);
250
- const [openFilters, setOpenFilters] = useState(() => Dict.entries(filters)
251
- .filter(([, value]) => isNotNullish(value))
252
- .map(([name]) => name));
253
- useEffect(() => {
254
- setOpenFilters(openFilters => {
255
- const currentlyOpenFilters = new Set(openFilters);
256
- const openFiltersNotYetInState = Dict.entries(filters)
257
- .filter(([name, value]) => isNotNullish(value) && !currentlyOpenFilters.has(name))
258
- .map(([name]) => name);
259
- return [...openFilters, ...openFiltersNotYetInState];
260
- });
261
- }, [filters]);
262
- const onAddFilter = useCallback((filter) => {
263
- setOpenFilters(openFilters => [...openFilters, filter]);
264
- }, []);
265
- return {
266
- chooser: {
267
- availableFilters,
268
- filters,
269
- label: t("common.filters"),
270
- onAddFilter,
271
- openFilters,
272
- },
273
- stack: {
274
- definition: filtersDefinition,
275
- filters,
276
- openedFilters: openFilters,
277
- onChangeOpened: setOpenFilters,
278
- },
279
- };
280
- };