@swan-io/lake 2.7.7 → 2.7.8
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
|
@@ -5,11 +5,13 @@ import { backgroundColor, colors, spacings } from "../constants/design";
|
|
|
5
5
|
import { typography } from "../constants/typography";
|
|
6
6
|
import { useMergeRefs } from "../hooks/useMergeRefs";
|
|
7
7
|
import { getFocusableElements } from "../utils/a11y";
|
|
8
|
+
import { isNotEmpty } from "../utils/nullish";
|
|
8
9
|
import { Box } from "./Box";
|
|
9
10
|
import { Icon } from "./Icon";
|
|
10
11
|
import { LakeTextInput } from "./LakeTextInput";
|
|
11
12
|
import { LoadingView } from "./LoadingView";
|
|
12
13
|
import { Popover } from "./Popover";
|
|
14
|
+
import { Separator } from "./Separator";
|
|
13
15
|
import { Space } from "./Space";
|
|
14
16
|
const DEFAULT_ELEMENT_HEIGHT = 64;
|
|
15
17
|
const DEFAULT_NB_SUGGESTION_DISPLAYED = 3.5;
|
|
@@ -27,9 +29,6 @@ const styles = StyleSheet.create({
|
|
|
27
29
|
transitionProperty: "background-color",
|
|
28
30
|
transitionDuration: "200ms",
|
|
29
31
|
outlineStyle: "none",
|
|
30
|
-
borderColor: colors.gray[100],
|
|
31
|
-
borderStyle: "solid",
|
|
32
|
-
borderBottomWidth: 1,
|
|
33
32
|
justifyContents: "center",
|
|
34
33
|
},
|
|
35
34
|
hoveredItem: {
|
|
@@ -85,10 +84,14 @@ const LakeComboboxWithRef = ({ inputRef, value, items, itemHeight = DEFAULT_ELEM
|
|
|
85
84
|
const listRef = useRef(null);
|
|
86
85
|
const listContainerRef = useRef(null);
|
|
87
86
|
const blurTimeoutId = useRef(undefined);
|
|
88
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
89
87
|
const [isFetchingAdditionalInfo, setIsFetchingAdditionalInfo] = useState(false);
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
// The Combobox has two distinct closed states: "closed" and "dismissed"
|
|
89
|
+
// When it's "closed", it will open on input focus or text change
|
|
90
|
+
// When it's "dismissed", it will NOT open on input focus, but will on text change
|
|
91
|
+
const [state, setState] = useState("closed");
|
|
92
|
+
const open = useCallback(() => setState("opened"), []);
|
|
93
|
+
const close = useCallback(() => setState("closed"), []);
|
|
94
|
+
const dismiss = useCallback(() => setState("dismissed"), []);
|
|
92
95
|
useImperativeHandle(externalRef, () => {
|
|
93
96
|
return {
|
|
94
97
|
open,
|
|
@@ -130,24 +133,30 @@ const LakeComboboxWithRef = ({ inputRef, value, items, itemHeight = DEFAULT_ELEM
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
}, []);
|
|
136
|
+
const handleChangeText = useCallback((value) => {
|
|
137
|
+
onValueChange(value);
|
|
138
|
+
setState(isNotEmpty(value) ? "opened" : "closed");
|
|
139
|
+
}, [onValueChange]);
|
|
133
140
|
const handleFocus = useCallback(() => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
if (isNotEmpty(value)) {
|
|
142
|
+
window.clearTimeout(blurTimeoutId.current);
|
|
143
|
+
blurTimeoutId.current = window.setTimeout(() => {
|
|
144
|
+
setState(prevState => (prevState === "closed" ? "opened" : prevState));
|
|
145
|
+
}, 100);
|
|
146
|
+
}
|
|
147
|
+
}, [value]);
|
|
139
148
|
const handleBlur = useCallback(() => {
|
|
140
149
|
window.clearTimeout(blurTimeoutId.current);
|
|
141
150
|
blurTimeoutId.current = window.setTimeout(() => {
|
|
142
|
-
|
|
151
|
+
setState("dismissed");
|
|
143
152
|
}, 100);
|
|
144
153
|
}, []);
|
|
145
|
-
return (_jsxs(View, { children: [_jsx(LakeTextInput, { containerRef: inputTextRef, style: styles.input, ariaExpanded:
|
|
154
|
+
return (_jsxs(View, { children: [_jsx(LakeTextInput, { containerRef: inputTextRef, style: styles.input, ariaExpanded: state === "opened", ariaControls: state === "opened" ? suggestionsId : "", enterKeyHint: "search", icon: icon, role: "combobox", placeholder: placeholder, value: value, disabled: disabled, error: error, hideErrors: hideErrors, onChangeText: handleChangeText, onChange: onChange, onFocus: handleFocus, onBlur: handleBlur, onKeyPress: handleKeyPress, id: id, readOnly: readOnly }), _jsx(Popover, { id: suggestionsId, role: "listbox", matchReferenceWidth: true, onEscapeKey: dismiss, referenceRef: ref, autoFocus: false, returnFocus: true, visible: state === "opened" && !items.isNotAsked(), underlay: false, forcedMode: "Dropdown", children: _jsx(View, { style: [styles.list, { maxHeight: itemHeight * nbItemsDisplayed }], children: items.match({
|
|
146
155
|
NotAsked: () => null,
|
|
147
156
|
Loading: () => _jsx(LoadingView, { style: styles.loader }),
|
|
148
157
|
Done: items => items.match({
|
|
149
158
|
Error: _ => (_jsx(Icon, { name: "error-circle-regular", size: 22, color: colors.negative[500] })),
|
|
150
|
-
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, renderItem: ({ item }) => {
|
|
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 }) => {
|
|
151
160
|
const rendered = renderItem(item);
|
|
152
161
|
return (_jsx(Pressable, { onFocus: handleFocus, onBlur: handleBlur, role: "listitem", onKeyDown: handleListItemKeyPress, style: ({ hovered, pressed, focused }) => [
|
|
153
162
|
styles.item,
|
|
@@ -160,7 +169,7 @@ const LakeComboboxWithRef = ({ inputRef, value, items, itemHeight = DEFAULT_ELEM
|
|
|
160
169
|
setIsFetchingAdditionalInfo(true);
|
|
161
170
|
void Promise.resolve(onSelectItem(item)).finally(() => {
|
|
162
171
|
setIsFetchingAdditionalInfo(false);
|
|
163
|
-
|
|
172
|
+
dismiss();
|
|
164
173
|
});
|
|
165
174
|
}, children: isReactText(rendered) ? (_jsx(Text, { numberOfLines: 1, style: styles.itemText, children: rendered })) : (rendered) }));
|
|
166
175
|
} })), ListFooterComponent, isFetchingAdditionalInfo ? (_jsxs(View, { style: styles.loaderAdditional, children: [_jsx(View, { style: styles.loaderAdditionalUnderlay }), _jsx(LoadingView, {})] })) : null] })),
|
|
@@ -18,10 +18,19 @@ const variants = StyleSheet.create({
|
|
|
18
18
|
smallMedium: texts.smallMedium,
|
|
19
19
|
smallRegular: texts.smallRegular,
|
|
20
20
|
});
|
|
21
|
+
const styles = StyleSheet.create({
|
|
22
|
+
tooltip: {
|
|
23
|
+
width: "100%",
|
|
24
|
+
},
|
|
25
|
+
ellipsis: {
|
|
26
|
+
overflow: "hidden",
|
|
27
|
+
textOverflow: "ellipsis",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
21
30
|
export const LakeText = forwardRef(({ align = "left", children, color, style, userSelect, variant = "regular", tooltip, ...props }, forwardedRef) => (_jsx(Text, { ref: forwardedRef, style: [
|
|
22
31
|
variants[variant],
|
|
23
32
|
alignments[align],
|
|
24
33
|
isNotNullish(color) && { color },
|
|
25
34
|
isNotNullish(userSelect) && { userSelect },
|
|
26
35
|
style,
|
|
27
|
-
], ...props, children: tooltip ? (_jsx(LakeTooltip, { ...tooltip, children: _jsx(Text, { children: children }) })) : (children) })));
|
|
36
|
+
], ...props, children: tooltip ? (_jsx(LakeTooltip, { containerStyle: styles.tooltip, ...tooltip, children: _jsx(Text, { style: styles.ellipsis, children: children }) })) : (children) })));
|
|
@@ -9,7 +9,8 @@ type Props = {
|
|
|
9
9
|
describedBy?: string;
|
|
10
10
|
matchReferenceWidth?: boolean;
|
|
11
11
|
matchReferenceMinWidth?: boolean;
|
|
12
|
-
onDismiss
|
|
12
|
+
onDismiss?: () => void;
|
|
13
|
+
onEscapeKey?: () => void;
|
|
13
14
|
referenceRef: RefObject<unknown>;
|
|
14
15
|
returnFocus?: boolean;
|
|
15
16
|
autoFocus?: boolean;
|
|
@@ -4,6 +4,7 @@ import { Pressable, ScrollView, StyleSheet, View, } from "react-native";
|
|
|
4
4
|
import { match, P } from "ts-pattern";
|
|
5
5
|
import { animations, backgroundColor, radii, shadows } from "../constants/design";
|
|
6
6
|
import { useResponsive } from "../hooks/useResponsive";
|
|
7
|
+
import { noop } from "../utils/function";
|
|
7
8
|
import { BottomPanel } from "./BottomPanel";
|
|
8
9
|
import { FocusTrap } from "./FocusTrap";
|
|
9
10
|
import { Portal } from "./Portal";
|
|
@@ -65,7 +66,7 @@ const animation = {
|
|
|
65
66
|
],
|
|
66
67
|
};
|
|
67
68
|
export const VIEWPORT_WIDTH_THRESHOLD = 600;
|
|
68
|
-
export const Popover = memo(({ children, id, label, role = "dialog", describedBy, matchReferenceWidth = false, matchReferenceMinWidth = false, onDismiss, referenceRef, returnFocus = true, autoFocus = true, visible, underlay = true, safetyMargin = 8, forcedMode, }) => {
|
|
69
|
+
export const Popover = memo(({ children, id, label, role = "dialog", describedBy, matchReferenceWidth = false, matchReferenceMinWidth = false, onDismiss = noop, onEscapeKey = onDismiss, referenceRef, returnFocus = true, autoFocus = true, visible, underlay = true, safetyMargin = 8, forcedMode, }) => {
|
|
69
70
|
const [rootElement, setRootElement] = useState(null);
|
|
70
71
|
const underlayRef = useRef(null);
|
|
71
72
|
const { desktop } = useResponsive(VIEWPORT_WIDTH_THRESHOLD);
|
|
@@ -147,5 +148,5 @@ export const Popover = memo(({ children, id, label, role = "dialog", describedBy
|
|
|
147
148
|
{
|
|
148
149
|
justifyContent: availableSpaceAbove > availableSpaceBelow ? FLEX_END : FLEX_START,
|
|
149
150
|
},
|
|
150
|
-
], id: id, role: role, "aria-describedby": describedBy, "aria-label": label, children: _jsx(FocusTrap, { focusLock: true, returnFocus: returnFocus, autoFocus: autoFocus, onEscapeKey:
|
|
151
|
+
], id: id, role: role, "aria-describedby": describedBy, "aria-label": label, children: _jsx(FocusTrap, { focusLock: true, returnFocus: returnFocus, autoFocus: autoFocus, onEscapeKey: onEscapeKey, onClickOutside: underlay ? undefined : onClickOutside, children: _jsx(Pressable, { tabIndex: -1, onPress: onPress, style: styles.defaultCursor, children: typeof children == "function" ? children({ mode: "dropdown" }) : children }) }) })) : null] })) : null }) }));
|
|
151
152
|
});
|