@nice2dev/ui-printing 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +56 -0
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.mjs +3085 -0
  6. package/dist/print-button/NicePrintButton.d.ts +5 -0
  7. package/dist/print-button/NicePrintButton.d.ts.map +1 -0
  8. package/dist/print-button/index.d.ts +2 -0
  9. package/dist/print-button/index.d.ts.map +1 -0
  10. package/dist/print-preview/NicePrintPreview.d.ts +5 -0
  11. package/dist/print-preview/NicePrintPreview.d.ts.map +1 -0
  12. package/dist/print-preview/index.d.ts +2 -0
  13. package/dist/print-preview/index.d.ts.map +1 -0
  14. package/dist/print-queue/NicePrintQueue.d.ts +5 -0
  15. package/dist/print-queue/NicePrintQueue.d.ts.map +1 -0
  16. package/dist/print-queue/index.d.ts +2 -0
  17. package/dist/print-queue/index.d.ts.map +1 -0
  18. package/dist/style.css +1 -0
  19. package/dist/template-browser/NiceTemplateBrowser.d.ts +5 -0
  20. package/dist/template-browser/NiceTemplateBrowser.d.ts.map +1 -0
  21. package/dist/template-browser/index.d.ts +2 -0
  22. package/dist/template-browser/index.d.ts.map +1 -0
  23. package/dist/template-editor/NiceTemplateEditor.d.ts +6 -0
  24. package/dist/template-editor/NiceTemplateEditor.d.ts.map +1 -0
  25. package/dist/template-editor/index.d.ts +2 -0
  26. package/dist/template-editor/index.d.ts.map +1 -0
  27. package/dist/types/printingTypes.d.ts +249 -0
  28. package/dist/types/printingTypes.d.ts.map +1 -0
  29. package/package.json +66 -0
  30. package/src/globals.d.ts +9 -0
  31. package/src/index.ts +69 -0
  32. package/src/print-button/NicePrintButton.tsx +182 -0
  33. package/src/print-button/PrintButton.module.css +167 -0
  34. package/src/print-button/index.ts +1 -0
  35. package/src/print-preview/NicePrintPreview.tsx +417 -0
  36. package/src/print-preview/PrintPreview.module.css +122 -0
  37. package/src/print-preview/index.ts +1 -0
  38. package/src/print-queue/NicePrintQueue.tsx +350 -0
  39. package/src/print-queue/PrintQueue.module.css +203 -0
  40. package/src/print-queue/index.ts +1 -0
  41. package/src/template-browser/NiceTemplateBrowser.tsx +221 -0
  42. package/src/template-browser/TemplateBrowser.module.css +277 -0
  43. package/src/template-browser/index.ts +1 -0
  44. package/src/template-editor/NiceTemplateEditor.tsx +2386 -0
  45. package/src/template-editor/NiceTemplateEditor.tsx.first +1011 -0
  46. package/src/template-editor/NiceTemplateEditor.tsx.tmp +392 -0
  47. package/src/template-editor/TemplateEditor.module.css +1462 -0
  48. package/src/template-editor/index.ts +1 -0
  49. package/src/types/printingTypes.ts +301 -0
