ferns-ui 0.34.1 → 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/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
 
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();
package/src/Tooltip.tsx CHANGED
@@ -118,7 +118,8 @@ const getTooltipPosition = ({
118
118
 
119
119
  interface TooltipProps {
120
120
  children: React.ReactElement;
121
- text: string;
121
+ // If text is undefined, the children will be rendered without a tooltip.
122
+ text?: string;
122
123
  idealDirection?: "top" | "bottom" | "left" | "right";
123
124
  bgColor?: "white" | "lightGray" | "gray" | "darkGray";
124
125
  }
@@ -214,6 +215,12 @@ export const Tooltip = (props: TooltipProps) => {
214
215
  }, [children.props]),
215
216
  };
216
217
 
218
+ // Allow disabling tooltips when there is no string, otherwise you need to wrap the children in a function to
219
+ // determine if there should be a tooltip or not, which gets messy.
220
+ if (!text) {
221
+ return children;
222
+ }
223
+
217
224
  return (
218
225
  <>
219
226
  {visible && (
package/src/Unifier.ts CHANGED
@@ -2,10 +2,11 @@
2
2
  /* eslint-disable @typescript-eslint/no-empty-function */
3
3
 
4
4
  import AsyncStorage from "@react-native-async-storage/async-storage";
5
+ import * as Clipboard from "expo-clipboard";
5
6
  import * as Font from "expo-font";
6
7
  import {FontSource} from "expo-font";
7
8
  import * as Haptics from "expo-haptics";
8
- import {Clipboard, Dimensions, Keyboard, Linking, Platform, Vibration} from "react-native";
9
+ import {Dimensions, Keyboard, Linking, Platform, Vibration} from "react-native";
9
10
 
10
11
  import {PermissionKind, UnifiedTheme} from "./Common";
11
12
  import {requestPermissions} from "./Permissions";