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
7 changes: 5 additions & 2 deletions src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { localeNames } from "../locales";
import type { LocaleCode } from "../locales";
import { mmToUnit } from "../lib/units";
import { useT } from "../lib/useT";
import { kbd } from "../lib/kbd";
import { useGlobalShortcuts } from "../hooks/useGlobalShortcuts";
import { useDesignFileActions } from "../hooks/useDesignFileActions";
import { useZplImportExport } from "../hooks/useZplImportExport";
Expand Down Expand Up @@ -114,15 +115,17 @@ export function AppShell() {
<button
onClick={() => undo()}
disabled={!canUndo}
title="Undo (⌘Z)"
title={`${t.app.undo} (${kbd('Z')})`}
aria-label={t.app.undo}
className="p-1.5 rounded text-muted hover:text-text hover:bg-surface-2 disabled:opacity-25 disabled:cursor-not-allowed transition-colors"
>
<ArrowUturnLeftIcon className="w-3.5 h-3.5" />
</button>
<button
onClick={() => redo()}
disabled={!canRedo}
title="Redo (⌘⇧Z)"
title={`${t.app.redo} (${kbd('Z', { shift: true })})`}
aria-label={t.app.redo}
className="p-1.5 rounded text-muted hover:text-text hover:bg-surface-2 disabled:opacity-25 disabled:cursor-not-allowed transition-colors"
>
<ArrowUturnRightIcon className="w-3.5 h-3.5" />
Expand Down
1 change: 1 addition & 0 deletions src/components/Fonts/FontManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function FontEntry({ name }: FontEntryProps) {
onClick={() => removeFont(name)}
className="opacity-0 group-hover:opacity-100 font-mono text-[10px] text-muted hover:text-red-400 transition-all px-1"
title={t.fonts.delete}
aria-label={t.fonts.delete}
>
×
</button>
Expand Down
66 changes: 34 additions & 32 deletions src/components/Output/ImportReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useState } from 'react';
import { XMarkIcon, ClipboardDocumentIcon, CheckIcon } from '@heroicons/react/16/solid';
import { partialLoss, formatReportAsText } from '../../lib/importReport';
import type { ImportResult } from '../../lib/importReport';
import { useT } from '../../lib/useT';
import { DialogShell } from '../ui/DialogShell';

export type { ImportResult };

Expand Down Expand Up @@ -55,6 +57,7 @@ interface Props {
}

export function ImportReportModal({ result, onClose }: Props) {
const t = useT();
const [copied, setCopied] = useState(false);

const handleCopy = () => {
Expand All @@ -65,41 +68,40 @@ export function ImportReportModal({ result, onClose }: Props) {
};

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
style={{ background: 'rgba(0,0,0,0.6)' }}
onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
<DialogShell
onClose={onClose}
labelledBy="import-report-title"
boxClassName="bg-surface border border-border rounded-lg w-130 flex flex-col shadow-2xl max-h-[80vh]"
>
<div className="bg-surface border border-border rounded-lg w-130 flex flex-col shadow-2xl max-h-[80vh]">
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
<span className="font-mono text-xs text-muted uppercase tracking-widest">Import Report</span>
<button
onClick={onClose}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
<span id="import-report-title" className="font-mono text-xs text-muted uppercase tracking-widest">Import Report</span>
<button
onClick={onClose}
aria-label={t.app.close}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>

<ImportSummaryBody result={result} />
<ImportSummaryBody result={result} />

<div className="flex justify-between items-center px-4 py-3 border-t border-border shrink-0">
<button
onClick={handleCopy}
className="flex items-center gap-1.5 font-mono text-[10px] text-muted hover:text-text transition-colors"
>
{copied
? <><CheckIcon className="w-3.5 h-3.5" /> Copied</>
: <><ClipboardDocumentIcon className="w-3.5 h-3.5" /> Copy report</>}
</button>
<button
onClick={onClose}
className="px-3 py-1.5 rounded text-xs font-mono bg-accent text-bg hover:opacity-90 transition-opacity"
>
Close
</button>
</div>
<div className="flex justify-between items-center px-4 py-3 border-t border-border shrink-0">
<button
onClick={handleCopy}
className="flex items-center gap-1.5 font-mono text-[10px] text-muted hover:text-text transition-colors"
>
{copied
? <><CheckIcon className="w-3.5 h-3.5" /> Copied</>
: <><ClipboardDocumentIcon className="w-3.5 h-3.5" /> Copy report</>}
</button>
<button
onClick={onClose}
className="px-3 py-1.5 rounded text-xs font-mono bg-accent text-bg hover:opacity-90 transition-opacity"
>
{t.app.close}
</button>
</div>
</div>
</DialogShell>
);
}
118 changes: 56 additions & 62 deletions src/components/Output/LabelPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { generateZPL } from '../../lib/zplGenerator';
import { fetchPreview, labelaryErrorMessage } from '../../lib/labelary';
import { triggerDownload } from '../../lib/triggerDownload';
import { useT } from '../../lib/useT';
import { DialogShell } from '../ui/DialogShell';

interface Props {
onClose: () => void;
Expand Down Expand Up @@ -51,72 +52,65 @@ export function LabelPreviewModal({ onClose }: Props) {
};

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-labelledby="label-preview-title"
<DialogShell
onClose={onClose}
labelledBy="label-preview-title"
boxClassName="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw] max-h-[90vh]"
>
<div
className="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw] max-h-[90vh]"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
<span id="label-preview-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
{t.output.previewHeading}
</span>
<button
onClick={onClose}
aria-label={t.app.close}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
<span id="label-preview-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
{t.output.previewHeading}
</span>
<button
onClick={onClose}
aria-label={t.app.close}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>

{/* Inset preview area: bg-bg gives a clear edge against the surrounding
surface (especially in light mode where the label image is white).
The outer div scrolls; the inner one stays at least as large as the
viewport so small previews are still centered. */}
<div className="flex-1 overflow-auto bg-bg min-h-24 min-w-48">
<div className="min-h-full min-w-full flex items-center justify-center p-4">
{loading && (
<span className="font-mono text-[10px] text-muted animate-pulse">{t.output.loading}</span>
)}
{!loading && error && (
<div className="flex flex-col items-center gap-3 max-w-64 text-center">
<span className="font-mono text-[10px] text-amber-400 leading-relaxed">{error}</span>
<button
onClick={handleDownloadFallback}
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-[10px] font-mono bg-surface-2 border border-border text-muted hover:text-text hover:border-accent transition-colors"
>
<ArrowDownTrayIcon className="w-3.5 h-3.5" />
Export ZPL instead
</button>
</div>
)}
{!loading && !error && previewUrl && (
<img
src={previewUrl}
alt="Label preview"
className="block shrink-0"
/>
)}
</div>
{/* Inset preview area: bg-bg gives a clear edge against the surrounding
surface (especially in light mode where the label image is white).
The outer div scrolls; the inner one stays at least as large as the
viewport so small previews are still centered. */}
<div className="flex-1 overflow-auto bg-bg min-h-24 min-w-48">
<div className="min-h-full min-w-full flex items-center justify-center p-4">
{loading && (
<span className="font-mono text-[10px] text-muted animate-pulse">{t.output.loading}</span>
)}
{!loading && error && (
<div className="flex flex-col items-center gap-3 max-w-64 text-center">
<span className="font-mono text-[10px] text-amber-400 leading-relaxed">{error}</span>
<button
onClick={handleDownloadFallback}
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-[10px] font-mono bg-surface-2 border border-border text-muted hover:text-text hover:border-accent transition-colors"
>
<ArrowDownTrayIcon className="w-3.5 h-3.5" />
Export ZPL instead
</button>
</div>
)}
{!loading && !error && previewUrl && (
<img
src={previewUrl}
alt="Label preview"
className="block shrink-0"
/>
)}
</div>
</div>

<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
<a
href="https://labelary.com/"
target="_blank"
rel="noreferrer"
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
>
{t.output.previewProvider}
</a>
</div>
<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
<a
href="https://labelary.com/"
target="_blank"
rel="noreferrer"
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
>
{t.output.previewProvider}
</a>
</div>
</div>
</DialogShell>
);
}
74 changes: 34 additions & 40 deletions src/components/Output/LabelaryNoticeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { XMarkIcon } from '@heroicons/react/16/solid';
import { useLabelStore } from '../../store/labelStore';
import { useT } from '../../lib/useT';
import { DialogShell } from '../ui/DialogShell';

