-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
1,982 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,9 +26,10 @@ pids | |
*.seed | ||
|
||
# Folders | ||
.turbo-cache | ||
build | ||
dist | ||
coverage | ||
dist | ||
node_modules | ||
|
||
# Extentions | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import sharedConfig, { StorybookConfig } from '@repo/storybook-config/main'; | ||
|
||
const config: StorybookConfig = { | ||
...sharedConfig, | ||
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], | ||
}; | ||
|
||
export default config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<script> | ||
window.global = window; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import sharedPreview, { Preview } from 'storybook-config/preview'; | ||
|
||
const preview: Preview = { | ||
...sharedPreview, | ||
}; | ||
|
||
export default preview; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
declare module '*.module.css'; | ||
declare module '*.png'; | ||
declare module '*.svg'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
{ | ||
"name": "@fuf-stack/uniform", | ||
"version": "0.0.1", | ||
"description": "fuf react form library", | ||
"author": "Hannes Tiede", | ||
"homepage": "https://github.com/fuf-stack/uniform#readme", | ||
"license": "MIT", | ||
"type": "module", | ||
"exports": { | ||
"./": "./dist/" | ||
}, | ||
"typesVersions": { | ||
"*": { | ||
"*": [ | ||
"./dist/*" | ||
] | ||
} | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/fuf-stack/uniform.git", | ||
"directory": "packages/uniform" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/fuf-stack/uniform/issues" | ||
}, | ||
"scripts": { | ||
"prepack": "rm -rf ./dist && tsup", | ||
"test": "vitest ./src" | ||
}, | ||
"dependencies": { | ||
"@dnd-kit/core": "6.1.0", | ||
"@dnd-kit/modifiers": "7.0.0", | ||
"@dnd-kit/sortable": "8.0.0", | ||
"@dnd-kit/utilities": "3.2.2", | ||
"@fuf-stack/pixels": "workspace:*", | ||
"@fuf-stack/veto": "workspace:*", | ||
"@nextui-org/button": "2.0.27", | ||
"@nextui-org/checkbox": "2.0.25", | ||
"@nextui-org/input": "2.1.17", | ||
"@nextui-org/radio": "2.0.25", | ||
"@nextui-org/select": "2.1.21", | ||
"@nextui-org/switch": "2.0.25", | ||
"@nextui-org/system": "2.0.15", | ||
"@react-aria/visually-hidden": "3.8.10", | ||
"react-icons": "5.0.1", | ||
"classnames": "2.5.1", | ||
"debug": "4.3.4", | ||
"react": "18.2.0", | ||
"react-dom": "18.2.0", | ||
"react-hook-form": "7.51.0", | ||
"react-select": "5.8.0", | ||
"slug": "9.0.0", | ||
"tailwind-variants": "0.1.20" | ||
}, | ||
"devDependencies": { | ||
"@repo/storybook-config": "workspace:*", | ||
"@repo/tailwind-config": "workspace:*", | ||
"@repo/vite-config": "workspace:*", | ||
"@types/debug": "4.1.12", | ||
"@types/react": "18.2.69", | ||
"@types/react-dom": "18.2.22", | ||
"@types/slug": "5.0.8" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import type { VetoInstance } from '@fuf-stack/veto'; | ||
import type { ReactNode } from 'react'; | ||
import type { FieldValues, SubmitHandler } from 'react-hook-form'; | ||
|
||
import { useEffect } from 'react'; | ||
import { useForm } from 'react-hook-form'; | ||
|
||
import cn from 'classnames'; | ||
|
||
import slugify from '../helpers/slugify'; | ||
import FormProvider from './subcomponents/FormContext'; | ||
import FormDebugViewer from './subcomponents/FormDebugViewer'; | ||
|
||
/** | ||
* recursively removes all fields that are null or undefined before | ||
* the form data is passed to the veto validation function | ||
*/ | ||
export const removeNullishFields = (obj: Record<string, unknown>) => { | ||
return JSON.parse( | ||
JSON.stringify(obj, (_key, value) => { | ||
return value === null ? undefined : value; | ||
}), | ||
); | ||
}; | ||
|
||
interface FormProps { | ||
/** form children */ | ||
children: ReactNode | ReactNode[]; | ||
/** CSS class name */ | ||
className?: string | string[]; | ||
/** initial form values */ | ||
initialValues?: FieldValues; | ||
/** name of the form */ | ||
name?: string; | ||
/** form submit handler */ | ||
onSubmit: SubmitHandler<FieldValues>; | ||
/** HTML data-testid attribute used in e2e tests */ | ||
testId?: string; | ||
/** veto validation schema */ | ||
validation?: VetoInstance; | ||
/** when the validation should be triggered */ | ||
validationTrigger?: | ||
| 'onChange' | ||
| 'onBlur' | ||
| 'onSubmit' | ||
| 'onTouched' | ||
| 'all' | ||
| 'all-instant'; | ||
} | ||
|
||
/** | ||
* Form component that has to wrap every uniform | ||
*/ | ||
const Form = ({ | ||
children, | ||
className = undefined, | ||
initialValues = undefined, | ||
name = undefined, | ||
onSubmit, | ||
testId = undefined, | ||
validation = undefined, | ||
validationTrigger = 'all', | ||
}: FormProps) => { | ||
const trigger = | ||
validationTrigger === 'all-instant' ? 'all' : validationTrigger; | ||
|
||
const methods = useForm( | ||
validation | ||
? { | ||
defaultValues: initialValues, | ||
resolver: async (values, ...args) => { | ||
const { data, errors, ...rest } = await validation.validateAsync( | ||
removeNullishFields(values), | ||
...args, | ||
); | ||
// https://github.com/react-hook-form/resolvers/blob/master/zod/src/zod.ts | ||
return { values: data || {}, errors: errors || {}, ...rest }; | ||
}, | ||
mode: trigger, // Validate form (default: all) | ||
} | ||
: { | ||
defaultValues: initialValues, | ||
}, | ||
); | ||
|
||
useEffect(() => { | ||
if (validationTrigger === 'all-instant') { | ||
methods.trigger(); | ||
} | ||
}, []); | ||
|
||
return ( | ||
<FormProvider {...methods} validation={validation}> | ||
<div className="flex w-full flex-row justify-between gap-6"> | ||
<form | ||
className={cn('flex-grow', className)} | ||
data-testid={slugify(testId || name || '')} | ||
name={name} | ||
onSubmit={methods.handleSubmit(onSubmit)} | ||
> | ||
{children} | ||
</form> | ||
<FormDebugViewer className="w-96 flex-shrink" /> | ||
</div> | ||
</FormProvider> | ||
); | ||
}; | ||
|
||
export default Form; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Form from './Form'; | ||
|
||
export default Form; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { VetoInstance } from '@fuf-stack/veto'; | ||
import type { FormProviderProps as HookFormProviderProps } from 'react-hook-form'; | ||
|
||
import React from 'react'; | ||
import { FormProvider as HookFormProvider } from 'react-hook-form'; | ||
|
||
export const ValidationSchemaContext = React.createContext< | ||
VetoInstance | undefined | ||
>(undefined); | ||
|
||
interface FormProviderProps | ||
extends HookFormProviderProps<Record<string, any>, any, undefined> { | ||
/** veto validation schema */ | ||
validation?: VetoInstance; | ||
} | ||
|
||
/** Provides the veto validation context to the form */ | ||
const FormProvider = ({ | ||
children, | ||
validation = undefined, | ||
...hookFormProps | ||
}: FormProviderProps) => { | ||
return ( | ||
<ValidationSchemaContext.Provider value={validation}> | ||
<HookFormProvider {...hookFormProps}>{children}</HookFormProvider> | ||
</ValidationSchemaContext.Provider> | ||
); | ||
}; | ||
|
||
export default FormProvider; |
88 changes: 88 additions & 0 deletions
88
packages/uniform/src/Form/subcomponents/FormDebugViewer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import type { VetoError } from '@fuf-stack/veto'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
import { FaTimes } from 'react-icons/fa'; | ||
import { FaBug } from 'react-icons/fa6'; | ||
|
||
import cn from 'classnames'; | ||
|
||
import Button from '@fuf-stack/pixels/Button'; | ||
import Card from '@fuf-stack/pixels/Card'; | ||
import useLocalStorage from '@fuf-stack/pixels/hooks/useLocalStorage'; | ||
import Json from '@fuf-stack/pixels/Json'; | ||
|
||
import { useFormContext } from '../../hooks'; | ||
|
||
interface FormDebugViewerProps { | ||
/** CSS class name */ | ||
className?: string; | ||
} | ||
|
||
const LOCALSTORAGE_DEBUG_KEY = 'uniform:form-debug-enabled'; | ||
|
||
/** Renders a form debug panel with information about the current form state */ | ||
const FormDebugViewer = ({ className = undefined }: FormDebugViewerProps) => { | ||
const { | ||
watch, | ||
formState: { dirtyFields, isValid, isSubmitting }, | ||
validation, | ||
} = useFormContext(); | ||
|
||
const [debug, setDebug] = useLocalStorage(LOCALSTORAGE_DEBUG_KEY, false); | ||
|
||
const [validationErrors, setValidationErrors] = useState< | ||
VetoError['errors'] | null | ||
>(null); | ||
|
||
const formValues = watch(); | ||
|
||
useEffect(() => { | ||
const updateValidationErrors = async () => { | ||
if (validation) { | ||
const validateResult = await validation?.validateAsync(formValues); | ||
setValidationErrors(validateResult?.errors); | ||
} | ||
}; | ||
updateValidationErrors(); | ||
}, [JSON.stringify(formValues)]); | ||
|
||
if (!debug) { | ||
return ( | ||
<Button | ||
ariaLabel="Enable form debug mode" | ||
onClick={() => setDebug(!debug)} | ||
className="absolute bottom-2.5 right-2.5 w-5 text-default-400" | ||
variant="light" | ||
icon={<FaBug />} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<Card | ||
className={cn(className)} | ||
header={ | ||
<div className="flex w-full flex-row justify-between"> | ||
<span className="text-lg">Debug Mode</span> | ||
<Button | ||
icon={<FaTimes className="text-danger" />} | ||
onClick={() => setDebug(false)} | ||
size="sm" | ||
variant="flat" | ||
/> | ||
</div> | ||
} | ||
> | ||
<Json | ||
value={{ | ||
values: formValues, | ||
errors: validationErrors, | ||
dirtyFields, | ||
isValid, | ||
isSubmitting, | ||
}} | ||
/> | ||
</Card> | ||
); | ||
}; | ||
export default FormDebugViewer; |
Oops, something went wrong.