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.
- package/bin/cli.js +364 -90
- package/package.json +2 -2
- package/templates/README.md.hbs +264 -0
- package/templates/_shared/ASSETS-README.md.hbs +39 -0
- package/templates/_shared/tokens.css.hbs +202 -0
- package/templates/_shared/tokens.js.hbs +34 -0
- package/templates/html/components.css +740 -0
- package/templates/html/index.html +135 -0
- package/templates/html/showcase.html +3550 -0
- package/templates/jsx/App.example.jsx +229 -0
- package/templates/jsx/components/AlertCritical.jsx +43 -0
- package/templates/jsx/components/Avatar.jsx +41 -0
- package/templates/jsx/components/Badge.jsx +12 -0
- package/templates/jsx/components/Banner.jsx +42 -0
- package/templates/jsx/components/Button.jsx +43 -0
- package/templates/jsx/components/Chip.jsx +28 -0
- package/templates/jsx/components/CodeBlock.jsx +42 -0
- package/templates/jsx/components/EmptyState.jsx +27 -0
- package/templates/jsx/components/Funnel.jsx +55 -0
- package/templates/jsx/components/Input.jsx +53 -0
- package/templates/jsx/components/KanbanColumn.jsx +51 -0
- package/templates/jsx/components/Kbd.jsx +11 -0
- package/templates/jsx/components/LeadCard.jsx +79 -0
- package/templates/jsx/components/Modal.jsx +57 -0
- package/templates/jsx/components/Panel.jsx +25 -0
- package/templates/jsx/components/Section.jsx +28 -0
- package/templates/jsx/components/Segmented.jsx +26 -0
- package/templates/jsx/components/Sidebar.jsx +49 -0
- package/templates/jsx/components/Spec.jsx +19 -0
- package/templates/jsx/components/StatCard.jsx +44 -0
- package/templates/jsx/components/TableActions.jsx +34 -0
- package/templates/jsx/components/Tag.jsx +21 -0
- package/templates/jsx/components/TagDot.jsx +26 -0
- package/templates/jsx/components/Toast.jsx +25 -0
- package/templates/jsx/components/Toggle.jsx +29 -0
- package/templates/jsx/components.css +740 -0
- package/templates/{icons.jsx → jsx/icons.jsx} +20 -9
- package/templates/jsx/index.js +31 -0
- package/templates/presets/zark/preset.json +26 -0
- package/templates/REFERENCE.md +0 -376
- package/templates/SHOWCASE.html +0 -254
- package/templates/brand.jsx +0 -89
- package/templates/components.jsx +0 -385
- package/templates/design-canvas.jsx +0 -789
- package/templates/foundations.jsx +0 -363
- package/templates/layouts.jsx +0 -232
- package/templates/patterns.jsx +0 -268
- package/templates/primitives.jsx +0 -306
- package/templates/tokens.css +0 -306
- package/templates/visual-references/icon-zark.png +0 -0
- package/templates/visual-references/logo-zark-principal.png +0 -0
- package/templates/visual-references/pasted-1777605750385-0.png +0 -0
- package/templates/visual-references/pasted-1777605766298-0.png +0 -0
- package/templates/visual-references/pasted-1777605775820-0.png +0 -0
- package/templates/visual-references/pasted-1777605789833-0.png +0 -0
- package/templates/visual-references/pasted-1777605802420-0.png +0 -0
- package/templates/visual-references/pasted-1777605812470-0.png +0 -0
- package/templates/visual-references/pasted-1777605817688-0.png +0 -0
- package/templates/visual-references/pasted-1777605828485-0.png +0 -0
- package/templates/visual-references/pasted-1777605837137-0.png +0 -0
- package/templates/visual-references/pasted-1777605849789-0.png +0 -0
- package/templates/visual-references/pasted-1777605864942-0.png +0 -0
- package/templates/visual-references/pasted-1777605877920-0.png +0 -0
- package/templates/visual-references/pasted-1777605897353-0.png +0 -0
- /package/templates/{assets/zark-logo.png → presets/zark/assets/logo-zark-laranja.png} +0 -0
- /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;
|