Skip to content

Commit d11195e

Browse files
authored
Merge pull request #34 from u8array/refactor/a11y-modals-and-icon-buttons
Refactor/a11y modals and icon buttons
2 parents 97b3261 + ee61393 commit d11195e

46 files changed

Lines changed: 705 additions & 395 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/components/AppShell.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { localeNames } from "../locales";
3636
import type { LocaleCode } from "../locales";
3737
import { mmToUnit } from "../lib/units";
3838
import { useT } from "../lib/useT";
39+
import { kbd } from "../lib/kbd";
3940
import { useGlobalShortcuts } from "../hooks/useGlobalShortcuts";
4041
import { useDesignFileActions } from "../hooks/useDesignFileActions";
4142
import { useZplImportExport } from "../hooks/useZplImportExport";
@@ -114,15 +115,17 @@ export function AppShell() {
114115
<button
115116
onClick={() => undo()}
116117
disabled={!canUndo}
117-
title="Undo (⌘Z)"
118+
title={`${t.app.undo} (${kbd('Z')})`}
119+
aria-label={t.app.undo}
118120
className="p-1.5 rounded text-muted hover:text-text hover:bg-surface-2 disabled:opacity-25 disabled:cursor-not-allowed transition-colors"
119121
>
120122
<ArrowUturnLeftIcon className="w-3.5 h-3.5" />
121123
</button>
122124
<button
123125
onClick={() => redo()}
124126
disabled={!canRedo}
125-
title="Redo (⌘⇧Z)"
127+
title={`${t.app.redo} (${kbd('Z', { shift: true })})`}
128+
aria-label={t.app.redo}
126129
className="p-1.5 rounded text-muted hover:text-text hover:bg-surface-2 disabled:opacity-25 disabled:cursor-not-allowed transition-colors"
127130
>
128131
<ArrowUturnRightIcon className="w-3.5 h-3.5" />

‎src/components/Fonts/FontManager.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ function FontEntry({ name }: FontEntryProps) {
6161
onClick={() => removeFont(name)}
6262
className="opacity-0 group-hover:opacity-100 font-mono text-[10px] text-muted hover:text-red-400 transition-all px-1"
6363
title={t.fonts.delete}
64+
aria-label={t.fonts.delete}
6465
>
6566
×
6667
</button>

‎src/components/Output/ImportReportModal.tsx‎

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { useState } from 'react';
22
import { XMarkIcon, ClipboardDocumentIcon, CheckIcon } from '@heroicons/react/16/solid';
33
import { partialLoss, formatReportAsText } from '../../lib/importReport';
44
import type { ImportResult } from '../../lib/importReport';
5+
import { useT } from '../../lib/useT';
6+
import { DialogShell } from '../ui/DialogShell';
57

68
export type { ImportResult };
79

@@ -55,6 +57,7 @@ interface Props {
5557
}
5658

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

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

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

84-
<ImportSummaryBody result={result} />
87+
<ImportSummaryBody result={result} />
8588

86-
<div className="flex justify-between items-center px-4 py-3 border-t border-border shrink-0">
87-
<button
88-
onClick={handleCopy}
89-
className="flex items-center gap-1.5 font-mono text-[10px] text-muted hover:text-text transition-colors"
90-
>
91-
{copied
92-
? <><CheckIcon className="w-3.5 h-3.5" /> Copied</>
93-
: <><ClipboardDocumentIcon className="w-3.5 h-3.5" /> Copy report</>}
94-
</button>
95-
<button
96-
onClick={onClose}
97-
className="px-3 py-1.5 rounded text-xs font-mono bg-accent text-bg hover:opacity-90 transition-opacity"
98-
>
99-
Close
100-
</button>
101-
</div>
89+
<div className="flex justify-between items-center px-4 py-3 border-t border-border shrink-0">
90+
<button
91+
onClick={handleCopy}
92+
className="flex items-center gap-1.5 font-mono text-[10px] text-muted hover:text-text transition-colors"
93+
>
94+
{copied
95+
? <><CheckIcon className="w-3.5 h-3.5" /> Copied</>
96+
: <><ClipboardDocumentIcon className="w-3.5 h-3.5" /> Copy report</>}
97+
</button>
98+
<button
99+
onClick={onClose}
100+
className="px-3 py-1.5 rounded text-xs font-mono bg-accent text-bg hover:opacity-90 transition-opacity"
101+
>
102+
{t.app.close}
103+
</button>
102104
</div>
103-
</div>
105+
</DialogShell>
104106
);
105107
}

