@swan-io/lake 8.4.5 → 8.5.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.4.5",
3
+ "version": "8.5.0",
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,
@@ -14,6 +14,7 @@ type Props = {
14
14
  actions?: ReactNode;
15
15
  readOnly?: boolean;
16
16
  style?: StyleProp<ViewStyle>;
17
+ description?: string;
17
18
  };
18
- export declare const LakeLabel: ({ label, optionalLabel, extra, readOnly, color, readOnlyColor, type, help, render, actions, style, }: Props) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const LakeLabel: ({ label, optionalLabel, description, extra, readOnly, color, readOnlyColor, type, help, render, actions, style, }: Props) => import("react/jsx-runtime").JSX.Element;
19
20
  export {};
@@ -17,6 +17,7 @@ const styles = StyleSheet.create({
17
17
  ...texts.medium,
18
18
  fontFamily: fonts.primary,
19
19
  color: colors.gray[700],
20
+ display: "flex",
20
21
  },
21
22
  shrink: {
22
23
  flexShrink: 1,
@@ -29,7 +30,7 @@ const Label = (props) => {
29
30
  return unstable_createElement("label", props);
30
31
  };
31
32
  const defaultLabelType = "formSmall";
32
- export const LakeLabel = ({ label, optionalLabel, extra, readOnly = false, color = "current", readOnlyColor = colors[color].primary, type = defaultLabelType, help, render, actions, style, }) => {
33
+ export const LakeLabel = ({ label, optionalLabel, description, extra, readOnly = false, color = "current", readOnlyColor = colors[color].primary, type = defaultLabelType, help, render, actions, style, }) => {
33
34
  const [id] = useState(() => uuid());
34
35
  const containerRef = useRef(null);
35
36
  const onClick = useCallback((event) => {
@@ -40,7 +41,7 @@ export const LakeLabel = ({ label, optionalLabel, extra, readOnly = false, color
40
41
  target === null || target === void 0 ? void 0 : target.focus();
41
42
  }
42
43
  }, [id]);
43
- return (_jsxs(Box, { style: [styles.container, style], direction: "row", alignItems: "center", justifyContent: "spaceBetween", children: [_jsxs(View, { style: commonStyles.fill, ref: containerRef, children: [_jsxs(Box, { direction: "row", justifyContent: "spaceBetween", alignItems: "center", children: [_jsxs(Box, { direction: "row", style: styles.shrink, children: [type === "form" || type === "formSmall" || type === "radioGroup" ? (_jsxs(Label, { onClick: onClick, htmlFor: id, style: [styles.label, readOnly && { color: readOnlyColor }], children: [label, optionalLabel != null ? (_jsxs(_Fragment, { children: [" - ", _jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: optionalLabel })] })) : null] })) : (_jsxs(LakeText, { variant: "medium", color: readOnlyColor, id: id, children: [label, optionalLabel != null ? (_jsxs(_Fragment, { children: [" - ", _jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: optionalLabel })] })) : null] })), isNotNullish(extra) && extra()] }), isNotNullish(help) && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 16 }), help] }))] }), _jsx(Space, { height: match(type)
44
+ return (_jsxs(Box, { style: [styles.container, style], direction: "row", alignItems: "center", justifyContent: "spaceBetween", children: [_jsxs(View, { style: commonStyles.fill, ref: containerRef, children: [_jsxs(Box, { direction: "row", justifyContent: "spaceBetween", alignItems: "center", children: [_jsxs(Box, { direction: "row", style: styles.shrink, children: [type === "form" || type === "formSmall" || type === "radioGroup" ? (_jsxs(Box, { style: styles.shrink, children: [_jsxs(Label, { onClick: onClick, htmlFor: id, style: [styles.label, readOnly && { color: readOnlyColor }], children: [label, optionalLabel != null ? (_jsxs(_Fragment, { children: [" - ", _jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: optionalLabel })] })) : null] }), description != null ? (_jsxs(_Fragment, { children: [_jsx(LakeText, { variant: "smallRegular", children: description }), _jsx(Space, { height: 8 })] })) : null] })) : (_jsxs(LakeText, { variant: "medium", color: readOnlyColor, id: id, children: [label, optionalLabel != null ? (_jsxs(_Fragment, { children: [" - ", _jsx(LakeText, { color: colors.gray[400], style: styles.optionalLabel, children: optionalLabel })] })) : null] })), isNotNullish(extra) && extra()] }), isNotNullish(help) && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 16 }), help] }))] }), _jsx(Space, { height: match(type)
44
45
  .returnType()
45
46
  .with("formSmall", "viewSmall", () => 4)
46
47
  .with("form", "view", () => 8)
@@ -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);