interface Props {
/** Called after the modal flipped the store flag, so callers only need
Expand All @@ -25,48 +26,41 @@ export function LabelaryNoticeModal({ onContinue, onClose }: Props) {
};

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-labelledby="labelary-notice-title"
<DialogShell
onClose={onClose}
labelledBy="labelary-notice-title"
boxClassName="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw]"
>
<div
className="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw]"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
<span id="labelary-notice-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
{t.output.previewNoticeTitle}
</span>
<button
onClick={onClose}
aria-label={t.app.close}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
<span id="labelary-notice-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
{t.output.previewNoticeTitle}
</span>
<button
onClick={onClose}
aria-label={t.app.close}
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>

<div className="flex flex-col gap-3 p-6 max-w-80 text-center font-mono text-[10px] text-muted leading-relaxed">
<span>{t.output.previewNoticeBody}</span>
<a
href="https://labelary.com/privacy.html"
target="_blank"
rel="noreferrer"
className="text-accent hover:underline"
>
{t.output.previewNoticePrivacyLink}
</a>
<button
onClick={handleContinue}
className="self-center mt-1 px-3 py-1.5 rounded text-[10px] font-mono bg-surface-2 border border-border text-text hover:border-accent transition-colors"
>
{t.output.previewNoticeAcknowledge}
</button>
</div>
<div className="flex flex-col gap-3 p-6 max-w-80 text-center font-mono text-[10px] text-muted leading-relaxed">
<span>{t.output.previewNoticeBody}</span>
<a
href="https://labelary.com/privacy.html"
target="_blank"
rel="noreferrer"
className="text-accent hover:underline"
>
{t.output.previewNoticePrivacyLink}
</a>
<button
onClick={handleContinue}
className="self-center mt-1 px-3 py-1.5 rounded text-[10px] font-mono bg-surface-2 border border-border text-text hover:border-accent transition-colors"
>
{t.output.previewNoticeAcknowledge}
</button>
</div>
</div>
</DialogShell>
);
}
Loading