From b48088a066a0489d12b471c16665e45984280fbd Mon Sep 17 00:00:00 2001 From: jameswilddev Date: Thu, 18 Nov 2021 14:54:45 +0000 Subject: [PATCH] Add checkbox component. --- components/createCheckboxComponent/index.tsx | 160 ++++ components/createCheckboxComponent/readme.md | 66 ++ components/createCheckboxComponent/unit.tsx | 760 +++++++++++++++++++ index.ts | 1 + readme.md | 1 + 5 files changed, 988 insertions(+) create mode 100644 components/createCheckboxComponent/index.tsx create mode 100644 components/createCheckboxComponent/readme.md create mode 100644 components/createCheckboxComponent/unit.tsx diff --git a/components/createCheckboxComponent/index.tsx b/components/createCheckboxComponent/index.tsx new file mode 100644 index 00000000..2d07a0f4 --- /dev/null +++ b/components/createCheckboxComponent/index.tsx @@ -0,0 +1,160 @@ +import * as React from "react"; +import { StyleSheet, View, Text, ViewStyle, TextStyle } from "react-native"; +import type { CheckboxStateStyle } from "../../types/CheckboxStateStyle"; +import type { CheckboxStyle } from "../../types/CheckboxStyle"; +import { Hitbox } from "../Hitbox"; + +const createViewStyle = ( + checkboxStyle: CheckboxStyle, + checkboxStateStyle: CheckboxStateStyle +): ViewStyle => { + const output: ViewStyle = { + backgroundColor: checkboxStateStyle.backgroundColor, + width: checkboxStyle.boxSize, + height: checkboxStyle.boxSize, + justifyContent: `center`, + alignItems: `center`, + }; + + if (checkboxStateStyle.border !== null) { + output.borderWidth = checkboxStateStyle.border.width; + output.borderColor = checkboxStateStyle.border.color; + } + + if (checkboxStateStyle.radius !== 0) { + output.borderRadius = checkboxStateStyle.radius; + } + + const relativeBorderWidth = + (checkboxStyle.enabledFalse.border === null + ? 0 + : checkboxStyle.enabledFalse.border.width) - + (checkboxStateStyle.border === null ? 0 : checkboxStateStyle.border.width); + + if (relativeBorderWidth !== 0) { + output.margin = relativeBorderWidth; + } + + return output; +}; + +const createTextStyle = ( + checkboxStyle: CheckboxStyle, + checkboxStateStyle: CheckboxStateStyle +): TextStyle => { + const output: TextStyle = { + fontFamily: checkboxStyle.fontFamily, + fontSize: checkboxStyle.fontSize, + lineHeight: checkboxStyle.fontSize * 1.4, + color: checkboxStateStyle.color, + }; + + if (checkboxStyle.boxLabelSpacing !== 0) { + output.paddingLeft = checkboxStyle.boxLabelSpacing; + } + + return output; +}; + +/** + * Creates a React component representing a form checkbox. + * @param checkboxStyle The style to apply to the checkbox. + * @returns The created React component. + */ +export const createCheckboxComponent = ( + checkboxStyle: CheckboxStyle +): React.FunctionComponent<{ + /** + * When true, the checkbox is checked. It is otherwise unchecked. + */ + value: boolean; + + /** + * Invoked when the checkbox is pressed. + * @param to True when the checkbox is changing from unchecked to checked, + * otherwise, false. + */ + onChange(to: boolean): void; + + /** + * When true, the checkbox will show alternative styles and will not accept + * input. It will otherwise show its default styles and accept input. + */ + disabled: boolean; +}> => { + const styles = StyleSheet.create({ + hitbox: { + width: `100%`, + flexDirection: `row`, + }, + disabledFalseView: createViewStyle( + checkboxStyle, + checkboxStyle.disabledFalse + ), + disabledTrueView: createViewStyle( + checkboxStyle, + checkboxStyle.disabledTrue + ), + enabledFalseView: createViewStyle( + checkboxStyle, + checkboxStyle.enabledFalse + ), + enabledTrueView: createViewStyle(checkboxStyle, checkboxStyle.enabledTrue), + disabledFalseText: createTextStyle( + checkboxStyle, + checkboxStyle.disabledFalse + ), + disabledTrueText: createTextStyle( + checkboxStyle, + checkboxStyle.disabledTrue + ), + enabledFalseText: createTextStyle( + checkboxStyle, + checkboxStyle.enabledFalse + ), + enabledTrueText: createTextStyle(checkboxStyle, checkboxStyle.enabledTrue), + }); + + return ({ value, onChange, disabled, children }) => ( + { + onChange(!value); + }} + style={styles.hitbox} + > + + {value + ? disabled + ? checkboxStyle.disabledTrue.boxChild + : checkboxStyle.enabledTrue.boxChild + : disabled + ? checkboxStyle.disabledFalse.boxChild + : checkboxStyle.enabledFalse.boxChild} + + + {children} + + + ); +}; diff --git a/components/createCheckboxComponent/readme.md b/components/createCheckboxComponent/readme.md new file mode 100644 index 00000000..9cdcbd65 --- /dev/null +++ b/components/createCheckboxComponent/readme.md @@ -0,0 +1,66 @@ +# `react-native-app-helpers/createCheckboxComponent` + +Creates a React component representing a form checkbox. + +## Usage + +```tsx +import { createCheckboxComponent } from "react-native-app-helpers"; + +const ExampleCheckbox = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: 'blue', + color: 'yellow', + boxChild: DF, + radius: 3, + border: { + width: 5, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: 'blue', + color: 'yellow', + boxChild: DT, + radius: 3, + border: { + width: 5, + color: `red`, + }, + }, + enabledFalse: { + backgroundColor: 'blue', + color: 'yellow', + boxChild: EF, + radius: 3, + border: { + width: 5, + color: `red`, + }, + }, + enabledTrue: { + backgroundColor: 'blue', + color: 'yellow', + boxChild: ET, + radius: 3, + border: { + width: 5, + color: `red`, + }, + }, +}); + +const ExampleScreen = () => { + const [value, setValue] = React.useState(false); + + return ( + + Example Label + + ); +}; +``` diff --git a/components/createCheckboxComponent/unit.tsx b/components/createCheckboxComponent/unit.tsx new file mode 100644 index 00000000..6c5f33b1 --- /dev/null +++ b/components/createCheckboxComponent/unit.tsx @@ -0,0 +1,760 @@ +import * as React from "react"; +import { Text, View } from "react-native"; +import { + createCheckboxComponent, + unwrapRenderedFunctionComponent, + Hitbox, +} from "../.."; + +test(`renders as expected when enabled and false`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Enabled False Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when enabled and true`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Enabled True Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when disabled and false`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Disabled False Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when disabled and true`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Disabled True Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when without borders`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: null, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: null, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: null, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: null, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Enabled False Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when without spacing`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 0, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Enabled False Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`renders as expected when without radius`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 0, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 0, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 0, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 0, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + expect(unwrapRenderedFunctionComponent(rendered)).toEqual( + + + Example Enabled False Box Child + + + Example Label + + + ); + + expect(onChange).not.toHaveBeenCalled(); +}); + +test(`raises the expected event when pressed when false`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + unwrapRenderedFunctionComponent(rendered).props[`onPress`](); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith(true); +}); + +test(`raises the expected event when pressed when true`, () => { + const Component = createCheckboxComponent({ + fontFamily: `Example Font Family`, + fontSize: 16, + boxSize: 14, + boxLabelSpacing: 5, + disabledFalse: { + backgroundColor: `blue`, + color: `yellow`, + boxChild: Example Disabled False Box Child, + radius: 3, + border: { + width: 9, + color: `red`, + }, + }, + disabledTrue: { + backgroundColor: `purple`, + color: `green`, + boxChild: Example Disabled True Box Child, + radius: 9, + border: { + width: 12, + color: `cyan`, + }, + }, + enabledFalse: { + backgroundColor: `orange`, + color: `black`, + boxChild: Example Enabled False Box Child, + radius: 4, + border: { + width: 24, + color: `magenta`, + }, + }, + enabledTrue: { + backgroundColor: `white`, + color: `brown`, + boxChild: Example Enabled True Box Child, + radius: 19, + border: { + width: 27, + color: `gray`, + }, + }, + }); + const onChange = jest.fn(); + + const rendered = ( + + Example Label + + ); + + unwrapRenderedFunctionComponent(rendered).props[`onPress`](); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith(false); +}); diff --git a/index.ts b/index.ts index aeeb46a2..777c78b1 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,7 @@ export { ContainerFillingKeyboardAvoidingView } from "./components/ContainerFill export { ContainerFillingScrollView } from "./components/ContainerFillingScrollView"; export { createButtonComponent } from "./components/createButtonComponent"; export { createCardComponent } from "./components/createCardComponent"; +export { createCheckboxComponent } from "./components/createCheckboxComponent"; export { createDropDownComponent } from "./components/createDropDownComponent"; export { createFiniteStateMachineRoutingComponent } from "./components/createFiniteStateMachineRoutingComponent"; export { createFixedWidthComponent } from "./components/createFixedWidthComponent"; diff --git a/readme.md b/readme.md index 87a7f382..9552bda2 100644 --- a/readme.md +++ b/readme.md @@ -21,6 +21,7 @@ import { createTextComponent } from "react-native-app-helpers"; - [ContainerFillingScrollView](./components/ContainerFillingScrollView/readme.md) - [createButtonComponent](./components/createButtonComponent/readme.md) - [createCardComponent](./components/createCardComponent/readme.md) +- [createCheckboxComponent](./components/createCheckboxComponent/readme.md) - [createDropDownComponent](./components/createDropDownComponent/readme.md) - [createFiniteStateMachineRoutingComponent](./components/createFiniteStateMachineRoutingComponent/readme.md) - [createFixedWidthComponent](./components/createFixedWidthComponent/readme.md)