From 95f40e54302dd1bb9e6a35db4cb9be189d7467da Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 17:21:57 +0400 Subject: [PATCH 1/5] feature: initialise shadcn/ui with theme configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dependencies: - Add class-variance-authority, clsx, tailwind-merge for component styling - Add lucide-react icon library - Add tw-animate-css for animation utilities - Pin TypeScript to 5.9.3 Configuration: - Add components.json with new-york style and slate base colour - Add cn() utility in lib/utils.ts - Enable cacheComponents in next.config.ts for "use cache" directive Styling: - Import tw-animate-css in globals.css - Reorganise @theme inline block placement - Remove Consolas from mono font fallback stack - Remove h1-bold class from home page heading Sets up the foundation for adding shadcn/ui components. Enables Next.js caching features including Partial Prerendering. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/globals.css | 102 ++++++++++++++++++++++------------------------ app/page.tsx | 2 +- components.json | 22 ++++++++++ lib/utils.ts | 6 +++ next.config.ts | 5 ++- package-lock.json | 59 ++++++++++++++++++++++++++- package.json | 9 +++- 7 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 components.json create mode 100644 lib/utils.ts diff --git a/app/globals.css b/app/globals.css index cc9c323..4d76cf6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,15 +1,61 @@ @import "tailwindcss"; +@import "tw-animate-css"; /* Applies dark: styles when element is .dark or inside .dark */ @custom-variant dark (&:where(.dark, .dark *)); +@theme inline { + /* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */ + /* E.g. --color-background → bg-background, text-background, border-background, etc. */ + /* "inline" (v4) means values are embedded here, not in a separate CSS layer */ + --font-display: var(--font-space-grotesk), Arial, sans-serif; + --font-sans-serif: var(--font-inter), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-jetbrains-mono), ui-monospace, Menlo, monospace; + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); +} + :root { /* Creates CSS variables available to all elements (no utility generation) */ --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.129 0.042 264.695); - --card: oklch(1 0 0); --card-foreground: oklch(0.129 0.042 264.695); --popover: oklch(1 0 0); @@ -26,14 +72,12 @@ --border: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508); --ring: oklch(0.704 0.04 256.788); - --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.984 0.003 247.858); - --sidebar-foreground: oklch(0.129 0.042 264.695); --sidebar-primary: oklch(0.208 0.042 265.755); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); @@ -78,56 +122,6 @@ --sidebar-ring: oklch(0.551 0.027 264.364); } -@theme inline { - /* Maps root and .dark variables to Tailwind utility classes */ - /* E.g. --color-background → bg-background, text-background, border-background, etc. */ - /* The keyword "inline" (v4) means "define theme values directly in this CSS file" */ - --color-background: var(--background); - --color-foreground: var(--foreground); - - --font-display: var(--font-space-grotesk), Arial, sans-serif; - --font-sans-serif: var(--font-inter), ui-sans-serif, system-ui, sans-serif; - --font-mono: - var(--font-jetbrains-mono), ui-monospace, Menlo, Consolas, monospace; - - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); -} - @layer base { /* Applies styles to actual elements (unlike :root/.dark which only store variables) */ /* Low specificity - can be overridden by components and utilities */ @@ -137,7 +131,7 @@ @apply border-border outline-ring/50; } body { - /* Page background and text color (text color inherited) */ + /* Page background and text color */ @apply bg-background text-foreground; } diff --git a/app/page.tsx b/app/page.tsx index 04764ac..c30eff3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ const Home = () => ( <> -

Welcome to the world of Next.js

+

Welcome to the world of Next.js

); diff --git a/components.json b/components.json new file mode 100644 index 0000000..46c5316 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..365058c --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/next.config.ts b/next.config.ts index 66e1566..dc94148 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,8 +1,11 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ reactCompiler: true, + + // Enables "use cache" directive, cacheLife(), cacheTag(), and Partial Prerendering. + // Routes are dynamic by default; use "use cache" to opt into caching. + cacheComponents: true, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index e8f1031..0a72f2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,14 @@ "dependencies": { "@vercel/analytics": "^1.6.0", "@vercel/speed-insights": "^1.3.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.555.0", "next": "^16.0.6", "next-themes": "^0.4.6", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@biomejs/biome": "^2.3.8", @@ -32,7 +36,8 @@ "lefthook": "^2.0.4", "markdownlint-cli2": "^0.19.1", "tailwindcss": "^4", - "typescript": "^5", + "tw-animate-css": "^1.4.0", + "typescript": "5.9.3", "vite-tsconfig-paths": "^5.1.4", "vitest": "^4.0.14" } @@ -5970,6 +5975,18 @@ "consola": "^3.2.3" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -6071,6 +6088,15 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -8607,6 +8633,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.555.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.555.0.tgz", + "integrity": "sha512-D8FvHUGbxWBRQM90NZeIyhAvkFfsh3u9ekrMvJ30Z6gnpBHS6HC6ldLg7tL45hwiIz/u66eKDtdA23gwwGsAHA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -12025,6 +12060,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", @@ -12264,6 +12309,16 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-fest": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.2.0.tgz", diff --git a/package.json b/package.json index 14c2af2..c340aa9 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,14 @@ "dependencies": { "@vercel/analytics": "^1.6.0", "@vercel/speed-insights": "^1.3.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.555.0", "next": "^16.0.6", "next-themes": "^0.4.6", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@biomejs/biome": "^2.3.8", @@ -46,7 +50,8 @@ "lefthook": "^2.0.4", "markdownlint-cli2": "^0.19.1", "tailwindcss": "^4", - "typescript": "^5", + "tw-animate-css": "^1.4.0", + "typescript": "5.9.3", "vite-tsconfig-paths": "^5.1.4", "vitest": "^4.0.14" } From 74826f3c6d8fae18ebc1707549e757ccbcaf40f4 Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 18:39:37 +0400 Subject: [PATCH 2/5] refactor: use Lucide Sun icon in theme toggle Theme Toggle: - Replace custom SunIcon SVG with lucide-react Sun component - Retain filled/outline behaviour based on active theme Aligns with project icon library configuration in components.json. --- components/theme-toggle.tsx | 38 +++++-------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx index d3a1cc9..5b0f6b2 100644 --- a/components/theme-toggle.tsx +++ b/components/theme-toggle.tsx @@ -1,40 +1,9 @@ "use client"; +import { Sun } from "lucide-react"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; -function SunIcon({ - filled, - className, -}: { - filled?: boolean; - className?: string; -}) { - return ( - - ); -} - export function ThemeToggle() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); @@ -52,7 +21,10 @@ export function ThemeToggle() { className="inline-flex size-10 items-center justify-center rounded-md text-foreground/70 transition-colors hover:bg-muted hover:text-foreground" aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`} > - + ); } From a66d3e4bbf44625ce87a0ea8d922750e3ba5b57c Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 20:37:56 +0400 Subject: [PATCH 3/5] feature: add navbar with shadcn/ui button and custom theme tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Navigation: - Create sticky navbar with three-column grid (logo, search placeholder, theme toggle) - Move theme-toggle into components/navigation/ directory - Refactor theme toggle to use shadcn/ui Button with ghost variant Components: - Add shadcn/ui button component with Radix slot support - Remove unused demo button component Theme: - Set custom orange primary colour for brand identity - Adjust background, sidebar, and foreground tokens for contrast - Add hex comments to OKLCH values for debugging Establishes site-wide navigation structure and introduces shadcn/ui as the component foundation. Theme tokens provide distinct brand colour while maintaining the centralised CSS variable architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/globals.css | 20 +++---- app/layout.tsx | 4 +- components/button.tsx | 34 ----------- components/navigation/navbar/index.tsx | 30 ++++++++++ components/{ => navigation}/theme-toggle.tsx | 10 ++-- components/ui/button.tsx | 60 ++++++++++++++++++++ package-lock.json | 36 +++++++++++- package.json | 1 + 8 files changed, 145 insertions(+), 50 deletions(-) delete mode 100644 components/button.tsx create mode 100644 components/navigation/navbar/index.tsx rename components/{ => navigation}/theme-toggle.tsx (76%) create mode 100644 components/ui/button.tsx diff --git a/app/globals.css b/app/globals.css index 4d76cf6..cf673cf 100644 --- a/app/globals.css +++ b/app/globals.css @@ -54,14 +54,14 @@ /* Creates CSS variables available to all elements (no utility generation) */ --radius: 0.625rem; - --background: oklch(1 0 0); + --background: oklch(0.994 0 0); /* #FDFDFD */ --foreground: oklch(0.129 0.042 264.695); --card: oklch(1 0 0); --card-foreground: oklch(0.129 0.042 264.695); --popover: oklch(1 0 0); --popover-foreground: oklch(0.129 0.042 264.695); - --primary: oklch(0.208 0.042 265.755); - --primary-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.7089 0.1967 46.81); + --primary-foreground: oklch(1 0 0); --secondary: oklch(0.968 0.007 247.896); --secondary-foreground: oklch(0.208 0.042 265.755); --muted: oklch(0.968 0.007 247.896); @@ -77,8 +77,8 @@ --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.984 0.003 247.858); - --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar: oklch(1 0 0); /* #FFFFFF */ + --sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */ --sidebar-primary: oklch(0.208 0.042 265.755); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.968 0.007 247.896); @@ -89,14 +89,14 @@ .dark { /* Overrides CSS variables when .dark class is present */ - --background: oklch(0.129 0.042 264.695); + --background: oklch(0 0 0); /* #000000 */ --foreground: oklch(0.984 0.003 247.858); --card: oklch(0.208 0.042 265.755); --card-foreground: oklch(0.984 0.003 247.858); --popover: oklch(0.208 0.042 265.755); --popover-foreground: oklch(0.984 0.003 247.858); - --primary: oklch(0.929 0.013 255.508); - --primary-foreground: oklch(0.208 0.042 265.755); + --primary: oklch(0.7089 0.1967 46.81); + --primary-foreground: oklch(1 0 0); --secondary: oklch(0.279 0.041 260.031); --secondary-foreground: oklch(0.984 0.003 247.858); --muted: oklch(0.279 0.041 260.031); @@ -112,8 +112,8 @@ --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.208 0.042 265.755); - --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar: oklch(0.178 0.013 270.6); /* #0F1117 */ + --sidebar-foreground: oklch(1 0 0); /* #FFFFFF */ --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.279 0.041 260.031); diff --git a/app/layout.tsx b/app/layout.tsx index 2b7d4e8..265fbf2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,9 +1,10 @@ import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; import type { Metadata } from "next"; -import "@/app/globals.css"; import { inter, jetbrainsMono, spaceGrotesk } from "@/app/fonts"; +import { Navbar } from "@/components/navigation/navbar"; import { ThemeProvider } from "@/components/theme-provider"; +import "@/app/globals.css"; export const metadata: Metadata = { title: "Devflow", @@ -29,6 +30,7 @@ export default function RootLayout({ enableSystem disableTransitionOnChange > + {children} diff --git a/components/button.tsx b/components/button.tsx deleted file mode 100644 index 7af8bc2..0000000 --- a/components/button.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Demo component — not from a UI library. Replace as needed. - -type ButtonProps = { - variant?: "primary" | "secondary"; - href: string; - icon?: React.ReactNode; - children: React.ReactNode; -}; - -export function Button({ - variant = "primary", - href, - icon, - children, -}: ButtonProps) { - const base = - "flex h-12 w-full items-center justify-center rounded-full px-5 font-display transition-colors md:w-[170px]"; - const styles = { - primary: "gap-2 bg-foreground text-background hover:bg-foreground/80", - secondary: "border border-border hover:bg-muted", - }; - - return ( - - {icon} - {children} - - ); -} diff --git a/components/navigation/navbar/index.tsx b/components/navigation/navbar/index.tsx new file mode 100644 index 0000000..811ce8a --- /dev/null +++ b/components/navigation/navbar/index.tsx @@ -0,0 +1,30 @@ +import Image from "next/image"; +import Link from "next/link"; +import { ThemeToggle } from "@/components/navigation/theme-toggle"; + +export const Navbar = () => ( + +); diff --git a/components/theme-toggle.tsx b/components/navigation/theme-toggle.tsx similarity index 76% rename from components/theme-toggle.tsx rename to components/navigation/theme-toggle.tsx index 5b0f6b2..3665de6 100644 --- a/components/theme-toggle.tsx +++ b/components/navigation/theme-toggle.tsx @@ -3,6 +3,7 @@ import { Sun } from "lucide-react"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; export function ThemeToggle() { const { theme, setTheme } = useTheme(); @@ -15,16 +16,17 @@ export function ThemeToggle() { } return ( - + ); } diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..c6f2d89 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,60 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +} + +export { Button, buttonVariants }; diff --git a/package-lock.json b/package-lock.json index 0a72f2d..bc41256 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "nextjs-base", "version": "0.1.0", "dependencies": { + "@radix-ui/react-slot": "^1.2.4", "@vercel/analytics": "^1.6.0", "@vercel/speed-insights": "^1.3.0", "class-variance-authority": "^0.7.1", @@ -3594,6 +3595,39 @@ "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", "license": "MIT" }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -4677,7 +4711,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { diff --git a/package.json b/package.json index c340aa9..c758bad 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test": "npm run test:unit && npm run test:e2e" }, "dependencies": { + "@radix-ui/react-slot": "^1.2.4", "@vercel/analytics": "^1.6.0", "@vercel/speed-insights": "^1.3.0", "class-variance-authority": "^0.7.1", From c27561116b7c9518ed8aaba5263cfd254542a4ad Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 21:03:07 +0400 Subject: [PATCH 4/5] rules: rename feature prefix to feat in commit template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shortens the feature commit prefix from "feature:" to "feat:" for consistency with conventional commit standards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/commands/commit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md index f9fb5ee..dd1d3f7 100644 --- a/.claude/commands/commit.md +++ b/.claude/commands/commit.md @@ -45,7 +45,7 @@ description: Create git commit for staged changes using template - `style:` code formatting, visual consistency, linting fixes; no functional change - `chore:` dev workflow, workspace config, dependency updates, dev tools e.g. `.vscode/**/*`, `pyproject.toml`, `.gitignore` - `docs:` documentation changes only e.g. `README.md`, `docs/**/*.md`, `x_docs/**/*.md` -- `feature:` new feature for users (adds functionality) +- `feat:` new feature for users (adds functionality) From f6e53e65e60d26906b26c2c557ea4be2a5eef6ef Mon Sep 17 00:00:00 2001 From: Michelle Date: Wed, 3 Dec 2025 06:14:50 +0400 Subject: [PATCH 5/5] refactor: coderabbit | improve navbar accessibility and branding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add aria-label to nav element for screen reader landmark identification - Fix logo alt text capitalisation to match "DevFlow" branding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/navigation/navbar/index.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/navigation/navbar/index.tsx b/components/navigation/navbar/index.tsx index 811ce8a..8ba76e3 100644 --- a/components/navigation/navbar/index.tsx +++ b/components/navigation/navbar/index.tsx @@ -3,14 +3,17 @@ import Link from "next/link"; import { ThemeToggle } from "@/components/navigation/theme-toggle"; export const Navbar = () => ( -