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 (
+
+ );
+};
+
+export const DefaultValues = () => {
+ const form = useForm({
+ ...formOptions,
+ defaultValues: {
+ doit: true,
+ },
+ });
+
+ return (
+
+ );
+};
+
+export const Disabled = () => {
+ const form = useForm(formOptions);
+
+ return (
+
+ );
+};
+
+export const DisabledDefaultValues = () => {
+ const form = useForm({
+ ...formOptions,
+ defaultValues: {
+ doit: true,
+ },
+ });
+
+ return (
+
+ );
+};
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 (
+
+ );
+};
+
+export const DefaultValues = () => {
+ const form = useForm({
+ ...formOptions,
+ defaultValues: {
+ doit: true,
+ },
+ });
+
+ return (
+
+ );
+};
+
+export const Disabled = () => {
+ const form = useForm(formOptions);
+
+ return (
+
+ );
+};
+
+export const DisabledDefaultValues = () => {
+ const form = useForm({
+ ...formOptions,
+ defaultValues: {
+ doit: true,
+ },
+ });
+
+ return (
+
+ );
+};
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',
+ },
+});