Skip to content

Commit

Permalink
feat: add getBaseField support to zod and yup forms
Browse files Browse the repository at this point in the history
  • Loading branch information
Pagebakers committed Mar 20, 2024
1 parent bd9c44c commit f0750b9
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 51 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{}
{
"typescript.tsdk": "node_modules/typescript/lib"
}
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ import {
DataGrid,
} from '@saas-ui-pro/react'

export default function InternalState() {
export default function SlotProps() {
return (
<Page title="Customers" height="400px">
<PageHeader title="Customers" />
Expand Down Expand Up @@ -774,6 +774,61 @@ export default function InternalState() {
}
```

### Context Menu

Using `slotsProps` you can use the `as` prop to customize the `tr` element and wrap it with a `ContextMenu`.

```jsx
import {
Page,
PageHeader,
PageBody,
Toolbar,
ToolbarButton,
DataGrid,
} from '@saas-ui-pro/react'
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuList,
ContextMenuItem
} from '@saas-ui/react'

export default function DataGridWithContextMenu() {
return (
<Page title="Customers" height="400px">
<PageHeader title="Customers" />
<PageBody p="0" contentWidth="full" position="relative">
<DataGrid
columns={dataGrid.columns.concat()}
data={dataGrid.data.concat()}
slotProps={{
row: () => ({
as: RowWithContext,
}),
}}
/>
</PageBody>
</Page>
)
}

const RowWithContext = (props: TableRowProps) => {
return (
<ContextMenu>
<ContextMenuTrigger>
<Tr {...props} />
</ContextMenuTrigger>
<ContextMenuList>
<ContextMenuItem>Edit</MenuItem>
<ContextMenuItem>Copy</MenuItem>
<ContextMenuItem>Delete</MenuItem>
</ContextMenuList>
</ContextMenu>
)
}
```

## Typescript

<Info>
Expand Down
47 changes: 47 additions & 0 deletions apps/website/src/pages/docs/components/forms/form/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,53 @@ export default function MyForm = () => {
}
```

### Custom base field

The base field is responsible for rendering the label, help text, error message and the input itself.
You can configure a custom base field using the `getBaseField` prop of `createForm`.

You can configure `extraProps` that can be passed to the `Field` component and will be available in your custom `BaseField` component.

```tsx
import { FormControl, FormLabel, HStack, Tooltip } from '@chakra-ui/react'
import { createForm, useBaseField, splitProps } from '@saas-ui/react'
import { LuInfo } from 'react-icons/lu'

const getBaseField: GetBaseField<{ infoLabel?: string }> = () => {
return {
extraProps: ['infoLabel'],
BaseField: (props) => {
const [{ children, infoLabel }, fieldProps] = splitProps(props, [
'children',
'infoLabel',
])

const { controlProps, labelProps, error } = useBaseField(fieldProps)

return (
<FormControl {...controlProps} isInvalid={!!error}>
<HStack alignItems="center" mb="2" spacing="0">
<FormLabel mb="0">{labelProps.label}</FormLabel>
{infoLabel ? (
<Tooltip label={infoLabel}>
<span>
<LuInfo />
</span>
</Tooltip>
) : null}
</HStack>
{children}
</FormControl>
)
},
}
}

export const Form = createForm({
getBaseField,
})
```

## Accessibility

The `Form` component wraps the children in a HTML `<form>` element.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,9 @@ export default function Page() {
}
```

### Condensed variant
### Compact variant

The compact variant can be used as a collapsed state on smaller screens, or as the primary navigation in a double sidebar layout, see below.
The `compact` variant can be used as a collapsed state on smaller screens, or as the primary navigation in a double sidebar layout, see below.
NavItem labels will be rendered as tooltips.

Use the `tooltipProps` prop to customize the tooltip.
Expand Down
16 changes: 8 additions & 8 deletions packages/saas-ui-core/src/sidebar/sidebar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ WithSolidLinks.args = {
),
}

export const VariantCondensed = Template.bind({})
VariantCondensed.args = {
export const VariantCompact = Template.bind({})
VariantCompact.args = {
variant: 'compact',
children: (
<>
Expand All @@ -341,8 +341,8 @@ VariantCondensed.args = {
),
}

export const VariantCondensedColor = Template.bind({})
VariantCondensedColor.args = {
export const VariantCompactColor = Template.bind({})
VariantCompactColor.args = {
variant: 'compact',
colorScheme: 'purple',
children: (
Expand All @@ -365,8 +365,8 @@ VariantCondensedColor.args = {
),
}

export const VariantCondensedResponsive = Template.bind({})
VariantCondensedResponsive.args = {
export const VariantCompactResponsive = Template.bind({})
VariantCompactResponsive.args = {
variant: { base: 'compact' },
toggleBreakpoint: false,
colorScheme: 'purple',
Expand Down Expand Up @@ -405,8 +405,8 @@ VariantCondensedResponsive.args = {
),
}

export const VariantCondensedNavGroup = Template.bind({})
VariantCondensedNavGroup.args = {
export const VariantCompactNavGroup = Template.bind({})
VariantCompactNavGroup.args = {
variant: 'compact',
colorScheme: 'purple',
children: (
Expand Down
16 changes: 12 additions & 4 deletions packages/saas-ui-forms/src/create-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { GetFieldResolver } from './field-resolver'

export interface CreateFormProps<
FieldDefs,
ExtraFieldProps extends object = object,
TGetBaseField extends GetBaseField = GetBaseField,
> {
resolver?: GetResolver
fieldResolver?: GetFieldResolver
fields?: FieldDefs extends Record<string, React.FC<any>> ? FieldDefs : never
getBaseField?: GetBaseField<ExtraFieldProps>
getBaseField?: TGetBaseField
}

export type FormType<
Expand All @@ -39,12 +39,20 @@ export type FormType<
id?: string
}

export function createForm<FieldDefs, ExtraFieldProps extends object = object>({
export function createForm<
FieldDefs,
TGetBaseField extends GetBaseField<any> = GetBaseField<any>,
>({
resolver,
fieldResolver = objectFieldResolver,
fields,
getBaseField,
}: CreateFormProps<FieldDefs, ExtraFieldProps> = {}) {
}: CreateFormProps<FieldDefs, TGetBaseField> = {}) {
type ExtraFieldProps =
TGetBaseField extends GetBaseField<infer ExtraFieldProps>
? ExtraFieldProps
: object

const DefaultForm = forwardRef(
<
TSchema = any,
Expand Down
5 changes: 2 additions & 3 deletions packages/saas-ui-forms/src/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ import { UseArrayFieldReturn } from './use-array-field'
export interface FormRenderContext<
TFieldValues extends FieldValues = FieldValues,
TContext extends object = object,
TExtraFieldProps extends object = object,
TFieldTypes = FieldProps<TFieldValues, TExtraFieldProps>,
TFieldTypes = FieldProps<TFieldValues>,
> extends UseFormReturn<TFieldValues, TContext> {
Field: React.FC<TFieldTypes & React.RefAttributes<FocusableElement>>
DisplayIf: React.FC<DisplayIfProps<TFieldValues>>
Expand Down Expand Up @@ -76,7 +75,7 @@ interface FormOptions<
* The form children, can be a render prop or a ReactNode.
*/
children?: MaybeRenderProp<
FormRenderContext<TFieldValues, TContext, TExtraFieldProps, TFieldTypes>
FormRenderContext<TFieldValues, TContext, TFieldTypes>
>
/**
* The field resolver, used to resolve the fields from schemas.
Expand Down
1 change: 1 addition & 0 deletions packages/saas-ui-forms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export type {
FieldOptions,
DefaultFieldOverrides,
WithStepFields,
GetBaseField,
} from './types'

// Exporting from './create-form'
Expand Down
8 changes: 4 additions & 4 deletions packages/saas-ui-forms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ type FieldPathWithArray<
export type MergeFieldProps<
FieldDefs,
TFieldValues extends FieldValues = FieldValues,
TCustomProps extends object = object,
TExtraFieldProps extends object = object,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = ValueOf<{
[K in keyof FieldDefs]: FieldDefs[K] extends React.FC<infer Props>
? { type?: K } & ShallowMerge<Props, BaseFieldProps<TFieldValues, TName>> &
TCustomProps
TExtraFieldProps
: never
}>

Expand All @@ -115,7 +115,7 @@ export type FormChildren<
FieldDefs,
TFieldValues extends FieldValues = FieldValues,
TContext extends object = object,
TCustomProps extends object = object,
TExtraFieldProps extends object = object,
> = MaybeRenderProp<
FormRenderContext<
TFieldValues,
Expand All @@ -125,7 +125,7 @@ export type FormChildren<
? DefaultFields
: ShallowMerge<DefaultFields, FieldDefs>,
TFieldValues,
TCustomProps
TExtraFieldProps
>
>
>
Expand Down
5 changes: 3 additions & 2 deletions packages/saas-ui-forms/src/use-array-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const [ArrayFieldRowProvider, useArrayFieldRowContext] =

export interface ArrayFieldOptions<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> {
/**
* The field name
Expand All @@ -91,12 +91,13 @@ export const useArrayField = ({
max,
}: ArrayFieldOptions) => {
const { control } = useFormContext()

const context = useFieldArray({
control,
name,
keyName,
})

console.log(context)
return {
...context,
name,
Expand Down
Loading

0 comments on commit f0750b9

Please sign in to comment.