Skip to content

Commit

Permalink
docs: Add shadcn UI example (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
marilari88 authored Feb 29, 2024
1 parent 084363d commit 26915e4
Show file tree
Hide file tree
Showing 47 changed files with 4,976 additions and 736 deletions.
1 change: 1 addition & 0 deletions docs/integration/ui-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,4 @@ Here you can find examples integrating with some of the popular UI libraries.
- [Headless UI](../../examples/headless-ui/)
- [Material UI](../../examples/material-ui/)
- [Radix UI](../../examples/radix-ui/)
- [Shadcn UI](../../examples/shadcn-ui/)
18 changes: 18 additions & 0 deletions examples/shadcn-ui/.eslintrc.cjs
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 },
],
},
}
24 changes: 24 additions & 0 deletions examples/shadcn-ui/.gitignore
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?
37 changes: 37 additions & 0 deletions examples/shadcn-ui/README.md
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 -->
17 changes: 17 additions & 0 deletions examples/shadcn-ui/components.json
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"
}
}
13 changes: 13 additions & 0 deletions examples/shadcn-ui/index.html
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>
55 changes: 55 additions & 0 deletions examples/shadcn-ui/package.json
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"
}
}
6 changes: 6 additions & 0 deletions examples/shadcn-ui/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
1 change: 1 addition & 0 deletions examples/shadcn-ui/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
197 changes: 197 additions & 0 deletions examples/shadcn-ui/src/App.tsx
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;
1 change: 1 addition & 0 deletions examples/shadcn-ui/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions examples/shadcn-ui/src/components/Field.tsx
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>;
};
Loading

0 comments on commit 26915e4

Please # to comment.