feat(site): tabs (#144)

This commit is contained in:
Darius Cepulis
2025-10-29 12:43:03 -05:00
committed by GitHub
parent 419911f2f2
commit df4692dbda
35 changed files with 999 additions and 304 deletions
+67
View File
@@ -0,0 +1,67 @@
import { useRef, useState } from 'react';
import useIsHydrated from '@/utils/useIsHydrated';
export interface CopyButtonProps {
children: React.ReactNode;
copied?: React.ReactNode; // Optional, passed via slot="copied" in Astro
copyFrom: {
container: string; // CSS selector for parent container (e.g., 'starlight-tabs')
target: string; // CSS selector for content element (e.g., '[role="tabpanel"]:not([hidden])')
};
className?: string;
style?: React.CSSProperties;
timeout?: number;
}
export default function CopyButton({
children,
copied,
copyFrom,
className,
style,
timeout = 2000,
}: CopyButtonProps) {
const buttonRef = useRef<HTMLButtonElement>(null);
const [isCopied, setIsCopied] = useState(false);
const isHydrated = useIsHydrated();
const disabled = !isHydrated;
const handleCopy = async () => {
try {
let text = '';
if (buttonRef.current) {
// Find the closest container
const container = buttonRef.current.closest(copyFrom.container);
if (container) {
// Find the target within that container
const target = container.querySelector(copyFrom.target);
text = target?.textContent || '';
}
}
if (text) {
await navigator.clipboard.writeText(text.trim());
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, timeout);
}
} catch (error) {
console.error('Failed to copy text:', error);
}
};
return (
<button
ref={buttonRef}
type="button"
disabled={disabled}
onClick={handleCopy}
className={className}
style={style}
aria-label={isCopied ? 'Copied' : 'Copy to clipboard'}
>
{isCopied ? (copied || children) : children}
</button>
);
}