‎src/components/Output/LabelPreview.tsx‎

Lines changed: 56 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { generateZPL } from '../../lib/zplGenerator';
55
import { fetchPreview, labelaryErrorMessage } from '../../lib/labelary';
66
import { triggerDownload } from '../../lib/triggerDownload';
77
import { useT } from '../../lib/useT';
8+
import { DialogShell } from '../ui/DialogShell';
89

910
interface Props {
1011
onClose: () => void;
@@ -51,72 +52,65 @@ export function LabelPreviewModal({ onClose }: Props) {
5152
};
5253

5354
return (
54-
<div
55-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
56-
onClick={onClose}
57-
role="dialog"
58-
aria-modal="true"
59-
aria-labelledby="label-preview-title"
55+
<DialogShell
56+
onClose={onClose}
57+
labelledBy="label-preview-title"
58+
boxClassName="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw] max-h-[90vh]"
6059
>
61-
<div
62-
className="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw] max-h-[90vh]"
63-
onClick={(e) => e.stopPropagation()}
64-
>
65-
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
66-
<span id="label-preview-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
67-
{t.output.previewHeading}
68-
</span>
69-
<button
70-
onClick={onClose}
71-
aria-label={t.app.close}
72-
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
73-
>
74-
<XMarkIcon className="w-4 h-4" />
75-
</button>
76-
</div>
60+
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
61+
<span id="label-preview-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
62+
{t.output.previewHeading}
63+
</span>
64+
<button
65+
onClick={onClose}
66+
aria-label={t.app.close}
67+
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
68+
>
69+
<XMarkIcon className="w-4 h-4" />
70+
</button>
71+
</div>
7772

78-
{/* Inset preview area: bg-bg gives a clear edge against the surrounding
79-
surface (especially in light mode where the label image is white).
80-
The outer div scrolls; the inner one stays at least as large as the
81-
viewport so small previews are still centered. */}
82-
<div className="flex-1 overflow-auto bg-bg min-h-24 min-w-48">
83-
<div className="min-h-full min-w-full flex items-center justify-center p-4">
84-
{loading && (
85-
<span className="font-mono text-[10px] text-muted animate-pulse">{t.output.loading}</span>
86-
)}
87-
{!loading && error && (
88-
<div className="flex flex-col items-center gap-3 max-w-64 text-center">
89-
<span className="font-mono text-[10px] text-amber-400 leading-relaxed">{error}</span>
90-
<button
91-
onClick={handleDownloadFallback}
92-
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"
93-
>
94-
<ArrowDownTrayIcon className="w-3.5 h-3.5" />
95-
Export ZPL instead
96-
</button>
97-
</div>
98-
)}
99-
{!loading && !error && previewUrl && (
100-
<img
101-
src={previewUrl}
102-
alt="Label preview"
103-
className="block shrink-0"
104-
/>
105-
)}
106-
</div>
73+
{/* Inset preview area: bg-bg gives a clear edge against the surrounding
74+
surface (especially in light mode where the label image is white).
75+
The outer div scrolls; the inner one stays at least as large as the
76+
viewport so small previews are still centered. */}
77+
<div className="flex-1 overflow-auto bg-bg min-h-24 min-w-48">
78+
<div className="min-h-full min-w-full flex items-center justify-center p-4">
79+
{loading && (
80+
<span className="font-mono text-[10px] text-muted animate-pulse">{t.output.loading}</span>
81+
)}
82+
{!loading && error && (
83+
<div className="flex flex-col items-center gap-3 max-w-64 text-center">
84+
<span className="font-mono text-[10px] text-amber-400 leading-relaxed">{error}</span>
85+
<button
86+
onClick={handleDownloadFallback}
87+
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"
88+
>
89+
<ArrowDownTrayIcon className="w-3.5 h-3.5" />
90+
Export ZPL instead
91+
</button>
92+
</div>
93+
)}
94+
{!loading && !error && previewUrl && (
95+
<img
96+
src={previewUrl}
97+
alt="Label preview"
98+
className="block shrink-0"
99+
/>
100+
)}
107101
</div>
102+
</div>
108103

109-
<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
110-
<a
111-
href="https://labelary.com/"
112-
target="_blank"
113-
rel="noreferrer"
114-
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
115-
>
116-
{t.output.previewProvider}
117-
</a>
118-
</div>
104+
<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
105+
<a
106+
href="https://labelary.com/"
107+
target="_blank"
108+
rel="noreferrer"
109+
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
110+
>
111+
{t.output.previewProvider}
112+
</a>
119113
</div>
120-
</div>
114+
</DialogShell>
121115
);
122116
}
Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { XMarkIcon } from '@heroicons/react/16/solid';
22
import { useLabelStore } from '../../store/labelStore';
33
import { useT } from '../../lib/useT';
4+
import { DialogShell } from '../ui/DialogShell';
45

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

