Skip to content

Commit

Permalink
feat: add formik helper components
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelTaylor3D committed Apr 4, 2024
1 parent 5f82b60 commit 0e5c7ab
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 3 deletions.
83 changes: 82 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"flowbite": "^2.3.0",
"flowbite-react": "^0.7.6",
"flowbite-typography": "^1.0.3",
"formik": "^2.4.5",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-content-loader": "^7.0.0",
Expand All @@ -52,7 +53,8 @@
"simplebar-react": "^3.2.4",
"styled-components": "^6.1.8",
"uuid": "^9.0.1",
"xterm": "^5.3.0"
"xterm": "^5.3.0",
"yup": "^1.4.0"
},
"devDependencies": {
"@commitlint/config-conventional": "^19.1.0",
Expand Down
59 changes: 59 additions & 0 deletions src/renderer/components/form/Field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { useFormikContext, FormikValues } from 'formik';
import { Label, HelperText } from 'flowbite-react';

interface FieldProps {
name: string;
label?: string;
readonly?: boolean;
children: React.ReactElement;
}

const Field: React.FC<FieldProps> = ({ name, label, readonly, children }) => {
const { errors, touched, values, setFieldValue, setFieldTouched }: FormikValues = useFormikContext();

const isError: boolean = !!errors[name] && !!touched[name];

// Enforce a single child
const child = React.Children.only(children);

// Styles for the read-only value container
const readOnlyValueStyles = "py-2 px-4 bg-gray-100 text-gray-800 rounded border border-gray-300";

if (readonly) {
return (
<div className="mb-4">
{label && <Label htmlFor={name}>{label}</Label>}
<div id={name} className={readOnlyValueStyles}>
{values[name] || 'N/A'} {/* Display 'N/A' if the value is falsy */}
</div>
{isError && <HelperText>{errors[name]}</HelperText>}
</div>
);
}

// Enhance the single child component with additional Formik-related props
const enhancedChild = React.cloneElement(child, {
id: name,
name,
onChange: (e: React.ChangeEvent<any>) => {
child.props.onChange?.(e);
setFieldValue(name, e.target.value);
},
onBlur: (e: React.FocusEvent<any>) => {
child.props.onBlur?.(e);
setFieldTouched(name, true);
},
color: isError ? 'failure' : 'gray',
});

return (
<div className="mb-4">
{label && <Label htmlFor={name}>{label}</Label>}
{enhancedChild}
{isError && <HelperText>{errors[name]}</HelperText>}
</div>
);
};

export default Field;
49 changes: 49 additions & 0 deletions src/renderer/components/form/Repeater.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { FieldArray, useFormikContext, FormikValues } from 'formik';
import { Button } from 'flowbite-react';

interface RepeaterProps {
name: string;
component: React.ReactElement;
maxNumber?: number;
minNumber?: number;
}

const Repeater: React.FC<RepeaterProps> = ({ name, component, maxNumber = 5, minNumber = 1 }) => {
const { values }: FormikValues = useFormikContext();
const groups = values[name] || [];

return (
<FieldArray
name={name}
render={(arrayHelpers) => (
<div>
{groups.map((group: any, index: number) => (
<div key={index} className="mb-4 flex items-center">
{/* Clone the component for each group and pass in the adjusted name prop */}
{React.cloneElement(component, { name: `${name}[${index}]` })}

{/* Remove button for each group */}
<Button
color="failure"
onClick={() => arrayHelpers.remove(index)}
disabled={groups.length <= minNumber} // Disable if minNumber reached
>
Remove
</Button>
</div>
))}

{/* Add button - shown if maxNumber hasn't been reached */}
{groups.length < maxNumber && (
<Button onClick={() => arrayHelpers.push({})}>
Add
</Button>
)}
</div>
)}
/>
);
};

export default Repeater;
25 changes: 25 additions & 0 deletions src/renderer/components/form/_example.addressgroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { TextInput } from 'flowbite-react';
import Field from './Field'; // Make sure the import path is correct

interface AddressGroupProps {
name: string; // Base name for the group, used to construct field names
}

const AddressGroup: React.FC<AddressGroupProps> = ({ name }) => {
return (
<div className="space-y-2">
<Field name={`${name}.street`} label="Street">
<TextInput placeholder="123 Main St" />
</Field>
<Field name={`${name}.city`} label="City">
<TextInput placeholder="Anytown" />
</Field>
<Field name={`${name}.zipCode`} label="Zip Code">
<TextInput placeholder="12345" />
</Field>
</div>
);
};

export default AddressGroup;
Loading

0 comments on commit 0e5c7ab

Please sign in to comment.