@buchorg/ui-core 1.0.0
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.
- package/package.json +14 -0
- package/src/index.tsx +257 -0
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buchorg/ui-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Componentes React compartidos: Tag, StatCard, FlagBar, etc.",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"types": "src/index.tsx",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@buchorg/plugin-sdk": "*"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": { "react": ">=18" }
|
|
14
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// @buchorg/ui-core — Componentes React compartidos
|
|
3
|
+
// Agnósticos al país; consumen CSS variables del tema activo.
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
import React, { type ReactNode, type HTMLAttributes } from 'react';
|
|
7
|
+
|
|
8
|
+
// -----------------------------------------------------------
|
|
9
|
+
// Tipos comunes
|
|
10
|
+
// -----------------------------------------------------------
|
|
11
|
+
export type TagVariant = 'primary' | 'success' | 'warning' | 'danger' | 'neutral';
|
|
12
|
+
|
|
13
|
+
// -----------------------------------------------------------
|
|
14
|
+
// Tag / Badge
|
|
15
|
+
// -----------------------------------------------------------
|
|
16
|
+
const tagStyles: Record<TagVariant, React.CSSProperties> = {
|
|
17
|
+
primary: { background: 'var(--country-primary)', color: '#fff' },
|
|
18
|
+
success: { background: '#d1fae5', color: '#065f46' },
|
|
19
|
+
warning: { background: '#fef3c7', color: '#92400e' },
|
|
20
|
+
danger: { background: '#fee2e2', color: '#991b1b' },
|
|
21
|
+
neutral: { background: '#f1f5f9', color: '#475569' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface TagProps { text: string; variant?: TagVariant; className?: string; }
|
|
25
|
+
|
|
26
|
+
export function Tag({ text, variant = 'neutral', className }: TagProps) {
|
|
27
|
+
return (
|
|
28
|
+
<span
|
|
29
|
+
className={className}
|
|
30
|
+
style={{
|
|
31
|
+
display: 'inline-flex', alignItems: 'center',
|
|
32
|
+
fontSize: 11, fontWeight: 600, letterSpacing: '.04em',
|
|
33
|
+
padding: '3px 9px', borderRadius: 20, textTransform: 'uppercase',
|
|
34
|
+
...tagStyles[variant],
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{text}
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -----------------------------------------------------------
|
|
43
|
+
// StatCard — tarjeta de métrica
|
|
44
|
+
// -----------------------------------------------------------
|
|
45
|
+
interface StatCardProps {
|
|
46
|
+
icon: string;
|
|
47
|
+
title: string;
|
|
48
|
+
value: string;
|
|
49
|
+
subtitle?: string;
|
|
50
|
+
accent?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function StatCard({ icon, title, value, subtitle, accent }: StatCardProps) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
style={{
|
|
57
|
+
background: 'var(--country-surface)',
|
|
58
|
+
border: `1px solid var(--country-border)`,
|
|
59
|
+
borderLeft: accent ? '4px solid var(--country-primary)' : undefined,
|
|
60
|
+
borderRadius: 12,
|
|
61
|
+
padding: '20px 24px',
|
|
62
|
+
transition: 'transform .2s, box-shadow .2s',
|
|
63
|
+
cursor: 'default',
|
|
64
|
+
fontFamily: 'var(--country-font, inherit)',
|
|
65
|
+
}}
|
|
66
|
+
onMouseEnter={e => {
|
|
67
|
+
(e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)';
|
|
68
|
+
(e.currentTarget as HTMLElement).style.boxShadow =
|
|
69
|
+
'0 8px 24px color-mix(in srgb, var(--country-primary) 14%, transparent)';
|
|
70
|
+
}}
|
|
71
|
+
onMouseLeave={e => {
|
|
72
|
+
(e.currentTarget as HTMLElement).style.transform = '';
|
|
73
|
+
(e.currentTarget as HTMLElement).style.boxShadow = '';
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<div style={{ fontSize: 28, marginBottom: 12 }}>{icon}</div>
|
|
77
|
+
<div style={{ fontSize: 12, fontWeight: 500, color: 'var(--country-text-secondary)', marginBottom: 4 }}>
|
|
78
|
+
{title}
|
|
79
|
+
</div>
|
|
80
|
+
<div style={{ fontSize: 26, fontWeight: 700, color: 'var(--country-text)', lineHeight: 1.1 }}>
|
|
81
|
+
{value}
|
|
82
|
+
</div>
|
|
83
|
+
{subtitle && (
|
|
84
|
+
<div style={{ fontSize: 12, color: 'var(--country-text-secondary)', marginTop: 4 }}>
|
|
85
|
+
{subtitle}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// -----------------------------------------------------------
|
|
93
|
+
// SectionTitle — título de sección con línea decorativa
|
|
94
|
+
// -----------------------------------------------------------
|
|
95
|
+
interface SectionTitleProps {
|
|
96
|
+
children: ReactNode;
|
|
97
|
+
accentLeft?: boolean;
|
|
98
|
+
style?: React.CSSProperties;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function SectionTitle({ children, accentLeft, style }: SectionTitleProps) {
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
fontSize: 11, fontWeight: 700, letterSpacing: '.1em',
|
|
106
|
+
textTransform: 'uppercase', color: 'var(--country-primary)',
|
|
107
|
+
marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8,
|
|
108
|
+
...(accentLeft ? {
|
|
109
|
+
paddingLeft: 10,
|
|
110
|
+
borderLeft: '3px solid var(--country-accent)',
|
|
111
|
+
} : {}),
|
|
112
|
+
...style,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
{!accentLeft && (
|
|
117
|
+
<span style={{ flex: 1, height: 1, background: 'color-mix(in srgb, var(--country-primary) 20%, transparent)' }} />
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// -----------------------------------------------------------
|
|
124
|
+
// FlagBar — franja de bandera configurable
|
|
125
|
+
// -----------------------------------------------------------
|
|
126
|
+
interface FlagBarProps {
|
|
127
|
+
colors: [string, string, string];
|
|
128
|
+
weights: [number, number, number];
|
|
129
|
+
height?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function FlagBar({ colors, weights, height = 6 }: FlagBarProps) {
|
|
133
|
+
const [w1, w2] = weights;
|
|
134
|
+
return (
|
|
135
|
+
<div style={{
|
|
136
|
+
height,
|
|
137
|
+
background: `linear-gradient(to right,
|
|
138
|
+
${colors[0]} ${w1}%,
|
|
139
|
+
${colors[1]} ${w1}% ${w1 + w2}%,
|
|
140
|
+
${colors[2]} ${w1 + w2}%
|
|
141
|
+
)`,
|
|
142
|
+
}} />
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// -----------------------------------------------------------
|
|
147
|
+
// TaxInfoBox — caja de información tributaria
|
|
148
|
+
// -----------------------------------------------------------
|
|
149
|
+
interface TaxInfoBoxProps {
|
|
150
|
+
icon: string;
|
|
151
|
+
label: string;
|
|
152
|
+
value: string;
|
|
153
|
+
borderColor: string;
|
|
154
|
+
bgColor: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function TaxInfoBox({ icon, label, value, borderColor, bgColor }: TaxInfoBoxProps) {
|
|
158
|
+
return (
|
|
159
|
+
<div style={{
|
|
160
|
+
marginTop: 28, background: bgColor,
|
|
161
|
+
border: `1px solid color-mix(in srgb, ${borderColor} 30%, transparent)`,
|
|
162
|
+
borderLeft: `4px solid ${borderColor}`,
|
|
163
|
+
borderRadius: 10, padding: '16px 20px',
|
|
164
|
+
display: 'flex', alignItems: 'center', gap: 16,
|
|
165
|
+
}}>
|
|
166
|
+
<span style={{ fontSize: 28 }}>{icon}</span>
|
|
167
|
+
<div>
|
|
168
|
+
<div style={{ fontSize: 13, color: 'var(--country-text-secondary)' }}>{label}</div>
|
|
169
|
+
<div style={{ fontSize: 18, fontWeight: 700, color: borderColor }}>{value}</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// -----------------------------------------------------------
|
|
176
|
+
// OrdersTable — tabla genérica de pedidos
|
|
177
|
+
// -----------------------------------------------------------
|
|
178
|
+
export interface OrderRow {
|
|
179
|
+
id: string;
|
|
180
|
+
customer: string;
|
|
181
|
+
amount: string; // ya formateado
|
|
182
|
+
status: TagVariant;
|
|
183
|
+
statusText: string;
|
|
184
|
+
docType: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface OrdersTableProps {
|
|
188
|
+
rows: OrderRow[];
|
|
189
|
+
headerBg: string; // color de fondo de cabecera
|
|
190
|
+
idColor: string;
|
|
191
|
+
docBg: string;
|
|
192
|
+
docColor: string;
|
|
193
|
+
docBorder: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function OrdersTable({ rows, headerBg, idColor, docBg, docColor, docBorder }: OrdersTableProps) {
|
|
197
|
+
return (
|
|
198
|
+
<div style={{ overflowX: 'auto', background: 'var(--country-surface)', borderRadius: 12, border: '1px solid var(--country-border)' }}>
|
|
199
|
+
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
200
|
+
<thead>
|
|
201
|
+
<tr>
|
|
202
|
+
{['N° Pedido', 'Cliente', 'Comprobante', 'Monto', 'Estado'].map((h, i) => (
|
|
203
|
+
<th key={h} style={{
|
|
204
|
+
background: headerBg, color: '#fff',
|
|
205
|
+
fontSize: 11, fontWeight: 700, letterSpacing: '.06em',
|
|
206
|
+
textTransform: 'uppercase', padding: '11px 14px', textAlign: 'left',
|
|
207
|
+
borderRadius: i === 0 ? '8px 0 0 0' : i === 4 ? '0 8px 0 0' : undefined,
|
|
208
|
+
}}>{h}</th>
|
|
209
|
+
))}
|
|
210
|
+
</tr>
|
|
211
|
+
</thead>
|
|
212
|
+
<tbody>
|
|
213
|
+
{rows.map((row, idx) => (
|
|
214
|
+
<tr key={row.id} style={{ background: idx % 2 === 1 ? 'color-mix(in srgb, var(--country-primary) 2%, transparent)' : undefined }}>
|
|
215
|
+
<td style={{ padding: '12px 14px', borderBottom: '1px solid var(--country-border)' }}>
|
|
216
|
+
<span style={{ fontFamily: 'monospace', fontSize: 12, fontWeight: 700, color: idColor }}>{row.id}</span>
|
|
217
|
+
</td>
|
|
218
|
+
<td style={{ padding: '12px 14px', fontSize: 13, borderBottom: '1px solid var(--country-border)', color: 'var(--country-text)' }}>{row.customer}</td>
|
|
219
|
+
<td style={{ padding: '12px 14px', borderBottom: '1px solid var(--country-border)' }}>
|
|
220
|
+
<span style={{
|
|
221
|
+
fontSize: 10, fontWeight: 600, padding: '2px 7px', borderRadius: 4,
|
|
222
|
+
background: docBg, color: docColor,
|
|
223
|
+
border: `1px solid ${docBorder}`,
|
|
224
|
+
textTransform: 'uppercase', letterSpacing: '.04em',
|
|
225
|
+
}}>{row.docType}</span>
|
|
226
|
+
</td>
|
|
227
|
+
<td style={{ padding: '12px 14px', fontSize: 13, fontWeight: 600, borderBottom: '1px solid var(--country-border)', color: 'var(--country-text)' }}>{row.amount}</td>
|
|
228
|
+
<td style={{ padding: '12px 14px', borderBottom: '1px solid var(--country-border)' }}>
|
|
229
|
+
<Tag text={row.statusText} variant={row.status} />
|
|
230
|
+
</td>
|
|
231
|
+
</tr>
|
|
232
|
+
))}
|
|
233
|
+
</tbody>
|
|
234
|
+
</table>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// -----------------------------------------------------------
|
|
240
|
+
// Skeleton
|
|
241
|
+
// -----------------------------------------------------------
|
|
242
|
+
interface SkeletonProps extends HTMLAttributes<HTMLDivElement> { lines?: number; }
|
|
243
|
+
|
|
244
|
+
export function Skeleton({ lines = 3, style, ...rest }: SkeletonProps) {
|
|
245
|
+
return (
|
|
246
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: '12px 0', ...style }} {...rest}>
|
|
247
|
+
{Array.from({ length: lines }).map((_, i) => (
|
|
248
|
+
<div key={i} style={{
|
|
249
|
+
height: 14, borderRadius: 4,
|
|
250
|
+
width: `${70 + Math.random() * 28}%`,
|
|
251
|
+
background: 'var(--country-surface)',
|
|
252
|
+
animation: 'skeleton-shimmer 1.4s infinite',
|
|
253
|
+
}} />
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|