2728
return (
28-
<div
29-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
30-
onClick={onClose}
31-
role="dialog"
32-
aria-modal="true"
33-
aria-labelledby="labelary-notice-title"
29+
<DialogShell
30+
onClose={onClose}
31+
labelledBy="labelary-notice-title"
32+
boxClassName="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw]"
3433
>
35-
<div
36-
className="bg-surface border border-border-2 rounded shadow-lg flex flex-col overflow-hidden max-w-[90vw]"
37-
onClick={(e) => e.stopPropagation()}
38-
>
39-
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
40-
<span id="labelary-notice-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
41-
{t.output.previewNoticeTitle}
42-
</span>
43-
<button
44-
onClick={onClose}
45-
aria-label={t.app.close}
46-
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
47-
>
48-
<XMarkIcon className="w-4 h-4" />
49-
</button>
50-
</div>
34+
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border-2 shrink-0">
35+
<span id="labelary-notice-title" className="font-mono text-[10px] text-muted uppercase tracking-widest">
36+
{t.output.previewNoticeTitle}
37+
</span>
38+
<button
39+
onClick={onClose}
40+
aria-label={t.app.close}
41+
className="p-0.5 rounded text-muted hover:text-text hover:bg-surface-2 transition-colors ml-6"
42+
>
43+
<XMarkIcon className="w-4 h-4" />
44+
</button>
45+
</div>
5146

52-
<div className="flex flex-col gap-3 p-6 max-w-80 text-center font-mono text-[10px] text-muted leading-relaxed">
53-
<span>{t.output.previewNoticeBody}</span>
54-
<a
55-
href="https://labelary.com/privacy.html"
56-
target="_blank"
57-
rel="noreferrer"
58-
className="text-accent hover:underline"
59-
>
60-
{t.output.previewNoticePrivacyLink}
61-
</a>
62-
<button
63-
onClick={handleContinue}
64-
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"
65-
>
66-
{t.output.previewNoticeAcknowledge}
67-
</button>
68-
</div>
47+
<div className="flex flex-col gap-3 p-6 max-w-80 text-center font-mono text-[10px] text-muted leading-relaxed">
48+
<span>{t.output.previewNoticeBody}</span>
49+
<a
50+
href="https://labelary.com/privacy.html"
51+
target="_blank"
52+
rel="noreferrer"
53+
className="text-accent hover:underline"
54+
>
55+
{t.output.previewNoticePrivacyLink}
56+
</a>
57+
<button
58+
onClick={handleContinue}
59+
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"
60+
>
61+
{t.output.previewNoticeAcknowledge}
62+
</button>
6963
</div>
70-
</div>
64+
</DialogShell>
7165
);
7266
}

0 commit comments

Comments
 (0)