From 2c50a5df0092674e46787c4363a93905b266146b Mon Sep 17 00:00:00 2001 From: Theophilus Adesola Date: Fri, 28 Aug 2026 11:41:55 +0100 Subject: [PATCH 1/4] feat: enhance keyboard navigation across components --- src/components/course/course-card.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/course/course-card.tsx b/src/components/course/course-card.tsx index 07d3f19..f258f41 100644 --- a/src/components/course/course-card.tsx +++ b/src/components/course/course-card.tsx @@ -42,10 +42,16 @@ export const CourseCard = memo(function CourseCard({
-
e.preventDefault()}> +
e.preventDefault()} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") e.preventDefault(); + }} + > - From d79bc5db3f48adcf0b5d035601f70337456608ff Mon Sep 17 00:00:00 2001 From: Theophilus Adesola Date: Fri, 28 Aug 2026 11:41:55 +0100 Subject: [PATCH 2/4] feat: add ARIA labels and attributes for screen readers --- src/components/course/module-list.tsx | 3 ++- src/components/course/quiz-interface.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/course/module-list.tsx b/src/components/course/module-list.tsx index 8152bba..ec29b3c 100644 --- a/src/components/course/module-list.tsx +++ b/src/components/course/module-list.tsx @@ -30,7 +30,7 @@ export function ModuleList({ Course Modules ({modules.length}) -
+
{sorted.map((mod, index) => { const isCompleted = completedModuleIds.includes(mod.id); const isCurrent = mod.id === currentModuleId; @@ -92,6 +92,7 @@ export function ModuleList({ key={mod.id} href={`/courses/${courseId}/modules/${mod.id}`} role="listitem" + aria-current={isCurrent ? "step" : undefined} className={cn( "flex items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-gray-50 cursor-pointer dark:hover:bg-gray-800", isCurrent && "bg-primary-50 border border-primary-200" diff --git a/src/components/course/quiz-interface.tsx b/src/components/course/quiz-interface.tsx index d856bc6..e43fcef 100644 --- a/src/components/course/quiz-interface.tsx +++ b/src/components/course/quiz-interface.tsx @@ -59,7 +59,7 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf // Results view if (attempt) { return ( - +
+
From 51a23a71b6ea86d7d0c74d8c3b62129bbcb2acfb Mon Sep 17 00:00:00 2001 From: Theophilus Adesola Date: Fri, 28 Aug 2026 11:41:55 +0100 Subject: [PATCH 3/4] feat: add smooth page transitions for route changes --- src/app/layout.tsx | 4 +++- src/components/shared/page-transition.tsx | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/components/shared/page-transition.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fb1d7d1..123e7ad 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -50,6 +50,8 @@ export const metadata: Metadata = { }, }; +import { PageTransition } from "@/components/shared/page-transition"; + export default function RootLayout({ children, }: { @@ -76,7 +78,7 @@ export default function RootLayout({
- {children} + {children}
diff --git a/src/components/shared/page-transition.tsx b/src/components/shared/page-transition.tsx new file mode 100644 index 0000000..3260157 --- /dev/null +++ b/src/components/shared/page-transition.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import React from "react"; + +export function PageTransition({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + + return ( +
+ {children} +
+ ); +} From 58c9efa22f201ac2203fadb65ce3c3358bdbe9ad Mon Sep 17 00:00:00 2001 From: Theophilus Adesola Date: Fri, 28 Aug 2026 11:41:55 +0100 Subject: [PATCH 4/4] feat: introduce composable skeleton variants for content types --- src/components/shared/loading-skeleton.tsx | 19 ++++++++++--------- src/components/ui/skeleton.tsx | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/components/shared/loading-skeleton.tsx b/src/components/shared/loading-skeleton.tsx index 22f63aa..3f52385 100644 --- a/src/components/shared/loading-skeleton.tsx +++ b/src/components/shared/loading-skeleton.tsx @@ -1,11 +1,12 @@ "use client"; import { cn } from "@/lib/utils/cn"; -import { Skeleton, SkeletonCircle, SkeletonStack, SkeletonText, + SkeletonRect, + SkeletonCard, } from "@/components/ui/skeleton"; interface LoadingSkeletonProps { @@ -28,7 +29,7 @@ export function LoadingSkeleton({ if (variant === "circle") { return ; } - return ; + return ; })}
); @@ -37,11 +38,11 @@ export function LoadingSkeleton({ export function CourseCardSkeleton() { return (
- +
- - + +
@@ -55,7 +56,7 @@ export function DashboardSkeleton() {
{Array.from({ length: 3 }).map((_, i) => ( - + ))}
@@ -70,7 +71,7 @@ export function DashboardSkeleton() { export function CredentialCardSkeleton() { return (
- +
@@ -107,12 +108,12 @@ export function VerifySkeleton() { export function CourseDetailSkeleton() { return (
- +
{Array.from({ length: 3 }).map((_, i) => ( - + ))}
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx index 83cb9b8..bd5c6ef 100644 --- a/src/components/ui/skeleton.tsx +++ b/src/components/ui/skeleton.tsx @@ -86,4 +86,22 @@ function SkeletonStack({ SkeletonStack.displayName = "SkeletonStack"; -export { Skeleton, SkeletonText, SkeletonCircle, SkeletonStack }; +/** Shortcut for a standard rectangle skeleton. */ +function SkeletonRect({ + className, + ...props +}: React.HTMLAttributes) { + return ; +} +SkeletonRect.displayName = "SkeletonRect"; + +/** Shortcut for a card skeleton. */ +function SkeletonCard({ + className, + ...props +}: React.HTMLAttributes) { + return ; +} +SkeletonCard.displayName = "SkeletonCard"; + +export { Skeleton, SkeletonText, SkeletonCircle, SkeletonStack, SkeletonRect, SkeletonCard };