feat(design-system): add Field component and docs (#41162)
* feat(ui): add Field component * feat(design-system): add Field documentation
This commit is contained in:
committed by
GitHub
parent
878ba0f4a0
commit
f42760e2d1
@@ -1842,6 +1842,138 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-choice-card": {
|
||||
name: "field-choice-card",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-choice-card")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-choice-card.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-checkbox": {
|
||||
name: "field-checkbox",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-checkbox")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-checkbox.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-demo": {
|
||||
name: "field-demo",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-demo")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-demo.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-fieldset": {
|
||||
name: "field-fieldset",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-fieldset")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-fieldset.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-input": {
|
||||
name: "field-input",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-input")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-input.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-group": {
|
||||
name: "field-group",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-group")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-group.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-radio": {
|
||||
name: "field-radio",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-radio")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-radio.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-responsive": {
|
||||
name: "field-responsive",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-responsive")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-responsive.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-select": {
|
||||
name: "field-select",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-select")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-select.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-slider": {
|
||||
name: "field-slider",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-slider")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-slider.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-switch": {
|
||||
name: "field-switch",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-switch")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-switch.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"field-textarea": {
|
||||
name: "field-textarea",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/field-textarea")),
|
||||
source: "",
|
||||
files: ["registry/default/example/field-textarea.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"form-patterns-pagelayout": {
|
||||
name: "form-patterns-pagelayout",
|
||||
type: "components:example",
|
||||
|
||||
@@ -291,6 +291,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/dropdown-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Field',
|
||||
href: '/docs/components/field',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Form',
|
||||
href: '/docs/components/form',
|
||||
|
||||
333
apps/design-system/content/docs/components/field.mdx
Normal file
333
apps/design-system/content/docs/components/field.mdx
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
title: Field
|
||||
description: Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
|
||||
component: true
|
||||
source:
|
||||
shadcn: true
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="field-demo"
|
||||
className="[&_.preview]:h-[800px] [&_.preview]:p-6 md:[&_.preview]:h-[850px]"
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="cli">
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add field
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="manual">
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="field" title="components/ui/field.tsx" />
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx showLineNumbers
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from '@/components/ui/field'
|
||||
```
|
||||
|
||||
```tsx showLineNumbers
|
||||
<FieldSet>
|
||||
<FieldLegend>Profile</FieldLegend>
|
||||
<FieldDescription>This appears on invoices and emails.</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="name">Full name</FieldLabel>
|
||||
<Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
|
||||
<FieldDescription>This appears on invoices and emails.</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="username">Username</FieldLabel>
|
||||
<Input id="username" autoComplete="off" aria-invalid />
|
||||
<FieldError>Choose another username.</FieldError>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Switch id="newsletter" />
|
||||
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
## Anatomy
|
||||
|
||||
The `Field` family is designed for composing accessible forms. A typical field is structured as follows:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Field>
|
||||
<FieldLabel htmlFor="input-id">Label</FieldLabel>
|
||||
{/* Input, Select, Switch, etc. */}
|
||||
<FieldDescription>Optional helper text.</FieldDescription>
|
||||
<FieldError>Validation message.</FieldError>
|
||||
</Field>
|
||||
```
|
||||
|
||||
- `Field` is the core wrapper for a single field.
|
||||
- `FieldContent` is a flex column that groups label and description. Not required if you have no description.
|
||||
- Wrap related fields with `FieldGroup`, and use `FieldSet` with `FieldLegend` for semantic grouping.
|
||||
|
||||
## Form
|
||||
|
||||
See the [Form](/docs/forms) documentation for building forms with the `Field` component and [React Hook Form](/docs/forms/react-hook-form) or [Tanstack Form](/docs/forms/tanstack-form).
|
||||
|
||||
## Examples
|
||||
|
||||
### Input
|
||||
|
||||
<ComponentPreview name="field-input" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Textarea
|
||||
|
||||
<ComponentPreview name="field-textarea" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Select
|
||||
|
||||
<ComponentPreview name="field-select" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Slider
|
||||
|
||||
<ComponentPreview name="field-slider" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Fieldset
|
||||
|
||||
<ComponentPreview name="field-fieldset" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Checkbox
|
||||
|
||||
<ComponentPreview name="field-checkbox" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Radio
|
||||
|
||||
<ComponentPreview name="field-radio" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Switch
|
||||
|
||||
<ComponentPreview name="field-switch" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Choice Card
|
||||
|
||||
Wrap `Field` components inside `FieldLabel` to create selectable field groups. This works with `RadioItem`, `Checkbox` and `Switch` components.
|
||||
|
||||
<ComponentPreview name="field-choice-card" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Field Group
|
||||
|
||||
Stack `Field` components with `FieldGroup`. Add `FieldSeparator` to divide them.
|
||||
|
||||
<ComponentPreview name="field-group" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
## Responsive Layout
|
||||
|
||||
- **Vertical fields:** Default orientation stacks label, control, and helper text—ideal for mobile-first layouts.
|
||||
- **Horizontal fields:** Set `orientation="horizontal"` on `Field` to align the label and control side-by-side. Pair with `FieldContent` to keep descriptions aligned.
|
||||
- **Responsive fields:** Set `orientation="responsive"` for automatic column layouts inside container-aware parents. Apply `@container/field-group` classes on `FieldGroup` to switch orientations at specific breakpoints.
|
||||
|
||||
<ComponentPreview
|
||||
name="field-responsive"
|
||||
className="!mb-4 [&_.preview]:h-[650px] [&_.preview]:p-6 [&_.preview]:md:h-[500px] [&_.preview]:md:p-10"
|
||||
/>
|
||||
|
||||
## Validation and Errors
|
||||
|
||||
- Add `data-invalid` to `Field` to switch the entire block into an error state.
|
||||
- Add `aria-invalid` on the input itself for assistive technologies.
|
||||
- Render `FieldError` immediately after the control or inside `FieldContent` to keep error messages aligned with the field.
|
||||
|
||||
```tsx showLineNumbers /data-invalid/ /aria-invalid/
|
||||
<Field data-invalid>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" type="email" aria-invalid />
|
||||
<FieldError>Enter a valid email address.</FieldError>
|
||||
</Field>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- `FieldSet` and `FieldLegend` keep related controls grouped for keyboard and assistive tech users.
|
||||
- `Field` outputs `role="group"` so nested controls inherit labeling from `FieldLabel` and `FieldLegend` when combined.
|
||||
- Apply `FieldSeparator` sparingly to ensure screen readers encounter clear section boundaries.
|
||||
|
||||
## API Reference
|
||||
|
||||
### FieldSet
|
||||
|
||||
Container that renders a semantic `fieldset` with spacing presets.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldSet>
|
||||
<FieldLegend>Delivery</FieldLegend>
|
||||
<FieldGroup>{/* Fields */}</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
### FieldLegend
|
||||
|
||||
Legend element for a `FieldSet`. Switch to the `label` variant to align with label sizing.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | --------------------- | ---------- |
|
||||
| `variant` | `"legend" \| "label"` | `"legend"` |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldLegend variant="label">Notification Preferences</FieldLegend>
|
||||
```
|
||||
|
||||
The `FieldLegend` has two variants: `legend` and `label`. The `label` variant applies label sizing and alignment. Handy if you have nested `FieldSet`.
|
||||
|
||||
### FieldGroup
|
||||
|
||||
Layout wrapper that stacks `Field` components and enables container queries for responsive orientations.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldGroup className="@container/field-group flex flex-col gap-6">
|
||||
<Field>{/* ... */}</Field>
|
||||
<Field>{/* ... */}</Field>
|
||||
</FieldGroup>
|
||||
```
|
||||
|
||||
### Field
|
||||
|
||||
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| -------------- | -------------------------------------------- | ------------ |
|
||||
| `orientation` | `"vertical" \| "horizontal" \| "responsive"` | `"vertical"` |
|
||||
| `className` | `string` | |
|
||||
| `data-invalid` | `boolean` | |
|
||||
|
||||
```tsx
|
||||
<Field orientation="horizontal">
|
||||
<FieldLabel htmlFor="remember">Remember me</FieldLabel>
|
||||
<Switch id="remember" />
|
||||
</Field>
|
||||
```
|
||||
|
||||
### FieldContent
|
||||
|
||||
Flex column that groups control and descriptions when the label sits beside the control. Not required if you have no description.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<Field>
|
||||
<Checkbox id="notifications" />
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="notifications">Notifications</FieldLabel>
|
||||
<FieldDescription>Email, SMS, and push options.</FieldDescription>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
```
|
||||
|
||||
### FieldLabel
|
||||
|
||||
Label styled for both direct inputs and nested `Field` children.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | --------- | ------- |
|
||||
| `className` | `string` | |
|
||||
| `asChild` | `boolean` | `false` |
|
||||
|
||||
```tsx
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
```
|
||||
|
||||
### FieldTitle
|
||||
|
||||
Renders a title with label styling inside `FieldContent`.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldContent>
|
||||
<FieldTitle>Enable Touch ID</FieldTitle>
|
||||
<FieldDescription>Unlock your device faster.</FieldDescription>
|
||||
</FieldContent>
|
||||
```
|
||||
|
||||
### FieldDescription
|
||||
|
||||
Helper text slot that automatically balances long lines in horizontal layouts.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldDescription>We never share your email with anyone.</FieldDescription>
|
||||
```
|
||||
|
||||
### FieldSeparator
|
||||
|
||||
Visual divider to separate sections inside a `FieldGroup`. Accepts optional inline content.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldSeparator>Or continue with</FieldSeparator>
|
||||
```
|
||||
|
||||
### FieldError
|
||||
|
||||
Accessible error container that accepts children or an `errors` array (e.g., from `react-hook-form`).
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | ------------------------------------------ | ------- |
|
||||
| `errors` | `Array<{ message?: string } \| undefined>` | |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldError errors={errors.username} />
|
||||
```
|
||||
|
||||
When the `errors` array contains multiple messages, the component renders a list automatically.
|
||||
|
||||
`FieldError` also accepts issues produced by any validator that implements [Standard Schema](https://standardschema.dev/), including Zod, Valibot, and ArkType. Pass the `issues` array from the schema result directly to render a unified error list across libraries.
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Checkbox_Shadcn_ as Checkbox } from 'ui'
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldCheckbox() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend variant="label">Show these items on the desktop</FieldLegend>
|
||||
<FieldDescription>Select the items you want to show on the desktop.</FieldDescription>
|
||||
<FieldGroup className="gap-3">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="finder-pref-9k2-hard-disks-ljj" />
|
||||
<FieldLabel
|
||||
htmlFor="finder-pref-9k2-hard-disks-ljj"
|
||||
className="font-normal"
|
||||
defaultChecked
|
||||
>
|
||||
Hard disks
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="finder-pref-9k2-external-disks-1yg" />
|
||||
<FieldLabel htmlFor="finder-pref-9k2-external-disks-1yg" className="font-normal">
|
||||
External disks
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="finder-pref-9k2-cds-dvds-fzt" />
|
||||
<FieldLabel htmlFor="finder-pref-9k2-cds-dvds-fzt" className="font-normal">
|
||||
CDs, DVDs, and iPods
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="finder-pref-9k2-connected-servers-6l2" />
|
||||
<FieldLabel htmlFor="finder-pref-9k2-connected-servers-6l2" className="font-normal">
|
||||
Connected servers
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="finder-pref-9k2-sync-folders-nep" defaultChecked />
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="finder-pref-9k2-sync-folders-nep">
|
||||
Sync Desktop & Documents folders
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Your Desktop & Documents folders are being synced with iCloud Drive. You can access
|
||||
them from other devices.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
FieldSet,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldGroup,
|
||||
FieldTitle,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
import { RadioGroup, RadioGroupItem } from 'ui/src/components/shadcn/ui/radio-group'
|
||||
|
||||
export default function FieldChoiceCard() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLabel htmlFor="compute-environment-p8w">Compute Environment</FieldLabel>
|
||||
<FieldDescription>Select the compute environment for your cluster.</FieldDescription>
|
||||
<RadioGroup defaultValue="kubernetes">
|
||||
<FieldLabel htmlFor="kubernetes-r2h">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Kubernetes</FieldTitle>
|
||||
<FieldDescription>
|
||||
Run GPU workloads on a K8s configured cluster.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel htmlFor="vm-z4k">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Virtual Machine</FieldTitle>
|
||||
<FieldDescription>
|
||||
Access a VM configured cluster to run GPU workloads.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="vm" id="vm-z4k" />
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
124
apps/design-system/registry/default/example/field-demo.tsx
Normal file
124
apps/design-system/registry/default/example/field-demo.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Input, Checkbox_Shadcn_ as Checkbox, Textarea, Button } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
FieldLegend,
|
||||
FieldDescription,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldGroup,
|
||||
FieldSeparator,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from 'ui/src/components/shadcn/ui/select'
|
||||
|
||||
export default function FieldDemo() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>All transactions are secure and encrypted</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-card-name-43j">Name on Card</FieldLabel>
|
||||
<Input id="checkout-7j9-card-name-43j" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-card-number-uw1">Card Number</FieldLabel>
|
||||
<Input
|
||||
id="checkout-7j9-card-number-uw1"
|
||||
placeholder="1234 5678 9012 3456"
|
||||
required
|
||||
/>
|
||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
||||
</Field>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-exp-month-ts6">Month</FieldLabel>
|
||||
<Select defaultValue="">
|
||||
<SelectTrigger id="checkout-exp-month-ts6">
|
||||
<SelectValue placeholder="MM" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="01">01</SelectItem>
|
||||
<SelectItem value="02">02</SelectItem>
|
||||
<SelectItem value="03">03</SelectItem>
|
||||
<SelectItem value="04">04</SelectItem>
|
||||
<SelectItem value="05">05</SelectItem>
|
||||
<SelectItem value="06">06</SelectItem>
|
||||
<SelectItem value="07">07</SelectItem>
|
||||
<SelectItem value="08">08</SelectItem>
|
||||
<SelectItem value="09">09</SelectItem>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="11">11</SelectItem>
|
||||
<SelectItem value="12">12</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-exp-year-f59">Year</FieldLabel>
|
||||
<Select defaultValue="">
|
||||
<SelectTrigger id="checkout-7j9-exp-year-f59">
|
||||
<SelectValue placeholder="YYYY" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="2024">2024</SelectItem>
|
||||
<SelectItem value="2025">2025</SelectItem>
|
||||
<SelectItem value="2026">2026</SelectItem>
|
||||
<SelectItem value="2027">2027</SelectItem>
|
||||
<SelectItem value="2028">2028</SelectItem>
|
||||
<SelectItem value="2029">2029</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-cvv">CVV</FieldLabel>
|
||||
<Input id="checkout-7j9-cvv" placeholder="123" required />
|
||||
</Field>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="checkout-7j9-same-as-shipping-wgm" defaultChecked />
|
||||
<FieldLabel htmlFor="checkout-7j9-same-as-shipping-wgm" className="font-normal">
|
||||
Same as shipping address
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="checkout-7j9-optional-comments">Comments</FieldLabel>
|
||||
<Textarea
|
||||
id="checkout-7j9-optional-comments"
|
||||
placeholder="Add any additional comments"
|
||||
className="resize-none"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
<Button htmlType="button" type="default">
|
||||
Cancel
|
||||
</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Input } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
FieldLegend,
|
||||
FieldDescription,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldGroup,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldFieldset() {
|
||||
return (
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
<FieldSet>
|
||||
<FieldLegend>Address Information</FieldLegend>
|
||||
<FieldDescription>We need your address to deliver your order.</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="street">Street Address</FieldLabel>
|
||||
<Input id="street" type="text" placeholder="123 Main St" />
|
||||
</Field>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="city">City</FieldLabel>
|
||||
<Input id="city" type="text" placeholder="New York" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="zip">Postal Code</FieldLabel>
|
||||
<Input id="zip" type="text" placeholder="90502" />
|
||||
</Field>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
54
apps/design-system/registry/default/example/field-group.tsx
Normal file
54
apps/design-system/registry/default/example/field-group.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Checkbox_Shadcn_ as Checkbox } from 'ui'
|
||||
import {
|
||||
FieldGroup,
|
||||
FieldSet,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
Field,
|
||||
FieldSeparator,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldGroupExample() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLabel>Responses</FieldLabel>
|
||||
<FieldDescription>
|
||||
Get notified when ChatGPT responds to requests that take time, like research or image
|
||||
generation.
|
||||
</FieldDescription>
|
||||
<FieldGroup data-slot="checkbox-group">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="push" defaultChecked disabled />
|
||||
<FieldLabel htmlFor="push" className="font-normal">
|
||||
Push notifications
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLabel>Tasks</FieldLabel>
|
||||
<FieldDescription>
|
||||
Get notified when tasks you've created have updates. <a href="/">Manage tasks</a>
|
||||
</FieldDescription>
|
||||
<FieldGroup data-slot="checkbox-group">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="push-tasks" />
|
||||
<FieldLabel htmlFor="push-tasks" className="font-normal">
|
||||
Push notifications
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="email-tasks" />
|
||||
<FieldLabel htmlFor="email-tasks" className="font-normal">
|
||||
Email notifications
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
apps/design-system/registry/default/example/field-hear.tsx
Normal file
67
apps/design-system/registry/default/example/field-hear.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Card, CardContent, Checkbox_Shadcn_ as Checkbox } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
FieldLegend,
|
||||
FieldDescription,
|
||||
FieldLabel,
|
||||
FieldGroup,
|
||||
Field,
|
||||
FieldTitle,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: 'Social Media',
|
||||
value: 'social-media',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Search Engine',
|
||||
value: 'search-engine',
|
||||
},
|
||||
{
|
||||
label: 'Referral',
|
||||
value: 'referral',
|
||||
},
|
||||
{
|
||||
label: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
]
|
||||
|
||||
export function FieldHear() {
|
||||
return (
|
||||
<Card className="py-4 shadow-none">
|
||||
<CardContent className="px-4">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet className="gap-4">
|
||||
<FieldLegend>How did you hear about us?</FieldLegend>
|
||||
<FieldDescription className="line-clamp-1">
|
||||
Select the option that best describes how you heard about us.
|
||||
</FieldDescription>
|
||||
<FieldGroup className="flex flex-row flex-wrap gap-2 [--radius:9999rem]">
|
||||
{options.map((option) => (
|
||||
<FieldLabel htmlFor={option.value} key={option.value} className="!w-fit">
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
className="gap-1.5 overflow-hidden !px-3 !py-1.5 transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:!px-2"
|
||||
>
|
||||
<Checkbox
|
||||
value={option.value}
|
||||
id={option.value}
|
||||
defaultChecked={option.value === 'social-media'}
|
||||
className="-ml-6 -translate-x-1 rounded-full transition-all duration-100 ease-linear data-[state=checked]:ml-0 data-[state=checked]:translate-x-0"
|
||||
/>
|
||||
<FieldTitle>{option.label}</FieldTitle>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
))}
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
29
apps/design-system/registry/default/example/field-input.tsx
Normal file
29
apps/design-system/registry/default/example/field-input.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Input } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldInput() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="username">Username</FieldLabel>
|
||||
<Input id="username" type="text" placeholder="Max Leiter" />
|
||||
<FieldDescription>Choose a unique username for your account.</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||||
<FieldDescription>Must be at least 8 characters long.</FieldDescription>
|
||||
<Input id="password" type="password" placeholder="••••••••" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
apps/design-system/registry/default/example/field-radio.tsx
Normal file
33
apps/design-system/registry/default/example/field-radio.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { FieldSet, FieldLabel, FieldDescription, Field } from 'ui/src/components/shadcn/ui/field'
|
||||
import { RadioGroup, RadioGroupItem } from 'ui/src/components/shadcn/ui/radio-group'
|
||||
|
||||
export default function FieldRadio() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldSet>
|
||||
<FieldLabel>Subscription Plan</FieldLabel>
|
||||
<FieldDescription>Yearly and lifetime plans offer significant savings.</FieldDescription>
|
||||
<RadioGroup defaultValue="monthly">
|
||||
<Field orientation="horizontal">
|
||||
<RadioGroupItem value="monthly" id="plan-monthly" />
|
||||
<FieldLabel htmlFor="plan-monthly" className="font-normal">
|
||||
Monthly ($9.99/month)
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<RadioGroupItem value="yearly" id="plan-yearly" />
|
||||
<FieldLabel htmlFor="plan-yearly" className="font-normal">
|
||||
Yearly ($99.99/year)
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<RadioGroupItem value="lifetime" id="plan-lifetime" />
|
||||
<FieldLabel htmlFor="plan-lifetime" className="font-normal">
|
||||
Lifetime ($299.99)
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Input, Textarea, Button } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
FieldLegend,
|
||||
FieldDescription,
|
||||
FieldSeparator,
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldLabel,
|
||||
FieldGroup,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldResponsive() {
|
||||
return (
|
||||
<div className="w-full max-w-4xl">
|
||||
<form>
|
||||
<FieldSet>
|
||||
<FieldLegend>Profile</FieldLegend>
|
||||
<FieldDescription>Fill in your profile information.</FieldDescription>
|
||||
<FieldSeparator />
|
||||
<FieldGroup>
|
||||
<Field orientation="responsive">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="name">Name</FieldLabel>
|
||||
<FieldDescription>Provide your full name for identification</FieldDescription>
|
||||
</FieldContent>
|
||||
<Input id="name" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<FieldSeparator />
|
||||
<Field orientation="responsive">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="lastName">Message</FieldLabel>
|
||||
<FieldDescription>
|
||||
You can write your message here. Keep it short, preferably under 100 characters.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<Textarea
|
||||
id="message"
|
||||
placeholder="Hello, world!"
|
||||
required
|
||||
className="min-h-[100px] resize-none sm:min-w-[300px]"
|
||||
/>
|
||||
</Field>
|
||||
<FieldSeparator />
|
||||
<Field orientation="responsive">
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
<Button htmlType="button" type="default">
|
||||
Cancel
|
||||
</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
apps/design-system/registry/default/example/field-select.tsx
Normal file
34
apps/design-system/registry/default/example/field-select.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Field, FieldLabel, FieldDescription } from 'ui/src/components/shadcn/ui/field'
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from 'ui/src/components/shadcn/ui/select'
|
||||
|
||||
export default function FieldSelect() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<Field>
|
||||
<FieldLabel>Department</FieldLabel>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose department" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="engineering">Engineering</SelectItem>
|
||||
<SelectItem value="design">Design</SelectItem>
|
||||
<SelectItem value="marketing">Marketing</SelectItem>
|
||||
<SelectItem value="sales">Sales</SelectItem>
|
||||
<SelectItem value="support">Customer Support</SelectItem>
|
||||
<SelectItem value="hr">Human Resources</SelectItem>
|
||||
<SelectItem value="finance">Finance</SelectItem>
|
||||
<SelectItem value="operations">Operations</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldDescription>Select your department or area of work.</FieldDescription>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
30
apps/design-system/registry/default/example/field-slider.tsx
Normal file
30
apps/design-system/registry/default/example/field-slider.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Slider } from 'ui'
|
||||
import { Field, FieldTitle, FieldDescription } from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldSlider() {
|
||||
const [value, setValue] = useState([200, 800])
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<Field>
|
||||
<FieldTitle>Price Range</FieldTitle>
|
||||
<FieldDescription>
|
||||
Set your budget range ($
|
||||
<span className="font-medium tabular-nums">{value[0]}</span> -{' '}
|
||||
<span className="font-medium tabular-nums">{value[1]}</span>).
|
||||
</FieldDescription>
|
||||
<Slider
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
max={1000}
|
||||
min={0}
|
||||
step={10}
|
||||
className="mt-2 w-full"
|
||||
aria-label="Price Range"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
apps/design-system/registry/default/example/field-switch.tsx
Normal file
24
apps/design-system/registry/default/example/field-switch.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Switch } from 'ui'
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldSwitch() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="2fa">Multi-factor authentication</FieldLabel>
|
||||
<FieldDescription>
|
||||
Enable multi-factor authentication. If you do not have a two-factor device, you can use
|
||||
a one-time code sent to your email.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<Switch id="2fa" />
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Textarea } from 'ui'
|
||||
import {
|
||||
FieldSet,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
} from 'ui/src/components/shadcn/ui/field'
|
||||
|
||||
export default function FieldTextarea() {
|
||||
return (
|
||||
<div className="w-full max-w-md">
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
|
||||
<Textarea id="feedback" placeholder="Your feedback helps us improve..." rows={4} />
|
||||
<FieldDescription>Share your thoughts about our service.</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1038,6 +1038,66 @@ export const examples: Registry = [
|
||||
type: 'components:example',
|
||||
files: ['example/form-item-layout-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-choice-card',
|
||||
type: 'components:example',
|
||||
files: ['example/field-choice-card.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-checkbox',
|
||||
type: 'components:example',
|
||||
files: ['example/field-checkbox.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/field-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-fieldset',
|
||||
type: 'components:example',
|
||||
files: ['example/field-fieldset.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-input',
|
||||
type: 'components:example',
|
||||
files: ['example/field-input.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-group',
|
||||
type: 'components:example',
|
||||
files: ['example/field-group.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-radio',
|
||||
type: 'components:example',
|
||||
files: ['example/field-radio.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-responsive',
|
||||
type: 'components:example',
|
||||
files: ['example/field-responsive.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-select',
|
||||
type: 'components:example',
|
||||
files: ['example/field-select.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-slider',
|
||||
type: 'components:example',
|
||||
files: ['example/field-slider.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-switch',
|
||||
type: 'components:example',
|
||||
files: ['example/field-switch.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'field-textarea',
|
||||
type: 'components:example',
|
||||
files: ['example/field-textarea.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'form-patterns-pagelayout',
|
||||
type: 'components:example',
|
||||
|
||||
@@ -127,6 +127,19 @@ export {
|
||||
AlertDescription as AlertDescription_Shadcn_,
|
||||
} from './src/components/shadcn/ui/alert'
|
||||
|
||||
export {
|
||||
Field as Field_Shadcn_,
|
||||
FieldContent as FieldContent_Shadcn_,
|
||||
FieldDescription as FieldDescription_Shadcn_,
|
||||
FieldError as FieldError_Shadcn_,
|
||||
FieldGroup as FieldGroup_Shadcn_,
|
||||
FieldLabel as FieldLabel_Shadcn_,
|
||||
FieldLegend as FieldLegend_Shadcn_,
|
||||
FieldSeparator as FieldSeparator_Shadcn_,
|
||||
FieldSet as FieldSet_Shadcn_,
|
||||
FieldTitle as FieldTitle_Shadcn_,
|
||||
} from './src/components/shadcn/ui/field'
|
||||
|
||||
export {
|
||||
useFormField as useFormField_Shadcn_,
|
||||
Form as Form_Shadcn_,
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
|
||||
230
packages/ui/src/components/shadcn/ui/field.tsx
Normal file
230
packages/ui/src/components/shadcn/ui/field.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import { useMemo } from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '../../../lib/utils/cn'
|
||||
import { Label } from './label'
|
||||
import { Separator } from './separator'
|
||||
|
||||
function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
||||
return (
|
||||
<fieldset
|
||||
data-slot="field-set"
|
||||
className={cn(
|
||||
'flex flex-col gap-6',
|
||||
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldLegend({
|
||||
className,
|
||||
variant = 'legend',
|
||||
...props
|
||||
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
|
||||
return (
|
||||
<legend
|
||||
data-slot="field-legend"
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
'mb-3 font-medium',
|
||||
'data-[variant=legend]:text-base',
|
||||
'data-[variant=label]:text-sm',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-group"
|
||||
className={cn(
|
||||
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const fieldVariants = cva('group/field data-[invalid=true]:text-destructive flex w-full gap-3', {
|
||||
variants: {
|
||||
orientation: {
|
||||
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
|
||||
horizontal: [
|
||||
'flex-row items-center',
|
||||
'[&>[data-slot=field-label]]:flex-auto',
|
||||
'has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start',
|
||||
],
|
||||
responsive: [
|
||||
'@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto',
|
||||
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
||||
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'vertical',
|
||||
},
|
||||
})
|
||||
|
||||
function Field({
|
||||
className,
|
||||
orientation = 'vertical',
|
||||
...props
|
||||
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="field"
|
||||
data-orientation={orientation}
|
||||
className={cn(fieldVariants({ orientation }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-content"
|
||||
className={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
||||
return (
|
||||
<Label
|
||||
data-slot="field-label"
|
||||
className={cn(
|
||||
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
|
||||
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4',
|
||||
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-label"
|
||||
className={cn(
|
||||
'flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
||||
return (
|
||||
<p
|
||||
data-slot="field-description"
|
||||
className={cn(
|
||||
'text-muted-foreground text-sm font-normal leading-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
||||
'nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5',
|
||||
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldSeparator({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<'div'> & {
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-slot="field-separator"
|
||||
data-content={!!children}
|
||||
className={cn(
|
||||
'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Separator className="absolute inset-0 top-1/2" />
|
||||
{children && (
|
||||
<span
|
||||
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
|
||||
data-slot="field-separator-content"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FieldError({
|
||||
className,
|
||||
children,
|
||||
errors,
|
||||
...props
|
||||
}: React.ComponentProps<'div'> & {
|
||||
errors?: Array<{ message?: string } | undefined>
|
||||
}) {
|
||||
const content = useMemo(() => {
|
||||
if (children) {
|
||||
return children
|
||||
}
|
||||
|
||||
if (!errors) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (errors?.length === 1 && errors[0]?.message) {
|
||||
return errors[0].message
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="ml-4 flex list-disc flex-col gap-1">
|
||||
{errors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
||||
</ul>
|
||||
)
|
||||
}, [children, errors])
|
||||
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
data-slot="field-error"
|
||||
className={cn('text-destructive text-sm font-normal', className)}
|
||||
{...props}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldContent,
|
||||
FieldTitle,
|
||||
}
|
||||
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
@@ -6,12 +6,63 @@ settings:
|
||||
|
||||
catalogs:
|
||||
default:
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.26.0
|
||||
version: 10.27.0
|
||||
'@supabase/auth-js':
|
||||
specifier: 2.86.0
|
||||
version: 2.86.0
|
||||
'@supabase/postgrest-js':
|
||||
specifier: 2.86.0
|
||||
version: 2.86.0
|
||||
'@supabase/realtime-js':
|
||||
specifier: 2.86.0
|
||||
version: 2.86.0
|
||||
'@supabase/supabase-js':
|
||||
specifier: 2.86.0
|
||||
version: 2.86.0
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.13.14
|
||||
'@types/react':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.3
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.0
|
||||
next:
|
||||
specifier: ^15.5.7
|
||||
version: 15.5.7
|
||||
react:
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.1
|
||||
recharts:
|
||||
specifier: ^2.15.4
|
||||
version: 2.15.4
|
||||
tailwindcss:
|
||||
specifier: 3.4.1
|
||||
version: 3.4.1
|
||||
tsx:
|
||||
specifier: 4.20.3
|
||||
version: 4.20.3
|
||||
typescript:
|
||||
specifier: ~5.9.0
|
||||
version: 5.9.2
|
||||
valtio:
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0
|
||||
vite:
|
||||
specifier: ^7.1.11
|
||||
version: 7.1.11
|
||||
vitest:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.4
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
|
||||
overrides:
|
||||
'@eslint/eslintrc>js-yaml': ^4.1.1
|
||||
@@ -2234,7 +2285,7 @@ importers:
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-separator':
|
||||
specifier: ^1.1.1
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-slider':
|
||||
specifier: ^1.3.6
|
||||
|
||||
Reference in New Issue
Block a user