55 | {post.title} 56 |
57 |{post.description}
58 | 65 |├── .github
└── workflows
│ └── nextjs.yml
├── .gitignore
├── README.md
├── components.json
├── next.config.js
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── file.svg
├── globe.svg
├── next.svg
├── vercel.svg
└── window.svg
├── src
├── app
│ ├── blog
│ │ ├── NavTrigger.tsx
│ │ ├── [slug]
│ │ │ ├── TocTrigger.tsx
│ │ │ └── page.tsx
│ │ ├── _data
│ │ │ ├── author.ts
│ │ │ └── posts.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── chat
│ │ ├── ChatItem.tsx
│ │ ├── ChatList.tsx
│ │ ├── _data
│ │ │ ├── mock-chats.ts
│ │ │ ├── mock-messages.ts
│ │ │ └── mock-settings.ts
│ │ └── page.tsx
│ ├── dashboard
│ │ ├── _data
│ │ │ └── menu.ts
│ │ ├── page.tsx
│ │ └── triggers.tsx
│ ├── favicon.ico
│ ├── fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ └── workshop
│ │ ├── blog
│ │ ├── [slug]
│ │ │ └── page.tsx
│ │ ├── guide.md
│ │ └── page.tsx
│ │ ├── chat
│ │ ├── guide.md
│ │ └── page.tsx
│ │ └── dashboard
│ │ ├── guide.md
│ │ └── page.tsx
├── components
│ ├── blog
│ │ ├── AuthorProfile.tsx
│ │ ├── BlogFooter.tsx
│ │ ├── Markdown.tsx
│ │ ├── NewsletterForm.tsx
│ │ ├── PostCard.tsx
│ │ └── TableOfContents.tsx
│ ├── chat
│ │ ├── ChatItem.tsx
│ │ ├── ChatList.tsx
│ │ ├── ChatSetting.tsx
│ │ ├── Conversation.tsx
│ │ └── Input.tsx
│ ├── dashboard
│ │ ├── AppSwitcher.tsx
│ │ ├── CollapsibleMenu.tsx
│ │ ├── OrderStats.tsx
│ │ ├── OrderTable.tsx
│ │ ├── UserSetting.tsx
│ │ └── _data
│ │ │ ├── mock-orders.ts
│ │ │ └── mock-stats.ts
│ └── ui
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── screen-size-indicator.tsx
│ │ ├── scroll-area.tsx
│ │ ├── separator.tsx
│ │ ├── table.tsx
│ │ ├── textarea.tsx
│ │ └── tooltip.tsx
└── lib
│ └── utils.ts
├── tailwind.config.ts
└── tsconfig.json
/.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@v4
34 | - name: Detect package manager
35 | id: detect-package-manager
36 | run: |
37 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then
38 | echo "manager=yarn" >> $GITHUB_OUTPUT
39 | echo "command=install" >> $GITHUB_OUTPUT
40 | echo "runner=yarn" >> $GITHUB_OUTPUT
41 | exit 0
42 | elif [ -f "${{ github.workspace }}/package.json" ]; then
43 | echo "manager=npm" >> $GITHUB_OUTPUT
44 | echo "command=ci" >> $GITHUB_OUTPUT
45 | echo "runner=npx --no-install" >> $GITHUB_OUTPUT
46 | exit 0
47 | else
48 | echo "Unable to determine package manager"
49 | exit 1
50 | fi
51 | - name: Setup Node
52 | uses: actions/setup-node@v4
53 | with:
54 | node-version: "20"
55 | cache: ${{ steps.detect-package-manager.outputs.manager }}
56 | - name: Setup Pages
57 | uses: actions/configure-pages@v5
58 | with:
59 | # Automatically inject basePath in your Next.js configuration file and disable
60 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
61 | #
62 | # You may remove this line if you want to manage the configuration yourself.
63 | static_site_generator: next
64 | - name: Restore cache
65 | uses: actions/cache@v4
66 | with:
67 | path: |
68 | .next/cache
69 | # Generate a new cache whenever packages or source files change.
70 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
71 | # If source files changed but packages didn't, rebuild from a prior cache.
72 | restore-keys: |
73 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
74 | - name: Install dependencies
75 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
76 | - name: Build with Next.js
77 | run: ${{ steps.detect-package-manager.outputs.runner }} next build
78 | - name: Upload artifact
79 | uses: actions/upload-pages-artifact@v3
80 | with:
81 | path: ./out
82 |
83 | # Deployment job
84 | deploy:
85 | environment:
86 | name: github-pages
87 | url: ${{ steps.deployment.outputs.page_url }}
88 | runs-on: ubuntu-latest
89 | needs: build
90 | steps:
91 | - name: Deploy to GitHub Pages
92 | id: deployment
93 | uses: actions/deploy-pages@v4
94 |
--------------------------------------------------------------------------------
/.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.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 | # env files (can opt-in for committing if needed)
33 | .env*
34 |
35 | # vercel
36 | .vercel
37 |
38 | # typescript
39 | *.tsbuildinfo
40 | next-env.d.ts
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a repo for learning how to build layout with [Tailwind Jun Layout](https://tailwindcss-jun-layout.vercel.app/).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies and run the development server:
6 |
7 | ```bash
8 | npm install && npm run dev
9 | ```
10 |
11 | Open [http://localhost:3333](http://localhost:3333) with your browser to see the result.
12 |
13 | There are 3 layout practices in this repo:
14 |
15 | - Dashboard
16 | - Chat
17 | - Blog
18 |
19 | ## Dashboard layout
20 |
21 | Build a dashboard layout at `src/app/workshop/dashboard/page.tsx`.
22 |
23 | Visit a completed version url `/app/dashboard` as a reference.
24 |
25 | ## Chat
26 |
27 | Build a chat app layout at `src/app/workshop/chat/page.tsx`.
28 |
29 | Visit a completed version url `/app/chat` as a reference.
30 |
31 | ## Blog
32 |
33 | Build a chat app layout at `src/app/workshop/blog/page.tsx`.
34 |
35 | Visit a completed version url `/app/blog` as a reference.
36 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | output: "export",
4 | images: {
5 | unoptimized: true,
6 | },
7 | };
8 |
9 | module.exports = nextConfig;
10 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | output: "export",
5 | assetPrefix:
6 | process.env.NODE_ENV === "production"
7 | ? "/learn-tailwindcss-jun-layout"
8 | : "",
9 | basePath:
10 | process.env.NODE_ENV === "production"
11 | ? "/learn-tailwindcss-jun-layout"
12 | : "",
13 | };
14 |
15 | export default nextConfig;
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learn-tailwindcss-jun-layout",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3333",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-avatar": "1.1.1",
13 | "@radix-ui/react-dropdown-menu": "2.1.2",
14 | "@radix-ui/react-scroll-area": "1.2.1",
15 | "@radix-ui/react-separator": "1.1.0",
16 | "@radix-ui/react-slot": "1.1.0",
17 | "@radix-ui/react-tooltip": "1.1.4",
18 | "class-variance-authority": "0.7.0",
19 | "clsx": "2.1.1",
20 | "lucide-react": "0.460.0",
21 | "next": "15.0.3",
22 | "react": "18.3.1",
23 | "react-dom": "18.3.1",
24 | "react-markdown": "9.0.1",
25 | "tailwind-merge": "2.5.4",
26 | "tailwindcss-animate": "1.0.7",
27 | "tailwindcss-jun-layout": "0.6.3"
28 | },
29 | "devDependencies": {
30 | "@tailwindcss/typography": "0.5.15",
31 | "@types/node": "^20",
32 | "@types/react": "^18",
33 | "@types/react-dom": "^18",
34 | "postcss": "^8",
35 | "tailwindcss": "^3.4.1",
36 | "typescript": "^5"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/blog/NavTrigger.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Button } from "@/components/ui/button";
3 | import { MenuIcon } from "lucide-react";
4 | import { triggerEdgeDrawer } from "tailwindcss-jun-layout";
5 |
6 | export default function NavTrigger() {
7 | return (
8 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/blog/[slug]/TocTrigger.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Button } from "@/components/ui/button";
3 | import { List } from "lucide-react";
4 | import { triggerEdgeDrawerRight } from "tailwindcss-jun-layout";
5 |
6 | export default function TocTrigger() {
7 | return (
8 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/blog/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { posts, type Post } from "@/app/blog/_data/posts";
2 | import { notFound } from "next/navigation";
3 | import { Badge } from "@/components/ui/badge";
4 | import { Markdown } from "@/components/blog/Markdown";
5 | import { TableOfContents } from "@/components/blog/TableOfContents";
6 | import { menuGroups } from "@/app/dashboard/_data/menu";
7 | import TocTrigger from "./TocTrigger";
8 |
9 | interface BlogPostPageProps {
10 | params: Promise<{
11 | slug: string;
12 | }>;
13 | }
14 |
15 | export function generateStaticParams() {
16 | return posts.map((post) => ({
17 | slug: post.slug,
18 | }));
19 | }
20 |
21 | export default async function BlogPostPage({ params }: BlogPostPageProps) {
22 | const { slug } = await params;
23 | const post = posts.find((p: Post) => p.slug === slug);
24 |
25 | if (!post) {
26 | notFound();
27 | }
28 |
29 | return (
30 | <>
31 | {post.description}
40 |
41 |
55 | {post.title}
56 |
57 |