├── .env.example
├── .eslintrc.json
├── .github
├── CODEOWNERS
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── pull_request_template.md
├── .gitignore
├── .nvmrc
├── .prettierignore
├── LICENSE.md
├── README.md
├── codegen.yml
├── components.json
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier.config.js
├── public
├── demo.png
└── opengraph-image.png
├── src
├── app
│ ├── about
│ │ └── page.tsx
│ ├── blog
│ │ ├── [slug]
│ │ │ └── page.tsx
│ │ ├── page.tsx
│ │ └── preview
│ │ │ └── [id]
│ │ │ └── page.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── all-blog-posts-list.tsx
│ ├── analytics.tsx
│ ├── badge-list-item.tsx
│ ├── badge-list.tsx
│ ├── bio.tsx
│ ├── blog-post-list-item.tsx
│ ├── blog-post-list.tsx
│ ├── blog-tags-filter.tsx
│ ├── card-list-skeleton.tsx
│ ├── create-next-app.tsx
│ ├── filter.tsx
│ ├── footer.tsx
│ ├── header-nav.tsx
│ ├── header.tsx
│ ├── mdx.tsx
│ ├── mode-toggle.tsx
│ ├── paragraph-skeleton.tsx
│ ├── providers.tsx
│ ├── scripts.tsx
│ ├── search.tsx
│ ├── sort.tsx
│ ├── theme-provider.tsx
│ └── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── carousel.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── drawer.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── pagination.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── resizable.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── sonner.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
├── hashnode
│ ├── api-url.ts
│ ├── fragments
│ │ ├── Draft.graphql
│ │ ├── PageInfo.graphql
│ │ ├── Post.graphql
│ │ └── Publication.graphql
│ ├── generated
│ │ ├── graphql.ts
│ │ └── schema.graphql
│ └── queries
│ │ ├── GetAuthorUsername.graphql
│ │ ├── GetDraftById.graphql
│ │ ├── GetPostBySlug.graphql
│ │ ├── GetPosts.graphql
│ │ ├── GetPublication.graphql
│ │ └── GetUser.graphql
├── lib
│ ├── create-post-json-ld.ts
│ ├── create-publication-json-ld.ts
│ └── utils.ts
├── server
│ ├── get-all-blog-posts.ts
│ ├── get-all-blog-tags.ts
│ ├── get-author-username.ts
│ ├── get-blog-post-draft.ts
│ ├── get-blog-post.ts
│ ├── get-blog-posts.ts
│ ├── get-publication.ts
│ └── get-user.ts
└── types
│ └── sort-types.ts
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | HASHNODE_HOST=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @alexkates
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to hashnode-next
2 |
3 | Thank you for considering contributing to hashnode-next. Your contributions are highly valued and appreciated.
4 |
5 | ## The basics
6 |
7 | - Before submitting a new issue or PR, check if it already exists in [issues](https://github.com/alexkates/hashnode-next/issues) or [PRs](https://github.com/alexkates/hashnode-next/pulls).
8 | - If there isn't an issue please _create one_ before any development begins
9 | - If you're working on an issue, please _comment_ on it so that others know you're working on it
10 |
11 | ## Developing
12 |
13 | The development branch is `main`. This is the branch that all pull
14 | requests should be made against.
15 |
16 | To develop locally:
17 |
18 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your
19 | own GitHub account and then
20 | [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
21 | 2. Create a new branch:
22 |
23 | ```sh
24 | git switch -c MY_BRANCH_NAME
25 | ```
26 |
27 | ## Installing
28 |
29 | hashnode-next uses [NVM](https://github.com/nvm-sh/nvm/blob/master/README.md) and [PNPM](https://pnpm.io/) for package management.
30 |
31 | To set the correct version of PNPM, run `nvm use` from the root. There is a `.nvmrc` file that controls the correct node version.
32 |
33 | ## Installing dependencies
34 |
35 | ```bash
36 | pnpm install
37 | ```
38 |
39 | ## Hashnode GQL codegen
40 |
41 | If you need to add or update the Hashnode GQL schema, run the following command to generate types and gql documents.
42 |
43 | ```bash
44 | pnpm codegen
45 | ```
46 |
47 | ## Building
48 |
49 | ```bash
50 | pnpm build
51 | ```
52 |
53 | ## Linting
54 |
55 | ```sh
56 | pnpm format
57 | pnpm lint
58 | ```
59 |
60 | If you get errors, be sure to fix them before committing.
61 |
62 | ## Making a Pull Request
63 |
64 | - Be sure to [check the "Allow edits from maintainers" option](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) while creating your PR.
65 | - If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. See more about [Linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
66 | - Be sure to fill the PR Template accordingly.
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## What does this PR do?
2 |
3 | Fixes # (issue)
4 |
5 | _If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists_
6 |
7 | ## Type of change
8 |
9 |
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] Chore (refactoring code, technical debt, workflow improvements)
13 | - [ ] Enhancement (small improvements)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
16 |
17 | ## How should this be tested?
18 |
19 | - Do a thing, then do another thing
20 | - Confirm that this thing happened
21 |
22 | ### Things to remember
23 |
24 | - [ ] Filled out the "How to test" section in this PR
25 | - [ ] Read [Contributing Guide](./CONTRIBUTING.md)
26 | - [ ] Ran `pnpm build`
27 | - [ ] Ran `pnpm format`
28 | - [ ] Ran `pnpm lint`
29 | - [ ] Checked for warnings, there are none
30 | - [ ] Removed all `console.logs`
31 | - [ ] Merged the latest changes from main onto my branch with `git pull origin main`
32 |
--------------------------------------------------------------------------------
/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | pnpm-lock.yaml
2 | node_modules
3 | src/hashnode/generated
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 hashnode-next
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 |
13 |
14 | ## Getting Started
15 |
16 | ```console
17 | npx create-next-app -e https://github.com/alexkates/hashnode-next
18 | ```
19 |
20 | ## Environment Variables
21 |
22 | [.env.example](.env.example) contains all the environment variables you need to run a copy of hashnode-next for your headless blog. You can simply copy this file and rename it to `.env.local` to get started.
23 |
24 | ## Contributing
25 |
26 | Please read through our [contributing guide](.github/CONTRIBUTING.md) before starting any work.
27 |
28 | 1. Fork the Project
29 | 2. Create your Feature Branch (`git checkout -b feature/my-amazing-feature`)
30 | 3. Commit your Changes (`git commit -am 'Add some my-amazing-feature'`)
31 | 4. Push to the Branch (`git push origin feature/my-amazing-feature`)
32 | 5. Open a Pull Request
33 |
34 | ## Authors
35 |
36 |
37 |
38 |
39 |
40 | ## Stats
41 |
42 | 
43 |
44 | ## License
45 |
46 | Distributed under the MIT License. See the [license](LICENSE.md) for more information.
47 |
--------------------------------------------------------------------------------
/codegen.yml:
--------------------------------------------------------------------------------
1 | schema: https://gql.hashnode.com
2 | documents: "./src/**/hashnode/**/*.graphql"
3 | generates:
4 | ./src/hashnode/generated/schema.graphql:
5 | plugins:
6 | - schema-ast
7 | config:
8 | includeDirectives: true
9 | ./src/hashnode/generated/graphql.ts:
10 | plugins:
11 | - typescript
12 | - typescript-operations
13 | - typed-document-node
14 | config:
15 | scalars:
16 | Date: string
17 | DateTime: string
18 | ObjectId: string
19 | JSONObject: Record
20 | Decimal: string
21 | CurrencyCode: string
22 | ImageContentType: string
23 | ImageUrl: string
24 |
--------------------------------------------------------------------------------
/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": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const HASHNODE_ANALYTICS_BASE_URL = "https://hn-ping2.hashnode.com";
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = {
5 | async rewrites() {
6 | return [
7 | {
8 | source: "/ping/data-event",
9 | destination: `${HASHNODE_ANALYTICS_BASE_URL}/api/data-event`,
10 | },
11 | {
12 | source: "/ping/view",
13 | destination: `${HASHNODE_ANALYTICS_BASE_URL}/api/view`,
14 | },
15 | ];
16 | },
17 | images: {
18 | remotePatterns: [
19 | {
20 | protocol: "https",
21 | hostname: "cdn.hashnode.com",
22 | },
23 | ],
24 | },
25 | };
26 |
27 | module.exports = nextConfig;
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hashnode-next",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "format": "prettier --write .",
11 | "codegen": "graphql-codegen --config codegen.yml"
12 | },
13 | "dependencies": {
14 | "@graphql-typed-document-node/core": "^3.2.0",
15 | "@hookform/resolvers": "^3.3.4",
16 | "@radix-ui/react-accordion": "^1.1.2",
17 | "@radix-ui/react-alert-dialog": "^1.0.5",
18 | "@radix-ui/react-aspect-ratio": "^1.0.3",
19 | "@radix-ui/react-avatar": "^1.0.4",
20 | "@radix-ui/react-checkbox": "^1.0.4",
21 | "@radix-ui/react-collapsible": "^1.0.3",
22 | "@radix-ui/react-context-menu": "^2.1.5",
23 | "@radix-ui/react-dialog": "^1.0.5",
24 | "@radix-ui/react-dropdown-menu": "^2.0.6",
25 | "@radix-ui/react-hover-card": "^1.0.7",
26 | "@radix-ui/react-icons": "^1.3.0",
27 | "@radix-ui/react-label": "^2.0.2",
28 | "@radix-ui/react-menubar": "^1.0.4",
29 | "@radix-ui/react-navigation-menu": "^1.1.4",
30 | "@radix-ui/react-popover": "^1.0.7",
31 | "@radix-ui/react-progress": "^1.0.3",
32 | "@radix-ui/react-radio-group": "^1.1.3",
33 | "@radix-ui/react-scroll-area": "^1.0.5",
34 | "@radix-ui/react-select": "^2.0.0",
35 | "@radix-ui/react-separator": "^1.0.3",
36 | "@radix-ui/react-slider": "^1.1.2",
37 | "@radix-ui/react-slot": "^1.0.2",
38 | "@radix-ui/react-switch": "^1.0.3",
39 | "@radix-ui/react-tabs": "^1.0.4",
40 | "@radix-ui/react-toast": "^1.1.5",
41 | "@radix-ui/react-toggle": "^1.0.3",
42 | "@radix-ui/react-toggle-group": "^1.0.4",
43 | "@radix-ui/react-tooltip": "^1.0.7",
44 | "@vercel/analytics": "^1.1.4",
45 | "class-variance-authority": "^0.7.0",
46 | "clsx": "^2.1.0",
47 | "cmdk": "^0.2.1",
48 | "date-fns": "^3.3.1",
49 | "embla-carousel-react": "8.0.0-rc21",
50 | "framer-motion": "^11.0.5",
51 | "graphql-request": "^6.1.0",
52 | "js-cookie": "^3.0.5",
53 | "lucide-react": "^0.316.0",
54 | "next": "14.1.0",
55 | "next-themes": "^0.2.1",
56 | "react": "^18.2.0",
57 | "react-day-picker": "^8.10.0",
58 | "react-dom": "^18.2.0",
59 | "react-hook-form": "^7.50.1",
60 | "react-markdown": "^9.0.1",
61 | "react-resizable-panels": "^1.0.10",
62 | "react-tweet": "^3.2.0",
63 | "react-wrap-balancer": "^1.1.0",
64 | "rehype-autolink-headings": "^7.1.0",
65 | "rehype-highlight": "^7.0.0",
66 | "rehype-raw": "^7.0.0",
67 | "rehype-slug": "^6.0.0",
68 | "remark-gfm": "^4.0.0",
69 | "sonner": "^1.4.0",
70 | "tailwind-merge": "^2.2.1",
71 | "use-debounce": "^10.0.0",
72 | "uuid": "^9.0.1",
73 | "vaul": "^0.8.9",
74 | "zod": "^3.22.4"
75 | },
76 | "devDependencies": {
77 | "@graphql-codegen/cli": "^5.0.2",
78 | "@graphql-codegen/typed-document-node": "^5.0.4",
79 | "@graphql-codegen/typescript": "^4.0.4",
80 | "@graphql-codegen/typescript-operations": "^4.1.2",
81 | "@tailwindcss/typography": "^0.5.10",
82 | "@types/js-cookie": "^3.0.6",
83 | "@types/node": "^20.11.19",
84 | "@types/react": "^18.2.55",
85 | "@types/react-dom": "^18.2.19",
86 | "@types/uuid": "^9.0.8",
87 | "autoprefixer": "^10.4.17",
88 | "eslint": "^8.56.0",
89 | "eslint-config-next": "14.1.0",
90 | "postcss": "^8.4.35",
91 | "prettier-plugin-organize-imports": "^3.2.4",
92 | "prettier-plugin-tailwindcss": "^0.5.11",
93 | "tailwindcss": "^3.4.1",
94 | "tailwindcss-animate": "^1.0.7",
95 | "tailwindcss-animation-delay": "^1.2.0",
96 | "typescript": "^5.3.3"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | // prettier.config.js, .prettierrc.js, prettier.config.mjs, or .prettierrc.mjs
2 |
3 | /** @type {import("prettier").Config} */
4 | const config = {
5 | printWidth: 150,
6 | plugins: ["prettier-plugin-tailwindcss", "prettier-plugin-organize-imports"],
7 | };
8 |
9 | module.exports = config;
10 |
--------------------------------------------------------------------------------
/public/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexkates/hashnode-next/5b0bc6249ccb5c44bebb4c990c26a4056648a34f/public/demo.png
--------------------------------------------------------------------------------
/public/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexkates/hashnode-next/5b0bc6249ccb5c44bebb4c990c26a4056648a34f/public/opengraph-image.png
--------------------------------------------------------------------------------
/src/app/about/page.tsx:
--------------------------------------------------------------------------------
1 | import BadgeList from "@/components/badge-list";
2 | import Bio from "@/components/bio";
3 | import CardListSkeleton from "@/components/card-list-skeleton";
4 | import ParagraphSkeleton from "@/components/paragraph-skeleton";
5 | import { cn, fadeIn } from "@/lib/utils";
6 | import { Suspense } from "react";
7 |
8 | export default async function Home() {
9 | return (
10 |
11 |
16 |
17 | }>
18 | Here are some Hashnode badges that I've earned
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/blog/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import Analytics from "@/components/analytics";
2 | import { Mdx } from "@/components/mdx";
3 | import createPostJsonLd from "@/lib/create-post-json-ld";
4 | import { cn, fadeIn } from "@/lib/utils";
5 | import getBlogPost from "@/server/get-blog-post";
6 | import getPublication from "@/server/get-publication";
7 | import { Metadata } from "next/types";
8 |
9 | type Props = {
10 | params: {
11 | slug: string;
12 | };
13 | };
14 |
15 | export async function generateMetadata({ params }: Props) {
16 | const post = await getBlogPost(params);
17 |
18 | const title = post?.seo?.title || post?.title;
19 | const canonicalUrl = post?.canonicalUrl;
20 | const description = post?.seo?.description || post?.subtitle || post?.title;
21 | const images = post?.coverImage?.url;
22 |
23 | const metadata: Metadata = {
24 | title,
25 | description,
26 | alternates: {
27 | canonical: canonicalUrl,
28 | },
29 | openGraph: {
30 | title,
31 | description,
32 | type: "article",
33 | siteName: "Alex Kates | Blog",
34 | images,
35 | },
36 | twitter: {
37 | card: "summary_large_image",
38 | title,
39 | description,
40 | images,
41 | creator: "@thealexkates",
42 | },
43 | };
44 |
45 | return metadata;
46 | }
47 |
48 | export default async function Page({ params }: Props) {
49 | const post = await getBlogPost(params);
50 | const publication = await getPublication();
51 |
52 | if (!post) {
53 | return null;
54 | }
55 |
56 | const jsonLd = createPostJsonLd(publication, post);
57 |
58 | const {
59 | publishedAt,
60 | readTimeInMinutes,
61 | title,
62 | views,
63 | id,
64 | content: { markdown },
65 | } = post;
66 |
67 | return (
68 | <>
69 |
70 | {title}
71 |
72 | {new Date(publishedAt).toLocaleDateString()} • {views} views • {readTimeInMinutes} min read
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | >
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/app/blog/page.tsx:
--------------------------------------------------------------------------------
1 | import AllBlogPostsList from "@/components/all-blog-posts-list";
2 | import BlogTagsFilter from "@/components/blog-tags-filter";
3 | import ParagraphSkeleton from "@/components/paragraph-skeleton";
4 | import Search from "@/components/search";
5 | import Sort from "@/components/sort";
6 | import { cn, fadeIn } from "@/lib/utils";
7 | import { Suspense } from "react";
8 |
9 | export default async function Page({
10 | searchParams,
11 | }: {
12 | searchParams?: {
13 | query?: string;
14 | sort?: string;
15 | tags?: string;
16 | };
17 | }) {
18 | const query = searchParams?.query || "";
19 | const sort = searchParams?.sort || "date";
20 | const tags = searchParams?.tags || "";
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/blog/preview/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import { Mdx } from "@/components/mdx";
2 | import { cn, fadeIn } from "@/lib/utils";
3 | import getBlogPostDraft from "@/server/get-blog-post-draft";
4 | import { Metadata } from "next/types";
5 |
6 | type Props = {
7 | params: {
8 | id: string;
9 | };
10 | };
11 |
12 | export async function generateMetadata({ params }: Props) {
13 | const post = await getBlogPostDraft(params);
14 | const title = post?.seo?.title || post?.title || "";
15 | const canonicalUrl = post?.canonicalUrl;
16 | const description = post?.seo?.description || post?.subtitle || post?.title || "";
17 | const images = post?.coverImage?.url;
18 |
19 | const metadata: Metadata = {
20 | title,
21 | description,
22 | alternates: {
23 | canonical: canonicalUrl,
24 | },
25 | openGraph: {
26 | title,
27 | description,
28 | type: "article",
29 | siteName: "Alex Kates | Blog",
30 | images,
31 | },
32 | twitter: {
33 | card: "summary_large_image",
34 | title,
35 | description,
36 | images,
37 | creator: "@alexkate",
38 | },
39 | };
40 |
41 | return metadata;
42 | }
43 |
44 | export default async function Page({ params }: Props) {
45 | const draft = await getBlogPostDraft(params);
46 |
47 | if (!draft) {
48 | return null;
49 | }
50 |
51 | const title = draft.title || "";
52 | const markdown = draft.content?.markdown || "";
53 |
54 | const { readTimeInMinutes, coverImage } = draft;
55 |
56 | return (
57 | <>
58 |
59 | {title}
60 |
61 | {new Date().toLocaleDateString()} • 0 views • {readTimeInMinutes} min read • 0 likes
62 |
63 |
64 |
65 |
66 |
67 | >
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexkates/hashnode-next/5b0bc6249ccb5c44bebb4c990c26a4056648a34f/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/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: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 5.9% 10%;
26 | --radius: 0.5rem;
27 | }
28 |
29 | .dark {
30 | --background: 240 10% 3.9%;
31 | --foreground: 0 0% 98%;
32 | --card: 240 10% 3.9%;
33 | --card-foreground: 0 0% 98%;
34 | --popover: 240 10% 3.9%;
35 | --popover-foreground: 0 0% 98%;
36 | --primary: 0 0% 98%;
37 | --primary-foreground: 240 5.9% 10%;
38 | --secondary: 240 3.7% 15.9%;
39 | --secondary-foreground: 0 0% 98%;
40 | --muted: 240 3.7% 15.9%;
41 | --muted-foreground: 240 5% 64.9%;
42 | --accent: 240 3.7% 15.9%;
43 | --accent-foreground: 0 0% 98%;
44 | --destructive: 0 62.8% 30.6%;
45 | --destructive-foreground: 0 0% 98%;
46 | --border: 240 3.7% 15.9%;
47 | --input: 240 3.7% 15.9%;
48 | --ring: 240 4.9% 83.9%;
49 | }
50 | }
51 |
52 | @layer base {
53 | * {
54 | @apply border-border;
55 | }
56 | body {
57 | @apply bg-background text-foreground;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import Footer from "@/components/footer";
2 | import Header from "@/components/header";
3 | import Providers from "@/components/providers";
4 | import Scripts from "@/components/scripts";
5 | import { Toaster } from "@/components/ui/toaster";
6 | import { validateEnvVars } from "@/lib/utils";
7 | import { Inter } from "next/font/google";
8 | import { Metadata } from "next/types";
9 | import "./globals.css";
10 |
11 | const inter = Inter({ subsets: ["latin"] });
12 |
13 | export async function generateMetadata() {
14 | const title = "hashnode-next";
15 | const description = "The fastest way to go headless with Hashnode";
16 | const images = "https://hashnode-next.dev/opengraph-image.png";
17 | const url = "https://hashnode-next.dev";
18 |
19 | const metadata: Metadata = {
20 | metadataBase: new URL("https://hashnode-next.dev"),
21 | title,
22 | description,
23 | openGraph: {
24 | title,
25 | description,
26 | type: "website",
27 | siteName: title,
28 | images,
29 | url,
30 | },
31 | twitter: {
32 | card: "summary_large_image",
33 | title,
34 | description,
35 | images,
36 | creator: "@thealexkates",
37 | site: "@thealexkates",
38 | },
39 | };
40 |
41 | return metadata;
42 | }
43 |
44 | export default function RootLayout({
45 | children,
46 | }: Readonly<{
47 | children: React.ReactNode;
48 | }>) {
49 | validateEnvVars();
50 |
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 | {children}
59 |
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import CreateNextApp from "@/components/create-next-app";
2 | import { Button } from "@/components/ui/button";
3 | import { cn, fadeIn } from "@/lib/utils";
4 | import { GitHubLogoIcon, VercelLogoIcon } from "@radix-ui/react-icons";
5 | import Link from "next/link";
6 |
7 | export default async function Page() {
8 | return (
9 |
10 |
11 | hashnode-next
12 | The fastest way to go headless with Hashnode
13 |
14 |
15 |
16 | Beautifully simple Hashnode starter-kit
17 |
18 | powered by
19 |
20 | Next.js
21 |
22 | and
23 |
24 | shadcn/ui
25 |
26 |
27 |
28 |
29 |
30 |
38 |
39 |
44 |
45 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/all-blog-posts-list.tsx:
--------------------------------------------------------------------------------
1 | import { createPublicationJsonLd } from "@/lib/create-publication-json-ld";
2 | import getAllBlogPosts from "@/server/get-all-blog-posts";
3 | import getPublication from "@/server/get-publication";
4 | import BlogPostList from "./blog-post-list";
5 |
6 | type Props = {
7 | query?: string;
8 | sort?: string;
9 | tags?: string;
10 | };
11 |
12 | async function AllBlogPostsList({ query, sort, tags }: Props) {
13 | const [posts, publication] = await Promise.all([getAllBlogPosts(), getPublication()]);
14 | const publicationJsonLd = createPublicationJsonLd(publication);
15 |
16 | return (
17 | <>
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | export default AllBlogPostsList;
25 |
--------------------------------------------------------------------------------
/src/components/analytics.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Cookies from "js-cookie";
4 | import { useEffect } from "react";
5 | import { v4 } from "uuid";
6 |
7 | type Props = {
8 | publicationId: string;
9 | postId: string;
10 | };
11 |
12 | export const GA_TRACKING_ID = "G-72XG3F8LNJ"; // This is Hashnode's GA tracking ID
13 |
14 | export default function Analytics({ publicationId, postId }: Props) {
15 | useEffect(() => {
16 | if (!publicationId || !postId) return;
17 |
18 | const _sendViewsToHashnodeInternalAnalytics = async () => {
19 | const event: Record = {
20 | event_type: "pageview",
21 | time: new Date().getTime(),
22 | event_properties: {
23 | hostname: window.location.hostname,
24 | url: window.location.pathname,
25 | eventType: "pageview",
26 | publicationId: publicationId,
27 | dateAdded: new Date().getTime(),
28 | referrer: window.document.referrer,
29 | },
30 | };
31 |
32 | let deviceId = Cookies.get("__amplitudeDeviceID");
33 | if (!deviceId) {
34 | deviceId = v4();
35 | Cookies.set("__amplitudeDeviceID", deviceId, {
36 | expires: 365 * 2,
37 | });
38 | }
39 |
40 | event["device_id"] = deviceId;
41 |
42 | await fetch(`/ping/data-event`, {
43 | method: "POST",
44 | headers: {
45 | "Content-Type": "application/json",
46 | },
47 | body: JSON.stringify({ events: [event] }),
48 | });
49 | };
50 |
51 | const _sendViewsToHashnodeAnalyticsDashboard = async () => {
52 | const LOCATION = window.location;
53 | const NAVIGATOR = window.navigator;
54 | const currentFullURL = LOCATION.protocol + "//" + LOCATION.hostname + LOCATION.pathname + LOCATION.search + LOCATION.hash;
55 |
56 | const query = new URL(currentFullURL).searchParams;
57 |
58 | const utm_id = query.get("utm_id");
59 | const utm_campaign = query.get("utm_campaign");
60 | const utm_source = query.get("utm_source");
61 | const utm_medium = query.get("utm_medium");
62 | const utm_term = query.get("utm_term");
63 | const utm_content = query.get("utm_content");
64 |
65 | let referrer = document.referrer || "";
66 | if (referrer.indexOf(window.location.hostname) !== -1) {
67 | referrer = "";
68 | }
69 |
70 | const data = {
71 | publicationId,
72 | postId,
73 | timestamp: Date.now(),
74 | url: currentFullURL,
75 | referrer: referrer,
76 | title: document.title,
77 | charset: document.characterSet || document.charset,
78 | lang: NAVIGATOR.language,
79 | userAgent: NAVIGATOR.userAgent,
80 | historyLength: window.history.length,
81 | timezoneOffset: new Date().getTimezoneOffset(),
82 | utm_id,
83 | utm_campaign,
84 | utm_source,
85 | utm_medium,
86 | utm_term,
87 | utm_content,
88 | };
89 |
90 | fetch(`/ping/view`, {
91 | method: "POST",
92 | headers: {
93 | "Content-Type": "application/json",
94 | },
95 | body: JSON.stringify({ data }),
96 | });
97 | };
98 |
99 | const _sendPageViewsToHashnodeGoogleAnalytics = () => {
100 | // @ts-ignore
101 | if (!window.gtag) return;
102 | // @ts-ignore
103 | window.gtag("config", GA_TRACKING_ID, {
104 | transport_url: "https://ping.hashnode.com",
105 | first_party_collection: true,
106 | });
107 | };
108 |
109 | _sendPageViewsToHashnodeGoogleAnalytics();
110 | _sendViewsToHashnodeInternalAnalytics();
111 | _sendViewsToHashnodeAnalyticsDashboard();
112 | }, [postId, publicationId]);
113 |
114 | return null;
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/badge-list-item.tsx:
--------------------------------------------------------------------------------
1 | import { Badge } from "@/hashnode/generated/graphql";
2 | import Image from "next/image";
3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
4 |
5 | type Props = {
6 | badge: Badge;
7 | };
8 |
9 | export default function BadgeListItem({ badge }: Props) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {badge.name}
18 | {new Date(badge.dateAssigned!).toLocaleDateString()}
19 |
20 |
21 | {badge.description}
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/badge-list.tsx:
--------------------------------------------------------------------------------
1 | import getUser from "@/server/get-user";
2 | import BadgeListItem from "./badge-list-item";
3 |
4 | export default async function BadgeList() {
5 | const { badges } = await getUser();
6 |
7 | return (
8 |
9 | {badges.map((badge) => (
10 |
11 | ))}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/bio.tsx:
--------------------------------------------------------------------------------
1 | import getUser from "@/server/get-user";
2 | import { Mdx } from "./mdx";
3 |
4 | export default async function bio() {
5 | const { bio } = await getUser();
6 |
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/blog-post-list-item.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from "@/hashnode/generated/graphql";
2 | import Link from "next/link";
3 | import { Badge } from "./ui/badge";
4 |
5 | type Props = {
6 | post: Post;
7 | };
8 |
9 | export default function BlogPostListItem({ post }: Props) {
10 | return (
11 |
12 |
13 |
14 |
15 |
{post.title}
16 |
17 |
18 | {new Date(post.publishedAt).toLocaleDateString()}
19 | •
20 | {post.views.toLocaleString()} views
21 | •
22 | {post.readTimeInMinutes} min read
23 | •
24 | {post.reactionCount} likes
25 |
26 |
27 |
{post.tags?.map((tag) => {tag.name.toLocaleLowerCase()})}
28 |
{post.brief}
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/blog-post-list.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from "@/hashnode/generated/graphql";
2 | import { SortTypes } from "@/types/sort-types";
3 | import BlogPostListItem from "./blog-post-list-item";
4 |
5 | type Props = {
6 | posts: Post[];
7 | query?: string;
8 | sort?: string;
9 | tags?: string;
10 | };
11 |
12 | function BlogPostList({ posts, query = "", sort = "", tags = "" }: Props) {
13 | const sortedPosts = posts.sort((a, b) => {
14 | if (sort === SortTypes.Date) {
15 | return new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime();
16 | } else if (sort === SortTypes.Views) {
17 | return b.views - a.views;
18 | } else if (sort === SortTypes.Likes) {
19 | return b.reactionCount - a.reactionCount;
20 | }
21 |
22 | return 0;
23 | });
24 |
25 | const tagsArray = tags?.split(",").filter((t) => t !== "");
26 |
27 | return (
28 |
29 | {sortedPosts
30 | .filter((post) => {
31 | const isMatchingQuery = post.content.text?.toLowerCase().includes(query?.toLowerCase() ?? "");
32 | const isMatchingTags = tagsArray?.length === 0 || tagsArray?.some((tag) => post.tags?.map((t) => t.name.toLowerCase()).includes(tag));
33 | return isMatchingQuery && isMatchingTags;
34 | })
35 | .map((post) => (
36 |
37 | ))}
38 |
39 | );
40 | }
41 |
42 | export default BlogPostList;
43 |
--------------------------------------------------------------------------------
/src/components/blog-tags-filter.tsx:
--------------------------------------------------------------------------------
1 | import getAllBlogTags from "@/server/get-all-blog-tags";
2 | import Filter from "./filter";
3 |
4 | async function BlogTagsFilter() {
5 | const allTags = await getAllBlogTags();
6 |
7 | return ;
8 | }
9 |
10 | export default BlogTagsFilter;
11 |
--------------------------------------------------------------------------------
/src/components/card-list-skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton } from "./ui/skeleton";
2 |
3 | function CardListSkeleton() {
4 | return (
5 |
6 | {Array.from({ length: 6 }).map((_, i) => (
7 | -
8 |
9 |
10 |
11 |
12 |
13 |
14 | ))}
15 |
16 | );
17 | }
18 |
19 | export default CardListSkeleton;
20 |
--------------------------------------------------------------------------------
/src/components/create-next-app.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CopyIcon } from "@radix-ui/react-icons";
4 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
5 | import { useToast } from "./ui/use-toast";
6 |
7 | const githubUrl = "https://github.com/alexkates/hashnode-next";
8 |
9 | export default function CreateNextApp() {
10 | return (
11 |
12 |
13 | npx create-next-app -e {githubUrl}
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | const commands = [
21 | { name: "npm", command: `npm create-next-app -e ${githubUrl}` },
22 | { name: "yarn", command: `yarn create next-app -e ${githubUrl}` },
23 | { name: "pnpm", command: `pnpm create next-app -e ${githubUrl}` },
24 | { name: "bun", command: `bunx create-next-app -e ${githubUrl}` },
25 | ];
26 |
27 | function PackageManagerCommands() {
28 | const { toast } = useToast();
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | {commands.map(({ name, command }) => (
37 | {
40 | await handleCopyClick({ command });
41 | toast({ description: `Copied ${name} command to clipboard.` });
42 | }}
43 | >
44 | {name}
45 |
46 | ))}
47 |
48 |
49 | );
50 | }
51 |
52 | async function handleCopyClick({ command }: { command: string }) {
53 | await navigator?.clipboard?.writeText(command);
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/filter.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Tag } from "@/hashnode/generated/graphql";
4 | import { TagIcon } from "lucide-react";
5 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
6 | import { Button } from "./ui/button";
7 | import {
8 | DropdownMenu,
9 | DropdownMenuCheckboxItem,
10 | DropdownMenuContent,
11 | DropdownMenuLabel,
12 | DropdownMenuSeparator,
13 | DropdownMenuTrigger,
14 | } from "./ui/dropdown-menu";
15 | import { ScrollArea } from "./ui/scroll-area";
16 |
17 | type Props = {
18 | tags: Pick[];
19 | };
20 |
21 | function Filter({ tags }: Props) {
22 | const { replace } = useRouter();
23 | const pathname = usePathname();
24 | const searchParams = useSearchParams();
25 |
26 | function onCheckedChanged(checked: boolean, tag: string) {
27 | const params = new URLSearchParams(searchParams);
28 |
29 | if (checked) {
30 | const existingTags = params.get("tags") || "";
31 | const tagsArray = existingTags.split(",");
32 | if (tagsArray[0] === "") {
33 | tagsArray.shift();
34 | }
35 | tagsArray.push(tag);
36 | params.set("tags", tagsArray.join(","));
37 | } else {
38 | const existingTags = params.get("tags");
39 | if (existingTags) {
40 | const tagsArray = existingTags.split(",");
41 | const updatedTagsArray = tagsArray.filter((t) => t !== tag);
42 | if (updatedTagsArray.length === 0) {
43 | params.delete("tags");
44 | } else {
45 | params.set("tags", updatedTagsArray.join(","));
46 | }
47 | }
48 | }
49 |
50 | replace(`${pathname}?${params.toString()}`);
51 | }
52 |
53 | function clear() {
54 | const params = new URLSearchParams(searchParams);
55 | params.delete("tags");
56 | replace(`${pathname}?${params.toString()}`);
57 | }
58 |
59 | return (
60 |
61 |
62 |
65 |
66 |
67 |
68 |
69 | Tags
70 |
73 |
74 |
75 | {tags
76 | .sort((a, b) => b.postsCount - a.postsCount)
77 | .map(({ name, postsCount }) => (
78 | onCheckedChanged(checked, name)}
82 | onSelect={(e) => e.preventDefault()}
83 | >
84 | {name} ({postsCount})
85 |
86 | ))}
87 |
88 |
89 |
90 | );
91 | }
92 |
93 | export default Filter;
94 |
--------------------------------------------------------------------------------
/src/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 |
4 | function Footer() {
5 | const links = [
6 | { href: "https://hashnode.com/headless", label: "Hashnode" },
7 | { href: "https://nextjs.org", label: "Next.js" },
8 | { href: "https://ui.shadcn.com", label: "shadcn/ui" },
9 | { href: "https://vercel.com", label: "Vercel" },
10 | ];
11 |
12 | return (
13 |
41 | );
42 | }
43 |
44 | export default Footer;
45 |
--------------------------------------------------------------------------------
/src/components/header-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 |
5 | import { NavigationMenu, NavigationMenuItem, NavigationMenuList } from "@/components/ui/navigation-menu";
6 | import { cn } from "@/lib/utils";
7 | import { SortTypes } from "@/types/sort-types";
8 | import { usePathname } from "next/navigation";
9 |
10 | export function HeaderNav() {
11 | const pathname = usePathname();
12 | const isActive = (path: string) => pathname === path.split("?")[0];
13 |
14 | const blogSearchParams = new URLSearchParams();
15 | blogSearchParams.set("sort", SortTypes.Date);
16 |
17 | const qotdSearchParams = new URLSearchParams();
18 | qotdSearchParams.set("tags", "inspirational,famous-quotes");
19 | qotdSearchParams.set("limit", "1");
20 |
21 | const navs = [
22 | {
23 | href: "/",
24 | label: "home",
25 | },
26 | {
27 | href: `/blog?${blogSearchParams.toString()}`,
28 | label: "blog",
29 | },
30 | {
31 | href: "/about",
32 | label: "about",
33 | },
34 | ];
35 |
36 | return (
37 |
38 |
39 | {navs.map((nav) => (
40 |
41 |
42 | {nav.label}
43 |
44 |
45 | ))}
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/header.tsx:
--------------------------------------------------------------------------------
1 | import { HeaderNav } from "./header-nav";
2 | import { ModeToggle } from "./mode-toggle";
3 |
4 | function Header() {
5 | return (
6 |
10 | );
11 | }
12 |
13 | export default Header;
14 |
--------------------------------------------------------------------------------
/src/components/mdx.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import "highlight.js/styles/github-dark-dimmed.min.css";
3 | import { Lightbulb } from "lucide-react";
4 | import ReactMarkdown, { Components } from "react-markdown";
5 | import { Tweet } from "react-tweet";
6 | import rehypeAutolinkHeadings from "rehype-autolink-headings";
7 | import rehypeHighlight from "rehype-highlight";
8 | import rehypeRaw from "rehype-raw";
9 | import rehypeSlug from "rehype-slug";
10 | import remarkGfm from "remark-gfm";
11 | import { Alert, AlertDescription } from "./ui/alert";
12 |
13 | const components: Components = {
14 | h1: ({ className, node: _n, ...props }) => ,
15 | h2: ({ className, node: _n, ...props }) => ,
16 | h3: ({ className, node: _n, ...props }) => ,
17 | h4: ({ className, node: _n, ...props }) => ,
18 | h5: ({ className, node: _n, ...props }) => ,
19 | h6: ({ className, node: _n, ...props }) => ,
20 | a: ({ className, href, node, target, ref, ...props }) => {
21 | const tweetMatch = (node?.properties?.href as string)?.match(/twitter\.com\/\w+\/status\/(\d+)/);
22 |
23 | if (typeof node?.properties.href === "string" && tweetMatch) {
24 | const tweetId = tweetMatch[1];
25 |
26 | if (!tweetId) return null;
27 |
28 | return ;
29 | }
30 |
31 | if (typeof node?.properties.href === "string" && node?.properties.href.includes("youtube.com")) {
32 | const youtubeId = new URL(node?.properties.href).searchParams.get("v");
33 |
34 | if (!youtubeId) return null;
35 |
36 | return ;
37 | }
38 |
39 | // Hashnode adds a style attribute to links, which we don't want
40 | delete props.style;
41 |
42 | return ;
43 | },
44 | strong: ({ className, node: _n, ...props }) => ,
45 | p: (props) => ,
46 | ul: ({ className, node: _n, ...props }) => ,
47 | ol: ({ className, node: _n, ...props }) =>
,
48 | li: (props) => ,
49 | blockquote: ({ className, node: _n, ...props }) => ,
50 | img: ({ className, alt, ...props }) => (
51 | // eslint-disable-next-line @next/next/no-img-element
52 |
53 | ),
54 | hr: ({ ...props }) =>
,
55 | pre: ({ className, node: _n, ...props }) => ,
56 | code: ({ className, children, node, ...props }) => {
57 | const isMultiline = node?.children?.length ?? 0 > 1;
58 | if (isMultiline) {
59 | const { className } = node?.properties ?? {};
60 | return {children}
;
61 | }
62 | return (
63 |
64 | {children}
65 |
66 | );
67 | },
68 | div: (props) => {
69 | if (props.node?.properties.dataNodeType === "callout-emoji") return null;
70 |
71 | if (props.node?.properties.dataNodeType === "callout-text")
72 | return (
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 |
81 | return ;
82 | },
83 | };
84 |
85 | interface MdxProps {
86 | code: string;
87 | baseUri?: string;
88 | }
89 |
90 | export function Mdx({ code, baseUri }: MdxProps) {
91 | const transformLink = (href: string) => {
92 | if (href.startsWith("http")) {
93 | return href;
94 | }
95 | return baseUri ? `${baseUri}${href}` : href;
96 | };
97 |
98 | const transformEmbeds = (code: string) => code.replace(/%\[(.*?)\]/g, "$1");
99 |
100 | const removeAligns = (code: string) => code.replace(/align=\"(left|right|center)\"/g, "");
101 |
102 | const sanatize = (code: string) => removeAligns(transformEmbeds(code));
103 |
104 | return (
105 |
118 | {sanatize(code)}
119 |
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/src/components/mode-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Moon, Sun } from "lucide-react";
4 | import { useTheme } from "next-themes";
5 |
6 | import { Button } from "@/components/ui/button";
7 |
8 | export function ModeToggle() {
9 | const { setTheme, theme } = useTheme();
10 |
11 | return (
12 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/paragraph-skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton } from "./ui/skeleton";
2 |
3 | function ParagraphSkeleton() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default ParagraphSkeleton;
17 |
--------------------------------------------------------------------------------
/src/components/providers.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from "@vercel/analytics/react";
2 | import ThemeProvider from "./theme-provider";
3 |
4 | type Props = {
5 | children: React.ReactNode;
6 | };
7 |
8 | function Providers({ children }: Props) {
9 | return (
10 |
11 | {children}
12 |
13 |
14 | );
15 | }
16 |
17 | export default Providers;
18 |
--------------------------------------------------------------------------------
/src/components/scripts.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { GA_TRACKING_ID } from "./analytics";
4 |
5 | export default function Scripts() {
6 | const googleAnalytics = `
7 | window.dataLayer = window.dataLayer || [];
8 | function gtag(){window.dataLayer.push(arguments);}
9 | gtag('js', new Date());`;
10 | return (
11 | <>
12 |
13 |
14 | >
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/search.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
4 | import { useDebouncedCallback } from "use-debounce";
5 | import { Input } from "./ui/input";
6 |
7 | type Props = {
8 | placeholder: string;
9 | };
10 |
11 | function Search({ placeholder }: Props) {
12 | const { replace } = useRouter();
13 | const pathname = usePathname();
14 | const searchParams = useSearchParams();
15 |
16 | const handleSearch = useDebouncedCallback((term: string) => {
17 | const params = new URLSearchParams(searchParams);
18 |
19 | if (term) {
20 | params.set("query", term);
21 | } else {
22 | params.delete("query");
23 | }
24 |
25 | replace(`${pathname}?${params.toString()}`);
26 | }, 300);
27 |
28 | return (
29 | handleSearch(e.target.value)} defaultValue={searchParams.get("query")?.toString()} autoFocus />
30 | );
31 | }
32 |
33 | export default Search;
34 |
--------------------------------------------------------------------------------
/src/components/sort.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SortTypes } from "@/types/sort-types";
4 | import { SortDescIcon } from "lucide-react";
5 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
6 | import { Button } from "./ui/button";
7 | import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
8 |
9 | function Sort() {
10 | const { replace } = useRouter();
11 | const pathname = usePathname();
12 | const searchParams = useSearchParams();
13 |
14 | function handleSort(sort: string) {
15 | const params = new URLSearchParams(searchParams);
16 |
17 | if (sort) {
18 | params.set("sort", sort);
19 | } else {
20 | params.delete("sort");
21 | }
22 |
23 | replace(`${pathname}?${params.toString()}`);
24 | }
25 |
26 | return (
27 |
28 |
29 |
32 |
33 |
34 |
35 | {Object.values(SortTypes).map((sortType) => (
36 | e.preventDefault()}>
37 | {sortType}
38 |
39 | ))}
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default Sort;
47 |
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ThemeProvider as NextThemesProvider } from "next-themes";
4 | import { type ThemeProviderProps } from "next-themes/dist/types";
5 |
6 | export default function ThemeProvider({ children, ...props }: ThemeProviderProps) {
7 | return {children};
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AccordionPrimitive from "@radix-ui/react-accordion";
4 | import { ChevronDownIcon } from "@radix-ui/react-icons";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Accordion = AccordionPrimitive.Root;
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => );
15 | AccordionItem.displayName = "AccordionItem";
16 |
17 | const AccordionTrigger = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, children, ...props }, ref) => (
21 |
22 | svg]:rotate-180",
26 | className,
27 | )}
28 | {...props}
29 | >
30 | {children}
31 |
32 |
33 |
34 | ));
35 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
36 |
37 | const AccordionContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, children, ...props }, ref) => (
41 |
46 | {children}
47 |
48 | ));
49 | AccordionContent.displayName = AccordionPrimitive.Content.displayName;
50 |
51 | export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
52 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
4 | import * as React from "react";
5 |
6 | import { buttonVariants } from "@/components/ui/button";
7 | import { cn } from "@/lib/utils";
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root;
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal;
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ));
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ));
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
47 |
48 | const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
49 |
50 | );
51 | AlertDialogHeader.displayName = "AlertDialogHeader";
52 |
53 | const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
54 |
55 | );
56 | AlertDialogFooter.displayName = "AlertDialogFooter";
57 |
58 | const AlertDialogTitle = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => );
62 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
63 |
64 | const AlertDialogDescription = React.forwardRef<
65 | React.ElementRef,
66 | React.ComponentPropsWithoutRef
67 | >(({ className, ...props }, ref) => (
68 |
69 | ));
70 | AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
71 |
72 | const AlertDialogAction = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >(({ className, ...props }, ref) => );
76 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
77 |
78 | const AlertDialogCancel = React.forwardRef<
79 | React.ElementRef,
80 | React.ComponentPropsWithoutRef
81 | >(({ className, ...props }, ref) => (
82 |
83 | ));
84 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
85 |
86 | export {
87 | AlertDialog,
88 | AlertDialogAction,
89 | AlertDialogCancel,
90 | AlertDialogContent,
91 | AlertDialogDescription,
92 | AlertDialogFooter,
93 | AlertDialogHeader,
94 | AlertDialogOverlay,
95 | AlertDialogPortal,
96 | AlertDialogTitle,
97 | AlertDialogTrigger,
98 | };
99 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from "class-variance-authority";
2 | import * as React from "react";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
13 | },
14 | },
15 | defaultVariants: {
16 | variant: "default",
17 | },
18 | },
19 | );
20 |
21 | const Alert = React.forwardRef & VariantProps>(
22 | ({ className, variant, ...props }, ref) => ,
23 | );
24 | Alert.displayName = "Alert";
25 |
26 | const AlertTitle = React.forwardRef>(({ className, ...props }, ref) => (
27 |
28 | ));
29 | AlertTitle.displayName = "AlertTitle";
30 |
31 | const AlertDescription = React.forwardRef>(({ className, ...props }, ref) => (
32 |
33 | ));
34 | AlertDescription.displayName = "AlertDescription";
35 |
36 | export { Alert, AlertDescription, AlertTitle };
37 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root;
6 |
7 | export { AspectRatio };
8 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Avatar = React.forwardRef, React.ComponentPropsWithoutRef>(
9 | ({ className, ...props }, ref) => (
10 |
11 | ),
12 | );
13 | Avatar.displayName = AvatarPrimitive.Root.displayName;
14 |
15 | const AvatarImage = React.forwardRef, React.ComponentPropsWithoutRef>(
16 | ({ className, ...props }, ref) => ,
17 | );
18 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
19 |
20 | const AvatarFallback = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, ...props }, ref) => (
24 |
25 | ));
26 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
27 |
28 | export { Avatar, AvatarFallback, AvatarImage };
29 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from "class-variance-authority";
2 | import * as React from "react";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12 | secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13 | destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
14 | outline: "text-foreground",
15 | },
16 | },
17 | defaultVariants: {
18 | variant: "default",
19 | },
20 | },
21 | );
22 |
23 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
24 |
25 | function Badge({ className, variant, ...props }: BadgeProps) {
26 | return ;
27 | }
28 |
29 | export { Badge, badgeVariants };
30 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
13 | destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
14 | outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
15 | secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
16 | ghost: "hover:bg-accent hover:text-accent-foreground",
17 | link: "text-primary underline-offset-4 hover:underline",
18 | },
19 | size: {
20 | default: "h-9 px-4 py-2",
21 | sm: "h-8 rounded-md px-3 text-xs",
22 | lg: "h-10 rounded-md px-8",
23 | icon: "h-9 w-9",
24 | },
25 | },
26 | defaultVariants: {
27 | variant: "default",
28 | size: "default",
29 | },
30 | },
31 | );
32 |
33 | export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps {
34 | asChild?: boolean;
35 | }
36 |
37 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
38 | const Comp = asChild ? Slot : "button";
39 | return ;
40 | });
41 | Button.displayName = "Button";
42 |
43 | export { Button, buttonVariants };
44 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
4 | import * as React from "react";
5 | import { DayPicker } from "react-day-picker";
6 |
7 | import { buttonVariants } from "@/components/ui/button";
8 | import { cn } from "@/lib/utils";
9 |
10 | export type CalendarProps = React.ComponentProps;
11 |
12 | function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
13 | return (
14 | .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
34 | : "[&:has([aria-selected])]:rounded-md",
35 | ),
36 | day: cn(buttonVariants({ variant: "ghost" }), "h-8 w-8 p-0 font-normal aria-selected:opacity-100"),
37 | day_range_start: "day-range-start",
38 | day_range_end: "day-range-end",
39 | day_selected:
40 | "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
41 | day_today: "bg-accent text-accent-foreground",
42 | day_outside:
43 | "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
44 | day_disabled: "text-muted-foreground opacity-50",
45 | day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
46 | day_hidden: "invisible",
47 | ...classNames,
48 | }}
49 | components={{
50 | IconLeft: ({ ...props }) => ,
51 | IconRight: ({ ...props }) => ,
52 | }}
53 | {...props}
54 | />
55 | );
56 | }
57 | Calendar.displayName = "Calendar";
58 |
59 | export { Calendar };
60 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef>(({ className, ...props }, ref) => (
6 |
7 | ));
8 | Card.displayName = "Card";
9 |
10 | const CardHeader = React.forwardRef>(({ className, ...props }, ref) => (
11 |
12 | ));
13 | CardHeader.displayName = "CardHeader";
14 |
15 | const CardTitle = React.forwardRef>(({ className, ...props }, ref) => (
16 |
17 | ));
18 | CardTitle.displayName = "CardTitle";
19 |
20 | const CardDescription = React.forwardRef>(({ className, ...props }, ref) => (
21 |
22 | ));
23 | CardDescription.displayName = "CardDescription";
24 |
25 | const CardContent = React.forwardRef>(({ className, ...props }, ref) => (
26 |
27 | ));
28 | CardContent.displayName = "CardContent";
29 |
30 | const CardFooter = React.forwardRef>(({ className, ...props }, ref) => (
31 |
32 | ));
33 | CardFooter.displayName = "CardFooter";
34 |
35 | export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
36 |
--------------------------------------------------------------------------------
/src/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons";
4 | import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
5 | import * as React from "react";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import { cn } from "@/lib/utils";
9 |
10 | type CarouselApi = UseEmblaCarouselType[1];
11 | type UseCarouselParameters = Parameters;
12 | type CarouselOptions = UseCarouselParameters[0];
13 | type CarouselPlugin = UseCarouselParameters[1];
14 |
15 | type CarouselProps = {
16 | opts?: CarouselOptions;
17 | plugins?: CarouselPlugin;
18 | orientation?: "horizontal" | "vertical";
19 | setApi?: (api: CarouselApi) => void;
20 | };
21 |
22 | type CarouselContextProps = {
23 | carouselRef: ReturnType[0];
24 | api: ReturnType[1];
25 | scrollPrev: () => void;
26 | scrollNext: () => void;
27 | canScrollPrev: boolean;
28 | canScrollNext: boolean;
29 | } & CarouselProps;
30 |
31 | const CarouselContext = React.createContext(null);
32 |
33 | function useCarousel() {
34 | const context = React.useContext(CarouselContext);
35 |
36 | if (!context) {
37 | throw new Error("useCarousel must be used within a ");
38 | }
39 |
40 | return context;
41 | }
42 |
43 | const Carousel = React.forwardRef & CarouselProps>(
44 | ({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
45 | const [carouselRef, api] = useEmblaCarousel(
46 | {
47 | ...opts,
48 | axis: orientation === "horizontal" ? "x" : "y",
49 | },
50 | plugins,
51 | );
52 | const [canScrollPrev, setCanScrollPrev] = React.useState(false);
53 | const [canScrollNext, setCanScrollNext] = React.useState(false);
54 |
55 | const onSelect = React.useCallback((api: CarouselApi) => {
56 | if (!api) {
57 | return;
58 | }
59 |
60 | setCanScrollPrev(api.canScrollPrev());
61 | setCanScrollNext(api.canScrollNext());
62 | }, []);
63 |
64 | const scrollPrev = React.useCallback(() => {
65 | api?.scrollPrev();
66 | }, [api]);
67 |
68 | const scrollNext = React.useCallback(() => {
69 | api?.scrollNext();
70 | }, [api]);
71 |
72 | const handleKeyDown = React.useCallback(
73 | (event: React.KeyboardEvent) => {
74 | if (event.key === "ArrowLeft") {
75 | event.preventDefault();
76 | scrollPrev();
77 | } else if (event.key === "ArrowRight") {
78 | event.preventDefault();
79 | scrollNext();
80 | }
81 | },
82 | [scrollPrev, scrollNext],
83 | );
84 |
85 | React.useEffect(() => {
86 | if (!api || !setApi) {
87 | return;
88 | }
89 |
90 | setApi(api);
91 | }, [api, setApi]);
92 |
93 | React.useEffect(() => {
94 | if (!api) {
95 | return;
96 | }
97 |
98 | onSelect(api);
99 | api.on("reInit", onSelect);
100 | api.on("select", onSelect);
101 |
102 | return () => {
103 | api?.off("select", onSelect);
104 | };
105 | }, [api, onSelect]);
106 |
107 | return (
108 |
120 |
128 | {children}
129 |
130 |
131 | );
132 | },
133 | );
134 | Carousel.displayName = "Carousel";
135 |
136 | const CarouselContent = React.forwardRef>(({ className, ...props }, ref) => {
137 | const { carouselRef, orientation } = useCarousel();
138 |
139 | return (
140 |
143 | );
144 | });
145 | CarouselContent.displayName = "CarouselContent";
146 |
147 | const CarouselItem = React.forwardRef>(({ className, ...props }, ref) => {
148 | const { orientation } = useCarousel();
149 |
150 | return (
151 |
158 | );
159 | });
160 | CarouselItem.displayName = "CarouselItem";
161 |
162 | const CarouselPrevious = React.forwardRef>(
163 | ({ className, variant = "outline", size = "icon", ...props }, ref) => {
164 | const { orientation, scrollPrev, canScrollPrev } = useCarousel();
165 |
166 | return (
167 |
183 | );
184 | },
185 | );
186 | CarouselPrevious.displayName = "CarouselPrevious";
187 |
188 | const CarouselNext = React.forwardRef>(
189 | ({ className, variant = "outline", size = "icon", ...props }, ref) => {
190 | const { orientation, scrollNext, canScrollNext } = useCarousel();
191 |
192 | return (
193 |
209 | );
210 | },
211 | );
212 | CarouselNext.displayName = "CarouselNext";
213 |
214 | export { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, type CarouselApi };
215 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4 | import { CheckIcon } from "@radix-ui/react-icons";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Checkbox = React.forwardRef, React.ComponentPropsWithoutRef>(
10 | ({ className, ...props }, ref) => (
11 |
19 |
20 |
21 |
22 |
23 | ),
24 | );
25 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
26 |
27 | export { Checkbox };
28 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4 |
5 | const Collapsible = CollapsiblePrimitive.Root;
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10 |
11 | export { Collapsible, CollapsibleContent, CollapsibleTrigger };
12 |
--------------------------------------------------------------------------------
/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { type DialogProps } from "@radix-ui/react-dialog";
4 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
5 | import { Command as CommandPrimitive } from "cmdk";
6 | import * as React from "react";
7 |
8 | import { Dialog, DialogContent } from "@/components/ui/dialog";
9 | import { cn } from "@/lib/utils";
10 |
11 | const Command = React.forwardRef, React.ComponentPropsWithoutRef>(
12 | ({ className, ...props }, ref) => (
13 |
18 | ),
19 | );
20 | Command.displayName = CommandPrimitive.displayName;
21 |
22 | interface CommandDialogProps extends DialogProps {}
23 |
24 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
25 | return (
26 |
33 | );
34 | };
35 |
36 | const CommandInput = React.forwardRef, React.ComponentPropsWithoutRef>(
37 | ({ className, ...props }, ref) => (
38 |
39 |
40 |
48 |
49 | ),
50 | );
51 |
52 | CommandInput.displayName = CommandPrimitive.Input.displayName;
53 |
54 | const CommandList = React.forwardRef, React.ComponentPropsWithoutRef>(
55 | ({ className, ...props }, ref) => (
56 |
57 | ),
58 | );
59 |
60 | CommandList.displayName = CommandPrimitive.List.displayName;
61 |
62 | const CommandEmpty = React.forwardRef, React.ComponentPropsWithoutRef>(
63 | (props, ref) => ,
64 | );
65 |
66 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
67 |
68 | const CommandGroup = React.forwardRef, React.ComponentPropsWithoutRef>(
69 | ({ className, ...props }, ref) => (
70 |
78 | ),
79 | );
80 |
81 | CommandGroup.displayName = CommandPrimitive.Group.displayName;
82 |
83 | const CommandSeparator = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => );
87 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
88 |
89 | const CommandItem = React.forwardRef, React.ComponentPropsWithoutRef>(
90 | ({ className, ...props }, ref) => (
91 |
99 | ),
100 | );
101 |
102 | CommandItem.displayName = CommandPrimitive.Item.displayName;
103 |
104 | const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => {
105 | return ;
106 | };
107 | CommandShortcut.displayName = "CommandShortcut";
108 |
109 | export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut };
110 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
4 | import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const ContextMenu = ContextMenuPrimitive.Root;
10 |
11 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
12 |
13 | const ContextMenuGroup = ContextMenuPrimitive.Group;
14 |
15 | const ContextMenuPortal = ContextMenuPrimitive.Portal;
16 |
17 | const ContextMenuSub = ContextMenuPrimitive.Sub;
18 |
19 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
20 |
21 | const ContextMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean;
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ));
40 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
41 |
42 | const ContextMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ));
55 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
56 |
57 | const ContextMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
62 |
70 |
71 | ));
72 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
73 |
74 | const ContextMenuItem = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef & {
77 | inset?: boolean;
78 | }
79 | >(({ className, inset, ...props }, ref) => (
80 |
89 | ));
90 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
91 |
92 | const ContextMenuCheckboxItem = React.forwardRef<
93 | React.ElementRef,
94 | React.ComponentPropsWithoutRef
95 | >(({ className, children, checked, ...props }, ref) => (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | ));
113 | ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
114 |
115 | const ContextMenuRadioItem = React.forwardRef<
116 | React.ElementRef,
117 | React.ComponentPropsWithoutRef
118 | >(({ className, children, ...props }, ref) => (
119 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ));
135 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
136 |
137 | const ContextMenuLabel = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef & {
140 | inset?: boolean;
141 | }
142 | >(({ className, inset, ...props }, ref) => (
143 |
144 | ));
145 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
146 |
147 | const ContextMenuSeparator = React.forwardRef<
148 | React.ElementRef,
149 | React.ComponentPropsWithoutRef
150 | >(({ className, ...props }, ref) => );
151 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
152 |
153 | const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
154 | return ;
155 | };
156 | ContextMenuShortcut.displayName = "ContextMenuShortcut";
157 |
158 | export {
159 | ContextMenu,
160 | ContextMenuCheckboxItem,
161 | ContextMenuContent,
162 | ContextMenuGroup,
163 | ContextMenuItem,
164 | ContextMenuLabel,
165 | ContextMenuPortal,
166 | ContextMenuRadioGroup,
167 | ContextMenuRadioItem,
168 | ContextMenuSeparator,
169 | ContextMenuShortcut,
170 | ContextMenuSub,
171 | ContextMenuSubContent,
172 | ContextMenuSubTrigger,
173 | ContextMenuTrigger,
174 | };
175 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as DialogPrimitive from "@radix-ui/react-dialog";
4 | import { Cross2Icon } from "@radix-ui/react-icons";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Dialog = DialogPrimitive.Root;
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger;
12 |
13 | const DialogPortal = DialogPrimitive.Portal;
14 |
15 | const DialogClose = DialogPrimitive.Close;
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ));
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ));
54 | DialogContent.displayName = DialogPrimitive.Content.displayName;
55 |
56 | const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
57 |
58 | );
59 | DialogHeader.displayName = "DialogHeader";
60 |
61 | const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
62 |
63 | );
64 | DialogFooter.displayName = "DialogFooter";
65 |
66 | const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>(
67 | ({ className, ...props }, ref) => (
68 |
69 | ),
70 | );
71 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
72 |
73 | const DialogDescription = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >(({ className, ...props }, ref) => );
77 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
78 |
79 | export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };
80 |
--------------------------------------------------------------------------------
/src/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { Drawer as DrawerPrimitive } from "vaul";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => (
9 |
10 | );
11 | Drawer.displayName = "Drawer";
12 |
13 | const DrawerTrigger = DrawerPrimitive.Trigger;
14 |
15 | const DrawerPortal = DrawerPrimitive.Portal;
16 |
17 | const DrawerClose = DrawerPrimitive.Close;
18 |
19 | const DrawerOverlay = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef
22 | >(({ className, ...props }, ref) => );
23 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
24 |
25 | const DrawerContent = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, children, ...props }, ref) => (
29 |
30 |
31 |
36 |
37 | {children}
38 |
39 |
40 | ));
41 | DrawerContent.displayName = "DrawerContent";
42 |
43 | const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => (
44 |
45 | );
46 | DrawerHeader.displayName = "DrawerHeader";
47 |
48 | const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => (
49 |
50 | );
51 | DrawerFooter.displayName = "DrawerFooter";
52 |
53 | const DrawerTitle = React.forwardRef, React.ComponentPropsWithoutRef>(
54 | ({ className, ...props }, ref) => (
55 |
56 | ),
57 | );
58 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
59 |
60 | const DrawerDescription = React.forwardRef<
61 | React.ElementRef,
62 | React.ComponentPropsWithoutRef
63 | >(({ className, ...props }, ref) => );
64 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
65 |
66 | export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger };
67 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
4 | import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root;
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group;
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub;
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean;
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ));
40 | DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
41 |
42 | const DropdownMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ));
55 | DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
56 |
57 | const DropdownMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, sideOffset = 4, ...props }, ref) => (
61 |
62 |
72 |
73 | ));
74 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
75 |
76 | const DropdownMenuItem = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef & {
79 | inset?: boolean;
80 | }
81 | >(({ className, inset, ...props }, ref) => (
82 |
91 | ));
92 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
93 |
94 | const DropdownMenuCheckboxItem = React.forwardRef<
95 | React.ElementRef,
96 | React.ComponentPropsWithoutRef
97 | >(({ className, children, checked, ...props }, ref) => (
98 |
107 |
108 |
109 |
110 |
111 |
112 | {children}
113 |
114 | ));
115 | DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
116 |
117 | const DropdownMenuRadioItem = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, children, ...props }, ref) => (
121 |
129 |
130 |
131 |
132 |
133 |
134 | {children}
135 |
136 | ));
137 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
138 |
139 | const DropdownMenuLabel = React.forwardRef<
140 | React.ElementRef,
141 | React.ComponentPropsWithoutRef & {
142 | inset?: boolean;
143 | }
144 | >(({ className, inset, ...props }, ref) => (
145 |
146 | ));
147 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
148 |
149 | const DropdownMenuSeparator = React.forwardRef<
150 | React.ElementRef,
151 | React.ComponentPropsWithoutRef
152 | >(({ className, ...props }, ref) => );
153 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
154 |
155 | const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
156 | return ;
157 | };
158 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
159 |
160 | export {
161 | DropdownMenu,
162 | DropdownMenuCheckboxItem,
163 | DropdownMenuContent,
164 | DropdownMenuGroup,
165 | DropdownMenuItem,
166 | DropdownMenuLabel,
167 | DropdownMenuPortal,
168 | DropdownMenuRadioGroup,
169 | DropdownMenuRadioItem,
170 | DropdownMenuSeparator,
171 | DropdownMenuShortcut,
172 | DropdownMenuSub,
173 | DropdownMenuSubContent,
174 | DropdownMenuSubTrigger,
175 | DropdownMenuTrigger,
176 | };
177 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as LabelPrimitive from "@radix-ui/react-label";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import * as React from "react";
4 | import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
5 |
6 | import { Label } from "@/components/ui/label";
7 | import { cn } from "@/lib/utils";
8 |
9 | const Form = FormProvider;
10 |
11 | type FormFieldContextValue = FieldPath> = {
12 | name: TName;
13 | };
14 |
15 | const FormFieldContext = React.createContext({} as FormFieldContextValue);
16 |
17 | const FormField = = FieldPath>({
18 | ...props
19 | }: ControllerProps) => {
20 | return (
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | const useFormField = () => {
28 | const fieldContext = React.useContext(FormFieldContext);
29 | const itemContext = React.useContext(FormItemContext);
30 | const { getFieldState, formState } = useFormContext();
31 |
32 | const fieldState = getFieldState(fieldContext.name, formState);
33 |
34 | if (!fieldContext) {
35 | throw new Error("useFormField should be used within ");
36 | }
37 |
38 | const { id } = itemContext;
39 |
40 | return {
41 | id,
42 | name: fieldContext.name,
43 | formItemId: `${id}-form-item`,
44 | formDescriptionId: `${id}-form-item-description`,
45 | formMessageId: `${id}-form-item-message`,
46 | ...fieldState,
47 | };
48 | };
49 |
50 | type FormItemContextValue = {
51 | id: string;
52 | };
53 |
54 | const FormItemContext = React.createContext({} as FormItemContextValue);
55 |
56 | const FormItem = React.forwardRef>(({ className, ...props }, ref) => {
57 | const id = React.useId();
58 |
59 | return (
60 |
61 |
62 |
63 | );
64 | });
65 | FormItem.displayName = "FormItem";
66 |
67 | const FormLabel = React.forwardRef, React.ComponentPropsWithoutRef>(
68 | ({ className, ...props }, ref) => {
69 | const { error, formItemId } = useFormField();
70 |
71 | return ;
72 | },
73 | );
74 | FormLabel.displayName = "FormLabel";
75 |
76 | const FormControl = React.forwardRef, React.ComponentPropsWithoutRef>(({ ...props }, ref) => {
77 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
78 |
79 | return (
80 |
87 | );
88 | });
89 | FormControl.displayName = "FormControl";
90 |
91 | const FormDescription = React.forwardRef>(({ className, ...props }, ref) => {
92 | const { formDescriptionId } = useFormField();
93 |
94 | return ;
95 | });
96 | FormDescription.displayName = "FormDescription";
97 |
98 | const FormMessage = React.forwardRef>(({ className, children, ...props }, ref) => {
99 | const { error, formMessageId } = useFormField();
100 | const body = error ? String(error?.message) : children;
101 |
102 | if (!body) {
103 | return null;
104 | }
105 |
106 | return (
107 |
108 | {body}
109 |
110 | );
111 | });
112 | FormMessage.displayName = "FormMessage";
113 |
114 | export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
115 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const HoverCard = HoverCardPrimitive.Root;
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger;
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ));
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
28 |
29 | export { HoverCard, HoverCardContent, HoverCardTrigger };
30 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export interface InputProps extends React.InputHTMLAttributes {}
6 |
7 | const Input = React.forwardRef(({ className, type, ...props }, ref) => {
8 | return (
9 |
18 | );
19 | });
20 | Input.displayName = "Input";
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { cva, type VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef & VariantProps
14 | >(({ className, ...props }, ref) => );
15 | Label.displayName = LabelPrimitive.Root.displayName;
16 |
17 | export { Label };
18 |
--------------------------------------------------------------------------------
/src/components/ui/menubar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons";
4 | import * as MenubarPrimitive from "@radix-ui/react-menubar";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const MenubarMenu = MenubarPrimitive.Menu;
10 |
11 | const MenubarGroup = MenubarPrimitive.Group;
12 |
13 | const MenubarPortal = MenubarPrimitive.Portal;
14 |
15 | const MenubarSub = MenubarPrimitive.Sub;
16 |
17 | const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
18 |
19 | const Menubar = React.forwardRef, React.ComponentPropsWithoutRef>(
20 | ({ className, ...props }, ref) => (
21 |
26 | ),
27 | );
28 | Menubar.displayName = MenubarPrimitive.Root.displayName;
29 |
30 | const MenubarTrigger = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
42 | ));
43 | MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
44 |
45 | const MenubarSubTrigger = React.forwardRef<
46 | React.ElementRef,
47 | React.ComponentPropsWithoutRef & {
48 | inset?: boolean;
49 | }
50 | >(({ className, inset, children, ...props }, ref) => (
51 |
60 | {children}
61 |
62 |
63 | ));
64 | MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
65 |
66 | const MenubarSubContent = React.forwardRef<
67 | React.ElementRef,
68 | React.ComponentPropsWithoutRef
69 | >(({ className, ...props }, ref) => (
70 |
78 | ));
79 | MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
80 |
81 | const MenubarContent = React.forwardRef<
82 | React.ElementRef,
83 | React.ComponentPropsWithoutRef
84 | >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
85 |
86 |
97 |
98 | ));
99 | MenubarContent.displayName = MenubarPrimitive.Content.displayName;
100 |
101 | const MenubarItem = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef & {
104 | inset?: boolean;
105 | }
106 | >(({ className, inset, ...props }, ref) => (
107 |
116 | ));
117 | MenubarItem.displayName = MenubarPrimitive.Item.displayName;
118 |
119 | const MenubarCheckboxItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, checked, ...props }, ref) => (
123 |
132 |
133 |
134 |
135 |
136 |
137 | {children}
138 |
139 | ));
140 | MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
141 |
142 | const MenubarRadioItem = React.forwardRef<
143 | React.ElementRef,
144 | React.ComponentPropsWithoutRef
145 | >(({ className, children, ...props }, ref) => (
146 |
154 |
155 |
156 |
157 |
158 |
159 | {children}
160 |
161 | ));
162 | MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
163 |
164 | const MenubarLabel = React.forwardRef<
165 | React.ElementRef,
166 | React.ComponentPropsWithoutRef & {
167 | inset?: boolean;
168 | }
169 | >(({ className, inset, ...props }, ref) => (
170 |
171 | ));
172 | MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
173 |
174 | const MenubarSeparator = React.forwardRef<
175 | React.ElementRef,
176 | React.ComponentPropsWithoutRef
177 | >(({ className, ...props }, ref) => );
178 | MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
179 |
180 | const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes) => {
181 | return ;
182 | };
183 | MenubarShortcut.displayname = "MenubarShortcut";
184 |
185 | export {
186 | Menubar,
187 | MenubarCheckboxItem,
188 | MenubarContent,
189 | MenubarGroup,
190 | MenubarItem,
191 | MenubarLabel,
192 | MenubarMenu,
193 | MenubarPortal,
194 | MenubarRadioGroup,
195 | MenubarRadioItem,
196 | MenubarSeparator,
197 | MenubarShortcut,
198 | MenubarSub,
199 | MenubarSubContent,
200 | MenubarSubTrigger,
201 | MenubarTrigger,
202 | };
203 |
--------------------------------------------------------------------------------
/src/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronDownIcon } from "@radix-ui/react-icons";
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
3 | import { cva } from "class-variance-authority";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
13 | {children}
14 |
15 |
16 | ));
17 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
18 |
19 | const NavigationMenuList = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef
22 | >(({ className, ...props }, ref) => (
23 |
24 | ));
25 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
26 |
27 | const NavigationMenuItem = NavigationMenuPrimitive.Item;
28 |
29 | const navigationMenuTriggerStyle = cva(
30 | "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
31 | );
32 |
33 | const NavigationMenuTrigger = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, children, ...props }, ref) => (
37 |
38 | {children}{" "}
39 |
40 |
41 | ));
42 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
43 |
44 | const NavigationMenuContent = React.forwardRef<
45 | React.ElementRef,
46 | React.ComponentPropsWithoutRef
47 | >(({ className, ...props }, ref) => (
48 |
56 | ));
57 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
58 |
59 | const NavigationMenuLink = NavigationMenuPrimitive.Link;
60 |
61 | const NavigationMenuViewport = React.forwardRef<
62 | React.ElementRef,
63 | React.ComponentPropsWithoutRef
64 | >(({ className, ...props }, ref) => (
65 |
66 |
74 |
75 | ));
76 | NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
77 |
78 | const NavigationMenuIndicator = React.forwardRef<
79 | React.ElementRef,
80 | React.ComponentPropsWithoutRef
81 | >(({ className, ...props }, ref) => (
82 |
90 |
91 |
92 | ));
93 | NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
94 |
95 | export {
96 | NavigationMenu,
97 | NavigationMenuContent,
98 | NavigationMenuIndicator,
99 | NavigationMenuItem,
100 | NavigationMenuLink,
101 | NavigationMenuList,
102 | NavigationMenuTrigger,
103 | NavigationMenuViewport,
104 | navigationMenuTriggerStyle,
105 | };
106 |
--------------------------------------------------------------------------------
/src/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronLeftIcon, ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons";
2 | import * as React from "react";
3 |
4 | import { ButtonProps, buttonVariants } from "@/components/ui/button";
5 | import { cn } from "@/lib/utils";
6 |
7 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
8 |
9 | );
10 | Pagination.displayName = "Pagination";
11 |
12 | const PaginationContent = React.forwardRef>(({ className, ...props }, ref) => (
13 |
14 | ));
15 | PaginationContent.displayName = "PaginationContent";
16 |
17 | const PaginationItem = React.forwardRef>(({ className, ...props }, ref) => (
18 |
19 | ));
20 | PaginationItem.displayName = "PaginationItem";
21 |
22 | type PaginationLinkProps = {
23 | isActive?: boolean;
24 | } & Pick &
25 | React.ComponentProps<"a">;
26 |
27 | const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
28 |
39 | );
40 | PaginationLink.displayName = "PaginationLink";
41 |
42 | const PaginationPrevious = ({ className, ...props }: React.ComponentProps) => (
43 |
44 |
45 | Previous
46 |
47 | );
48 | PaginationPrevious.displayName = "PaginationPrevious";
49 |
50 | const PaginationNext = ({ className, ...props }: React.ComponentProps) => (
51 |
52 | Next
53 |
54 |
55 | );
56 | PaginationNext.displayName = "PaginationNext";
57 |
58 | const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
59 |
60 |
61 | More pages
62 |
63 | );
64 | PaginationEllipsis.displayName = "PaginationEllipsis";
65 |
66 | export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious };
67 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as PopoverPrimitive from "@radix-ui/react-popover";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Popover = PopoverPrimitive.Root;
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 |
12 | const PopoverAnchor = PopoverPrimitive.Anchor;
13 |
14 | const PopoverContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
18 |
19 |
29 |
30 | ));
31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
32 |
33 | export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
34 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as ProgressPrimitive from "@radix-ui/react-progress";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Progress = React.forwardRef, React.ComponentPropsWithoutRef>(
9 | ({ className, value, ...props }, ref) => (
10 |
11 |
15 |
16 | ),
17 | );
18 | Progress.displayName = ProgressPrimitive.Root.displayName;
19 |
20 | export { Progress };
21 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CheckIcon } from "@radix-ui/react-icons";
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return ;
14 | });
15 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
16 |
17 | const RadioGroupItem = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => {
21 | return (
22 |
30 |
31 |
32 |
33 |
34 | );
35 | });
36 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
37 |
38 | export { RadioGroup, RadioGroupItem };
39 |
--------------------------------------------------------------------------------
/src/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { DragHandleDots2Icon } from "@radix-ui/react-icons";
4 | import * as ResizablePrimitive from "react-resizable-panels";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps) => (
9 |
10 | );
11 |
12 | const ResizablePanel = ResizablePrimitive.Panel;
13 |
14 | const ResizableHandle = ({
15 | withHandle,
16 | className,
17 | ...props
18 | }: React.ComponentProps & {
19 | withHandle?: boolean;
20 | }) => (
21 | div]:rotate-90",
24 | className,
25 | )}
26 | {...props}
27 | >
28 | {withHandle && (
29 |
30 |
31 |
32 | )}
33 |
34 | );
35 |
36 | export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
37 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
13 | {children}
14 |
15 |
16 |
17 | ));
18 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
19 |
20 | const ScrollBar = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, orientation = "vertical", ...props }, ref) => (
24 |
35 |
36 |
37 | ));
38 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
39 |
40 | export { ScrollArea, ScrollBar };
41 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons";
4 | import * as SelectPrimitive from "@radix-ui/react-select";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Select = SelectPrimitive.Root;
10 |
11 | const SelectGroup = SelectPrimitive.Group;
12 |
13 | const SelectValue = SelectPrimitive.Value;
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className,
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ));
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
40 |
41 |
42 | ));
43 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
44 |
45 | const SelectScrollDownButton = React.forwardRef<
46 | React.ElementRef,
47 | React.ComponentPropsWithoutRef
48 | >(({ className, ...props }, ref) => (
49 |
50 |
51 |
52 | ));
53 | SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
54 |
55 | const SelectContent = React.forwardRef<
56 | React.ElementRef,
57 | React.ComponentPropsWithoutRef
58 | >(({ className, children, position = "popper", ...props }, ref) => (
59 |
60 |
71 |
72 |
75 | {children}
76 |
77 |
78 |
79 |
80 | ));
81 | SelectContent.displayName = SelectPrimitive.Content.displayName;
82 |
83 | const SelectLabel = React.forwardRef, React.ComponentPropsWithoutRef>(
84 | ({ className, ...props }, ref) => ,
85 | );
86 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
87 |
88 | const SelectItem = React.forwardRef, React.ComponentPropsWithoutRef>(
89 | ({ className, children, ...props }, ref) => (
90 |
98 |
99 |
100 |
101 |
102 |
103 | {children}
104 |
105 | ),
106 | );
107 | SelectItem.displayName = SelectPrimitive.Item.displayName;
108 |
109 | const SelectSeparator = React.forwardRef<
110 | React.ElementRef,
111 | React.ComponentPropsWithoutRef
112 | >(({ className, ...props }, ref) => );
113 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
114 |
115 | export {
116 | Select,
117 | SelectContent,
118 | SelectGroup,
119 | SelectItem,
120 | SelectLabel,
121 | SelectScrollDownButton,
122 | SelectScrollUpButton,
123 | SelectSeparator,
124 | SelectTrigger,
125 | SelectValue,
126 | };
127 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Separator = React.forwardRef, React.ComponentPropsWithoutRef>(
9 | ({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
10 |
17 | ),
18 | );
19 | Separator.displayName = SeparatorPrimitive.Root.displayName;
20 |
21 | export { Separator };
22 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SheetPrimitive from "@radix-ui/react-dialog";
4 | import { Cross2Icon } from "@radix-ui/react-icons";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 | import * as React from "react";
7 |
8 | import { cn } from "@/lib/utils";
9 |
10 | const Sheet = SheetPrimitive.Root;
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger;
13 |
14 | const SheetClose = SheetPrimitive.Close;
15 |
16 | const SheetPortal = SheetPrimitive.Portal;
17 |
18 | const SheetOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(
19 | ({ className, ...props }, ref) => (
20 |
28 | ),
29 | );
30 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
31 |
32 | const sheetVariants = cva(
33 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
34 | {
35 | variants: {
36 | side: {
37 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
38 | bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
39 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
40 | right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
41 | },
42 | },
43 | defaultVariants: {
44 | side: "right",
45 | },
46 | },
47 | );
48 |
49 | interface SheetContentProps extends React.ComponentPropsWithoutRef, VariantProps {}
50 |
51 | const SheetContent = React.forwardRef, SheetContentProps>(
52 | ({ side = "right", className, children, ...props }, ref) => (
53 |
54 |
55 |
56 | {children}
57 |
58 |
59 | Close
60 |
61 |
62 |
63 | ),
64 | );
65 | SheetContent.displayName = SheetPrimitive.Content.displayName;
66 |
67 | const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (
68 |
69 | );
70 | SheetHeader.displayName = "SheetHeader";
71 |
72 | const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (
73 |
74 | );
75 | SheetFooter.displayName = "SheetFooter";
76 |
77 | const SheetTitle = React.forwardRef, React.ComponentPropsWithoutRef>(
78 | ({ className, ...props }, ref) => ,
79 | );
80 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
81 |
82 | const SheetDescription = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => );
86 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
87 |
88 | export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger };
89 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function Skeleton({ className, ...props }: React.HTMLAttributes) {
4 | return ;
5 | }
6 |
7 | export { Skeleton };
8 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SliderPrimitive from "@radix-ui/react-slider";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Slider = React.forwardRef, React.ComponentPropsWithoutRef>(
9 | ({ className, ...props }, ref) => (
10 |
11 |
12 |
13 |
14 |
15 |
16 | ),
17 | );
18 | Slider.displayName = SliderPrimitive.Root.displayName;
19 |
20 | export { Slider };
21 |
--------------------------------------------------------------------------------
/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useTheme } from "next-themes";
4 | import { Toaster as Sonner } from "sonner";
5 |
6 | type ToasterProps = React.ComponentProps;
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = "system" } = useTheme();
10 |
11 | return (
12 |
26 | );
27 | };
28 |
29 | export { Toaster };
30 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SwitchPrimitives from "@radix-ui/react-switch";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Switch = React.forwardRef, React.ComponentPropsWithoutRef>(
9 | ({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ),
25 | );
26 | Switch.displayName = SwitchPrimitives.Root.displayName;
27 |
28 | export { Switch };
29 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Table = React.forwardRef>(({ className, ...props }, ref) => (
6 |
9 | ));
10 | Table.displayName = "Table";
11 |
12 | const TableHeader = React.forwardRef>(({ className, ...props }, ref) => (
13 |
14 | ));
15 | TableHeader.displayName = "TableHeader";
16 |
17 | const TableBody = React.forwardRef>(({ className, ...props }, ref) => (
18 |
19 | ));
20 | TableBody.displayName = "TableBody";
21 |
22 | const TableFooter = React.forwardRef>(({ className, ...props }, ref) => (
23 | tr]:last:border-b-0", className)} {...props} />
24 | ));
25 | TableFooter.displayName = "TableFooter";
26 |
27 | const TableRow = React.forwardRef>(({ className, ...props }, ref) => (
28 |
29 | ));
30 | TableRow.displayName = "TableRow";
31 |
32 | const TableHead = React.forwardRef>(({ className, ...props }, ref) => (
33 | [role=checkbox]]:translate-y-[2px]",
37 | className,
38 | )}
39 | {...props}
40 | />
41 | ));
42 | TableHead.displayName = "TableHead";
43 |
44 | const TableCell = React.forwardRef>(({ className, ...props }, ref) => (
45 | [role=checkbox]]:translate-y-[2px]", className)} {...props} />
46 | ));
47 | TableCell.displayName = "TableCell";
48 |
49 | const TableCaption = React.forwardRef>(({ className, ...props }, ref) => (
50 |
51 | ));
52 | TableCaption.displayName = "TableCaption";
53 |
54 | export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
55 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as TabsPrimitive from "@radix-ui/react-tabs";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Tabs = TabsPrimitive.Root;
9 |
10 | const TabsList = React.forwardRef, React.ComponentPropsWithoutRef>(
11 | ({ className, ...props }, ref) => (
12 |
17 | ),
18 | );
19 | TabsList.displayName = TabsPrimitive.List.displayName;
20 |
21 | const TabsTrigger = React.forwardRef, React.ComponentPropsWithoutRef>(
22 | ({ className, ...props }, ref) => (
23 |
31 | ),
32 | );
33 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
34 |
35 | const TabsContent = React.forwardRef, React.ComponentPropsWithoutRef>(
36 | ({ className, ...props }, ref) => (
37 |
45 | ),
46 | );
47 | TabsContent.displayName = TabsPrimitive.Content.displayName;
48 |
49 | export { Tabs, TabsContent, TabsList, TabsTrigger };
50 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export interface TextareaProps extends React.TextareaHTMLAttributes {}
6 |
7 | const Textarea = React.forwardRef | |