tercul-frontend/client/src/components/work/LineNumberedText.tsx
Damir Mukimov 4a23f496fa
Major frontend development updates
- Enhanced annotation system with improved inline editing
- Updated author components with new card and header designs
- Improved reading view with enhanced line numbering and controls
- Added new blog management features and tag management
- Updated UI components with improved accessibility and styling
- Enhanced search functionality with better filtering
- Added new dashboard features and activity feeds
- Improved translation selector and work comparison tools
- Updated GraphQL integration and API hooks
- Enhanced responsive design and mobile experience
2025-11-27 03:44:09 +01:00

121 lines
3.2 KiB
TypeScript

import { Copy } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
interface LineNumberedTextProps {
content: string;
fontSizeClass?: string;
onLineClick?: (lineNumber: number) => void;
highlightedLine?: number;
}
export function LineNumberedText({
content,
fontSizeClass = "text-size-md",
onLineClick,
highlightedLine,
}: LineNumberedTextProps) {
const { toast } = useToast();
const [hoveredLine, setHoveredLine] = useState<number | null>(null);
// Split content into lines
const lines = content.split("\n");
const handleLineHover = (lineNumber: number) => {
setHoveredLine(lineNumber);
};
const handleLineLeave = () => {
setHoveredLine(null);
};
const handleCopyLine = (_lineNumber: number, lineText: string) => {
navigator.clipboard.writeText(lineText);
toast({
description: "Line copied to clipboard",
duration: 2000,
});
};
const handleCopyLineLink = (lineNumber: number) => {
const url = new URL(window.location.href);
url.hash = `line-${lineNumber}`;
navigator.clipboard.writeText(url.toString());
toast({
description: "Link to line copied to clipboard",
duration: 2000,
});
};
return (
<div className={`reading-text ${fontSizeClass}`}>
{lines.map((line, index) => {
const lineNumber = index + 1;
const isHighlighted = lineNumber === highlightedLine;
const isHovered = lineNumber === hoveredLine;
return (
<div
key={`line-${lineNumber}`}
id={`line-${lineNumber}`}
className={`text-line group ${
isHighlighted
? "bg-navy/10 dark:bg-cream/10"
: "hover:bg-navy/5 dark:hover:bg-cream/5"
} py-1 rounded flex`}
onMouseEnter={() => handleLineHover(lineNumber)}
onMouseLeave={handleLineLeave}
onClick={() => onLineClick?.(lineNumber)}
>
<span className="line-number">{lineNumber}</span>
<p className="flex-1">{line}</p>
{/* Copy buttons that appear on hover */}
{isHovered && (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity ml-2">
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={(e) => {
e.stopPropagation();
handleCopyLine(lineNumber, line);
}}
>
<Copy className="h-4 w-4" />
<span className="sr-only">Copy line</span>
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={(e) => {
e.stopPropagation();
handleCopyLineLink(lineNumber);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
<span className="sr-only">Copy link to line</span>
</Button>
</div>
)}
</div>
);
})}
</div>
);
}