-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
084363d
commit 26915e4
Showing
47 changed files
with
4,976 additions
and
736 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module.exports = { | ||
root: true, | ||
env: { browser: true, es2020: true }, | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/recommended', | ||
'plugin:react-hooks/recommended', | ||
], | ||
ignorePatterns: ['dist', '.eslintrc.cjs'], | ||
parser: '@typescript-eslint/parser', | ||
plugins: ['react-refresh'], | ||
rules: { | ||
'react-refresh/only-export-components': [ | ||
'warn', | ||
{ allowConstantExport: true }, | ||
], | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Shadcn UI Integration | ||
|
||
[Shadcn UI](https://ui.shadcn.com/) | ||
Shadcn UI is a comprehensive component library built with React. It provides a wide range of pre-built components that can be easily integrated into your projects. The library is designed to be simple to use, allowing you to add components to your project either by copy/pasting them directly or using the provided CLI. | ||
|
||
## Installation | ||
|
||
To install a component, you can simply copy and paste the component code into your project. Alternatively, you can use the Shadcn UI CLI to automatically add components to your project. By default, the CLI will place the components into the `src/components/ui` folder. | ||
|
||
## Conform Forms integration | ||
|
||
This example includes a set of components in a separate folder `src/components/conform` that extend the shadcn components. By using these components, you can quickly and easily build complex forms with full validation and error handling. | ||
|
||
## Additional infos | ||
|
||
This example we leverage [Vite](https://vitejs.dev/) and [Tailwind CSS](https://tailwindcss.com/) | ||
|
||
**Components** | ||
|
||
- Checkbox | ||
- Checkbox group | ||
- Combobox | ||
- Date picker | ||
- Radio group | ||
- Select | ||
- Slider | ||
- Switch | ||
- Textarea | ||
- Toggle group | ||
|
||
## Demo | ||
|
||
<!-- sandbox src="/examples/shadcn-ui" --> | ||
|
||
Try it out on [Codesandbox](https://codesandbox.io/s/github/edmundhung/conform/tree/main/examples/shadcn-ui). | ||
|
||
<!-- /sandbox --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"$schema": "https://ui.shadcn.com/schema.json", | ||
"style": "default", | ||
"rsc": false, | ||
"tsx": true, | ||
"tailwind": { | ||
"config": "tailwind.config.js", | ||
"css": "src/index.css", | ||
"baseColor": "stone", | ||
"cssVariables": false, | ||
"prefix": "" | ||
}, | ||
"aliases": { | ||
"components": "@/components", | ||
"utils": "@/lib/utils" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React + TS</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "shadcn-ui", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"@conform-to/react": "workspace:^", | ||
"@conform-to/zod": "workspace:^", | ||
"@radix-ui/react-checkbox": "^1.0.4", | ||
"@radix-ui/react-dialog": "^1.0.5", | ||
"@radix-ui/react-label": "^2.0.2", | ||
"@radix-ui/react-popover": "^1.0.7", | ||
"@radix-ui/react-radio-group": "^1.1.3", | ||
"@radix-ui/react-select": "^2.0.0", | ||
"@radix-ui/react-slider": "^1.1.2", | ||
"@radix-ui/react-slot": "^1.0.2", | ||
"@radix-ui/react-switch": "^1.0.3", | ||
"@radix-ui/react-toggle": "^1.0.3", | ||
"@radix-ui/react-toggle-group": "^1.0.4", | ||
"class-variance-authority": "^0.7.0", | ||
"clsx": "^2.1.0", | ||
"cmdk": "^0.2.1", | ||
"date-fns": "^3.3.1", | ||
"lucide-react": "^0.338.0", | ||
"react": "^18.2.0", | ||
"react-day-picker": "^8.10.0", | ||
"react-dom": "^18.2.0", | ||
"tailwind-merge": "^2.2.1", | ||
"tailwindcss-animate": "^1.0.7", | ||
"zod": "3.21.4" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.11.20", | ||
"@types/react": "^18.2.56", | ||
"@types/react-dom": "^18.2.19", | ||
"@typescript-eslint/eslint-plugin": "^7.0.2", | ||
"@typescript-eslint/parser": "^7.0.2", | ||
"@vitejs/plugin-react": "^4.2.1", | ||
"autoprefixer": "^10.4.17", | ||
"eslint": "^8.56.0", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"eslint-plugin-react-refresh": "^0.4.5", | ||
"postcss": "^8.4.35", | ||
"tailwindcss": "^3.4.1", | ||
"tailwindcss-animatecss": "^3.0.5", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.1.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default { | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: {}, | ||
}, | ||
}; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { Field, FieldError } from '@/components/Field'; | ||
import { Button } from '@/components/ui/button'; | ||
import { Label } from '@/components/ui/label'; | ||
import { useForm } from '@conform-to/react'; | ||
import { parseWithZod } from '@conform-to/zod'; | ||
import { z } from 'zod'; | ||
import { InputConform } from './components/conform/Input'; | ||
import { DatePickerConform } from './components/conform/DatePicker'; | ||
import { CountryPickerConform } from './components/conform/CountryPicker'; | ||
import { RadioGroupConform } from './components/conform/RadioGroup'; | ||
import { CheckboxConform } from './components/conform/Checkbox'; | ||
import { SelectConform } from './components/conform/Select'; | ||
import { SliderConform } from './components/conform/Slider'; | ||
import { SwitchConform } from './components/conform/Switch'; | ||
import { TextareaConform } from './components/conform/Textarea'; | ||
import { ToggleGroupConform } from './components/conform/ToggleGroup'; | ||
import { CheckboxGroupConform } from './components/conform/CheckboxGroup'; | ||
|
||
const UserSubscriptionSchema = z.object({ | ||
name: z | ||
.string({ required_error: 'Name is required' }) | ||
.min(3, { message: 'Name must be at least 3 characters long' }), | ||
dateOfBirth: z | ||
.date({ | ||
required_error: 'Date of birth is required', | ||
invalid_type_error: 'Invalid date', | ||
}) | ||
.max(new Date(), { message: 'Date of birth cannot be in the future' }), | ||
country: z.string({ required_error: 'Country is required' }), | ||
gender: z.enum(['male', 'female', 'other'], { | ||
required_error: 'Gender is required', | ||
}), | ||
agreeToTerms: z.boolean({ required_error: 'You must agree to the terms' }), | ||
job: z.enum(['developer', 'designer', 'manager'], { | ||
required_error: 'You must select a job', | ||
}), | ||
age: z.number().min(18, 'You must have be more than 18'), | ||
isAdult: z | ||
.boolean() | ||
.optional() | ||
.refine((val) => val == true, 'You must be an adult'), | ||
description: z.string().min(10, 'Description must be at least 10 characters'), | ||
accountType: z.enum(['personal', 'business'], { | ||
required_error: 'You must select an account type', | ||
}), | ||
interests: z | ||
.array(z.string()) | ||
.min(3, 'You must select at least three interest'), | ||
}); | ||
|
||
function App() { | ||
const [form, fields] = useForm({ | ||
id: '#', | ||
onValidate({ formData }) { | ||
return parseWithZod(formData, { schema: UserSubscriptionSchema }); | ||
}, | ||
onSubmit(e) { | ||
e.preventDefault(); | ||
const form = e.currentTarget; | ||
const formData = new FormData(form); | ||
const result = parseWithZod(formData, { schema: UserSubscriptionSchema }); | ||
alert(JSON.stringify(result, null, 2)); | ||
}, | ||
shouldRevalidate: 'onInput', | ||
}); | ||
return ( | ||
<div className="flex flex-col gap-6 p-10"> | ||
<h1 className="text-2xl">Shadcn + Conform example</h1> | ||
<form | ||
method="POST" | ||
id={form.id} | ||
onSubmit={form.onSubmit} | ||
className="flex flex-col gap-4 items-start" | ||
> | ||
<Field> | ||
<Label htmlFor={fields.name.id}>Name</Label> | ||
<InputConform meta={fields.name} type="text" /> | ||
{fields.name.errors && <FieldError>{fields.name.errors}</FieldError>} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.dateOfBirth.id}>Birth date</Label> | ||
<DatePickerConform meta={fields.dateOfBirth} /> | ||
{fields.dateOfBirth.errors && ( | ||
<FieldError>{fields.dateOfBirth.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.country.id}>Country</Label> | ||
<CountryPickerConform meta={fields.country} /> | ||
{fields.country.errors && ( | ||
<FieldError>{fields.country.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.gender.id}>Gender</Label> | ||
<RadioGroupConform | ||
meta={fields.gender} | ||
items={[ | ||
{ value: 'male', label: 'male' }, | ||
{ value: 'female', label: 'female' }, | ||
{ value: 'other', label: 'other' }, | ||
]} | ||
/> | ||
{fields.gender.errors && ( | ||
<FieldError>{fields.gender.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<div className="flex gap-2 items-center"> | ||
<CheckboxConform meta={fields.agreeToTerms} /> | ||
<Label htmlFor={fields.agreeToTerms.id}>Agree to terms</Label> | ||
</div> | ||
{fields.agreeToTerms.errors && ( | ||
<FieldError>{fields.agreeToTerms.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.job.id}>Job</Label> | ||
<SelectConform | ||
placeholder="Select a job" | ||
meta={fields.job} | ||
items={[ | ||
{ value: 'developer', name: 'Developer' }, | ||
{ value: 'designer', name: 'Design' }, | ||
{ value: 'manager', name: 'Manager' }, | ||
]} | ||
/> | ||
{fields.job.errors && <FieldError>{fields.job.errors}</FieldError>} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.age.id}>Age</Label> | ||
<SliderConform meta={fields.age} step={1} /> | ||
{fields.age.errors && <FieldError>{fields.age.errors}</FieldError>} | ||
</Field> | ||
<Field> | ||
<div className="flex items-center gap-2"> | ||
<Label htmlFor={fields.isAdult.id}>Is adult</Label> | ||
<SwitchConform meta={fields.isAdult} /> | ||
</div> | ||
{fields.isAdult.errors && ( | ||
<FieldError>{fields.isAdult.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.description.id}>Description</Label> | ||
<TextareaConform meta={fields.description} /> | ||
{fields.description.errors && ( | ||
<FieldError>{fields.description.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<Label htmlFor={fields.accountType.id}>Account type</Label> | ||
<ToggleGroupConform | ||
type="single" | ||
meta={fields.accountType} | ||
items={[ | ||
{ value: 'personal', label: 'Personal' }, | ||
{ value: 'business', label: 'Business' }, | ||
]} | ||
/> | ||
{fields.accountType.errors && ( | ||
<FieldError>{fields.accountType.errors}</FieldError> | ||
)} | ||
</Field> | ||
<Field> | ||
<fieldset>Interests</fieldset> | ||
<CheckboxGroupConform | ||
meta={fields.interests} | ||
items={[ | ||
{ value: 'react', name: 'React' }, | ||
{ value: 'vue', name: 'Vue' }, | ||
{ value: 'svelte', name: 'Svelte' }, | ||
{ value: 'angular', name: 'Angular' }, | ||
{ value: 'ember', name: 'Ember' }, | ||
{ value: 'next', name: 'Next' }, | ||
{ value: 'nuxt', name: 'Nuxt' }, | ||
{ value: 'sapper', name: 'Sapper' }, | ||
{ value: 'glimmer', name: 'Glimmer' }, | ||
]} | ||
/> | ||
{fields.interests.errors && ( | ||
<FieldError>{fields.interests.errors}</FieldError> | ||
)} | ||
</Field> | ||
|
||
<div className="flex gap-2"> | ||
<Button type="submit">Submit</Button> | ||
<Button type="reset" variant="outline"> | ||
Reset | ||
</Button> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
} | ||
|
||
export default App; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const Field = ({ children }: { children: React.ReactNode }) => { | ||
return <div className="flex flex-col gap-2">{children}</div>; | ||
}; | ||
|
||
export const FieldError = ({ children }: { children: React.ReactNode }) => { | ||
return <div className="text-sm text-red-600">{children}</div>; | ||
}; |
Oops, something went wrong.