ferns-ui 0.36.4 → 0.37.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.
Files changed (87) hide show
  1. package/dist/Banner.d.ts +6 -16
  2. package/dist/Banner.js +52 -43
  3. package/dist/Banner.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/useStoredState.d.ts +1 -0
  8. package/dist/useStoredState.js +33 -0
  9. package/dist/useStoredState.js.map +1 -0
  10. package/package.json +55 -56
  11. package/src/ActionSheet.tsx +1231 -0
  12. package/src/Avatar.tsx +317 -0
  13. package/src/Badge.tsx +65 -0
  14. package/src/Banner.tsx +149 -0
  15. package/src/BlurBox.native.tsx +40 -0
  16. package/src/BlurBox.tsx +31 -0
  17. package/src/Body.tsx +32 -0
  18. package/src/Box.tsx +308 -0
  19. package/src/Button.tsx +219 -0
  20. package/src/Card.tsx +23 -0
  21. package/src/CheckBox.tsx +118 -0
  22. package/src/Common.ts +2743 -0
  23. package/src/Constants.ts +53 -0
  24. package/src/CustomSelect.tsx +85 -0
  25. package/src/DateTimeActionSheet.tsx +409 -0
  26. package/src/DateTimeField.android.tsx +101 -0
  27. package/src/DateTimeField.ios.tsx +83 -0
  28. package/src/DateTimeField.tsx +69 -0
  29. package/src/DecimalRangeActionSheet.tsx +113 -0
  30. package/src/ErrorBoundary.tsx +37 -0
  31. package/src/ErrorPage.tsx +44 -0
  32. package/src/FernsProvider.tsx +21 -0
  33. package/src/Field.tsx +299 -0
  34. package/src/FieldWithLabels.tsx +36 -0
  35. package/src/FlatList.tsx +2 -0
  36. package/src/Form.tsx +182 -0
  37. package/src/HeaderButtons.tsx +107 -0
  38. package/src/Heading.tsx +53 -0
  39. package/src/HeightActionSheet.tsx +104 -0
  40. package/src/Hyperlink.tsx +181 -0
  41. package/src/Icon.tsx +24 -0
  42. package/src/IconButton.tsx +165 -0
  43. package/src/Image.tsx +50 -0
  44. package/src/ImageBackground.tsx +14 -0
  45. package/src/InfoTooltipButton.tsx +23 -0
  46. package/src/Layer.tsx +17 -0
  47. package/src/Link.tsx +17 -0
  48. package/src/Mask.tsx +21 -0
  49. package/src/MediaQuery.ts +46 -0
  50. package/src/Meta.tsx +9 -0
  51. package/src/Modal.tsx +248 -0
  52. package/src/ModalSheet.tsx +58 -0
  53. package/src/NumberPickerActionSheet.tsx +66 -0
  54. package/src/Page.tsx +133 -0
  55. package/src/Permissions.ts +44 -0
  56. package/src/PickerSelect.tsx +553 -0
  57. package/src/Pill.tsx +24 -0
  58. package/src/Pog.tsx +87 -0
  59. package/src/ProgressBar.tsx +55 -0
  60. package/src/ScrollView.tsx +2 -0
  61. package/src/SegmentedControl.tsx +102 -0
  62. package/src/SelectList.tsx +89 -0
  63. package/src/SideDrawer.tsx +62 -0
  64. package/src/Spinner.tsx +20 -0
  65. package/src/SplitPage.native.tsx +160 -0
  66. package/src/SplitPage.tsx +302 -0
  67. package/src/Switch.tsx +19 -0
  68. package/src/Table.tsx +87 -0
  69. package/src/TableHeader.tsx +36 -0
  70. package/src/TableHeaderCell.tsx +76 -0
  71. package/src/TableRow.tsx +87 -0
  72. package/src/TapToEdit.tsx +221 -0
  73. package/src/Text.tsx +131 -0
  74. package/src/TextArea.tsx +16 -0
  75. package/src/TextField.tsx +401 -0
  76. package/src/TextFieldNumberActionSheet.tsx +61 -0
  77. package/src/Toast.tsx +106 -0
  78. package/src/Tooltip.tsx +269 -0
  79. package/src/UnifiedScreens.ts +24 -0
  80. package/src/Unifier.ts +371 -0
  81. package/src/Utilities.tsx +159 -0
  82. package/src/WithLabel.tsx +57 -0
  83. package/src/dayjsExtended.ts +10 -0
  84. package/src/index.tsx +1347 -0
  85. package/src/polyfill.d.ts +11 -0
  86. package/src/tableContext.tsx +80 -0
  87. package/src/useStoredState.ts +39 -0
