zark-design 1.0.0 → 3.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.
Files changed (66) hide show
  1. package/bin/cli.js +364 -90
  2. package/package.json +2 -2
  3. package/templates/README.md.hbs +264 -0
  4. package/templates/_shared/ASSETS-README.md.hbs +39 -0
  5. package/templates/_shared/tokens.css.hbs +202 -0
  6. package/templates/_shared/tokens.js.hbs +34 -0
  7. package/templates/html/components.css +740 -0
  8. package/templates/html/index.html +135 -0
  9. package/templates/html/showcase.html +3550 -0
  10. package/templates/jsx/App.example.jsx +229 -0
  11. package/templates/jsx/components/AlertCritical.jsx +43 -0
  12. package/templates/jsx/components/Avatar.jsx +41 -0
  13. package/templates/jsx/components/Badge.jsx +12 -0
  14. package/templates/jsx/components/Banner.jsx +42 -0
  15. package/templates/jsx/components/Button.jsx +43 -0
  16. package/templates/jsx/components/Chip.jsx +28 -0
  17. package/templates/jsx/components/CodeBlock.jsx +42 -0
  18. package/templates/jsx/components/EmptyState.jsx +27 -0
  19. package/templates/jsx/components/Funnel.jsx +55 -0
  20. package/templates/jsx/components/Input.jsx +53 -0
  21. package/templates/jsx/components/KanbanColumn.jsx +51 -0
  22. package/templates/jsx/components/Kbd.jsx +11 -0
  23. package/templates/jsx/components/LeadCard.jsx +79 -0
  24. package/templates/jsx/components/Modal.jsx +57 -0
  25. package/templates/jsx/components/Panel.jsx +25 -0
  26. package/templates/jsx/components/Section.jsx +28 -0
  27. package/templates/jsx/components/Segmented.jsx +26 -0
  28. package/templates/jsx/components/Sidebar.jsx +49 -0
  29. package/templates/jsx/components/Spec.jsx +19 -0
  30. package/templates/jsx/components/StatCard.jsx +44 -0
  31. package/templates/jsx/components/TableActions.jsx +34 -0
  32. package/templates/jsx/components/Tag.jsx +21 -0
  33. package/templates/jsx/components/TagDot.jsx +26 -0
  34. package/templates/jsx/components/Toast.jsx +25 -0
  35. package/templates/jsx/components/Toggle.jsx +29 -0
  36. package/templates/jsx/components.css +740 -0
  37. package/templates/{icons.jsx → jsx/icons.jsx} +20 -9
  38. package/templates/jsx/index.js +31 -0
  39. package/templates/presets/zark/preset.json +26 -0
  40. package/templates/REFERENCE.md +0 -376
  41. package/templates/SHOWCASE.html +0 -254
  42. package/templates/brand.jsx +0 -89
  43. package/templates/components.jsx +0 -385
  44. package/templates/design-canvas.jsx +0 -789
  45. package/templates/foundations.jsx +0 -363
  46. package/templates/layouts.jsx +0 -232
  47. package/templates/patterns.jsx +0 -268
  48. package/templates/primitives.jsx +0 -306
  49. package/templates/tokens.css +0 -306
  50. package/templates/visual-references/icon-zark.png +0 -0
  51. package/templates/visual-references/logo-zark-principal.png +0 -0
  52. package/templates/visual-references/pasted-1777605750385-0.png +0 -0
  53. package/templates/visual-references/pasted-1777605766298-0.png +0 -0
  54. package/templates/visual-references/pasted-1777605775820-0.png +0 -0
  55. package/templates/visual-references/pasted-1777605789833-0.png +0 -0
  56. package/templates/visual-references/pasted-1777605802420-0.png +0 -0
  57. package/templates/visual-references/pasted-1777605812470-0.png +0 -0
  58. package/templates/visual-references/pasted-1777605817688-0.png +0 -0
  59. package/templates/visual-references/pasted-1777605828485-0.png +0 -0
  60. package/templates/visual-references/pasted-1777605837137-0.png +0 -0
  61. package/templates/visual-references/pasted-1777605849789-0.png +0 -0
  62. package/templates/visual-references/pasted-1777605864942-0.png +0 -0
  63. package/templates/visual-references/pasted-1777605877920-0.png +0 -0
  64. package/templates/visual-references/pasted-1777605897353-0.png +0 -0
  65. /package/templates/{assets/zark-logo.png → presets/zark/assets/logo-zark-laranja.png} +0 -0
  66. /package/templates/{assets → presets/zark/assets}/zark-icon.png +0 -0
