From 769d61ecc4cfabac3c87ea3f3c57ebc4cda30de6 Mon Sep 17 00:00:00 2001 From: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:01:46 +0600 Subject: [PATCH 1/3] chore(main): release devui 1.3.0 (#185) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d0a2c8d..6a7753e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.2.0" + ".": "1.3.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d8b43..fdd116c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.3.0](https://github.com/fahimahammed/DevUI/compare/devui-v1.2.0...devui-v1.3.0) (2025-10-05) + + +### Features + +* Feat/theme switcher 17 ([#156](https://github.com/fahimahammed/DevUI/issues/156)) ([d340933](https://github.com/fahimahammed/DevUI/commit/d3409330a13143c35658e38701189f0e6d3e433b)) +* Feature/ai powered snippet ([#177](https://github.com/fahimahammed/DevUI/issues/177)) ([1f4d4fa](https://github.com/fahimahammed/DevUI/commit/1f4d4fac3293eadd469ab24917a50a03f23b77e0)) +* Tooltip ([#172](https://github.com/fahimahammed/DevUI/issues/172)) ([c0bb4f1](https://github.com/fahimahammed/DevUI/commit/c0bb4f1c23832e989d9d2ca7c4f716355bee03eb)) + + +### Bug Fixes + +* fix merge conflict ([78028f8](https://github.com/fahimahammed/DevUI/commit/78028f8dd2a5b0b20c26c3f88aece464881f3ffc)) + ## [1.2.0](https://github.com/fahimahammed/DevUI/compare/devui-v1.1.0...devui-v1.2.0) (2025-10-04) diff --git a/package-lock.json b/package-lock.json index 2bf0002..f0e96b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "devui", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "devui", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", diff --git a/package.json b/package.json index 193902a..fab2690 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "devui", - "version": "1.2.0", + "version": "1.3.0", "private": true, "scripts": { "dev": "next dev --turbopack", From 0a7d2d275c775e00a618107a79f137e2999dbf4c Mon Sep 17 00:00:00 2001 From: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:30:17 +0600 Subject: [PATCH 2/3] chore: release (#187) * feat: Feat/button animations 19 (#183) * fix: Add Dropdown / Select Component #11 (#33) * feat(button): add hover and active micro-animations --------- Co-authored-by: Yashveer Singh * feat(UI): improve keyboard accessibility across components (#182) * upstream * feat: Feature/ai powered snippet (#177) * fix: Add Dropdown / Select Component #11 (#33) * fix(calendar): use deterministic ISO date for data-day to prevent hydration mismatch * fetch * chore: release 1.1.0 (#174) * chore: sync with main branch (#159) * adding sidebar (#110) (#112) Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> * V1.0.3 (#130) * Add light/dark theme toggle in header (#121) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * Refactored Code Snippet UI (#122) * Made Header & Cards more visually appealing * Improved Search & Filter Design * Formatted Code * Made theme button bigger * Changed animation settings for Header Text * Minor UI Adjustments * Redesigned the UI for code snippets * Used shiki to get more modern code snippets with highlighting * Refactored Code Snippet UI --------- Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S * release: new update (#154) * fix: sync code with prod (#142) * adding sidebar (#110) (#112) Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> * V1.0.3 (#130) * Add light/dark theme toggle in header (#121) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * Refactored Code Snippet UI (#122) * Made Header & Cards more visually appealing * Improved Search & Filter Design * Formatted Code * Made theme button bigger * Changed animation settings for Header Text * Minor UI Adjustments * Redesigned the UI for code snippets * Used shiki to get more modern code snippets with highlighting * Refactored Code Snippet UI --------- Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S * docs(readme): add contributors section with contrib.rocks auto-generated image * feat:NEW COMPONENT (#143) * Create SECURITY.md * feat: Feature/tooltip and fab components (#144) * NEW COMPONENT * add @radix-ui/react-tooltip and related components` * feat: Add Versatile File Upload Component with Drag-and-Drop & Progress (#147) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * File Upload Drag And Drop * feat: add skeleton loading support to ComponentCard (#134) * Feature/fix slider invert direction, issue #56 (#150) * fix-hydration issue for calendar * fix/slider invert direction and visibility issue * Fixed Dark Mode UI Issue (#151) --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 * sync (#161) * adding sidebar (#110) (#112) Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> * V1.0.3 (#130) * Add light/dark theme toggle in header (#121) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * Refactored Code Snippet UI (#122) * Made Header & Cards more visually appealing * Improved Search & Filter Design * Formatted Code * Made theme button bigger * Changed animation settings for Header Text * Minor UI Adjustments * Redesigned the UI for code snippets * Used shiki to get more modern code snippets with highlighting * Refactored Code Snippet UI --------- Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S * release: new update (#154) * fix: sync code with prod (#142) * adding sidebar (#110) (#112) Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> * V1.0.3 (#130) * Add light/dark theme toggle in header (#121) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * Refactored Code Snippet UI (#122) * Made Header & Cards more visually appealing * Improved Search & Filter Design * Formatted Code * Made theme button bigger * Changed animation settings for Header Text * Minor UI Adjustments * Redesigned the UI for code snippets * Used shiki to get more modern code snippets with highlighting * Refactored Code Snippet UI --------- Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S * docs(readme): add contributors section with contrib.rocks auto-generated image * feat:NEW COMPONENT (#143) * Create SECURITY.md * feat: Feature/tooltip and fab components (#144) * NEW COMPONENT * add @radix-ui/react-tooltip and related components` * feat: Add Versatile File Upload Component with Drag-and-Drop & Progress (#147) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * File Upload Drag And Drop * feat: add skeleton loading support to ComponentCard (#134) * Feature/fix slider invert direction, issue #56 (#150) * fix-hydration issue for calendar * fix/slider invert direction and visibility issue * Fixed Dark Mode UI Issue (#151) --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 * chore: update ci --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 * chore(stage): release devui 1.0.0 (#160) * release please update * chore: remove files * Fixed Theme Selector in the HomePage (#168) * Feat/animated search 23 (#167) * fix: Add Dropdown / Select Component #11 (#33) * chore(main): release devui 1.1.0 (#166) * feat(header): add animated live search bar * Merge branch 'feat/theme-switcher-17' and resolve conflict --------- Co-authored-by: Yashveer Singh Co-authored-by: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> * Added duplicate issue workflow (#170) * Enhance BackToTopButton with scroll progress indicator and improved animations (#169) * Add: Sonner (#171) * fix button * add: drawer * add: toaster component * add: Sonner * chore: update ci --------- Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 Co-authored-by: Soumya Jaiswal <130769536+nios-x@users.noreply.github.com> Co-authored-by: khushthecoder Co-authored-by: Yashveer Singh Co-authored-by: Debasish Mahata <137865254+debasish5452v@users.noreply.github.com> Co-authored-by: Dipanshu Rawat * Added AI-Powered Code Snippet Generator for Components * Update Header.tsx * Update components.tsx * Update Header.tsx * Update components.tsx * Update components.tsx --------- Co-authored-by: Yashveer Singh Co-authored-by: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 Co-authored-by: Soumya Jaiswal <130769536+nios-x@users.noreply.github.com> Co-authored-by: khushthecoder Co-authored-by: Debasish Mahata <137865254+debasish5452v@users.noreply.github.com> Co-authored-by: Dipanshu Rawat * feat: Tooltip (#172) * add: tooltip component * add: tooltip * keyboard accessibility, ARIA support, and focus visibility across all major interactive UI components --------- Co-authored-by: Yashveer Singh Co-authored-by: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 Co-authored-by: Soumya Jaiswal <130769536+nios-x@users.noreply.github.com> Co-authored-by: khushthecoder Co-authored-by: Debasish Mahata <137865254+debasish5452v@users.noreply.github.com> Co-authored-by: Dipanshu Rawat * feat: Typography (#186) --------- Co-authored-by: khushthecoder Co-authored-by: Yashveer Singh Co-authored-by: Vishal Gaikwad <154438441+ivishal-g@users.noreply.github.com> Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S Co-authored-by: Durva Kadam Co-authored-by: Mjtbvs <159575377+Nishat30@users.noreply.github.com> Co-authored-by: dhruvil-1207 Co-authored-by: Soumya Jaiswal <130769536+nios-x@users.noreply.github.com> Co-authored-by: Debasish Mahata <137865254+debasish5452v@users.noreply.github.com> Co-authored-by: Dipanshu Rawat --- src/app/globals.css | 133 ++++++++++--- src/components/CodeBlock.tsx | 156 ++++++++++----- src/components/ui/button.tsx | 9 +- src/components/ui/calendar.tsx | 124 ++++++------ src/components/ui/dialog.tsx | 113 ++++------- src/components/ui/menubar.tsx | 169 ++++++++-------- src/components/ui/tabs.tsx | 1 + src/data/components.tsx | 350 ++++++++++++++++++++++++++++----- 8 files changed, 708 insertions(+), 347 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 4f804f4..9634c0e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -127,44 +127,133 @@ /* CSS Variables for Theme-Aware Colors */ :root { - /* Light Theme Colors (e.g., matching a background of gray-50/100) */ --skeleton-bg-start: #e5e7eb; /* gray-200 */ --skeleton-bg-shimmer: #f3f4f6; /* gray-100 */ } .dark { - /* Dark Theme Colors (e.g., matching a background of gray-900/800) */ --skeleton-bg-start: #374151; /* gray-700 */ --skeleton-bg-shimmer: #4b5563; /* gray-600 */ } -/* Base class applied to the skeleton element */ -.skeleton-shimmer { - /* Adjust color stops to be slightly wider for a smoother gradient */ - background: linear-gradient( - 90deg, - var(--skeleton-bg-start) 0%, - var(--skeleton-bg-shimmer) 20%, - var(--skeleton-bg-start) 40%, - var(--skeleton-bg-start) 100% - ); - background-size: 200% 100%; /* Important for the movement effect */ - animation: shimmer 1.5s infinite cubic-bezier(0.4, 0, 0.2, 1); -} - +/* Base styles (removed dangerous global selector) */ @layer base { - * { - @apply border-border outline-ring/50; + html, body, #__next { + height: 100%; } body { @apply bg-background text-foreground; - /* Prevent horizontal overflow from button animations */ overflow-x: hidden; } + + /* sensible default: hide focus outlines unless keyboard navigation triggers them */ + :focus:not(:focus-visible) { + outline: none; + } } +/* Utilities & accessibility-focused rules */ @layer utilities { + /* ========================== + Accessibility / Focus rules + ========================== */ + + /* Fallback simple variables */ + :root { + --focus-ring-fallback: rgba(59,130,246,0.9); + --focus-ring-dark-fallback: rgba(125,211,252,0.9); + } + + /* Prefer explicit token if available (fallback to above) */ + :root { + --focus-ring: var(--focus-ring, var(--focus-ring-fallback)); + --focus-ring-dark: var(--focus-ring-dark, var(--focus-ring-dark-fallback)); + } + + /* Better focus-visible defaults for common interactive elements */ + button:focus-visible, + a:focus-visible, + input:focus-visible, + select:focus-visible, + textarea:focus-visible, + [role="menuitem"]:focus-visible, + [role="tab"]:focus-visible, + [role="button"]:focus-visible, + [role="link"]:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--focus-ring); + border-radius: 0.375rem; + } + + /* Dark mode: use the dark ring for contrast */ + .dark button:focus-visible, + .dark a:focus-visible, + .dark input:focus-visible, + .dark select:focus-visible, + .dark textarea:focus-visible, + .dark [role="menuitem"]:focus-visible, + .dark [role="tab"]:focus-visible, + .dark [role="button"]:focus-visible, + .dark [role="link"]:focus-visible { + box-shadow: 0 0 0 3px var(--focus-ring-dark); + } + + /* Stronger ring for primary interactive controls */ + button[data-slot="menubar-trigger"]:focus-visible, + [data-slot="menubar-item"]:focus-visible, + [data-slot="tabs-trigger"]:focus-visible, + .btn-glow:focus-visible, + .code-copy-button:focus-visible, + [data-slot="code-copy"]:focus-visible { + box-shadow: 0 0 0 4px var(--focus-ring); + } + + /* Ensure dialog close is strongly visible */ + [aria-label="Close dialog"]:focus-visible, + [data-slot="dialog-close"]:focus-visible, + .DialogClose:focus-visible { + box-shadow: 0 0 0 4px var(--focus-ring); + } + + /* Make focus rings appear above overlays if necessary */ + :where([role="dialog"], [data-slot="menubar-content"], [data-slot="menubar-portal"]) :focus-visible { + z-index: 10001; + } + + /* Small icon-only controls get a tighter ring */ + .icon-focus:focus-visible { + box-shadow: 0 0 0 2px var(--focus-ring); + border-radius: 9999px; + } + + /* Skip link visibility */ + .skip-link:focus { + transform: translateY(0); + z-index: 9999; + } + + /* Reduce motion for users who prefer reduced motion */ + @media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.001s !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001s !important; + scroll-behavior: auto !important; + } + } + + /* Utility: visibly focusable element helper */ + .visibly-focusable:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--focus-ring); + } + + /* Fine tune for very small elements (icon-only, icon buttons) */ + .icon-focus:focus-visible { + box-shadow: 0 0 0 2px var(--focus-ring); + border-radius: 9999px; + } /* Extra small breakpoint for very small screens */ @media (min-width: 475px) { @@ -206,7 +295,6 @@ @apply gap-4 sm:gap-6 lg:gap-8; } - /* Responsive gap utilities */ .gap-responsive-sm { @apply gap-2 sm:gap-3 lg:gap-4; } @@ -289,7 +377,6 @@ opacity: 0.15; } - /* Dark theme adjustments for glow */ .dark .btn-glow:hover::before { opacity: 0.05; } @@ -354,7 +441,7 @@ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -/* Focus visible for accessibility */ +/* Focus visible helper */ .focus-ring:focus-visible { @apply outline-none ring-2 ring-primary ring-offset-2 ring-offset-background; } @@ -451,4 +538,4 @@ input[type="search"] { input[type="text"]:focus, input[type="search"]:focus { @apply shadow-sm shadow-primary/20; -} +} \ No newline at end of file diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index a68a0f5..468d692 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -1,6 +1,8 @@ +/** biome-ignore-all lint/a11y/useSemanticElements: */ +/** biome-ignore-all lint/a11y/noNoninteractiveTabindex: */ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef, KeyboardEvent } from "react"; import { codeToHtml } from "shiki"; import { Check, Copy } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -22,13 +24,20 @@ export const CodeBlock = ({ showLineNumbers = true, }: CodeBlockProps) => { const [copied, setCopied] = useState(false); - const { logEvent } = useAnalytics(componentName || 'CodeBlock'); - const { theme, resolvedTheme } = useTheme(); + const { logEvent } = useAnalytics(componentName || "CodeBlock"); + const { resolvedTheme } = useTheme(); const [highlightedCode, setHighlightedCode] = useState(""); const [mounted, setMounted] = useState(false); + const [liveMessage, setLiveMessage] = useState(""); + const codeRegionRef = useRef(null); - // Calculate line count and create line numbers array - const lines = code.split('\n'); + // ids for aria + const safeName = (componentName || "code-snippet").replace(/\s+/g, "-").toLowerCase(); + const labelId = `${safeName}-label`; + const regionId = `${safeName}-region`; + + // Calculate lines + const lines = code.split("\n"); const lineCount = lines.length; const maxLineNumberWidth = lineCount.toString().length; @@ -45,71 +54,98 @@ export const CodeBlock = ({ theme, }); - // If line numbers are enabled, process the HTML to add line number structure if (showLineNumbers) { - const processedHtml = addLineNumbersToHtml(html, lineCount); + const processedHtml = addLineNumbersToHtml(html); setHighlightedCode(processedHtml); } else { setHighlightedCode(html); } } catch (error) { console.error("Highlighting error:", error); - setHighlightedCode(`
${code}
`); + setHighlightedCode(`
${escapeHtml(code)}
`); } }; generateHighlight(); - }, [code, language, mounted, resolvedTheme, showLineNumbers, lineCount]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [code, language, mounted, resolvedTheme, showLineNumbers]); + + // Escape HTML fallback + const escapeHtml = (str: string) => + str.replace(/&/g, "&").replace(//g, ">"); - // Function to add line numbers to the highlighted HTML - const addLineNumbersToHtml = (html: string, totalLines: number) => { - // Split HTML by newlines and add line number structure - const htmlLines = html.split('\n'); + // Add line numbers into the highlighted HTML - preserves HTML inside code + const addLineNumbersToHtml = (html: string) => { + // Extract code content between ... + const codeMatch = html.match(/]*>([\s\S]*?)<\/code>/); const preMatch = html.match(/]*>/); - const codeMatch = html.match(/]*>/); - const preOpenTag = preMatch ? preMatch[0] : '
';
-    const codeOpenTag = codeMatch ? codeMatch[0] : '';
+    const preOpenTag = preMatch ? preMatch[0] : "
";
+    const codeOpenTag = codeMatch?.[0]?.match(/^]*>/)?.[0] ?? "";
 
-    // Extract the content between  and 
-    const codeContent = html.match(/]*>([\s\S]*?)<\/code>/)?.[1] || '';
-    const lines = codeContent.split('\n');
+    const codeContent = codeMatch?.[1] ?? "";
+    const contentLines = codeContent.split("\n");
 
-    // Wrap each line with line number data
-    const numberedLines = lines.map((line, index) => {
-      const lineNumber = index + 1;
-      return `${line}`;
-    }).join('\n');
+    // Wrap each line in a span
+    const numberedLines = contentLines
+      .map((line, i) => {
+        const lineNumber = i + 1;
+        // Keep raw HTML for tokens (line can contain spans)
+        return `${line}`;
+      })
+      .join("\n");
 
     return `${preOpenTag}${codeOpenTag}${numberedLines}
`; }; + // copy handler const handleCopy = async () => { try { await navigator.clipboard.writeText(code); setCopied(true); - logEvent('copy'); // Log the copy event with componentName - toast.success("Code copied to clipboard!", { - duration: 2000, - }); + setLiveMessage("Code copied to clipboard"); + logEvent("copy"); toast.success("Code copied to clipboard!"); - setTimeout(() => setCopied(false), 2000); + setTimeout(() => { + setCopied(false); + setLiveMessage(""); + }, 1600); } catch (err) { toast.error("Failed to copy code"); + setLiveMessage("Failed to copy code"); } }; + // Keyboard: Ctrl/Cmd+C when code region focused should copy + const onRegionKeyDown = (e: KeyboardEvent) => { + const isMac = navigator.platform.toUpperCase().includes("MAC"); + const copyPressed = (isMac && e.metaKey && e.key === "c") || (!isMac && e.ctrlKey && e.key === "c"); + + if (copyPressed) { + e.preventDefault(); + handleCopy(); + return; + } + + // allow usual navigation keys and not trap them + // (don't intercept ArrowUp/Down etc — code block is scrollable) + }; + return ( -
- {/* Header with primary color accents */} -
+
+ {/* Header */} +
- {/* VS Code style dots with primary color */} -
+
- {/* Language Label */} + {language} @@ -120,6 +156,8 @@ export const CodeBlock = ({ size="sm" onClick={handleCopy} className="h-8 px-3 text-primary hover:bg-primary/10 hover:text-primary transition-all" + aria-label={componentName ? `Copy ${componentName} code` : "Copy code"} + title={componentName ? `Copy ${componentName} code` : "Copy code"} > {copied ? ( <> @@ -135,20 +173,26 @@ export const CodeBlock = ({
- {/* Code content with Shiki highlighting and logical line numbers */} + {/* Code region: focusable, labeled, supports Ctrl/Cmd+C */}
+ {/* decorative line numbers column - aria-hidden */} {showLineNumbers && ( )} + {/* highlighted HTML (dangerous, but from shiki) */} {highlightedCode ? (
) : (
             {code}
@@ -191,7 +236,16 @@ export const CodeBlock = ({
         )}
       
- {/* Subtle primary color accent at bottom */} + {/* live region for screen readers */} +
+ {liveMessage} +
+
); diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 3e0495f..042190b 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,8 @@ import { cva, type VariantProps } from "class-variance-authority"; 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 duration-200 ease-out cursor-pointer 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 hover:shadow-lg focus:shadow-lg", + // ✅ ADDED: Added smooth transitions for transform and shadow. Added subtle hover and active scale animations. + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 ease-out cursor-pointer 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 hover:shadow-lg focus:shadow-lg hover:scale-[1.03] active:scale-[0.98]", { variants: { variant: { @@ -19,7 +20,8 @@ const buttonVariants = cva( "bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:shadow-md focus:shadow-md hover:shadow-secondary/20 dark:hover:shadow-secondary/10 focus:shadow-secondary/20 dark:focus:shadow-secondary/10", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 hover:shadow-sm focus:shadow-sm", - link: "text-primary underline-offset-4 hover:underline hover:shadow-none focus:shadow-none hover:scale-100 focus:scale-100", + // ✅ ADDED: Disabled scale animation for link variant to keep it looking like a link. + link: "text-primary underline-offset-4 hover:underline hover:shadow-none focus:shadow-none hover:scale-100 active:scale-100", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", @@ -55,7 +57,8 @@ function Button({ className={cn( buttonVariants({ variant, size, className }), loading && "relative aria-busy:opacity-90", - "btn-interactive btn-glow" + // ✅ ADDED: Added the .btn-glow class to apply the glow effect from globals.css + "btn-glow" )} aria-busy={loading || undefined} disabled={(props as any).disabled || loading} diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx index b292799..5b64ee3 100644 --- a/src/components/ui/calendar.tsx +++ b/src/components/ui/calendar.tsx @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/a11y/useSemanticElements: */ "use client" import * as React from "react" @@ -19,15 +20,24 @@ function Calendar({ buttonVariant = "ghost", formatters, components, + "aria-label": ariaLabel, + "aria-labelledby": ariaLabelledby, ...props }: React.ComponentProps & { - buttonVariant?: React.ComponentProps["variant"] + buttonVariant?: React.ComponentProps["variant"]; + "aria-label"?: string; + "aria-labelledby"?: string; }) { - const defaultClassNames = getDefaultClassNames() + const defaultClassNames = getDefaultClassNames(); + + const computedAriaLabel = ariaLabel ?? "Calendar"; return ( svg]:rotate-180`, @@ -73,10 +83,7 @@ function Calendar({ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", defaultClassNames.dropdown_root ), - dropdown: cn( - "absolute bg-popover inset-0 opacity-0", - defaultClassNames.dropdown - ), + dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown), caption_label: cn( "select-none font-medium", captionLayout === "label" @@ -91,36 +98,18 @@ function Calendar({ defaultClassNames.weekday ), week: cn("flex w-full mt-2", defaultClassNames.week), - week_number_header: cn( - "select-none w-(--cell-size)", - defaultClassNames.week_number_header - ), - week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", - defaultClassNames.week_number - ), + week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header), + week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number), day: cn( "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", defaultClassNames.day ), - range_start: cn( - "rounded-l-md bg-accent", - defaultClassNames.range_start - ), + range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start), range_middle: cn("rounded-none", defaultClassNames.range_middle), range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), - today: cn( - "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", - defaultClassNames.today - ), - outside: cn( - "text-muted-foreground aria-selected:text-muted-foreground", - defaultClassNames.outside - ), - disabled: cn( - "text-muted-foreground opacity-50", - defaultClassNames.disabled - ), + today: cn("bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today), + outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside), + disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled), hidden: cn("invisible", defaultClassNames.hidden), ...classNames, }} @@ -130,76 +119,89 @@ function Calendar({
- ) + ); }, Chevron: ({ className, orientation, ...props }) => { if (orientation === "left") { - return ( - - ) + return ; } if (orientation === "right") { - return ( - - ) + return ; } - return ( - - ) + return ; }, DayButton: CalendarDayButton, WeekNumber: ({ children, ...props }) => { return ( -
- {children} -
+
{children}
- ) + ); }, ...components, }} {...props} /> - ) + ); } +/* CalendarDayButton — improved ARIA + keyboard semantics */ function CalendarDayButton({ className, day, modifiers, + disabled, ...props }: React.ComponentProps) { - const defaultClassNames = getDefaultClassNames() + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); - const ref = React.useRef(null) + // focus when DayPicker marks this item focused (keyboard navigation) React.useEffect(() => { - if (modifiers.focused) ref.current?.focus() - }, [modifiers.focused]) + if (modifiers.focused) { + ref.current?.focus(); + } + }, [modifiers.focused]); + + const isToday = !!modifiers.today; + const isSelected = !!modifiers.selected; + const isDisabled = !!modifiers.disabled; + + // accessible label for the date (e.g., "Tuesday, July 15, 2025") + const ariaLabel = day?.date + ? day.date.toLocaleDateString(undefined, { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }) + : undefined; return (
-