package/src/Box.tsx ADDED
@@ -0,0 +1,308 @@
1
+ /* eslint-disable react/prop-types */
2
+ import React, {useImperativeHandle} from "react";
3
+ import {
4
+ KeyboardAvoidingView,
5
+ Platform,
6
+ Pressable,
7
+ SafeAreaView,
8
+ ScrollView,
9
+ View,
10
+ } from "react-native";
11
+
12
+ import {UnifiedTheme} from ".";
13
+ import {AlignContent, AlignItems, AlignSelf, BoxProps, JustifyContent, SPACING} from "./Common";
14
+ import {mediaQueryLargerThan} from "./MediaQuery";
15
+ import {Unifier} from "./Unifier";
16
+
17
+ const ALIGN_CONTENT = {
18
+ start: "flex-start",
19
+ end: "flex-end",
20
+ center: "center",
21
+ between: "space-between",
22
+ around: "space-around",
23
+ stretch: "stretch",
24
+ };
25
+
26
+ const ALIGN_ITEMS = {
27
+ start: "flex-start",
28
+ end: "flex-end",
29
+ center: "center",
30
+ baseline: "baseline",
31
+ stretch: "stretch",
32
+ };
33
+
34
+ const ALIGN_SELF = {
35
+ auto: "auto",
36
+ baseline: "baseline",
37
+ start: "flex-start",
38
+ end: "flex-end",
39
+ center: "center",
40
+ between: "space-between",
41
+ around: "space-around",
42
+ stretch: "stretch",
43
+ };
44
+
45
+ const BORDER_WIDTH = 1;
46
+
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: {
72
+ [prop: string]: (
73
+ value: any,
74
+ all: {[prop: string]: any}
75
+ ) => {[style: string]: string | number} | {};
76
+ } = {
77
+ alignItems: (value: AlignItems) => ({alignItems: ALIGN_ITEMS[value]}),
78
+ alignContent: (value: AlignContent) => ({alignContent: ALIGN_CONTENT[value]}),
79
+ alignSelf: (value: AlignSelf) => ({alignSelf: ALIGN_SELF[value]}),
80
+ color: (value: keyof UnifiedTheme) => ({backgroundColor: Unifier.theme[value]}),
81
+ direction: (value: any) => ({flexDirection: value, display: "flex"}),
82
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
+ smDirection: (value: any) =>
84
+ mediaQueryLargerThan("sm") ? {flexDirection: value, display: "flex"} : {},
85
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
86
+ mdDirection: (value: any) =>
87
+ mediaQueryLargerThan("md") ? {flexDirection: value, display: "flex"} : {},
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ lgDirection: (value: any) =>
90
+ mediaQueryLargerThan("lg") ? {flexDirection: value, display: "flex"} : {},
91
+ display: (value: any) => {
92
+ return value === "flex" ? {flex: undefined} : {flex: 0, flexDirection: "row"};
93
+ },
94
+ flex: (value: string) => {
95
+ if (value === "grow") {
96
+ return {flexGrow: 1, flexShrink: 1, display: "flex"};
97
+ } else if (value === "shrink") {
98
+ return {flexShrink: 1, display: "flex"};
99
+ } else {
100
+ return {flex: 0, display: "flex"};
101
+ }
102
+ },
103
+ justifyContent: (value: JustifyContent) => ({justifyContent: ALIGN_CONTENT[value]}),
104
+ height: (value) => {
105
+ if (props.border && !isNaN(Number(value))) {
106
+ return {height: Number(value) + 2 * 2};
107
+ } else {
108
+ return {height: value};
109
+ }
110
+ },
111
+ margin: (value) => ({margin: value * SPACING}),
112
+ marginRight: (value) => ({marginRight: value * SPACING}),
113
+ marginLeft: (value) => ({marginLeft: value * SPACING}),
114
+ marginTop: (value) => ({marginTop: value * SPACING}),
115
+ marginBottom: (value) => ({marginBottom: value * SPACING}),
116
+ paddingX: (value) => ({paddingLeft: value * SPACING, paddingRight: value * SPACING}),
117
+ paddingY: (value) => ({paddingTop: value * SPACING, paddingBottom: value * SPACING}),
118
+ padding: (value) => ({padding: value * SPACING}),
119
+ zIndex: (value) => ({zIndex: value ? value : undefined}),
120
+ position: (value) => ({position: value}),
121
+ top: (top) => ({top: top ? 0 : undefined}),
122
+ bottom: (bottom) => ({bottom: bottom ? 0 : undefined}),
123
+ right: (right) => ({right: right ? 0 : undefined}),
124
+ left: (left) => ({left: left ? 0 : undefined}),
125
+ rounding: (rounding, allProps) => {
126
+ if (rounding === "circle") {
127
+ if (!allProps.height && !allProps.width) {
128
+ console.warn("Cannot use Box rounding='circle' without height or width.");
129
+ return {borderRadius: undefined};
130
+ }
131
+ return {borderRadius: allProps.height || allProps.width};
132
+ }
133
+
134
+ if (rounding === "pill") {
135
+ return {borderRadius: 999};
136
+ }
137
+
138
+ if (typeof rounding === "number") {
139
+ return {borderRadius: rounding * 4};
140
+ }
141
+
142
+ return {borderRadius: undefined};
143
+ },
144
+ overflow: (value) => {
145
+ if (value === "scrollY" || value === "scroll") {
146
+ return {overflow: "scroll"};
147
+ }
148
+ return {overflow: value};
149
+ },
150
+ width: (value) => {
151
+ if (props.border && !isNaN(Number(value))) {
152
+ return {width: Number(value) + 2 * 2};
153
+ } else {
154
+ return {width: value};
155
+ }
156
+ },
157
+ wrap: (value) => ({flexWrap: value ? "wrap" : "nowrap", alignItems: "flex-start"}),
158
+ shadow: (value) => {
159
+ if (!value) {
160
+ return {};
161
+ }
162
+ if (Platform.OS === "ios" || Platform.OS === "web") {
163
+ return {
164
+ shadowColor: "#999",
165
+ shadowOffset: {
166
+ width: 2,
167
+ height: 2,
168
+ },
169
+ shadowRadius: 2,
170
+ shadowOpacity: 1.0,
171
+ };
172
+ } else {
173
+ return {elevation: 4};
174
+ }
175
+ },
176
+ border: (value: keyof UnifiedTheme) => {
177
+ if (!value) {
178
+ return {};
179
+ }
180
+ return {borderColor: Unifier.theme[value], borderWidth: BORDER_WIDTH};
181
+ },
182
+ borderBottom: (value: keyof UnifiedTheme) => {
183
+ if (!value) {
184
+ return {};
185
+ }
186
+ return {borderBottomColor: Unifier.theme[value], borderBottomWidth: BORDER_WIDTH};
187
+ },
188
+ borderTop: (value: keyof UnifiedTheme) => {
189
+ if (!value) {
190
+ return {};
191
+ }
192
+ return {borderTopColor: Unifier.theme[value], borderTopWidth: BORDER_WIDTH};
193
+ },
194
+ borderRight: (value: keyof UnifiedTheme) => {
195
+ if (!value) {
196
+ return {};
197
+ }
198
+ return {borderRightColor: Unifier.theme[value], borderRightWidth: BORDER_WIDTH};
199
+ },
200
+ borderLeft: (value: keyof UnifiedTheme) => {
201
+ if (!value) {
202
+ return {};
203
+ }
204
+ return {borderLeftColor: Unifier.theme[value], borderLeftWidth: BORDER_WIDTH};
205
+ },
206
+ };
207
+
208
+ const scrollRef = props.scrollRef ?? React.createRef();
209
+
210
+ const propsToStyle = (): any => {
211
+ let style: any = {};
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));
216
+ } else if (prop !== "children" && prop !== "onClick") {
217
+ style[prop] = value;
218
+ // console.warn(`Box: unknown property ${prop}`);
219
+ }
220
+ }
221
+
222
+ if (props.wrap && props.alignItems) {
223
+ console.warn("React Native doesn't support wrap and alignItems together.");
224
+ }
225
+
226
+ // Finally, dangerously set overrides.
227
+ if (props.dangerouslySetInlineStyle) {
228
+ style = {...style, ...props.dangerouslySetInlineStyle.__style};
229
+ }
230
+
231
+ return style;
232
+ };
233
+
234
+ const onHoverIn = () => {
235
+ props.onHoverStart?.();
236
+ };
237
+
238
+ const onHoverOut = () => {
239
+ props.onHoverEnd?.();
240
+ };
241
+
242
+ let box;
243
+
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
+ }
272
+
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
+ );
306
+ }
307
+ return box;
308
+ });
package/src/Button.tsx ADDED
@@ -0,0 +1,219 @@
1
+ import debounce from "lodash/debounce";
2
+ import React, {useState} from "react";
3
+ import {ActivityIndicator, Pressable, View} from "react-native";
4
+
5
+ import {Box} from "./Box";
6
+ import {ButtonColor, Color, IconName, IconPrefix, TooltipDirection, UnifiedTheme} from "./Common";
7
+ import {Icon} from "./Icon";
8
+ import {Modal} from "./Modal";
9
+ import {Text} from "./Text";
10
+ import {Tooltip} from "./Tooltip";
11
+ import {Unifier} from "./Unifier";
12
+
13
+ export interface ButtonProps {
14
+ children?: React.ReactElement;
15
+ text: string;
16
+ // TODO make this work for all colors
17
+ color?: ButtonColor | Color;
18
+ // default gray
19
+ disabled?: boolean; // default false
20
+ inline?: boolean; // default false
21
+ size?: "sm" | "md" | "lg"; // default md
22
+ type?: "solid" | "ghost" | "outline"; // default solid
23
+ loading?: boolean;
24
+ onClick: any;
25
+ icon?: IconName;
26
+ iconPrefix?: IconPrefix;
27
+ iconColor?: ButtonColor | Color;
28
+ withConfirmation?: boolean;
29
+ confirmationText?: string;
30
+ confirmationHeading?: string;
31
+ shape?: "rounded" | "pill";
32
+ testID?: string;
33
+ tooltip?: {
34
+ text: string;
35
+ idealDirection?: TooltipDirection;
36
+ };
37
+ }
38
+
39
+ const buttonTextColor: {[buttonColor: string]: "white" | "darkGray"} = {
40
+ blue: "white",
41
+ lightGray: "darkGray",
42
+ red: "white",
43
+ transparent: "white",
44
+ white: "darkGray",
45
+ primary: "white",
46
+ secondary: "white",
47
+ accent: "white",
48
+ tertiary: "white",
49
+ facebook: "white",
50
+ twitter: "white",
51
+ google: "white",
52
+ };
53
+
54
+ const HEIGHTS = {
55
+ sm: 36,
56
+ md: 40,
57
+ lg: 48,
58
+ };
59
+
60
+ export function Button({
61
+ disabled = false,
62
+ type = "solid",
63
+ loading: propsLoading,
64
+ children,
65
+ text,
66
+ inline = false,
67
+ icon,
68
+ iconPrefix,
69
+ size = "md",
70
+ onClick,
71
+ color = "gray",
72
+ withConfirmation = false,
73
+ confirmationText = "Are you sure you want to continue?",
74
+ confirmationHeading = "Confirm",
75
+ shape = "rounded",
76
+ testID,
77
+ tooltip,
78
+ }: ButtonProps) {
79
+ const [loading, setLoading] = useState(propsLoading);
80
+ const [showConfirmation, setShowConfirmation] = useState(false);
81
+
82
+ const getBackgroundColor = (backgroundColor: string): string => {
83
+ if (type === "ghost" || type === "outline") {
84
+ return "transparent";
85
+ } else {
86
+ return Unifier.theme[backgroundColor as keyof UnifiedTheme];
87
+ }
88
+ };
89
+
90
+ const getTextColor = (textColor: Color): Color => {
91
+ if (type === "ghost" || type === "outline") {
92
+ return textColor;
93
+ } else if (textColor === undefined) {
94
+ return "darkGray";
95
+ } else {
96
+ return buttonTextColor[textColor] || "white";
97
+ }
98
+ };
99
+
100
+ const getBorderColor = (borderColor: string): string => {
101
+ if (type === "outline") {
102
+ return Unifier.theme[getTextColor(borderColor as Color)];
103
+ } else {
104
+ return "transparent";
105
+ }
106
+ };
107
+
108
+ if (color === "gray") {
109
+ color = "lightGray";
110
+ }
111
+
112
+ const renderConfirmation = () => {
113
+ return (
114
+ <Modal
115
+ heading={confirmationHeading}
116
+ primaryButtonOnClick={() => {
117
+ onClick();
118
+ setShowConfirmation(false);
119
+ }}
120
+ primaryButtonText="Confirm"
121
+ secondaryButtonOnClick={(): void => setShowConfirmation(false)}
122
+ secondaryButtonText="Cancel"
123
+ size="sm"
124
+ visible={showConfirmation}
125
+ onDismiss={(): void => {
126
+ setShowConfirmation(false);
127
+ }}
128
+ >
129
+ <Text>{confirmationText}</Text>
130
+ </Modal>
131
+ );
132
+ };
133
+
134
+ const renderButton = () => {
135
+ return (
136
+ <View>
137
+ <Pressable
138
+ disabled={disabled || loading}
139
+ style={{
140
+ alignSelf: inline === true ? undefined : "stretch",
141
+ height: HEIGHTS[size || "md"],
142
+ backgroundColor: getBackgroundColor(color),
143
+ // width: inline === true ? undefined : "100%",
144
+ flexShrink: inline ? 1 : 0,
145
+ // flexGrow: inline ? 0 : 1,
146
+ alignItems: "center",
147
+ justifyContent: "center",
148
+ borderRadius: shape === "pill" ? 999 : 5,
149
+ borderColor: getBorderColor(color),
150
+ borderWidth: type === "outline" ? 2 : 0,
151
+ opacity: disabled ? 0.4 : 1,
152
+ flexDirection: "row",
153
+ paddingHorizontal: 8 * 2,
154
+ }}
155
+ testID={testID}
156
+ onPress={debounce(
157
+ async () => {
158
+ Unifier.utils.haptic();
159
+ setLoading(true);
160
+ try {
161
+ if (withConfirmation && !showConfirmation) {
162
+ setShowConfirmation(true);
163
+ } else if (onClick) {
164
+ await onClick();
165
+ }
166
+ } catch (e) {
167
+ setLoading(false);
168
+ throw e;
169
+ }
170
+ setLoading(false);
171
+ },
172
+ 500,
173
+ {leading: true}
174
+ )}
175
+ >
176
+ {icon !== undefined && (
177
+ <Box paddingX={2}>
178
+ <Icon
179
+ color={getTextColor(color as Color)}
180
+ name={icon}
181
+ prefix={iconPrefix || "far"}
182
+ size={size}
183
+ />
184
+ </Box>
185
+ )}
186
+ {Boolean(children) && children}
187
+ {Boolean(text) && (
188
+ <Text
189
+ color={getTextColor(color as Color)}
190
+ font="button"
191
+ inline={inline}
192
+ size={size}
193
+ skipLinking
194
+ weight="bold"
195
+ >
196
+ {text}
197
+ </Text>
198
+ )}
199
+ {Boolean(loading) && (
200
+ <Box marginLeft={2}>
201
+ <ActivityIndicator color={getTextColor(color as Color)} size="small" />
202
+ </Box>
203
+ )}
204
+ </Pressable>
205
+ {Boolean(withConfirmation) && renderConfirmation()}
206
+ </View>
207
+ );
208
+ };
209
+
210
+ if (tooltip) {
211
+ return (
212
+ <Tooltip idealDirection={tooltip.idealDirection} text={tooltip.text}>
213
+ {renderButton()}
214
+ </Tooltip>
215
+ );
216
+ } else {
217
+ return renderButton();
218
+ }
219
+ }
package/src/Card.tsx ADDED
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {BoxProps} from "./Common";
5
+
6
+ export class Card extends React.Component<BoxProps, {}> {
7
+ render() {
8
+ return (
9
+ <Box
10
+ color={this.props.color || "white"}
11
+ direction="column"
12
+ display="flex"
13
+ padding={this.props.padding || 4}
14
+ rounding={3}
15
+ shadow
16
+ width={this.props.width}
17
+ {...this.props}
18
+ >
19
+ {this.props.children}
20
+ </Box>
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,118 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {BoxColor, CheckBoxProps} from "./Common";
5
+ import {Icon} from "./Icon";
6
+ import {Text} from "./Text";
7
+
8
+ export function CheckBox({
9
+ color,
10
+ checked,
11
+ size,
12
+ radio,
13
+ label,
14
+ labelColor,
15
+ subLabel,
16
+ disabled,
17
+ onChange,
18
+ onClick,
19
+ indeterminate,
20
+ }: CheckBoxProps): React.ReactElement {
21
+ if (checked && indeterminate) {
22
+ console.error("CheckBox cannot be checked and indeterminate at the same time");
23
+ }
24
+
25
+ const doOnClick = () => {
26
+ if (disabled) {
27
+ return;
28
+ }
29
+ if (!indeterminate) {
30
+ onChange({value: !checked});
31
+ }
32
+ onClick && onClick();
33
+ };
34
+
35
+ const renderCheckBox = () => {
36
+ let bgColor: BoxColor;
37
+ if (disabled) {
38
+ bgColor = "gray";
39
+ } else if (checked) {
40
+ bgColor = color || "darkGray";
41
+ } else {
42
+ bgColor = "white";
43
+ }
44
+ return (
45
+ <Box
46
+ border={color || "darkGray"}
47
+ color={bgColor}
48
+ height={size === "sm" ? 16 : 24}
49
+ rounding={radio ? "circle" : size === "sm" ? 2 : 3}
50
+ width={size === "sm" ? 16 : 24}
51
+ onClick={doOnClick}
52
+ >
53
+ <Box
54
+ alignItems="center"
55
+ direction="column"
56
+ display="flex"
57
+ height="100%"
58
+ justifyContent="center"
59
+ width="100%"
60
+ >
61
+ {checked && (
62
+ <Icon color="white" name="check" prefix="fas" size={size === "sm" ? "sm" : "md"} />
63
+ )}
64
+ {indeterminate && (
65
+ <Icon
66
+ color={color || "darkGray"}
67
+ name="circle"
68
+ prefix="fas"
69
+ size={size === "sm" ? "sm" : "md"}
70
+ />
71
+ )}
72
+ </Box>
73
+ </Box>
74
+ );
75
+ };
76
+
77
+ return (
78
+ <Box
79
+ alignItems="center"
80
+ direction="row"
81
+ display="flex"
82
+ maxHeight={60}
83
+ paddingY={1}
84
+ width="100%"
85
+ >
86
+ <Box
87
+ display="flex"
88
+ justifyContent="center"
89
+ maxWidth={size === "sm" ? 14 : 20}
90
+ width={size === "sm" ? 14 : 20}
91
+ >
92
+ {renderCheckBox()}
93
+ </Box>
94
+ <Box
95
+ direction="column"
96
+ display="flex"
97
+ height="100%"
98
+ justifyContent="center"
99
+ marginLeft={4}
100
+ onClick={doOnClick}
101
+ >
102
+ <Text
103
+ color={labelColor || "darkGray"}
104
+ numberOfLines={subLabel ? 1 : 2}
105
+ size={size}
106
+ weight="bold"
107
+ >
108
+ {label}
109
+ </Text>
110
+ {Boolean(subLabel) && (
111
+ <Text color={labelColor || "darkGray"} size="sm" weight="bold">
112
+ {subLabel!}
113
+ </Text>
114
+ )}
115
+ </Box>
116
+ </Box>
117
+ );
118
+ }