diff --git a/src/components/Form/FieldCheckbox/FieldCheckbox.spec.tsx b/src/components/Form/FieldCheckbox/FieldCheckbox.spec.tsx new file mode 100644 index 000000000..0b1c780cf --- /dev/null +++ b/src/components/Form/FieldCheckbox/FieldCheckbox.spec.tsx @@ -0,0 +1,129 @@ +import { FormLabel } from '@chakra-ui/react'; +import { expect, test, vi } from 'vitest'; +import { z } from 'zod'; + +import { render, screen, setupUser } from '@/tests/utils'; + +import { FormField } from '../FormField'; +import { FormFieldController } from '../FormFieldController'; +import { FormMocked } from '../form-test-utils'; + +test('update value', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: true }); +}); + +test('double click', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); + +test('default value', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); + +test('disabled', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); diff --git a/src/components/Form/FieldCheckbox/docs.stories.tsx b/src/components/Form/FieldCheckbox/docs.stories.tsx new file mode 100644 index 000000000..648adca25 --- /dev/null +++ b/src/components/Form/FieldCheckbox/docs.stories.tsx @@ -0,0 +1,143 @@ +import { Box, Button, Stack } from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { + Form, + FormField, + FormFieldController, + FormFieldHelper, + FormFieldLabel, +} from '../'; + +export default { + title: 'Form/FieldCheckbox', +}; + +type FormSchema = z.infer>; +const zFormSchema = () => + z.object({ + doit: z.literal(true), + }); + +const formOptions = { + mode: 'onBlur', + resolver: zodResolver(zFormSchema()), +} as const; + +export const Default = () => { + const form = useForm(formOptions); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const DefaultValues = () => { + const form = useForm({ + ...formOptions, + defaultValues: { + doit: true, + }, + }); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const Disabled = () => { + const form = useForm(formOptions); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const DisabledDefaultValues = () => { + const form = useForm({ + ...formOptions, + defaultValues: { + doit: true, + }, + }); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; diff --git a/src/components/Form/FieldCheckbox/index.tsx b/src/components/Form/FieldCheckbox/index.tsx new file mode 100644 index 000000000..48f4ecbd8 --- /dev/null +++ b/src/components/Form/FieldCheckbox/index.tsx @@ -0,0 +1,58 @@ +import { ReactNode } from 'react'; + +import { Checkbox, CheckboxProps, Flex, FlexProps } from '@chakra-ui/react'; +import { + Controller, + ControllerRenderProps, + FieldPath, + FieldValues, +} from 'react-hook-form'; + +import { FieldCommonProps } from '../FormFieldController'; +import { FormFieldError } from '../FormFieldError'; + +export type CheckboxRootProps = Pick; + +export type FieldCheckboxProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + type: 'checkbox'; + label?: ReactNode; + checkboxProps?: RemoveFromType< + RemoveFromType< + Omit, + CheckboxRootProps + >, + ControllerRenderProps + >; + containerProps?: FlexProps; +} & CheckboxRootProps & + FieldCommonProps; + +export const FieldCheckbox = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>( + props: FieldCheckboxProps +) => { + return ( + ( + + + {props.label} + + + + )} + /> + ); +}; diff --git a/src/components/Form/FieldSwitch/FieldSwitch.spec.tsx b/src/components/Form/FieldSwitch/FieldSwitch.spec.tsx new file mode 100644 index 000000000..1a22defd6 --- /dev/null +++ b/src/components/Form/FieldSwitch/FieldSwitch.spec.tsx @@ -0,0 +1,129 @@ +import { FormLabel } from '@chakra-ui/react'; +import { expect, test, vi } from 'vitest'; +import { z } from 'zod'; + +import { render, screen, setupUser } from '@/tests/utils'; + +import { FormField } from '../FormField'; +import { FormFieldController } from '../FormFieldController'; +import { FormMocked } from '../form-test-utils'; + +test('update value', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: true }); +}); + +test('double click', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); + +test('default value', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); + +test('disabled', async () => { + const user = setupUser(); + const mockedSubmit = vi.fn(); + + render( + + {({ form }) => ( + + Should I do something? + + + )} + + ); + await user.click(screen.getByLabelText('Should I do something?')); + await user.click(screen.getByRole('button', { name: 'Submit' })); + expect(mockedSubmit).toHaveBeenCalledWith({ doit: false }); +}); diff --git a/src/components/Form/FieldSwitch/docs.stories.tsx b/src/components/Form/FieldSwitch/docs.stories.tsx new file mode 100644 index 000000000..147cc454f --- /dev/null +++ b/src/components/Form/FieldSwitch/docs.stories.tsx @@ -0,0 +1,143 @@ +import { Box, Button, Stack } from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { + Form, + FormField, + FormFieldController, + FormFieldHelper, + FormFieldLabel, +} from '../'; + +export default { + title: 'Form/FieldSwitch', +}; + +type FormSchema = z.infer>; +const zFormSchema = () => + z.object({ + doit: z.literal(true), + }); + +const formOptions = { + mode: 'onBlur', + resolver: zodResolver(zFormSchema()), +} as const; + +export const Default = () => { + const form = useForm(formOptions); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const DefaultValues = () => { + const form = useForm({ + ...formOptions, + defaultValues: { + doit: true, + }, + }); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const Disabled = () => { + const form = useForm(formOptions); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; + +export const DisabledDefaultValues = () => { + const form = useForm({ + ...formOptions, + defaultValues: { + doit: true, + }, + }); + + return ( +
console.log(values)}> + + + Should I do something? + + Helper + + + + + +
+ ); +}; diff --git a/src/components/Form/FieldSwitch/index.tsx b/src/components/Form/FieldSwitch/index.tsx new file mode 100644 index 000000000..b30b425ef --- /dev/null +++ b/src/components/Form/FieldSwitch/index.tsx @@ -0,0 +1,60 @@ +import { ReactNode } from 'react'; + +import { Flex, FlexProps, Switch, SwitchProps } from '@chakra-ui/react'; +import { + Controller, + ControllerRenderProps, + FieldPath, + FieldValues, +} from 'react-hook-form'; + +import { FieldCommonProps } from '../FormFieldController'; +import { FormFieldError } from '../FormFieldError'; + +type SwitchRootProps = Pick; + +export type FieldSwitchProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + type: 'switch'; + label?: ReactNode; + switchProps?: RemoveFromType< + RemoveFromType< + Omit, + SwitchRootProps + >, + ControllerRenderProps + >; + containerProps?: FlexProps; +} & SwitchRootProps & + FieldCommonProps; + +export const FieldSwitch = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>( + props: FieldSwitchProps +) => { + return ( + ( + + + {props.label} + + + + )} + /> + ); +}; diff --git a/src/components/Form/FormFieldController.tsx b/src/components/Form/FormFieldController.tsx index 316650ba2..4ade904f7 100644 --- a/src/components/Form/FormFieldController.tsx +++ b/src/components/Form/FormFieldController.tsx @@ -9,6 +9,7 @@ import { import { useFormField } from '@/components/Form/FormField'; +import { FieldCheckbox, FieldCheckboxProps } from './FieldCheckbox'; import { FieldCheckboxes, FieldCheckboxesProps } from './FieldCheckboxes'; import { FieldCurrency, FieldCurrencyProps } from './FieldCurrency'; import { FieldDate, FieldDateProps } from './FieldDate'; @@ -17,6 +18,7 @@ import { FieldOtp, FieldOtpProps } from './FieldOtp'; import { FieldPassword, FieldPasswordProps } from './FieldPassword'; import { FieldRadios, FieldRadiosProps } from './FieldRadios'; import { FieldSelect, FieldSelectProps } from './FieldSelect'; +import { FieldSwitch, FieldSwitchProps } from './FieldSwitch'; import { FieldText, FieldTextProps } from './FieldText'; import { FieldTextarea, FieldTextareaProps } from './FieldTextarea'; @@ -45,6 +47,8 @@ export type FormFieldControllerProps< > = | FieldCustomProps // -- ADD NEW FIELD PROPS TYPE HERE -- + | FieldCheckboxProps + | FieldSwitchProps | FieldTextProps | FieldTextareaProps | FieldSelectProps @@ -107,6 +111,12 @@ export const FormFieldController = < case 'radios': return ; + case 'checkbox': + return ; + + case 'switch': + return ; + // -- ADD NEW FIELD COMPONENT HERE -- } }; diff --git a/src/theme/components/checkbox.ts b/src/theme/components/checkbox.ts index 4c998a28a..c0538ce10 100644 --- a/src/theme/components/checkbox.ts +++ b/src/theme/components/checkbox.ts @@ -1,6 +1,11 @@ -import { defineStyleConfig } from '@chakra-ui/react'; +import { checkboxAnatomy } from '@chakra-ui/anatomy'; +import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; -export const checkboxTheme = defineStyleConfig({ +const { defineMultiStyleConfig } = createMultiStyleConfigHelpers( + checkboxAnatomy.keys +); + +export const checkboxTheme = defineMultiStyleConfig({ defaultProps: { colorScheme: 'brand', }, diff --git a/src/theme/components/index.ts b/src/theme/components/index.ts index 894bacaee..fc30236eb 100644 --- a/src/theme/components/index.ts +++ b/src/theme/components/index.ts @@ -10,4 +10,5 @@ export { pinInputTheme as PinInput } from './pin-input'; export { popoverTheme as Popover } from './popover'; export { radioTheme as Radio } from './radio'; export { selectTheme as Select } from './select'; +export { switchTheme as Switch } from './switch'; export { textareaTheme as Textarea } from './textarea'; diff --git a/src/theme/components/switch.ts b/src/theme/components/switch.ts new file mode 100644 index 000000000..4ae0180ea --- /dev/null +++ b/src/theme/components/switch.ts @@ -0,0 +1,12 @@ +import { switchAnatomy } from '@chakra-ui/anatomy'; +import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; + +const { defineMultiStyleConfig } = createMultiStyleConfigHelpers( + switchAnatomy.keys +); + +export const switchTheme = defineMultiStyleConfig({ + defaultProps: { + colorScheme: 'brand', + }, +});