Skip to content
Merged
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
75 changes: 42 additions & 33 deletions apps/web/src/components/base64/base64-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import { cn } from '@/lib/utils';
import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation';
import { SendToMenu } from '@/components/ui/send-to-menu';
import { IconTransform } from '@tabler/icons-react';
import { ToolPageHeader } from '@/components/tools/tool-page-header';
import { RevealItem } from '@/components/dashboard/dashboard-reveal';
import { CATEGORY_ACCENT } from '@/components/dashboard/types';

type Mode = 'encode' | 'decode';

Expand Down Expand Up @@ -152,39 +156,44 @@ export function Base64Layout() {
const outputCharCount = output.length;

return (
<div className="flex flex-col h-full gap-4">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 shrink-0 md:hidden">
<div>
<h1 className="text-lg font-semibold tracking-tight">{t('title')}</h1>
<p className="text-xs text-muted-foreground">{t('subtitle')}</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="gap-1.5 text-xs"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="h-3.5 w-3.5" />
{t('upload')}
</Button>
<input
ref={fileInputRef}
type="file"
accept=".txt,.b64,.base64,.json,.xml,.html,.css,.js,.ts,.md"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleFileUpload(file);
e.target.value = '';
}}
/>
<Button variant="outline" size="sm" className="gap-1.5 text-xs" onClick={handleClear}>
<Trash2 className="h-3.5 w-3.5" />
{t('clear')}
</Button>
</div>
<div className="relative flex flex-col h-full gap-4 overflow-hidden dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<ToolPageHeader
icon={IconTransform}
title={t('title')}
description={t('subtitle')}
accent={CATEGORY_ACCENT.Converters}
/>
</RevealItem>

{/* Mobile upload/clear actions */}
<div className="flex items-center gap-2 shrink-0 md:hidden">
<Button
variant="outline"
size="sm"
className="gap-1.5 text-xs"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="h-3.5 w-3.5" />
{t('upload')}
</Button>
<input
ref={fileInputRef}
type="file"
accept=".txt,.b64,.base64,.json,.xml,.html,.css,.js,.ts,.md"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleFileUpload(file);
e.target.value = '';
}}
/>
<Button variant="outline" size="sm" className="gap-1.5 text-xs" onClick={handleClear}>
<Trash2 className="h-3.5 w-3.5" />
{t('clear')}
</Button>
</div>

{/* Mode Toggle */}
Expand Down
27 changes: 20 additions & 7 deletions apps/web/src/components/csv-excel-json/csv-excel-json-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { ToolHeader } from "@/components/tools/tool-header";
import { ToolPinButton } from "@/components/tools/tool-header";
import { ToolPageHeader } from "@/components/tools/tool-page-header";
import { RevealItem } from "@/components/dashboard/dashboard-reveal";
import { CATEGORY_ACCENT } from "@/components/dashboard/types";
import { useToolUsage } from "@/hooks/use-tool-usage";
import { cn } from "@/lib/utils";
import {
Expand Down Expand Up @@ -149,13 +152,23 @@ export function CsvExcelJsonTool() {
};

return (
<div className="flex flex-col h-full min-h-0 overflow-auto pb-4">
<div className="relative flex flex-col h-full min-h-0 overflow-auto pb-4 dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<div className="flex items-center justify-between gap-3 mb-4">
<ToolPageHeader
icon={IconFileSpreadsheet}
title={t("title")}
description={t("subtitle")}
accent={CATEGORY_ACCENT.Converters}
className="flex-1"
/>
<ToolPinButton toolId="csv-excel-json" />
</div>
</RevealItem>

<Card className="border-border/60 shadow-sm">
<ToolHeader
title={t("title")}
description={t("subtitle")}
toolId="csv-excel-json"
/>
<CardContent className="space-y-6 pt-2">
<div
role="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslations } from 'next-intl';
import { IconPhoto } from '@tabler/icons-react';
import { ToolPageHeader } from '@/components/tools/tool-page-header';
import { RevealItem } from '@/components/dashboard/dashboard-reveal';
import { CATEGORY_ACCENT } from '@/components/dashboard/types';

type Mode = 'dataUri' | 'rawString';

Expand Down Expand Up @@ -97,39 +101,44 @@ export function ImageToBase64Layout() {
const outputCharCount = currentOutput.length;

return (
<div className="flex flex-col h-full gap-4">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 shrink-0 md:hidden">
<div>
<h1 className="text-lg font-semibold tracking-tight">{t('title')}</h1>
<p className="text-xs text-muted-foreground">{t('subtitle')}</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="gap-1.5 text-xs"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="h-3.5 w-3.5" />
{t('upload')}
</Button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file && file.type.startsWith('image/')) processFile(file);
e.target.value = '';
}}
/>
<Button variant="outline" size="sm" className="gap-1.5 text-xs" onClick={handleClear}>
<Trash2 className="h-3.5 w-3.5" />
{t('clear')}
</Button>
</div>
<div className="relative flex flex-col h-full gap-4 overflow-hidden dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<ToolPageHeader
icon={IconPhoto}
title={t('title')}
description={t('subtitle')}
accent={CATEGORY_ACCENT.Converters}
/>
</RevealItem>

{/* Mobile upload/clear actions */}
<div className="flex items-center gap-2 shrink-0 md:hidden">
<Button
variant="outline"
size="sm"
className="gap-1.5 text-xs"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="h-3.5 w-3.5" />
{t('upload')}
</Button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file && file.type.startsWith('image/')) processFile(file);
e.target.value = '';
}}
/>
<Button variant="outline" size="sm" className="gap-1.5 text-xs" onClick={handleClear}>
<Trash2 className="h-3.5 w-3.5" />
{t('clear')}
</Button>
</div>

