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
55 changes: 52 additions & 3 deletions apps/web/content/docs/ui/auto-form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,54 @@ Import each one from `@vitnode/core/components/form/fields/<file>`. Spread
| `AutoFormArray` | `array` | [Arrays](#arrays) |

Every field takes `label`, `labelRight` (content pinned to the label's right
edge) and `description`.
edge), `description` and [`tip`](#field-tips).

## Field tips

<Preview name="auto-form-tip" className="*:max-w-full" />

`tip` adds a small question-mark button right after the label. It opens a
bubble with extra help that most people can skip. Every built-in field
supports it, `AutoFormArray` included.

```tsx
{
id: "slug",
component: props => (
<AutoFormInput
{...props}
label={t("slug.label")}
description={t("slug.description")}
tip={t("slug.tip")}
labelRight={<Badge variant="secondary">{t("public")}</Badge>}
/>
),
}
```

Pick the right slot for your text:

| Prop | Shows | Use it for |
| ------------- | ------------------------------------------ | ----------------------------------------- |
| `label` | Always, above the control | The field's name |
| `description` | Always, below the control | What the field does, in one line |
| `tip` | Only when asked, in a bubble by the label | Background, edge cases, "why would I..." |
| `labelRight` | Always, at the end of the label row | Actions, badges, a language switcher |

- **Mouse:** hover the icon for a moment, then move onto the bubble to read it.
Or click the icon: the bubble stays open until you click it again or
anywhere else.
- **Touch:** tap the icon to open, tap it again or anywhere else to close.
- **Keyboard:** Tab reaches the icon, Enter or Space opens it, Escape closes it.

Pressing the icon never focuses the control, toggles a checkbox or switch, or
submits the form. When a submit fails, focus still lands on the invalid
control, not on the icon. Empty values (`undefined`, `""`, `false`) render no
icon, so `tip={isAdmin && t("slug.tip")}` is fine.

Translate the tip like the label, with `use-intl`. In a
[multi-language field](#multi-language-fields) it stays in the UI language
while you switch the language you're editing.

## Schema

Expand Down Expand Up @@ -324,7 +371,8 @@ import {
A field is any component. Build it from AutoForm's parts and it gets the same
ids, ARIA and errors as the built-in ones:

- `AutoFormLabel` and `AutoFormDesc` render the label and description.
- `AutoFormLabel` and `AutoFormDesc` render the label and description. Pass
`tip` to `AutoFormLabel` for the [help icon](#field-tips).
- `FormControl` passes `id`, `aria-invalid` and `aria-describedby` to its child.
- `FormMessage` shows the field's errors.

Expand All @@ -343,12 +391,13 @@ import { FormControl, FormMessage } from '@vitnode/core/components/ui/form'
const SlowModeField = ({
label,
description,
tip,
field,
}: Omit<ItemAutoFormComponentProps, 'field'> & {
field: FormFieldApi<number>
}) => (
<>
<AutoFormLabel>{label}</AutoFormLabel>
<AutoFormLabel tip={tip}>{label}</AutoFormLabel>
<FormControl>
<input
type="range"
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/docs/examples/auto-form-custom-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ const SlowModeField = ({
description,
field,
label,
tip,
}: Omit<ItemAutoFormComponentProps, 'field'> & {
field: FormFieldApi<number>
}) => (
<>
<div className="flex items-center justify-between gap-4">
<AutoFormLabel>{label}</AutoFormLabel>
<AutoFormLabel tip={tip}>{label}</AutoFormLabel>
<output className="text-muted-foreground text-sm tabular-nums">
{field.value === 0 ? 'Off' : `${field.value}s`}
</output>
Expand Down Expand Up @@ -59,6 +60,7 @@ export default function AutoFormCustomFieldExample() {
{...props}
description="Seconds a member waits between posts in this thread."
label="Slow mode"
tip="Moderators skip the wait. Existing posts are not affected."
/>
),
},
Expand Down
54 changes: 54 additions & 0 deletions apps/web/src/docs/examples/auto-form-tip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { AutoForm } from '@vitnode/core/components/form/auto-form'
import { AutoFormInput } from '@vitnode/core/components/form/fields/input'
import { AutoFormSwitch } from '@vitnode/core/components/form/fields/switch'
import { Badge } from '@vitnode/core/components/ui/badge'
import { toast } from 'sonner'
import { z } from 'zod'

export default function AutoFormTipExample() {
const formSchema = z.object({
slug: z
.string()
.regex(
/^[a-z\d]+(?:-[a-z\d]+)*$/,
'Use lowercase letters, numbers and dashes',
)
.default('general-chat'),
slowMode: z.boolean().default(false),
})

return (
<AutoForm
className="w-full"
fields={[
{
id: 'slug',
component: (props) => (
<AutoFormInput
{...props}
description="Shown in the forum's address."
label="Slug"
labelRight={<Badge variant="secondary">Public</Badge>}
tip="Changing the slug later breaks every link already shared, so pick one you can live with. Old bookmarks will not follow along."
/>
),
},
{
id: 'slowMode',
component: (props) => (
<AutoFormSwitch
{...props}
description="Members wait 30 seconds between posts."
label="Slow mode"
tip="Handy when a thread catches fire. Moderators and admins are never slowed down."
/>
),
},
]}
formSchema={formSchema}
onSubmit={(values) => {
toast.success('Forum saved', { description: `/${values.slug}` })
}}
/>
)
}
1 change: 1 addition & 0 deletions apps/web/src/locales/@vitnode/core/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -2709,6 +2709,7 @@
"account": "Konto"
},
"optional": "Opcjonalne",
"field_tip": "Więcej informacji",
"or": "lub",
"pick_color": "Wybierz kolor",
"color_value": "Wartość koloru",
Expand Down
Loading
Loading