@swan-io/lake 13.6.6 → 13.7.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
package/src/assets/main.css
CHANGED
|
@@ -6,6 +6,7 @@ export type Item<V> = {
|
|
|
6
6
|
name: string;
|
|
7
7
|
value: V;
|
|
8
8
|
icon?: ReactNode;
|
|
9
|
+
searchTerms?: string[];
|
|
9
10
|
};
|
|
10
11
|
export type SelectProps<V, T extends Item<V> = Item<V>> = {
|
|
11
12
|
ref?: Ref<View>;
|
|
@@ -34,5 +35,7 @@ export type SelectProps<V, T extends Item<V> = Item<V>> = {
|
|
|
34
35
|
error?: string;
|
|
35
36
|
readOnly?: boolean;
|
|
36
37
|
style?: StyleProp<ViewStyle>;
|
|
38
|
+
hasSearch?: boolean;
|
|
39
|
+
searchPlaceholder?: string;
|
|
37
40
|
};
|
|
38
|
-
export declare const LakeSelect: <V, T extends Item<V> = Item<V>>({ ref, title, items, valueStyle, size, color, disabled, mode, placeholder, readOnly, id, matchReferenceWidth, value, error, hideErrors, icon, onValueChange, disabledItems, renderItem, PopoverFooter, style, }: SelectProps<V, T>) => import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export declare const LakeSelect: <V, T extends Item<V> = Item<V>>({ ref, title, items, valueStyle, size, color, disabled, mode, placeholder, readOnly, id, matchReferenceWidth, value, error, hideErrors, icon, onValueChange, disabledItems, renderItem, PopoverFooter, style, hasSearch, searchPlaceholder, }: SelectProps<V, T>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { useCallback, useRef } from "react";
|
|
3
|
-
import { StyleSheet, View, } from "react-native";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { StyleSheet, TextInput, View, } from "react-native";
|
|
4
4
|
import { commonStyles } from "../constants/commonStyles";
|
|
5
|
-
import { colors, invariantColors, radii, shadows, spacings, texts, } from "../constants/design";
|
|
5
|
+
import { backgroundColor, colors, invariantColors, radii, shadows, spacings, texts, } from "../constants/design";
|
|
6
|
+
import { useBoolean } from "../hooks/useBoolean";
|
|
6
7
|
import { useDisclosure } from "../hooks/useDisclosure";
|
|
7
8
|
import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
8
9
|
import { getFocusableElements } from "../utils/a11y";
|
|
10
|
+
import { setHighlightApi } from "../utils/highlights";
|
|
9
11
|
import { isNotNullish, isNullishOrEmpty } from "../utils/nullish";
|
|
10
12
|
import { Box } from "./Box";
|
|
11
13
|
import { Fill } from "./Fill";
|
|
@@ -111,9 +113,37 @@ const styles = StyleSheet.create({
|
|
|
111
113
|
errorContainer: {
|
|
112
114
|
borderColor: colors.negative[500],
|
|
113
115
|
},
|
|
116
|
+
filterContainer: {
|
|
117
|
+
flexGrow: 1,
|
|
118
|
+
flexShrink: 1,
|
|
119
|
+
margin: 16,
|
|
120
|
+
},
|
|
121
|
+
filterInput: {
|
|
122
|
+
...texts.regular,
|
|
123
|
+
backgroundColor: backgroundColor.accented,
|
|
124
|
+
borderColor: colors.gray[100],
|
|
125
|
+
borderRadius: 4,
|
|
126
|
+
borderWidth: 1,
|
|
127
|
+
flexGrow: 1,
|
|
128
|
+
flexShrink: 1,
|
|
129
|
+
height: 40,
|
|
130
|
+
outlineStyle: "none",
|
|
131
|
+
paddingHorizontal: 12,
|
|
132
|
+
paddingLeft: 40,
|
|
133
|
+
placeholderTextColor: colors.gray[300],
|
|
134
|
+
},
|
|
135
|
+
filterFocused: {
|
|
136
|
+
borderColor: colors.gray[200],
|
|
137
|
+
},
|
|
138
|
+
searchIcon: {
|
|
139
|
+
position: "absolute",
|
|
140
|
+
left: 14,
|
|
141
|
+
},
|
|
114
142
|
});
|
|
115
|
-
export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "current", disabled = false, mode = "normal", placeholder, readOnly = false, id, matchReferenceWidth = true, value, error, hideErrors = false, icon, onValueChange, disabledItems = [], renderItem, PopoverFooter, style, }) => {
|
|
143
|
+
export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "current", disabled = false, mode = "normal", placeholder, readOnly = false, id, matchReferenceWidth = true, value, error, hideErrors = false, icon, onValueChange, disabledItems = [], renderItem, PopoverFooter, style, hasSearch = false, searchPlaceholder, }) => {
|
|
116
144
|
var _a;
|
|
145
|
+
const [filter, setFilter] = useState("");
|
|
146
|
+
const [filterFocused, setFilterFocused] = useBoolean(false);
|
|
117
147
|
const inputRef = useRef(null);
|
|
118
148
|
const listRef = useRef(null);
|
|
119
149
|
const typingTimeoutRef = useRef(undefined);
|
|
@@ -152,6 +182,30 @@ export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "curre
|
|
|
152
182
|
}, 300);
|
|
153
183
|
}, [items, onValueChange, visible]);
|
|
154
184
|
const name = (_a = itemValue === null || itemValue === void 0 ? void 0 : itemValue.name) !== null && _a !== void 0 ? _a : value;
|
|
185
|
+
const filteredItems = useMemo(() => {
|
|
186
|
+
if (isNullishOrEmpty(filter)) {
|
|
187
|
+
return items;
|
|
188
|
+
}
|
|
189
|
+
const lowerFilter = filter.toLowerCase();
|
|
190
|
+
return items.filter(item => {
|
|
191
|
+
var _a;
|
|
192
|
+
return item.name.toLowerCase().includes(lowerFilter) ||
|
|
193
|
+
((_a = item.searchTerms) === null || _a === void 0 ? void 0 : _a.some(term => term.toLowerCase() === lowerFilter));
|
|
194
|
+
});
|
|
195
|
+
}, [items, filter]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!visible) {
|
|
198
|
+
setFilter("");
|
|
199
|
+
}
|
|
200
|
+
}, [visible]);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
var _a;
|
|
203
|
+
if (!hasSearch) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
setHighlightApi(filter, (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.element);
|
|
207
|
+
}, [filter, hasSearch]);
|
|
208
|
+
const ListHeaderComponent = useMemo(() => (_jsxs(Box, { direction: "row", alignItems: "center", style: styles.filterContainer, children: [_jsx(TextInput, { autoComplete: "off", inputMode: "search", multiline: false, rows: 1, onChangeText: filterValue => setFilter(filterValue), placeholder: searchPlaceholder, value: filter, onFocus: setFilterFocused.on, onBlur: setFilterFocused.off, style: [styles.filterInput, filterFocused && styles.filterFocused] }), _jsx(Icon, { name: "search-filled", color: colors[color].primary, size: 20, style: styles.searchIcon })] })), [filter, filterFocused, setFilterFocused, searchPlaceholder, color]);
|
|
155
209
|
return (_jsxs(View, { style: commonStyles.fill, children: [_jsx(Pressable, { id: id, ref: mergedRef, "aria-haspopup": "listbox", role: "button", "aria-expanded": visible, disabled: readOnly || disabled, style: ({ focused, hovered, pressed }) => [
|
|
156
210
|
mode === "normal" ? styles.normal : styles.borderless,
|
|
157
211
|
size === "small" && styles.small,
|
|
@@ -174,7 +228,7 @@ export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "curre
|
|
|
174
228
|
styles.itemText,
|
|
175
229
|
styles.selectPlaceholder,
|
|
176
230
|
isSmall && styles.selectSmallPlaceholder,
|
|
177
|
-
], children: placeholder !== null && placeholder !== void 0 ? placeholder : " " }))] }), _jsx(Fill, { minWidth: 8 }), !disabled && (_jsx(Icon, { color: colors.gray[900], name: visible ? "chevron-up-filled" : "chevron-down-filled", size: 16 }))] })] })) }), !hideErrors && (_jsx(LakeText, { variant: "smallRegular", color: colors.negative[500], style: styles.errorText, children: error !== null && error !== void 0 ? error : " " })), _jsxs(Popover, { role: "listbox", matchReferenceMinWidth: matchReferenceWidth, onDismiss: close, referenceRef: inputRef, returnFocus: true, visible: visible, children: [isNotNullish(title) && (_jsxs(_Fragment, { children: [_jsx(LakeText, { variant: "semibold", color: colors.gray[900], style: styles.selectListTitle, children: title }), _jsx(Separator, {})] })), _jsx(FlatList, { role: "list", data:
|
|
231
|
+
], children: placeholder !== null && placeholder !== void 0 ? placeholder : " " }))] }), _jsx(Fill, { minWidth: 8 }), !disabled && (_jsx(Icon, { color: colors.gray[900], name: visible ? "chevron-up-filled" : "chevron-down-filled", size: 16 }))] })] })) }), !hideErrors && (_jsx(LakeText, { variant: "smallRegular", color: colors.negative[500], style: styles.errorText, children: error !== null && error !== void 0 ? error : " " })), _jsxs(Popover, { role: "listbox", matchReferenceMinWidth: matchReferenceWidth, onDismiss: close, referenceRef: inputRef, returnFocus: true, visible: visible, children: [isNotNullish(title) && (_jsxs(_Fragment, { children: [_jsx(LakeText, { variant: "semibold", color: colors.gray[900], style: styles.selectListTitle, children: title }), _jsx(Separator, {})] })), _jsx(FlatList, { role: "list", data: filteredItems, ref: listRef, contentContainerStyle: styles.listContent, onKeyDown: (event) => {
|
|
178
232
|
var _a;
|
|
179
233
|
const { key } = event.nativeEvent;
|
|
180
234
|
if (key === "ArrowDown" || key === "ArrowUp") {
|
|
@@ -186,7 +240,7 @@ export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "curre
|
|
|
186
240
|
(_a = focusableElements[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
187
241
|
}
|
|
188
242
|
}
|
|
189
|
-
}, keyExtractor: (_, index) => `select-item-${index}`, renderItem: ({ item, index }) => {
|
|
243
|
+
}, keyExtractor: (_, index) => `select-item-${index}`, ListHeaderComponent: hasSearch ? ListHeaderComponent : undefined, renderItem: ({ item, index }) => {
|
|
190
244
|
const isSelected = value === item.value;
|
|
191
245
|
const disablement = disabledItems.find(({ value }) => value === item.value);
|
|
192
246
|
const content = renderItem != null ? (renderItem(item, isSelected)) : (_jsxs(_Fragment, { children: [isNotNullish(item.icon) && (_jsxs(_Fragment, { children: [item.icon, _jsx(Space, { width: 12 })] })), _jsx(LakeText, { color: colors.gray[900], numberOfLines: 1, style: [styles.itemText, isSelected && styles.selected], children: item.name })] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setHighlightApi: (text: string, element: HTMLElement | null | undefined, minLength?: number) => (() => void) | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isNullishOrEmpty } from "./nullish";
|
|
2
|
+
const highlightName = "lake-highlight";
|
|
3
|
+
export const setHighlightApi = (text, element, minLength = 2) => {
|
|
4
|
+
var _a, _b;
|
|
5
|
+
if (!("highlights" in CSS)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (element == null || isNullishOrEmpty(text) || text.length < minLength) {
|
|
9
|
+
CSS.highlights.delete(highlightName);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const str = text.toLowerCase();
|
|
13
|
+
const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
14
|
+
const ranges = [];
|
|
15
|
+
let currentNode = treeWalker.nextNode();
|
|
16
|
+
while (currentNode != null) {
|
|
17
|
+
const text = (_b = (_a = currentNode.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "";
|
|
18
|
+
let startPos = 0;
|
|
19
|
+
while (startPos < text.length) {
|
|
20
|
+
const index = text.indexOf(str, startPos);
|
|
21
|
+
if (index === -1) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
const range = new Range();
|
|
25
|
+
range.setStart(currentNode, index);
|
|
26
|
+
range.setEnd(currentNode, index + str.length);
|
|
27
|
+
ranges.push(range);
|
|
28
|
+
startPos = index + str.length;
|
|
29
|
+
}
|
|
30
|
+
currentNode = treeWalker.nextNode();
|
|
31
|
+
}
|
|
32
|
+
if (ranges.length > 0) {
|
|
33
|
+
CSS.highlights.set(highlightName, new Highlight(...ranges));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
CSS.highlights.delete(highlightName);
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
CSS.highlights.delete(highlightName);
|
|
40
|
+
};
|
|
41
|
+
};
|