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
5 changes: 5 additions & 0 deletions app/tools/[tool]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const toolComponents = {
'json-validator': dynamic(() => import('@/app/tools/json-validator').then((mod) => mod.default)),
'xml-formatter': dynamic(() => import('@/app/tools/xml-formatter').then((mod) => mod.default)),
'xml-validator': dynamic(() => import('@/app/tools/xml-validator').then((mod) => mod.default)),
'regex-tester': dynamic(() => import('@/app/tools/regex-tester').then((mod) => mod.default)),
'url-encoder-decoder': dynamic(() =>
import('@/app/tools/url-encoder-decoder').then((mod) => mod.default),
),
Expand All @@ -62,6 +63,7 @@ const toolComponents = {
'sha256-hash': dynamic(() => import('@/app/tools/sha256-hash').then((mod) => mod.default)),
'sha512-hash': dynamic(() => import('@/app/tools/sha512-hash').then((mod) => mod.default)),
'uuid-generator': dynamic(() => import('@/app/tools/uuid-generator').then((mod) => mod.default)),
'lorem-ipsum': dynamic(() => import('@/app/tools/lorem-ipsum').then((mod) => mod.default)),
'jwt-decoder': dynamic(() => import('@/app/tools/jwt-decoder').then((mod) => mod.default)),
'epoch-to-date': dynamic(() => import('@/app/tools/epoch-to-date').then((mod) => mod.default)),
'date-to-epoch': dynamic(() => import('@/app/tools/date-to-epoch').then((mod) => mod.default)),
Expand Down Expand Up @@ -99,6 +101,9 @@ const toolComponents = {
'namespace-server-delegation': dynamic(() =>
import('@/app/tools/namespace-server-delegation').then((mod) => mod.default),
),
'subnet-calculator': dynamic(() =>
import('@/app/tools/subnet-calculator').then((mod) => mod.default),
),
'responsive-tester': dynamic(() =>
import('@/app/tools/responsive-tester').then((mod) => mod.default),
),
Expand Down
85 changes: 48 additions & 37 deletions app/tools/data-diff/components/DiffNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/**
* DiffNavigation - Navigate between changes and export results
* DiffNavigation - Navigate between changes and export diff results
* Provides previous/next change navigation, copy summary, and download actions
*/

'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { ChevronUp, ChevronDown, Copy, Download, Check, FileText } from 'lucide-react';
import { useState } from 'react';
import type { DiffResult } from '@/lib/tools/comparators';

interface DiffNavigationProps {
Expand Down Expand Up @@ -33,6 +36,8 @@ export function DiffNavigation({
const changes = result.changes.filter((c) => c.type !== 'unchanged');
const totalChanges = changes.length;

if (totalChanges === 0) return null;

const handleCopy = async () => {
await onCopy();
setCopied(true);
Expand All @@ -52,60 +57,66 @@ export function DiffNavigation({
};

return (
<div className="flex items-center gap-2">
{totalChanges > 0 && (
<>
<span className="text-xs text-muted-foreground">
{currentIndex + 1} / {totalChanges}
</span>
<Button
variant="ghost"
size="sm"
onClick={handlePrevious}
disabled={currentIndex === 0}
aria-label="Previous change"
className="h-7 px-2"
>
<ChevronUp className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleNext}
disabled={currentIndex === totalChanges - 1}
aria-label="Next change"
className="h-7 px-2"
>
<ChevronDown className="h-4 w-4" />
</Button>
<div className="h-4 w-px bg-border mx-1" />
</>
)}
<div className="flex items-center gap-1" role="navigation" aria-label="Change navigation">
{/* Change counter */}
<span className="text-xs text-muted-foreground tabular-nums px-1">
{currentIndex + 1}/{totalChanges}
</span>

{/* Previous / Next */}
<Button
variant="ghost"
size="sm"
onClick={handlePrevious}
disabled={currentIndex === 0}
aria-label="Previous change"
className="h-7 w-7 p-0"
>
<ChevronUp className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleNext}
disabled={currentIndex >= totalChanges - 1}
aria-label="Next change"
className="h-7 w-7 p-0"
>
<ChevronDown className="h-4 w-4" />
</Button>

<div className="h-4 w-px bg-border mx-1" aria-hidden="true" />

{/* Copy summary */}
<Button
variant="ghost"
size="sm"
onClick={handleCopy}
aria-label="Copy diff summary"
className="h-7 px-2"
aria-label="Copy diff summary to clipboard"
className="h-7 w-7 p-0"
>
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>

{/* Download report */}
<Button
variant="ghost"
size="sm"
onClick={onDownload}
aria-label="Download diff report"
className="h-7 px-2"
aria-label="Download diff report as HTML"
className="h-7 w-7 p-0"
>
<Download className="h-4 w-4" />
</Button>

{/* Download patch (text format only) */}
{format === 'text' && onDownloadPatch && (
<Button
variant="ghost"
size="sm"
onClick={onDownloadPatch}
aria-label="Download unified diff patch"
className="h-7 px-2"
aria-label="Download unified diff patch file"
className="h-7 w-7 p-0"
>
<FileText className="h-4 w-4" />
</Button>
Expand Down
Loading
Loading