ElementsPanel.tsx
4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import React from 'react';
import { ScrollArea } from '../../ui/scroll-area';
import type { ElementType } from '../../../types/labelTemplate';
/** Left element library: four categories; input-at-print items can have config for correct display */
const ELEMENT_CATEGORIES: {
title: string;
subtitle?: string;
items: { label: string; type: ElementType; config?: Record<string, unknown> }[];
}[] = [
{
title: 'Template Info',
items: [
{ label: 'Text', type: 'TEXT_STATIC' },
{ label: 'QR Code', type: 'QRCODE' },
{ label: 'Barcode', type: 'BARCODE' },
{ label: 'Blank Space', type: 'BLANK' },
{ label: 'Price', type: 'TEXT_PRICE' },
{ label: 'Image', type: 'IMAGE' },
{ label: 'Logo', type: 'IMAGE' },
],
},
{
title: 'Label Info',
items: [
{ label: 'Label Name', type: 'TEXT_PRODUCT' },
{ label: 'Text', type: 'TEXT_STATIC' },
{ label: 'QR Code', type: 'QRCODE' },
{ label: 'Barcode', type: 'BARCODE' },
{ label: 'Nutrition Facts', type: 'NUTRITION' },
{ label: 'Price', type: 'TEXT_PRICE' },
{ label: 'Duration Date', type: 'DATE' },
{ label: 'Duration Time', type: 'TIME' },
{ label: 'Duration', type: 'DURATION' },
{ label: 'Image', type: 'IMAGE' },
{ label: 'Label Type', type: 'TEXT_STATIC' },
{ label: 'How-to', type: 'TEXT_STATIC' },
{ label: 'Expiration Alert', type: 'TEXT_STATIC' },
],
},
{
title: 'Auto-generated',
items: [
{ label: 'Company', type: 'TEXT_STATIC' },
{ label: 'Employee', type: 'TEXT_STATIC' },
{ label: 'Current Date', type: 'DATE' },
{ label: 'Current Time', type: 'TIME' },
{ label: 'Label ID', type: 'TEXT_STATIC' },
],
},
{
title: 'Input at Print',
subtitle: 'Click to add to canvas',
items: [
{ label: 'Text', type: 'TEXT_STATIC', config: { inputType: 'text' } },
{ label: 'Weight', type: 'WEIGHT' },
{ label: 'Number', type: 'TEXT_STATIC', config: { inputType: 'number', text: '0' } },
{ label: 'Date & Time', type: 'DATE', config: { inputType: 'datetime' } },
{ label: 'Multiple Options', type: 'TEXT_STATIC', config: { inputType: 'options' } },
],
},
];
interface ElementsPanelProps {
onAddElement: (type: ElementType, configOverride?: Partial<Record<string, unknown>>) => void;
}
const CATEGORY_STYLES = [
{ bg: 'bg-gray-50', border: 'border-gray-200', header: 'bg-gray-100' },
{ bg: 'bg-gray-50/80', border: 'border-gray-200', header: 'bg-gray-100/90' },
{ bg: 'bg-gray-50', border: 'border-gray-200', header: 'bg-gray-100' },
{ bg: 'bg-gray-50/80', border: 'border-gray-200', header: 'bg-gray-100/90' },
];
export function ElementsPanel({ onAddElement }: ElementsPanelProps) {
return (
<div className="w-48 shrink-0 border-r border-gray-200 bg-white flex flex-col h-full">
<div className="px-3 py-2.5 border-b border-gray-200 font-semibold text-gray-800 text-sm bg-gray-50 border-l-[3px] border-l-[#1e3a8a]">
Elements
</div>
<ScrollArea className="flex-1">
<div className="p-2 space-y-4">
{ELEMENT_CATEGORIES.map((cat, idx) => {
const style = CATEGORY_STYLES[idx % CATEGORY_STYLES.length];
return (
<div
key={cat.title}
className={`rounded-lg border-2 ${style.border} ${style.bg} overflow-hidden`}
>
<div className={`pl-4 pr-2.5 py-1.5 text-xs font-semibold text-gray-700 uppercase tracking-wider ${style.header}`}>
{cat.title}
</div>
{cat.subtitle && (
<div className="pl-4 pr-2.5 py-0.5 text-[10px] text-gray-500">
{cat.subtitle}
</div>
)}
<div className="grid grid-cols-2 gap-1.5 p-2">
{cat.items.map((item, i) => (
<button
key={`${cat.title}-${item.label}-${i}`}
type="button"
onClick={() => onAddElement(item.type, item.config)}
className="text-left px-2 py-1.5 text-xs rounded border border-gray-200 bg-white hover:bg-gray-100 truncate transition-colors text-gray-700"
>
{item.label}
</button>
))}
</div>
</div>
);
})}
</div>
</ScrollArea>
</div>
);
}