Compare commits

...

49 Commits

Author SHA1 Message Date
shadcn
8a4764ed91 chore: registry build 2025-10-15 12:05:03 +04:00
Chisom Uma
e934d4645b feat(registry): Add Austin UI Components to the registry index (#8456)
* feat(registry): Add Austin UI Components to the registry index

* Fix JSON syntax for @austin-ui registry URL

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 12:02:40 +04:00
shadcn
08b8e499d8 chore: update registries.json (#8470) 2025-10-15 11:52:43 +04:00
shadcn
69402b3579 ci: update deprecated to ignore www/package.json 2025-10-15 11:39:53 +04:00
shadcn
679c852254 Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-15 11:37:26 +04:00
github-actions[bot]
d478412e44 chore(release): version packages (#8453)
* chore(release): version packages

* chore(release): version packages

* chore: deps

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 11:37:01 +04:00
shadcn
d5c8a25150 Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-15 11:30:53 +04:00
Benjamin
26433a651c Added react-market registry (#8346)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 11:30:43 +04:00
shadcn
c3da716e94 debug: ci 2025-10-15 11:30:24 +04:00
dependabot[bot]
b2572d0287 chore(deps): bump tj-actions/changed-files in /.github/workflows (#8466)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44 to 46.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v44...v46)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: '46'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 11:30:03 +04:00
shadcn
b83f042416 fix: actions 2025-10-15 11:26:28 +04:00
shadcn
6567897393 fix: actions 2025-10-15 11:07:14 +04:00
shadcn
2675fa3941 ci: add deprecated action (#8465)
* ci: add deprecated action

* ci: add label

* Potential fix for code scanning alert no. 12: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-10-15 11:04:30 +04:00
Shodai Suzuki
fbda67c88c docs: remove isolated remix page from installation guides (#7027) 2025-10-15 10:39:15 +04:00
shadcn
e8674ee848 feat(shadcn): allow path to override targets (#8452) 2025-10-15 10:37:58 +04:00
kuzma031
adb66f4d43 fix(components): use type-only import for VariantProps in sidebar component to support verbatimModuleSyntax (#7437)
* fix sidebar variantprops imports

* chore: registry build

---------

Co-authored-by: Djordje Kuzmanovic <djordje@voiced.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 10:36:32 +04:00
shadcn
3afb46eaf6 feat: add llms.txt (#8460) 2025-10-14 23:30:07 +04:00
shadcn
7cd019ad36 feat(shadcn): add support for color vars (#8459)
* feat(shadcn): add support for color vars

* chore: add changeset
2025-10-14 23:07:27 +04:00
Serhat Arslan
bea7d30536 docs: fix next/link import syntax in component examples (#8427)
Fix incorrect named import syntax for Next.js Link component.

Changed from:
  import { Link } from "next/link"

To correct default import:
  import Link from "next/link"

Next.js Link is a default export, not a named export. The incorrect
syntax causes TypeScript error:
'Module "next/link" has no exported member "Link"'

Affected files:
- Button component docs
- Badge component docs
- Breadcrumb component docs (v4 and www)
- Navigation Menu component docs

Fixes issue where users copying code snippets get immediate errors.

Co-authored-by: serhatx1 <armonikadijital@gmail.com>
2025-10-14 22:38:58 +04:00
Ziad Beyens
40c3ff513a fix(registry): handle universal registry items with no files (#8420)
* fix(registry): handle universal registry items with no files

Allow registry items with registryDependencies but no files to be
considered universal registry items. Previously the function required
files.length to be truthy, which excluded valid items with only
registryDependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-14 17:14:05 +04:00
Gildas Garcia
89ebfdce47 feat(registry): Add Shadcn Admin Kit to the registry index (#8442) 2025-10-14 17:12:12 +04:00
Haz
b83023034a fix(cli): fix add registry item with at-property css rule (#8451)
* Add fixture and test

* Handle at-property as regular CSS rules in updateCssPlugin

* Add changeset entry

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-14 17:11:29 +04:00
Ujjwal Sharma
6a534d7954 fix(components): use type import for ToasterProps (#8376)
* fix(components): use type import for ToasterProps

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-14 16:40:17 +04:00
Felix
ef1987ded9 docs(Toast): add deprecation Callout (#6982)
* docs(Toast): add deprecation Callout

* docs(Toast): add tw4 and react19 specifics
2025-10-13 22:21:50 +04:00
Jakob Guddas
77bf7d28b4 feat(components): changed sonner defaults to use lucide icons (#7620)
* feat(components): changed sonner defaults to use lucide icons

* Update new-york-v4 sonner.tsx

* fix: icons and docs

* fix

* fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-13 22:16:50 +04:00
Ayush Shrestha
41f4f7357d refactor: initialize FormFieldContext and FormItemContext with null values rather empty object with type assertion (#4847) 2025-10-13 21:32:43 +04:00
Carlos Pegueros
bc99818e04 docs(www): fix sonner not showing on first render (#6235) 2025-10-13 21:17:32 +04:00
Shahar Ilany
162ba7b13c Small fixes for installation document (#6623)
* Fix TanStack Start icon color

* Fix a11y for Laravel and TanStack Start
2025-10-13 21:14:11 +04:00
Ryann Mack
f12db1e3a2 fix(sonner): support border radius from theme (#7825)
* fix(sonner): support border radius from theme

* chore: rebuild registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-13 21:12:00 +04:00
shadcn
ce3e2b1df8 Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-13 12:39:19 +04:00
shadcn
dcfe911b33 docs: button cursor 2025-10-13 12:38:58 +04:00
Jrocam
7210a4919a fix(components): checkbox alignment with grid 🔳 (#4772)
* fix(components): checkbox alignment with grid 🔳

* fix(checkbox): internal checkbox check alignment (default style)

* fix: new-york-v4

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-13 12:29:44 +04:00
Krishna Agarwal
d198908510 fix(field): return null when errors are empty (#8419)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-13 12:22:34 +04:00
shadcn
b0b1cd1f0d feat: add dropdown menu dialog example (#8438)
* feat: add dropdown menu dialog example

* chore: remove dropdown-dialog
2025-10-13 11:57:20 +04:00
shadcn
f3d70724b6 chore: remove new-components-01 (#8439) 2025-10-13 11:55:08 +04:00
Dada Khalandar
407e9c6802 fix: correct link to Form documentation in form.mdx (#8416) 2025-10-11 10:15:07 +04:00
shadcn
c67e630521 feat: add docs and examples for react-hook-form and tanstack form (#8412)
* feat: add next forms docs

* feat

* docs: rhf and tsf

* docs: forms

* feat: update react-hook-form docs

* feat: update docs for both lib

* docs: update tanstack docs

* docs: update

* fix

* fix

* fix

* add forms link in sidebar
2025-10-10 21:29:30 +04:00
Daniel Petho
f494411953 feat(registry): add fancy components to registries (#8397) 2025-10-10 16:09:13 +04:00
Mudunuri bhaskara karthikeya varma
a43c1d1342 feat: add @eldoraui registry URL to registries.json (#8379)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-09 11:06:55 +04:00
Hin
607a6fd127 feat: add shadcn map to the registry (#8375) 2025-10-08 20:15:45 +04:00
Irsyad A. Panjaitan
fbcc665b49 Add @intentui registry URL to registries.json (#8380) 2025-10-08 00:07:59 +04:00
Shivam S.
7ddcf31e43 fix(docs): correct Empty component structure in documentation (#8374) 2025-10-07 20:58:15 +04:00
Louis J.
3e39163b08 feat: add @elevenlabs-ui registry URL to registries.json (#8364) 2025-10-07 13:12:24 +04:00
shadcn
e311fdae04 fix(www): dashboard 2025-10-07 10:09:34 +04:00
shadcn
26640d9d88 fix: code 2025-10-06 16:57:33 +04:00
Seoku
3e20c228da fix(input-group-textarea): prevent child elements from overflowing by… (#8341)
* fix(input-group-textarea): prevent child elements from overflowing by adding min-w-0

* chore: run registry:build

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-06 16:11:01 +04:00
Morgan Feeney
0810c0e1a2 Add @zippystarter registry URL to registries.json (#8354) 2025-10-06 16:00:28 +04:00
Ahdeetai
1205ea5445 Add @scrollxui in trusted registries (#8322)
* Add @scrollxui in trusted registries

* fix: missing comma

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-06 16:00:12 +04:00
Rob Austin
4430ab8bab Add @shadcnblocks registry URL to registries.json (#8344)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-05 20:03:40 +04:00
209 changed files with 14267 additions and 5268 deletions

78
.github/workflows/deprecated.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: Deprecated
on:
pull_request_target:
types: [opened, synchronize]
permissions:
issues: write
contents: read
pull-requests: write
jobs:
deprecated:
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
apps/www/**
files_ignore: |
apps/www/public/r/**
base_sha: ${{ github.event.pull_request.base.sha }}
sha: ${{ github.event.pull_request.head.sha }}
- name: Comment on PR if www files changed
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const changedFiles = `${{ steps.changed-files.outputs.all_changed_files }}`.split(' ');
const wwwFiles = changedFiles.filter(file =>
file.startsWith('apps/www/') &&
!file.startsWith('apps/www/public/r/') &&
file !== 'apps/www/package.json'
);
if (wwwFiles.length > 0) {
const comment = `Looks like this PR modifies files in \`apps/www\`, which is deprecated.
Consider applying the change to \`apps/v4\` if relevant.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
// Add deprecated label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['deprecated']
});
} else {
// Remove deprecated label if no www files are changed
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'deprecated'
});
} catch (error) {
// Label doesn't exist, which is fine
console.log('Deprecated label not found, skipping removal');
}
}

View File

@@ -142,13 +142,7 @@ const chartConfig = {
export function ChartAreaInteractive() {
const isMobile = useIsMobile()
const [timeRange, setTimeRange] = React.useState("90d")
React.useEffect(() => {
if (isMobile) {
setTimeRange("7d")
}
}, [isMobile])
const [timeRange, setTimeRange] = React.useState("7d")
const filteredData = chartData.filter((item) => {
const date = new Date(item.date)

View File

@@ -9,12 +9,14 @@ export function ComponentPreviewTabs({
className,
align = "center",
hideCode = false,
chromeLessOnMobile = false,
component,
source,
...props
}: React.ComponentProps<"div"> & {
align?: "center" | "start" | "end"
hideCode?: boolean
chromeLessOnMobile?: boolean
component: React.ReactNode
source: React.ReactNode
}) {
@@ -51,7 +53,8 @@ export function ComponentPreviewTabs({
</Tabs>
<div
data-tab={tab}
className="data-[tab=code]:border-code relative rounded-lg border md:-mx-1"
data-chrome-less-on-mobile={chromeLessOnMobile}
className="data-[tab=code]:border-code relative rounded-lg border data-[chrome-less-on-mobile=true]:border-0 sm:data-[chrome-less-on-mobile=true]:border md:-mx-1"
>
<div
data-slot="preview"
@@ -61,7 +64,8 @@ export function ComponentPreviewTabs({
<div
data-align={align}
className={cn(
"preview flex h-[450px] w-full justify-center p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start"
"preview flex w-full justify-center data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start",
chromeLessOnMobile ? "sm:p-10" : "h-[450px] p-10"
)}
>
{component}

View File

@@ -10,6 +10,7 @@ export function ComponentPreview({
className,
align = "center",
hideCode = false,
chromeLessOnMobile = false,
...props
}: React.ComponentProps<"div"> & {
name: string
@@ -17,12 +18,13 @@ export function ComponentPreview({
description?: string
hideCode?: boolean
type?: "block" | "component" | "example"
chromeLessOnMobile?: boolean
}) {
const Component = Index[name]?.component
if (!Component) {
return (
<p className="text-muted-foreground text-sm">
<p className="text-muted-foreground mt-6 text-sm">
Component{" "}
<code className="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm">
{name}
@@ -63,6 +65,7 @@ export function ComponentPreview({
hideCode={hideCode}
component={<Component />}
source={<ComponentSource name={name} collapsible={false} />}
chromeLessOnMobile={chromeLessOnMobile}
{...props}
/>
)

View File

@@ -43,6 +43,14 @@ export async function ComponentSource({
return null
}
// Fix imports.
// Replace @/registry/new-york-v4/ with @/components/.
code = code.replaceAll("@/registry/new-york-v4/", "@/components/")
// Replace export default with export.
code = code.replaceAll("export default", "export")
code = code.replaceAll("/* eslint-disable react/no-children-prop */\n", "")
const lang = language ?? title?.split(".").pop() ?? "tsx"
const highlightedCode = await highlightCode(code, lang)

View File

@@ -31,6 +31,10 @@ const TOP_LEVEL_SECTIONS = [
name: "MCP Server",
href: "/docs/mcp",
},
{
name: "Forms",
href: "/docs/forms",
},
{
name: "Changelog",
href: "/docs/changelog",

View File

@@ -29,6 +29,10 @@ const TOP_LEVEL_SECTIONS = [
name: "MCP Server",
href: "/docs/mcp",
},
{
name: "Forms",
href: "/docs/forms",
},
{
name: "Changelog",
href: "/docs/changelog",

View File

@@ -34,15 +34,24 @@ import { Spinner } from "@/components/ui/spinner"
Here's what it looks like:
<ComponentPreview name="spinner-basic" className="[&_.preview]:h-[250px]" />
<ComponentPreview
name="spinner-basic"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
Here's what it looks like in a button:
<ComponentPreview name="spinner-button" className="[&_.preview]:h-[250px]" />
<ComponentPreview
name="spinner-button"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
You can edit the code and replace it with your own spinner.
<ComponentPreview name="spinner-custom" className="[&_.preview]:h-[250px]" />
<ComponentPreview
name="spinner-custom"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
### Kbd
@@ -65,7 +74,10 @@ Use `KbdGroup` to group keyboard keys together.
</KbdGroup>
```
<ComponentPreview name="kbd-demo" className="[&_.preview]:h-[250px]" />
<ComponentPreview
name="kbd-demo"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
You can add it to buttons, tooltips, input groups, and more.
@@ -73,7 +85,10 @@ You can add it to buttons, tooltips, input groups, and more.
I got a lot of requests for this one: Button Group. It's a container that groups related buttons together with consistent styling. Great for action groups, split buttons, and more.
<ComponentPreview name="button-group-demo" className="[&_.preview]:h-[250px]" />
<ComponentPreview
name="button-group-demo"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
Here's the code:
@@ -107,14 +122,14 @@ Use `ButtonGroupSeparator` to create split buttons. Classic dropdown pattern.
<ComponentPreview
name="button-group-dropdown"
className="[&_.preview]:h-[250px]"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
You can also use it to add prefix or suffix buttons and text to inputs.
<ComponentPreview
name="button-group-select"
className="[&_.preview]:h-[250px]"
className="[&_.preview]:h-[250px] [&_pre]:!h-[250px]"
/>
```tsx showLineNumbers
@@ -148,31 +163,37 @@ import {
Here's a preview with icons:
<ComponentPreview name="input-group-icon" className="[&_.preview]:h-[300px]" />
<ComponentPreview
name="input-group-icon"
className="[&_.preview]:h-[300px] [&_pre]:!h-[300px]"
/>
You can also add buttons to the input group.
<ComponentPreview
name="input-group-button"
className="[&_.preview]:h-[300px]"
className="[&_.preview]:h-[300px] [&_pre]:!h-[300px]"
/>
Or text, labels, tooltips,...
<ComponentPreview name="input-group-text" className="[&_.preview]:h-[350px]" />
<ComponentPreview
name="input-group-text"
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
/>
It also works with textareas so you can build really complex components with lots of knobs and dials or yet another prompt form.
<ComponentPreview
name="input-group-textarea"
className="[&_.preview]:h-[450px]"
className="[&_.preview]:h-[450px] [&_pre]:!h-[450px]"
/>
Oh here are some cool ones with spinners:
<ComponentPreview
name="input-group-spinner"
className="[&_.preview]:h-[350px]"
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
/>
### Field
@@ -202,15 +223,24 @@ Here's a basic field with an input:
</Field>
```
<ComponentPreview name="field-input" className="[&_.preview]:h-[350px]" />
<ComponentPreview
name="field-input"
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
/>
It works with all form controls. Inputs, textareas, selects, checkboxes, radios, switches, sliders, you name it. Here's a full example:
<ComponentPreview name="field-demo" className="[&_.preview]:h-[850px]" />
<ComponentPreview
name="field-demo"
className="[&_.preview]:h-[850px] [&_pre]:!h-[850px]"
/>
Here are some checkbox fields:
<ComponentPreview name="field-checkbox" className="[&_.preview]:h-[500px]" />
<ComponentPreview
name="field-checkbox"
className="[&_.preview]:h-[500px] [&_pre]:!h-[500px]"
/>
You can group fields together using `FieldGroup` and `FieldSet`. Perfect for
multi-section forms.
@@ -225,16 +255,25 @@ multi-section forms.
</FieldSet>
```
<ComponentPreview name="field-fieldset" className="[&_.preview]:h-[500px]" />
<ComponentPreview
name="field-fieldset"
className="[&_.preview]:h-[500px] [&_pre]:!h-[500px]"
/>
Making it responsive is easy. Use `orientation="responsive"` and it switches
between vertical and horizontal layouts based on container width. Done.
<ComponentPreview name="field-responsive" className="[&_.preview]:h-[600px]" />
<ComponentPreview
name="field-responsive"
className="[&_.preview]:h-[600px] [&_pre]:!h-[600px]"
/>
Wait here's more. Wrap your fields in `FieldLabel` to create a selectable field group. Really easy. And it looks great.
<ComponentPreview name="field-choice-card" className="[&_.preview]:h-[600px]" />
<ComponentPreview
name="field-choice-card"
className="[&_.preview]:h-[600px] [&_pre]:!h-[600px]"
/>
### Item
@@ -268,26 +307,26 @@ Here's a basic item:
<ComponentPreview
name="item-demo"
className="[&_.preview]:h-[300px] [&_.preview]:p-4"
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
/>
You can add icons, avatars, or images to the item.
<ComponentPreview
name="item-icon"
className="[&_.preview]:h-[300px] [&_.preview]:p-4"
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
/>
<ComponentPreview
name="item-avatar"
className="[&_.preview]:h-[300px] [&_.preview]:p-4"
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
/>
And here's what a list of items looks like with `ItemGroup`:
<ComponentPreview
name="item-group"
className="[&_.preview]:h-[500px] [&_.preview]:p-4"
className="[&_.preview]:h-[500px] [&_.preview]:p-4 [&_pre]:!h-[500px]"
/>
Need it as a link? Use the `asChild` prop:
@@ -308,7 +347,7 @@ Need it as a link? Use the `asChild` prop:
<ComponentPreview
name="item-link"
className="[&_.preview]:h-[400px] [&_.preview]:p-4"
className="[&_.preview]:h-[400px] [&_.preview]:p-4 [&_pre]:!h-[400px]"
/>
### Empty
@@ -342,16 +381,22 @@ Here's how you use it:
<ComponentPreview
name="empty-demo"
className="[&_.preview]:h-[400px] [&_.preview]:p-4"
className="[&_.preview]:h-[400px] [&_.preview]:p-4 [&_pre]:!h-[400px]"
/>
You can use it with avatars:
<ComponentPreview name="empty-avatar" className="[&_.preview]:h-[400px]" />
<ComponentPreview
name="empty-avatar"
className="[&_.preview]:h-[400px] [&_pre]:!h-[400px]"
/>
Or with input groups for things like search results or email subscriptions:
<ComponentPreview name="empty-input-group" className="[&_.preview]:h-[450px]" />
<ComponentPreview
name="empty-input-group"
className="[&_.preview]:h-[450px] [&_pre]:!h-[450px]"
/>
That's it. Seven new components. Works with all your libraries. Ready for your projects.

View File

@@ -14,6 +14,7 @@
"blocks",
"figma",
"changelog",
"[llms.txt](/llms.txt)",
"legacy"
]
}

View File

@@ -57,7 +57,7 @@ import { Badge } from "@/components/ui/badge"
You can use the `asChild` prop to make another component look like a badge. Here's an example of a link that looks like a badge.
```tsx showLineNumbers
import { Link } from "next/link"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"

View File

@@ -180,7 +180,7 @@ To use a custom link component from your routing library, you can use the `asChi
/>
```tsx showLineNumbers {1,8-10}
import { Link } from "next/link"
import Link from "next/link"
...

View File

@@ -71,7 +71,20 @@ import { Button } from "@/components/ui/button"
<Button variant="outline">Button</Button>
```
---
## Cursor
Tailwind v4 [switched](https://tailwindcss.com/docs/upgrade-guide#buttons-use-the-default-cursor) from `cursor: pointer` to `cursor: default` for the button component.
If you want to keep the `cursor: pointer` behavior, add the following code to your CSS file:
```css showLineNumbers title="globals.css"
@layer base {
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}
```
## Examples
@@ -265,7 +278,7 @@ To create a button group, use the `ButtonGroup` component. See the [Button Group
You can use the `asChild` prop to make another component look like a button. Here's an example of a link that looks like a button.
```tsx showLineNumbers
import { Link } from "next/link"
import Link from "next/link"
import { Button } from "@/components/ui/button"

View File

@@ -56,9 +56,3 @@ import { Checkbox } from "@/components/ui/checkbox"
```tsx
<Checkbox />
```
## Examples
### Form
<ComponentPreview name="checkbox-form-multiple" />

View File

@@ -143,7 +143,3 @@ export function ExampleCombobox() {
You can create a responsive combobox by using the `<Popover />` on desktop and the `<Drawer />` components on mobile.
<ComponentPreview name="combobox-responsive" />
### Form
<ComponentPreview name="combobox-form" />

View File

@@ -94,7 +94,3 @@ This component uses the `chrono-node` library to parse natural language dates.
title="Natural Language Picker"
description="A calendar with natural language picker."
/>
### Form
<ComponentPreview name="date-picker-form" />

View File

@@ -93,3 +93,19 @@ import {
name="dropdown-menu-radio-group"
description="A dropdown menu with radio items."
/>
### Dialog
This example shows how to open a dialog from a dropdown menu.
Use `modal={false}` on the `DropdownMenu` component.
```tsx showLineNumbers
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="outline">Actions</Button>
</DropdownMenuTrigger>
</DropdownMenu>
```
<ComponentPreview name="dropdown-menu-dialog" />

View File

@@ -57,9 +57,9 @@ import {
<EmptyMedia variant="icon">
<Icon />
</EmptyMedia>
<EmptyTitle>No data</EmptyTitle>
<EmptyDescription>No data found</EmptyDescription>
</EmptyHeader>
<EmptyTitle>No data</EmptyTitle>
<EmptyDescription>No data found</EmptyDescription>
<EmptyContent>
<Button>Add data</Button>
</EmptyContent>

View File

@@ -98,6 +98,10 @@ The `Field` family is designed for composing accessible forms. A typical field i
- `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

View File

@@ -1,10 +1,18 @@
---
title: React Hook Form
title: Form
description: Building forms with React Hook Form and Zod.
links:
doc: https://react-hook-form.com
---
import { InfoIcon } from "lucide-react"
<Callout icon={<InfoIcon />} title="We are not actively developing this component anymore.">
The Form component is an abstraction over the `react-hook-form` library. Going forward, we recommend using the [`<Field />`](/docs/components/field) component to build forms. See the [Form](/docs/forms) documentation for more information.
</Callout>
Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.
Well-designed HTML forms are:
@@ -119,8 +127,6 @@ npm install @radix-ui/react-label @radix-ui/react-slot react-hook-form @hookform
## Usage
<Steps>
### Create a form schema
Define the shape of your form using a Zod schema. You can read more about using Zod in the [Zod documentation](https://zod.dev).
@@ -233,23 +239,3 @@ export function ProfileForm() {
### Done
That's it. You now have a fully accessible form that is type-safe with client-side validation.
<ComponentPreview
name="input-form"
className="[&_[role=tablist]]:hidden [&>div>div:first-child]:hidden"
/>
</Steps>
## Examples
See the following links for more examples on how to use the `<Form />` component with other components:
- [Checkbox](/docs/components/checkbox#form)
- [Date Picker](/docs/components/date-picker#form)
- [Input](/docs/components/input#form)
- [Radio Group](/docs/components/radio-group#form)
- [Select](/docs/components/select#form)
- [Switch](/docs/components/switch#form)
- [Textarea](/docs/components/textarea#form)
- [Combobox](/docs/components/combobox#form)

View File

@@ -253,3 +253,9 @@ All other props are passed through to the underlying `<Textarea />` component.
</InputGroupAddon>
</InputGroup>
```
## Changelog
### 2025-10-06 `InputGroup`
Add the `min-w-0` class to the `InputGroup` component. See [diff](https://github.com/shadcn-ui/ui/pull/8341/files#diff-0e2ee95d0050ca4c5d82339df86c54e14a6739dc4638fdda0eec8f73aebc2da9).

View File

@@ -94,10 +94,6 @@ import { Input } from "@/components/ui/input"
description="An input component with a button."
/>
### Form
<ComponentPreview name="input-form" />
## Changelog
### 2025-09-18 Remove `flex` class

View File

@@ -83,7 +83,7 @@ import {
You can use the `asChild` prop to make another component look like a navigation menu trigger. Here's an example of a link that looks like a navigation menu trigger.
```tsx showLineNumbers title="components/example-navigation-menu.tsx"
import { Link } from "next/link"
import Link from "next/link"
export function NavigationMenuDemo() {
return (

View File

@@ -69,9 +69,3 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
</div>
</RadioGroup>
```
## Examples
### Form
<ComponentPreview name="radio-group-form" />

View File

@@ -84,7 +84,3 @@ import {
name="select-scrollable"
description="A select component with a scrollable list of options."
/>
### Form
<ComponentPreview name="select-form" />

View File

@@ -68,7 +68,7 @@ npm install sonner next-themes
<Step>Add the Toaster component</Step>
```tsx title="app/layout.tsx" {1,9}
```tsx showLineNumbers title="app/layout.tsx" {1,8}
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }) {
@@ -76,8 +76,8 @@ export default function RootLayout({ children }) {
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
<main>{children}</main>
</body>
</html>
)
@@ -99,3 +99,56 @@ import { toast } from "sonner"
```tsx
toast("Event has been created.")
```
## Examples
<ComponentPreview name="sonner-types" />
## Changelog
### 2025-10-13 Icons
We've updated the Sonner component to use icons from `lucide`. Update your `sonner.tsx` file to use the new icons.
```tsx showLineNumbers title="components/ui/sonner.tsx" {3-9,20-26}
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }
```

View File

@@ -56,9 +56,3 @@ import { Switch } from "@/components/ui/switch"
```tsx
<Switch />
```
## Examples
### Form
<ComponentPreview name="switch-form" />

View File

@@ -79,7 +79,3 @@ import { Textarea } from "@/components/ui/textarea"
name="textarea-with-button"
description="A textarea with a button"
/>
### Form
<ComponentPreview name="textarea-form" />

View File

@@ -0,0 +1,45 @@
---
title: Forms
description: Build forms with React and shadcn/ui.
---
import { ClipboardListIcon, InfoIcon } from "lucide-react"
## Pick Your Framework
Start by selecting your framework. Then follow the instructions to learn how to build forms with shadcn/ui and the form library of your choice.
<div className="mt-8 grid gap-4 sm:grid-cols-2 sm:gap-6">
<LinkedCard href="/docs/forms/react-hook-form">
<ClipboardListIcon className="size-10" />
<p className="mt-2 font-medium">React Hook Form</p>
</LinkedCard>
<LinkedCard href="/docs/forms/tanstack-form">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="size-10"
fill="currentColor"
>
<path d="M6.93 13.688a.343.343 0 0 1 .468.132l.063.106c.48.851.98 1.66 1.5 2.426a35.65 35.65 0 0 0 2.074 2.742.345.345 0 0 1-.039.484l-.074.066c-2.543 2.223-4.191 2.665-4.953 1.333-.746-1.305-.477-3.672.808-7.11a.344.344 0 0 1 .153-.18ZM17.75 16.3a.34.34 0 0 1 .395.27l.02.1c.628 3.286.187 4.93-1.325 4.93-1.48 0-3.36-1.402-5.649-4.203a.327.327 0 0 1-.074-.222c0-.188.156-.34.344-.34h.121a32.984 32.984 0 0 0 2.809-.098c1.07-.086 2.191-.23 3.359-.437zm.871-6.977a.353.353 0 0 1 .445-.21l.102.034c3.262 1.11 4.504 2.332 3.719 3.664-.766 1.305-2.993 2.254-6.684 2.848a.362.362 0 0 1-.238-.047.343.343 0 0 1-.125-.476l.062-.106a34.07 34.07 0 0 0 1.367-2.523c.477-.989.93-2.051 1.352-3.184zM7.797 8.34a.362.362 0 0 1 .238.047.343.343 0 0 1 .125.476l-.062.106a34.088 34.088 0 0 0-1.367 2.523c-.477.988-.93 2.051-1.352 3.184a.353.353 0 0 1-.445.21l-.102-.034C1.57 13.742.328 12.52 1.113 11.188 1.88 9.883 4.106 8.934 7.797 8.34Zm5.281-3.984c2.543-2.223 4.192-2.664 4.953-1.332.746 1.304.477 3.671-.808 7.109a.344.344 0 0 1-.153.18.343.343 0 0 1-.468-.133l-.063-.106a34.64 34.64 0 0 0-1.5-2.426 35.65 35.65 0 0 0-2.074-2.742.345.345 0 0 1 .039-.484ZM7.285 2.274c1.48 0 3.364 1.402 5.649 4.203a.349.349 0 0 1 .078.218.348.348 0 0 1-.348.344l-.117-.004a34.584 34.584 0 0 0-2.809.102 35.54 35.54 0 0 0-3.363.437.343.343 0 0 1-.394-.273l-.02-.098c-.629-3.285-.188-4.93 1.324-4.93Zm2.871 5.812h3.688a.638.638 0 0 1 .55.316l1.848 3.22a.644.644 0 0 1 0 .628l-1.847 3.223a.638.638 0 0 1-.551.316h-3.688a.627.627 0 0 1-.547-.316L7.758 12.25a.644.644 0 0 1 0-.629L9.61 8.402a.627.627 0 0 1 .546-.316Zm3.23.793a.638.638 0 0 1 .552.316l1.39 2.426a.644.644 0 0 1 0 .629l-1.39 2.43a.638.638 0 0 1-.551.316h-2.774a.627.627 0 0 1-.546-.316l-1.395-2.43a.644.644 0 0 1 0-.629l1.395-2.426a.627.627 0 0 1 .546-.316Zm-.491.867h-1.79a.624.624 0 0 0-.546.316l-.899 1.56a.644.644 0 0 0 0 .628l.899 1.563a.632.632 0 0 0 .547.316h1.789a.632.632 0 0 0 .547-.316l.898-1.563a.644.644 0 0 0 0-.629l-.898-1.558a.624.624 0 0 0-.547-.317Zm-.477.828c.227 0 .438.121.547.317l.422.73a.625.625 0 0 1 0 .629l-.422.734a.627.627 0 0 1-.547.317h-.836a.632.632 0 0 1-.547-.317l-.422-.734a.625.625 0 0 1 0-.629l.422-.73a.632.632 0 0 1 .547-.317zm-.418.817a.548.548 0 0 0-.473.273.547.547 0 0 0 0 .547.544.544 0 0 0 .473.27.544.544 0 0 0 .473-.27.547.547 0 0 0 0-.547.548.548 0 0 0-.473-.273Zm-4.422.546h.98M18.98 7.75c.391-1.895.477-3.344.223-4.398-.148-.63-.422-1.137-.84-1.508-.441-.39-1-.582-1.625-.582-1.035 0-2.12.472-3.281 1.367a14.9 14.9 0 0 0-1.473 1.316 1.206 1.206 0 0 0-.136-.144c-1.446-1.285-2.66-2.082-3.7-2.39-.617-.184-1.195-.2-1.722-.024-.559.187-1.004.574-1.317 1.117-.515.894-.652 2.074-.46 3.527.078.59.214 1.235.402 1.934a1.119 1.119 0 0 0-.215.047C3.008 8.62 1.71 9.269.926 10.015c-.465.442-.77.938-.883 1.481-.113.578 0 1.156.312 1.7.516.894 1.465 1.597 2.817 2.155.543.223 1.156.426 1.844.61a1.023 1.023 0 0 0-.07.226c-.391 1.891-.477 3.344-.223 4.395.148.629.425 1.14.84 1.508.44.39 1 .582 1.625.582 1.035 0 2.12-.473 3.28-1.364.477-.37.973-.816 1.489-1.336a1.2 1.2 0 0 0 .195.227c1.446 1.285 2.66 2.082 3.7 2.39.617.184 1.195.2 1.722.024.559-.187 1.004-.574 1.317-1.117.515-.894.652-2.074.46-3.527a14.941 14.941 0 0 0-.425-2.012 1.225 1.225 0 0 0 .238-.047c1.828-.61 3.125-1.258 3.91-2.004.465-.441.77-.937.883-1.48.113-.578 0-1.157-.313-1.7-.515-.894-1.464-1.597-2.816-2.156a14.576 14.576 0 0 0-1.906-.625.865.865 0 0 0 .059-.195z" />
</svg>
<p className="mt-2 font-medium">TanStack Form</p>
</LinkedCard>
<LinkedCard href="#" className="border border-dashed bg-transparent">
<svg
role="img"
viewBox="0 0 24 24"
className="size-10"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<title>React</title>
<path
d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"
/>
</svg>
<p className="mt-2 font-medium">useActionState</p>
<p className="text-muted-foreground mt-1 text-xs">(Coming Soon)</p>
</LinkedCard>
</div>

View File

@@ -0,0 +1,3 @@
{
"pages": ["react-hook-form", "tanstack-form"]
}

View File

@@ -0,0 +1,397 @@
---
title: Next.js
description: Build forms in React using useActionState and Server Actions.
---
import { InfoIcon } from "lucide-react"
In this guide, we will take a look at building forms with Next.js using `useActionState` and Server Actions. We'll cover building forms, validation, pending states, accessibility, and more.
## Demo
We are going to build the following form with a simple text input and a textarea. On submit, we'll use a server action to validate the form data and update the form state.
<ComponentPreview
name="form-next-demo"
className="[&_.preview]:h-[700px] [&_pre]:!h-[700px]"
/>
<Callout icon={<InfoIcon />}>
**Note:** The examples on this page intentionally disable browser validation
to show how schema validation and form errors work in server actions.
</Callout>
## Approach
This form leverages Next.js and React's built-in capabilities for form handling. We'll build our form using the `<Field />` component, which gives you **complete flexibility over the markup and styling**.
- Uses Next.js `<Form />` component for navigation and progressive enhancement.
- `<Field />` components for building accessible forms.
- `useActionState` for managing form state and errors.
- Handles loading states with pending prop.
- Server Actions for handling form submissions.
- Server-side validation using Zod.
## Anatomy
Here's a basic example of a form using the `<Field />` component.
```tsx showLineNumbers
<Form action={formAction}>
<FieldGroup>
<Field data-invalid={!!formState.errors?.title?.length}>
<FieldLabel htmlFor="title">Bug Title</FieldLabel>
<Input
id="title"
name="title"
defaultValue={formState.values.title}
disabled={pending}
aria-invalid={!!formState.errors?.title?.length}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
<FieldDescription>
Provide a concise title for your bug report.
</FieldDescription>
{formState.errors?.title && (
<FieldError>{formState.errors.title[0]}</FieldError>
)}
</Field>
</FieldGroup>
<Button type="submit">Submit</Button>
</Form>
```
## Usage
### Create a form schema
We'll start by defining the shape of our form using a Zod schema in a `schema.ts` file.
<Callout icon={<InfoIcon />}>
**Note:** This example uses `zod v3` for schema validation, but you can
replace it with any other schema validation library. Make sure your schema
library conforms to the Standard Schema specification.
</Callout>
```tsx showLineNumbers title="schema.ts"
import { z } from "zod"
export const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})
```
### Define the form state type
Next, we'll create a type for our form state that includes values, errors, and success status. This will be used to type the form state on the client and server.
```tsx showLineNumbers title="schema.ts"
import { z } from "zod"
export type FormState = {
values?: z.infer<typeof formSchema>
errors: null | Partial<Record<keyof z.infer<typeof formSchema>, string[]>>
success: boolean
}
```
**Important:** We define the schema and the `FormState` type in a separate file so we can import them into both the client and server components.
### Create the Server Action
A server action is a function that runs on the server and can be called from the client. We'll use it to validate the form data and update the form state.
<ComponentSource
src="/registry/new-york-v4/examples/form-next-demo-action.ts"
title="actions.ts"
/>
**Note:** We're returning `values` for error cases. This is because we want to keep the user submitted values in the form state. For success cases, we're returning empty values to reset the form.
### Build the form
We can now build the form using the `<Field />` component. We'll use the `useActionState` hook to manage the form state, server action, and pending state.
<ComponentSource
src="/registry/new-york-v4/examples/form-next-demo.tsx"
title="form.tsx"
/>
### Done
That's it. You now have a fully accessible form with client and server-side validation.
When you submit the form, the `formAction` function will be called on the server. The server action will validate the form data and update the form state.
If the form data is invalid, the server action will return the errors to the client. If the form data is valid, the server action will return the success status and update the form state.
## Pending States
Use the `pending` prop from `useActionState` to show loading indicators and disable form inputs.
```tsx showLineNumbers {11,26-34}
"use client"
import * as React from "react"
import Form from "next/form"
import { Spinner } from "@/components/ui/spinner"
import { bugReportFormAction } from "./actions"
export function BugReportForm() {
const [formState, formAction, pending] = React.useActionState(
bugReportFormAction,
{
errors: null,
success: false,
}
)
return (
<Form action={formAction}>
<FieldGroup>
<Field data-disabled={pending}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" name="name" disabled={pending} />
</Field>
<Field>
<Button type="submit" disabled={pending}>
{pending && <Spinner />} Submit
</Button>
</Field>
</FieldGroup>
</Form>
)
}
```
## Disabled States
### Submit Button
To disable the submit button, use the `pending` prop on the button's `disabled` prop.
```tsx showLineNumbers
<Button type="submit" disabled={pending}>
{pending && <Spinner />} Submit
</Button>
```
### Field
To apply a disabled state and styling to a `<Field />` component, use the `data-disabled` prop on the `<Field />` component.
```tsx showLineNumbers
<Field data-disabled={pending}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" name="name" disabled={pending} />
</Field>
```
## Validation
### Server-side Validation
Use `safeParse()` on your schema in your server action to validate the form data.
```tsx showLineNumbers title="actions.ts" {12-20}
"use server"
export async function bugReportFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
const result = formSchema.safeParse(values)
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
return {
errors: null,
success: true,
}
}
```
### Business Logic Validation
You can add additional custom validation logic in your server action.
Make sure to return the values on validation errors. This is to ensure that the form state maintains the user's input.
```tsx showLineNumbers title="actions.ts" {22-35}
"use server"
export async function bugReportFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
const result = formSchema.safeParse(values)
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
// Check if email already exists in database.
const existingUser = await db.user.findUnique({
where: { email: result.data.email },
})
if (existingUser) {
return {
values,
success: false,
errors: {
email: ["This email is already registered"],
},
}
}
return {
errors: null,
success: true,
}
}
```
## Displaying Errors
Display errors next to the field using `<FieldError />`. Make sure to add the `data-invalid` prop to the `<Field />` component and `aria-invalid` prop to the input.
```tsx showLineNumbers
<Field data-invalid={!!formState.errors?.email?.length}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
name="email"
type="email"
aria-invalid={!!formState.errors?.email?.length}
/>
{formState.errors?.email && (
<FieldError>{formState.errors.email[0]}</FieldError>
)}
</Field>
```
## Resetting the Form
When you submit a form with a server action, React will automatically reset the form state to the initial values.
### Reset on Success
To reset the form on success, you can omit the `values` from the server action and React will automatically reset the form state to the initial values. This is standard React behavior.
```tsx showLineNumbers title="actions.ts" {22-26}
export async function demoFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
// Validation.
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
// Business logic.
callYourDatabaseOrAPI(values)
// Omit the values on success to reset the form state.
return {
errors: null,
success: true,
}
}
```
### Preserve on Validation Errors
To prevent the form from being reset on failure, you can return the values in the server action. This is to ensure that the form state maintains the user's input.
```tsx showLineNumbers title="actions.ts" {12-17}
export async function demoFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
// Validation.
if (!result.success) {
return {
// Return the values on validation errors.
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
}
```
## Complex Forms
Here is an example of a more complex form with multiple fields and validation.
<ComponentPreview
name="form-next-complex"
className="[&_.preview]:h-[1100px] [&_pre]:!h-[1100px]"
hideCode
/>
### Schema
<ComponentSource
src="/registry/new-york-v4/examples/form-next-complex-schema.ts"
title="schema.ts"
/>
### Form
<ComponentSource
src="/registry/new-york-v4/examples/form-next-complex.tsx"
title="form.tsx"
/>
### Server Action
<ComponentSource
src="/registry/new-york-v4/examples/form-next-complex-action.ts"
title="actions.ts"
/>

View File

@@ -0,0 +1,629 @@
---
title: React Hook Form
description: Build forms in React using React Hook Form and Zod.
links:
doc: https://react-hook-form.com
---
import { InfoIcon } from "lucide-react"
In this guide, we will take a look at building forms with React Hook Form. We'll cover building forms with the `<Field />` component, adding schema validation using Zod, error handling, accessibility, and more.
## Demo
We are going to build the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.
<Callout icon={<InfoIcon />}>
**Note:** For the purpose of this demo, we have intentionally disabled browser
validation to show how schema validation and form errors work in React Hook
Form. It is recommended to add basic browser validation in your production
code.
</Callout>
<ComponentPreview
name="form-rhf-demo"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
## Approach
This form leverages React Hook Form for performant, flexible form handling. We'll build our form using the `<Field />` component, which gives you **complete flexibility over the markup and styling**.
- Uses React Hook Form's `useForm` hook for form state management.
- `<Controller />` component for controlled inputs.
- `<Field />` components for building accessible forms.
- Client-side validation using Zod with `zodResolver`.
## Anatomy
Here's a basic example of a form using the `<Controller />` component from React Hook Form and the `<Field />` component.
```tsx showLineNumbers {5-18}
<Controller
name="title"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Bug Title</FieldLabel>
<Input
{...field}
id={field.name}
aria-invalid={fieldState.invalid}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
<FieldDescription>
Provide a concise title for your bug report.
</FieldDescription>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
```
## Form
### Create a form schema
We'll start by defining the shape of our form using a Zod schema
<Callout icon={<InfoIcon />}>
**Note:** This example uses `zod v3` for schema validation, but you can
replace it with any other Standard Schema validation library supported by
React Hook Form.
</Callout>
```tsx showLineNumbers title="form.tsx"
import * as z from "zod"
const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})
```
### Setup the form
Next, we'll use the `useForm` hook from React Hook Form to create our form instance. We'll also add the Zod resolver to validate the form data.
```tsx showLineNumbers title="form.tsx" {17-23}
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})
export function BugReportForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
description: "",
},
})
function onSubmit(data: z.infer<typeof formSchema>) {
// Do something with the form values.
console.log(data)
}
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* ... */}
{/* Build the form here */}
{/* ... */}
</form>
)
}
```
### Build the form
We can now build the form using the `<Controller />` component from React Hook Form and the `<Field />` component.
<ComponentSource
src="/registry/new-york-v4/examples/form-rhf-demo.tsx"
title="form.tsx"
/>
### Done
That's it. You now have a fully accessible form with client-side validation.
When you submit the form, the `onSubmit` function will be called with the validated form data. If the form data is invalid, React Hook Form will display the errors next to each field.
## Validation
### Client-side Validation
React Hook Form validates your form data using the Zod schema. Define a schema and pass it to the `resolver` option of the `useForm` hook.
```tsx showLineNumbers title="example-form.tsx" {5-8,12}
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
const formSchema = z.object({
title: z.string(),
description: z.string().optional(),
})
export function ExampleForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
description: "",
},
})
}
```
### Validation Modes
React Hook Form supports different validation modes.
```tsx showLineNumbers title="form.tsx" {3}
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
mode: "onChange",
})
```
| Mode | Description |
| ------------- | -------------------------------------------------------- |
| `"onChange"` | Validation triggers on every change. |
| `"onBlur"` | Validation triggers on blur. |
| `"onSubmit"` | Validation triggers on submit (default). |
| `"onTouched"` | Validation triggers on first blur, then on every change. |
| `"all"` | Validation triggers on blur and change. |
## Displaying Errors
Display errors next to the field using `<FieldError />`. For styling and accessibility:
- Add the `data-invalid` prop to the `<Field />` component.
- Add the `aria-invalid` prop to the form control such as `<Input />`, `<SelectTrigger />`, `<Checkbox />`, etc.
```tsx showLineNumbers title="form.tsx" {5,11,13}
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
{...field}
id={field.name}
type="email"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
```
## Working with Different Field Types
### Input
- For input fields, spread the `field` object onto the `<Input />` component.
- To show errors, add the `aria-invalid` prop to the `<Input />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-rhf-input"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
For simple text inputs, spread the `field` object onto the input.
```tsx showLineNumbers title="form.tsx" {5,7,8}
<Controller
name="name"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Name</FieldLabel>
<Input {...field} id={field.name} aria-invalid={fieldState.invalid} />
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
```
### Textarea
- For textarea fields, spread the `field` object onto the `<Textarea />` component.
- To show errors, add the `aria-invalid` prop to the `<Textarea />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-rhf-textarea"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
For textarea fields, spread the `field` object onto the textarea.
```tsx showLineNumbers title="form.tsx" {5,10,18}
<Controller
name="about"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="form-rhf-textarea-about">More about you</FieldLabel>
<Textarea
{...field}
id="form-rhf-textarea-about"
aria-invalid={fieldState.invalid}
placeholder="I'm a software engineer..."
className="min-h-[120px]"
/>
<FieldDescription>
Tell us more about yourself. This will be used to help us personalize
your experience.
</FieldDescription>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
```
### Select
- For select components, use `field.value` and `field.onChange` on the `<Select />` component.
- To show errors, add the `aria-invalid` prop to the `<SelectTrigger />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-rhf-select"
className="sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {5,13,22}
<Controller
name="language"
control={form.control}
render={({ field, fieldState }) => (
<Field orientation="responsive" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="form-rhf-select-language">
Spoken Language
</FieldLabel>
<FieldDescription>
For best results, select the language you speak.
</FieldDescription>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</FieldContent>
<Select
name={field.name}
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger
id="form-rhf-select-language"
aria-invalid={fieldState.invalid}
className="min-w-[120px]"
>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="item-aligned">
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</Field>
)}
/>
```
### Checkbox
- For checkbox arrays, use `field.value` and `field.onChange` with array manipulation.
- To show errors, add the `aria-invalid` prop to the `<Checkbox />` component and the `data-invalid` prop to the `<Field />` component.
- Remember to add `data-slot="checkbox-group"` to the `<FieldGroup />` component for proper styling and spacing.
<ComponentPreview
name="form-rhf-checkbox"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {10,15,20-22,38}
<Controller
name="tasks"
control={form.control}
render={({ field, fieldState }) => (
<FieldSet>
<FieldLegend variant="label">Tasks</FieldLegend>
<FieldDescription>
Get notified when tasks you&apos;ve created have updates.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{tasks.map((task) => (
<Field
key={task.id}
orientation="horizontal"
data-invalid={fieldState.invalid}
>
<Checkbox
id={`form-rhf-checkbox-${task.id}`}
name={field.name}
aria-invalid={fieldState.invalid}
checked={field.value.includes(task.id)}
onCheckedChange={(checked) => {
const newValue = checked
? [...field.value, task.id]
: field.value.filter((value) => value !== task.id)
field.onChange(newValue)
}}
/>
<FieldLabel
htmlFor={`form-rhf-checkbox-${task.id}`}
className="font-normal"
>
{task.label}
</FieldLabel>
</Field>
))}
</FieldGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</FieldSet>
)}
/>
```
### Radio Group
- For radio groups, use `field.value` and `field.onChange` on the `<RadioGroup />` component.
- To show errors, add the `aria-invalid` prop to the `<RadioGroupItem />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-rhf-radiogroup"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {12-13,17,25,31}
<Controller
name="plan"
control={form.control}
render={({ field, fieldState }) => (
<FieldSet>
<FieldLegend>Plan</FieldLegend>
<FieldDescription>
You can upgrade or downgrade your plan at any time.
</FieldDescription>
<RadioGroup
name={field.name}
value={field.value}
onValueChange={field.onChange}
>
{plans.map((plan) => (
<FieldLabel key={plan.id} htmlFor={`form-rhf-radiogroup-${plan.id}`}>
<Field orientation="horizontal" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldTitle>{plan.title}</FieldTitle>
<FieldDescription>{plan.description}</FieldDescription>
</FieldContent>
<RadioGroupItem
value={plan.id}
id={`form-rhf-radiogroup-${plan.id}`}
aria-invalid={fieldState.invalid}
/>
</Field>
</FieldLabel>
))}
</RadioGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</FieldSet>
)}
/>
```
### Switch
- For switches, use `field.value` and `field.onChange` on the `<Switch />` component.
- To show errors, add the `aria-invalid` prop to the `<Switch />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-rhf-switch"
className="sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {5,13,18-19}
<Controller
name="twoFactor"
control={form.control}
render={({ field, fieldState }) => (
<Field orientation="horizontal" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="form-rhf-switch-twoFactor">
Multi-factor authentication
</FieldLabel>
<FieldDescription>
Enable multi-factor authentication to secure your account.
</FieldDescription>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</FieldContent>
<Switch
id="form-rhf-switch-twoFactor"
name={field.name}
checked={field.value}
onCheckedChange={field.onChange}
aria-invalid={fieldState.invalid}
/>
</Field>
)}
/>
```
### Complex Forms
Here is an example of a more complex form with multiple fields and validation.
<ComponentPreview
name="form-rhf-complex"
className="sm:[&_.preview]:h-[1300px] sm:[&_pre]:!h-[1300px]"
chromeLessOnMobile
/>
## Resetting the Form
Use `form.reset()` to reset the form to its default values.
```tsx showLineNumbers
<Button type="button" variant="outline" onClick={() => form.reset()}>
Reset
</Button>
```
## Array Fields
React Hook Form provides a `useFieldArray` hook for managing dynamic array fields. This is useful when you need to add or remove fields dynamically.
<ComponentPreview
name="form-rhf-array"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
### Using useFieldArray
Use the `useFieldArray` hook to manage array fields. It provides `fields`, `append`, and `remove` methods.
```tsx showLineNumbers title="form.tsx" {8-11}
import { useFieldArray, useForm } from "react-hook-form"
export function ExampleForm() {
const form = useForm({
// ... form config
})
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "emails",
})
}
```
### Array Field Structure
Wrap your array fields in a `<FieldSet />` with a `<FieldLegend />` and `<FieldDescription />`.
```tsx showLineNumbers title="form.tsx"
<FieldSet className="gap-4">
<FieldLegend variant="label">Email Addresses</FieldLegend>
<FieldDescription>
Add up to 5 email addresses where we can contact you.
</FieldDescription>
<FieldGroup className="gap-4">{/* Array items go here */}</FieldGroup>
</FieldSet>
```
### Controller Pattern for Array Items
Map over the `fields` array and use `<Controller />` for each item. **Make sure to use `field.id` as the key**.
```tsx showLineNumbers title="form.tsx"
{
fields.map((field, index) => (
<Controller
key={field.id}
name={`emails.${index}.address`}
control={form.control}
render={({ field: controllerField, fieldState }) => (
<Field orientation="horizontal" data-invalid={fieldState.invalid}>
<FieldContent>
<InputGroup>
<InputGroupInput
{...controllerField}
id={`form-rhf-array-email-${index}`}
aria-invalid={fieldState.invalid}
placeholder="name@example.com"
type="email"
autoComplete="email"
/>
{/* Remove button */}
</InputGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</FieldContent>
</Field>
)}
/>
))
}
```
### Adding Items
Use the `append` method to add new items to the array.
```tsx showLineNumbers title="form.tsx"
<Button
type="button"
variant="outline"
size="sm"
onClick={() => append({ address: "" })}
disabled={fields.length >= 5}
>
Add Email Address
</Button>
```
### Removing Items
Use the `remove` method to remove items from the array. Add the remove button conditionally.
```tsx showLineNumbers title="form.tsx"
{
fields.length > 1 && (
<InputGroupAddon align="inline-end">
<InputGroupButton
type="button"
variant="ghost"
size="icon-xs"
onClick={() => remove(index)}
aria-label={`Remove email ${index + 1}`}
>
<XIcon />
</InputGroupButton>
</InputGroupAddon>
)
}
```
### Array Validation
Use Zod's `array` method to validate array fields.
```tsx showLineNumbers title="form.tsx"
const formSchema = z.object({
emails: z
.array(
z.object({
address: z.string().email("Enter a valid email address."),
})
)
.min(1, "Add at least one email address.")
.max(5, "You can add up to 5 email addresses."),
})
```

View File

@@ -0,0 +1,698 @@
---
title: TanStack Form
description: Build forms in React using TanStack Form and Zod.
links:
doc: https://tanstack.com/form
---
import { InfoIcon } from "lucide-react"
This guide explores how to build forms using TanStack Form. You'll learn to create forms with the `<Field />` component, implement schema validation with Zod, handle errors, and ensure accessibility.
## Demo
We'll start by building the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors.
<Callout icon={<InfoIcon />}>
**Note:** For the purpose of this demo, we have intentionally disabled browser
validation to show how schema validation and form errors work in TanStack
Form. It is recommended to add basic browser validation in your production
code.
</Callout>
<ComponentPreview
name="form-tanstack-demo"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
## Approach
This form leverages TanStack Form for powerful, headless form handling. We'll build our form using the `<Field />` component, which gives you **complete flexibility over the markup and styling**.
- Uses TanStack Form's `useForm` hook for form state management.
- `form.Field` component with render prop pattern for controlled inputs.
- `<Field />` components for building accessible forms.
- Client-side validation using Zod.
- Real-time validation feedback.
## Anatomy
Here's a basic example of a form using TanStack Form with the `<Field />` component.
```tsx showLineNumbers {15-31}
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<FieldGroup>
<form.Field
name="title"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Bug Title</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
<FieldDescription>
Provide a concise title for your bug report.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>
</FieldGroup>
<Button type="submit">Submit</Button>
</form>
```
## Form
### Create a schema
We'll start by defining the shape of our form using a Zod schema.
<Callout icon={<InfoIcon />}>
**Note:** This example uses `zod v3` for schema validation. TanStack Form
integrates seamlessly with Zod and other Standard Schema validation libraries
through its validators API.
</Callout>
```tsx showLineNumbers title="form.tsx"
import * as z from "zod"
const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})
```
### Setup the form
Use the `useForm` hook from TanStack Form to create your form instance with Zod validation.
```tsx showLineNumbers title="form.tsx" {10-21}
import { useForm } from "@tanstack/react-form"
import { toast } from "sonner"
import * as z from "zod"
const formSchema = z.object({
// ...
})
export function BugReportForm() {
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
toast.success("Form submitted successfully")
},
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
{/* ... */}
</form>
)
}
```
We are using `onSubmit` to validate the form data here. TanStack Form supports other validation modes, which you can read about in the [documentation](https://tanstack.com/form/latest/docs/framework/react/guides/dynamic-validation).
### Build the form
We can now build the form using the `form.Field` component from TanStack Form and the `<Field />` component.
<ComponentSource
src="/registry/new-york-v4/examples/form-tanstack-demo.tsx"
title="form.tsx"
/>
### Done
That's it. You now have a fully accessible form with client-side validation.
When you submit the form, the `onSubmit` function will be called with the validated form data. If the form data is invalid, TanStack Form will display the errors next to each field.
## Validation
### Client-side Validation
TanStack Form validates your form data using the Zod schema. Validation happens in real-time as the user types.
```tsx showLineNumbers title="form.tsx" {13-15}
import { useForm } from "@tanstack/react-form"
const formSchema = z.object({
// ...
})
export function BugReportForm() {
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
console.log(value)
},
})
return <form onSubmit={/* ... */}>{/* ... */}</form>
}
```
### Validation Modes
TanStack Form supports different validation strategies through the `validators` option:
| Mode | Description |
| ------------ | ------------------------------------ |
| `"onChange"` | Validation triggers on every change. |
| `"onBlur"` | Validation triggers on blur. |
| `"onSubmit"` | Validation triggers on submit. |
```tsx showLineNumbers title="form.tsx" {6-9}
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
onChange: formSchema,
onBlur: formSchema,
},
})
```
## Displaying Errors
Display errors next to the field using `<FieldError />`. For styling and accessibility:
- Add the `data-invalid` prop to the `<Field />` component.
- Add the `aria-invalid` prop to the form control such as `<Input />`, `<SelectTrigger />`, `<Checkbox />`, etc.
```tsx showLineNumbers title="form.tsx" {4,18}
<form.Field
name="email"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
type="email"
aria-invalid={isInvalid}
/>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>
```
## Working with Different Field Types
### Input
- For input fields, use `field.state.value` and `field.handleChange` on the `<Input />` component.
- To show errors, add the `aria-invalid` prop to the `<Input />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-tanstack-input"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {6,11-14,22}
<form.Field
name="username"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor="form-tanstack-input-username">Username</FieldLabel>
<Input
id="form-tanstack-input-username"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="shadcn"
autoComplete="username"
/>
<FieldDescription>
This is your public display name. Must be between 3 and 10 characters.
Must only contain letters, numbers, and underscores.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>
```
### Textarea
- For textarea fields, use `field.state.value` and `field.handleChange` on the `<Textarea />` component.
- To show errors, add the `aria-invalid` prop to the `<Textarea />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-tanstack-textarea"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {6,13-16,24}
<form.Field
name="about"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor="form-tanstack-textarea-about">
More about you
</FieldLabel>
<Textarea
id="form-tanstack-textarea-about"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="I'm a software engineer..."
className="min-h-[120px]"
/>
<FieldDescription>
Tell us more about yourself. This will be used to help us personalize
your experience.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>
```
### Select
- For select components, use `field.state.value` and `field.handleChange` on the `<Select />` component.
- To show errors, add the `aria-invalid` prop to the `<SelectTrigger />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-tanstack-select"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {6,18-19,23}
<form.Field
name="language"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field orientation="responsive" data-invalid={isInvalid}>
<FieldContent>
<FieldLabel htmlFor="form-tanstack-select-language">
Spoken Language
</FieldLabel>
<FieldDescription>
For best results, select the language you speak.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldContent>
<Select
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger
id="form-tanstack-select-language"
aria-invalid={isInvalid}
className="min-w-[120px]"
>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="item-aligned">
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</Field>
)
}}
/>
```
### Checkbox
- For checkbox, use `field.state.value` and `field.handleChange` on the `<Checkbox />` component.
- To show errors, add the `aria-invalid` prop to the `<Checkbox />` component and the `data-invalid` prop to the `<Field />` component.
- For checkbox arrays, use `mode="array"` on the `<form.Field />` component and TanStack Form's array helpers.
- Remember to add `data-slot="checkbox-group"` to the `<FieldGroup />` component for proper styling and spacing.
<ComponentPreview
name="form-tanstack-checkbox"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {12,17,22-24,44}
<form.Field
name="tasks"
mode="array"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet>
<FieldLegend variant="label">Tasks</FieldLegend>
<FieldDescription>
Get notified when tasks you&apos;ve created have updates.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{tasks.map((task) => (
<Field
key={task.id}
orientation="horizontal"
data-invalid={isInvalid}
>
<Checkbox
id={`form-tanstack-checkbox-${task.id}`}
name={field.name}
aria-invalid={isInvalid}
checked={field.state.value.includes(task.id)}
onCheckedChange={(checked) => {
if (checked) {
field.pushValue(task.id)
} else {
const index = field.state.value.indexOf(task.id)
if (index > -1) {
field.removeValue(index)
}
}
}}
/>
<FieldLabel
htmlFor={`form-tanstack-checkbox-${task.id}`}
className="font-normal"
>
{task.label}
</FieldLabel>
</Field>
))}
</FieldGroup>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldSet>
)
}}
/>
```
### Radio Group
- For radio groups, use `field.state.value` and `field.handleChange` on the `<RadioGroup />` component.
- To show errors, add the `aria-invalid` prop to the `<RadioGroupItem />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-tanstack-radiogroup"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {21,29,35}
<form.Field
name="plan"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet>
<FieldLegend>Plan</FieldLegend>
<FieldDescription>
You can upgrade or downgrade your plan at any time.
</FieldDescription>
<RadioGroup
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
>
{plans.map((plan) => (
<FieldLabel
key={plan.id}
htmlFor={`form-tanstack-radiogroup-${plan.id}`}
>
<Field orientation="horizontal" data-invalid={isInvalid}>
<FieldContent>
<FieldTitle>{plan.title}</FieldTitle>
<FieldDescription>{plan.description}</FieldDescription>
</FieldContent>
<RadioGroupItem
value={plan.id}
id={`form-tanstack-radiogroup-${plan.id}`}
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
))}
</RadioGroup>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldSet>
)
}}
/>
```
### Switch
- For switches, use `field.state.value` and `field.handleChange` on the `<Switch />` component.
- To show errors, add the `aria-invalid` prop to the `<Switch />` component and the `data-invalid` prop to the `<Field />` component.
<ComponentPreview
name="form-tanstack-switch"
className="sm:[&_.preview]:h-[500px] sm:[&_pre]:!h-[500px]"
chromeLessOnMobile
/>
```tsx showLineNumbers title="form.tsx" {6,14,19-21}
<form.Field
name="twoFactor"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field orientation="horizontal" data-invalid={isInvalid}>
<FieldContent>
<FieldLabel htmlFor="form-tanstack-switch-twoFactor">
Multi-factor authentication
</FieldLabel>
<FieldDescription>
Enable multi-factor authentication to secure your account.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldContent>
<Switch
id="form-tanstack-switch-twoFactor"
name={field.name}
checked={field.state.value}
onCheckedChange={field.handleChange}
aria-invalid={isInvalid}
/>
</Field>
)
}}
/>
```
### Complex Forms
Here is an example of a more complex form with multiple fields and validation.
<ComponentPreview
name="form-tanstack-complex"
className="sm:[&_.preview]:h-[1100px] sm:[&_pre]:!h-[1100px]"
chromeLessOnMobile
/>
## Resetting the Form
Use `form.reset()` to reset the form to its default values.
```tsx showLineNumbers
<Button type="button" variant="outline" onClick={() => form.reset()}>
Reset
</Button>
```
## Array Fields
TanStack Form provides powerful array field management with `mode="array"`. This allows you to dynamically add, remove, and update array items with full validation support.
<ComponentPreview
name="form-tanstack-array"
className="sm:[&_.preview]:h-[700px] sm:[&_pre]:!h-[700px]"
chromeLessOnMobile
/>
This example demonstrates managing multiple email addresses with array fields. Users can add up to 5 email addresses, remove individual addresses, and each address is validated independently.
### Array Field Structure
Use `mode="array"` on the parent field to enable array field management.
```tsx showLineNumbers title="form.tsx" {3,12-14}
<form.Field
name="emails"
mode="array"
children={(field) => {
return (
<FieldSet>
<FieldLegend variant="label">Email Addresses</FieldLegend>
<FieldDescription>
Add up to 5 email addresses where we can contact you.
</FieldDescription>
<FieldGroup>
{field.state.value.map((_, index) => (
// Nested field for each array item
))}
</FieldGroup>
</FieldSet>
)
}}
/>
```
### Nested Fields
Access individual array items using bracket notation: `fieldName[index].propertyName`. This example uses `InputGroup` to display the remove button inline with the input.
```tsx showLineNumbers title="form.tsx"
<form.Field
name={`emails[${index}].address`}
children={(subField) => {
const isSubFieldInvalid =
subField.state.meta.isTouched && !subField.state.meta.isValid
return (
<Field orientation="horizontal" data-invalid={isSubFieldInvalid}>
<FieldContent>
<InputGroup>
<InputGroupInput
id={`form-tanstack-array-email-${index}`}
name={subField.name}
value={subField.state.value}
onBlur={subField.handleBlur}
onChange={(e) => subField.handleChange(e.target.value)}
aria-invalid={isSubFieldInvalid}
placeholder="name@example.com"
type="email"
/>
{field.state.value.length > 1 && (
<InputGroupAddon align="inline-end">
<InputGroupButton
type="button"
variant="ghost"
size="icon-xs"
onClick={() => field.removeValue(index)}
aria-label={`Remove email ${index + 1}`}
>
<XIcon />
</InputGroupButton>
</InputGroupAddon>
)}
</InputGroup>
{isSubFieldInvalid && (
<FieldError errors={subField.state.meta.errors} />
)}
</FieldContent>
</Field>
)
}}
/>
```
### Adding Items
Use `field.pushValue(item)` to add items to an array field. You can disable the button when the array reaches its maximum length.
```tsx showLineNumbers title="form.tsx"
<Button
type="button"
variant="outline"
size="sm"
onClick={() => field.pushValue({ address: "" })}
disabled={field.state.value.length >= 5}
>
Add Email Address
</Button>
```
### Removing Items
Use `field.removeValue(index)` to remove items from an array field. You can conditionally show the remove button only when there's more than one item.
```tsx showLineNumbers title="form.tsx"
{
field.state.value.length > 1 && (
<InputGroupButton
onClick={() => field.removeValue(index)}
aria-label={`Remove email ${index + 1}`}
>
<XIcon />
</InputGroupButton>
)
}
```
### Array Validation
Validate array fields using Zod's array methods.
```tsx showLineNumbers title="form.tsx"
const formSchema = z.object({
emails: z
.array(
z.object({
address: z.string().email("Enter a valid email address."),
})
)
.min(1, "Add at least one email address.")
.max(5, "You can add up to 5 email addresses."),
})
```

View File

@@ -4,6 +4,7 @@
"(root)",
"changelog",
"components",
"forms",
"installation",
"dark-mode",
"registry"

View File

@@ -42,16 +42,6 @@ const nextConfig = {
destination: "/docs/figma",
permanent: true,
},
{
source: "/docs/forms",
destination: "/docs/components/form",
permanent: false,
},
{
source: "/docs/forms/react-hook-form",
destination: "/docs/components/form",
permanent: false,
},
{
source: "/sidebar",
destination: "/docs/components/sidebar",

View File

@@ -89,7 +89,7 @@
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "3.4.0",
"shadcn": "3.4.1",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",

145
apps/v4/public/llms.txt Normal file
View File

@@ -0,0 +1,145 @@
# shadcn/ui
> shadcn/ui is a collection of beautifully-designed, accessible components and a code distribution platform. It is built with TypeScript, Tailwind CSS, and Radix UI primitives. It supports multiple frameworks including Next.js, Vite, Remix, Astro, and more. Open Source. Open Code. AI-Ready. It also comes with a command-line tool to install and manage components and a registry system to publish and distribute code.
## Overview
- [Introduction](https://ui.shadcn.com/docs): Core principles—Open Code, Composition, Distribution, Beautiful Defaults, and AI-Ready design.
- [CLI](https://ui.shadcn.com/docs/cli): Command-line tool for installing and managing components.
- [components.json](https://ui.shadcn.com/docs/components-json): Configuration file for customizing the CLI and component installation.
- [Theming](https://ui.shadcn.com/docs/theming): Guide to customizing colors, typography, and design tokens.
- [Changelog](https://ui.shadcn.com/docs/changelog): Release notes and version history.
- [About](https://ui.shadcn.com/docs/about): Credits and project information.
## Installation
- [Next.js](https://ui.shadcn.com/docs/installation/next): Install shadcn/ui in a Next.js project.
- [Vite](https://ui.shadcn.com/docs/installation/vite): Install shadcn/ui in a Vite project.
- [Remix](https://ui.shadcn.com/docs/installation/remix): Install shadcn/ui in a Remix project.
- [Astro](https://ui.shadcn.com/docs/installation/astro): Install shadcn/ui in an Astro project.
- [Laravel](https://ui.shadcn.com/docs/installation/laravel): Install shadcn/ui in a Laravel project.
- [Gatsby](https://ui.shadcn.com/docs/installation/gatsby): Install shadcn/ui in a Gatsby project.
- [React Router](https://ui.shadcn.com/docs/installation/react-router): Install shadcn/ui in a React Router project.
- [TanStack Router](https://ui.shadcn.com/docs/installation/tanstack-router): Install shadcn/ui in a TanStack Router project.
- [TanStack Start](https://ui.shadcn.com/docs/installation/tanstack): Install shadcn/ui in a TanStack Start project.
- [Manual Installation](https://ui.shadcn.com/docs/installation/manual): Manually install shadcn/ui without the CLI.
## Components
### Form & Input
- [Form](https://ui.shadcn.com/docs/components/form): Building forms with React Hook Form and Zod validation.
- [Field](https://ui.shadcn.com/docs/components/field): Field component for form inputs with labels and error messages.
- [Button](https://ui.shadcn.com/docs/components/button): Button component with multiple variants.
- [Button Group](https://ui.shadcn.com/docs/components/button-group): Group multiple buttons together.
- [Input](https://ui.shadcn.com/docs/components/input): Text input component.
- [Input Group](https://ui.shadcn.com/docs/components/input-group): Input component with prefix and suffix addons.
- [Input OTP](https://ui.shadcn.com/docs/components/input-otp): One-time password input component.
- [Textarea](https://ui.shadcn.com/docs/components/textarea): Multi-line text input component.
- [Checkbox](https://ui.shadcn.com/docs/components/checkbox): Checkbox input component.
- [Radio Group](https://ui.shadcn.com/docs/components/radio-group): Radio button group component.
- [Select](https://ui.shadcn.com/docs/components/select): Select dropdown component.
- [Switch](https://ui.shadcn.com/docs/components/switch): Toggle switch component.
- [Slider](https://ui.shadcn.com/docs/components/slider): Slider input component.
- [Calendar](https://ui.shadcn.com/docs/components/calendar): Calendar component for date selection.
- [Date Picker](https://ui.shadcn.com/docs/components/date-picker): Date picker component combining input and calendar.
- [Combobox](https://ui.shadcn.com/docs/components/combobox): Searchable select component with autocomplete.
- [Label](https://ui.shadcn.com/docs/components/label): Form label component.
### Layout & Navigation
- [Accordion](https://ui.shadcn.com/docs/components/accordion): Collapsible accordion component.
- [Breadcrumb](https://ui.shadcn.com/docs/components/breadcrumb): Breadcrumb navigation component.
- [Navigation Menu](https://ui.shadcn.com/docs/components/navigation-menu): Accessible navigation menu with dropdowns.
- [Sidebar](https://ui.shadcn.com/docs/components/sidebar): Collapsible sidebar component for app layouts.
- [Tabs](https://ui.shadcn.com/docs/components/tabs): Tabbed interface component.
- [Separator](https://ui.shadcn.com/docs/components/separator): Visual divider between content sections.
- [Scroll Area](https://ui.shadcn.com/docs/components/scroll-area): Custom scrollable area with styled scrollbars.
- [Resizable](https://ui.shadcn.com/docs/components/resizable): Resizable panel layout component.
### Overlays & Dialogs
- [Dialog](https://ui.shadcn.com/docs/components/dialog): Modal dialog component.
- [Alert Dialog](https://ui.shadcn.com/docs/components/alert-dialog): Alert dialog for confirmation prompts.
- [Sheet](https://ui.shadcn.com/docs/components/sheet): Slide-out panel component (drawer).
- [Drawer](https://ui.shadcn.com/docs/components/drawer): Mobile-friendly drawer component using Vaul.
- [Popover](https://ui.shadcn.com/docs/components/popover): Floating popover component.
- [Tooltip](https://ui.shadcn.com/docs/components/tooltip): Tooltip component for additional context.
- [Hover Card](https://ui.shadcn.com/docs/components/hover-card): Card that appears on hover.
- [Context Menu](https://ui.shadcn.com/docs/components/context-menu): Right-click context menu.
- [Dropdown Menu](https://ui.shadcn.com/docs/components/dropdown-menu): Dropdown menu component.
- [Menubar](https://ui.shadcn.com/docs/components/menubar): Horizontal menubar component.
- [Command](https://ui.shadcn.com/docs/components/command): Command palette component (cmdk).
### Feedback & Status
- [Alert](https://ui.shadcn.com/docs/components/alert): Alert component for messages and notifications.
- [Toast](https://ui.shadcn.com/docs/components/toast): Toast notification component using Sonner.
- [Progress](https://ui.shadcn.com/docs/components/progress): Progress bar component.
- [Spinner](https://ui.shadcn.com/docs/components/spinner): Loading spinner component.
- [Skeleton](https://ui.shadcn.com/docs/components/skeleton): Skeleton loading placeholder.
- [Badge](https://ui.shadcn.com/docs/components/badge): Badge component for labels and status indicators.
- [Empty](https://ui.shadcn.com/docs/components/empty): Empty state component for no data scenarios.
### Display & Media
- [Avatar](https://ui.shadcn.com/docs/components/avatar): Avatar component for user profiles.
- [Card](https://ui.shadcn.com/docs/components/card): Card container component.
- [Table](https://ui.shadcn.com/docs/components/table): Table component for displaying data.
- [Data Table](https://ui.shadcn.com/docs/components/data-table): Advanced data table with sorting, filtering, and pagination.
- [Chart](https://ui.shadcn.com/docs/components/chart): Chart components using Recharts.
- [Carousel](https://ui.shadcn.com/docs/components/carousel): Carousel component using Embla Carousel.
- [Aspect Ratio](https://ui.shadcn.com/docs/components/aspect-ratio): Container that maintains aspect ratio.
- [Typography](https://ui.shadcn.com/docs/components/typography): Typography styles and components.
- [Item](https://ui.shadcn.com/docs/components/item): Generic item component for lists and menus.
- [Kbd](https://ui.shadcn.com/docs/components/kbd): Keyboard shortcut display component.
### Misc
- [Collapsible](https://ui.shadcn.com/docs/components/collapsible): Collapsible container component.
- [Toggle](https://ui.shadcn.com/docs/components/toggle): Toggle button component.
- [Toggle Group](https://ui.shadcn.com/docs/components/toggle-group): Group of toggle buttons.
- [Pagination](https://ui.shadcn.com/docs/components/pagination): Pagination component for lists and tables.
## Dark Mode
- [Dark Mode](https://ui.shadcn.com/docs/dark-mode): Overview of dark mode implementation.
- [Dark Mode - Next.js](https://ui.shadcn.com/docs/dark-mode/next): Dark mode setup for Next.js.
- [Dark Mode - Vite](https://ui.shadcn.com/docs/dark-mode/vite): Dark mode setup for Vite.
- [Dark Mode - Astro](https://ui.shadcn.com/docs/dark-mode/astro): Dark mode setup for Astro.
- [Dark Mode - Remix](https://ui.shadcn.com/docs/dark-mode/remix): Dark mode setup for Remix.
## Forms
- [Forms Overview](https://ui.shadcn.com/docs/forms): Guide to building forms with shadcn/ui.
- [React Hook Form](https://ui.shadcn.com/docs/forms/react-hook-form): Using shadcn/ui with React Hook Form.
- [TanStack Form](https://ui.shadcn.com/docs/forms/tanstack-form): Using shadcn/ui with TanStack Form.
- [Forms - Next.js](https://ui.shadcn.com/docs/forms/next): Building forms in Next.js with Server Actions.
## Advanced
- [Monorepo](https://ui.shadcn.com/docs/monorepo): Using shadcn/ui in a monorepo setup.
- [React 19](https://ui.shadcn.com/docs/react-19): React 19 support and migration guide.
- [Tailwind CSS v4](https://ui.shadcn.com/docs/tailwind-v4): Tailwind CSS v4 support and setup.
- [JavaScript](https://ui.shadcn.com/docs/javascript): Using shadcn/ui with JavaScript (no TypeScript).
- [Figma](https://ui.shadcn.com/docs/figma): Figma design resources.
- [v0](https://ui.shadcn.com/docs/v0): Generating UI with v0 by Vercel.
## MCP Server
- [MCP Server](https://ui.shadcn.com/docs/mcp): Model Context Protocol server for AI integrations. Allows AI assistants to browse, search, and install components from registries using natural language. Works with Claude Code, Cursor, VS Code (GitHub Copilot), Codex and more.
## Registry
- [Registry Overview](https://ui.shadcn.com/docs/registry): Creating and publishing your own component registry.
- [Getting Started](https://ui.shadcn.com/docs/registry/getting-started): Set up your own registry.
- [Examples](https://ui.shadcn.com/docs/registry/examples): Example registries.
- [FAQ](https://ui.shadcn.com/docs/registry/faq): Common questions about registries.
- [Authentication](https://ui.shadcn.com/docs/registry/authentication): Adding authentication to your registry.
- [Registry MCP](https://ui.shadcn.com/docs/registry/mcp): MCP integration for registries.
### Registry Schemas
- [Registry Schema](https://ui.shadcn.com/schema/registry.json): JSON Schema for registry index files. Defines the structure for a collection of components, hooks, pages, etc. Requires name, homepage, and items array.
- [Registry Item Schema](https://ui.shadcn.com/schema/registry-item.json): JSON Schema for individual registry items. Defines components, hooks, themes, and other distributable code with properties for dependencies, files, Tailwind config, CSS variables, and more.

View File

@@ -1,43 +1,52 @@
{
"@8bitcn": "https://8bitcn.com/r/{name}.json",
"@97cn": "https://97cn.itzik.co/r/{name}.json",
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
"@animate-ui": "https://animate-ui.com/r/{name}.json",
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
"@basecn": "https://basecn.dev/r/{name}.json",
"@better-upload": "https://better-upload.com/r/{name}.json",
"@billingsdk": "https://billingsdk.com/r/{name}.json",
"@blocks": "https://blocks.so/r/{name}.json",
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
"@clerk": "https://clerk.com/r/{name}.json",
"@cult-ui": "https://cult-ui.com/r/{name}.json",
"@eldoraui": "https://eldoraui.site/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
"@fancy": "https://fancycomponents.dev/r/{name}.json",
"@formcn": "https://formcn.dev/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
"@kokonutui": "https://kokonutui.com/r/{name}.json",
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
"@magicui": "https://magicui.design/r/{name}.json",
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
"@originui": "https://originui.com/r/{name}.json",
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
"@tailark": "https://tailark.com/r/{name}.json",
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@reui": "https://reui.io/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@paceui-ui": "https://ui.paceui.com/r/{name}.json",
"@basecn": "https://basecn.dev/r/{name}.json",
"@ncdai": "https://chanhdai.com/r/{name}.json",
"@8bitcn": "https://8bitcn.com/r/{name}.json",
"@billingsdk": "https://billingsdk.com/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@nativeui": "https://nativeui.io/registry/{name}.json",
"@ncdai": "https://chanhdai.com/r/{name}.json",
"@paceui-ui": "https://ui.paceui.com/r/{name}.json",
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@react-market": "https://www.react-market.com/get/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
"@reui": "https://reui.io/r/{name}.json",
"@rigidui": "https://rigidui.com/r/{name}.json",
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
"@smoothui": "https://smoothui.dev/r/{name}.json",
"@svgl": "https://svgl.app/r/{name}.json",
"@formcn": "https://formcn.dev/r/{name}.json",
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
"@rigidui": "https://rigidui.com/r/{name}.json",
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
"@tailark": "https://tailark.com/r/{name}.json",
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
"@97cn": "https://97cn.itzik.co/r/{name}.json",
"@better-upload": "https://better-upload.com/r/{name}.json"
"@zippystarter": "https://zippystarter.com/r/{name}.json"
}

View File

@@ -9,7 +9,7 @@
"files": [
{
"path": "ui/checkbox.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n className={cn(\"flex items-center justify-center text-current\")}\n >\n <Check className=\"h-4 w-4\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n className={cn(\"grid place-content-center text-current\")}\n >\n <Check className=\"h-4 w-4\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n",
"type": "registry:ui",
"target": ""
}

View File

@@ -17,7 +17,7 @@
"files": [
{
"path": "ui/form.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n {} as FormFieldContextValue\n)\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState, formState } = useFormContext()\n\n const fieldState = getFieldState(fieldContext.name, formState)\n\n if (!fieldContext) {\n throw new Error(\"useFormField should be used within <FormField>\")\n }\n\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\ntype FormItemContextValue = {\n id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n {} as FormItemContextValue\n)\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n </FormItemContext.Provider>\n )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={\n !error\n ? `${formDescriptionId}`\n : `${formDescriptionId} ${formMessageId}`\n }\n aria-invalid={!!error}\n {...props}\n />\n )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error?.message ?? \"\") : children\n\n if (!body) {\n return null\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-sm font-medium text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n}\n",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue | null>(null)\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState, formState } = useFormContext()\n\n if (!fieldContext) {\n throw new Error(\"useFormField should be used within <FormField>\")\n }\n\n if (!itemContext) {\n throw new Error(\"useFormField should be used within <FormItem>\")\n }\n\n const fieldState = getFieldState(fieldContext.name, formState)\n\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\ntype FormItemContextValue = {\n id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue | null>(null)\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n </FormItemContext.Provider>\n )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={\n !error\n ? `${formDescriptionId}`\n : `${formDescriptionId} ${formMessageId}`\n }\n aria-invalid={!!error}\n {...props}\n />\n )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error?.message ?? \"\") : children\n\n if (!body) {\n return null\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-sm font-medium text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n}\n",
"type": "registry:ui",
"target": ""
}

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@
"files": [
{
"path": "ui/sonner.tsx",
"content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme()\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n toastOptions={{\n classNames: {\n toast:\n \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n description: \"group-[.toast]:text-muted-foreground\",\n actionButton:\n \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n cancelButton:\n \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n },\n }}\n {...props}\n />\n )\n}\n\nexport { Toaster }\n",
"content": "\"use client\"\n\nimport {\n CircleCheck,\n Info,\n LoaderCircle,\n OctagonX,\n TriangleAlert,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme()\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n icons={{\n success: <CircleCheck className=\"h-4 w-4\" />,\n info: <Info className=\"h-4 w-4\" />,\n warning: <TriangleAlert className=\"h-4 w-4\" />,\n error: <OctagonX className=\"h-4 w-4\" />,\n loading: <LoaderCircle className=\"h-4 w-4 animate-spin\" />,\n }}\n toastOptions={{\n classNames: {\n toast:\n \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n description: \"group-[.toast]:text-muted-foreground\",\n actionButton:\n \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n cancelButton:\n \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n },\n }}\n {...props}\n />\n )\n}\n\nexport { Toaster }\n",
"type": "registry:ui",
"target": ""
}

View File

@@ -8,7 +8,7 @@
"files": [
{
"path": "registry/new-york-v4/ui/checkbox.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { CheckIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Checkbox({\n className,\n ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n className={cn(\n \"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"flex items-center justify-center text-current transition-none\"\n >\n <CheckIcon className=\"size-3.5\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport { Checkbox }\n",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { CheckIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Checkbox({\n className,\n ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n className={cn(\n \"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"grid place-content-center text-current transition-none\"\n >\n <CheckIcon className=\"size-3.5\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n}\n\nexport { Checkbox }\n",
"type": "registry:ui"
}
]

View File

@@ -0,0 +1,19 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "dropdown-menu-dialog",
"type": "registry:example",
"registryDependencies": [
"dropdown-menu",
"dialog",
"button",
"input",
"label"
],
"files": [
{
"path": "registry/new-york-v4/examples/dropdown-menu-dialog.tsx",
"content": "\"use client\"\n\nimport { useState } from \"react\"\nimport { MoreHorizontalIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/registry/new-york-v4/ui/dialog\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from \"@/registry/new-york-v4/ui/dropdown-menu\"\nimport { Field, FieldGroup, FieldLabel } from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport { Label } from \"@/registry/new-york-v4/ui/label\"\nimport { Textarea } from \"@/registry/new-york-v4/ui/textarea\"\n\nexport default function DropdownMenuDialog() {\n const [showNewDialog, setShowNewDialog] = useState(false)\n const [showShareDialog, setShowShareDialog] = useState(false)\n\n return (\n <>\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" aria-label=\"Open menu\" size=\"icon-sm\">\n <MoreHorizontalIcon />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"w-40\" align=\"end\">\n <DropdownMenuLabel>File Actions</DropdownMenuLabel>\n <DropdownMenuGroup>\n <DropdownMenuItem onSelect={() => setShowNewDialog(true)}>\n New File...\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={() => setShowShareDialog(true)}>\n Share...\n </DropdownMenuItem>\n <DropdownMenuItem disabled>Download</DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n <Dialog open={showNewDialog} onOpenChange={setShowNewDialog}>\n <DialogContent className=\"sm:max-w-[425px]\">\n <DialogHeader>\n <DialogTitle>Create New File</DialogTitle>\n <DialogDescription>\n Provide a name for your new file. Click create when you&apos;re\n done.\n </DialogDescription>\n </DialogHeader>\n <FieldGroup className=\"pb-3\">\n <Field>\n <FieldLabel htmlFor=\"filename\">File Name</FieldLabel>\n <Input id=\"filename\" name=\"filename\" placeholder=\"document.txt\" />\n </Field>\n </FieldGroup>\n <DialogFooter>\n <DialogClose asChild>\n <Button variant=\"outline\">Cancel</Button>\n </DialogClose>\n <Button type=\"submit\">Create</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={showShareDialog} onOpenChange={setShowShareDialog}>\n <DialogContent className=\"sm:max-w-[425px]\">\n <DialogHeader>\n <DialogTitle>Share File</DialogTitle>\n <DialogDescription>\n Anyone with the link will be able to view this file.\n </DialogDescription>\n </DialogHeader>\n <FieldGroup className=\"py-3\">\n <Field>\n <Label htmlFor=\"email\">Email Address</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n placeholder=\"shadcn@vercel.com\"\n autoComplete=\"off\"\n />\n </Field>\n <Field>\n <FieldLabel htmlFor=\"message\">Message (Optional)</FieldLabel>\n <Textarea\n id=\"message\"\n name=\"message\"\n placeholder=\"Check out this file\"\n />\n </Field>\n </FieldGroup>\n <DialogFooter>\n <DialogClose asChild>\n <Button variant=\"outline\">Cancel</Button>\n </DialogClose>\n <Button type=\"submit\">Send Invite</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -9,7 +9,7 @@
"files": [
{
"path": "registry/new-york-v4/examples/empty-demo.tsx",
"content": "import { IconFolderCode } from \"@tabler/icons-react\"\nimport { ArrowUpRightIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from \"@/registry/new-york-v4/ui/empty\"\n\nexport default function EmptyDemo() {\n return (\n <Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <IconFolderCode />\n </EmptyMedia>\n <EmptyTitle>No Projects Yet</EmptyTitle>\n <EmptyDescription>\n You haven&apos;t created any projects yet. Get started by creating\n your first project.\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n <div className=\"flex gap-2\">\n <Button size=\"sm\">Create Project</Button>\n <Button variant=\"outline\">Import Project</Button>\n </div>\n </EmptyContent>\n <Button\n variant=\"link\"\n asChild\n className=\"text-muted-foreground\"\n size=\"sm\"\n >\n <a href=\"#\">\n Learn More <ArrowUpRightIcon />\n </a>\n </Button>\n </Empty>\n )\n}\n",
"content": "import { IconFolderCode } from \"@tabler/icons-react\"\nimport { ArrowUpRightIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from \"@/registry/new-york-v4/ui/empty\"\n\nexport default function EmptyDemo() {\n return (\n <Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <IconFolderCode />\n </EmptyMedia>\n <EmptyTitle>No Projects Yet</EmptyTitle>\n <EmptyDescription>\n You haven&apos;t created any projects yet. Get started by creating\n your first project.\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n <div className=\"flex gap-2\">\n <Button>Create Project</Button>\n <Button variant=\"outline\">Import Project</Button>\n </div>\n </EmptyContent>\n <Button\n variant=\"link\"\n asChild\n className=\"text-muted-foreground\"\n size=\"sm\"\n >\n <a href=\"#\">\n Learn More <ArrowUpRightIcon />\n </a>\n </Button>\n </Empty>\n )\n}\n",
"type": "registry:example"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-next-demo",
"type": "registry:example",
"registryDependencies": [
"field",
"input",
"textarea",
"button",
"card",
"spinner"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-next-demo.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport Form from \"next/form\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupText,\n InputGroupTextarea,\n} from \"@/registry/new-york-v4/ui/input-group\"\nimport { Spinner } from \"@/registry/new-york-v4/ui/spinner\"\n\nimport { demoFormAction } from \"./form-next-demo-action\"\nimport { type FormState } from \"./form-next-demo-schema\"\n\nexport default function FormNextDemo() {\n const [formState, formAction, pending] = React.useActionState<\n FormState,\n FormData\n >(demoFormAction, {\n values: {\n title: \"\",\n description: \"\",\n },\n errors: null,\n success: false,\n })\n const [descriptionLength, setDescriptionLength] = React.useState(0)\n\n React.useEffect(() => {\n if (formState.success) {\n toast(\"Thank you for your feedback\", {\n description: \"We'll review your report and get back to you soon.\",\n })\n }\n }, [formState.success])\n\n React.useEffect(() => {\n setDescriptionLength(formState.values.description.length)\n }, [formState.values.description])\n\n return (\n <Card className=\"w-full max-w-md\">\n <CardHeader>\n <CardTitle>Bug Report</CardTitle>\n <CardDescription>\n Help us improve by reporting bugs you encounter.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <Form action={formAction} id=\"bug-report-form\">\n <FieldGroup>\n <Field data-invalid={!!formState.errors?.title?.length}>\n <FieldLabel htmlFor=\"title\">Bug Title</FieldLabel>\n <Input\n id=\"title\"\n name=\"title\"\n defaultValue={formState.values.title}\n disabled={pending}\n aria-invalid={!!formState.errors?.title?.length}\n placeholder=\"Login button not working on mobile\"\n autoComplete=\"off\"\n />\n {formState.errors?.title && (\n <FieldError>{formState.errors.title[0]}</FieldError>\n )}\n </Field>\n <Field data-invalid={!!formState.errors?.description?.length}>\n <FieldLabel htmlFor=\"description\">Description</FieldLabel>\n <InputGroup>\n <InputGroupTextarea\n id=\"description\"\n name=\"description\"\n defaultValue={formState.values.description}\n placeholder=\"I'm having an issue with the login button on mobile.\"\n rows={6}\n className=\"min-h-24 resize-none\"\n disabled={pending}\n aria-invalid={!!formState.errors?.description?.length}\n onChange={(e) => setDescriptionLength(e.target.value.length)}\n />\n <InputGroupAddon align=\"block-end\">\n <InputGroupText className=\"tabular-nums\">\n {descriptionLength}/100 characters\n </InputGroupText>\n </InputGroupAddon>\n </InputGroup>\n <FieldDescription>\n Include steps to reproduce, expected behavior, and what actually\n happened.\n </FieldDescription>\n {formState.errors?.description && (\n <FieldError>{formState.errors.description[0]}</FieldError>\n )}\n </Field>\n </FieldGroup>\n </Form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"submit\" disabled={pending} form=\"bug-report-form\">\n {pending && <Spinner />}\n Submit\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-demo",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"input",
"input-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-demo.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupText,\n InputGroupTextarea,\n} from \"@/registry/new-york-v4/ui/input-group\"\n\nconst formSchema = z.object({\n title: z\n .string()\n .min(5, \"Bug title must be at least 5 characters.\")\n .max(32, \"Bug title must be at most 32 characters.\"),\n description: z\n .string()\n .min(20, \"Description must be at least 20 characters.\")\n .max(100, \"Description must be at most 100 characters.\"),\n})\n\nexport default function BugReportForm() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n title: \"\",\n description: \"\",\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Bug Report</CardTitle>\n <CardDescription>\n Help us improve by reporting bugs you encounter.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-demo\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"title\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"form-rhf-demo-title\">\n Bug Title\n </FieldLabel>\n <Input\n {...field}\n id=\"form-rhf-demo-title\"\n aria-invalid={fieldState.invalid}\n placeholder=\"Login button not working on mobile\"\n autoComplete=\"off\"\n />\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </Field>\n )}\n />\n <Controller\n name=\"description\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"form-rhf-demo-description\">\n Description\n </FieldLabel>\n <InputGroup>\n <InputGroupTextarea\n {...field}\n id=\"form-rhf-demo-description\"\n placeholder=\"I'm having an issue with the login button on mobile.\"\n rows={6}\n className=\"min-h-24 resize-none\"\n aria-invalid={fieldState.invalid}\n />\n <InputGroupAddon align=\"block-end\">\n <InputGroupText className=\"tabular-nums\">\n {field.value.length}/100 characters\n </InputGroupText>\n </InputGroupAddon>\n </InputGroup>\n <FieldDescription>\n Include steps to reproduce, expected behavior, and what\n actually happened.\n </FieldDescription>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </Field>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-demo\">\n Submit\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-input",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"input",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-input.tsx",
"content": "\"use client\"\n\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\n\nconst formSchema = z.object({\n username: z\n .string()\n .min(3, \"Username must be at least 3 characters.\")\n .max(10, \"Username must be at most 10 characters.\")\n .regex(\n /^[a-zA-Z0-9_]+$/,\n \"Username can only contain letters, numbers, and underscores.\"\n ),\n})\n\nexport default function FormRhfInput() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n username: \"\",\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Profile Settings</CardTitle>\n <CardDescription>\n Update your profile information below.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-input\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"username\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"form-rhf-input-username\">\n Username\n </FieldLabel>\n <Input\n {...field}\n id=\"form-rhf-input-username\"\n aria-invalid={fieldState.invalid}\n placeholder=\"shadcn\"\n autoComplete=\"username\"\n />\n <FieldDescription>\n This is your public display name. Must be between 3 and 10\n characters. Must only contain letters, numbers, and\n underscores.\n </FieldDescription>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </Field>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-input\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-radiogroup",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"radio-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-radiogroup.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSet,\n FieldTitle,\n} from \"@/registry/new-york-v4/ui/field\"\nimport {\n RadioGroup,\n RadioGroupItem,\n} from \"@/registry/new-york-v4/ui/radio-group\"\n\nconst plans = [\n {\n id: \"starter\",\n title: \"Starter (100K tokens/month)\",\n description: \"For everyday use with basic features.\",\n },\n {\n id: \"pro\",\n title: \"Pro (1M tokens/month)\",\n description: \"For advanced AI usage with more features.\",\n },\n {\n id: \"enterprise\",\n title: \"Enterprise (Unlimited tokens)\",\n description: \"For large teams and heavy usage.\",\n },\n] as const\n\nconst formSchema = z.object({\n plan: z.string().min(1, \"You must select a subscription plan to continue.\"),\n})\n\nexport default function FormRhfRadioGroup() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n plan: \"\",\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Subscription Plan</CardTitle>\n <CardDescription>\n See pricing and features for each plan.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-radiogroup\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"plan\"\n control={form.control}\n render={({ field, fieldState }) => (\n <FieldSet data-invalid={fieldState.invalid}>\n <FieldLegend>Plan</FieldLegend>\n <FieldDescription>\n You can upgrade or downgrade your plan at any time.\n </FieldDescription>\n <RadioGroup\n name={field.name}\n value={field.value}\n onValueChange={field.onChange}\n aria-invalid={fieldState.invalid}\n >\n {plans.map((plan) => (\n <FieldLabel\n key={plan.id}\n htmlFor={`form-rhf-radiogroup-${plan.id}`}\n >\n <Field\n orientation=\"horizontal\"\n data-invalid={fieldState.invalid}\n >\n <FieldContent>\n <FieldTitle>{plan.title}</FieldTitle>\n <FieldDescription>\n {plan.description}\n </FieldDescription>\n </FieldContent>\n <RadioGroupItem\n value={plan.id}\n id={`form-rhf-radiogroup-${plan.id}`}\n aria-invalid={fieldState.invalid}\n />\n </Field>\n </FieldLabel>\n ))}\n </RadioGroup>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </FieldSet>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-radiogroup\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-select",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"select",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-select.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/new-york-v4/ui/select\"\n\nconst spokenLanguages = [\n { label: \"English\", value: \"en\" },\n { label: \"Spanish\", value: \"es\" },\n { label: \"French\", value: \"fr\" },\n { label: \"German\", value: \"de\" },\n { label: \"Italian\", value: \"it\" },\n { label: \"Chinese\", value: \"zh\" },\n { label: \"Japanese\", value: \"ja\" },\n] as const\n\nconst formSchema = z.object({\n language: z\n .string()\n .min(1, \"Please select your spoken language.\")\n .refine((val) => val !== \"auto\", {\n message:\n \"Auto-detection is not allowed. Please select a specific language.\",\n }),\n})\n\nexport default function FormRhfSelect() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n language: \"\",\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-lg\">\n <CardHeader>\n <CardTitle>Language Preferences</CardTitle>\n <CardDescription>\n Select your preferred spoken language.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-select\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"language\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field\n orientation=\"responsive\"\n data-invalid={fieldState.invalid}\n >\n <FieldContent>\n <FieldLabel htmlFor=\"form-rhf-select-language\">\n Spoken Language\n </FieldLabel>\n <FieldDescription>\n For best results, select the language you speak.\n </FieldDescription>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </FieldContent>\n <Select\n name={field.name}\n value={field.value}\n onValueChange={field.onChange}\n >\n <SelectTrigger\n id=\"form-rhf-select-language\"\n aria-invalid={fieldState.invalid}\n className=\"min-w-[120px]\"\n >\n <SelectValue placeholder=\"Select\" />\n </SelectTrigger>\n <SelectContent position=\"item-aligned\">\n <SelectItem value=\"auto\">Auto</SelectItem>\n <SelectSeparator />\n {spokenLanguages.map((language) => (\n <SelectItem key={language.value} value={language.value}>\n {language.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </Field>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-select\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-switch",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"switch",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-switch.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Switch } from \"@/registry/new-york-v4/ui/switch\"\n\nconst formSchema = z.object({\n twoFactor: z.boolean().refine((val) => val === true, {\n message: \"It is highly recommended to enable two-factor authentication.\",\n }),\n})\n\nexport default function FormRhfSwitch() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n twoFactor: false,\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Security Settings</CardTitle>\n <CardDescription>\n Manage your account security preferences.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-switch\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"twoFactor\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field\n orientation=\"horizontal\"\n data-invalid={fieldState.invalid}\n >\n <FieldContent>\n <FieldLabel htmlFor=\"form-rhf-switch-twoFactor\">\n Multi-factor authentication\n </FieldLabel>\n <FieldDescription>\n Enable multi-factor authentication to secure your account.\n </FieldDescription>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </FieldContent>\n <Switch\n id=\"form-rhf-switch-twoFactor\"\n name={field.name}\n checked={field.value}\n onCheckedChange={field.onChange}\n aria-invalid={fieldState.invalid}\n />\n </Field>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-switch\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-rhf-textarea",
"type": "registry:example",
"dependencies": [
"react-hook-form",
"@hookform/resolvers",
"zod"
],
"registryDependencies": [
"field",
"textarea",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-textarea.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Controller, useForm } from \"react-hook-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Textarea } from \"@/registry/new-york-v4/ui/textarea\"\n\nconst formSchema = z.object({\n about: z\n .string()\n .min(10, \"Please provide at least 10 characters.\")\n .max(200, \"Please keep it under 200 characters.\"),\n})\n\nexport default function FormRhfTextarea() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: {\n about: \"\",\n },\n })\n\n function onSubmit(data: z.infer<typeof formSchema>) {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n }\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Personalization</CardTitle>\n <CardDescription>\n Customize your experience by telling us more about yourself.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form id=\"form-rhf-textarea\" onSubmit={form.handleSubmit(onSubmit)}>\n <FieldGroup>\n <Controller\n name=\"about\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"form-rhf-textarea-about\">\n More about you\n </FieldLabel>\n <Textarea\n {...field}\n id=\"form-rhf-textarea-about\"\n aria-invalid={fieldState.invalid}\n placeholder=\"I'm a software engineer...\"\n className=\"min-h-[120px]\"\n />\n <FieldDescription>\n Tell us more about yourself. This will be used to help us\n personalize your experience.\n </FieldDescription>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </Field>\n )}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-rhf-textarea\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-tanstack-input",
"type": "registry:example",
"dependencies": [
"@tanstack/react-form",
"zod"
],
"registryDependencies": [
"field",
"input",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-input.tsx",
"content": "/* eslint-disable react/no-children-prop */\n\"use client\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\n\nconst formSchema = z.object({\n username: z\n .string()\n .min(3, \"Username must be at least 3 characters.\")\n .max(10, \"Username must be at most 10 characters.\")\n .regex(\n /^[a-zA-Z0-9_]+$/,\n \"Username can only contain letters, numbers, and underscores.\"\n ),\n})\n\nexport default function FormTanstackInput() {\n const form = useForm({\n defaultValues: {\n username: \"\",\n },\n validators: {\n onSubmit: formSchema,\n },\n onSubmit: async ({ value }) => {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(value, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n },\n })\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Profile Settings</CardTitle>\n <CardDescription>\n Update your profile information below.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form\n id=\"form-tanstack-input\"\n onSubmit={(e) => {\n e.preventDefault()\n form.handleSubmit()\n }}\n >\n <FieldGroup>\n <form.Field\n name=\"username\"\n children={(field) => {\n const isInvalid =\n field.state.meta.isTouched && !field.state.meta.isValid\n return (\n <Field data-invalid={isInvalid}>\n <FieldLabel htmlFor=\"form-tanstack-input-username\">\n Username\n </FieldLabel>\n <Input\n id=\"form-tanstack-input-username\"\n name={field.name}\n value={field.state.value}\n onBlur={field.handleBlur}\n onChange={(e) => field.handleChange(e.target.value)}\n aria-invalid={isInvalid}\n placeholder=\"shadcn\"\n autoComplete=\"username\"\n />\n <FieldDescription>\n This is your public display name. Must be between 3 and 10\n characters. Must only contain letters, numbers, and\n underscores.\n </FieldDescription>\n {isInvalid && (\n <FieldError errors={field.state.meta.errors} />\n )}\n </Field>\n )\n }}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-tanstack-input\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-tanstack-radiogroup",
"type": "registry:example",
"dependencies": [
"@tanstack/react-form",
"zod"
],
"registryDependencies": [
"field",
"radio-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-radiogroup.tsx",
"content": "/* eslint-disable react/no-children-prop */\n\"use client\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSet,\n FieldTitle,\n} from \"@/registry/new-york-v4/ui/field\"\nimport {\n RadioGroup,\n RadioGroupItem,\n} from \"@/registry/new-york-v4/ui/radio-group\"\n\nconst plans = [\n {\n id: \"starter\",\n title: \"Starter (100K tokens/month)\",\n description: \"For everyday use with basic features.\",\n },\n {\n id: \"pro\",\n title: \"Pro (1M tokens/month)\",\n description: \"For advanced AI usage with more features.\",\n },\n {\n id: \"enterprise\",\n title: \"Enterprise (Unlimited tokens)\",\n description: \"For large teams and heavy usage.\",\n },\n] as const\n\nconst formSchema = z.object({\n plan: z.string().min(1, \"You must select a subscription plan to continue.\"),\n})\n\nexport default function FormTanstackRadioGroup() {\n const form = useForm({\n defaultValues: {\n plan: \"\",\n },\n validators: {\n onSubmit: formSchema,\n },\n onSubmit: async ({ value }) => {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(value, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n },\n })\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Subscription Plan</CardTitle>\n <CardDescription>\n See pricing and features for each plan.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form\n id=\"form-tanstack-radiogroup\"\n onSubmit={(e) => {\n e.preventDefault()\n form.handleSubmit()\n }}\n >\n <FieldGroup>\n <form.Field\n name=\"plan\"\n children={(field) => {\n const isInvalid =\n field.state.meta.isTouched && !field.state.meta.isValid\n return (\n <FieldSet>\n <FieldLegend>Plan</FieldLegend>\n <FieldDescription>\n You can upgrade or downgrade your plan at any time.\n </FieldDescription>\n <RadioGroup\n name={field.name}\n value={field.state.value}\n onValueChange={field.handleChange}\n >\n {plans.map((plan) => (\n <FieldLabel\n key={plan.id}\n htmlFor={`form-tanstack-radiogroup-${plan.id}`}\n >\n <Field\n orientation=\"horizontal\"\n data-invalid={isInvalid}\n >\n <FieldContent>\n <FieldTitle>{plan.title}</FieldTitle>\n <FieldDescription>\n {plan.description}\n </FieldDescription>\n </FieldContent>\n <RadioGroupItem\n value={plan.id}\n id={`form-tanstack-radiogroup-${plan.id}`}\n aria-invalid={isInvalid}\n />\n </Field>\n </FieldLabel>\n ))}\n </RadioGroup>\n {isInvalid && (\n <FieldError errors={field.state.meta.errors} />\n )}\n </FieldSet>\n )\n }}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-tanstack-radiogroup\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-tanstack-switch",
"type": "registry:example",
"dependencies": [
"@tanstack/react-form",
"zod"
],
"registryDependencies": [
"field",
"switch",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-switch.tsx",
"content": "/* eslint-disable react/no-children-prop */\n\"use client\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Switch } from \"@/registry/new-york-v4/ui/switch\"\n\nconst formSchema = z.object({\n twoFactor: z.boolean().refine((val) => val === true, {\n message: \"It is highly recommended to enable two-factor authentication.\",\n }),\n})\n\nexport default function FormTanstackSwitch() {\n const form = useForm({\n defaultValues: {\n twoFactor: false,\n },\n validators: {\n onSubmit: formSchema,\n },\n onSubmit: async ({ value }) => {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(value, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n },\n })\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Security Settings</CardTitle>\n <CardDescription>\n Manage your account security preferences.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form\n id=\"form-tanstack-switch\"\n onSubmit={(e) => {\n e.preventDefault()\n form.handleSubmit()\n }}\n >\n <FieldGroup>\n <form.Field\n name=\"twoFactor\"\n children={(field) => {\n const isInvalid =\n field.state.meta.isTouched && !field.state.meta.isValid\n return (\n <Field orientation=\"horizontal\" data-invalid={isInvalid}>\n <FieldContent>\n <FieldLabel htmlFor=\"form-tanstack-switch-twoFactor\">\n Multi-factor authentication\n </FieldLabel>\n <FieldDescription>\n Enable multi-factor authentication to secure your\n account.\n </FieldDescription>\n {isInvalid && (\n <FieldError errors={field.state.meta.errors} />\n )}\n </FieldContent>\n <Switch\n id=\"form-tanstack-switch-twoFactor\"\n name={field.name}\n checked={field.state.value}\n onCheckedChange={field.handleChange}\n aria-invalid={isInvalid}\n />\n </Field>\n )\n }}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-tanstack-switch\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "form-tanstack-textarea",
"type": "registry:example",
"dependencies": [
"@tanstack/react-form",
"zod"
],
"registryDependencies": [
"field",
"textarea",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-textarea.tsx",
"content": "/* eslint-disable react/no-children-prop */\n\"use client\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { toast } from \"sonner\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Field,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Textarea } from \"@/registry/new-york-v4/ui/textarea\"\n\nconst formSchema = z.object({\n about: z\n .string()\n .min(10, \"Please provide at least 10 characters.\")\n .max(200, \"Please keep it under 200 characters.\"),\n})\n\nexport default function FormTanstackTextarea() {\n const form = useForm({\n defaultValues: {\n about: \"\",\n },\n validators: {\n onSubmit: formSchema,\n },\n onSubmit: async ({ value }) => {\n toast(\"You submitted the following values:\", {\n description: (\n <pre className=\"bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4\">\n <code>{JSON.stringify(value, null, 2)}</code>\n </pre>\n ),\n position: \"bottom-right\",\n classNames: {\n content: \"flex flex-col gap-2\",\n },\n style: {\n \"--border-radius\": \"calc(var(--radius) + 4px)\",\n } as React.CSSProperties,\n })\n },\n })\n\n return (\n <Card className=\"w-full sm:max-w-md\">\n <CardHeader>\n <CardTitle>Personalization</CardTitle>\n <CardDescription>\n Customize your experience by telling us more about yourself.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form\n id=\"form-tanstack-textarea\"\n onSubmit={(e) => {\n e.preventDefault()\n form.handleSubmit()\n }}\n >\n <FieldGroup>\n <form.Field\n name=\"about\"\n children={(field) => {\n const isInvalid =\n field.state.meta.isTouched && !field.state.meta.isValid\n return (\n <Field data-invalid={isInvalid}>\n <FieldLabel htmlFor=\"form-tanstack-textarea-about\">\n More about you\n </FieldLabel>\n <Textarea\n id=\"form-tanstack-textarea-about\"\n name={field.name}\n value={field.state.value}\n onBlur={field.handleBlur}\n onChange={(e) => field.handleChange(e.target.value)}\n aria-invalid={isInvalid}\n placeholder=\"I'm a software engineer...\"\n className=\"min-h-[120px]\"\n />\n <FieldDescription>\n Tell us more about yourself. This will be used to help us\n personalize your experience.\n </FieldDescription>\n {isInvalid && (\n <FieldError errors={field.state.meta.errors} />\n )}\n </Field>\n )\n }}\n />\n </FieldGroup>\n </form>\n </CardContent>\n <CardFooter>\n <Field orientation=\"horizontal\">\n <Button type=\"button\" variant=\"outline\" onClick={() => form.reset()}>\n Reset\n </Button>\n <Button type=\"submit\" form=\"form-tanstack-textarea\">\n Save\n </Button>\n </Field>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "sonner-types",
"type": "registry:example",
"registryDependencies": [
"sonner"
],
"files": [
{
"path": "registry/new-york-v4/examples/sonner-types.tsx",
"content": "\"use client\"\n\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\n\nexport default function SonnerTypes() {\n return (\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" onClick={() => toast(\"Event has been created\")}>\n Default\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => toast.success(\"Event has been created\")}\n >\n Success\n </Button>\n <Button\n variant=\"outline\"\n onClick={() =>\n toast.info(\"Be at the area 10 minutes before the event time\")\n }\n >\n Info\n </Button>\n <Button\n variant=\"outline\"\n onClick={() =>\n toast.warning(\"Event start time cannot be earlier than 8am\")\n }\n >\n Warning\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => toast.error(\"Event has not been created\")}\n >\n Error\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n toast.promise<{ name: string }>(\n () =>\n new Promise((resolve) =>\n setTimeout(() => resolve({ name: \"Event\" }), 2000)\n ),\n {\n loading: \"Loading...\",\n success: (data) => `${data.name} has been created`,\n error: \"Error\",\n }\n )\n }}\n >\n Promise\n </Button>\n </div>\n )\n}\n",
"type": "registry:example"
}
]
}

View File

@@ -9,7 +9,7 @@
"files": [
{
"path": "registry/new-york-v4/ui/sonner.tsx",
"content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, ToasterProps } from \"sonner\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme()\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\",\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\nexport { Toaster }\n",
"content": "\"use client\"\n\nimport {\n CircleCheckIcon,\n InfoIcon,\n Loader2Icon,\n OctagonXIcon,\n TriangleAlertIcon,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme()\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n icons={{\n success: <CircleCheckIcon className=\"size-4\" />,\n info: <InfoIcon className=\"size-4\" />,\n warning: <TriangleAlertIcon className=\"size-4\" />,\n error: <OctagonXIcon className=\"size-4\" />,\n loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n }}\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\",\n \"--border-radius\": \"var(--radius)\",\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\nexport { Toaster }\n",
"type": "registry:ui"
}
]

View File

@@ -9,7 +9,7 @@
"files": [
{
"path": "ui/checkbox.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n className={cn(\"flex items-center justify-center text-current\")}\n >\n <Check className=\"h-4 w-4\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n className={cn(\"grid place-content-center text-current\")}\n >\n <Check className=\"h-4 w-4\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n",
"type": "registry:ui",
"target": ""
}

View File

@@ -17,7 +17,7 @@
"files": [
{
"path": "ui/form.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/registry/new-york/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n {} as FormFieldContextValue\n)\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState, formState } = useFormContext()\n\n const fieldState = getFieldState(fieldContext.name, formState)\n\n if (!fieldContext) {\n throw new Error(\"useFormField should be used within <FormField>\")\n }\n\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\ntype FormItemContextValue = {\n id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n {} as FormItemContextValue\n)\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n </FormItemContext.Provider>\n )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={\n !error\n ? `${formDescriptionId}`\n : `${formDescriptionId} ${formMessageId}`\n }\n aria-invalid={!!error}\n {...props}\n />\n )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-[0.8rem] text-muted-foreground\", className)}\n {...props}\n />\n )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error?.message ?? \"\") : children\n\n if (!body) {\n return null\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-[0.8rem] font-medium text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n}\n",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/registry/new-york/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue | null>(null)\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState, formState } = useFormContext()\n\n if (!fieldContext) {\n throw new Error(\"useFormField should be used within <FormField>\")\n }\n\n if (!itemContext) {\n throw new Error(\"useFormField should be used within <FormItem>\")\n }\n\n const fieldState = getFieldState(fieldContext.name, formState)\n\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\ntype FormItemContextValue = {\n id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue | null>(null)\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n </FormItemContext.Provider>\n )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={\n !error\n ? `${formDescriptionId}`\n : `${formDescriptionId} ${formMessageId}`\n }\n aria-invalid={!!error}\n {...props}\n />\n )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-[0.8rem] text-muted-foreground\", className)}\n {...props}\n />\n )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error?.message ?? \"\") : children\n\n if (!body) {\n return null\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-[0.8rem] font-medium text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n}\n",
"type": "registry:ui",
"target": ""
}

File diff suppressed because one or more lines are too long

View File

@@ -2161,141 +2161,6 @@
],
"categories": ["authentication", "otp"]
},
{
"name": "new-components-01",
"type": "registry:block",
"description": "New components",
"files": [
{
"path": "registry/new-york-v4/blocks/new-components-01/page.tsx",
"type": "registry:page",
"target": "app/page.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/field.tsx",
"type": "registry:ui",
"target": "components/ui/field.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/button-group.tsx",
"type": "registry:ui",
"target": "components/ui/button-group.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/input-group.tsx",
"type": "registry:ui",
"target": "components/ui/input-group.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/empty.tsx",
"type": "registry:ui",
"target": "components/ui/empty.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/item.tsx",
"type": "registry:ui",
"target": "components/ui/item.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/spinner.tsx",
"type": "registry:ui",
"target": "components/ui/spinner.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/button.tsx",
"type": "registry:ui",
"target": "components/ui/button.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/input.tsx",
"type": "registry:ui",
"target": "components/ui/input.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/tooltip.tsx",
"type": "registry:ui",
"target": "components/ui/tooltip.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/ui/dropdown-menu.tsx",
"type": "registry:ui",
"target": "components/ui/dropdown-menu.tsx"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/appearance-settings.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/button-group-demo.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/button-group-input-group.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/button-group-nested.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/button-group-popover.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/empty-avatar-group.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/empty-input-group.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/field-choice-card.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/field-demo.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/field-slider.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/input-group-button.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/input-group-demo.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/input-group-textarea.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/item-avatar.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/item-demo.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/notion-prompt-form.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/spinner-badge.tsx",
"type": "registry:component"
},
{
"path": "registry/new-york-v4/blocks/new-components-01/components/spinner-empty.tsx",
"type": "registry:component"
}
],
"categories": ["components", "showcase"]
},
{
"name": "chart-area-axes",
"type": "registry:block",
@@ -4527,6 +4392,280 @@
}
]
},
{
"name": "form-rhf-demo",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": [
"field",
"input",
"input-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-demo.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-input",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "input", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-input.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-select",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "select", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-select.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-checkbox",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "checkbox", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-checkbox.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-switch",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "switch", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-switch.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-textarea",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "textarea", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-textarea.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-radiogroup",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": ["field", "radio-group", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-radiogroup.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-array",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": [
"field",
"input",
"input-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-array.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-complex",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": [
"field",
"button",
"card",
"checkbox",
"radio-group",
"select",
"switch"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-complex.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-rhf-password",
"type": "registry:example",
"dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
"registryDependencies": [
"field",
"input-group",
"progress",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-rhf-password.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-demo",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": [
"field",
"input",
"input-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-demo.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-input",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "input", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-input.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-textarea",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "textarea", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-textarea.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-select",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "select", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-select.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-checkbox",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "checkbox", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-checkbox.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-switch",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "switch", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-switch.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-radiogroup",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": ["field", "radio-group", "button", "card"],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-radiogroup.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-array",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": [
"field",
"input",
"input-group",
"button",
"card"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-array.tsx",
"type": "registry:example"
}
]
},
{
"name": "form-tanstack-complex",
"type": "registry:example",
"dependencies": ["@tanstack/react-form", "zod"],
"registryDependencies": [
"field",
"button",
"card",
"checkbox",
"radio-group",
"select",
"switch"
],
"files": [
{
"path": "registry/new-york-v4/examples/form-tanstack-complex.tsx",
"type": "registry:example"
}
]
},
{
"name": "drawer-dialog",
"type": "registry:example",
@@ -4571,6 +4710,23 @@
}
]
},
{
"name": "dropdown-menu-dialog",
"type": "registry:example",
"registryDependencies": [
"dropdown-menu",
"dialog",
"button",
"input",
"label"
],
"files": [
{
"path": "registry/new-york-v4/examples/dropdown-menu-dialog.tsx",
"type": "registry:example"
}
]
},
{
"name": "hover-card-demo",
"type": "registry:example",
@@ -5270,6 +5426,17 @@
}
]
},
{
"name": "sonner-types",
"type": "registry:example",
"registryDependencies": ["sonner"],
"files": [
{
"path": "registry/new-york-v4/examples/sonner-types.tsx",
"type": "registry:example"
}
]
},
{
"name": "spinner-demo",
"type": "registry:example",

View File

@@ -318,10 +318,5 @@
"name": "otp-05",
"description": "A simple OTP form with social providers.",
"categories": ["authentication", "otp"]
},
{
"name": "new-components-01",
"description": "New components",
"categories": ["components", "showcase"]
}
]

View File

@@ -2456,136 +2456,6 @@ export const Index: Record<string, any> = {
categories: ["authentication","otp"],
meta: undefined,
},
"new-components-01": {
name: "new-components-01",
description: "New components",
type: "registry:block",
registryDependencies: undefined,
files: [{
path: "registry/new-york-v4/blocks/new-components-01/page.tsx",
type: "registry:page",
target: "app/page.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/field.tsx",
type: "registry:ui",
target: "components/ui/field.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/button-group.tsx",
type: "registry:ui",
target: "components/ui/button-group.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/input-group.tsx",
type: "registry:ui",
target: "components/ui/input-group.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/empty.tsx",
type: "registry:ui",
target: "components/ui/empty.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/item.tsx",
type: "registry:ui",
target: "components/ui/item.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/spinner.tsx",
type: "registry:ui",
target: "components/ui/spinner.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/button.tsx",
type: "registry:ui",
target: "components/ui/button.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/input.tsx",
type: "registry:ui",
target: "components/ui/input.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/tooltip.tsx",
type: "registry:ui",
target: "components/ui/tooltip.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/ui/dropdown-menu.tsx",
type: "registry:ui",
target: "components/ui/dropdown-menu.tsx"
},{
path: "registry/new-york-v4/blocks/new-components-01/components/appearance-settings.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/button-group-demo.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/button-group-input-group.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/button-group-nested.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/button-group-popover.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/empty-avatar-group.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/empty-input-group.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/field-choice-card.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/field-demo.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/field-slider.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/input-group-button.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/input-group-demo.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/input-group-textarea.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/item-avatar.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/item-demo.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/notion-prompt-form.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/spinner-badge.tsx",
type: "registry:component",
target: ""
},{
path: "registry/new-york-v4/blocks/new-components-01/components/spinner-empty.tsx",
type: "registry:component",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/blocks/new-components-01/page.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: ["components","showcase"],
meta: undefined,
},
"chart-area-axes": {
name: "chart-area-axes",
description: "",
@@ -5520,6 +5390,348 @@ export const Index: Record<string, any> = {
categories: undefined,
meta: undefined,
},
"form-rhf-demo": {
name: "form-rhf-demo",
description: "",
type: "registry:example",
registryDependencies: ["field","input","input-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-demo.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-demo.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-input": {
name: "form-rhf-input",
description: "",
type: "registry:example",
registryDependencies: ["field","input","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-input.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-input.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-select": {
name: "form-rhf-select",
description: "",
type: "registry:example",
registryDependencies: ["field","select","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-select.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-select.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-checkbox": {
name: "form-rhf-checkbox",
description: "",
type: "registry:example",
registryDependencies: ["field","checkbox","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-checkbox.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-checkbox.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-switch": {
name: "form-rhf-switch",
description: "",
type: "registry:example",
registryDependencies: ["field","switch","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-switch.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-switch.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-textarea": {
name: "form-rhf-textarea",
description: "",
type: "registry:example",
registryDependencies: ["field","textarea","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-textarea.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-textarea.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-radiogroup": {
name: "form-rhf-radiogroup",
description: "",
type: "registry:example",
registryDependencies: ["field","radio-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-radiogroup.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-radiogroup.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-array": {
name: "form-rhf-array",
description: "",
type: "registry:example",
registryDependencies: ["field","input","input-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-array.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-array.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-complex": {
name: "form-rhf-complex",
description: "",
type: "registry:example",
registryDependencies: ["field","button","card","checkbox","radio-group","select","switch"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-complex.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-complex.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-rhf-password": {
name: "form-rhf-password",
description: "",
type: "registry:example",
registryDependencies: ["field","input-group","progress","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-rhf-password.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-rhf-password.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-demo": {
name: "form-tanstack-demo",
description: "",
type: "registry:example",
registryDependencies: ["field","input","input-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-demo.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-demo.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-input": {
name: "form-tanstack-input",
description: "",
type: "registry:example",
registryDependencies: ["field","input","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-input.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-input.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-textarea": {
name: "form-tanstack-textarea",
description: "",
type: "registry:example",
registryDependencies: ["field","textarea","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-textarea.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-textarea.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-select": {
name: "form-tanstack-select",
description: "",
type: "registry:example",
registryDependencies: ["field","select","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-select.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-select.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-checkbox": {
name: "form-tanstack-checkbox",
description: "",
type: "registry:example",
registryDependencies: ["field","checkbox","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-checkbox.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-checkbox.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-switch": {
name: "form-tanstack-switch",
description: "",
type: "registry:example",
registryDependencies: ["field","switch","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-switch.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-switch.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-radiogroup": {
name: "form-tanstack-radiogroup",
description: "",
type: "registry:example",
registryDependencies: ["field","radio-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-radiogroup.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-radiogroup.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-array": {
name: "form-tanstack-array",
description: "",
type: "registry:example",
registryDependencies: ["field","input","input-group","button","card"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-array.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-array.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"form-tanstack-complex": {
name: "form-tanstack-complex",
description: "",
type: "registry:example",
registryDependencies: ["field","button","card","checkbox","radio-group","select","switch"],
files: [{
path: "registry/new-york-v4/examples/form-tanstack-complex.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/form-tanstack-complex.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"drawer-dialog": {
name: "drawer-dialog",
description: "",
@@ -5592,6 +5804,24 @@ export const Index: Record<string, any> = {
categories: undefined,
meta: undefined,
},
"dropdown-menu-dialog": {
name: "dropdown-menu-dialog",
description: "",
type: "registry:example",
registryDependencies: ["dropdown-menu","dialog","button","input","label"],
files: [{
path: "registry/new-york-v4/examples/dropdown-menu-dialog.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/dropdown-menu-dialog.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"hover-card-demo": {
name: "hover-card-demo",
description: "",
@@ -6726,6 +6956,24 @@ export const Index: Record<string, any> = {
categories: undefined,
meta: undefined,
},
"sonner-types": {
name: "sonner-types",
description: "",
type: "registry:example",
registryDependencies: ["sonner"],
files: [{
path: "registry/new-york-v4/examples/sonner-types.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(async () => {
const mod = await import("@/registry/new-york-v4/examples/sonner-types.tsx")
const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
return { default: mod.default || mod[exportName] }
}),
categories: undefined,
meta: undefined,
},
"spinner-demo": {
name: "spinner-demo",
description: "",

View File

@@ -1,144 +0,0 @@
"use client"
import { IconMinus, IconPlus } from "@tabler/icons-react"
import { CheckIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import { Switch } from "@/registry/new-york-v4/ui/switch"
const accents = [
{
name: "Blue",
value: "blue",
},
{
name: "Amber",
value: "amber",
},
{
name: "Green",
value: "green",
},
{
name: "Rose",
value: "rose",
},
]
export function AppearanceSettings() {
return (
<FieldSet>
<FieldGroup>
<FieldSet>
<FieldLegend>Compute Environment</FieldLegend>
<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. This is the
default.
</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 workloads. (Coming
soon)
</FieldDescription>
</FieldContent>
<RadioGroupItem value="vm" id="vm-z4k" />
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Accent</FieldTitle>
<FieldDescription>Select the accent color to use.</FieldDescription>
</FieldContent>
<FieldSet aria-label="Accent">
<RadioGroup className="flex flex-wrap gap-2" defaultValue="blue">
{accents.map((accent) => (
<Label
htmlFor={accent.value}
key={accent.value}
data-theme={accent.value}
className="flex size-6 items-center justify-center rounded-full data-[theme=amber]:bg-amber-600 data-[theme=blue]:bg-blue-700 data-[theme=green]:bg-green-600 data-[theme=rose]:bg-rose-600"
>
<RadioGroupItem
id={accent.value}
value={accent.value}
aria-label={accent.name}
className="peer sr-only"
/>
<CheckIcon className="hidden size-4 stroke-white peer-data-[state=checked]:block" />
</Label>
))}
</RadioGroup>
</FieldSet>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="number-of-gpus-f6l">Number of GPUs</FieldLabel>
<FieldDescription>You can add more later.</FieldDescription>
</FieldContent>
<ButtonGroup>
<Input
id="number-of-gpus-f6l"
placeholder="8"
size={3}
className="h-8 !w-14 font-mono"
maxLength={3}
/>
<Button variant="outline" size="icon-sm" type="button">
<IconMinus />
</Button>
<Button variant="outline" size="icon-sm" type="button">
<IconPlus />
</Button>
</ButtonGroup>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="tinting">Wallpaper Tinting</FieldLabel>
<FieldDescription>
Allow the wallpaper to be tinted.
</FieldDescription>
</FieldContent>
<Switch id="tinting" defaultChecked />
</Field>
</FieldGroup>
</FieldSet>
)
}

View File

@@ -1,113 +0,0 @@
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
export function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal")
return (
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button variant="outline" size="icon" aria-label="Go Back">
<ArrowLeftIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
Add to List
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
Personal
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
Work
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
Other
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,57 +0,0 @@
"use client"
import * as React from "react"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<ButtonGroup className="[--radius:9999rem]">
<ButtonGroup>
<Button variant="outline" size="icon">
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup className="flex-1">
<InputGroup>
<InputGroupInput
placeholder={
voiceEnabled ? "Record and send audio..." : "Send a message..."
}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
onClick={() => setVoiceEnabled(!voiceEnabled)}
data-active={voiceEnabled}
className="data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
aria-pressed={voiceEnabled}
size="icon-xs"
>
<AudioLinesIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,32 +0,0 @@
"use client"
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
export function ButtonGroupNested() {
return (
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-sm" aria-label="Previous">
<ArrowLeftIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Next">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,45 +0,0 @@
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
export function ButtonGroupPopover() {
return (
<ButtonGroup>
<Button variant="outline" size="sm">
<BotIcon /> Copilot
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" aria-label="Open Popover" size="icon-sm">
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="rounded-xl p-0 text-sm">
<div className="px-4 py-3">
<div className="text-sm font-medium">Agent Tasks</div>
</div>
<Separator />
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
<Textarea
placeholder="Describe your task in natural language."
className="mb-4 resize-none"
/>
<p className="font-medium">Start a new task with Copilot</p>
<p className="text-muted-foreground">
Describe your task in natural language. Copilot will work in the
background and open a pull request for your review.
</p>
</div>
</PopoverContent>
</Popover>
</ButtonGroup>
)
}

View File

@@ -1,57 +0,0 @@
import { PlusIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/new-york-v4/ui/empty"
export function EmptyAvatarGroup() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>
Invite your team to collaborate on this project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">
<PlusIcon />
Invite Members
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,43 +0,0 @@
import { SearchIcon } from "lucide-react"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/registry/new-york-v4/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Kbd } from "@/registry/new-york-v4/ui/kbd"
export function EmptyInputGroup() {
return (
<Empty>
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching for
what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,54 +0,0 @@
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
export 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">
<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
<FieldContent>
<FieldTitle>Kubernetes</FieldTitle>
<FieldDescription>
Run GPU workloads on a K8s configured cluster.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel htmlFor="vm-z4k">
<Field orientation="horizontal">
<RadioGroupItem value="vm" id="vm-z4k" />
<FieldContent>
<FieldTitle>Virtual Machine</FieldTitle>
<FieldDescription>
Access a VM configured cluster to run workloads.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
</FieldGroup>
</div>
)
}

View File

@@ -1,151 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
export 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="John Doe"
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-7j9-exp-month-ts6">
Month
</FieldLabel>
<Select defaultValue="">
<SelectTrigger id="checkout-7j9-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>
<FieldSeparator />
<FieldSet>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-optional-comments">
Comments
</FieldLabel>
<Textarea
id="checkout-7j9-optional-comments"
placeholder="Add any additional comments"
/>
</Field>
</FieldGroup>
</FieldSet>
<Field orientation="horizontal">
<Button type="submit">Submit</Button>
<Button variant="outline" type="button">
Cancel
</Button>
</Field>
</FieldGroup>
</form>
</div>
)
}

View File

@@ -1,35 +0,0 @@
"use client"
import { useState } from "react"
import {
Field,
FieldDescription,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Slider } from "@/registry/new-york-v4/ui/slider"
export 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>
)
}

View File

@@ -1,58 +0,0 @@
"use client"
import * as React from "react"
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function InputGroupButtonExample() {
const [isFavorite, setIsFavorite] = React.useState(false)
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup className="[--radius:9999px]">
<InputGroupInput id="input-secure-19" />
<Popover>
<PopoverTrigger asChild>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</InputGroupAddon>
</PopoverTrigger>
<PopoverContent
align="start"
className="flex flex-col gap-1 rounded-xl text-sm"
>
<p className="font-medium">Your connection is not secure.</p>
<p>You should not enter any sensitive information on this site.</p>
</PopoverContent>
</Popover>
<InputGroupAddon className="text-muted-foreground !pl-1">
https://
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton
onClick={() => setIsFavorite(!isFavorite)}
size="icon-xs"
>
<IconStar
data-favorite={isFavorite}
className="data-[favorite=true]:fill-primary data-[favorite=true]:stroke-primary"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,97 +0,0 @@
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/registry/new-york-v4/ui/input-group"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function InputGroupDemo() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="example.com" className="!pl-1" />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton className="rounded-full" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:0.95rem]"
>
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ml-auto">52% used</InputGroupText>
<Separator orientation="vertical" className="!h-4" />
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="@shadcn" />
<InputGroupAddon align="inline-end">
<div className="bg-primary text-foreground flex size-4 items-center justify-center rounded-full">
<IconCheck className="size-3 text-white" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,46 +0,0 @@
import {
IconBrandJavascript,
IconCopy,
IconCornerDownLeft,
IconRefresh,
} from "@tabler/icons-react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@/registry/new-york-v4/ui/input-group"
export function InputGroupTextareaExample() {
return (
<div className="grid w-full max-w-md gap-4">
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
className="min-h-[180px]"
/>
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" className="ml-auto" variant="default">
Run <IconCornerDownLeft />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<IconBrandJavascript />
script.js
</InputGroupText>
<InputGroupButton className="ml-auto">
<IconRefresh />
</InputGroupButton>
<InputGroupButton variant="ghost">
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,78 +0,0 @@
import { Plus } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/registry/new-york-v4/ui/item"
export function ItemAvatar() {
return (
<div className="flex w-full max-w-lg flex-col gap-6">
<Item variant="outline" className="hidden">
<ItemMedia>
<Avatar className="size-10">
<AvatarImage src="https://github.com/maxleiter.png" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>Max Leiter</ItemTitle>
<ItemDescription>Last seen 5 months ago</ItemDescription>
</ItemContent>
<ItemActions>
<Button
size="icon-sm"
variant="outline"
className="rounded-full"
aria-label="Invite"
>
<Plus />
</Button>
</ItemActions>
</Item>
<Item variant="outline">
<ItemMedia>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar className="hidden sm:flex">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="hidden sm:flex">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</ItemMedia>
<ItemContent>
<ItemTitle>No Team Members</ItemTitle>
<ItemDescription>Invite your team to collaborate.</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="outline">
Invite
</Button>
</ItemActions>
</Item>
</div>
)
}

View File

@@ -1,42 +0,0 @@
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/registry/new-york-v4/ui/item"
export function ItemDemo() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<Item variant="outline">
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription className="text-pretty">
Verify via email or phone number.
</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm">Enable</Button>
</ItemActions>
</Item>
<Item variant="outline" size="sm" asChild>
<a href="#">
<ItemMedia>
<BadgeCheckIcon className="size-5" />
</ItemMedia>
<ItemContent>
<ItemTitle>Your profile has been verified.</ItemTitle>
</ItemContent>
<ItemActions>
<ChevronRightIcon className="size-4" />
</ItemActions>
</a>
</Item>
</div>
)
}

View File

@@ -1,467 +0,0 @@
"use client"
import { useMemo, useState } from "react"
import {
IconApps,
IconArrowUp,
IconAt,
IconBook,
IconBrandAbstract,
IconBrandOpenai,
IconBrandZeit,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/registry/new-york-v4/ui/command"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import { Field, FieldLabel } from "@/registry/new-york-v4/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/registry/new-york-v4/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
const SAMPLE_DATA = {
mentionable: [
{
type: "page",
title: "Meeting Notes",
image: "📝",
},
{
type: "page",
title: "Project Dashboard",
image: "📊",
},
{
type: "page",
title: "Ideas & Brainstorming",
image: "💡",
},
{
type: "page",
title: "Calendar & Events",
image: "📅",
},
{
type: "page",
title: "Documentation",
image: "📚",
},
{
type: "page",
title: "Goals & Objectives",
image: "🎯",
},
{
type: "page",
title: "Budget Planning",
image: "💰",
},
{
type: "page",
title: "Team Directory",
image: "👥",
},
{
type: "page",
title: "Technical Specs",
image: "🔧",
},
{
type: "page",
title: "Analytics Report",
image: "📈",
},
{
type: "user",
title: "shadcn",
image: "https://github.com/shadcn.png",
workspace: "Workspace",
},
{
type: "user",
title: "maxleiter",
image: "https://github.com/maxleiter.png",
workspace: "Cursor",
},
{
type: "user",
title: "evilrabbit",
image: "https://github.com/evilrabbit.png",
workspace: "Vercel",
},
],
models: [
{
name: "Auto",
icon: IconBrandZeit,
},
{
name: "Claude Sonnet 4",
icon: IconBrandAbstract,
badge: "Beta",
},
{
name: "GPT-5",
icon: IconBrandOpenai,
badge: "Beta",
},
],
}
function MentionableIcon({
item,
}: {
item: (typeof SAMPLE_DATA.mentionable)[0]
}) {
return item.type === "page" ? (
<span className="flex size-4 items-center justify-center">
{item.image}
</span>
) : (
<Avatar className="size-4">
<AvatarImage src={item.image} />
<AvatarFallback>{item.title[0]}</AvatarFallback>
</Avatar>
)
}
export function NotionPromptForm() {
const [mentions, setMentions] = useState<string[]>([])
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
const [selectedModel, setSelectedModel] = useState<
(typeof SAMPLE_DATA.models)[0]
>(SAMPLE_DATA.models[0])
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
const grouped = useMemo(() => {
return SAMPLE_DATA.mentionable.reduce(
(acc, item) => {
const isAvailable = !mentions.includes(item.title)
if (isAvailable) {
if (!acc[item.type]) {
acc[item.type] = []
}
acc[item.type].push(item)
}
return acc
},
{} as Record<string, typeof SAMPLE_DATA.mentionable>
)
}, [mentions])
const hasMentions = mentions.length > 0
return (
<form className="[--radius:1.2rem]">
<Field>
<FieldLabel htmlFor="notion-prompt" className="sr-only">
Prompt
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="notion-prompt"
placeholder="Ask, search, or make anything..."
/>
<InputGroupAddon align="block-start">
<Popover
open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen}
>
<Tooltip>
<TooltipTrigger
asChild
onFocusCapture={(e) => e.stopPropagation()}
>
<PopoverTrigger asChild>
<InputGroupButton
variant="outline"
size={!hasMentions ? "sm" : "icon-sm"}
className="rounded-full transition-transform"
>
<IconAt /> {!hasMentions && "Add context"}
</InputGroupButton>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Mention a person, page, or date</TooltipContent>
</Tooltip>
<PopoverContent className="p-0 [--radius:1.2rem]" align="start">
<Command>
<CommandInput placeholder="Search pages..." />
<CommandList>
<CommandEmpty>No pages found</CommandEmpty>
{Object.entries(grouped).map(([type, items]) => (
<CommandGroup
key={type}
heading={type === "page" ? "Pages" : "Users"}
>
{items.map((item) => (
<CommandItem
key={item.title}
value={item.title}
onSelect={(currentValue) => {
setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false)
}}
>
<MentionableIcon item={item} />
{item.title}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
<div className="no-scrollbar -m-1.5 flex gap-1 overflow-y-auto p-1.5">
{mentions.map((mention) => {
const item = SAMPLE_DATA.mentionable.find(
(item) => item.title === mention
)
if (!item) {
return null
}
return (
<InputGroupButton
key={mention}
size="sm"
variant="secondary"
className="rounded-full !pl-2"
onClick={() => {
setMentions((prev) => prev.filter((m) => m !== mention))
}}
>
<MentionableIcon item={item} />
{item.title}
<IconX />
</InputGroupButton>
)
})}
</div>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="gap-1">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
size="icon-sm"
className="rounded-full"
aria-label="Attach file"
>
<IconPaperclip />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>Attach file</TooltipContent>
</Tooltip>
<DropdownMenu
open={modelPopoverOpen}
onOpenChange={setModelPopoverOpen}
>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<InputGroupButton size="sm" className="rounded-full">
{selectedModel.icon && selectedModel.name !== "Auto" && (
<selectedModel.icon />
)}
{selectedModel.name}
</InputGroupButton>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent>Select AI model</TooltipContent>
</Tooltip>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:1.2rem]"
>
<DropdownMenuGroup className="w-72">
<DropdownMenuLabel className="text-muted-foreground text-xs">
Get answers about your workspace
</DropdownMenuLabel>
{SAMPLE_DATA.models.map((model) => (
<DropdownMenuCheckboxItem
key={model.name}
checked={model.name === selectedModel.name}
onCheckedChange={(checked) => {
if (checked) {
setSelectedModel(model)
}
}}
className="pl-2 *:[span:first-child]:right-2 *:[span:first-child]:left-auto"
>
{model.icon && <model.icon />}
{model.name}
{model.badge && (
<Badge
variant="secondary"
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
>
{model.badge}
</Badge>
)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu open={scopeMenuOpen} onOpenChange={setScopeMenuOpen}>
<DropdownMenuTrigger asChild>
<InputGroupButton size="sm" className="rounded-full">
<IconWorld /> All Sources
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:1.2rem]"
>
<DropdownMenuGroup>
<DropdownMenuItem
asChild
onSelect={(e) => e.preventDefault()}
>
<label htmlFor="web-search">
<IconWorld /> Web Search{" "}
<Switch
id="web-search"
className="ml-auto"
defaultChecked
/>
</label>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
asChild
onSelect={(e) => e.preventDefault()}
>
<label htmlFor="apps">
<IconApps /> Apps and Integrations
<Switch id="apps" className="ml-auto" defaultChecked />
</label>
</DropdownMenuItem>
<DropdownMenuItem>
<IconCircleDashedPlus /> All Sources I can access
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Avatar className="size-4">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
shadcn
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-72 p-0 [--radius:1.2rem]">
<Command>
<CommandInput
placeholder="Find or use knowledge in..."
autoFocus
/>
<CommandList>
<CommandEmpty>No knowledge found</CommandEmpty>
<CommandGroup>
{SAMPLE_DATA.mentionable
.filter((item) => item.type === "user")
.map((user) => (
<CommandItem
key={user.title}
value={user.title}
onSelect={() => {
// Handle user selection here
console.log("Selected user:", user.title)
}}
>
<Avatar className="size-4">
<AvatarImage src={user.image} />
<AvatarFallback>
{user.title[0]}
</AvatarFallback>
</Avatar>
{user.title}{" "}
<span className="text-muted-foreground">
- {user.workspace}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuItem>
<IconBook /> Help Center
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlus /> Connect Apps
</DropdownMenuItem>
<DropdownMenuLabel className="text-muted-foreground text-xs">
We&apos;ll only search in the sources selected here.
</DropdownMenuLabel>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupButton
aria-label="Send"
className="ml-auto rounded-full"
variant="default"
size="icon-sm"
>
<IconArrowUp />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</form>
)
}

View File

@@ -1,21 +0,0 @@
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function SpinnerBadge() {
return (
<div className="flex items-center gap-2 [--radius:1.2rem]">
<Badge>
<Spinner />
Syncing
</Badge>
<Badge variant="secondary">
<Spinner />
Updating
</Badge>
<Badge variant="outline">
<Spinner />
Processing
</Badge>
</div>
)
}

View File

@@ -1,31 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/new-york-v4/ui/empty"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function SpinnerEmpty() {
return (
<Empty className="w-full">
<EmptyHeader>
<EmptyMedia variant="icon">
<Spinner />
</EmptyMedia>
<EmptyTitle>Processing your request</EmptyTitle>
<EmptyDescription>
Please wait while we process your request. Do not refresh the page.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
Cancel
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,83 +0,0 @@
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Separator } from "@/registry/new-york-v4/ui/separator"
const buttonGroupVariants = cva(
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
{
variants: {
orientation: {
horizontal:
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
vertical:
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
},
},
defaultVariants: {
orientation: "horizontal",
},
}
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<"div"> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "div"
return (
<Comp
className={cn(
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
className
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}

View File

@@ -1,60 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View File

@@ -1,257 +0,0 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
)
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
)
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View File

@@ -1,104 +0,0 @@
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
function Empty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty"
className={cn(
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
className
)}
{...props}
/>
)
}
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-header"
className={cn(
"flex max-w-sm flex-col items-center gap-2 text-center",
className
)}
{...props}
/>
)
}
const emptyMediaVariants = cva(
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: "default",
},
}
)
function EmptyMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
)
}
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-lg font-medium tracking-tight", className)}
{...props}
/>
)
}
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
data-slot="empty-description"
className={cn(
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-content"
className={cn(
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
className
)}
{...props}
/>
)
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}

Some files were not shown because too many files have changed in this diff Show More