@lotics/ui 2.1.0 → 2.3.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 +2 -1
- package/src/combobox.tsx +14 -0
- package/src/drawer.tsx +98 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./tokens": "./src/tokens.ts",
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"./time_field": "./src/time_field.tsx",
|
|
120
120
|
"./date_calendar": "./src/date_calendar.tsx",
|
|
121
121
|
"./dialog": "./src/dialog.tsx",
|
|
122
|
+
"./drawer": "./src/drawer.tsx",
|
|
122
123
|
"./screen_router": "./src/screen_router.tsx",
|
|
123
124
|
"./route_matching": "./src/route_matching.ts",
|
|
124
125
|
"./menu_title": "./src/menu_title.tsx",
|
package/src/combobox.tsx
CHANGED
|
@@ -63,6 +63,14 @@ export interface ComboboxProps<T extends string = string, D = unknown> {
|
|
|
63
63
|
emptyText?: string;
|
|
64
64
|
accessibilityLabel?: string;
|
|
65
65
|
disabled?: boolean;
|
|
66
|
+
/** Single-select only: show an in-input clear ✕ whenever the input has text
|
|
67
|
+
* (a reflected selection or a typed query). Pressing it empties the input,
|
|
68
|
+
* resets the search, and fires `onClear` so the consumer can deselect. */
|
|
69
|
+
clearable?: boolean;
|
|
70
|
+
/** Fired when the clear ✕ is pressed (the input + search are reset first). */
|
|
71
|
+
onClear?: () => void;
|
|
72
|
+
/** Accessible label for the clear ✕ (default "Clear"). */
|
|
73
|
+
clearLabel?: string;
|
|
66
74
|
autoFocus?: boolean;
|
|
67
75
|
testID?: string;
|
|
68
76
|
style?: StyleProp<ViewStyle>;
|
|
@@ -106,6 +114,9 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
|
|
|
106
114
|
emptyText = "No results",
|
|
107
115
|
accessibilityLabel = "Results",
|
|
108
116
|
disabled = false,
|
|
117
|
+
clearable = false,
|
|
118
|
+
onClear,
|
|
119
|
+
clearLabel,
|
|
109
120
|
autoFocus = false,
|
|
110
121
|
testID,
|
|
111
122
|
style,
|
|
@@ -262,6 +273,9 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
|
|
|
262
273
|
testID={testID}
|
|
263
274
|
icon={multi ? undefined : "search"}
|
|
264
275
|
value={query}
|
|
276
|
+
clearable={!multi && clearable}
|
|
277
|
+
clearLabel={clearLabel}
|
|
278
|
+
onClear={onClear}
|
|
265
279
|
onChangeText={handleChangeText}
|
|
266
280
|
onFocus={() => {
|
|
267
281
|
if (!disabled) setOpen(true);
|
package/src/drawer.tsx
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { Modal, Pressable, StyleSheet, View } from "react-native";
|
|
3
|
+
import { useScreenSize } from "@lotics/ui/use_screen_size";
|
|
4
|
+
import { PortalHost } from "@lotics/ui/portal";
|
|
5
|
+
import { colors } from "@lotics/ui/colors";
|
|
6
|
+
import { Button } from "@lotics/ui/button";
|
|
7
|
+
import { Text } from "@lotics/ui/text";
|
|
8
|
+
import { useOverlayScope } from "@lotics/ui/overlay_scope";
|
|
9
|
+
|
|
10
|
+
export interface DrawerProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onOpenChange: (open: boolean) => void;
|
|
13
|
+
/** Header title. A string renders as the standard title; a node renders as-is. */
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
/** Panel width on non-small screens (number = px, or a `%` string). Full-width on small screens. */
|
|
16
|
+
width?: number | `${number}%`;
|
|
17
|
+
/**
|
|
18
|
+
* Drawer body. Fills the panel below the header as a plain flex column — lay
|
|
19
|
+
* out your own scroll area and pinned footer (e.g. a thread that scrolls above
|
|
20
|
+
* a fixed composer).
|
|
21
|
+
*/
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
testID?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A right-docked overlay panel: a scrim over the surface plus a full-height panel
|
|
28
|
+
* pinned to the right edge. Use it for secondary content that should sit beside
|
|
29
|
+
* the main surface without leaving it — a comment thread, a record side-panel —
|
|
30
|
+
* where a centered Dialog would feel heavy. Controlled via `open`/`onOpenChange`;
|
|
31
|
+
* the scrim and the header close button both dismiss it. The panel goes
|
|
32
|
+
* full-width on small screens.
|
|
33
|
+
*/
|
|
34
|
+
export function Drawer(props: DrawerProps) {
|
|
35
|
+
const { open, onOpenChange, title, width = 420, children, testID } = props;
|
|
36
|
+
const screenSize = useScreenSize();
|
|
37
|
+
useOverlayScope(open);
|
|
38
|
+
const handleClose = () => onOpenChange(false);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Modal visible={open} onRequestClose={handleClose} transparent>
|
|
42
|
+
<View style={styles.base}>
|
|
43
|
+
{/* Scrim is a sibling of the panel, so tapping the panel never closes. */}
|
|
44
|
+
<Pressable style={styles.scrim} onPress={handleClose} accessibilityLabel="Close" />
|
|
45
|
+
<View style={[styles.panel, { width: screenSize.small ? "100%" : width }]}>
|
|
46
|
+
<PortalHost>
|
|
47
|
+
<View style={styles.header}>
|
|
48
|
+
{typeof title === "string" ? (
|
|
49
|
+
<Text size="lg" weight="semibold" style={{ flex: 1 }}>
|
|
50
|
+
{title}
|
|
51
|
+
</Text>
|
|
52
|
+
) : (
|
|
53
|
+
<View style={{ flex: 1 }}>{title}</View>
|
|
54
|
+
)}
|
|
55
|
+
<Button icon="x" accessibilityLabel="Close" onPress={handleClose} />
|
|
56
|
+
</View>
|
|
57
|
+
<View testID={testID} style={styles.body}>
|
|
58
|
+
{children}
|
|
59
|
+
</View>
|
|
60
|
+
</PortalHost>
|
|
61
|
+
</View>
|
|
62
|
+
</View>
|
|
63
|
+
</Modal>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const styles = StyleSheet.create({
|
|
68
|
+
base: {
|
|
69
|
+
height: "100%",
|
|
70
|
+
width: "100%",
|
|
71
|
+
flexDirection: "row",
|
|
72
|
+
},
|
|
73
|
+
scrim: {
|
|
74
|
+
flex: 1,
|
|
75
|
+
backgroundColor: "rgba(0, 0, 0, 0.15)",
|
|
76
|
+
backdropFilter: "blur(3px)",
|
|
77
|
+
},
|
|
78
|
+
panel: {
|
|
79
|
+
height: "100%",
|
|
80
|
+
backgroundColor: colors.background,
|
|
81
|
+
borderLeftWidth: 1,
|
|
82
|
+
borderLeftColor: colors.border,
|
|
83
|
+
boxShadow: "-8px 0 24px rgba(0, 0, 0, 0.08)",
|
|
84
|
+
},
|
|
85
|
+
header: {
|
|
86
|
+
flexDirection: "row",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
gap: 8,
|
|
89
|
+
paddingLeft: 20,
|
|
90
|
+
paddingRight: 12,
|
|
91
|
+
paddingTop: 16,
|
|
92
|
+
paddingBottom: 8,
|
|
93
|
+
minHeight: 56,
|
|
94
|
+
},
|
|
95
|
+
body: {
|
|
96
|
+
flex: 1,
|
|
97
|
+
},
|
|
98
|
+
});
|