@swan-io/lake 8.4.6 → 8.5.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/FilterChooser.js +2 -1
- package/src/components/Filters.js +2 -1
- package/src/components/FlatList.d.ts +31 -0
- package/src/components/FlatList.js +34 -0
- package/src/components/LakeCombobox.js +6 -9
- package/src/components/LakeSelect.js +2 -1
- package/src/components/MultiSelect.js +5 -4
- package/src/components/SectionList.d.ts +33 -0
- package/src/components/SectionList.js +35 -0
- package/src/components/SegmentedControl.d.ts +2 -2
- package/src/components/SegmentedControl.js +73 -23
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { Pressable, StyleSheet, View } from "react-native";
|
|
4
4
|
import { colors } from "../constants/design";
|
|
5
5
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
6
6
|
import { isNotNullishOrEmpty } from "../utils/nullish";
|
|
7
7
|
import { Box } from "./Box";
|
|
8
|
+
import { FlatList } from "./FlatList";
|
|
8
9
|
import { Icon } from "./Icon";
|
|
9
10
|
import { LakeButton } from "./LakeButton";
|
|
10
11
|
import { LakeText } from "./LakeText";
|
|
@@ -3,7 +3,7 @@ import { DatePickerModal, } from "@swan-io/shared-business/src/components/DatePi
|
|
|
3
3
|
import { useForm } from "@swan-io/use-form";
|
|
4
4
|
import dayjs from "dayjs";
|
|
5
5
|
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
-
import {
|
|
6
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
7
7
|
import { P, match } from "ts-pattern";
|
|
8
8
|
import { colors, shadows, spacings } from "../constants/design";
|
|
9
9
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
@@ -11,6 +11,7 @@ import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
|
11
11
|
import { usePreviousValue } from "../hooks/usePreviousValue";
|
|
12
12
|
import { isNotNullish } from "../utils/nullish";
|
|
13
13
|
import { Box } from "./Box";
|
|
14
|
+
import { FlatList } from "./FlatList";
|
|
14
15
|
import { Icon } from "./Icon";
|
|
15
16
|
import { LakeButton } from "./LakeButton";
|
|
16
17
|
import { LakeCheckbox } from "./LakeCheckbox";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ForwardedRef, ReactNode } from "react";
|
|
2
|
+
import { ScrollView, ScrollViewProps, StyleProp, ViewStyle, WebRole } from "react-native";
|
|
3
|
+
export type FlatListRef = ScrollView;
|
|
4
|
+
export type ListRenderItemInfo<T> = {
|
|
5
|
+
item: T;
|
|
6
|
+
index: number;
|
|
7
|
+
};
|
|
8
|
+
type Props<T> = {
|
|
9
|
+
ItemSeparatorComponent?: ReactNode;
|
|
10
|
+
ListEmptyComponent?: ReactNode;
|
|
11
|
+
ListFooterComponent?: ReactNode;
|
|
12
|
+
ListHeaderComponent?: ReactNode;
|
|
13
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
14
|
+
data: T[];
|
|
15
|
+
horizontal?: boolean;
|
|
16
|
+
keyExtractor: (item: T, index: number) => string;
|
|
17
|
+
onEndReached?: () => void;
|
|
18
|
+
onEndReachedThresholdPx?: number;
|
|
19
|
+
onKeyDown?: ScrollViewProps["onKeyDown"];
|
|
20
|
+
onScroll?: ScrollViewProps["onScroll"];
|
|
21
|
+
renderItem: (info: ListRenderItemInfo<T>) => ReactNode;
|
|
22
|
+
role?: WebRole;
|
|
23
|
+
scrollEventThrottle?: number;
|
|
24
|
+
showsScrollIndicators?: boolean;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
};
|
|
27
|
+
declare const FlatListWithRef: <T>({ ItemSeparatorComponent, ListEmptyComponent, ListFooterComponent, ListHeaderComponent, contentContainerStyle, data, horizontal, keyExtractor, onEndReached, onEndReachedThresholdPx, onKeyDown, onScroll, renderItem, role, scrollEventThrottle, showsScrollIndicators, style, }: Props<T>, forwardedRef: ForwardedRef<FlatListRef>) => import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare const FlatList: <T>(props: Props<T> & {
|
|
29
|
+
ref?: ForwardedRef<FlatListRef>;
|
|
30
|
+
}) => ReturnType<typeof FlatListWithRef>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, forwardRef, useEffect, useRef } from "react";
|
|
3
|
+
import { ScrollView, StyleSheet, View, } from "react-native";
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
scrollTracker: {
|
|
6
|
+
position: "absolute",
|
|
7
|
+
pointerEvents: "none",
|
|
8
|
+
left: 0,
|
|
9
|
+
bottom: 0,
|
|
10
|
+
right: 0,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const FlatListWithRef = ({ ItemSeparatorComponent, ListEmptyComponent, ListFooterComponent, ListHeaderComponent, contentContainerStyle, data, horizontal = false, keyExtractor, onEndReached, onEndReachedThresholdPx = 200, onKeyDown, onScroll, renderItem, role, scrollEventThrottle = 0, showsScrollIndicators = true, style, }, forwardedRef) => {
|
|
14
|
+
const scrollTrackerRef = useRef(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const element = scrollTrackerRef.current;
|
|
17
|
+
if (element != null) {
|
|
18
|
+
const observer = new IntersectionObserver(entries => {
|
|
19
|
+
entries.forEach(entry => {
|
|
20
|
+
if (entry.isIntersecting) {
|
|
21
|
+
onEndReached === null || onEndReached === void 0 ? void 0 : onEndReached();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
observer.observe(element);
|
|
26
|
+
return () => {
|
|
27
|
+
observer.unobserve(element);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// re-create an observer only on list length change
|
|
31
|
+
}, [data.length]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
32
|
+
return (_jsxs(ScrollView, { contentContainerStyle: contentContainerStyle, horizontal: horizontal, onKeyDown: onKeyDown, onScroll: onScroll, ref: forwardedRef, role: role, scrollEventThrottle: scrollEventThrottle, showsHorizontalScrollIndicator: showsScrollIndicators, showsVerticalScrollIndicator: showsScrollIndicators, style: style, children: [ListHeaderComponent, data.length > 0 ? (_jsxs(View, { children: [data.map((item, index) => (_jsxs(Fragment, { children: [index !== 0 && ItemSeparatorComponent, renderItem({ item, index })] }, keyExtractor(item, index)))), _jsx(View, { ref: scrollTrackerRef, style: [styles.scrollTracker, { height: onEndReachedThresholdPx }] })] })) : (ListEmptyComponent), ListFooterComponent] }));
|
|
33
|
+
};
|
|
34
|
+
export const FlatList = forwardRef(FlatListWithRef);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useCallback, useId, useImperativeHandle, useRef, useState, } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { Pressable, StyleSheet, Text, View, } from "react-native";
|
|
4
4
|
import { backgroundColor, colors, spacings } from "../constants/design";
|
|
5
5
|
import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
6
6
|
import { getFocusableElements } from "../utils/a11y";
|
|
7
7
|
import { isNotEmpty } from "../utils/nullish";
|
|
8
8
|
import { Box } from "./Box";
|
|
9
|
+
import { FlatList } from "./FlatList";
|
|
9
10
|
import { Icon } from "./Icon";
|
|
10
11
|
import { LakeText } from "./LakeText";
|
|
11
12
|
import { LakeTextInput } from "./LakeTextInput";
|
|
@@ -19,7 +20,9 @@ const styles = StyleSheet.create({
|
|
|
19
20
|
list: {
|
|
20
21
|
marginVertical: spacings[8],
|
|
21
22
|
},
|
|
22
|
-
flatList: {
|
|
23
|
+
flatList: {
|
|
24
|
+
scrollBehavior: "smooth",
|
|
25
|
+
},
|
|
23
26
|
item: {
|
|
24
27
|
flexShrink: 1,
|
|
25
28
|
flexGrow: 1,
|
|
@@ -71,15 +74,9 @@ const styles = StyleSheet.create({
|
|
|
71
74
|
flexGrow: 1,
|
|
72
75
|
},
|
|
73
76
|
});
|
|
74
|
-
const getItemLayout = (_data, index) => ({
|
|
75
|
-
length: DEFAULT_ELEMENT_HEIGHT,
|
|
76
|
-
offset: DEFAULT_ELEMENT_HEIGHT * index,
|
|
77
|
-
index,
|
|
78
|
-
});
|
|
79
77
|
const LakeComboboxWithRef = ({ inputRef, value, items, itemHeight = DEFAULT_ELEMENT_HEIGHT, nbItemsDisplayed = DEFAULT_NB_SUGGESTION_DISPLAYED, ListFooterComponent, onChange, onValueChange, onSelectItem, renderItem, keyExtractor, icon, placeholder, disabled = false, emptyResultText, readOnly, id, error, hideErrors, }, forwardedRef) => {
|
|
80
78
|
const ref = useRef(null);
|
|
81
79
|
const inputTextRef = useMergeRefs(ref, inputRef);
|
|
82
|
-
const listRef = useRef(null);
|
|
83
80
|
const listContainerRef = useRef(null);
|
|
84
81
|
const blurTimeoutId = useRef(undefined);
|
|
85
82
|
const [isFetchingAdditionalInfo, setIsFetchingAdditionalInfo] = useState(false);
|
|
@@ -156,7 +153,7 @@ const LakeComboboxWithRef = ({ inputRef, value, items, itemHeight = DEFAULT_ELEM
|
|
|
156
153
|
Loading: () => _jsx(LoadingView, { style: styles.loader }),
|
|
157
154
|
Done: items => items.match({
|
|
158
155
|
Error: _ => (_jsx(Icon, { name: "error-circle-regular", size: 22, color: colors.negative[500] })),
|
|
159
|
-
Ok: items => (_jsxs(View, { ref: listContainerRef, style: styles.listContainer, children: [items.length === 0 ? (_jsxs(Box, { justifyContent: "center", alignItems: "center", style: styles.emptyList, children: [_jsx(Icon, { name: "clipboard-search-regular", size: 24, color: colors.gray.primary }), _jsx(Space, { height: 8 }), _jsx(Text, { style: styles.emptyListText, children: emptyResultText })] })) : (_jsx(FlatList, {
|
|
156
|
+
Ok: items => (_jsxs(View, { ref: listContainerRef, style: styles.listContainer, children: [items.length === 0 ? (_jsxs(Box, { justifyContent: "center", alignItems: "center", style: styles.emptyList, children: [_jsx(Icon, { name: "clipboard-search-regular", size: 24, color: colors.gray.primary }), _jsx(Space, { height: 8 }), _jsx(Text, { style: styles.emptyListText, children: emptyResultText })] })) : (_jsx(FlatList, { keyExtractor: keyExtractor, role: "list", data: items, style: styles.flatList, ItemSeparatorComponent: _jsx(Separator, {}), renderItem: ({ item }) => {
|
|
160
157
|
const rendered = renderItem(item);
|
|
161
158
|
return (_jsx(Pressable, { onFocus: handleFocus, onBlur: handleBlur, role: "listitem", onKeyDown: handleListItemKeyPress, style: ({ hovered, pressed, focused }) => [
|
|
162
159
|
styles.item,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useCallback, useRef, } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { StyleSheet, View, } from "react-native";
|
|
4
4
|
import { commonStyles } from "../constants/commonStyles";
|
|
5
5
|
import { colors, invariantColors, radii, shadows, spacings, texts, } from "../constants/design";
|
|
6
6
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
@@ -9,6 +9,7 @@ import { getFocusableElements } from "../utils/a11y";
|
|
|
9
9
|
import { isNotNullish, isNullishOrEmpty } from "../utils/nullish";
|
|
10
10
|
import { Box } from "./Box";
|
|
11
11
|
import { Fill } from "./Fill";
|
|
12
|
+
import { FlatList } from "./FlatList";
|
|
12
13
|
import { Icon } from "./Icon";
|
|
13
14
|
import { LakeText } from "./LakeText";
|
|
14
15
|
import { LakeTooltip } from "./LakeTooltip";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Array, Dict, Option } from "@swan-io/boxed";
|
|
3
3
|
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { Pressable, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
5
5
|
import { backgroundColor, colors, radii, shadows, texts } from "../constants/design";
|
|
6
6
|
import { useBoolean } from "../hooks/useBoolean";
|
|
7
7
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
@@ -9,10 +9,12 @@ import { groupBy } from "../utils/array";
|
|
|
9
9
|
import { isNotNullish, isNotNullishOrEmpty } from "../utils/nullish";
|
|
10
10
|
import { safeSplitAround } from "../utils/string";
|
|
11
11
|
import { Box } from "./Box";
|
|
12
|
+
import { FlatList } from "./FlatList";
|
|
12
13
|
import { Icon } from "./Icon";
|
|
13
14
|
import { InputError } from "./InputError";
|
|
14
15
|
import { Popover } from "./Popover";
|
|
15
16
|
import { PressableText } from "./Pressable";
|
|
17
|
+
import { SectionList } from "./SectionList";
|
|
16
18
|
import { Space } from "./Space";
|
|
17
19
|
import { Tag } from "./Tag";
|
|
18
20
|
const MAX_INPUT_HEIGHT = 120;
|
|
@@ -141,7 +143,6 @@ export const MultiSelect = memo(({ color = "gray", disabled = false, emptyResult
|
|
|
141
143
|
const shouldScrollToBottomRef = useRef(false);
|
|
142
144
|
const selectedTagListRef = useRef(null);
|
|
143
145
|
const inputRef = useRef(null);
|
|
144
|
-
const listRef = useRef(null);
|
|
145
146
|
const [visible, { open, close }] = useDisclosure(false);
|
|
146
147
|
const tint50 = colors[color][50];
|
|
147
148
|
const tint100 = colors[color][100];
|
|
@@ -198,11 +199,11 @@ export const MultiSelect = memo(({ color = "gray", disabled = false, emptyResult
|
|
|
198
199
|
(focused || visible) && styles.focused,
|
|
199
200
|
disabled && styles.disabled,
|
|
200
201
|
isNotNullish(error) && styles.errored,
|
|
201
|
-
], children: [_jsx(Box, { ref: selectedTagListRef, alignItems: "center", direction: "row", style: styles.tagsList, children: selectedTags.length > 0 ? (selectedTags.map(item => (_jsx(Tag, { color: color, onPressRemove: disabled ? undefined : () => handleRemoveItem(item), style: styles.tag, children: item.label }, item.value)))) : placeholder !== "" ? (_jsx(Text, { role: "label", numberOfLines: 1, style: styles.placeholder, children: placeholder })) : null }), _jsxs(Box, { direction: "row", alignItems: "center", style: styles.actions, children: [selectedTags.length >= 1 && !disabled && (_jsxs(_Fragment, { children: [_jsx(Pressable, { role: "button", onPress: handleClearAll, children: _jsx(Icon, { name: "dismiss-filled", color: colors.gray.primary, size: 15 }) }), _jsx(Space, { width: 8 })] })), _jsx(Icon, { color: colors.gray.primary, name: visible ? "chevron-up-filled" : "chevron-down-filled", size: 20 })] })] }), _jsx(Popover, { role: "listbox", matchReferenceWidth: true, onDismiss: close, referenceRef: inputRef, returnFocus: true, visible: visible, children: _jsx(View, { style: styles.list, children: enableGroups ? (_jsx(SectionList, { role: "listbox", "aria-multiselectable": true, keyExtractor: (item, index) => `group-field-${item.value}-${index}`,
|
|
202
|
+
], children: [_jsx(Box, { ref: selectedTagListRef, alignItems: "center", direction: "row", style: styles.tagsList, children: selectedTags.length > 0 ? (selectedTags.map(item => (_jsx(Tag, { color: color, onPressRemove: disabled ? undefined : () => handleRemoveItem(item), style: styles.tag, children: item.label }, item.value)))) : placeholder !== "" ? (_jsx(Text, { role: "label", numberOfLines: 1, style: styles.placeholder, children: placeholder })) : null }), _jsxs(Box, { direction: "row", alignItems: "center", style: styles.actions, children: [selectedTags.length >= 1 && !disabled && (_jsxs(_Fragment, { children: [_jsx(Pressable, { role: "button", onPress: handleClearAll, children: _jsx(Icon, { name: "dismiss-filled", color: colors.gray.primary, size: 15 }) }), _jsx(Space, { width: 8 })] })), _jsx(Icon, { color: colors.gray.primary, name: visible ? "chevron-up-filled" : "chevron-down-filled", size: 20 })] })] }), _jsx(Popover, { role: "listbox", matchReferenceWidth: true, onDismiss: close, referenceRef: inputRef, returnFocus: true, visible: visible, children: _jsx(View, { style: styles.list, children: enableGroups ? (_jsx(SectionList, { role: "listbox", "aria-multiselectable": true, keyExtractor: (item, index) => `group-field-${item.value}-${index}`, ListHeaderComponent: ListHeaderComponent, ListEmptyComponent: ListEmptyComponent, ListFooterComponent: _jsx(Space, { height: 16 }), sections: sections, renderSectionHeader: ({ title, data }) => (_jsxs(Pressable, { role: "listitem", onPress: () => handleSelectGroup(data), style: ({ hovered, pressed, focused }) => [
|
|
202
203
|
styles.groupTitleBase,
|
|
203
204
|
(hovered || focused) && { backgroundColor: tint50 },
|
|
204
205
|
pressed && { backgroundColor: tint100 },
|
|
205
|
-
], children: [_jsx(Text, { numberOfLines: 1, style: styles.groupTitle, children: title }), isNotNullish(renderTagGroup) && (_jsx(Tag, { color: color, children: renderTagGroup(data) }))] })), renderItem: ({ item }) => (_jsx(LineItem, { color: color, filter: filter, item: item, handleSelectItem: handleSelectItem, style: styles.lineInGroup })) })) : (_jsx(FlatList, {
|
|
206
|
+
], children: [_jsx(Text, { numberOfLines: 1, style: styles.groupTitle, children: title }), isNotNullish(renderTagGroup) && (_jsx(Tag, { color: color, children: renderTagGroup(data) }))] })), renderItem: ({ item }) => (_jsx(LineItem, { color: color, filter: filter, item: item, handleSelectItem: handleSelectItem, style: styles.lineInGroup })) })) : (_jsx(FlatList, { role: "list", data: filteredItems, keyExtractor: item => `field-${item.value}`, ListHeaderComponent: ListHeaderComponent, ListEmptyComponent: ListEmptyComponent, ListFooterComponent: _jsx(Space, { height: 8 }), renderItem: ({ item }) => (_jsx(LineItem, { color: color, filter: filter, item: item, handleSelectItem: handleSelectItem })) })) }) }), _jsx(InputError, { message: error })] }));
|
|
206
207
|
});
|
|
207
208
|
const LineItem = ({ item, color, filter, handleSelectItem, style }) => {
|
|
208
209
|
const { label, disabled = false } = item;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ForwardedRef, ReactNode } from "react";
|
|
2
|
+
import { ScrollView, ScrollViewProps, StyleProp, ViewStyle, WebRole } from "react-native";
|
|
3
|
+
import { ListRenderItemInfo } from "./FlatList";
|
|
4
|
+
export type SectionListRef = ScrollView;
|
|
5
|
+
type Section<T> = {
|
|
6
|
+
title: string;
|
|
7
|
+
data: T[];
|
|
8
|
+
};
|
|
9
|
+
type Props<T> = {
|
|
10
|
+
ItemSeparatorComponent?: ReactNode;
|
|
11
|
+
ListEmptyComponent?: ReactNode;
|
|
12
|
+
ListFooterComponent?: ReactNode;
|
|
13
|
+
ListHeaderComponent?: ReactNode;
|
|
14
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
15
|
+
horizontal?: boolean;
|
|
16
|
+
keyExtractor: (item: T, index: number) => string;
|
|
17
|
+
onEndReached?: () => void;
|
|
18
|
+
onEndReachedThresholdPx?: number;
|
|
19
|
+
onKeyDown?: ScrollViewProps["onKeyDown"];
|
|
20
|
+
onScroll?: ScrollViewProps["onScroll"];
|
|
21
|
+
renderItem: (info: ListRenderItemInfo<T>) => ReactNode;
|
|
22
|
+
renderSectionHeader?: (section: Section<T>) => ReactNode;
|
|
23
|
+
role?: WebRole;
|
|
24
|
+
scrollEventThrottle?: number;
|
|
25
|
+
sections: Section<T>[];
|
|
26
|
+
showsScrollIndicators?: boolean;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
};
|
|
29
|
+
declare const SectionListWithRef: <T>({ ItemSeparatorComponent, ListEmptyComponent, ListFooterComponent, ListHeaderComponent, contentContainerStyle, horizontal, keyExtractor, onEndReached, onEndReachedThresholdPx, onKeyDown, onScroll, renderItem, renderSectionHeader, role, scrollEventThrottle, sections, showsScrollIndicators, style, }: Props<T>, forwardedRef: ForwardedRef<SectionListRef>) => import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare const SectionList: <T>(props: Props<T> & {
|
|
31
|
+
ref?: ForwardedRef<SectionListRef>;
|
|
32
|
+
}) => ReturnType<typeof SectionListWithRef>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, forwardRef, useEffect, useId, useRef } from "react";
|
|
3
|
+
import { ScrollView, StyleSheet, View, } from "react-native";
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
scrollTracker: {
|
|
6
|
+
position: "absolute",
|
|
7
|
+
pointerEvents: "none",
|
|
8
|
+
bottom: 0,
|
|
9
|
+
left: 0,
|
|
10
|
+
right: 0,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const SectionListWithRef = ({ ItemSeparatorComponent, ListEmptyComponent, ListFooterComponent, ListHeaderComponent, contentContainerStyle, horizontal = false, keyExtractor, onEndReached, onEndReachedThresholdPx = 200, onKeyDown, onScroll, renderItem, renderSectionHeader, role, scrollEventThrottle = 0, sections, showsScrollIndicators = true, style, }, forwardedRef) => {
|
|
14
|
+
const groupId = useId();
|
|
15
|
+
const scrollTrackerRef = useRef(null);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const element = scrollTrackerRef.current;
|
|
18
|
+
if (element != null) {
|
|
19
|
+
const observer = new IntersectionObserver(entries => {
|
|
20
|
+
entries.forEach(entry => {
|
|
21
|
+
if (entry.isIntersecting) {
|
|
22
|
+
onEndReached === null || onEndReached === void 0 ? void 0 : onEndReached();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
observer.observe(element);
|
|
27
|
+
return () => {
|
|
28
|
+
observer.unobserve(element);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// re-create an observer only on list length change
|
|
32
|
+
}, [sections.length]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
33
|
+
return (_jsxs(ScrollView, { contentContainerStyle: contentContainerStyle, horizontal: horizontal, onKeyDown: onKeyDown, onScroll: onScroll, ref: forwardedRef, role: role, scrollEventThrottle: scrollEventThrottle, showsHorizontalScrollIndicator: showsScrollIndicators, showsVerticalScrollIndicator: showsScrollIndicators, style: style, children: [ListHeaderComponent, sections.length > 0 ? (_jsxs(View, { children: [sections.map(section => (_jsxs(Fragment, { children: [renderSectionHeader === null || renderSectionHeader === void 0 ? void 0 : renderSectionHeader(section), section.data.map((item, index) => (_jsxs(Fragment, { children: [index !== 0 && ItemSeparatorComponent, renderItem({ item, index })] }, keyExtractor(item, index))))] }, `group-${groupId}-${section.title}`))), _jsx(View, { ref: scrollTrackerRef, style: [styles.scrollTracker, { height: onEndReachedThresholdPx }] })] })) : (ListEmptyComponent), ListFooterComponent] }));
|
|
34
|
+
};
|
|
35
|
+
export const SectionList = forwardRef(SectionListWithRef);
|
|
@@ -3,12 +3,12 @@ export type Item<T extends string> = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
id: T;
|
|
5
5
|
icon?: ReactNode;
|
|
6
|
+
activeIcon?: ReactNode;
|
|
6
7
|
};
|
|
7
8
|
type Props<T extends string> = {
|
|
8
|
-
mode?: "desktop" | "mobile";
|
|
9
9
|
selected: T;
|
|
10
10
|
items: ReadonlyArray<Item<T>>;
|
|
11
11
|
onValueChange: (value: T) => void;
|
|
12
12
|
};
|
|
13
|
-
export declare const SegmentedControl: <T extends string>({
|
|
13
|
+
export declare const SegmentedControl: <T extends string>({ selected, items, onValueChange, }: Props<T>) => import("react/jsx-runtime").JSX.Element;
|
|
14
14
|
export {};
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { P, match } from "ts-pattern";
|
|
3
5
|
import { backgroundColor, colors, radii, spacings, texts } from "../constants/design";
|
|
4
6
|
import { isNotNullish } from "../utils/nullish";
|
|
7
|
+
import { BottomPanel } from "./BottomPanel";
|
|
5
8
|
import { Box } from "./Box";
|
|
9
|
+
import { Icon } from "./Icon";
|
|
10
|
+
import { LakeButton } from "./LakeButton";
|
|
6
11
|
import { LakeText } from "./LakeText";
|
|
7
12
|
import { Pressable } from "./Pressable";
|
|
13
|
+
import { ResponsiveContainer } from "./ResponsiveContainer";
|
|
8
14
|
import { Space } from "./Space";
|
|
9
15
|
const styles = StyleSheet.create({
|
|
10
16
|
container: {
|
|
17
|
+
padding: spacings[4],
|
|
11
18
|
backgroundColor: colors.gray[50],
|
|
12
19
|
borderRadius: radii[8],
|
|
13
|
-
padding: spacings[4],
|
|
14
20
|
},
|
|
15
21
|
selectedItemIndicator: {
|
|
16
22
|
position: "absolute",
|
|
@@ -25,15 +31,44 @@ const styles = StyleSheet.create({
|
|
|
25
31
|
borderRadius: radii[4],
|
|
26
32
|
backgroundColor: backgroundColor.accented,
|
|
27
33
|
},
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
itemMobile: {
|
|
35
|
+
backgroundColor: backgroundColor.accented,
|
|
36
|
+
borderRadius: radii[4],
|
|
37
|
+
padding: spacings[16],
|
|
38
|
+
flexDirection: "row",
|
|
39
|
+
height: 60,
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
justifyContent: "center",
|
|
30
42
|
flexGrow: 1,
|
|
31
|
-
|
|
43
|
+
},
|
|
44
|
+
dropdownItem: {
|
|
45
|
+
backgroundColor: backgroundColor.accented,
|
|
46
|
+
borderRadius: radii[4],
|
|
47
|
+
padding: spacings[16],
|
|
48
|
+
flexDirection: "row",
|
|
49
|
+
height: 60,
|
|
32
50
|
alignItems: "center",
|
|
51
|
+
flexGrow: 1,
|
|
52
|
+
},
|
|
53
|
+
dropdownItemSelected: {
|
|
54
|
+
backgroundColor: colors.gray[50],
|
|
55
|
+
borderRadius: radii[4],
|
|
33
56
|
padding: spacings[16],
|
|
34
|
-
flexDirection: "
|
|
57
|
+
flexDirection: "row",
|
|
58
|
+
height: 60,
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
flexGrow: 1,
|
|
61
|
+
},
|
|
62
|
+
button: {
|
|
63
|
+
width: 60,
|
|
64
|
+
height: 60,
|
|
35
65
|
},
|
|
36
66
|
itemDesktop: {
|
|
67
|
+
flexBasis: "0%",
|
|
68
|
+
flexGrow: 1,
|
|
69
|
+
flexShrink: 1,
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
padding: spacings[16],
|
|
37
72
|
flexDirection: "row",
|
|
38
73
|
justifyContent: "center",
|
|
39
74
|
},
|
|
@@ -41,28 +76,43 @@ const styles = StyleSheet.create({
|
|
|
41
76
|
userSelect: "none",
|
|
42
77
|
lineHeight: texts.regular.fontSize,
|
|
43
78
|
},
|
|
44
|
-
selectedItemMobile: {
|
|
45
|
-
right: 0,
|
|
46
|
-
},
|
|
47
79
|
selectedItemDesktop: {
|
|
48
80
|
bottom: 0,
|
|
49
81
|
},
|
|
50
82
|
});
|
|
51
|
-
export const SegmentedControl = ({
|
|
83
|
+
export const SegmentedControl = ({ selected, items, onValueChange, }) => {
|
|
52
84
|
const selectedItemIndex = items.findIndex(item => item.id === selected);
|
|
53
|
-
|
|
85
|
+
const selectedItem = items.find(item => item.id === selected);
|
|
86
|
+
const [pressed, setPressed] = useState(false);
|
|
87
|
+
return (_jsx(ResponsiveContainer, { style: styles.container, children: ({ small }) => small ? (_jsxs(Box, { direction: "row", alignItems: "center", justifyContent: "spaceBetween", children: [_jsxs(Pressable, { style: styles.itemMobile, onPress: () => {
|
|
88
|
+
setPressed(true);
|
|
89
|
+
}, children: [isNotNullish(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) &&
|
|
90
|
+
match(selectedItem)
|
|
91
|
+
.with({ icon: P.nonNullable, activeIcon: P.nonNullable }, () => selectedItem.activeIcon)
|
|
92
|
+
.with({ icon: P.nonNullable }, () => selectedItem.icon)
|
|
93
|
+
.otherwise(() => null), _jsx(Space, { height: 8, width: 12 }), _jsx(LakeText, { color: colors.gray[900], numberOfLines: 1, variant: "regular", style: styles.itemText, children: selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.name })] }), _jsx(BottomPanel, { visible: pressed === true, onPressClose: () => {
|
|
94
|
+
setPressed(false);
|
|
95
|
+
}, children: items.map(item => (_jsxs(Box, { direction: "row", children: [_jsxs(Pressable, { style: (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id
|
|
96
|
+
? styles.dropdownItemSelected
|
|
97
|
+
: styles.dropdownItem, onPress: () => {
|
|
98
|
+
onValueChange(item.id);
|
|
99
|
+
setPressed(false);
|
|
100
|
+
}, children: [isNotNullish(item.icon) &&
|
|
101
|
+
match(item)
|
|
102
|
+
.with({ icon: P.nonNullable, activeIcon: P.nonNullable }, () => (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id ? selectedItem.activeIcon : item.icon)
|
|
103
|
+
.with({ icon: P.nonNullable }, () => item.icon)
|
|
104
|
+
.otherwise(() => null), _jsx(Space, { height: 8, width: 12 }), _jsx(LakeText, { color: colors.gray[900], numberOfLines: 1, variant: "regular", style: styles.itemText, children: item.name })] }, item.id), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id && (_jsx(Box, { justifyContent: "center", style: { paddingHorizontal: spacings[24], backgroundColor: colors.gray[50] }, children: _jsx(Icon, { size: 16, name: "lake-check", color: colors.positive[500] }) }))] }))) }), _jsx(LakeButton, { mode: "tertiary", style: styles.button, size: "small", icon: "more-horizontal-filled", onPress: () => setPressed(true), ariaLabel: "Previous" })] })) : (_jsxs(Box, { direction: "row", children: [_jsx(View, { role: "none", style: [
|
|
54
105
|
styles.selectedItemIndicator,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
: {
|
|
62
|
-
width: `${(1 / items.length) * 100}%`,
|
|
63
|
-
transform: `translateX(${selectedItemIndex * 100}%)`,
|
|
64
|
-
},
|
|
65
|
-
] }), items.map(item => (_jsxs(Pressable, { style: [styles.item, mode === "desktop" && styles.itemDesktop], onPress: () => {
|
|
106
|
+
styles.selectedItemDesktop,
|
|
107
|
+
{
|
|
108
|
+
width: `${(1 / items.length) * 100}%`,
|
|
109
|
+
transform: `translateX(${selectedItemIndex * 100}%)`,
|
|
110
|
+
},
|
|
111
|
+
] }), items.map(item => (_jsxs(Pressable, { style: styles.itemDesktop, onPress: () => {
|
|
66
112
|
onValueChange(item.id);
|
|
67
|
-
}, children: [
|
|
113
|
+
}, children: [_jsxs(_Fragment, { children: [isNotNullish(item.icon) &&
|
|
114
|
+
match(item)
|
|
115
|
+
.with({ icon: P.nonNullable, activeIcon: P.nonNullable }, () => (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id ? selectedItem.activeIcon : item.icon)
|
|
116
|
+
.with({ icon: P.nonNullable }, () => item.icon)
|
|
117
|
+
.otherwise(() => null), _jsx(Space, { height: 8, width: 12 })] }), _jsx(LakeText, { color: colors.gray[900], numberOfLines: 1, variant: "regular", style: styles.itemText, children: item.name })] }, item.id)))] })) }));
|
|
68
118
|
};
|