カレンダーのUIは、週/日/月の表示、ドラッグでの予定作成、重なった予定の横並びと要求が多く、つい既製ライブラリに任せてその中身を読まないまま進みがちだ。next.js カレンダー ui を自前で組むとどこまで書くのか。kargulstudio/calender-app-design は、Figmaの「Dashboard – calendar」デザインから起こしたpeople-opsワークスペースの試作で、その実装が丸ごと読める。★289・MIT・Next.js 16/React 19。本記事はソースを実測し、ビルドとテストまで確認した。

TWO WAYS二つの道

カレンダーUIを自前で組むと何が手に入るか

既製を組み込む

カレンダーライブラリ / SaaS

  • 機能は揃う見た目をデザインに寄せにくい
  • 挙動はブラックボックスドラッグ・重なりの実装は読めない

この試作

Next.js 16+React 19+shadcn/ui

  • Figma変数→Tailwindトークンデザインに1対1で合わせる
  • 挙動が全部ソースにあるドラッグ・重なり整列・⌘Kを読める
出典: README(2026-10-27時点)/AI機能は無い

30秒でわかるcalender-app-design(2026-10-27時点)

  • ・正体:Figmaデザインから起こしたカレンダー/休暇申請UIのフロントエンド試作。バックエンド無し、Vercelで公開
  • ・スタック:Next.js 16(App Router)・React 19・TypeScript・Tailwind v4・shadcn/ui(Radix)・Zustand・cmdk
  • ・何が読めるか:ドラッグ作成/移動/リサイズ、重なりイベントの横並び整列、⌘Kパレット、localStorage永続の実装
  • ・実測:本番ビルド成功(静的18ページ)/vitest 15件通過。dev起動とライブURLの実挙動は未検証
  • ・注意:AI機能は無い。製品ではなく実装の参照(v0.1.0・リリースタグ無し)

本サイトは普段AI関連OSSとセキュリティを扱うが、これはAI機能ゼロの題材で、読者の明示依頼により実装リファレンスとして取り上げる。無理にAIの角度は付けない。shadcn/uiやTailwindのトークンでFigmaのデザインをUIに写し取る過程が、デザインシステムを自前のコードに落とす具体例として読める。

calender-app-designとは|バックエンド無しのデザイン試作

READMEによれば、これは「Kargul Workspace」というpeople-opsワークスペースのデザイン試作で、既定ルートの /time-off(休暇申請ページ)がFigmaデザインを忠実に再現し、サイドバーの他ページも同じ視覚言語で組まれている。すべてブラウザ内で完結し、サーバは無い。データは localStorage に保存される。

読者の3つの問いに沿って整理する。何ができるかは、週/日/月のカレンダー、ドラッグでの予定操作、休暇申請と残日数計算、⌘K検索。何を解決するかは、Figmaのデザインを、既製カレンダーに寄せ込むのではなく現行スタックで1対1に実装する手本を示すこと。何を代替するかは、特定の製品を置き換えるものではなく、FullCalendarなどのライブラリに任せていた挙動を自分で書くとどうなるかを見せる参照実装という位置づけだ。

package.json のパッケージ名は calender-app(リポジトリ名ともに calendar の綴り違い)、バージョンは 0.1.0、git tag は0件。現役の製品としてではなく、コードを読む/写経する前提で見るのが妥当だ。

仕組み:ポインタの動きが15分刻みの予定になるまで

タイムグリッドのドラッグは src/components/calendar/time-grid.tsx が担う。空いた場所でのドラッグは新規作成、予定本体のドラッグは移動、下端(data-resize)のドラッグはリサイズ——の3モードを Drag 型で切り替える。ポインタ座標はまず pxToMinutes() で分に変換され、snap() で15分(SNAP_MINUTES)刻みに丸められ、clamp() で0〜1440分と最小長(MIN_EVENT_MINUTES)に収める。確定時に moveEvent() などストアを更新する。

DRAG PIPELINEドラッグの通り道

ポインタの動きが15分刻みの予定になるまで

  1. ポインタ座標

    onPointerDown/Move

  2. 分に換算

    pxToMinutes()

  3. 15分スナップ

    snap()

  4. ストア更新

    moveEvent()

  5. 再レイアウト

    layoutDay()

出典: src/components/calendar/time-grid.ts・layout.ts(2026-10-27時点)

真価は重なりの整列だ。layout.ts の layoutDay() は、時間が重なる予定を推移的なクラスタにまとめ、各クラスタを衝突しない最小の列数に割り当てる。各列の終端時刻を保持する columnEnds を走査し、開始時刻以前に終わる空き列を探して再利用、無ければ列を増やす。これで3件が重なれば2〜3列に横並びする。ドラッグ中はプレビューを一時的に差し込んで layoutDay() を通し、レイアウトがライブに組み替わる。付属の layout.test.ts は、非重複は全幅・重複は列分割・15分スナップの3観点を検証する。