@@ -0,0 +1,229 @@
1
+ // App.example.jsx — exemplo completo demonstrando o design system em uso.
2
+ // Replique a estrutura no seu projeto. Lembre de importar tokens.css + components.css
3
+ // no entry-point (main.jsx / App.jsx).
4
+
5
+ import React, { useState } from 'react';
6
+ import './tokens.css';
7
+ import './components.css';
8
+
9
+ import {
10
+ Button,
11
+ Input,
12
+ Tag,
13
+ TagDot,
14
+ Badge,
15
+ Toggle,
16
+ Segmented,
17
+ Chips,
18
+ StatCard,
19
+ AlertCritical,
20
+ Funnel,
21
+ EmptyState,
22
+ KanbanBoard,
23
+ KanbanColumn,
24
+ LeadCard,
25
+ TableActions,
26
+ Sidebar,
27
+ SidebarLink,
28
+ SidebarSection,
29
+ Panel,
30
+ Section,
31
+ Modal,
32
+ Toast,
33
+ Announce,
34
+ Tip,
35
+ CodeBlock,
36
+ Avatar,
37
+ AvatarSpace,
38
+ Kbd,
39
+ Spec,
40
+ Icons,
41
+ } from './index';
42
+
43
+ import logoZark from './assets/logo-zark-laranja.png';
44
+ import iconZark from './assets/zark-icon.png';
45
+
46
+ export default function App() {
47
+ const [tab, setTab] = useState('hoje');
48
+ const [filter, setFilter] = useState('todas');
49
+ const [stealth, setStealth] = useState(false);
50
+ const [modalOpen, setModalOpen] = useState(false);
51
+
52
+ return (
53
+ <div style={{ display: 'grid', gridTemplateColumns: 'var(--sidebar-w) 1fr', minHeight: '100vh' }}>
54
+
55
+ {/* SIDEBAR */}
56
+ <Sidebar logo={<img src={logoZark} alt="ZARK" className="zk-sidebar-logo"/>}>
57
+ <SidebarSection>Workspace</SidebarSection>
58
+ <SidebarLink active icon={<Icons.Home/>} shortcut={<Kbd>⌥D</Kbd>}>Dashboard</SidebarLink>
59
+ <SidebarLink icon={<Icons.Activity/>} shortcut={<Kbd>⌥P</Kbd>}>Pipeline</SidebarLink>
60
+ <SidebarLink icon={<Icons.Usage/>} shortcut={<Kbd>⌥T</Kbd>}>Painel de Equipe</SidebarLink>
61
+ <SidebarLink icon={<Icons.Docs/>} shortcut={<Kbd>⌥F</Kbd>}>Financeiro</SidebarLink>
62
+
63
+ <SidebarSection>Compartilhados</SidebarSection>
64
+ <SidebarLink icon={<AvatarSpace name="Z" spaceKey="zark" size={18}/>}>ZARK</SidebarLink>
65
+ <SidebarLink icon={<AvatarSpace name="V" spaceKey="vipcar" size={18}/>}>VIPCAR</SidebarLink>
66
+ <SidebarLink icon={<AvatarSpace name="L" spaceKey="limppe" size={18}/>}>LIMPPE TEC</SidebarLink>
67
+ </Sidebar>
68
+
69
+ {/* MAIN */}
70
+ <main style={{ padding: 32 }}>
71
+
72
+ {/* Header */}
73
+ <header style={{
74
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
75
+ marginBottom: 32,
76
+ }}>
77
+ <div>
78
+ <div className="eyebrow">Hoje · 05 mai 2026</div>
79
+ <h1 style={{
80
+ fontFamily: 'var(--font-display)',
81
+ fontSize: 'var(--fs-3xl)',
82
+ fontWeight: 700, margin: 0, color: 'var(--ink-700)',
83
+ letterSpacing: 'var(--ls-tight)',
84
+ }}>Olá, Renan</h1>
85
+ </div>
86
+ <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
87
+ <Segmented
88
+ value={tab}
89
+ onChange={setTab}
90
+ items={[
91
+ { value: 'hoje', label: 'Hoje' },
92
+ { value: 'semana', label: 'Semana' },
93
+ { value: 'todas', label: 'Todas' },
94
+ ]}
95
+ />
96
+ <Button variant="primary" icon={<Icons.Plus size={14}/>} onClick={() => setModalOpen(true)}>
97
+ Nova Tarefa
98
+ </Button>
99
+ </div>
100
+ </header>
101
+
102
+ {/* Stats */}
103
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 24 }}>
104
+ <StatCard label="CONCLUÍDAS · SEMANA" value="8" sub="+18% vs anterior" iconColor="green" icon={<Icons.Check size={14}/>}/>
105
+ <StatCard label="CRIADAS · SEMANA" value="19" sub="média 2.7/dia" iconColor="orange" icon={<Icons.Plus size={14}/>}/>
106
+ <StatCard label="ATIVAS · TOTAL" value="45" sub="12 esta semana" iconColor="blue" icon={<Icons.Activity size={14}/>}/>
107
+ <StatCard label="ATRASADAS" value="8" sub="requer ação imediata" iconColor="red" icon={<Icons.Warning size={14}/>} critical/>
108
+ </div>
109
+
110
+ {/* Critical alert */}
111
+ <div style={{ marginBottom: 32 }}>
112
+ <AlertCritical
113
+ title="8 tarefas em atraso"
114
+ items={[
115
+ { tag: <TagDot kind="priority" value="urgent">URGENTE</TagDot>, label: 'Enviar proposta para TR PAULO', source: 'ZARK · Tarefas', meta: '3 dias' },
116
+ { tag: <TagDot kind="priority" value="urgent">URGENTE</TagDot>, label: 'CHAMANDO O HEXA!', source: 'VIPCAR · Acompanhamento', meta: '3 dias' },
117
+ ]}
118
+ footer="+ 6 outras tarefas atrasadas"
119
+ />
120
+ </div>
121
+
122
+ {/* Pipeline funnel */}
123
+ <Panel title="Funil de Vendas" kicker="Pipeline · maio">
124
+ <Funnel stages={[
125
+ { label: 'Novo', count: 0, color: '#3b82f6', percent: 0 },
126
+ { label: 'Qualificando', count: 4, color: '#7c3aed', percent: 36 },
127
+ { label: 'Reunião', count: 1, color: 'var(--brand-500)', percent: 18 },
128
+ { label: 'Proposta', count: 0, color: '#06b6d4', percent: 0 },
129
+ { label: 'Won', count: 0, color: 'var(--success-500)', percent: 0 },
130
+ { label: 'Perdido', count: 0, color: 'var(--ink-300)', percent: 0 },
131
+ ]}/>
132
+ </Panel>
133
+
134
+ <div style={{ height: 24 }}/>
135
+
136
+ {/* Filter chips + Kanban */}
137
+ <Chips
138
+ value={filter}
139
+ onChange={setFilter}
140
+ items={[
141
+ { value: 'todas', label: 'TODAS', count: 7 },
142
+ { value: 'urgentes', label: 'URGENTES', count: 4 },
143
+ { value: 'paradas', label: 'PARADAS', count: 5 },
144
+ ]}
145
+ />
146
+
147
+ <div style={{ height: 16 }}/>
148
+
149
+ <KanbanBoard>
150
+ <KanbanColumn title="A FAZER" count={3} dotColor="#9a6500" onAddClick={() => alert('Nova tarefa')}>
151
+ <LeadCard
152
+ hot
153
+ name="Tiffany Dias"
154
+ status={<TagDot kind="status" value="paused">PARADO</TagDot>}
155
+ channels={<><Icons.Bell size={12}/><Icons.Globe size={12}/></>}
156
+ time="5d"
157
+ indicator="Indicado por Karol"
158
+ paused
159
+ />
160
+ </KanbanColumn>
161
+
162
+ <KanbanColumn title="EM PROGRESSO" count={1} dotColor="var(--brand-500)" onAddClick={() => {}}>
163
+ <LeadCard
164
+ hot
165
+ name="Paulo Turismo"
166
+ company="TR PAULO"
167
+ status={<TagDot kind="status" value="paused">PARADO</TagDot>}
168
+ channels={<Icons.Bell size={12}/>}
169
+ time="5d"
170
+ followUp="Follow-up: 27/04/2026"
171
+ indicator="Indicado por Gustavo"
172
+ paused
173
+ />
174
+ </KanbanColumn>
175
+
176
+ <KanbanColumn title="EM REVISÃO" count={0} dotColor="var(--info-500)" onAddClick={() => {}}>
177
+ <EmptyState text="Arraste tarefas aqui" style={{ minHeight: 120 }}/>
178
+ </KanbanColumn>
179
+ </KanbanBoard>
180
+
181
+ <div style={{ height: 32 }}/>
182
+
183
+ {/* Inputs / settings demo */}
184
+ <Panel title="Inputs & controles">
185
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', marginBottom: 16 }}>
186
+ <Input size="sm" placeholder="Buscar tarefas..." icon={<Icons.Search size={14}/>} suffix={<Kbd>⌘K</Kbd>}/>
187
+ <Toggle on={stealth} onChange={setStealth}/>
188
+ <span style={{ fontSize: 'var(--fs-md)', color: 'var(--ink-600)' }}>Modo stealth</span>
189
+ <Tag tone="ember" size="sm">NEW</Tag>
190
+ </div>
191
+
192
+ <div style={{ display: 'flex', gap: 8 }}>
193
+ <Button variant="primary" icon={<Icons.Plus size={14}/>}>Novo Lead</Button>
194
+ <Button variant="secondary" icon={<Icons.Filter size={14}/>}>Filtrar</Button>
195
+ <Button variant="soft" icon={<Icons.Sparkles size={14}/>}>Try with AI</Button>
196
+ <Button variant="ghost" icon={<Icons.Help size={14}/>}>Help</Button>
197
+ </div>
198
+
199
+ <Spec label="Heights" value="22 / 28 / 32 / 40 px"/>
200
+ <Spec label="Primary fill" value="brand-600 → brand-700 hover"/>
201
+ </Panel>
202
+
203
+ <div style={{ height: 32 }}/>
204
+
205
+ {/* Toast */}
206
+ <Toast tone="default" icon={<img src={iconZark} alt="ZARK" style={{ height: 14, filter: 'brightness(0) invert(1)' }}/>}>
207
+ Welcome to ZARK!
208
+ </Toast>
209
+
210
+ {/* Modal */}
211
+ <Modal
212
+ open={modalOpen}
213
+ onClose={() => setModalOpen(false)}
214
+ title="Vamos começar"
215
+ description="Complete essas ações pra ganhar créditos bônus."
216
+ footer={
217
+ <>
218
+ <Button variant="ghost" onClick={() => setModalOpen(false)}>Cancelar</Button>
219
+ <Button variant="primary" onClick={() => setModalOpen(false)}>Continuar</Button>
220
+ </>
221
+ }
222
+ >
223
+ <p style={{ margin: 0, color: 'var(--ink-500)' }}>Conteúdo do modal aqui.</p>
224
+ </Modal>
225
+
226
+ </main>
227
+ </div>
228
+ );
229
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * AlertCritical — banner pleno vermelho pra ação imediata
5
+ * Padrão derivado do "8 TAREFAS EM ATRASO" do dashboard.
6
+ *
7
+ * @param title string ou ReactNode (renderizado no header)
8
+ * @param items [{ tag, label, source, meta }]
9
+ * @param footer string (ex: "+ 4 outras tarefas atrasadas")
10
+ * @param icon opcional — default usa um ⚠
11
+ */
12
+ export function AlertCritical({ title, items = [], footer, icon, onItemClick }) {
13
+ return (
14
+ <div className="alert-critical">
15
+ <div className="alert-critical-head">
16
+ {icon || (
17
+ <svg width="14" height="14" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
18
+ <path d="M9 2L1 15h16L9 2zM9 7v4M9 13v0.5"/>
19
+ </svg>
20
+ )}
21
+ {title}
22
+ </div>
23
+ <div className="alert-critical-list">
24
+ {items.map((it, i) => (
25
+ <div
26
+ key={i}
27
+ className="alert-critical-row"
28
+ onClick={() => onItemClick?.(it, i)}
29
+ style={onItemClick ? { cursor: 'pointer' } : undefined}
30
+ >
31
+ {it.tag}
32
+ <span style={{ fontWeight: 500 }}>{it.label}</span>
33
+ <span className="src">{it.source}</span>
34
+ <span style={{ fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{it.meta}</span>
35
+ </div>
36
+ ))}
37
+ </div>
38
+ {footer && <div className="alert-critical-foot">{footer}</div>}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ export default AlertCritical;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Avatar — circle (default) ou square (square=true)
5
+ *
6
+ * @param name string — primeira letra é renderizada
7
+ * @param size number (px) default 24
8
+ * @param color CSS color default --brand-500
9
+ * @param square boolean — usa raio --r-sm em vez de 50%
10
+ * @param src string — se passada, renderiza <img>
11
+ */
12
+ export function Avatar({ name, size = 24, color = 'var(--brand-500)', square = false, src, className = '', ...rest }) {
13
+ const cls = ['avatar', square && 'avatar-sq', className].filter(Boolean).join(' ');
14
+ const style = {
15
+ width: size,
16
+ height: size,
17
+ background: color,
18
+ fontSize: Math.round(size * 0.42),
19
+ ...rest.style,
20
+ };
21
+
22
+ if (src) {
23
+ return <img src={src} alt={name || ''} className={cls} style={{ ...style, objectFit: 'cover' }}/>;
24
+ }
25
+ return <span className={cls} style={style}>{(name || '?').charAt(0).toUpperCase()}</span>;
26
+ }
27
+
28
+ /**
29
+ * AvatarSpace — square avatar com cor fixa por espaço/cliente
30
+ * @param spaceKey 'zark' | 'allsec' | 'vipcar' | 'limppe' | 'gerais' | 'mixshop' | 'vipseg'
31
+ */
32
+ export function AvatarSpace({ name, spaceKey = 'zark', size = 24 }) {
33
+ const color = `var(--space-${spaceKey})`;
34
+ return (
35
+ <span className="av-space" style={{ background: color, width: size, height: size, fontSize: Math.round(size * 0.46) }}>
36
+ {(name || spaceKey).charAt(0).toUpperCase()}
37
+ </span>
38
+ );
39
+ }
40
+
41
+ export default Avatar;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Badge — small notification counter
5
+ * @param tone 'ember' (default) | 'ink'
6
+ */
7
+ export function Badge({ count, tone = 'ember', className = '', ...rest }) {
8
+ const cls = ['badge', tone === 'ink' && 'badge-ink', className].filter(Boolean).join(' ');
9
+ return <span className={cls} {...rest}>{count}</span>;
10
+ }
11
+
12
+ export default Badge;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Announce — top announcement bar (full-width, neutro)
5
+ */
6
+ export function Announce({ children, className = '', ...rest }) {
7
+ return <div className={`announce ${className}`.trim()} {...rest}>{children}</div>;
8
+ }
9
+
10
+ /**
11
+ * Tip — in-page tip card com ícone ember + título + descrição
12
+ *
13
+ * @param eyebrow string (uppercase mono small)
14
+ * @param title string
15
+ * @param desc string
16
+ * @param icon ReactNode
17
+ * @param onClose function opcional (mostra close X)
18
+ */
19
+ export function Tip({ eyebrow, title, desc, icon, onClose }) {
20
+ return (
21
+ <div className="tip">
22
+ {icon && <span className="ico">{icon}</span>}
23
+ <div style={{ flex: 1 }}>
24
+ {eyebrow && <div className="what">{eyebrow}</div>}
25
+ {title && <div className="title">{title}</div>}
26
+ {desc && <div className="desc">{desc}</div>}
27
+ </div>
28
+ {onClose && (
29
+ <svg
30
+ width="12" height="12" viewBox="0 0 18 18"
31
+ fill="none" stroke="currentColor" strokeWidth="1.5"
32
+ style={{ color: 'var(--ink-300)', cursor: 'pointer' }}
33
+ onClick={onClose}
34
+ >
35
+ <path d="M5 5l8 8M13 5l-8 8"/>
36
+ </svg>
37
+ )}
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default Announce;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Button — 5 variants × 4 sizes
5
+ * @param variant primary · secondary · soft · ghost · danger
6
+ * @param size xs · sm · md · lg
7
+ * @param icon ReactNode rendered before children
8
+ * @param iconRight ReactNode rendered after children
9
+ * @param iconOnly boolean — square button with no padding
10
+ * @param full boolean — width: 100%
11
+ * @param loading boolean — replaces icon with spinner
12
+ */
13
+ export function Button({
14
+ variant = 'secondary',
15
+ size = 'md',
16
+ icon,
17
+ iconRight,
18
+ iconOnly = false,
19
+ full = false,
20
+ loading = false,
21
+ className = '',
22
+ children,
23
+ ...rest
24
+ }) {
25
+ const cls = [
26
+ 'btn',
27
+ `btn-${variant}`,
28
+ `btn-${size}`,
29
+ iconOnly && 'btn-icon-only',
30
+ full && 'btn-full',
31
+ className,
32
+ ].filter(Boolean).join(' ');
33
+
34
+ return (
35
+ <button className={cls} {...rest}>
36
+ {loading ? <span className="spinner"/> : icon}
37
+ {children}
38
+ {iconRight}
39
+ </button>
40
+ );
41
+ }
42
+
43
+ export default Button;
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Chip — filter pill-style (mas raio sm, sem pill)
5
+ * @param items [{ value, label, count?, icon? }]
6
+ * @param value selected value
7
+ * @param onChange function(nextValue)
8
+ */
9
+ export function Chips({ items = [], value, onChange, className = '' }) {
10
+ return (
11
+ <div className={`chips ${className}`.trim()}>
12
+ {items.map(it => (
13
+ <button
14
+ key={it.value}
15
+ type="button"
16
+ className={`chip ${it.value === value ? 'active' : ''}`.trim()}
17
+ onClick={() => onChange?.(it.value)}
18
+ >
19
+ {it.icon}
20
+ {it.label}
21
+ {typeof it.count === 'number' && <span className="count">({it.count})</span>}
22
+ </button>
23
+ ))}
24
+ </div>
25
+ );
26
+ }
27
+
28
+ export default Chips;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * CodeBlock — bg --code-bg warm, font-mono, com botões eye/copy no canto.
5
+ * Pode receber tanto string quanto JSX (pra syntax highlighting custom via spans .k .s).
6
+ *
7
+ * @param code string ou ReactNode
8
+ * @param onCopy function — handler do copy
9
+ * @param onPreview function — handler do eye
10
+ */
11
+ export function CodeBlock({ code, onCopy, onPreview, className = '' }) {
12
+ const handleCopy = () => {
13
+ if (onCopy) return onCopy();
14
+ if (typeof code === 'string' && navigator.clipboard) {
15
+ navigator.clipboard.writeText(code);
16
+ }
17
+ };
18
+
19
+ return (
20
+ <div className={`code-block ${className}`.trim()}>
21
+ <div className="copy">
22
+ {onPreview && (
23
+ <button className="btn btn-ghost btn-icon-only btn-xs" onClick={onPreview} title="Preview">
24
+ <svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5">
25
+ <path d="M1 9c2-3.5 5-5 8-5s6 1.5 8 5c-2 3.5-5 5-8 5s-6-1.5-8-5z"/>
26
+ <circle cx="9" cy="9" r="2"/>
27
+ </svg>
28
+ </button>
29
+ )}
30
+ <button className="btn btn-ghost btn-icon-only btn-xs" onClick={handleCopy} title="Copiar">
31
+ <svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5">
32
+ <rect x="6" y="6" width="9" height="9" rx="1.5"/>
33
+ <path d="M3 12V4a1 1 0 011-1h8"/>
34
+ </svg>
35
+ </button>
36
+ </div>
37
+ {typeof code === 'string' ? <pre style={{ margin: 0 }}>{code}</pre> : code}
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default CodeBlock;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * EmptyState — dashed border + sparkle/icon + texto muted
5
+ * Usado em colunas Kanban vazias, drop zones do Pipeline, listas sem dados.
6
+ *
7
+ * @param icon ReactNode — default usa Sparkles
8
+ * @param text string ou ReactNode
9
+ * @param action ReactNode opcional (botão, link)
10
+ */
11
+ export function EmptyState({ icon, text, action, style, ...rest }) {
12
+ return (
13
+ <div className="empty-state" style={style} {...rest}>
14
+ <div className="ico">
15
+ {icon || (
16
+ <svg width="24" height="24" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
17
+ <path d="M9 2l1.5 4.5L15 8l-4.5 1.5L9 14l-1.5-4.5L3 8l4.5-1.5z"/>
18
+ </svg>
19
+ )}
20
+ </div>
21
+ {text && <div className="text">{text}</div>}
22
+ {action}
23
+ </div>
24
+ );
25
+ }
26
+
27
+ export default EmptyState;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Funnel — horizontal segmented funnel (Pipeline de Vendas)
5
+ *
6
+ * @param stages [{ label, count, color, percent }]
7
+ * color — qualquer CSS color (sugestão: var(--brand-500), var(--success-500), etc)
8
+ * percent — 0-100, largura proporcional do segmento
9
+ */
10
+ export function Funnel({ stages = [] }) {
11
+ const total = stages.reduce((s, x) => s + (x.percent || 0), 0);
12
+ const fillRest = Math.max(0, 100 - total);
13
+
14
+ return (
15
+ <div>
16
+ <div className="funnel">
17
+ {stages.map((s, i) => (
18
+ <span key={i} style={{ width: `${s.percent || 0}%`, background: s.color }}/>
19
+ ))}
20
+ {fillRest > 0 && <span style={{ width: `${fillRest}%`, background: 'var(--ink-200)' }}/>}
21
+ </div>
22
+ <div className="funnel-stages" style={{ gridTemplateColumns: `repeat(${stages.length}, 1fr)` }}>
23
+ {stages.map((s, i) => (
24
+ <div key={i} className="funnel-stage">
25
+ <span className="funnel-stage-label" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
26
+ <span style={{ width: 6, height: 6, borderRadius: '50%', background: s.color }}/>
27
+ {s.label}
28
+ </span>
29
+ <span className="funnel-stage-count">{s.count}</span>
30
+ </div>
31
+ ))}
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ /**
38
+ * ProgressBar — linear progress (meta, recebido vs previsto)
39
+ * @param value 0-100
40
+ * @param tone 'success' (default) | 'ember' | 'danger'
41
+ */
42
+ export function ProgressBar({ value = 0, tone = 'success', height = 4 }) {
43
+ const colorMap = {
44
+ success: 'var(--success-500)',
45
+ ember: 'var(--brand-500)',
46
+ danger: 'var(--danger-500)',
47
+ };
48
+ return (
49
+ <div className="stat-progress" style={{ height }}>
50
+ <span style={{ width: `${Math.max(0, Math.min(100, value))}%`, background: colorMap[tone] }}/>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export default Funnel;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Input — text input with optional icon and suffix
5
+ * @param size sm · md · lg
6
+ * @param icon ReactNode rendered before input
7
+ * @param suffix ReactNode rendered after input
8
+ * @param mono boolean — monospace font (API keys, codes)
9
+ * @param invalid boolean — danger border + ring
10
+ */
11
+ export function Input({
12
+ size = 'md',
13
+ icon,
14
+ suffix,
15
+ mono = false,
16
+ invalid = false,
17
+ className = '',
18
+ ...rest
19
+ }) {
20
+ const cls = [
21
+ 'input',
22
+ `input-${size}`,
23
+ mono && 'mono',
24
+ invalid && 'invalid',
25
+ className,
26
+ ].filter(Boolean).join(' ');
27
+
28
+ return (
29
+ <label className={cls}>
30
+ {icon && <span className="ico">{icon}</span>}
31
+ <input {...rest}/>
32
+ {suffix && <span className="suffix">{suffix}</span>}
33
+ </label>
34
+ );
35
+ }
36
+
37
+ /**
38
+ * UrlInputFull — full-width URL input with scheme prefix
39
+ * Substitui o antigo .url-pill (search-style)
40
+ */
41
+ export function UrlInputFull({ scheme = 'https://', placeholder, schemeIcon, ...rest }) {
42
+ return (
43
+ <div className="url-input-full">
44
+ <span className="scheme">
45
+ {schemeIcon}
46
+ {scheme}
47
+ </span>
48
+ <input placeholder={placeholder} {...rest}/>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default Input;
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * KanbanColumn — coluna de Kanban com header + dot colorido + contador
5
+ *
6
+ * @param title string
7
+ * @param count number
8
+ * @param dotColor CSS color (segue cor do status)
9
+ * @param onAddClick function — handler do "+ Nova tarefa"
10
+ * @param addLabel string — default "+ Nova tarefa"
11
+ * @param children cards (LeadCard, custom...) ou EmptyState
12
+ */
13
+ export function KanbanColumn({
14
+ title,
15
+ count,
16
+ dotColor = 'var(--ink-300)',
17
+ onAddClick,
18
+ addLabel = 'Nova tarefa',
19
+ children,
20
+ }) {
21
+ return (
22
+ <div className="k-col">
23
+ <div className="k-col-head">
24
+ <span className="lh">
25
+ <span className="dot" style={{ background: dotColor }}/>
26
+ {title}
27
+ </span>
28
+ <span className="count">{count}</span>
29
+ </div>
30
+ {onAddClick && (
31
+ <button className="k-add" onClick={onAddClick}>
32
+ <svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
33
+ <path d="M9 4v10M4 9h10"/>
34
+ </svg>
35
+ {addLabel}
36
+ </button>
37
+ )}
38
+ {children}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ /**
44
+ * KanbanBoard — wrapper opcional que aplica o display:flex correto + scroll horizontal.
45
+ * Use se quiser; também pode renderizar colunas direto num div.kanban próprio.
46
+ */
47
+ export function KanbanBoard({ children, className = '', ...rest }) {
48
+ return <div className={`kanban ${className}`.trim()} {...rest}>{children}</div>;
49
+ }
50
+
51
+ export default KanbanColumn;