Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 39 additions & 23 deletions src/components/react/Callout.tsx
Original file line number Diff line number Diff line change
@@ -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<CalloutType, CalloutConfig> = {
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 (
<div style={{ marginTop: '2.5rem', marginBottom: '1.5rem' }}>
<Alert className={cn(styles[type])}>
<Alert className={cn(config.borderStyle)}>
<Icon />
{title && (
<AlertTitle className={titleStyles[type]}>{title}</AlertTitle>
<AlertTitle className={config.titleStyle}>{title}</AlertTitle>
)}
<AlertDescription className="[&>p]:m-0 justify-items-stretch">
{children}
Expand Down
14 changes: 6 additions & 8 deletions src/components/react/DownloadCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,12 @@ interface DownloadCardsProps {
}

function groupByPlatform(binaries: Binary[]): Record<string, Binary[]> {
const grouped: Record<string, Binary[]> = {};
for (const binary of binaries) {
if (!grouped[binary.platform]) {
grouped[binary.platform] = [];
}
grouped[binary.platform].push(binary);
}
return grouped;
return binaries.reduce<Record<string, Binary[]>>((grouped, binary) => {
const list = grouped[binary.platform] ?? [];
list.push(binary);
grouped[binary.platform] = list;
return grouped;
}, {});
}

function DownloadCards({
Expand Down
69 changes: 39 additions & 30 deletions src/components/react/PageFeedback.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { motion } from 'motion/react';
import type React from 'react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';

Expand All @@ -11,37 +12,45 @@ declare global {
}
}

const ThumbsUp = ({ className }: { className?: string }) => (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M7 10v12" />
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
</svg>
);
interface IconProps {
className?: string;
}

function ThumbsUp({ className }: IconProps): React.ReactElement {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M7 10v12" />
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
</svg>
);
}

const ThumbsDown = ({ className }: { className?: string }) => (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M17 14V2" />
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
</svg>
);
function ThumbsDown({ className }: IconProps): React.ReactElement {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M17 14V2" />
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
</svg>
);
}

export function PageFeedback() {
const [submitted, setSubmitted] = useState<'yes' | 'no' | null>(null);
Expand Down
80 changes: 36 additions & 44 deletions src/components/react/StatusIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,30 @@ interface StatusIconProps {
children?: ReactNode;
}

const CheckIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="#22c55e"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
style={{
display: 'inline',
verticalAlign: '-0.2em',
marginRight: '0.35em',
}}
>
<path d="M20 6 9 17l-5-5" />
</svg>
);
const iconStyle = {
display: 'inline',
verticalAlign: '-0.2em',
marginRight: '0.35em',
} as const;

const XIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="#ef4444"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
style={{
display: 'inline',
verticalAlign: '-0.2em',
marginRight: '0.35em',
}}
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
);
const ICONS = {
check: {
stroke: '#22c55e',
paths: <path d="M20 6 9 17l-5-5" />,
},
x: {
stroke: '#ef4444',
paths: (
<>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</>
),
},
} 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 (
<div
style={{
Expand All @@ -60,7 +39,20 @@ export function StatusIcon({ type, children }: StatusIconProps) {
}}
>
<span style={{ flexShrink: 0 }}>
<Icon />
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke={icon.stroke}
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
style={iconStyle}
>
{icon.paths}
</svg>
</span>
<span>{children}</span>
</div>
Expand Down
39 changes: 24 additions & 15 deletions src/components/react/api/VersionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import type React from 'react';
import {
Select,
SelectContent,
Expand All @@ -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 (
<span
className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${colors[badge]}`}
className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${config.className}`}
>
{labels[badge]}
{config.label}
</span>
);
}
Expand Down
25 changes: 9 additions & 16 deletions src/lib/api-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down