状態の永続:Zustand persist と localStorage

アプリの状態は src/stores/app-store.ts のZustandストアに集約され、persist ミドルウェアで calendar-app:state というキーにJSON保存される。実装には実務で効く工夫がある。

localStorage永続で踏みがちな3点への対処

  • ・容量超過:約5MBの上限に達すると setItem が例外を投げる。safeLocalStorage でtry/catchし、1度だけトーストで知らせてメモリ動作を続ける
  • ・ハイドレーション不一致:skipHydration: true でマウント後に復元し、サーバ描画(シード)との食い違いを避ける
  • ・スキーマ変更:version と migrate で旧データを移行(例:ワークスペース名の改称)。partialize で関数を除いて保存する

状態の永続を「persist を付けるだけ」で終えず、容量・描画・移行まで面倒を見ている点が、同じ構成でアプリを組むときの参照になる。

⌘Kパレットとキーボード操作

src/components/shell/global-search.tsx が cmdk の CommandDialog でコマンドパレットを組む。⌘Kで開き、ページ・予定・休暇申請・ドキュメントを横断検索し、CommandGroup で「アクション」「ページ」などに束ねる。検索結果の予定は日付と開始時刻でソートして出す。

キーボードは ⌘K 検索、N 新規予定、R 休暇申請、T 今日、←/→ 前後、D/W/M 表示切替、? ショートカット一覧——とデスクトップ操作が一通り揃う。カレンダーの表示状態(view・focus・ポップオーバー)は calendar-store.ts に分離され、スマホでは週表示を3日窓に縮める compact 分岐も持つ。

実測と採用判断:これは製品ではなく実装の参照

実際に動かした手順と結果は次のとおり。

git clone --depth 1 https://github.com/kargulstudio/calender-app-design
cd calender-app-design
npm install
npm run build   # 本番ビルド(静的18ページ生成を確認)
npm run test    # vitest: 4ファイル15件すべて通過

実測した結果(2026-10-27)

  • ・npm install 成功(699パッケージ)。npm run build 成功し、18の静的ページを生成。npm run test(vitest)は4ファイル15件すべて通過
  • ・npm run typecheck(素の tsc --noEmit)は LayoutProps 不明で失敗するが、これはNext.js 16が next build 時に生成するグローバル型が未生成のためで、ビルド内の型検査は通る

未検証の範囲

  • ・npm run dev(開発サーバ)とブラウザでのドラッグ・⌘Kの実挙動は未検証。挙動の記述はソース読解に基づく
  • ・公開中のライブURLでの実挙動は未検証

採る前に確かめる点を図にまとめる。

BEFORE YOU USE採る前に確かめる

これは製品ではなく実装の参照である

  1. 1.ライセンスはMIT

    改変・再配布できる(LICENSE実体で確認)

  2. 2.バックエンドは無い

    永続はブラウザのlocalStorageだけ

  3. 3.リリースタグ無し・v0.1.0

    APIの安定は保証されない。読む/写経する用途向き

出典: package.json・LICENSE・git tag(2026-10-27時点)
観点 calender-app-design 一般的なカレンダーライブラリ
位置づけ デザイン試作・実装の参照(v0.1.0) 汎用の再利用コンポーネント
ドラッグ/重なり整列 ソースで読める(time-grid.tsx・layout.ts) 内部実装(挙動は設定で調整)
永続 localStorageのみ(バックエンド無し) データ層は利用側が用意
見た目 特定Figmaデザインに密結合 テーマで調整
安定性 リリースタグ無し・APIの安定保証なし セマンティックバージョニング

結論として、本番にそのまま載せる製品ではなく、同種のUIを現行スタックで組むときに関数やコンポーネントを読んで写経する参照として価値がある。MITなので取り込みも問題ない。

まとめ

この記事の結論

  • ・向いている人:Next.js 16/React 19/Tailwind v4/shadcnでカレンダーや日時UIを自前実装する開発者。ドラッグ・重なり整列・localStorage永続の具体を読みたい人
  • ・待った方がよい人:すぐ使える完成品や、バックエンド連携・多機能を求める人。これは試作であり安定APIの保証は無い

参照ソース

・kargulstudio/calender-app-design(公式リポジトリ) — README・LICENSE・package.json・src/(2026-10-27時点、★289・MIT)
・Zustand persist middleware(公式ドキュメント) — version/migrate/partialize/skipHydrationの仕様
・cmdk(公式リポジトリ) — CommandDialog/CommandGroupの仕様