diff --git a/.gitignore b/.gitignore index afd6235..e8e4b18 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ # production /build +/dist +tsconfig.tsbuildinfo # misc .DS_Store diff --git a/README.md b/README.md index bdddee6..e96b40a 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,21 @@ Live demo - [open](https://react-formik-abstraction.vercel.app/) ## Naming Convention -We will use a **PascalCase**. Pascal case is a programming variable naming convention where the first letter of each compound word in a variable is capitalized. +We will use a **kebab-case**. Kebab case is a programming variable naming convention where all letters are lowercase and words are separated by hyphens. -Here are some examples of kebab case: `ComponentName.tsx` and `OtherComponent.tsx`. +Here are some examples of kebab case: `component-name.tsx` and `other-component.tsx`. + +## Component Structure Convention + +When creating a component, we will follow the convention of splitting it into separate files for the controller, model, view, index, and stories. This helps in maintaining a clean and organized codebase. + +Here is the structure: + +- `component-name.controller.ts` - Contains the logic and state management for the component. +- `component-name.model.ts` - Defines the data structures and types used by the component. +- `component-name.view.tsx` - Contains the JSX and styling for the component. +- `component-name.stories.tsx` - Contains the Storybook stories for the component. +- `index.ts` - Exports the component and any related hooks or utilities. ## Directory Structure diff --git a/index.html b/index.html index b819faa..5642615 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,7 @@ - + - Multi-step form diff --git a/src/App.tsx b/src/App.tsx index bf7f4c7..8e84dac 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,8 @@ -import { StepsProvider } from '@providers/StepsProvider'; +import Content from '@/components/content'; +import Layout from '@/components/layout'; +import { StepsProvider } from '@/providers/steps.provider'; -import { Content } from '@/components/Content/Content'; -import { Layout } from '@/components/Layout/Layout'; - -import './App.css'; +import './global.css'; const App = () => ( diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index 55759b0..09c565a 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -1,15 +1,14 @@ -import { Form } from '@components/Form/Form'; - -import { Actions } from '@/components/Layout/components/Actions'; -import { steps } from '@/const/formData'; +import Form from '@/components/form'; +import { Actions } from '@/components/layout/components/actions'; +import { steps } from '@/const/form-data'; import { clsxm } from '@/lib/clsxm'; -import { Header } from './components/Header'; -import { Summary } from './steps/Summary'; -import { stepsComponent } from './ContentModel'; -import { useContent } from './useContent'; +import { Header } from './components/header'; +import { useContent } from './content.controller'; +import { stepsComponent } from './content.model'; +import { Summary } from './steps'; -export const Content = () => { +const Content = () => { const { active, form, showSummary } = useContent(); return ( @@ -32,3 +31,5 @@ export const Content = () => { ); }; + +export default Content; diff --git a/src/components/Content/components/header/header.model.ts b/src/components/Content/components/header/header.model.ts new file mode 100644 index 0000000..48c5493 --- /dev/null +++ b/src/components/Content/components/header/header.model.ts @@ -0,0 +1,4 @@ +export type HeaderProps = { + description?: string; + title: string; +}; diff --git a/src/components/Content/components/Header.tsx b/src/components/Content/components/header/header.tsx similarity index 79% rename from src/components/Content/components/Header.tsx rename to src/components/Content/components/header/header.tsx index 3bfd40f..8a79b90 100644 --- a/src/components/Content/components/Header.tsx +++ b/src/components/Content/components/header/header.tsx @@ -1,7 +1,4 @@ -export type HeaderProps = { - description?: string; - title: string; -}; +import type { HeaderProps } from './header.model'; export const Header = ({ title, description }: HeaderProps) => (
diff --git a/src/components/Content/components/header/index.ts b/src/components/Content/components/header/index.ts new file mode 100644 index 0000000..bea5a07 --- /dev/null +++ b/src/components/Content/components/header/index.ts @@ -0,0 +1,2 @@ +export { Header } from './header'; +export type { HeaderProps } from './header.model'; diff --git a/src/components/Content/useContent.tsx b/src/components/Content/content.controller.ts similarity index 70% rename from src/components/Content/useContent.tsx rename to src/components/Content/content.controller.ts index e126c74..d215cd5 100644 --- a/src/components/Content/useContent.tsx +++ b/src/components/Content/content.controller.ts @@ -1,9 +1,9 @@ import { useState } from 'react'; -import { useForm } from '@components/Form/useForm'; -import { useStepsContext } from '@/providers/StepsProvider'; +import { useForm } from '@/components/form/form.controller'; +import { useStepsContext } from '@/providers/steps.provider'; -import { initialValues } from './ContentModel'; +import { initialValues } from './content.model'; export const useContent = () => { const { diff --git a/src/components/Content/ContentModel.tsx b/src/components/Content/content.model.tsx similarity index 60% rename from src/components/Content/ContentModel.tsx rename to src/components/Content/content.model.tsx index 971d479..c164a95 100644 --- a/src/components/Content/ContentModel.tsx +++ b/src/components/Content/content.model.tsx @@ -1,7 +1,4 @@ -import { FinishingUp } from './steps/FinishingUp'; -import { PersonalInfo } from './steps/PersonalInfo'; -import { PickAddons } from './steps/PickAddons'; -import { SelectPlan } from './steps/SelectPlan'; +import { FinishingUp, PersonalInfo, PickAddons, SelectPlan } from './steps'; export const initialValues = { name: '', diff --git a/src/components/Content/index.ts b/src/components/Content/index.ts new file mode 100644 index 0000000..3089d80 --- /dev/null +++ b/src/components/Content/index.ts @@ -0,0 +1 @@ +export { default } from './content'; diff --git a/src/components/Content/steps/FinishingUp.tsx b/src/components/Content/steps/finishing-up.tsx similarity index 90% rename from src/components/Content/steps/FinishingUp.tsx rename to src/components/Content/steps/finishing-up.tsx index bf93b61..3e3a99b 100644 --- a/src/components/Content/steps/FinishingUp.tsx +++ b/src/components/Content/steps/finishing-up.tsx @@ -1,9 +1,9 @@ -import { FormikContextType, FormikValues, useFormikContext } from 'formik'; +import { type FormikContextType, type FormikValues, useFormikContext } from 'formik'; -import { planOptions } from '@/const/formData'; -import { addonOptions } from '@/const/formData'; +import { planOptions } from '@/const/form-data'; +import { addonOptions } from '@/const/form-data'; import { sumNumbers } from '@/lib/helpers'; -import { useStepsContext } from '@/providers/StepsProvider'; +import { useStepsContext } from '@/providers/steps.provider'; export const FinishingUp = () => { const { values }: FormikContextType = useFormikContext(); diff --git a/src/components/Content/steps/index.ts b/src/components/Content/steps/index.ts new file mode 100644 index 0000000..1ceb52e --- /dev/null +++ b/src/components/Content/steps/index.ts @@ -0,0 +1,5 @@ +export { FinishingUp } from './finishing-up'; +export { PersonalInfo } from './personal-info'; +export { PickAddons } from './pick-addons'; +export { SelectPlan } from './select-plan'; +export { Summary } from './summary'; diff --git a/src/components/Content/steps/PersonalInfo.tsx b/src/components/Content/steps/personal-info.tsx similarity index 90% rename from src/components/Content/steps/PersonalInfo.tsx rename to src/components/Content/steps/personal-info.tsx index 00ee27f..e22db8f 100644 --- a/src/components/Content/steps/PersonalInfo.tsx +++ b/src/components/Content/steps/personal-info.tsx @@ -1,4 +1,4 @@ -import { Input } from '@components/Form/Fields/Input'; +import { Input } from '@/components/form'; export const PersonalInfo = () => (
diff --git a/src/components/Content/steps/PickAddons.tsx b/src/components/Content/steps/pick-addons.tsx similarity index 74% rename from src/components/Content/steps/PickAddons.tsx rename to src/components/Content/steps/pick-addons.tsx index 524b0c8..6156445 100644 --- a/src/components/Content/steps/PickAddons.tsx +++ b/src/components/Content/steps/pick-addons.tsx @@ -1,7 +1,7 @@ -import { FormikContextType, FormikValues, useFormikContext } from 'formik'; +import { type FormikContextType, type FormikValues, useFormikContext } from 'formik'; -import { Checkbox } from '@/components/Form/Fields/Checkbox'; -import { addonOptions } from '@/const/formData'; +import { Checkbox } from '@/components/form'; +import { addonOptions } from '@/const/form-data'; export const PickAddons = () => { const { values }: FormikContextType = useFormikContext(); diff --git a/src/components/Content/steps/SelectPlan.tsx b/src/components/Content/steps/select-plan.tsx similarity index 67% rename from src/components/Content/steps/SelectPlan.tsx rename to src/components/Content/steps/select-plan.tsx index 7184c5e..739c171 100644 --- a/src/components/Content/steps/SelectPlan.tsx +++ b/src/components/Content/steps/select-plan.tsx @@ -1,15 +1,14 @@ -import { FormikContextType, FormikValues, useFormikContext } from 'formik'; +import { type FormikContextType, type FormikValues, useFormikContext } from 'formik'; -import { RadioBox } from '@/components/Form/Fields/RadioBox'; -import { SwitchButton } from '@/components/Form/Fields/SwitchButton'; -import { planOptions } from '@/const/formData'; +import { RadioBox, Switch } from '@/components/form'; +import { planOptions } from '@/const/form-data'; + +const durationClassName = (isActive: boolean) => + isActive ? 'text-denim text-sm font-medium' : 'text-grey text-sm font-medium'; export const SelectPlan = () => { const { values }: FormikContextType = useFormikContext(); - const durationClassName = (isActive: boolean) => - isActive ? 'text-denim text-sm font-medium' : 'text-grey text-sm font-medium'; - return (
@@ -28,7 +27,7 @@ export const SelectPlan = () => {

Monthly

- +

Yearly

diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 25e628b..7765b7b 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,15 +1,8 @@ -import { PropsWithChildren } from 'react'; -import { FormikContextType, FormikProvider, FormikValues } from 'formik'; +import { FormikProvider } from 'formik'; -import { useFormType } from './useForm'; +import type { FormProps } from './form.model'; -export type FormType = { - className?: string; - form: FormikContextType; -} & Pick & - PropsWithChildren; - -export const Form = ({ children, className, form }: FormType) => ( +const Form = ({ children, className, form }: FormProps) => (
@@ -18,3 +11,5 @@ export const Form = ({ children, className, form }: FormType) => ( ); + +export default Form; diff --git a/src/components/Form/UI/SwitchButton/SwitchButton.stories.tsx b/src/components/Form/UI/SwitchButton/SwitchButton.stories.tsx deleted file mode 100644 index 30744f5..0000000 --- a/src/components/Form/UI/SwitchButton/SwitchButton.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta } from '@storybook/react'; - -import SwitchButton from './SwitchButton'; - -const meta: Meta = { - title: 'SwitchButton', - component: SwitchButton, -}; - -export default meta; - -export const Primary = { - args: { - checked: true, - }, -}; diff --git a/src/components/Form/components/form-field/form-field.controller.ts b/src/components/Form/components/form-field/form-field.controller.ts new file mode 100644 index 0000000..30d450b --- /dev/null +++ b/src/components/Form/components/form-field/form-field.controller.ts @@ -0,0 +1,13 @@ +import type { FormFieldProps } from './form-field.model'; + +export const useFormField =

>( + props: P, +) => { + const { label, name, className, ...otherProps } = props; + const id = name; + + return { + formFieldProps: { id, name, label, className }, + childProps: { ...otherProps, id, name }, + }; +}; diff --git a/src/components/Form/components/form-field/form-field.model.ts b/src/components/Form/components/form-field/form-field.model.ts new file mode 100644 index 0000000..0d5b3d3 --- /dev/null +++ b/src/components/Form/components/form-field/form-field.model.ts @@ -0,0 +1,8 @@ +import { PropsWithChildren } from 'react'; + +export type FormFieldProps = { + className?: string; + id: string; + label?: string; + name: string; +} & PropsWithChildren; diff --git a/src/components/Form/FormField.tsx b/src/components/Form/components/form-field/form-field.tsx similarity index 50% rename from src/components/Form/FormField.tsx rename to src/components/Form/components/form-field/form-field.tsx index 2c70b30..f1d8979 100644 --- a/src/components/Form/FormField.tsx +++ b/src/components/Form/components/form-field/form-field.tsx @@ -1,27 +1,9 @@ -import { PropsWithChildren } from 'react'; import { clsxm } from '@lib/clsxm'; import { useFormikContext } from 'formik'; -type FormFieldType = { - className?: string; - id: string; - label?: string; - name: string; -} & PropsWithChildren; +import type { FormFieldProps } from './form-field.model'; -export const useFormField =

>( - props: P, -) => { - const { label, name, className, ...otherProps } = props; - const id = name; - - return { - formFieldProps: { id, name, label, className }, - childProps: { ...otherProps, id, name }, - }; -}; - -export const FormField = ({ children, name, id, label, className }: FormFieldType) => { +const FormField = ({ children, name, id, label, className }: FormFieldProps) => { const form = useFormikContext(); const { error } = form.getFieldMeta(name); @@ -39,3 +21,5 @@ export const FormField = ({ children, name, id, label, className }: FormFieldTyp

); }; + +export default FormField; diff --git a/src/components/Form/components/form-field/index.ts b/src/components/Form/components/form-field/index.ts new file mode 100644 index 0000000..efde47f --- /dev/null +++ b/src/components/Form/components/form-field/index.ts @@ -0,0 +1,3 @@ +export { default } from './form-field'; +export { useFormField } from './form-field.controller'; +export type { FormFieldProps } from './form-field.model'; diff --git a/src/components/Form/components/ui/checkbox/checkbox.model.ts b/src/components/Form/components/ui/checkbox/checkbox.model.ts new file mode 100644 index 0000000..697858a --- /dev/null +++ b/src/components/Form/components/ui/checkbox/checkbox.model.ts @@ -0,0 +1,9 @@ +import { ComponentProps } from 'react'; + +import { AddonType } from '@/@types/addon'; + +export type CheckboxUIProps = { + className?: string; + content: AddonType; + duration: 'mo' | 'yr'; +} & Omit, 'type' | 'content'>; diff --git a/src/components/Form/UI/Checkbox/Checkbox.stories.tsx b/src/components/Form/components/ui/checkbox/checkbox.stories.tsx similarity index 92% rename from src/components/Form/UI/Checkbox/Checkbox.stories.tsx rename to src/components/Form/components/ui/checkbox/checkbox.stories.tsx index c2807e6..64d7e1f 100644 --- a/src/components/Form/UI/Checkbox/Checkbox.stories.tsx +++ b/src/components/Form/components/ui/checkbox/checkbox.stories.tsx @@ -1,6 +1,6 @@ import type { Meta } from '@storybook/react'; -import Checkbox from './Checkbox'; +import Checkbox from './checkbox'; const meta: Meta = { title: 'Checkbox', diff --git a/src/components/Form/UI/Checkbox/Checkbox.tsx b/src/components/Form/components/ui/checkbox/checkbox.tsx similarity index 83% rename from src/components/Form/UI/Checkbox/Checkbox.tsx rename to src/components/Form/components/ui/checkbox/checkbox.tsx index 7810b36..5b55d72 100644 --- a/src/components/Form/UI/Checkbox/Checkbox.tsx +++ b/src/components/Form/components/ui/checkbox/checkbox.tsx @@ -1,15 +1,8 @@ -import { ComponentProps } from 'react'; - -import { AddonType } from '@/@types/addon'; import { clsxm } from '@/lib/clsxm'; -import { ReactComponent as CheckIcon } from '~/svg/check.svg'; +import type { CheckboxUIProps } from './checkbox.model'; -export type CheckboxUIProps = { - className?: string; - content: AddonType; - duration: 'mo' | 'yr'; -} & Omit, 'type' | 'content'>; +import { ReactComponent as CheckIcon } from '~/svg/check.svg'; const Checkbox = ({ className, content, duration, name, ...props }: CheckboxUIProps) => (
diff --git a/src/components/Layout/components/actions/index.ts b/src/components/Layout/components/actions/index.ts new file mode 100644 index 0000000..2c8582b --- /dev/null +++ b/src/components/Layout/components/actions/index.ts @@ -0,0 +1,2 @@ +export { Actions } from './actions'; +export type { ActionsProps } from './actions.model'; diff --git a/src/components/Layout/components/side-bar/index.ts b/src/components/Layout/components/side-bar/index.ts new file mode 100644 index 0000000..499d33b --- /dev/null +++ b/src/components/Layout/components/side-bar/index.ts @@ -0,0 +1,2 @@ +export { SideBar } from './side-bar'; +export type { SideBarProps } from './side-bar.model'; diff --git a/src/components/Layout/components/side-bar/side-bar.model.ts b/src/components/Layout/components/side-bar/side-bar.model.ts new file mode 100644 index 0000000..2b08b2a --- /dev/null +++ b/src/components/Layout/components/side-bar/side-bar.model.ts @@ -0,0 +1,8 @@ +export type PointType = { + children: string; + id: number; +}; + +export type SideBarProps = { + steps: PointType[]; +}; diff --git a/src/components/Layout/components/Sidebar.tsx b/src/components/Layout/components/side-bar/side-bar.tsx similarity index 79% rename from src/components/Layout/components/Sidebar.tsx rename to src/components/Layout/components/side-bar/side-bar.tsx index a4366b8..9dff1ae 100644 --- a/src/components/Layout/components/Sidebar.tsx +++ b/src/components/Layout/components/side-bar/side-bar.tsx @@ -1,15 +1,8 @@ -import { StepPoint } from '@/components/commons/StepPoint'; +import { StepPoint } from '@/components/commons/step-point'; import { clsxm } from '@/lib/clsxm'; -import { useStepsContext } from '@/providers/StepsProvider'; +import { useStepsContext } from '@/providers/steps.provider'; -export type PointType = { - children: string; - id: number; -}; - -export type SideBarProps = { - steps: PointType[]; -}; +import type { SideBarProps } from './side-bar.model'; export const SideBar = ({ steps }: SideBarProps) => { const { diff --git a/src/components/Layout/index.ts b/src/components/Layout/index.ts new file mode 100644 index 0000000..3e22dcc --- /dev/null +++ b/src/components/Layout/index.ts @@ -0,0 +1 @@ +export { default } from './layout'; diff --git a/src/components/commons/Button.tsx b/src/components/commons/button/button.model.ts similarity index 62% rename from src/components/commons/Button.tsx rename to src/components/commons/button/button.model.ts index a98c5a8..1146706 100644 --- a/src/components/commons/Button.tsx +++ b/src/components/commons/button/button.model.ts @@ -1,20 +1,12 @@ import { ComponentProps, PropsWithChildren } from 'react'; -import { clsxm } from '@/lib/clsxm'; - export type ButtonProps = PropsWithChildren & { className?: string; variant?: keyof typeof buttonVariants; } & ComponentProps<'button'>; -const buttonVariants = { +export const buttonVariants = { primary: 'border-none bg-denim rounded px-4 h-10 text-white text-sm font-medium', secondary: 'border-none bg-purple rounded px-4 h-10 text-white text-sm font-medium', clear: 'border-none bg-transparent h-10 text-grey text-sm font-medium', } as const; - -export const Button = ({ className, variant = 'primary', children, ...props }: ButtonProps) => ( - -); diff --git a/src/components/commons/button/button.tsx b/src/components/commons/button/button.tsx new file mode 100644 index 0000000..b1a34cc --- /dev/null +++ b/src/components/commons/button/button.tsx @@ -0,0 +1,15 @@ +import { clsxm } from '@/lib/clsxm'; + +import { type ButtonProps, buttonVariants } from './button.model'; + +export const Button = ({ + className, + variant = 'primary', + children, + type = 'button', + ...props +}: ButtonProps) => ( + +); diff --git a/src/components/commons/button/index.ts b/src/components/commons/button/index.ts new file mode 100644 index 0000000..31c2a93 --- /dev/null +++ b/src/components/commons/button/index.ts @@ -0,0 +1,2 @@ +export { Button } from './button'; +export type { ButtonProps } from './button.model'; diff --git a/src/components/commons/step-point/index.ts b/src/components/commons/step-point/index.ts new file mode 100644 index 0000000..d82a6ff --- /dev/null +++ b/src/components/commons/step-point/index.ts @@ -0,0 +1,2 @@ +export { StepPoint } from './step-point'; +export type { StepPointProps } from './step-point.model'; diff --git a/src/components/commons/step-point/step-point.model.ts b/src/components/commons/step-point/step-point.model.ts new file mode 100644 index 0000000..9493f02 --- /dev/null +++ b/src/components/commons/step-point/step-point.model.ts @@ -0,0 +1,8 @@ +import { PropsWithChildren } from 'react'; + +export type StepPointProps = PropsWithChildren & { + clickable: boolean; + handleClick: () => void; + id: number; + isActive: boolean; +}; diff --git a/src/components/commons/StepPoint.tsx b/src/components/commons/step-point/step-point.tsx similarity index 85% rename from src/components/commons/StepPoint.tsx rename to src/components/commons/step-point/step-point.tsx index 6a2b5c1..f614eb6 100644 --- a/src/components/commons/StepPoint.tsx +++ b/src/components/commons/step-point/step-point.tsx @@ -1,13 +1,6 @@ -import { PropsWithChildren } from 'react'; - import { clsxm } from '@/lib/clsxm'; -export type StepPointProps = PropsWithChildren & { - clickable: boolean; - handleClick: () => void; - id: number; - isActive: boolean; -}; +import type { StepPointProps } from './step-point.model'; export const StepPoint = ({ isActive, id, children, clickable, handleClick }: StepPointProps) => (