diff --git a/src/components/react/Callout.tsx b/src/components/react/Callout.tsx index dae9fd3..38b87e9 100644 --- a/src/components/react/Callout.tsx +++ b/src/components/react/Callout.tsx @@ -1,44 +1,60 @@ +import type { LucideIcon } from 'lucide-react'; import { AlertCircle, AlertTriangle, Info, Lightbulb } from 'lucide-react'; import type { ReactNode } from 'react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { cn } from '@/lib/utils'; +type CalloutType = 'info' | 'warning' | 'danger' | 'tip'; + interface CalloutProps { - type?: 'info' | 'warning' | 'danger' | 'tip'; + type?: CalloutType; title?: string; children: ReactNode; } -const icons = { - info: Info, - warning: AlertTriangle, - danger: AlertCircle, - tip: Lightbulb, -}; - -const styles = { - info: 'border-green-500/50 [&>svg]:text-green-500', - warning: 'border-amber-500/50 [&>svg]:text-amber-500', - danger: 'border-red-500/50 [&>svg]:text-red-500', - tip: 'border-blue-500/50 [&>svg]:text-blue-500', -}; +interface CalloutConfig { + icon: LucideIcon; + borderStyle: string; + titleStyle: string; +} -const titleStyles = { - info: 'text-green-500', - warning: 'text-amber-500', - danger: 'text-red-500', - tip: 'text-blue-500', +const CALLOUT_CONFIG: Record = { + info: { + icon: Info, + borderStyle: 'border-green-500/50 [&>svg]:text-green-500', + titleStyle: 'text-green-500', + }, + warning: { + icon: AlertTriangle, + borderStyle: 'border-amber-500/50 [&>svg]:text-amber-500', + titleStyle: 'text-amber-500', + }, + danger: { + icon: AlertCircle, + borderStyle: 'border-red-500/50 [&>svg]:text-red-500', + titleStyle: 'text-red-500', + }, + tip: { + icon: Lightbulb, + borderStyle: 'border-blue-500/50 [&>svg]:text-blue-500', + titleStyle: 'text-blue-500', + }, }; -export function Callout({ type = 'info', title, children }: CalloutProps) { - const Icon = icons[type]; +export function Callout({ + type = 'info', + title, + children, +}: CalloutProps): ReactNode { + const config = CALLOUT_CONFIG[type]; + const Icon = config.icon; return (
- + {title && ( - {title} + {title} )} {children} diff --git a/src/components/react/DownloadCards.tsx b/src/components/react/DownloadCards.tsx index 2bf64d6..1a33a4b 100644 --- a/src/components/react/DownloadCards.tsx +++ b/src/components/react/DownloadCards.tsx @@ -47,14 +47,12 @@ interface DownloadCardsProps { } function groupByPlatform(binaries: Binary[]): Record { - const grouped: Record = {}; - for (const binary of binaries) { - if (!grouped[binary.platform]) { - grouped[binary.platform] = []; - } - grouped[binary.platform].push(binary); - } - return grouped; + return binaries.reduce>((grouped, binary) => { + const list = grouped[binary.platform] ?? []; + list.push(binary); + grouped[binary.platform] = list; + return grouped; + }, {}); } function DownloadCards({ diff --git a/src/components/react/PageFeedback.tsx b/src/components/react/PageFeedback.tsx index 461a6f2..72f8449 100644 --- a/src/components/react/PageFeedback.tsx +++ b/src/components/react/PageFeedback.tsx @@ -1,4 +1,5 @@ import { motion } from 'motion/react'; +import type React from 'react'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; @@ -11,37 +12,45 @@ declare global { } } -const ThumbsUp = ({ className }: { className?: string }) => ( - -); +interface IconProps { + className?: string; +} + +function ThumbsUp({ className }: IconProps): React.ReactElement { + return ( + + ); +} -const ThumbsDown = ({ className }: { className?: string }) => ( - -); +function ThumbsDown({ className }: IconProps): React.ReactElement { + return ( + + ); +} export function PageFeedback() { const [submitted, setSubmitted] = useState<'yes' | 'no' | null>(null); diff --git a/src/components/react/StatusIcon.tsx b/src/components/react/StatusIcon.tsx index 7d1bc67..fa6130a 100644 --- a/src/components/react/StatusIcon.tsx +++ b/src/components/react/StatusIcon.tsx @@ -5,51 +5,30 @@ interface StatusIconProps { children?: ReactNode; } -const CheckIcon = () => ( - - - -); +const iconStyle = { + display: 'inline', + verticalAlign: '-0.2em', + marginRight: '0.35em', +} as const; -const XIcon = () => ( - - - - -); +const ICONS = { + check: { + stroke: '#22c55e', + paths: , + }, + x: { + stroke: '#ef4444', + paths: ( + <> + + + + ), + }, +} as const; -export function StatusIcon({ type, children }: StatusIconProps) { - const Icon = type === 'check' ? CheckIcon : XIcon; +export function StatusIcon({ type, children }: StatusIconProps): ReactNode { + const icon = ICONS[type]; return (
- + + {icon.paths} + {children}
diff --git a/src/components/react/api/VersionSelector.tsx b/src/components/react/api/VersionSelector.tsx index 78859ec..5679783 100644 --- a/src/components/react/api/VersionSelector.tsx +++ b/src/components/react/api/VersionSelector.tsx @@ -1,5 +1,6 @@ 'use client'; +import type React from 'react'; import { Select, SelectContent, @@ -21,26 +22,34 @@ interface VersionSelectorProps { currentPath: string; } -function BadgeIcon({ badge }: { badge?: APIVersion['badge'] }) { - if (!badge) return null; - - const colors = { - dev: 'bg-amber-500/20 text-amber-600 dark:text-amber-400', - stable: 'bg-green-500/20 text-green-600 dark:text-green-400', - deprecated: 'bg-red-500/20 text-red-600 dark:text-red-400', - }; +const BADGE_CONFIG = { + dev: { + label: 'dev', + className: 'bg-amber-500/20 text-amber-600 dark:text-amber-400', + }, + stable: { + label: 'stable', + className: 'bg-green-500/20 text-green-600 dark:text-green-400', + }, + deprecated: { + label: 'old', + className: 'bg-red-500/20 text-red-600 dark:text-red-400', + }, +} as const; - const labels = { - dev: 'dev', - stable: 'stable', - deprecated: 'old', - }; +function BadgeIcon({ + badge, +}: { + badge?: APIVersion['badge']; +}): React.ReactNode { + if (!badge) return null; + const config = BADGE_CONFIG[badge]; return ( - {labels[badge]} + {config.label} ); } diff --git a/src/lib/api-versions.ts b/src/lib/api-versions.ts index 8f98337..c4fa99d 100644 --- a/src/lib/api-versions.ts +++ b/src/lib/api-versions.ts @@ -49,22 +49,15 @@ export function getVersion(id: string): APIVersion | undefined { */ export function getVersionFromPath(path: string): string | null { const match = path.match(/^\/api\/([^/]+)/); - if (match) { - const slugOrId = match[1]; - // Check for exact ID match first - const exactMatch = API_VERSIONS.find((v) => v.id === slugOrId); - if (exactMatch) { - return exactMatch.id; - } - // Check for slugified version match - const slugMatch = API_VERSIONS.find( - (v) => versionToSlug(v.id) === slugOrId, - ); - if (slugMatch) { - return slugMatch.id; - } - } - return null; + if (!match) return null; + + const slugOrId = match[1]; + // Check for exact ID match first, then slugified version match + const version = + API_VERSIONS.find((v) => v.id === slugOrId) ?? + API_VERSIONS.find((v) => versionToSlug(v.id) === slugOrId); + + return version?.id ?? null; } /**