Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 53 additions & 96 deletions app/components/form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import cn from 'classnames'
import type { FormikConfig } from 'formik'
import { Formik, Form as FormikForm } from 'formik'
import { useFormikContext } from 'formik'
import type { ReactNode } from 'react'
import { cloneElement } from 'react'
import { useEffect } from 'react'
import invariant from 'tiny-invariant'

import type { ButtonProps } from '@oxide/ui'
Expand All @@ -14,49 +9,6 @@ import { addProps, classed, flattenChildren, isOneOf, pluckFirstOfType } from '@

import './form.css'

export interface FormProps<Values> extends FormikConfig<Values> {
id: string
className?: string
children: ReactNode
/** true if submission can happen, false otherwise */
setSubmitState?: (state: boolean) => void
}

export function Form<Values extends Record<string, unknown>>({
id,
children,
className,
setSubmitState,
...formikProps
}: FormProps<Values>) {
// Coerce container so it can be used in wrap
return (
<Formik {...formikProps} validateOnBlur={false} isInitialValid={false}>
<FormikForm id={id} className={cn('ox-form', className)} autoComplete="off">
{children}
<FormSubmitState setSubmitState={setSubmitState} />
</FormikForm>
</Formik>
)
}

/**
* This annoying little component exists solely to inform the parent when the submit state changes.
*/
const FormSubmitState = ({
setSubmitState,
}: {
setSubmitState?: (state: boolean) => void
}) => {
const context = useFormikContext()
useEffect(() => {
if (setSubmitState) {
setSubmitState(context.dirty && context.isValid)
}
}, [context.dirty, context.isValid, setSubmitState])
return null
}

interface FormActionsProps {
formId?: string
children: React.ReactNode
Expand All @@ -65,58 +17,63 @@ interface FormActionsProps {
className?: string
}

/**
* This component is the area at the bottom of a form that contains
* the submit button and any other actions. The first button is automatically
* given a type of "submit." Default styles are applied all buttons but can be
* overridden.
*/
Form.Actions = ({
children,
formId,
submitDisabled = true,
error,
className,
}: FormActionsProps) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const childArray = flattenChildren(children).map(
addProps<typeof Button>((i, props) => ({
size: 'sm',
...props,
}))
)
export const Form = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This nested declaration likely makes the babel plugin not work for these components.

/**
* This component is the area at the bottom of a form that contains
* the submit button and any other actions. The first button is automatically
* given a type of "submit." Default styles are applied all buttons but can be
* overridden.
*/
Actions: ({
children,
formId,
submitDisabled = true,
error,
className,
}: FormActionsProps) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const childArray = flattenChildren(children).map(
addProps<typeof Button>((i, props) => ({
size: 'sm',
...props,
}))
)

invariant(
isOneOf(childArray, [Form.Submit, Form.Cancel, Button]),
'Form.Actions should only receive Button components as children'
)
invariant(
isOneOf(childArray, [Form.Submit, Form.Cancel, Button]),
'Form.Actions should only receive Button components as children'
)

const submit = pluckFirstOfType(childArray, Form.Submit)
const submit = pluckFirstOfType(childArray, Form.Submit)

invariant(submit, 'Form.Actions must contain a Form.Submit component')
invariant(submit, 'Form.Actions must contain a Form.Submit component')

return (
<div
className={cn('flex w-full items-center gap-[0.625rem] children:shrink-0', className)}
>
{cloneElement(submit, { form: formId, disabled: submitDisabled })}
{childArray}
{error && (
<div className="flex !shrink grow items-start justify-end text-mono-sm text-error">
<Error12Icon className="mx-2 mt-0.5 shrink-0" />
<span>{error.message}</span>
</div>
)}
</div>
)
}
return (
<div
className={cn(
'flex w-full items-center gap-[0.625rem] children:shrink-0',
className
)}
>
{cloneElement(submit, { form: formId, disabled: submitDisabled })}
{childArray}
{error && (
<div className="flex !shrink grow items-start justify-end text-mono-sm text-error">
<Error12Icon className="mx-2 mt-0.5 shrink-0" />
<span>{error.message}</span>
</div>
)}
</div>
)
},

Form.Submit = (props: ButtonProps) => <Button type="submit" variant="default" {...props} />
Submit: (props: ButtonProps) => <Button type="submit" {...props} />,

Form.Cancel = (props: ButtonProps) => (
<Button variant="ghost" color="secondary" {...props}>
Cancel
</Button>
)
Cancel: (props: ButtonProps) => (
<Button variant="ghost" color="secondary" {...props}>
Cancel
</Button>
),

Form.Heading = classed.h2`text-content text-sans-light-2xl`
Heading: classed.h2`text-content text-sans-light-2xl`,
}
55 changes: 0 additions & 55 deletions app/components/form/FullPageForm.tsx

This file was deleted.

58 changes: 0 additions & 58 deletions app/components/form/SideModalForm.tsx

This file was deleted.

13 changes: 0 additions & 13 deletions app/components/form/fields/CheckboxField.tsx

This file was deleted.

23 changes: 0 additions & 23 deletions app/components/form/fields/DescriptionField.tsx

This file was deleted.

24 changes: 0 additions & 24 deletions app/components/form/fields/DiskSizeField.tsx

This file was deleted.

Loading