@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/lake",
3
- "version": "8.4.6",
3
+ "version": "8.5.1",
4
4
  "engines": {
5
5
  "node": ">=20.9.0",
6
6
  "yarn": "^1.22.0"
@@ -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 { FlatList, Pressable, StyleSheet, View } from "react-native";
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 { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
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 { FlatList, Pressable, StyleSheet, Text, View, } from "react-native";
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: { scrollBehavior: "smooth" },
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, { ref: listRef, keyExtractor: keyExtractor, getItemLayout: getItemLayout, role: "list", data: items, style: styles.flatList, ItemSeparatorComponent: Separator, renderItem: ({ item }) => {
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 { FlatList, StyleSheet, View, } from "react-native";
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 { FlatList, Pressable, SectionList, StyleSheet, Text, TextInput, View, } from "react-native";
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}`, extraData: filter, ListHeaderComponent: ListHeaderComponent, ListEmptyComponent: ListEmptyComponent, ListFooterComponent: _jsx(Space, { height: 16 }), sections: sections, renderSectionHeader: ({ section: { title, data }, }) => (_jsxs(Pressable, { role: "listitem", onPress: () => handleSelectGroup(data), style: ({ hovered, pressed, focused }) => [
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, { ref: listRef, role: "list", data: filteredItems, extraData: filter, 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
+ ], 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>({ mode, selected, items, onValueChange, }: Props<T>) => import("react/jsx-runtime").JSX.Element;
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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
- item: {
29
- flexBasis: "0%",
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
- flexShrink: 1,
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: "column",
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 = ({ mode = "desktop", selected, items, onValueChange, }) => {
83
+ export const SegmentedControl = ({ selected, items, onValueChange, }) => {
52
84
  const selectedItemIndex = items.findIndex(item => item.id === selected);
53
- return (_jsx(Box, { style: styles.container, children: _jsxs(Box, { direction: mode === "mobile" ? "column" : "row", children: [_jsx(View, { role: "none", style: [
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
- mode === "mobile" ? styles.selectedItemMobile : styles.selectedItemDesktop,
56
- mode === "mobile"
57
- ? {
58
- height: `${(1 / items.length) * 100}%`,
59
- transform: `translateY(${selectedItemIndex * 100}%)`,
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: [isNotNullish(item.icon) && (_jsxs(_Fragment, { children: [item.icon, _jsx(Space, { height: 8, width: 12 })] })), _jsx(LakeText, { color: colors.gray[900], numberOfLines: 1, variant: "regular", style: styles.itemText, children: item.name })] }, item.id)))] }) }));
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
  };