ferns-ui 0.34.0 → 0.35.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/dist/Avatar.d.ts +14 -0
- package/dist/Avatar.js +73 -8
- package/dist/Avatar.js.map +1 -1
- package/dist/Box.d.ts +1 -15
- package/dist/Box.js +185 -182
- package/dist/Box.js.map +1 -1
- package/dist/Common.d.ts +8 -5
- package/dist/IconButton.d.ts +6 -1
- package/dist/IconButton.js +12 -3
- package/dist/IconButton.js.map +1 -1
- package/dist/Switch.js +1 -1
- package/dist/Switch.js.map +1 -1
- package/dist/TableRow.js.map +1 -1
- package/dist/TextField.js +3 -3
- package/dist/TextField.js.map +1 -1
- package/dist/Tooltip.d.ts +1 -1
- package/dist/Tooltip.js +5 -0
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.js +2 -1
- package/dist/Unifier.js.map +1 -1
- package/dist/WithLabel.d.ts +3 -2
- package/dist/WithLabel.js +2 -2
- package/dist/WithLabel.js.map +1 -1
- package/package.json +3 -2
- package/src/Avatar.tsx +177 -50
- package/src/Box.tsx +109 -100
- package/src/Common.ts +9 -9
- package/src/IconButton.tsx +27 -1
- package/src/Switch.tsx +1 -1
- package/src/TableRow.tsx +1 -1
- package/src/TextField.tsx +3 -3
- package/src/Tooltip.tsx +8 -1
- package/src/Unifier.ts +2 -1
- package/src/WithLabel.tsx +4 -1
package/src/Avatar.tsx
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import {ImageResult, manipulateAsync, SaveFormat} from "expo-image-manipulator";
|
|
3
3
|
import {launchImageLibraryAsync, MediaTypeOptions} from "expo-image-picker";
|
|
4
4
|
import React, {useEffect, useState} from "react";
|
|
5
|
-
import {Image, ImageResizeMode, Text, View} from "react-native";
|
|
5
|
+
import {Image, ImageResizeMode, Platform, Text, View} from "react-native";
|
|
6
6
|
|
|
7
7
|
import {Box} from "./Box";
|
|
8
|
-
import {AllColors,
|
|
8
|
+
import {AllColors, IconName, UnsignedUpTo12} from "./Common";
|
|
9
9
|
import {Icon} from "./Icon";
|
|
10
|
+
import {isMobileDevice} from "./MediaQuery";
|
|
11
|
+
import {Tooltip} from "./Tooltip";
|
|
10
12
|
import {Unifier} from "./Unifier";
|
|
11
13
|
|
|
12
14
|
const sizes = {
|
|
@@ -17,6 +19,37 @@ const sizes = {
|
|
|
17
19
|
xl: 120,
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
const sizeIconPadding: {[id: string]: UnsignedUpTo12} = {
|
|
23
|
+
xs: 0,
|
|
24
|
+
sm: 0,
|
|
25
|
+
md: 1,
|
|
26
|
+
lg: 1,
|
|
27
|
+
xl: 2,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const statusIcons: {[id: string]: {icon: IconName; color: AllColors; label: string}} = {
|
|
31
|
+
online: {icon: "circle", color: "green", label: "Online"},
|
|
32
|
+
offline: {icon: "circle", color: "gray", label: "Offline"},
|
|
33
|
+
doNotDisturb: {icon: "minus-circle", color: "red", label: "Do Not Disturb"},
|
|
34
|
+
away: {icon: "moon", color: "orange", label: "Away"},
|
|
35
|
+
meeting: {icon: "calendar", color: "orange", label: "In a Meeting"},
|
|
36
|
+
vacation: {icon: "plane", color: "orange", label: "On Vacation"},
|
|
37
|
+
sick: {icon: "clinic-medical", color: "orange", label: "Sick"},
|
|
38
|
+
outOfOffice: {icon: "clock", color: "orange", label: "Out of Office"},
|
|
39
|
+
commuting: {icon: "car", color: "orange", label: "Commuting"},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AvatarStatus =
|
|
43
|
+
| "online"
|
|
44
|
+
| "offline"
|
|
45
|
+
| "doNotDisturb"
|
|
46
|
+
| "away"
|
|
47
|
+
| "meeting"
|
|
48
|
+
| "vacation"
|
|
49
|
+
| "sick"
|
|
50
|
+
| "outOfOffice"
|
|
51
|
+
| "commuting";
|
|
52
|
+
|
|
20
53
|
interface AvatarProps {
|
|
21
54
|
// Color for the background of the circle when no src picture is present.
|
|
22
55
|
backgroundColor?: AllColors;
|
|
@@ -67,10 +100,24 @@ interface AvatarProps {
|
|
|
67
100
|
* The image format that the image will be saved as after any edits by the expo-image-manipulator
|
|
68
101
|
*/
|
|
69
102
|
avatarImageFormat?: SaveFormat;
|
|
103
|
+
/**
|
|
104
|
+
* The status of the user to display with the avatar.
|
|
105
|
+
*/
|
|
106
|
+
status?: AvatarStatus;
|
|
107
|
+
/**
|
|
108
|
+
* If true, the status indicator will show a mobile icon instead of a dot, if status is one of
|
|
109
|
+
* "online", "away", "offline", or "doNotDisturb". Will show the normal status icon in other cases.
|
|
110
|
+
*/
|
|
111
|
+
statusMobile?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Text to show when hovering over the avatar image. Only works on web.
|
|
114
|
+
*/
|
|
115
|
+
statusText?: string;
|
|
70
116
|
}
|
|
71
117
|
|
|
72
118
|
export const Avatar = (props: AvatarProps): React.ReactElement => {
|
|
73
119
|
const [isImageLoaded, setIsImageLoaded] = useState(true);
|
|
120
|
+
const [hovered, setHovered] = useState(false);
|
|
74
121
|
const [src, setSrc] = useState(props.src ?? undefined);
|
|
75
122
|
const {
|
|
76
123
|
name,
|
|
@@ -135,56 +182,136 @@ export const Avatar = (props: AvatarProps): React.ReactElement => {
|
|
|
135
182
|
);
|
|
136
183
|
};
|
|
137
184
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
width={editAvatarImage ? width + iconSizeToNumber(size) : width}
|
|
146
|
-
>
|
|
147
|
-
{editAvatarImage && (
|
|
148
|
-
<Box bottom position="absolute" right zIndex={5} onClick={pickImage}>
|
|
149
|
-
<Icon color="black" name="edit" size={size} />
|
|
150
|
-
</Box>
|
|
151
|
-
)}
|
|
152
|
-
{src && isImageLoaded ? (
|
|
153
|
-
// TODO: Make our Image component rounding work so that we can use it for Avatar. Currently it creates an
|
|
154
|
-
// unrounded box around the Image.
|
|
155
|
-
<Image
|
|
156
|
-
resizeMode={imageFit as ImageResizeMode}
|
|
157
|
-
source={{uri: src, cache: "force-cache"}}
|
|
158
|
-
style={{
|
|
159
|
-
borderRadius: radius,
|
|
160
|
-
height,
|
|
161
|
-
width,
|
|
162
|
-
display: "flex",
|
|
163
|
-
alignItems: "center",
|
|
164
|
-
justifyContent: "center",
|
|
165
|
-
overflow: "hidden",
|
|
166
|
-
}}
|
|
167
|
-
onError={handleImageError}
|
|
168
|
-
/>
|
|
169
|
-
) : (
|
|
170
|
-
<View
|
|
171
|
-
style={{
|
|
172
|
-
height,
|
|
173
|
-
width,
|
|
174
|
-
borderRadius: radius,
|
|
175
|
-
display: "flex",
|
|
176
|
-
alignItems: "center",
|
|
177
|
-
justifyContent: "center",
|
|
178
|
-
backgroundColor: props.backgroundColor
|
|
179
|
-
? Unifier.theme[props.backgroundColor]
|
|
180
|
-
: Unifier.theme.gray,
|
|
185
|
+
const renderEditIcon = () => {
|
|
186
|
+
if (editAvatarImage && hovered && Platform.OS === "web") {
|
|
187
|
+
return (
|
|
188
|
+
<Box
|
|
189
|
+
alignItems="center"
|
|
190
|
+
dangerouslySetInlineStyle={{
|
|
191
|
+
__style: {backgroundColor: "rgba(255,255,255,0.5)", borderRadius: radius},
|
|
181
192
|
}}
|
|
193
|
+
height={height}
|
|
194
|
+
justifyContent="center"
|
|
195
|
+
position="absolute"
|
|
196
|
+
// width={width}
|
|
197
|
+
zIndex={5}
|
|
198
|
+
onClick={pickImage}
|
|
199
|
+
>
|
|
200
|
+
<Icon color="darkGray" name="edit" size={size} />
|
|
201
|
+
</Box>
|
|
202
|
+
);
|
|
203
|
+
} else if (editAvatarImage && Platform.OS !== "web") {
|
|
204
|
+
return (
|
|
205
|
+
<Box
|
|
206
|
+
bottom
|
|
207
|
+
left={Boolean(props.status)}
|
|
208
|
+
paddingX={sizeIconPadding[size]}
|
|
209
|
+
position="absolute"
|
|
210
|
+
right={!Boolean(props.status)}
|
|
211
|
+
zIndex={5}
|
|
212
|
+
onClick={pickImage}
|
|
182
213
|
>
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
<Icon color="darkGray" name="edit" size={size} />
|
|
215
|
+
</Box>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const renderStatusIcon = () => {
|
|
222
|
+
if (!props.status) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
// eslint-disable-next-line prefer-const
|
|
226
|
+
let {icon, color} = statusIcons[props.status];
|
|
227
|
+
if (
|
|
228
|
+
props.statusMobile &&
|
|
229
|
+
["online", "away", "offline", "doNotDisturb"].includes(props.status)
|
|
230
|
+
) {
|
|
231
|
+
icon = "mobile-alt";
|
|
232
|
+
}
|
|
233
|
+
if (!icon || !color) {
|
|
234
|
+
console.warn(`Avatar: Invalid status ${props.status}`);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return (
|
|
238
|
+
<Box bottom paddingX={sizeIconPadding[size]} position="absolute" right zIndex={5}>
|
|
239
|
+
<Icon color={color} name={icon} size={size} />
|
|
240
|
+
</Box>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const avatar = (
|
|
245
|
+
<Box height={height} position="relative" width={width}>
|
|
246
|
+
<Box
|
|
247
|
+
border={outline ? "white" : undefined}
|
|
248
|
+
height={height}
|
|
249
|
+
overflow="hidden"
|
|
250
|
+
position="relative"
|
|
251
|
+
rounding="circle"
|
|
252
|
+
width={width}
|
|
253
|
+
onHoverEnd={() => setHovered(false)}
|
|
254
|
+
onHoverStart={() => setHovered(true)}
|
|
255
|
+
>
|
|
256
|
+
{src && isImageLoaded ? (
|
|
257
|
+
// TODO: Make our Image component rounding work so that we can use it for Avatar. Currently it creates an
|
|
258
|
+
// unrounded box around the Image.
|
|
259
|
+
<Image
|
|
260
|
+
resizeMode={imageFit as ImageResizeMode}
|
|
261
|
+
source={{uri: src, cache: "force-cache"}}
|
|
262
|
+
style={{
|
|
263
|
+
borderRadius: radius,
|
|
264
|
+
height,
|
|
265
|
+
width,
|
|
266
|
+
display: "flex",
|
|
267
|
+
alignItems: "center",
|
|
268
|
+
justifyContent: "center",
|
|
269
|
+
overflow: "hidden",
|
|
270
|
+
}}
|
|
271
|
+
onError={handleImageError}
|
|
272
|
+
/>
|
|
273
|
+
) : (
|
|
274
|
+
<View
|
|
275
|
+
style={{
|
|
276
|
+
height,
|
|
277
|
+
width,
|
|
278
|
+
borderRadius: radius,
|
|
279
|
+
display: "flex",
|
|
280
|
+
alignItems: "center",
|
|
281
|
+
justifyContent: "center",
|
|
282
|
+
backgroundColor: props.backgroundColor
|
|
283
|
+
? Unifier.theme[props.backgroundColor]
|
|
284
|
+
: Unifier.theme.gray,
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
<Text style={{fontSize, color: props.textColor ?? Unifier.theme.darkGray}}>
|
|
288
|
+
{computedInitials}
|
|
289
|
+
</Text>
|
|
290
|
+
</View>
|
|
291
|
+
)}
|
|
292
|
+
</Box>
|
|
293
|
+
{/* Needs to come after the image so it renders on top. */}
|
|
294
|
+
{renderEditIcon()}
|
|
295
|
+
{renderStatusIcon()}
|
|
188
296
|
</Box>
|
|
189
297
|
);
|
|
298
|
+
|
|
299
|
+
let status = props.statusText;
|
|
300
|
+
if (!status && props.status) {
|
|
301
|
+
status = statusIcons[props.status]?.label;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (status) {
|
|
305
|
+
// Need to wrap the tooltip so it doesn't expand to 100% width and render the tooltip off. Don't show the
|
|
306
|
+
// tooltips on mobile because they intercept the edit avatar clicks.
|
|
307
|
+
return (
|
|
308
|
+
<Box width={width}>
|
|
309
|
+
<Tooltip idealDirection="top" text={isMobileDevice() ? undefined : status}>
|
|
310
|
+
{avatar}
|
|
311
|
+
</Tooltip>
|
|
312
|
+
</Box>
|
|
313
|
+
);
|
|
314
|
+
} else {
|
|
315
|
+
return avatar;
|
|
316
|
+
}
|
|
190
317
|
};
|
package/src/Box.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import React, {useImperativeHandle} from "react";
|
|
2
3
|
import {
|
|
3
4
|
KeyboardAvoidingView,
|
|
4
5
|
Platform,
|
|
@@ -43,8 +44,31 @@ const ALIGN_SELF = {
|
|
|
43
44
|
|
|
44
45
|
const BORDER_WIDTH = 1;
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// eslint-disable-next-line react/display-name
|
|
48
|
+
export const Box = React.forwardRef((props: BoxProps, ref) => {
|
|
49
|
+
useImperativeHandle(ref, () => ({
|
|
50
|
+
scrollToEnd: () => {
|
|
51
|
+
if (scrollRef && scrollRef.current) {
|
|
52
|
+
// HACK HACK HACK...but it works. Probably need to do some onContentSizeChange or onLayout to
|
|
53
|
+
// avoid this, but it works well enough.
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
scrollRef && scrollRef.current && (scrollRef.current as any).scrollToEnd();
|
|
56
|
+
}, 50);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
scrollTo: (y: number) => {
|
|
61
|
+
if (scrollRef && scrollRef.current) {
|
|
62
|
+
// HACK HACK HACK...but it works. Probably need to do some onContentSizeChange or onLayout to
|
|
63
|
+
// avoid this, but it works well enough.
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
scrollRef && scrollRef.current && (scrollRef.current as any).scrollTo({y});
|
|
66
|
+
}, 50);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
const BOX_STYLE_MAP: {
|
|
48
72
|
[prop: string]: (
|
|
49
73
|
value: any,
|
|
50
74
|
all: {[prop: string]: any}
|
|
@@ -78,7 +102,7 @@ export class Box extends React.Component<BoxProps, {}> {
|
|
|
78
102
|
},
|
|
79
103
|
justifyContent: (value: JustifyContent) => ({justifyContent: ALIGN_CONTENT[value]}),
|
|
80
104
|
height: (value) => {
|
|
81
|
-
if (
|
|
105
|
+
if (props.border && !isNaN(Number(value))) {
|
|
82
106
|
return {height: Number(value) + 2 * 2};
|
|
83
107
|
} else {
|
|
84
108
|
return {height: value};
|
|
@@ -124,7 +148,7 @@ export class Box extends React.Component<BoxProps, {}> {
|
|
|
124
148
|
return {overflow: value};
|
|
125
149
|
},
|
|
126
150
|
width: (value) => {
|
|
127
|
-
if (
|
|
151
|
+
if (props.border && !isNaN(Number(value))) {
|
|
128
152
|
return {width: Number(value) + 2 * 2};
|
|
129
153
|
} else {
|
|
130
154
|
return {width: value};
|
|
@@ -181,119 +205,104 @@ export class Box extends React.Component<BoxProps, {}> {
|
|
|
181
205
|
},
|
|
182
206
|
};
|
|
183
207
|
|
|
184
|
-
scrollRef = React.createRef();
|
|
185
|
-
|
|
186
|
-
constructor(props: BoxProps) {
|
|
187
|
-
super(props);
|
|
188
|
-
if (props.scrollRef) {
|
|
189
|
-
this.scrollRef = props.scrollRef;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
public scrollToEnd = () => {
|
|
194
|
-
if (this.scrollRef && this.scrollRef.current) {
|
|
195
|
-
// HACK HACK HACK...but it works. Probably need to do some onContentSizeChange or onLayout to
|
|
196
|
-
// avoid this, but it works well enough.
|
|
197
|
-
setTimeout(() => {
|
|
198
|
-
this.scrollRef && this.scrollRef.current && (this.scrollRef.current as any).scrollToEnd();
|
|
199
|
-
}, 50);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
public scrollTo = (y: number) => {
|
|
204
|
-
if (this.scrollRef && this.scrollRef.current) {
|
|
205
|
-
// HACK HACK HACK...but it works. Probably need to do some onContentSizeChange or onLayout to
|
|
206
|
-
// avoid this, but it works well enough.
|
|
207
|
-
setTimeout(() => {
|
|
208
|
-
this.scrollRef && this.scrollRef.current && (this.scrollRef.current as any).scrollTo({y});
|
|
209
|
-
}, 50);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
208
|
+
const scrollRef = props.scrollRef ?? React.createRef();
|
|
212
209
|
|
|
213
|
-
propsToStyle(): any {
|
|
210
|
+
const propsToStyle = (): any => {
|
|
214
211
|
let style: any = {};
|
|
215
|
-
for (const prop of Object.keys(
|
|
216
|
-
const value = (
|
|
217
|
-
if (
|
|
218
|
-
Object.assign(style,
|
|
212
|
+
for (const prop of Object.keys(props)) {
|
|
213
|
+
const value = (props as any)[prop];
|
|
214
|
+
if (BOX_STYLE_MAP[prop]) {
|
|
215
|
+
Object.assign(style, BOX_STYLE_MAP[prop](value, props));
|
|
219
216
|
} else if (prop !== "children" && prop !== "onClick") {
|
|
220
217
|
style[prop] = value;
|
|
221
218
|
// console.warn(`Box: unknown property ${prop}`);
|
|
222
219
|
}
|
|
223
220
|
}
|
|
224
221
|
|
|
225
|
-
if (
|
|
222
|
+
if (props.wrap && props.alignItems) {
|
|
226
223
|
console.warn("React Native doesn't support wrap and alignItems together.");
|
|
227
224
|
}
|
|
228
225
|
|
|
229
226
|
// Finally, dangerously set overrides.
|
|
230
|
-
if (
|
|
231
|
-
style = {...style, ...
|
|
227
|
+
if (props.dangerouslySetInlineStyle) {
|
|
228
|
+
style = {...style, ...props.dangerouslySetInlineStyle.__style};
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
return style;
|
|
235
|
-
}
|
|
232
|
+
};
|
|
236
233
|
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
const onHoverIn = () => {
|
|
235
|
+
props.onHoverStart?.();
|
|
236
|
+
};
|
|
239
237
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
style={this.propsToStyle()}
|
|
244
|
-
testID={this.props.testID ? `${this.props.testID}-clickable` : undefined}
|
|
245
|
-
// TODO: refactor this better..
|
|
246
|
-
onLayout={this.props.onLayout}
|
|
247
|
-
onPress={() => {
|
|
248
|
-
Unifier.utils.haptic();
|
|
249
|
-
this.props.onClick();
|
|
250
|
-
}}
|
|
251
|
-
>
|
|
252
|
-
{this.props.children}
|
|
253
|
-
</Pressable>
|
|
254
|
-
);
|
|
255
|
-
} else {
|
|
256
|
-
box = (
|
|
257
|
-
<View style={this.propsToStyle()} testID={this.props.testID}>
|
|
258
|
-
{this.props.children}
|
|
259
|
-
</View>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
238
|
+
const onHoverOut = () => {
|
|
239
|
+
props.onHoverEnd?.();
|
|
240
|
+
};
|
|
262
241
|
|
|
263
|
-
|
|
264
|
-
const {justifyContent, alignContent, alignItems, ...scrollStyle} = this.propsToStyle();
|
|
242
|
+
let box;
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
244
|
+
if (props.onClick) {
|
|
245
|
+
box = (
|
|
246
|
+
<Pressable
|
|
247
|
+
style={propsToStyle()}
|
|
248
|
+
testID={props.testID ? `${props.testID}-clickable` : undefined}
|
|
249
|
+
onLayout={props.onLayout}
|
|
250
|
+
onPointerEnter={onHoverIn}
|
|
251
|
+
onPointerLeave={onHoverOut}
|
|
252
|
+
onPress={() => {
|
|
253
|
+
Unifier.utils.haptic();
|
|
254
|
+
props.onClick!();
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{props.children}
|
|
258
|
+
</Pressable>
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
box = (
|
|
262
|
+
<View
|
|
263
|
+
style={propsToStyle()}
|
|
264
|
+
testID={props.testID}
|
|
265
|
+
onPointerEnter={onHoverIn}
|
|
266
|
+
onPointerLeave={onHoverOut}
|
|
267
|
+
>
|
|
268
|
+
{props.children}
|
|
269
|
+
</View>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
285
272
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
273
|
+
if (props.scroll) {
|
|
274
|
+
const {justifyContent, alignContent, alignItems, ...scrollStyle} = propsToStyle();
|
|
275
|
+
|
|
276
|
+
box = (
|
|
277
|
+
<ScrollView
|
|
278
|
+
ref={props.scrollRef || scrollRef}
|
|
279
|
+
contentContainerStyle={{justifyContent, alignContent, alignItems}}
|
|
280
|
+
horizontal={props.overflow === "scrollX"}
|
|
281
|
+
keyboardShouldPersistTaps="handled"
|
|
282
|
+
nestedScrollEnabled
|
|
283
|
+
scrollEventThrottle={50}
|
|
284
|
+
style={scrollStyle}
|
|
285
|
+
onScroll={(event) => {
|
|
286
|
+
if (props.onScroll && event) {
|
|
287
|
+
props.onScroll(event.nativeEvent.contentOffset.y);
|
|
288
|
+
}
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
{box}
|
|
292
|
+
</ScrollView>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (props.avoidKeyboard) {
|
|
297
|
+
box = (
|
|
298
|
+
<KeyboardAvoidingView
|
|
299
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
300
|
+
keyboardVerticalOffset={props.keyboardOffset}
|
|
301
|
+
style={{flex: 1, display: "flex"}}
|
|
302
|
+
>
|
|
303
|
+
<SafeAreaView style={{flex: 1, display: "flex"}}>{box}</SafeAreaView>
|
|
304
|
+
</KeyboardAvoidingView>
|
|
305
|
+
);
|
|
298
306
|
}
|
|
299
|
-
|
|
307
|
+
return box;
|
|
308
|
+
});
|
package/src/Common.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {ReactElement, ReactNode, SyntheticEvent} from "react";
|
|
1
|
+
import React, {ReactElement, ReactNode, SyntheticEvent} from "react";
|
|
2
2
|
import {ListRenderItemInfo} from "react-native";
|
|
3
3
|
|
|
4
4
|
export interface BaseProfile {
|
|
@@ -1932,14 +1932,12 @@ export interface BoxProps {
|
|
|
1932
1932
|
wrap?: boolean;
|
|
1933
1933
|
zIndex?: number | "auto";
|
|
1934
1934
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
className?: any;
|
|
1935
|
+
onClick?: () => void | Promise<void>;
|
|
1936
|
+
className?: string;
|
|
1938
1937
|
style?: any;
|
|
1939
|
-
|
|
1940
|
-
|
|
1938
|
+
onHoverStart?: () => void | Promise<void>;
|
|
1939
|
+
onHoverEnd?: () => void | Promise<void>;
|
|
1941
1940
|
scroll?: boolean;
|
|
1942
|
-
// Pattern Addition. Shadows!
|
|
1943
1941
|
shadow?: boolean;
|
|
1944
1942
|
border?: AllColors;
|
|
1945
1943
|
borderBottom?: AllColors;
|
|
@@ -1949,9 +1947,9 @@ export interface BoxProps {
|
|
|
1949
1947
|
|
|
1950
1948
|
avoidKeyboard?: boolean;
|
|
1951
1949
|
keyboardOffset?: number;
|
|
1952
|
-
scrollRef?: any
|
|
1950
|
+
scrollRef?: React.RefObject<any>;
|
|
1953
1951
|
onScroll?: (offsetY: number) => void;
|
|
1954
|
-
onLayout?: (event:
|
|
1952
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
1955
1953
|
testID?: string;
|
|
1956
1954
|
}
|
|
1957
1955
|
|
|
@@ -1997,6 +1995,8 @@ export interface NavigatorProps {
|
|
|
1997
1995
|
|
|
1998
1996
|
export type TooltipDirection = "top" | "bottom" | "left" | "right";
|
|
1999
1997
|
|
|
1998
|
+
export type IndicatorDirection = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
1999
|
+
|
|
2000
2000
|
export interface PillProps {
|
|
2001
2001
|
text: string;
|
|
2002
2002
|
color: AllColors;
|
package/src/IconButton.tsx
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React, {forwardRef, useState} from "react";
|
|
2
|
-
import {Platform, Pressable} from "react-native";
|
|
2
|
+
import {Platform, Pressable, View, ViewStyle} from "react-native";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
AllColors,
|
|
5
6
|
ButtonColor,
|
|
6
7
|
Color,
|
|
7
8
|
IconName,
|
|
8
9
|
IconPrefix,
|
|
9
10
|
IconSize,
|
|
10
11
|
iconSizeToNumber,
|
|
12
|
+
IndicatorDirection,
|
|
11
13
|
ThemeColor,
|
|
12
14
|
TooltipDirection,
|
|
13
15
|
} from "./Common";
|
|
@@ -34,6 +36,8 @@ export interface IconButtonProps {
|
|
|
34
36
|
text: string;
|
|
35
37
|
idealDirection?: TooltipDirection;
|
|
36
38
|
};
|
|
39
|
+
indicator?: boolean;
|
|
40
|
+
indicatorStyle?: {position: IndicatorDirection; color: AllColors};
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// eslint-disable-next-line react/display-name
|
|
@@ -50,6 +54,8 @@ export const IconButton = forwardRef(
|
|
|
50
54
|
confirmationText = "Are you sure you want to continue?",
|
|
51
55
|
confirmationHeading = "Confirm",
|
|
52
56
|
tooltip,
|
|
57
|
+
indicator,
|
|
58
|
+
indicatorStyle = {position: "bottomRight", color: "primary"},
|
|
53
59
|
}: IconButtonProps,
|
|
54
60
|
ref
|
|
55
61
|
) => {
|
|
@@ -65,6 +71,15 @@ export const IconButton = forwardRef(
|
|
|
65
71
|
color = Unifier.theme[bgColor];
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
const IndicatorPosition = {
|
|
75
|
+
bottomRight: {bottom: "20%", right: "20%"},
|
|
76
|
+
bottomLeft: {bottom: "20%", left: "20%"},
|
|
77
|
+
topRight: {top: "20%", right: "20%"},
|
|
78
|
+
topLeft: {top: "20%", left: "20%"},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const indicatorPosition = {position: "absolute", ...IndicatorPosition[indicatorStyle.position]};
|
|
82
|
+
|
|
68
83
|
const renderConfirmation = () => {
|
|
69
84
|
return (
|
|
70
85
|
<Modal
|
|
@@ -117,7 +132,18 @@ export const IconButton = forwardRef(
|
|
|
117
132
|
}}
|
|
118
133
|
>
|
|
119
134
|
<Icon color={iconColor} name={icon} prefix={prefix || "fas"} size={size} />
|
|
135
|
+
{indicator && (
|
|
136
|
+
<View style={indicatorPosition as ViewStyle}>
|
|
137
|
+
<Icon
|
|
138
|
+
color={indicatorStyle.color}
|
|
139
|
+
name="circle"
|
|
140
|
+
prefix={prefix || "fas"}
|
|
141
|
+
size="sm"
|
|
142
|
+
/>
|
|
143
|
+
</View>
|
|
144
|
+
)}
|
|
120
145
|
</Pressable>
|
|
146
|
+
|
|
121
147
|
{Boolean(withConfirmation) && renderConfirmation()}
|
|
122
148
|
</>
|
|
123
149
|
);
|
package/src/Switch.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import {WithLabel} from "./WithLabel";
|
|
|
7
7
|
export class Switch extends React.Component<SwitchProps, {}> {
|
|
8
8
|
render() {
|
|
9
9
|
return (
|
|
10
|
-
<WithLabel labelInline labelJustifyContent="between" {...this.props}>
|
|
10
|
+
<WithLabel labelAlignItems="center" labelInline labelJustifyContent="between" {...this.props}>
|
|
11
11
|
<NativeSwitch
|
|
12
12
|
disabled={this.props.disabled}
|
|
13
13
|
value={this.props.switched}
|
package/src/TableRow.tsx
CHANGED
|
@@ -40,7 +40,7 @@ export function TableRow({
|
|
|
40
40
|
}: Props): React.ReactElement {
|
|
41
41
|
const [isExpanded, setIsExpanded] = React.useState(expanded || false);
|
|
42
42
|
const {columns, hasDrawerContents} = useTableContext();
|
|
43
|
-
const rowRef = useRef<Box>(null);
|
|
43
|
+
const rowRef = useRef<typeof Box>(null);
|
|
44
44
|
|
|
45
45
|
const renderCellWithColumnIndex = (child: React.ReactNode, index: number) => {
|
|
46
46
|
if (!columns[index]) {
|
package/src/TextField.tsx
CHANGED
|
@@ -190,7 +190,7 @@ export function TextField({
|
|
|
190
190
|
} else if (type === "height") {
|
|
191
191
|
weightActionSheetRef?.current?.show();
|
|
192
192
|
}
|
|
193
|
-
}, [decimalRangeActionSheetRef, numberRangeActionSheetRef, type, weightActionSheetRef]);
|
|
193
|
+
}, [decimalRangeActionSheetRef, disabled, numberRangeActionSheetRef, type, weightActionSheetRef]);
|
|
194
194
|
|
|
195
195
|
let displayValue = value;
|
|
196
196
|
if (displayValue) {
|
|
@@ -297,8 +297,8 @@ export function TextField({
|
|
|
297
297
|
if (!isHandledByModal) {
|
|
298
298
|
setFocused(false);
|
|
299
299
|
}
|
|
300
|
-
if (onBlur
|
|
301
|
-
onBlur({value});
|
|
300
|
+
if (onBlur) {
|
|
301
|
+
onBlur({value: value ?? ""});
|
|
302
302
|
}
|
|
303
303
|
// if (type === "date") {
|
|
304
304
|
// actionSheetRef?.current?.hide();
|