{/* Mode Toggle */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import {
parseIntegerInBase,
type NumberBase,
} from '@/lib/number-base';
import { IconArrowsExchange } from '@tabler/icons-react';
import { ToolPageHeader } from '@/components/tools/tool-page-header';
import { RevealItem } from '@/components/dashboard/dashboard-reveal';
import { CATEGORY_ACCENT } from '@/components/dashboard/types';

export function NumberBaseConverterLayout() {
const t = useTranslations('NumberBaseConverter');
Expand Down Expand Up @@ -74,11 +78,17 @@ export function NumberBaseConverterLayout() {
};

return (
<div className="flex h-full min-h-0 w-full flex-col gap-4">
<div className="shrink-0 md:hidden">
<h1 className="text-lg font-semibold tracking-tight">{t('title')}</h1>
<p className="text-xs text-muted-foreground">{t('subtitle')}</p>
</div>
<div className="relative flex h-full min-h-0 w-full flex-col gap-4 overflow-hidden dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<ToolPageHeader
icon={IconArrowsExchange}
title={t('title')}
description={t('subtitle')}
accent={CATEGORY_ACCENT.Converters}
/>
</RevealItem>

<div className="grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-2">
<Card className="flex flex-col gap-4 overflow-auto p-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { Label } from '@/components/ui/label';
import { formatTimestampAll, parseTimestampInput } from '@/lib/timestamp-convert';
import { Check, Copy } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { IconCalendarTime } from '@tabler/icons-react';
import { ToolPageHeader } from '@/components/tools/tool-page-header';
import { RevealItem } from '@/components/dashboard/dashboard-reveal';
import { CATEGORY_ACCENT } from '@/components/dashboard/types';

function CopyField({
label,
Expand Down Expand Up @@ -55,15 +59,19 @@ export function TimestampConverterLayout() {
const setNow = () => setInput(String(Date.now()));

return (
<div className="flex flex-col h-full gap-4 min-h-0">
<div className="shrink-0 md:hidden">
<h1 className="text-lg font-semibold tracking-tight">{t('title')}</h1>
<p className="text-xs text-muted-foreground">
{t.rich('subtitle', {
<div className="relative flex flex-col h-full gap-4 min-h-0 overflow-hidden dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<ToolPageHeader
icon={IconCalendarTime}
title={t('title')}
description={t.rich('subtitle', {
code: (chunks) => <code className="text-foreground">{chunks}</code>,
})}
</p>
</div>
accent={CATEGORY_ACCENT.Converters}
/>
</RevealItem>

<Card className="p-4 space-y-3 shrink-0">
<div className="flex flex-col gap-2 sm:flex-row sm:items-end">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/tools/tool-page-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CATEGORY_ACCENT } from '@/components/dashboard/types'
interface ToolPageHeaderProps {
icon: React.ElementType
title: string
description: string
description: React.ReactNode
accent?: { bg: string; text: string }
className?: string
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
CommandList,
} from '@/components/ui/command'
import { convert, UNIT_CATEGORIES, getCategoryKeys, getUnitKeys } from '@/lib/unit-converter'
import { IconRuler } from '@tabler/icons-react'
import { ToolPageHeader } from '@/components/tools/tool-page-header'
import { RevealItem } from '@/components/dashboard/dashboard-reveal'
import { CATEGORY_ACCENT } from '@/components/dashboard/types'

function formatResult(n: number): string {
if (!isFinite(n)) return '—'
Expand Down Expand Up @@ -84,7 +88,21 @@ export function UnitConverterLayout() {
}, [resultText])

return (
<div className="max-w-2xl mx-auto space-y-5">
<div className="relative space-y-5 overflow-hidden dashboard-grid-bg">
<div className="dash-ambient -z-10" aria-hidden />

<RevealItem index={0}>
<ToolPageHeader
icon={IconRuler}
title={t('title')}
description={t.rich('subtitle', {
strong: (chunks) => <strong>{chunks}</strong>,
})}
accent={CATEGORY_ACCENT.Converters}
/>
</RevealItem>

<div className="max-w-2xl mx-auto space-y-5">
{/* Category — searchable combobox */}
<div className="space-y-1.5">
<Label>{t('category')}</Label>
Expand Down Expand Up @@ -212,6 +230,7 @@ export function UnitConverterLayout() {
</CardContent>
</Card>
)}
</div>
</div>
)
}
Loading