Repository: gurbaaz27/shadcn-calendar-heatmap
Branch: main
Commit: 759856717f14
Files: 31
Total size: 78.0 KB
Directory structure:
gitextract_l_cd6g81/
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── (components)/
│ │ ├── copy-llms-button.tsx
│ │ ├── example-code.tsx
│ │ └── example-variants.ts
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── bun.lockb
├── components/
│ ├── code.tsx
│ ├── copy-button.tsx
│ ├── icons.tsx
│ ├── page-header.tsx
│ ├── site-footer.tsx
│ ├── site-header.tsx
│ └── ui/
│ ├── button.tsx
│ ├── calendar-heatmap.tsx
│ ├── dropdown-menu.tsx
│ ├── select.tsx
│ └── sonner.tsx
├── components.json
├── config/
│ └── site.ts
├── lib/
│ └── utils.ts
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── public/
│ └── llms.txt
├── tailwind.config.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Gurbaaz Singh Nandra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# shadcn-calendar-heatmap
A modern, customizable calendar heatmap component built on top of [react-day-picker](https://react-day-picker.js.org/) following [shadcn/ui](https://ui.shadcn.com/) patterns.
**Accessible. Unstyled. Customizable. Open Source.**

## ✨ Features
- 🎨 **Fully Customizable** - Style with Tailwind CSS classes
- 📅 **Multiple Data Modes** - Use direct date arrays or weighted dates with auto-categorization
- 🔢 **Multi-month Support** - Display any number of months
- ♿ **Accessible** - Built on react-day-picker with full keyboard navigation
- 🎯 **Type Safe** - Written in TypeScript with full type definitions
- 🌈 **Preset Variants** - GitHub streaks, temperature heatmaps, rainbow colors, and more
## 🚀 Demo
Check out the live demo at [shadcn-calendar-heatmap.vercel.app](https://shadcn-calendar-heatmap.vercel.app)
## 📦 Installation
This component follows the shadcn/ui philosophy - copy the component directly into your project.
### 1. Install Dependencies
```bash
npm install react-day-picker date-fns lucide-react
# or
yarn add react-day-picker date-fns lucide-react
# or
pnpm add react-day-picker date-fns lucide-react
```
### 2. Copy the Component
Copy [`components/ui/calendar-heatmap.tsx`](https://github.com/gurbaaz27/shadcn-calendar-heatmap/blob/main/components/ui/calendar-heatmap.tsx) into your project's components directory.
### 3. Ensure you have the required utilities
Make sure you have the `cn` utility function (standard in shadcn/ui projects):
```typescript
// lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
## 📖 Usage
### Basic Example - GitHub Contribution Graph
```tsx
import { CalendarHeatmap } from "@/components/ui/calendar-heatmap"
export default function MyComponent() {
return (
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
datesPerVariant={[
[new Date('Jan 1, 2024'), new Date('Jan 15, 2024')],
[new Date('Jun 12, 2024'), new Date('July 1, 2024')],
[new Date('Jan 19, 2024'), new Date('Apr 14, 2024')],
]}
/>
)
}
```
### Using Weighted Dates
Pass dates with numeric weights, and the component auto-categorizes them:
```tsx
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-blue-300 hover:bg-blue-300",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-amber-400 hover:bg-amber-400",
"text-white hover:text-white bg-red-700 hover:bg-red-700",
]}
weightedDates={[
{ date: new Date('Jan 1, 2024'), weight: 2 },
{ date: new Date('Jun 12, 2024'), weight: 8 },
{ date: new Date('Apr 19, 2024'), weight: 13.5 },
]}
/>
```
### Multi-month Display
```tsx
<CalendarHeatmap
numberOfMonths={3}
variantClassnames={[...]}
datesPerVariant={[...]}
/>
```
## 🔧 API Reference
### Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `variantClassnames` | `string[]` | ✅ | Array of Tailwind CSS classes for each intensity level |
| `datesPerVariant` | `Date[][]` | ⚡ | 2D array where each inner array contains dates for that variant |
| `weightedDates` | `WeightedDateEntry[]` | ⚡ | Array of `{ date: Date, weight: number }` objects |
| `numberOfMonths` | `number` | ❌ | Number of months to display (default: 1) |
| `showOutsideDays` | `boolean` | ❌ | Show days from adjacent months (default: true) |
> ⚡ You must provide either `datesPerVariant` OR `weightedDates`, not both.
The component also accepts all props from [react-day-picker](https://react-day-picker.js.org/api/interfaces/DayPickerMultipleProps).
### Types
```typescript
type WeightedDateEntry = {
date: Date
weight: number
}
```
## 🎨 Customization Examples
### Temperature Heatmap
```tsx
const Heatmap = [
"text-white hover:text-white bg-blue-300 hover:bg-blue-300", // Cold
"text-white hover:text-white bg-green-500 hover:bg-green-500", // Mild
"text-white hover:text-white bg-amber-400 hover:bg-amber-400", // Warm
"text-white hover:text-white bg-red-700 hover:bg-red-700", // Hot
]
```
### Rainbow Colors
```tsx
const Rainbow = [
"text-white hover:text-white bg-violet-400 hover:bg-violet-400",
"text-white hover:text-white bg-indigo-400 hover:bg-indigo-400",
"text-white hover:text-white bg-blue-400 hover:bg-blue-400",
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-yellow-400 hover:bg-yellow-400",
"text-white hover:text-white bg-orange-400 hover:bg-orange-400",
"text-white hover:text-white bg-red-400 hover:bg-red-400",
]
```
## ⭐ Star History
<a href="https://star-history.com/#gurbaaz27/shadcn-calendar-heatmap&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=gurbaaz27/shadcn-calendar-heatmap&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=gurbaaz27/shadcn-calendar-heatmap&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=gurbaaz27/shadcn-calendar-heatmap&type=Date" />
</picture>
</a>
## 🤝 Contributing
Contributions are welcome! Feel free to open an issue or submit a pull request.
## 📄 License
MIT © [Gurbaaz Singh Nandra](https://x.com/GurbaazNandra)
## 🔗 Links
- [Live Demo](https://shadcn-calendar-heatmap.vercel.app)
- [GitHub Repository](https://github.com/gurbaaz27/shadcn-calendar-heatmap)
- [Twitter/X](https://x.com/GurbaazNandra)
================================================
FILE: app/(components)/copy-llms-button.tsx
================================================
"use client"
import { useState } from "react"
import { buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { Check, Copy } from "lucide-react"
interface CopyLlmsButtonProps {
content: string
className?: string
}
export function CopyLlmsButton({ content, className }: CopyLlmsButtonProps) {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
await navigator.clipboard.writeText(content)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<button
onClick={handleCopy}
className={cn(
buttonVariants({ variant: "outline" }),
"relative !py-0 group",
className
)}
>
{copied ? (
<Check className="mr-2 h-4 w-4" />
) : (
<Copy className="mr-2 h-4 w-4" />
)}
{copied ? "Copied" : "Copy llms.txt"}
</button>
)
}
================================================
FILE: app/(components)/example-code.tsx
================================================
import { Code } from "@/components/code"
const tsx = `import { CalendarHeatmap } from "@/components/ui/calendar-heatmap"
// Github-style streak pattern
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
datesPerVariant={[
[new Date('Jan 1, 2024'), new Date('Jan 15, 2024'), new Date('Feb 18, 2024')],
[new Date('Jun 12, 2024'), new Date('July 1, 2024'), new Date('Feb 29, 2024'), new Date('May 4, 2024')],
[new Date('Jan 19, 2024'), new Date('Apr 14, 2024')],
]}
/>
// Or you may simply pass weighted array of dates,
// and they would be slotted to different variants based on length of \`variantClassnames\`
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
weightedDates={[
{ date: new Date('Jan 1, 2024'), weight: 2 }, { date : new Date('Jan 15, 2024'), weight: 1.5 },
{ date: new Date('Jun 12, 2024'), weight: 8 } , { date: new Date('July 1, 2024'), weight: 5 },
{ date: new Date('Jan 19, 2024'), weight: 6 }, { date: new Date('Apr 19, 2024'), weight: 13.5 }
]}
/>
// Component code at https://github.com/gurbaaz27/shadcn-calendar-heatmap/blob/main/components/ui/calendar-heatmap.tsx
`
const code = `\`\`\`tsx /maxLength={6}/ /render/ /slots/1 /.map((slot, idx)/1 /Slot/2,3,4 /props.char/2 /<FakeCaret />/
${tsx}
\`\`\``
export function ExampleCode() {
return (
<div className="relative code-example w-full overflow-hidden lg:opacity-0 lg:animate-fade-in [animation-delay:5000ms] animate-none">
<div className="w-full">
<Code dark={false} code={code} toCopy={tsx} />
<Code dark={true} code={code} toCopy={tsx} />
</div>
<div className="hidden lg:[display:unset] absolute inset-x-0 top-0 -bottom-full code-example-overlay pointer-events-none z-20 [animation-delay:5000ms]"></div>
{/* Anchor */}
<div className="code-example-anchor absolute pointer-events-none w-px h-px -top-[5.5rem]" />
</div>
)
}
================================================
FILE: app/(components)/example-variants.ts
================================================
import {
currentMonthFirstDate,
currentMonthLastDate,
randomDate,
} from "@/lib/utils"
export const GithubStreak = [
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]
export const GithubStreakDates = [
[...Array(12)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(3))
),
[...Array(9)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(3))
),
[...Array(6)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(3))
),
]
export const Heatmap = [
"text-white hover:text-white bg-blue-300 hover:bg-blue-300",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-amber-400 hover:bg-amber-400",
"text-white hover:text-white bg-red-700 hover:bg-red-700",
]
export const HeatmapDatesWeight = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
export const Rainbow = [
"text-white hover:text-white bg-violet-400 hover:bg-violet-400",
"text-white hover:text-white bg-indigo-400 hover:bg-indigo-400",
"text-white hover:text-white bg-blue-400 hover:bg-blue-400",
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-yellow-400 hover:bg-yellow-400",
"text-white hover:text-white bg-orange-400 hover:bg-orange-400",
"text-white hover:text-white bg-red-400 hover:bg-red-400",
]
export const RainbowDates = [
[...Array(3)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(2)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(1)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(3)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(2)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(1)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
[...Array(3)].map((_) =>
randomDate(currentMonthFirstDate(), currentMonthLastDate(2))
),
]
================================================
FILE: app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@media (prefers-color-scheme: dark) {
:root {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
/* @apply bg-background text-foreground selection:bg-[#6B2BF4] selection:text-foreground; */
@apply bg-background text-foreground;
/* font-feature-settings: "rlig" 1, "calt" 1; */
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
}
@layer utilities {
}
[data-highlighted-chars] {
@apply bg-zinc-900 rounded;
box-shadow: 2px 2px 0 2px rgba(139, 139, 148, 0.5);
}
[data-highlighted-chars] .dark {
@apply bg-zinc-700/50 rounded;
box-shadow: 2px 2px 0 2px rgba(139, 139, 148, 0.5);
}
[data-highlighted-chars] * {
@apply !text-white;
}
[data-rehype-pretty-code-figure] pre {
@apply pb-4 pt-6 max-h-[650px] overflow-x-auto rounded-lg border !bg-transparent;
}
[data-rehype-pretty-code-figure] [data-line] {
@apply inline-block min-h-4 w-full py-0.5 px-4;
}
.code-example-overlay {
background-image: linear-gradient(
to bottom,
theme("colors.background") 60%,
transparent
);
transform: translateY(0);
animation: move-overlay 4s ease-out forwards;
animation-delay: 3s;
}
.code-example-light {
}
.code-example-dark {
display: none;
}
@media (prefers-color-scheme: dark) {
.code-example-light {
display: none;
}
.code-example-dark {
display: unset;
}
}
@media (prefers-reduced-motion: reduce) {
.code-example-overlay {
opacity: 0;
animation: none;
}
}
@keyframes move-overlay {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-100%);
}
}
================================================
FILE: app/layout.tsx
================================================
import "./globals.css"
import type { Metadata } from "next"
import { cn } from "@/lib/utils"
import { GeistSans } from "geist/font/sans"
import { JetBrains_Mono } from "next/font/google"
import { SiteHeader } from "@/components/site-header"
import { SiteFooter } from "@/components/site-footer"
import { Toaster } from "@/components/ui/sonner"
import { siteConfig } from "@/config/site"
export const fontMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
})
export const metadata: Metadata = {
title: {
default: siteConfig.title,
template: `%s - ${siteConfig.name}`,
},
metadataBase: new URL(siteConfig.url),
description: siteConfig.description,
keywords: [
"React",
"Heatmap",
"Calendar",
"Next.js",
"Tailwind CSS",
"Server Components",
"Accessible",
"Shadcn",
],
authors: [
{
name: "gurbaaz",
url: "https://gurbaaz.me",
},
],
creator: "gurbaaz",
openGraph: {
type: "website",
locale: "en_IN",
url: siteConfig.url,
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: [siteConfig.ogImage],
creator: "@GurbaazNandra",
},
icons: {
icon: "/favicon.ico",
shortcut: "/favicon-16x16.png",
apple: "/apple-touch-icon.png",
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body
className={cn(
"min-h-[100dvh] bg-background font-sans antialiased",
GeistSans.className
)}
>
<div className="relative flex min-h-[100dvh] flex-col bg-background">
<SiteHeader />
<main className="flex-1 flex flex-col">{children}</main>
<SiteFooter />
</div>
<Toaster />
</body>
</html>
)
}
================================================
FILE: app/page.tsx
================================================
import {
PageActions,
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
PageHeaderNotifier,
} from "@/components/page-header"
import { buttonVariants } from "@/components/ui/button"
import { siteConfig } from "@/config/site"
import {
cn,
currentMonthFirstDate,
currentMonthLastDate,
randomDate,
} from "@/lib/utils"
import Link from "next/link"
import { CalendarHeatmap } from "@/components/ui/calendar-heatmap"
import { Icons } from "@/components/icons"
import { Star } from "lucide-react"
import { ExampleCode } from "./(components)/example-code"
import { CopyLlmsButton } from "./(components)/copy-llms-button"
import { readFile } from "fs/promises"
import path from "path"
import {
GithubStreak,
GithubStreakDates,
Heatmap,
HeatmapDatesWeight,
Rainbow,
RainbowDates,
} from "./(components)/example-variants"
const fadeUpClassname =
"lg:motion-safe:opacity-0 lg:motion-safe:animate-fade-up"
async function getRepoStarCount() {
const res = await fetch(`https://api.github.com/repos/${siteConfig.name}`)
const data = await res.json()
const starCount = data.stargazers_count
if (starCount > 999) {
return (starCount / 1000).toFixed(1) + "K"
}
return starCount
}
export default async function IndexPage() {
const starCount = await getRepoStarCount()
const llmsContent = await readFile(
path.join(process.cwd(), "public", "llms.txt"),
"utf-8"
)
return (
<div className="container relative flex-1 flex flex-col justify-center items-center">
<PageHeader>
<PageHeaderNotifier>
Excited to officially launch our new shadcn-based component!
<span className="mx-2">🎉</span>
</PageHeaderNotifier>
<PageHeaderHeading className={cn(fadeUpClassname)}>
Modern alternative to primitive react heatmaps.
</PageHeaderHeading>
<CalendarHeatmap
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:1000ms]"
)}
variantClassnames={GithubStreak}
datesPerVariant={GithubStreakDates}
/>
<PageHeaderDescription
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:3000ms]"
)}
>
Showcase Github streaks. Visualise user growth. Understand global
warming trends. <br></br>
Convey more with less.
<br></br>
Unstyled. Customizable. Open Source.
</PageHeaderDescription>
<PageActions
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:3000ms]"
)}
>
<Link
target="_blank"
rel="noreferrer"
href={siteConfig.links.github}
className={cn(
"relative !py-0 group",
buttonVariants({ variant: "outline" })
)}
>
<Icons.gitHub className="mr-2 h-4 w-4" />
<div className="flex items-center h-full">
<div className="hidden md:[display:unset]">{siteConfig.name}</div>
<div className="hidden md:[display:unset] h-full w-px bg-input group-hover:bg-foregrounds mx-4" />
<Star size={16} className="mr-2" />
<div>{starCount}</div>
</div>
</Link>
<CopyLlmsButton content={llmsContent} />
</PageActions>
</PageHeader>
<ExampleCode />
<PageHeader>
<PageHeaderHeading
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]",
"text-3xl md:text-4xl",
"py-4"
)}
>
Examples
</PageHeaderHeading>
<PageHeaderHeading
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]",
"text-xl md:text-2xl"
)}
>
Github Streaks
</PageHeaderHeading>
<CalendarHeatmap
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:1000ms]"
)}
numberOfMonths={3}
variantClassnames={GithubStreak}
datesPerVariant={GithubStreakDates}
/>
<PageHeaderHeading
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]",
"text-xl md:text-2xl"
)}
>
Temperature Heatmap
</PageHeaderHeading>
<CalendarHeatmap
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]"
)}
variantClassnames={Heatmap}
weightedDates={HeatmapDatesWeight.map((wgt) => ({
date: randomDate(currentMonthFirstDate(), currentMonthLastDate()),
weight: wgt,
}))}
/>
<PageHeaderHeading
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]",
"text-xl md:text-2xl"
)}
>
Rainbow Colors
</PageHeaderHeading>
<CalendarHeatmap
className={cn(
fadeUpClassname,
"lg:motion-safe:[animation-delay:4000ms]"
)}
numberOfMonths={2}
variantClassnames={Rainbow}
datesPerVariant={RainbowDates}
/>
</PageHeader>
</div>
)
}
export const revalidate = 3600
================================================
FILE: components/code.tsx
================================================
import * as React from "react"
import { unified } from "unified"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import rehypeStringify from "rehype-stringify"
import rehypePrettyCode from "rehype-pretty-code"
import { CopyButton } from "./copy-button"
export async function Code({
code,
toCopy,
dark = true,
}: {
code: string
toCopy?: string
dark?: boolean
}) {
const highlightedCode = await highlightCode(code, dark)
return (
<div className={`relative code-example-${dark ? "dark" : "light"}`}>
<pre
dangerouslySetInnerHTML={{
__html: highlightedCode,
}}
/>
{toCopy && (
<div className="absolute top-4 right-6">
<CopyButton value={toCopy} />
</div>
)}
</div>
)
}
async function highlightCode(code: string, dark: boolean) {
const file = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, {
keepBackground: false,
theme: dark ? "vesper" : "github-light",
})
.use(rehypeStringify)
.process(code)
return String(file)
}
================================================
FILE: components/copy-button.tsx
================================================
// Stolen from @shadcn/ui the man the machine!!
"use client"
import * as React from "react"
import { CheckIcon, CopyIcon } from "@radix-ui/react-icons"
import type { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu"
import { cn } from "@/lib/utils"
import { Button } from "./ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "./ui/dropdown-menu"
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
value: string
src?: string
}
export async function copyToClipboardWithMeta(value: string) {
window && window.isSecureContext && navigator.clipboard.writeText(value)
}
export function CopyButton({
value,
className,
src,
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
return (
<Button
size="icon"
variant="ghost"
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50",
className
)}
onClick={() => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? (
<CheckIcon className="h-3 w-3" />
) : (
<CopyIcon className="h-3 w-3" />
)}
</Button>
)
}
interface CopyWithClassNamesProps extends DropdownMenuTriggerProps {
value: string
classNames: string
className?: string
}
export function CopyWithClassNames({
value,
classNames,
className,
...props
}: CopyWithClassNamesProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
const copyToClipboard = React.useCallback((value: string) => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}, [])
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50",
className
)}
>
{hasCopied ? (
<CheckIcon className="h-3 w-3" />
) : (
<CopyIcon className="h-3 w-3" />
)}
<span className="sr-only">Copy</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyToClipboard(value)}>
Component
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyToClipboard(classNames)}>
Classname
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
interface CopyNpmCommandButtonProps extends DropdownMenuTriggerProps {
commands: {
__npmCommand__: string
__yarnCommand__: string
__pnpmCommand__: string
__bunCommand__: string
}
}
export function CopyNpmCommandButton({
commands,
className,
...props
}: CopyNpmCommandButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
const copyCommand = React.useCallback(
(value: string, pm: "npm" | "pnpm" | "yarn" | "bun") => {
copyToClipboardWithMeta(value)
setHasCopied(true)
},
[]
)
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className={cn(
"relative z-10 h-6 w-6 hover:bg-zinc-700 hover:text-zinc-50",
className
)}
>
{hasCopied ? (
<CheckIcon className="h-3 w-3" />
) : (
<CopyIcon className="h-3 w-3" />
)}
<span className="sr-only">Copy</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => copyCommand(commands.__npmCommand__, "npm")}
>
npm
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => copyCommand(commands.__yarnCommand__, "yarn")}
>
yarn
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => copyCommand(commands.__pnpmCommand__, "pnpm")}
>
pnpm
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => copyCommand(commands.__bunCommand__, "bun")}
>
bun
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
================================================
FILE: components/icons.tsx
================================================
type IconProps = React.HTMLAttributes<SVGElement>
export const Icons = {
logo: (props: IconProps) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" {...props}>
<rect width="256" height="256" fill="none" />
<line
x1="208"
y1="128"
x2="128"
y2="208"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
/>
<line
x1="192"
y1="40"
x2="40"
y2="192"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
/>
</svg>
),
twitter: (props: IconProps) => (
<svg
{...props}
height="23"
viewBox="0 0 1200 1227"
width="23"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" />
</svg>
),
gitHub: (props: IconProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
),
radix: (props: IconProps) => (
<svg viewBox="0 0 25 25" fill="none" {...props}>
<path
d="M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z"
fill="currentcolor"
></path>
<path d="M12 0H4V8H12V0Z" fill="currentcolor"></path>
<path
d="M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z"
fill="currentcolor"
></path>
</svg>
),
aria: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M13.966 22.624l-1.69-4.281H8.122l3.892-9.144 5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z" />
</svg>
),
npm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"
fill="currentColor"
/>
</svg>
),
yarn: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z"
fill="currentColor"
/>
</svg>
),
pnpm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z"
fill="currentColor"
/>
</svg>
),
react: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"
fill="currentColor"
/>
</svg>
),
tailwind: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z"
fill="currentColor"
/>
</svg>
),
google: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
fill="currentColor"
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
/>
</svg>
),
apple: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
),
paypal: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z"
fill="currentColor"
/>
</svg>
),
spinner: (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
),
}
================================================
FILE: components/page-header.tsx
================================================
import { cn } from "../lib/utils"
function PageHeader({
className,
children,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<section
className={cn(
"mx-auto flex max-w-[800px] flex-col items-center gap-2 py-8 md:py-12 md:pb-8 lg:py-18 lg:pb-16",
className
)}
{...props}
>
{children}
</section>
)
}
function PageHeaderNotifier({
className,
...props
}: React.HTMLAttributes<HTMLHeadingElement>) {
return (
<h1
className={cn(
"text-center text-xs font-semibold leading-tight md:text-sm lg:leading-[1.1] min-w-fit backdrop-blur-[2px] p-2 rounded mb-2",
className
)}
style={{
background: "rgba(99, 86, 36, 0.1)",
}}
{...props}
/>
)
}
function PageHeaderHeading({
className,
...props
}: React.HTMLAttributes<HTMLHeadingElement>) {
return (
<h1
className={cn(
"text-center text-3xl font-bold leading-tight tracking-tighter md:text-6xl lg:leading-[1.1] max-w-[330px] md:min-w-[750px] ",
className
)}
{...props}
/>
)
}
function PageHeaderDescription({
className,
...props
}: React.HTMLAttributes<HTMLParagraphElement>) {
return (
<p
className={cn(
"max-w-[750px] text-center text-base text-muted-foreground sm:text-lg text-pretty",
className
)}
{...props}
/>
)
}
function PageActions({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"flex w-full items-center justify-center space-x-4 py-4 md:pb-10",
className
)}
{...props}
/>
)
}
export {
PageHeader,
PageHeaderNotifier,
PageHeaderHeading,
PageHeaderDescription,
PageActions,
}
================================================
FILE: components/site-footer.tsx
================================================
import { siteConfig } from "../config/site"
export function SiteFooter() {
return (
<footer className="py-6 md:px-8 md:py-0">
<div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
<p className="text-balance text-center text-sm leading-loose text-muted-foreground md:text-left">
Built by{" "}
<a
href={siteConfig.links.twitter}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
GurbaazNandra
</a>
, website inspired from{" "}
<a
href="https://input-otp.rodz.dev/"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
rodz
</a>
. The source code is available on{" "}
<a
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
GitHub
</a>
.
</p>
</div>
</footer>
)
}
================================================
FILE: components/site-header.tsx
================================================
import Link from "next/link"
import { siteConfig } from "../config/site"
import { cn } from "../lib/utils"
import { buttonVariants } from "./ui/button"
import { Icons } from "./icons"
export function SiteHeader() {
return (
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-14 max-w-screen-2xl items-center">
<div className="flex flex-1 items-center justify-between space-x-2 md:justify-end">
<nav className="flex items-center">
<Link
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
>
<div
className={cn(
buttonVariants({
variant: "ghost",
}),
"w-9 px-0"
)}
>
<Icons.gitHub className="h-4 w-4" />
<span className="sr-only">GitHub</span>
</div>
</Link>
<Link
href={siteConfig.links.twitter}
target="_blank"
rel="noreferrer"
>
<div
className={cn(
buttonVariants({
variant: "ghost",
}),
"w-9 px-0"
)}
>
<Icons.twitter className="h-3 w-3 fill-current" />
<span className="sr-only">Twitter</span>
</div>
</Link>
</nav>
</div>
</div>
</header>
)
}
================================================
FILE: components/ui/button.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: components/ui/calendar-heatmap.tsx
================================================
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
// type utilities
type UnionKeys<T> = T extends T ? keyof T : never
type Expand<T> = T extends T ? { [K in keyof T]: T[K] } : never
type OneOf<T extends {}[]> = {
[K in keyof T]: Expand<
T[K] & Partial<Record<Exclude<UnionKeys<T[number]>, keyof T[K]>, never>>
>
}[number]
// types
export type Classname = string
export type WeightedDateEntry = {
date: Date
weight: number
}
interface IDatesPerVariant {
datesPerVariant: Date[][]
}
interface IWeightedDatesEntry {
weightedDates: WeightedDateEntry[]
}
type VariantDatesInput = OneOf<[IDatesPerVariant, IWeightedDatesEntry]>
export type CalendarProps = React.ComponentProps<typeof DayPicker> & {
variantClassnames: Classname[]
} & VariantDatesInput
/// utlity functions
function useModifers(
variantClassnames: Classname[],
datesPerVariant: Date[][]
): [Record<string, Date[]>, Record<string, string>] {
const noOfVariants = variantClassnames.length
const variantLabels = [...Array(noOfVariants)].map(
(_, idx) => `__variant${idx}`
)
const modifiers = variantLabels.reduce((acc, key, index) => {
acc[key] = datesPerVariant[index]
return acc
}, {} as Record<string, Date[]>)
const modifiersClassNames = variantLabels.reduce((acc, key, index) => {
acc[key] = variantClassnames[index]
return acc
}, {} as Record<string, string>)
return [modifiers, modifiersClassNames]
}
function categorizeDatesPerVariant(
weightedDates: WeightedDateEntry[],
noOfVariants: number
) {
const sortedEntries = weightedDates.sort((a, b) => a.weight - b.weight)
const categorizedRecord = [...Array(noOfVariants)].map(() => [] as Date[])
const minNumber = sortedEntries[0].weight
const maxNumber = sortedEntries[sortedEntries.length - 1].weight
const range = minNumber == maxNumber ? 1 : (maxNumber - minNumber) / noOfVariants;
sortedEntries.forEach((entry) => {
const category = Math.min(
Math.floor((entry.weight - minNumber) / range),
noOfVariants - 1
)
categorizedRecord[category].push(entry.date)
})
return categorizedRecord
}
function CalendarHeatmap({
variantClassnames,
datesPerVariant,
weightedDates,
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
const noOfVariants = variantClassnames.length
weightedDates = weightedDates ?? []
datesPerVariant =
datesPerVariant ?? categorizeDatesPerVariant(weightedDates, noOfVariants)
const [modifiers, modifiersClassNames] = useModifers(
variantClassnames,
datesPerVariant
)
return (
<DayPicker
modifiers={modifiers}
modifiersClassNames={modifiersClassNames}
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
),
day_range_end: "day-range-end",
// day_selected:
// "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
)
}
CalendarHeatmap.displayName = "CalendarHeatmap"
export { CalendarHeatmap }
================================================
FILE: components/ui/dropdown-menu.tsx
================================================
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
================================================
FILE: components/ui/select.tsx
================================================
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
================================================
FILE: components/ui/sonner.tsx
================================================
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}
export { Toaster }
================================================
FILE: components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: config/site.ts
================================================
export const siteConfig = {
title: "Shadcn Calendar Heatmap",
name: "gurbaaz27/shadcn-calendar-heatmap",
url: "https://shadcn-calendar-heatmap.vercel.app",
ogImage: "https://shadcn-calendar-heatmap.vercel.app/og.png",
description:
"Accessible. Unstyled. Customizable. Open Source. Build your own calendar heatmap effortlessly.",
links: {
twitter: "https://x.com/GurbaazNandra",
github: "https://github.com/gurbaaz27/shadcn-calendar-heatmap",
},
}
export type SiteConfig = typeof siteConfig
================================================
FILE: lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function randomDate(start: Date, end: Date) {
return new Date(
start.getTime() + Math.random() * (end.getTime() - start.getTime())
)
}
export function currentMonthFirstDate() {
const date = new Date()
return new Date(date.getFullYear(), date.getMonth(), 1)
}
export function currentMonthLastDate(month: number = 1) {
const date = new Date()
return new Date(date.getFullYear(), date.getMonth() + month, 0)
}
================================================
FILE: next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
================================================
FILE: package.json
================================================
{
"name": "shadcn-calendar-heatmap",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"geist": "^1.3.0",
"lucide-react": "^0.396.0",
"next": "14.2.4",
"next-themes": "^0.3.0",
"react": "^18",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
"rehype-pretty-code": "^0.13.2",
"rehype-stringify": "^10.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.4"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}
================================================
FILE: postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
================================================
FILE: public/llms.txt
================================================
# shadcn-calendar-heatmap
> A customizable calendar heatmap component built on top of react-day-picker, following shadcn/ui patterns. Accessible, unstyled by default, and fully customizable with Tailwind CSS.
## Overview
shadcn-calendar-heatmap is a React component that transforms the DayPicker calendar into a heatmap visualization. It allows you to display dates with varying intensities using custom color variants - perfect for GitHub contribution graphs, temperature heatmaps, activity tracking, and more.
## Installation
The component is designed to be copied directly into your project following the shadcn/ui philosophy. Copy the component from:
https://github.com/gurbaaz27/shadcn-calendar-heatmap/blob/main/components/ui/calendar-heatmap.tsx
### Dependencies
- react-day-picker (^8.10.1)
- tailwind-merge
- class-variance-authority
- lucide-react (for icons)
## Component API
### CalendarHeatmap Props
The component extends all props from `react-day-picker`'s DayPicker, plus:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `variantClassnames` | `string[]` | Yes | Array of Tailwind CSS classes for each intensity level |
| `datesPerVariant` | `Date[][]` | One of these | 2D array where each inner array contains dates for that variant |
| `weightedDates` | `WeightedDateEntry[]` | One of these | Array of `{ date: Date, weight: number }` objects |
| `numberOfMonths` | `number` | No | Number of months to display (default: 1) |
| `showOutsideDays` | `boolean` | No | Show days from adjacent months (default: true) |
**Note:** You must provide either `datesPerVariant` OR `weightedDates`, not both.
### WeightedDateEntry Type
```typescript
type WeightedDateEntry = {
date: Date
weight: number
}
```
## Usage Examples
### Basic GitHub-style Contribution Graph
```tsx
import { CalendarHeatmap } from "@/components/ui/calendar-heatmap"
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
datesPerVariant={[
[new Date('Jan 1, 2024'), new Date('Jan 15, 2024')], // Low intensity
[new Date('Jun 12, 2024'), new Date('July 1, 2024')], // Medium intensity
[new Date('Jan 19, 2024'), new Date('Apr 14, 2024')], // High intensity
]}
/>
```
### Using Weighted Dates (Auto-categorization)
When you have numeric data associated with dates, use `weightedDates`. The component automatically categorizes dates into variants based on their weights:
```tsx
<CalendarHeatmap
variantClassnames={[
"text-white hover:text-white bg-blue-300 hover:bg-blue-300",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-amber-400 hover:bg-amber-400",
"text-white hover:text-white bg-red-700 hover:bg-red-700",
]}
weightedDates={[
{ date: new Date('Jan 1, 2024'), weight: 2 },
{ date: new Date('Jan 15, 2024'), weight: 1.5 },
{ date: new Date('Jun 12, 2024'), weight: 8 },
{ date: new Date('July 1, 2024'), weight: 5 },
{ date: new Date('Jan 19, 2024'), weight: 6 },
{ date: new Date('Apr 19, 2024'), weight: 13.5 },
]}
/>
```
### Multi-month Display
```tsx
<CalendarHeatmap
numberOfMonths={3}
variantClassnames={[
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-green-500 hover:bg-green-500",
"text-white hover:text-white bg-green-700 hover:bg-green-700",
]}
datesPerVariant={[
[/* dates for variant 0 */],
[/* dates for variant 1 */],
[/* dates for variant 2 */],
]}
/>
```
### Rainbow Variant Example
```tsx
const Rainbow = [
"text-white hover:text-white bg-violet-400 hover:bg-violet-400",
"text-white hover:text-white bg-indigo-400 hover:bg-indigo-400",
"text-white hover:text-white bg-blue-400 hover:bg-blue-400",
"text-white hover:text-white bg-green-400 hover:bg-green-400",
"text-white hover:text-white bg-yellow-400 hover:bg-yellow-400",
"text-white hover:text-white bg-orange-400 hover:bg-orange-400",
"text-white hover:text-white bg-red-400 hover:bg-red-400",
]
<CalendarHeatmap
numberOfMonths={2}
variantClassnames={Rainbow}
datesPerVariant={rainbowDates}
/>
```
## How It Works
1. **Variant Classes**: Each string in `variantClassnames` represents a color/style intensity level. The component creates DayPicker modifiers for each variant.
2. **Date Mapping**:
- With `datesPerVariant`: Dates in the first array get the first variant class, second array gets second class, etc.
- With `weightedDates`: The component sorts dates by weight, divides the range into equal segments based on the number of variants, and assigns each date to its corresponding variant.
3. **Styling**: The component uses Tailwind CSS classes. Include both normal and hover states in your variant classes for consistent interaction feedback.
## Customization
### Custom Styling
Override default calendar styles via the `classNames` prop:
```tsx
<CalendarHeatmap
variantClassnames={[...]}
datesPerVariant={[...]}
classNames={{
months: "flex flex-col",
month: "space-y-2",
caption: "text-lg font-bold",
// ... other DayPicker classNames
}}
/>
```
### Additional Props
Since the component extends DayPicker, you can use any DayPicker prop:
```tsx
<CalendarHeatmap
variantClassnames={[...]}
datesPerVariant={[...]}
fromDate={new Date('2024-01-01')}
toDate={new Date('2024-12-31')}
disabled={[new Date('2024-07-04')]}
onDayClick={(day) => console.log(day)}
/>
```
## Links
- Demo: https://shadcn-calendar-heatmap.vercel.app
- GitHub: https://github.com/gurbaaz27/shadcn-calendar-heatmap
- Component Source: https://github.com/gurbaaz27/shadcn-calendar-heatmap/blob/main/components/ui/calendar-heatmap.tsx
## License
MIT
================================================
FILE: tailwind.config.ts
================================================
import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"fade-in": {
from: {
opacity: "0",
},
to: { opacity: "1" },
},
"fade-up": {
from: {
opacity: "0",
transform: "translateY(var(--fade-distance, .25rem))",
},
to: { opacity: "1", transform: "translateY(0)" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"fade-in": "fade-in 0.3s ease-out forwards",
"fade-up": "fade-up 1s ease-out forwards",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
gitextract_l_cd6g81/ ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── (components)/ │ │ ├── copy-llms-button.tsx │ │ ├── example-code.tsx │ │ └── example-variants.ts │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── bun.lockb ├── components/ │ ├── code.tsx │ ├── copy-button.tsx │ ├── icons.tsx │ ├── page-header.tsx │ ├── site-footer.tsx │ ├── site-header.tsx │ └── ui/ │ ├── button.tsx │ ├── calendar-heatmap.tsx │ ├── dropdown-menu.tsx │ ├── select.tsx │ └── sonner.tsx ├── components.json ├── config/ │ └── site.ts ├── lib/ │ └── utils.ts ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── public/ │ └── llms.txt ├── tailwind.config.ts └── tsconfig.json
SYMBOL INDEX (42 symbols across 15 files)
FILE: app/(components)/copy-llms-button.tsx
type CopyLlmsButtonProps (line 8) | interface CopyLlmsButtonProps {
function CopyLlmsButton (line 13) | function CopyLlmsButton({ content, className }: CopyLlmsButtonProps) {
FILE: app/(components)/example-code.tsx
function ExampleCode (line 41) | function ExampleCode() {
FILE: app/layout.tsx
function RootLayout (line 70) | function RootLayout({
FILE: app/page.tsx
function getRepoStarCount (line 36) | async function getRepoStarCount() {
function IndexPage (line 48) | async function IndexPage() {
FILE: components/code.tsx
function Code (line 9) | async function Code({
function highlightCode (line 36) | async function highlightCode(code: string, dark: boolean) {
FILE: components/copy-button.tsx
type CopyButtonProps (line 17) | interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
function copyToClipboardWithMeta (line 22) | async function copyToClipboardWithMeta(value: string) {
function CopyButton (line 26) | function CopyButton({
type CopyWithClassNamesProps (line 64) | interface CopyWithClassNamesProps extends DropdownMenuTriggerProps {
function CopyWithClassNames (line 70) | function CopyWithClassNames({
type CopyNpmCommandButtonProps (line 120) | interface CopyNpmCommandButtonProps extends DropdownMenuTriggerProps {
function CopyNpmCommandButton (line 129) | function CopyNpmCommandButton({
FILE: components/icons.tsx
type IconProps (line 1) | type IconProps = React.HTMLAttributes<SVGElement>
FILE: components/page-header.tsx
function PageHeader (line 3) | function PageHeader({
function PageHeaderNotifier (line 21) | function PageHeaderNotifier({
function PageHeaderHeading (line 39) | function PageHeaderHeading({
function PageHeaderDescription (line 54) | function PageHeaderDescription({
function PageActions (line 69) | function PageActions({
FILE: components/site-footer.tsx
function SiteFooter (line 3) | function SiteFooter() {
FILE: components/site-header.tsx
function SiteHeader (line 8) | function SiteHeader() {
FILE: components/ui/button.tsx
type ButtonProps (line 36) | interface ButtonProps
FILE: components/ui/calendar-heatmap.tsx
type UnionKeys (line 11) | type UnionKeys<T> = T extends T ? keyof T : never
type Expand (line 12) | type Expand<T> = T extends T ? { [K in keyof T]: T[K] } : never
type OneOf (line 13) | type OneOf<T extends {}[]> = {
type Classname (line 20) | type Classname = string
type WeightedDateEntry (line 21) | type WeightedDateEntry = {
type IDatesPerVariant (line 26) | interface IDatesPerVariant {
type IWeightedDatesEntry (line 29) | interface IWeightedDatesEntry {
type VariantDatesInput (line 33) | type VariantDatesInput = OneOf<[IDatesPerVariant, IWeightedDatesEntry]>
type CalendarProps (line 35) | type CalendarProps = React.ComponentProps<typeof DayPicker> & {
function useModifers (line 40) | function useModifers(
function categorizeDatesPerVariant (line 63) | function categorizeDatesPerVariant(
function CalendarHeatmap (line 86) | function CalendarHeatmap({
FILE: components/ui/sonner.tsx
type ToasterProps (line 6) | type ToasterProps = React.ComponentProps<typeof Sonner>
FILE: config/site.ts
type SiteConfig (line 13) | type SiteConfig = typeof siteConfig
FILE: lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
function randomDate (line 8) | function randomDate(start: Date, end: Date) {
function currentMonthFirstDate (line 14) | function currentMonthFirstDate() {
function currentMonthLastDate (line 19) | function currentMonthLastDate(month: number = 1) {
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (85K chars).
[
{
"path": ".eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": ".gitignore",
"chars": 391,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "LICENSE",
"chars": 1077,
"preview": "MIT License\n\nCopyright (c) 2024 Gurbaaz Singh Nandra\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 5827,
"preview": "# shadcn-calendar-heatmap\n\nA modern, customizable calendar heatmap component built on top of [react-day-picker](https://"
},
{
"path": "app/(components)/copy-llms-button.tsx",
"chars": 905,
"preview": "\"use client\"\n\nimport { useState } from \"react\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { cn } fro"
},
{
"path": "app/(components)/example-code.tsx",
"chars": 2291,
"preview": "import { Code } from \"@/components/code\"\n\nconst tsx = `import { CalendarHeatmap } from \"@/components/ui/calendar-heatmap"
},
{
"path": "app/(components)/example-variants.ts",
"chars": 2205,
"preview": "import {\n currentMonthFirstDate,\n currentMonthLastDate,\n randomDate,\n} from \"@/lib/utils\"\n\nexport const GithubStreak "
},
{
"path": "app/globals.css",
"chars": 3567,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --f"
},
{
"path": "app/layout.tsx",
"chars": 2121,
"preview": "import \"./globals.css\"\nimport type { Metadata } from \"next\"\nimport { cn } from \"@/lib/utils\"\nimport { GeistSans } from \""
},
{
"path": "app/page.tsx",
"chars": 5485,
"preview": "import {\n PageActions,\n PageHeader,\n PageHeaderDescription,\n PageHeaderHeading,\n PageHeaderNotifier,\n} from \"@/comp"
},
{
"path": "components/code.tsx",
"chars": 1120,
"preview": "import * as React from \"react\"\nimport { unified } from \"unified\"\nimport remarkParse from \"remark-parse\"\nimport remarkReh"
},
{
"path": "components/copy-button.tsx",
"chars": 4700,
"preview": "// Stolen from @shadcn/ui the man the machine!!\n\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, CopyIco"
},
{
"path": "components/icons.tsx",
"chars": 12772,
"preview": "type IconProps = React.HTMLAttributes<SVGElement>\n\nexport const Icons = {\n logo: (props: IconProps) => (\n <svg xmlns"
},
{
"path": "components/page-header.tsx",
"chars": 1777,
"preview": "import { cn } from \"../lib/utils\"\n\nfunction PageHeader({\n className,\n children,\n ...props\n}: React.HTMLAttributes<HTM"
},
{
"path": "components/site-footer.tsx",
"chars": 1194,
"preview": "import { siteConfig } from \"../config/site\"\n\nexport function SiteFooter() {\n return (\n <footer className=\"py-6 md:px"
},
{
"path": "components/site-header.tsx",
"chars": 1666,
"preview": "import Link from \"next/link\"\n\nimport { siteConfig } from \"../config/site\"\nimport { cn } from \"../lib/utils\"\nimport { but"
},
{
"path": "components/ui/button.tsx",
"chars": 1835,
"preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
},
{
"path": "components/ui/calendar-heatmap.tsx",
"chars": 5082,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { DayPicker"
},
{
"path": "components/ui/dropdown-menu.tsx",
"chars": 7309,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimpo"
},
{
"path": "components/ui/select.tsx",
"chars": 5629,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, C"
},
{
"path": "components/ui/sonner.tsx",
"chars": 894,
"preview": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = Rea"
},
{
"path": "components.json",
"chars": 341,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"default\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {\n"
},
{
"path": "config/site.ts",
"chars": 516,
"preview": "export const siteConfig = {\n title: \"Shadcn Calendar Heatmap\",\n name: \"gurbaaz27/shadcn-calendar-heatmap\",\n url: \"htt"
},
{
"path": "lib/utils.ts",
"chars": 599,
"preview": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: Cla"
},
{
"path": "next.config.mjs",
"chars": 92,
"preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n"
},
{
"path": "package.json",
"chars": 1242,
"preview": "{\n \"name\": \"shadcn-calendar-heatmap\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n "
},
{
"path": "postcss.config.mjs",
"chars": 135,
"preview": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n plugins: {\n tailwindcss: {},\n },\n};\n\nexport d"
},
{
"path": "public/llms.txt",
"chars": 5912,
"preview": "# shadcn-calendar-heatmap\n\n> A customizable calendar heatmap component built on top of react-day-picker, following shadc"
},
{
"path": "tailwind.config.ts",
"chars": 2622,
"preview": "import type { Config } from \"tailwindcss\"\n\nconst config = {\n darkMode: [\"class\"],\n content: [\n \"./pages/**/*.{ts,ts"
},
{
"path": "tsconfig.json",
"chars": 574,
"preview": "{\n \"compilerOptions\": {\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n "
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the gurbaaz27/shadcn-calendar-heatmap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (78.0 KB), approximately 25.6k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.