├── .eslintrc.json ├── .github └── workflows │ └── nextjs.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── app ├── _components │ ├── field-list.tsx │ ├── hero.tsx │ └── intro.tsx ├── globals.css ├── icon.svg ├── layout.tsx ├── opengraph-image.png ├── page.tsx └── stop-enter-submit │ ├── _components │ ├── correct-form.tsx │ ├── correct-textarea-form.tsx │ ├── incorrect-form.tsx │ └── petition-form.tsx │ ├── opengraph-image.png │ └── page.tsx ├── bun.lockb ├── components.json ├── components └── ui │ ├── accordion.tsx │ ├── button.tsx │ ├── code-block.tsx │ ├── collapsible.tsx │ ├── copy-button.tsx │ ├── external-link.tsx │ ├── footer.tsx │ ├── form-unit.tsx │ ├── form.tsx │ ├── github-link.tsx │ ├── input.tsx │ ├── label.tsx │ ├── ref.tsx │ ├── select.tsx │ ├── tabs.tsx │ └── textarea.tsx ├── fields ├── address │ ├── component.tsx │ ├── doc.mdx │ ├── html.html │ └── zod.ts ├── email │ ├── component.tsx │ ├── doc.mdx │ ├── html.html │ └── zod.ts ├── name │ ├── component.tsx │ ├── doc.mdx │ ├── html.html │ └── zod.ts ├── password │ ├── component.tsx │ ├── doc.mdx │ ├── html.html │ └── zod.ts └── tel │ ├── component.tsx │ ├── doc.mdx │ ├── html.html │ └── zod.ts ├── lib ├── config.ts ├── fields.ts ├── file.ts └── utils.ts ├── mdx-components.tsx ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── hero.svg └── stop-enter-trigger │ ├── chrome.mp4 │ ├── ogimage.png │ └── safari.mp4 ├── tailwind.config.js ├── tsconfig.json └── types └── field-meta.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } -------------------------------------------------------------------------------- /.github/workflows/nextjs.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages 2 | # 3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started 4 | # 5 | name: Deploy Next.js site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ['main'] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: 'pages' 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - uses: oven-sh/setup-bun@v1 35 | with: 36 | bun-version: latest 37 | - name: Setup Node 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: '16' 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v3 43 | with: 44 | # Automatically inject basePath in your Next.js configuration file and disable 45 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). 46 | # 47 | # You may remove this line if you want to manage the configuration yourself. 48 | static_site_generator: next 49 | - name: Restore cache 50 | uses: actions/cache@v3 51 | with: 52 | path: | 53 | .next/cache 54 | # Generate a new cache whenever packages or source files change. 55 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/bun.lockb') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 56 | # If source files changed but packages didn't, rebuild from a prior cache. 57 | restore-keys: | 58 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- 59 | - name: Install dependencies 60 | run: bun install 61 | - name: Build with Next.js 62 | run: bun run build 63 | - name: Upload artifact 64 | uses: actions/upload-pages-artifact@v2 65 | with: 66 | path: ./out 67 | 68 | # Deployment job 69 | deploy: 70 | environment: 71 | name: github-pages 72 | url: ${{ steps.deployment.outputs.page_url }} 73 | runs-on: ubuntu-latest 74 | needs: build 75 | steps: 76 | - name: Deploy to GitHub Pages 77 | id: deployment 78 | uses: actions/deploy-pages@v2 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 貢献者ガイド 2 | 3 | ふぉーむがいどへの貢献に興味を持っていただきありがとうございます。ここにあなたがいることを私たちは嬉しく思っています。 4 | 5 | 初めてプルリクエストを提出する前に、このドキュメントを一度確認してください。また、類似の内容で既に誰かが作業していないか、オープンなイシューとプルリクエストを確認することを強くお勧めします。 6 | 7 | 何か助けが必要な場合は、[@d151005](https://twitter.com/d151005) までお気軽にご連絡ください。 8 | 9 | ## このリポジトリについて 10 | 11 | このリポジトリでは主に以下のライブラリを使用しています。 12 | 13 | - Next.js(App Router) 14 | - [shadcn/ui](https://ui.shadcn.com) 15 | 16 | ## 構造 17 | 18 | このリポジトリの構造は以下のようになっています。 19 | 20 | ``` 21 | ├── app 22 | ├── components 23 | │   └── ui 24 | ├── fields 25 | └── lib 26 | ``` 27 | 28 | | Path | Description | 29 | | --------------- | ---------------------------------------- | 30 | | `components/ui` | アプリケーションで使用するコンポーネント | 31 | | `fields` | ドキュメントの各フォームフィールド関連 | 32 | | `lib` | ヘルパー関数 | 33 | 34 | ## 開発 35 | 36 | ### リポジトリのクローンから始める 37 | 38 | ``` 39 | git clone https://github.com/dninomiya/form-guide.git 40 | ``` 41 | 42 | ### 依存関係のインストール 43 | 44 | ``` 45 | bun install 46 | bun dev 47 | 48 | http://localhost:3000/form-guide 49 | ``` 50 | 51 | _/form-guide を末尾につける点に注意してください。_ 52 | 53 | ## フォームフィールド 54 | 55 | 各フォームフィールドのディレクトリにあるファイルを編集すると自動的にドキュメントサイトに反映されます。誤字脱字を除いては、基本的には Discussions で合意を得た上でプルリクエストをお願いします。 56 | 57 | ```bash 58 | └── fields 59 |    ├── address 60 |    │   ├── component.tsx # プレビュー 61 |    │   ├── doc.mdx # ドキュメント 62 |    │   ├── html.html # HTMLコード 63 |    │   └── zod.ts # zodコード 64 | ``` 65 | 66 | zod のコードは実際に `component.tsx` で使用される点にご注意ください。(ただのサンプルコードではありません) 67 | 68 | ## コミットコメント 69 | 70 | 内容がわかれば問題ありませんが、可能であれば[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)に準拠してください。 71 | 72 | ## 新しい機能やフォームフィールドのリクエスト 73 | 74 | 新しいコンポーネントや機能のリクエストがある場合は、GitHub でディスカッションを開始してください。お手伝いできることがあります。 75 | 76 | ## テスト 77 | 78 | 現在テストはなく、実装予定もありませんが実装いただける場合プルリクエストをお願いします。 79 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daichi Ninomiya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ふぉーむがいど 2 | 3 | オープンソースのフォーム実装ガイドです。 4 | 5 | ![hero](app/opengraph-image.png) 6 | 7 | ## ドキュメント 8 | 9 | https://dninomiya.github.io/form-guide/ 10 | 11 | ## 貢献する 12 | 13 | [貢献者ガイド](/CONTRIBUTING.md) をご参照ください。 14 | 15 | ## ライセンス 16 | 17 | [MIT license](/LICENSE.md). 18 | -------------------------------------------------------------------------------- /app/_components/field-list.tsx: -------------------------------------------------------------------------------- 1 | import FormUnit from '@/components/ui/form-unit'; 2 | import { FIELDS } from '@/lib/fields'; 3 | import { getFile } from '@/lib/file'; 4 | 5 | export default function FieldList() { 6 | const units = FIELDS.map((field) => { 7 | const html = getFile(`fields/${field.id}/html.html`); 8 | const zod = getFile(`fields/${field.id}/zod.ts`); 9 | 10 | return { 11 | id: field.id, 12 | sources: [ 13 | { 14 | code: html, 15 | lang: 'html', 16 | label: 'HTML', 17 | }, 18 | { 19 | code: zod, 20 | lang: 'ts', 21 | label: 'Zod', 22 | }, 23 | ], 24 | }; 25 | }); 26 | 27 | return ( 28 |
29 | {units.map(({ id, sources }) => ( 30 | 31 | ))} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/_components/hero.tsx: -------------------------------------------------------------------------------- 1 | import { AppConfig } from '@/lib/config'; 2 | import { cn } from '@/lib/utils'; 3 | import { Cherry_Bomb_One } from 'next/font/google'; 4 | import Image from 'next/image'; 5 | 6 | const cherry = Cherry_Bomb_One({ 7 | weight: '400', 8 | preload: false, 9 | display: 'swap', 10 | }); 11 | 12 | export default function Hero() { 13 | return ( 14 |
15 |
16 | 17 |
18 |
19 |

22 | ふぉーむがいど 23 |

24 | 25 | {AppConfig.version} 26 | 27 |
28 |

29 | オープンソースのフォーム実装ガイド 30 |

31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/_components/intro.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | Collapsible, 5 | CollapsibleContent, 6 | CollapsibleTrigger, 7 | } from '@/components/ui/collapsible'; 8 | import { AppConfig } from '@/lib/config'; 9 | import { ArrowUpRight, ChevronDown } from 'lucide-react'; 10 | 11 | const links = [ 12 | { 13 | title: '58 Form Design & UX Best Practices', 14 | url: 'https://www.ventureharbour.com/form-design-best-practices', 15 | }, 16 | { 17 | title: '支払いフォームと住所フォームのベストプラクティス', 18 | url: 'https://web.dev/payment-and-address-form-best-practices/', 19 | }, 20 | { 21 | title: 'アドレスフォームのベストプラクティス', 22 | url: 'https://web.dev/codelab-address-form-best-practices/', 23 | }, 24 | { 25 | title: 'アドレスフィールドデザインのベストプラクティス', 26 | url: 'https://uxplanet.org/address-field-design-best-practices-a80390caaee0', 27 | }, 28 | { 29 | title: 30 | '名前、電話番号、メールアドレス、郵便番号等の最適なmaxlengthはいくつか調べてみた', 31 | url: 'https://qiita.com/kyogom/items/49fe51044d57e3b19929', 32 | }, 33 | { 34 | title: 35 | '今どきの入力フォームはこう書く!HTMLコーダーがおさえるべきinputタグの書き方まとめ', 36 | url: 'https://ics.media/entry/11221', 37 | }, 38 | { 39 | title: 'これだけは押さえよう!住所フォームの作り方', 40 | url: 'https://blog.kenall.jp/entry/address-form-best-practice', 41 | }, 42 | { 43 | title: 'Form Design Principles: 13 Empirically Backed Best Practices', 44 | url: 'https://cxl.com/blog/form-design-best-practices/', 45 | }, 46 | { 47 | title: 'Form design best practices', 48 | url: 'https://coyleandrew.medium.com/form-design-best-practices-9525c321d759', 49 | }, 50 | ]; 51 | 52 | const navItems = [ 53 | { 54 | url: `${AppConfig.githubURL}/graphs/contributors`, 55 | title: 'コントリビュータ', 56 | }, 57 | { 58 | url: `${AppConfig.githubURL}/discussions`, 59 | title: '意見交換', 60 | }, 61 | { 62 | url: AppConfig.xURL, 63 | title: 'メンテナーに連絡', 64 | }, 65 | ]; 66 | 67 | export default function Intro() { 68 | return ( 69 | 70 | 71 | 🔰 はじめに 72 | 73 | 74 | 75 |
76 |

77 | このページはオープンソースのフォーム実装ガイドです。フォームの実装において、ユーザー体験を向上させるためのベストプラクティスをまとめています。プルリクエストやコメントを歓迎しています。 78 |

79 | 80 |

81 | 82 | 完成度を高めるため、積極的に意見を募っています🙏🏻 83 | 84 |

85 | 86 |

関連

87 | 88 | 101 | 102 |

参考資料

103 | 104 | 117 |
118 |
119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | :root { 71 | font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', 72 | 'Hiragino Sans', Meiryo, sans-serif; 73 | } 74 | 75 | * { 76 | @apply border-border; 77 | } 78 | body { 79 | @apply bg-background text-foreground; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 15 | 17 | 19 | 21 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from '@/components/ui/footer'; 2 | import { AppConfig } from '@/lib/config'; 3 | import type { Metadata } from 'next'; 4 | import './globals.css'; 5 | 6 | export const metadata: Metadata = { 7 | metadataBase: 8 | process.env.NODE_ENV === 'production' 9 | ? new URL('https://dninomiya.github.io') 10 | : new URL(`http://localhost:${process.env.PORT || 3000}`), 11 | title: AppConfig.title, 12 | description: AppConfig.description, 13 | openGraph: { 14 | title: AppConfig.title, 15 | description: AppConfig.description, 16 | }, 17 | twitter: { 18 | card: 'summary_large_image', 19 | }, 20 | }; 21 | 22 | export default function RootLayout({ 23 | children, 24 | }: { 25 | children: React.ReactNode; 26 | }) { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 |
{children}
34 |