ferns-ui 0.4.3 → 0.6.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/Pog.tsx CHANGED
@@ -5,125 +5,6 @@ import {Box} from "./Box";
5
5
  import {AllColors, IconPrefix, IconSize} from "./Common";
6
6
  import {Icon} from "./Icon";
7
7
 
8
- /*
9
- Originally based on https://github.com/pinterest/gestalt
10
- Forked, added type definitions, and added features.
11
- */
12
- // TOOD: create styles
13
- // :root {
14
- // --lightGray: #efefef;
15
- // --gray: #8e8e8e;
16
- // --darkGray: #111;
17
- // }
18
-
19
- // .pog {
20
- // composes: circle from "./Borders.module.css";
21
- // composes: flex from "./Layout.module.css";
22
- // composes: itemsCenter from "./Layout.module.css";
23
- // composes: justifyCenter from "./Layout.module.css";
24
- // }
25
-
26
- // .focused {
27
- // composes: accessibilityOutlineFocus from "./Focus.module.css";
28
- // }
29
-
30
- // .selected {
31
- // composes: darkGrayBg from "./Colors.module.css";
32
- // }
33
-
34
- // .transparent {
35
- // composes: transparentBg from "./Colors.module.css";
36
- // }
37
-
38
- // .transparentDarkGray {
39
- // composes: transparentDarkGrayBg from "./Colors.module.css";
40
- // }
41
-
42
- // .transparent.hovered,
43
- // .transparent.focused,
44
- // .white.hovered,
45
- // .white.focused {
46
- // background-color: rgba(0, 0, 0, 0.06);
47
- // }
48
-
49
- // .transparent.active,
50
- // .white.active {
51
- // background-color: rgba(0, 0, 0, 0.1);
52
- // }
53
-
54
- // .transparentDarkGray.hovered,
55
- // .transparentDarkGray.focused {
56
- // background-color: var(--darkGray);
57
- // }
58
-
59
- // .transparentDarkGray.active {
60
- // background-color: var(--darkGray);
61
- // }
62
-
63
- // .white {
64
- // composes: whiteBg from "./Colors.module.css";
65
- // }
66
-
67
- // .white.hovered,
68
- // .white.focused {
69
- // background-color: #f0f0f0;
70
- // }
71
-
72
- // .white.active {
73
- // background-color: #e5e5e5;
74
- // }
75
-
76
- // .lightGray {
77
- // composes: lightGrayBg from "./Colors.module.css";
78
- // }
79
-
80
- // .lightGray.hovered,
81
- // .lightGray.focused {
82
- // background-color: #e2e2e2;
83
- // }
84
-
85
- // .lightGray.active {
86
- // background-color: #dadada;
87
- // }
88
-
89
- // .gray {
90
- // composes: grayBg from "./Colors.module.css";
91
- // }
92
-
93
- // .gray.hovered,
94
- // .gray.focused {
95
- // background-color: #878787;
96
- // }
97
-
98
- // .gray.active {
99
- // background-color: #828282;
100
- // }
101
-
102
- // .darkGray {
103
- // composes: darkGrayBg from "./Colors.module.css";
104
- // }
105
-
106
- // .blue {
107
- // composes: blueBg from "./Colors.module.css";
108
- // }
109
-
110
- // .blue.hovered,
111
- // .blue.focused {
112
- // background-color: #4a8ad4;
113
- // }
114
-
115
- // .blue.active {
116
- // background-color: #4a85c9;
117
- // }
118
-
119
- const styles = {
120
- pog: "",
121
- selected: "",
122
- active: "",
123
- focused: "",
124
- hovered: "",
125
- };
126
-
127
8
  const SIZE_NAME_TO_PIXEL = {
128
9
  xs: 24,
129
10
  sm: 32,
@@ -132,7 +13,6 @@ const SIZE_NAME_TO_PIXEL = {
132
13
  xl: 56,
133
14
  };
134
15
 
135
- // type PogColor = "gray" | "darkGray" | "red" | "blue" | "white" | "orange";
136
16
  interface Props {
137
17
  active?: boolean;
138
18
  bgColor?: "transparent" | "transparentDarkGray" | "gray" | "lightGray" | "white" | "blue";
@@ -157,10 +37,7 @@ const defaultIconButtonIconColors = {
157
37
 
158
38
  export default function Pog(props: Props) {
159
39
  const {
160
- active = false,
161
40
  bgColor = "transparent",
162
- focused = false,
163
- hovered = false,
164
41
  iconColor,
165
42
  icon,
166
43
  iconPrefix = "fas",
@@ -168,7 +45,6 @@ export default function Pog(props: Props) {
168
45
  size = "md",
169
46
  } = props;
170
47
 
171
- const iconSize = SIZE_NAME_TO_PIXEL[size] / 2;
172
48
  const color =
173
49
  (selected && "white") ||
174
50
  iconColor ||
@@ -5,7 +5,7 @@ import RNPickerSelect from "./PickerSelect";
5
5
  import {Unifier} from "./Unifier";
6
6
 
7
7
  // Use "" if you want to have an "unset" value.
8
- export type SelectListOptions = {label: string; value: string | number}[];
8
+ export type SelectListOptions = {label: string; value: string}[];
9
9
  export interface SelectListProps extends FieldWithLabelsProps {
10
10
  id?: string;
11
11
  name?: string;
@@ -17,33 +17,29 @@ export interface SelectListProps extends FieldWithLabelsProps {
17
17
  placeholder?: string;
18
18
  }
19
19
 
20
- export class SelectList extends React.Component<SelectListProps, {}> {
21
- state = {showing: false};
22
-
23
- render() {
24
- return (
25
- <RNPickerSelect
26
- items={this.props.options}
27
- placeholder={{}}
28
- style={{
29
- viewContainer: {
30
- flexDirection: "row",
31
- justifyContent: "center",
32
- alignItems: "center",
33
- minHeight: 50,
34
- width: "100%",
35
- // Add padding so the border doesn't mess up layouts
36
- paddingHorizontal: 6,
37
- paddingVertical: 4,
38
- borderColor: Unifier.theme.gray,
39
- borderWidth: 1,
40
- borderRadius: 5,
41
- backgroundColor: Unifier.theme.white,
42
- },
43
- }}
44
- value={this.props.value}
45
- onValueChange={this.props.onChange}
46
- />
47
- );
48
- }
20
+ export function SelectList({options, value, onChange}: SelectListProps) {
21
+ return (
22
+ <RNPickerSelect
23
+ items={options}
24
+ placeholder={{}}
25
+ style={{
26
+ viewContainer: {
27
+ flexDirection: "row",
28
+ justifyContent: "center",
29
+ alignItems: "center",
30
+ minHeight: 50,
31
+ width: "100%",
32
+ // Add padding so the border doesn't mess up layouts
33
+ paddingHorizontal: 6,
34
+ paddingVertical: 4,
35
+ borderColor: Unifier.theme.gray,
36
+ borderWidth: 1,
37
+ borderRadius: 5,
38
+ backgroundColor: Unifier.theme.white,
39
+ },
40
+ }}
41
+ value={value}
42
+ onValueChange={onChange}
43
+ />
44
+ );
49
45
  }
package/src/SplitPage.tsx CHANGED
@@ -3,9 +3,7 @@ import {ListRenderItemInfo, View} from "react-native";
3
3
 
4
4
  import {Box} from "./Box";
5
5
  import {Color} from "./Common";
6
- import {ErrorBoundary} from "./ErrorBoundary";
7
6
  import {FlatList} from "./FlatList";
8
- import {Icon} from "./Icon";
9
7
  import {IconButton} from "./IconButton";
10
8
  import {mediaQueryLargerThan} from "./MediaQuery";
11
9
  import {Spinner} from "./Spinner";
package/src/TapToEdit.tsx CHANGED
@@ -1,48 +1,118 @@
1
- import React from "react";
1
+ import React, {ReactElement, useState} from "react";
2
2
 
3
3
  import {Box} from "./Box";
4
4
  import {Button} from "./Button";
5
- import {TextFieldProps} from "./Common";
5
+ import {BoxProps} from "./Common";
6
+ import {Field, FieldProps} from "./Field";
6
7
  import {Icon} from "./Icon";
7
8
  import {Text} from "./Text";
8
- import {TextField} from "./TextField";
9
9
 
10
- interface TapToEditState {
11
- showEdit: boolean;
10
+ export interface TapToEditProps extends Omit<FieldProps, "handleChange"> {
11
+ title: string;
12
+ // Not required if not editable.
13
+ onSave?: (value: any) => void | Promise<void>;
14
+ // Defaults to true
15
+ editable?: boolean;
16
+ // For changing how the non-editing row renders
17
+ rowBoxProps?: Partial<BoxProps>;
18
+ transform?: (value: any) => string;
19
+ fieldComponent?: (setValue: () => void) => ReactElement;
12
20
  }
13
21
 
14
- export class TapToEdit extends React.Component<TextFieldProps, TapToEditState> {
15
- state = {showEdit: false};
22
+ export const TapToEdit = (props: TapToEditProps): ReactElement => {
23
+ const [editing, setEditing] = useState(false);
24
+ const [value, setValue] = useState(props.initialValue);
16
25
 
17
- render() {
18
- if (!this.state.showEdit) {
19
- return (
20
- <Box direction="row" display="flex" onClick={() => this.setState({showEdit: true})}>
21
- <Box marginRight={2}>
22
- <Icon color="primaryDark" name="edit" prefix="far" size="lg" />
23
- </Box>
24
- <Text>{this.props.children}</Text>
25
- </Box>
26
- );
27
- } else {
28
- return (
29
- <Box>
30
- <TextField {...this.props} />
31
- <Box paddingY={1} width={100}>
26
+ const {title, editable = true, rowBoxProps, transform, fieldComponent, ...fieldProps} = props;
27
+ if (editing) {
28
+ return (
29
+ <Box direction="column" maxWidth="100%" paddingX={3} paddingY={2} width="100%">
30
+ {fieldComponent ? (
31
+ fieldComponent(setValue as any)
32
+ ) : (
33
+ <Field
34
+ handleChange={(_: string, val: any): void => setValue(val)}
35
+ label={props.title}
36
+ {...fieldProps}
37
+ />
38
+ )}
39
+ <Box direction="row">
40
+ <Button
41
+ color="blue"
42
+ inline
43
+ text="Save"
44
+ onClick={async (): Promise<void> => {
45
+ if (!props.onSave) {
46
+ console.error("No onSave provided for editable TapToEdit");
47
+ } else {
48
+ await props.onSave(value);
49
+ }
50
+ setEditing(false);
51
+ }}
52
+ />
53
+ <Box marginLeft={2}>
32
54
  <Button
33
- color="primary"
55
+ color="red"
34
56
  inline
35
- text="Save"
36
- onClick={() => {
37
- this.setState({showEdit: false});
38
- if (this.props.onSubmitEditing) {
39
- this.props.onSubmitEditing();
40
- }
57
+ text="Cancel"
58
+ onClick={(): void => {
59
+ setEditing(false);
41
60
  }}
42
61
  />
43
62
  </Box>
44
63
  </Box>
45
- );
64
+ </Box>
65
+ );
66
+ } else {
67
+ let displayValue = value;
68
+ // If transform is present, that takes priority
69
+ if (transform) {
70
+ displayValue = transform(value);
71
+ } else {
72
+ // If no transform, try and display the value reasonably.
73
+ if (fieldProps?.type === "boolean") {
74
+ displayValue = value ? "Yes" : "No";
75
+ } else if (fieldProps?.type === "percent") {
76
+ // Prevent floating point errors from showing up by using parseFloat and precision. Pass through parseFloat again
77
+ // to trim off insignificant zeroes.
78
+ displayValue = `${parseFloat(parseFloat(String(value * 100)).toPrecision(7))}%`;
79
+ } else if (fieldProps?.type === "currency") {
80
+ // TODO: support currencies other than USD in Field and related components.
81
+ const formatter = new Intl.NumberFormat("en-US", {
82
+ style: "currency",
83
+ currency: "USD",
84
+ minimumFractionDigits: 2, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
85
+ });
86
+ displayValue = formatter.format(value);
87
+ } else if (fieldProps?.type === "multiselect") {
88
+ // ???
89
+ displayValue = value.join(", ");
90
+ }
46
91
  }
92
+ return (
93
+ <Box
94
+ direction="row"
95
+ justifyContent="between"
96
+ maxWidth="100%"
97
+ paddingX={3}
98
+ paddingY={2}
99
+ width="100%"
100
+ {...rowBoxProps}
101
+ >
102
+ <Box>
103
+ <Text weight="bold">{title}:</Text>
104
+ </Box>
105
+ <Box direction="row" flex="shrink" marginLeft={2}>
106
+ <Box flex="shrink">
107
+ <Text overflow="breakWord">{displayValue}</Text>
108
+ </Box>
109
+ {editable && (
110
+ <Box marginLeft={2} onClick={(): void => setEditing(true)}>
111
+ <Icon color="darkGray" name="edit" size="lg" />
112
+ </Box>
113
+ )}
114
+ </Box>
115
+ </Box>
116
+ );
47
117
  }
48
- }
118
+ };
package/src/Unifier.ts CHANGED
@@ -366,6 +366,4 @@ class UnifierClass {
366
366
  };
367
367
  }
368
368
 
369
- const NOTIFICATION_TAB_KEY = "@unifier/tabNotifications";
370
-
371
369
  export const Unifier = new UnifierClass();