@@ -0,0 +1,221 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events -- modal overlay backdrops (Esc-on-parent provides keyboard close), selection rows in .map() accompanied by visible cursor:pointer styling and sibling buttons; full keyboard migration via useAccessibleClick + extracted subcomponents tracked in TODO_1.0.10 A2.3 */
2
+ import React, { useState } from 'react';
3
+
4
+ import type { PrintTemplate, TemplateId as _TemplateId, NiceTemplateBrowserProps } from '../types/printingTypes';
5
+
6
+ import styles from './TemplateBrowser.module.css';
7
+
8
+ const CATEGORIES = ['Faktury', 'Raporty', 'Etykiety', 'Listy', 'Umowy', 'Inne'];
9
+
10
+ function importTemplateFromJson(file: File): Promise<PrintTemplate> {
11
+ return new Promise((resolve, reject) => {
12
+ const reader = new FileReader();
13
+ reader.onload = (e) => {
14
+ try {
15
+ resolve(JSON.parse(e.target?.result as string));
16
+ } catch (err) {
17
+ reject(err);
18
+ }
19
+ };
20
+ reader.readAsText(file);
21
+ });
22
+ }
23
+
24
+ function exportTemplate(template: PrintTemplate) {
25
+ const blob = new Blob([JSON.stringify(template, null, 2)], { type: 'application/json' });
26
+ const url = URL.createObjectURL(blob);
27
+ const a = document.createElement('a');
28
+ a.href = url;
29
+ a.download = `${template.name.replace(/\s+/g, '_')}.json`;
30
+ a.click();
31
+ URL.revokeObjectURL(url);
32
+ }
33
+
34
+ export const NiceTemplateBrowser: React.FC<NiceTemplateBrowserProps> = ({
35
+ templates,
36
+ selectedId,
37
+ onSelect,
38
+ onImport,
39
+ onExport,
40
+ onDelete,
41
+ onNew,
42
+ className,
43
+ style,
44
+ theme = 'light',
45
+ }) => {
46
+ const [search, setSearch] = useState('');
47
+ const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
48
+ const [hoveredId, setHoveredId] = useState<string | null>(null);
49
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
50
+
51
+ const handleFileImport = async (e: React.ChangeEvent<HTMLInputElement>) => {
52
+ const file = e.target.files?.[0];
53
+ if (!file) {
54
+ return;
55
+ }
56
+ try {
57
+ const tpl = await importTemplateFromJson(file);
58
+ onImport?.(tpl);
59
+ } catch {
60
+ // invalid file — silently ignore upload errors from untrusted sources
61
+ }
62
+ e.target.value = '';
63
+ };
64
+
65
+ const filtered = templates.filter((t) => {
66
+ const matchSearch =
67
+ !search ||
68
+ t.name.toLowerCase().includes(search.toLowerCase()) ||
69
+ (t.description ?? '').toLowerCase().includes(search.toLowerCase()) ||
70
+ (t.tags ?? []).some((tag) => tag.toLowerCase().includes(search.toLowerCase()));
71
+ const matchCat = !categoryFilter || t.category === categoryFilter;
72
+ return matchSearch && matchCat;
73
+ });
74
+
75
+ return (
76
+ <div
77
+ className={`${styles.root} ${theme === 'dark' ? styles.dark : ''} ${className ?? ''}`}
78
+ style={style}
79
+ >
80
+ {/* Search + actions bar */}
81
+ <div className={styles.topBar}>
82
+ <input
83
+ type="search"
84
+ className={styles.searchInput}
85
+ placeholder="Szukaj szablonów…"
86
+ value={search}
87
+ onChange={(e) => setSearch(e.target.value)}
88
+ />
89
+ <button className={styles.actionBtn} onClick={onNew} title="Nowy szablon">
90
+ + Nowy
91
+ </button>
92
+ <button
93
+ className={styles.actionBtn}
94
+ onClick={() => fileInputRef.current?.click()}
95
+ title="Importuj JSON"
96
+ >
97
+ ⬆ Import
98
+ </button>
99
+ <input ref={fileInputRef} type="file" accept=".json" hidden onChange={handleFileImport} />
100
+ </div>
101
+
102
+ {/* Category chips */}
103
+ <div className={styles.categoryRow}>
104
+ <button
105
+ className={`${styles.catChip} ${!categoryFilter ? styles.catChipActive : ''}`}
106
+ onClick={() => setCategoryFilter(null)}
107
+ >
108
+ Wszystkie
109
+ </button>
110
+ {CATEGORIES.map((cat) => (
111
+ <button
112
+ key={cat}
113
+ className={`${styles.catChip} ${categoryFilter === cat ? styles.catChipActive : ''}`}
114
+ onClick={() => setCategoryFilter((c) => (c === cat ? null : cat))}
115
+ >
116
+ {cat}
117
+ </button>
118
+ ))}
119
+ </div>
120
+
121
+ {/* Template grid */}
122
+ <div className={styles.grid}>
123
+ {filtered.map((tpl) => (
124
+ <div
125
+ key={tpl.id}
126
+ className={`${styles.card} ${selectedId === tpl.id ? styles.cardSelected : ''}`}
127
+ onClick={() => onSelect?.(tpl)}
128
+ onMouseEnter={() => setHoveredId(tpl.id)}
129
+ onMouseLeave={() => setHoveredId(null)}
130
+ >
131
+ {/* Thumbnail */}
132
+ <div className={styles.thumbnail}>
133
+ {tpl.previewImageUrl ? (
134
+ <img src={tpl.previewImageUrl} alt={tpl.name} className={styles.thumbnailImg} />
135
+ ) : (
136
+ <div className={styles.thumbnailPlaceholder}>
137
+ <div className={styles.paperMini}>
138
+ <div
139
+ className={styles.paperLine}
140
+ style={{ width: '80%' } as React.CSSProperties}
141
+ />
142
+ <div
143
+ className={styles.paperLine}
144
+ style={{ width: '60%' } as React.CSSProperties}
145
+ />
146
+ <div
147
+ className={styles.paperLine}
148
+ style={{ width: '90%' } as React.CSSProperties}
149
+ />
150
+ <div
151
+ className={styles.paperLine}
152
+ style={{ width: '50%' } as React.CSSProperties}
153
+ />
154
+ </div>
155
+ </div>
156
+ )}
157
+ {/* Hover actions */}
158
+ {hoveredId === tpl.id && (
159
+ <div className={styles.cardOverlay}>
160
+ <button
161
+ className={styles.overlayBtn}
162
+ onClick={(e) => {
163
+ e.stopPropagation();
164
+ onExport?.(tpl);
165
+ exportTemplate(tpl);
166
+ }}
167
+ title="Eksportuj"
168
+ >
169
+
170
+ </button>
171
+ {onDelete && (
172
+ <button
173
+ className={styles.overlayBtnDanger}
174
+ onClick={(e) => {
175
+ e.stopPropagation();
176
+ onDelete(tpl.id);
177
+ }}
178
+ title="Usuń"
179
+ >
180
+
181
+ </button>
182
+ )}
183
+ </div>
184
+ )}
185
+ </div>
186
+
187
+ {/* Card info */}
188
+ <div className={styles.cardBody}>
189
+ <div className={styles.cardName}>{tpl.name}</div>
190
+ {tpl.description && <div className={styles.cardDesc}>{tpl.description}</div>}
191
+ <div className={styles.cardMeta}>
192
+ <span className={styles.cardSize}>
193
+ {tpl.paperSize} {tpl.orientation === 'landscape' ? '↔' : '↕'}
194
+ </span>
195
+ {tpl.category && <span className={styles.cardCat}>{tpl.category}</span>}
196
+ </div>
197
+ {tpl.tags && tpl.tags.length > 0 && (
198
+ <div className={styles.cardTags}>
199
+ {tpl.tags.slice(0, 3).map((tag) => (
200
+ <span key={tag} className={styles.cardTag}>
201
+ {tag}
202
+ </span>
203
+ ))}
204
+ </div>
205
+ )}
206
+ </div>
207
+ </div>
208
+ ))}
209
+
210
+ {filtered.length === 0 && (
211
+ <div className={styles.emptyState}>
212
+ <span className={styles.emptyIcon}>📄</span>
213
+ <span>
214
+ {search ? `Brak wyników dla "${search}"` : 'Brak szablonów. Utwórz pierwszy!'}
215
+ </span>
216
+ </div>
217
+ )}
218
+ </div>
219
+ </div>
220
+ );
221
+ };
@@ -0,0 +1,277 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ font-family: inherit;
6
+ font-size: 13px;
7
+ background: var(--nice-bg-secondary, #f8fafc);
8
+ border: 1px solid var(--nice-border, #e2e8f0);
9
+ border-radius: var(--nice-radius-lg, 8px);
10
+ overflow: hidden;
11
+ }
12
+
13
+ .dark { background: var(--nice-text, #0f172a); color: var(--nice-border, #e2e8f0); border-color: var(--nice-text, #1e293b); }
14
+
15
+ /* ── Top bar ── */
16
+ .topBar {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--nice-space-2, 8px);
20
+ padding: var(--nice-space-2-5, 10px) var(--nice-space-3, 12px);
21
+ background: var(--nice-bg, #FFF);
22
+ border-bottom: 1px solid var(--nice-border, #e2e8f0);
23
+ flex-shrink: 0;
24
+ }
25
+
26
+ .dark .topBar { background: var(--nice-text, #1e293b); border-bottom-color: var(--nice-text, #334155); }
27
+
28
+ .searchInput {
29
+ flex: 1;
30
+ padding: var(--nice-space-1-5, 6px) var(--nice-space-2-5, 10px);
31
+ border: 1px solid var(--nice-border, #e2e8f0);
32
+ border-radius: var(--nice-radius-md, 6px);
33
+ font-size: 13px;
34
+ background: var(--nice-bg-secondary, #f8fafc);
35
+ color: var(--nice-text, #1e293b);
36
+ outline: none;
37
+ }
38
+
39
+ .searchInput:focus { border-color: var(--nice-primary-light, #93c5fd); background: var(--nice-bg, #FFF); }
40
+ .dark .searchInput { background: var(--nice-text, #334155); border-color: var(--nice-text-secondary, #475569); color: var(--nice-border, #e2e8f0); }
41
+
42
+ .actionBtn {
43
+ padding: var(--nice-space-1-5, 6px) var(--nice-space-3, 12px);
44
+ border: 1px solid var(--nice-border, #e2e8f0);
45
+ border-radius: var(--nice-radius-md, 6px);
46
+ background: var(--nice-bg, #FFF);
47
+ cursor: pointer;
48
+ font-size: 12px;
49
+ font-weight: 500;
50
+ color: var(--nice-text-secondary, #475569);
51
+ white-space: nowrap;
52
+ transition: all 0.1s;
53
+ }
54
+
55
+ .actionBtn:hover { background: var(--nice-bg-secondary, #f1f5f9); }
56
+ .dark .actionBtn { background: var(--nice-text, #334155); border-color: var(--nice-text-secondary, #475569); color: var(--nice-border, #e2e8f0); }
57
+
58
+ /* ── Categories ── */
59
+ .categoryRow {
60
+ display: flex;
61
+ gap: var(--nice-space-1-5, 6px);
62
+ padding: var(--nice-space-2, 8px) var(--nice-space-3, 12px);
63
+ border-bottom: 1px solid var(--nice-bg-secondary, #f1f5f9);
64
+ flex-wrap: wrap;
65
+ flex-shrink: 0;
66
+ background: var(--nice-bg, #FFF);
67
+ }
68
+
69
+ .dark .categoryRow { background: var(--nice-text, #1e293b); border-bottom-color: var(--nice-text, #334155); }
70
+
71
+ .catChip {
72
+ padding: var(--nice-space-0-75, 3px) var(--nice-space-2-5, 10px);
73
+ border: 1px solid var(--nice-border, #e2e8f0);
74
+ border-radius: var(--nice-radius-xl, 14px);
75
+ background: var(--nice-bg, #FFF);
76
+ cursor: pointer;
77
+ font-size: 12px;
78
+ color: var(--nice-text-secondary, #64748b);
79
+ transition: all 0.1s;
80
+ }
81
+
82
+ .catChip:hover { background: var(--nice-bg-secondary, #f1f5f9); }
83
+
84
+ .catChipActive {
85
+ background: var(--nice-primary-hover, #2563eb);
86
+ border-color: var(--nice-primary-hover, #2563eb);
87
+ color: var(--nice-bg, #FFF);
88
+ }
89
+
90
+ /* ── Template grid ── */
91
+ .grid {
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ gap: var(--nice-space-3, 12px);
95
+ padding: var(--nice-space-3, 12px);
96
+ overflow-y: auto;
97
+ flex: 1;
98
+ align-content: flex-start;
99
+ }
100
+
101
+ /* ── Template card ── */
102
+ .card {
103
+ flex: 0 0 auto;
104
+ width: 180px;
105
+ background: var(--nice-bg, #FFF);
106
+ border: 2px solid var(--nice-border, #e2e8f0);
107
+ border-radius: var(--nice-radius-lg, 8px);
108
+ overflow: hidden;
109
+ cursor: pointer;
110
+ transition: all 0.15s;
111
+ }
112
+
113
+ .card:hover {
114
+ border-color: var(--nice-primary-light, #93c5fd);
115
+ box-shadow: 0 2px 8px var(--nice-overlay-8, rgba(0, 0, 0, 0.08));
116
+ }
117
+
118
+ .cardSelected {
119
+ border-color: var(--nice-primary-hover, #2563eb) !important;
120
+ box-shadow: 0 0 0 3px var(--nice-primary-tint-15, rgba(37, 99, 235, 0.15));
121
+ }
122
+
123
+ .dark .card { background: var(--nice-text, #1e293b); border-color: var(--nice-text, #334155); }
124
+
125
+ /* Thumbnail */
126
+ .thumbnail {
127
+ position: relative;
128
+ height: 120px;
129
+ background: var(--nice-bg-secondary, #f1f5f9);
130
+ overflow: hidden;
131
+ }
132
+
133
+ .thumbnailImg {
134
+ width: 100%;
135
+ height: 100%;
136
+ object-fit: cover;
137
+ }
138
+
139
+ .thumbnailPlaceholder {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ height: 100%;
144
+ }
145
+
146
+ .paperMini {
147
+ width: 70px;
148
+ background: var(--nice-bg, #FFF);
149
+ border: 1px solid var(--nice-border, #e2e8f0);
150
+ border-radius: var(--nice-radius-sm, 2px);
151
+ padding: var(--nice-space-2, 8px) var(--nice-space-1-5, 6px);
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: var(--nice-space-1, 4px);
155
+ box-shadow: 0 1px 4px var(--nice-overlay-8, rgba(0, 0, 0, 0.08));
156
+ }
157
+
158
+ .paperLine {
159
+ height: 4px;
160
+ background: var(--nice-border, #e2e8f0);
161
+ border-radius: var(--nice-radius-sm, 2px);
162
+ }
163
+
164
+ /* Hover overlay */
165
+ .cardOverlay {
166
+ position: absolute;
167
+ inset: 0;
168
+ background: var(--nice-overlay-35, rgba(0, 0, 0, 0.35));
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ gap: var(--nice-space-2, 8px);
173
+ }
174
+
175
+ .overlayBtn {
176
+ width: 32px;
177
+ height: 32px;
178
+ background: var(--nice-bg, #FFF);
179
+ border: none;
180
+ border-radius: var(--nice-radius-full, 50%);
181
+ cursor: pointer;
182
+ font-size: 14px;
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ transition: all 0.1s;
187
+ }
188
+
189
+ .overlayBtn:hover { background: var(--nice-primary-bg, #e0f2fe); }
190
+
191
+ .overlayBtnDanger {
192
+ width: 28px; height: 28px;
193
+ display: flex; align-items: center; justify-content: center;
194
+ background: var(--nice-overlay-light-90, rgba(255, 255, 255, 0.9));
195
+ border: 1px solid var(--nice-border, #e2e8f0);
196
+ border-radius: var(--nice-radius-md, 6px);
197
+ cursor: pointer;
198
+ font-size: 14px;
199
+ color: var(--nice-danger, #ef4444);
200
+ transition: all 0.1s;
201
+ }
202
+
203
+ .overlayBtnDanger:hover { background: var(--nice-danger-bg, #fee2e2) !important; }
204
+
205
+ /* Card body */
206
+ .cardBody { padding: var(--nice-space-2, 8px) var(--nice-space-2-5, 10px); }
207
+
208
+ .cardName {
209
+ font-weight: 600;
210
+ font-size: 13px;
211
+ color: var(--nice-text, #0f172a);
212
+ white-space: nowrap;
213
+ overflow: hidden;
214
+ text-overflow: ellipsis;
215
+ margin-bottom: var(--nice-space-0-5, 2px);
216
+ }
217
+
218
+ .dark .cardName { color: var(--nice-bg-secondary, #f1f5f9); }
219
+
220
+ .cardDesc {
221
+ font-size: 11px;
222
+ color: var(--nice-text-secondary, #64748b);
223
+ display: -webkit-box;
224
+ -webkit-line-clamp: 2;
225
+ line-clamp: 2;
226
+ -webkit-box-orient: vertical;
227
+ overflow: hidden;
228
+ margin-bottom: var(--nice-space-1, 4px);
229
+ }
230
+
231
+ .cardMeta {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: var(--nice-space-1-5, 6px);
235
+ margin-bottom: var(--nice-space-1, 4px);
236
+ }
237
+
238
+ .cardSize {
239
+ font-size: 10px;
240
+ color: var(--nice-text-muted, #94a3b8);
241
+ padding: var(--nice-space-px, 1px) var(--nice-space-1-25, 5px);
242
+ background: var(--nice-bg-secondary, #f1f5f9);
243
+ border-radius: var(--nice-radius-sm, 4px);
244
+ }
245
+
246
+ .cardCat {
247
+ font-size: 10px;
248
+ color: var(--nice-primary-hover, #2563eb);
249
+ background: var(--nice-primary-bg, #eff6ff);
250
+ padding: var(--nice-space-px, 1px) var(--nice-space-1-25, 5px);
251
+ border-radius: var(--nice-radius-sm, 4px);
252
+ }
253
+
254
+ .cardTags { display: flex; gap: var(--nice-space-0-75, 3px); flex-wrap: wrap; }
255
+
256
+ .cardTag {
257
+ font-size: 9px;
258
+ color: var(--nice-text-muted, #94a3b8);
259
+ background: var(--nice-bg-secondary, #f8fafc);
260
+ border: 1px solid var(--nice-border, #e2e8f0);
261
+ border-radius: var(--nice-radius-sm, 4px);
262
+ padding: var(--nice-space-px, 1px) var(--nice-space-1, 4px);
263
+ }
264
+
265
+ /* Empty state */
266
+ .emptyState {
267
+ width: 100%;
268
+ padding: var(--nice-space-12, 48px);
269
+ display: flex;
270
+ flex-direction: column;
271
+ align-items: center;
272
+ gap: var(--nice-space-2, 8px);
273
+ color: var(--nice-text-muted, #94a3b8);
274
+ font-size: 14px;
275
+ }
276
+
277
+ .emptyIcon { font-size: 40px; }
@@ -0,0 +1 @@
1
+ export { NiceTemplateBrowser } from './NiceTemplateBrowser';