feat: add responsive case study sharing

This commit is contained in:
2026-09-09 23:30:35 +03:30
parent 4b9d4ae893
commit 0c15775ff7
4 changed files with 388 additions and 22 deletions

View File

@@ -1,15 +1,14 @@
'use client';
import { useCallback, useEffect, useRef, useState, type PointerEvent, type ReactNode } from 'react';
import { Check, Copy, X } from 'lucide-react';
import { useCallback, useEffect, useRef, type PointerEvent, type ReactNode } from 'react';
import { X } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { messagesByLocale, type Locale } from '@/i18n/content';
import { useDismissCaseStudy } from './PortfolioRouteShell';
import { CaseStudyShare } from './CaseStudyShare';
import { useDrawerDrag } from './useDrawerDrag';
import styles from './CaseStudyDrawer.module.css';
type CopyState = 'idle' | 'copied' | 'error';
export function CaseStudyDrawer({ children, locale }: { children: ReactNode; locale: Locale }) {
const copy = messagesByLocale[locale].projects;
const pathname = usePathname();
@@ -21,7 +20,6 @@ export function CaseStudyDrawer({ children, locale }: { children: ReactNode; loc
const closing = useRef(false);
const returnFocusRef = useRef<HTMLElement | null>(null);
const backgroundScrollY = useRef(0);
const [copyState, setCopyState] = useState<CopyState>('idle');
const finishDismiss = useCallback(() => {
const dialog = dialogRef.current;
@@ -95,20 +93,10 @@ export function CaseStudyDrawer({ children, locale }: { children: ReactNode; loc
useEffect(() => {
scrollRef.current?.scrollTo({ top: 0, behavior: 'auto' });
requestAnimationFrame(() => {
setCopyState('idle');
dialogRef.current?.querySelector<HTMLElement>('[data-case-study-initial-focus]')?.focus();
});
}, [pathname]);
const copyCurrentLink = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setCopyState('copied');
} catch {
setCopyState('error');
}
};
const handleBackdropDown = (event: PointerEvent<HTMLDialogElement>) => {
backdropPressed.current = event.target === event.currentTarget;
};
@@ -118,8 +106,6 @@ export function CaseStudyDrawer({ children, locale }: { children: ReactNode; loc
backdropPressed.current = false;
};
const feedback = copyState === 'copied' ? copy.linkCopied : copyState === 'error' ? copy.copyFailed : '';
return (
<dialog
ref={dialogRef}
@@ -141,10 +127,7 @@ export function CaseStudyDrawer({ children, locale }: { children: ReactNode; loc
</div>
<span className={styles.drawerLabel}>{copy.drawerLabel}</span>
<div className={styles.actions}>
<button type="button" className={styles.actionButton} onClick={copyCurrentLink} aria-label={copy.copyLink}>
{copyState === 'copied' ? <Check size={19} aria-hidden="true" /> : <Copy size={18} aria-hidden="true" />}
<span className={styles.actionText}>{copyState === 'copied' ? copy.linkCopied : copy.copyLink}</span>
</button>
<CaseStudyShare locale={locale} />
<button
type="button"
className={styles.iconButton}
@@ -156,7 +139,6 @@ export function CaseStudyDrawer({ children, locale }: { children: ReactNode; loc
<X size={21} aria-hidden="true" />
</button>
</div>
<span className={styles.liveRegion} aria-live="polite">{feedback}</span>
</div>
<div ref={scrollRef} className={styles.scrollArea}>{children}</div>
</div>