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/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, iconSizeToNumber} from "./Common";
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
- return (
139
- <Box
140
- border={outline ? "white" : undefined}
141
- height={height}
142
- overflow="hidden"
143
- position="relative"
144
- rounding="circle"
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
- <Text style={{fontSize, color: props.textColor ?? Unifier.theme.darkGray}}>
184
- {computedInitials}
185
- </Text>
186
- </View>
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
- import React from "react";
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
- export class Box extends React.Component<BoxProps, {}> {
47
- BOX_STYLE_MAP: {
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 (this.props.border && !isNaN(Number(value))) {
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 (this.props.border && !isNaN(Number(value))) {
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(this.props)) {
216
- const value = (this.props as any)[prop];
217
- if (this.BOX_STYLE_MAP[prop]) {
218
- Object.assign(style, this.BOX_STYLE_MAP[prop](value, this.props));
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 (this.props.wrap && this.props.alignItems) {
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 (this.props.dangerouslySetInlineStyle) {
231
- style = {...style, ...this.props.dangerouslySetInlineStyle.__style};
227
+ if (props.dangerouslySetInlineStyle) {
228
+ style = {...style, ...props.dangerouslySetInlineStyle.__style};
232
229
  }
233
230
 
234
231
  return style;
235
- }
232
+ };
236
233
 
237
- render() {
238
- let box;
234
+ const onHoverIn = () => {
235
+ props.onHoverStart?.();
236
+ };
239
237
 
240
- if (this.props.onClick) {
241
- box = (
242
- <Pressable
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
- if (this.props.scroll) {
264
- const {justifyContent, alignContent, alignItems, ...scrollStyle} = this.propsToStyle();
242
+ let box;
265
243
 
266
- box = (
267
- <ScrollView
268
- ref={this.props.scrollRef || this.scrollRef}
269
- contentContainerStyle={{justifyContent, alignContent, alignItems}}
270
- horizontal={this.props.overflow === "scrollX"}
271
- keyboardShouldPersistTaps="handled"
272
- nestedScrollEnabled
273
- scrollEventThrottle={50}
274
- style={scrollStyle}
275
- onScroll={(event) => {
276
- if (this.props.onScroll && event) {
277
- this.props.onScroll(event.nativeEvent.contentOffset.y);
278
- }
279
- }}
280
- >
281
- {box}
282
- </ScrollView>
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
- if (this.props.avoidKeyboard) {
287
- box = (
288
- <KeyboardAvoidingView
289
- behavior={Platform.OS === "ios" ? "padding" : "height"}
290
- keyboardVerticalOffset={this.props.keyboardOffset}
291
- style={{flex: 1, display: "flex"}}
292
- >
293
- <SafeAreaView style={{flex: 1, display: "flex"}}>{box}</SafeAreaView>
294
- </KeyboardAvoidingView>
295
- );
296
- }
297
- return box;
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
- // Pattern additions
1936
- onClick?: any;
1937
- className?: any;
1935
+ onClick?: () => void | Promise<void>;
1936
+ className?: string;
1938
1937
  style?: any;
1939
-
1940
- // Pattern Addition. Adds keyboard aware scrollview
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: any) => void;
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;
@@ -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 && value) {
301
- onBlur({value});
300
+ if (onBlur) {
301
+ onBlur({value: value ?? ""});
302
302
  }
303
303
  // if (type === "date") {
304
304
  // actionSheetRef?.current?.hide();