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,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kbd — tecla com bg surface, border line-200, font-mono
|
|
5
|
+
* @example <Kbd>⌘</Kbd><Kbd>K</Kbd>
|
|
6
|
+
*/
|
|
7
|
+
export function Kbd({ children, className = '', ...rest }) {
|
|
8
|
+
return <span className={`kbd ${className}`.trim()} {...rest}>{children}</span>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default Kbd;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LeadCard — card compacto para Pipeline de Vendas
|
|
5
|
+
*
|
|
6
|
+
* @param name string — nome do lead
|
|
7
|
+
* @param company string — sublinha (TR PAULO, Save Car, etc)
|
|
8
|
+
* @param status ReactNode — ex: <TagDot kind="status" value="paused">PARADO</TagDot>
|
|
9
|
+
* @param hot boolean — mostra flame icon ember antes do nome
|
|
10
|
+
* @param channels ReactNode[] — ícones de canal (telefone, instagram, email)
|
|
11
|
+
* @param time string — "5d", "2h ago"
|
|
12
|
+
* @param followUp string — "Follow-up: 27/04/2026" (renderiza em ember)
|
|
13
|
+
* @param indicator string — "Indicado por X"
|
|
14
|
+
* @param paused boolean — adiciona border-left amber
|
|
15
|
+
* @param overdue boolean — adiciona border-left vermelho
|
|
16
|
+
*/
|
|
17
|
+
export function LeadCard({
|
|
18
|
+
name,
|
|
19
|
+
company,
|
|
20
|
+
status,
|
|
21
|
+
hot = false,
|
|
22
|
+
channels,
|
|
23
|
+
time,
|
|
24
|
+
followUp,
|
|
25
|
+
indicator,
|
|
26
|
+
paused = false,
|
|
27
|
+
overdue = false,
|
|
28
|
+
className = '',
|
|
29
|
+
...rest
|
|
30
|
+
}) {
|
|
31
|
+
const cls = [
|
|
32
|
+
'lead-card',
|
|
33
|
+
paused && 'is-paused',
|
|
34
|
+
overdue && 'is-overdue',
|
|
35
|
+
className,
|
|
36
|
+
].filter(Boolean).join(' ');
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={cls} {...rest}>
|
|
40
|
+
<div className="lead-card-head">
|
|
41
|
+
<span className="lead-card-name">
|
|
42
|
+
{hot && (
|
|
43
|
+
<svg className="flame" width="14" height="14" viewBox="0 0 18 18" fill="currentColor">
|
|
44
|
+
<path d="M9 2c0 0 4 3 4 7a4 4 0 11-8 0c0-2 1-3 1-3 0 1 1 2 2 2 0-2 1-4 1-6z"/>
|
|
45
|
+
</svg>
|
|
46
|
+
)}
|
|
47
|
+
{name}
|
|
48
|
+
</span>
|
|
49
|
+
{status}
|
|
50
|
+
</div>
|
|
51
|
+
{company && <div className="lead-card-sub">{company}</div>}
|
|
52
|
+
{(channels || time) && (
|
|
53
|
+
<div className="lead-card-meta">
|
|
54
|
+
{channels && <span className="icons">{channels}</span>}
|
|
55
|
+
{time && <span style={{ fontFamily: 'var(--font-mono)' }}>{time}</span>}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
{followUp && (
|
|
59
|
+
<div className="lead-card-followup">
|
|
60
|
+
<svg width="11" height="11" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
61
|
+
<rect x="3" y="4" width="12" height="11" rx="1.5"/>
|
|
62
|
+
<path d="M3 7h12M6 2v3M12 2v3"/>
|
|
63
|
+
</svg>
|
|
64
|
+
{followUp}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
{indicator && (
|
|
68
|
+
<div className="lead-card-foot">
|
|
69
|
+
<svg width="11" height="11" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
70
|
+
<path d="M3 9c2-3 5-4 6-4s4 1 6 4c-2 3-5 4-6 4s-4-1-6-4z"/>
|
|
71
|
+
</svg>
|
|
72
|
+
{indicator}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default LeadCard;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Modal — overlay + card max-width 460px, raio 2xl, shadow modal
|
|
6
|
+
*
|
|
7
|
+
* @param open boolean
|
|
8
|
+
* @param onClose function
|
|
9
|
+
* @param title string (Space Grotesk)
|
|
10
|
+
* @param description string opcional
|
|
11
|
+
* @param footer ReactNode (botões à direita)
|
|
12
|
+
* @param closeOnOverlay boolean — default true
|
|
13
|
+
*/
|
|
14
|
+
export function Modal({
|
|
15
|
+
open,
|
|
16
|
+
onClose,
|
|
17
|
+
title,
|
|
18
|
+
description,
|
|
19
|
+
footer,
|
|
20
|
+
closeOnOverlay = true,
|
|
21
|
+
children,
|
|
22
|
+
}) {
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!open) return;
|
|
25
|
+
const onKey = (e) => e.key === 'Escape' && onClose?.();
|
|
26
|
+
window.addEventListener('keydown', onKey);
|
|
27
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
28
|
+
}, [open, onClose]);
|
|
29
|
+
|
|
30
|
+
if (!open) return null;
|
|
31
|
+
|
|
32
|
+
const content = (
|
|
33
|
+
<div
|
|
34
|
+
className="modal-overlay"
|
|
35
|
+
onClick={(e) => closeOnOverlay && e.target === e.currentTarget && onClose?.()}
|
|
36
|
+
>
|
|
37
|
+
<div className="modal" role="dialog" aria-modal="true">
|
|
38
|
+
{(title || description) && (
|
|
39
|
+
<div className="modal-head">
|
|
40
|
+
{title && (
|
|
41
|
+
<div className="modal-title-row">
|
|
42
|
+
<h3 className="modal-title">{title}</h3>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
{description && <p className="modal-desc">{description}</p>}
|
|
46
|
+
</div>
|
|
47
|
+
)}
|
|
48
|
+
{children && <div className="modal-body">{children}</div>}
|
|
49
|
+
{footer && <div className="modal-foot">{footer}</div>}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return typeof document !== 'undefined' ? createPortal(content, document.body) : content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default Modal;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Panel — card padrão com surface bg, border line-200, raio xl, sombra sm
|
|
5
|
+
*
|
|
6
|
+
* @param title string ou ReactNode
|
|
7
|
+
* @param kicker string opcional (uppercase mono à direita)
|
|
8
|
+
* @param flush boolean — remove o padding interno (padding: 0)
|
|
9
|
+
*/
|
|
10
|
+
export function Panel({ title, kicker, flush = false, className = '', children }) {
|
|
11
|
+
const bodyCls = `panel-body ${flush ? 'flush' : ''}`.trim();
|
|
12
|
+
return (
|
|
13
|
+
<div className={`panel ${className}`.trim()}>
|
|
14
|
+
{(title || kicker) && (
|
|
15
|
+
<div className="panel-head">
|
|
16
|
+
{title && <div className="panel-title">{title}</div>}
|
|
17
|
+
{kicker && <div className="panel-kicker">{kicker}</div>}
|
|
18
|
+
</div>
|
|
19
|
+
)}
|
|
20
|
+
<div className={bodyCls}>{children}</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default Panel;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section — bloco de página com eyebrow + título display + descrição
|
|
5
|
+
*
|
|
6
|
+
* @param eyebrow uppercase mono small (ex: "01 — Foundations")
|
|
7
|
+
* @param title h2 em Space Grotesk
|
|
8
|
+
* @param description texto auxiliar à direita
|
|
9
|
+
* @param id anchor para nav
|
|
10
|
+
*/
|
|
11
|
+
export function Section({ eyebrow, title, description, id, children }) {
|
|
12
|
+
return (
|
|
13
|
+
<section id={id} className="section">
|
|
14
|
+
<div className="section-inner">
|
|
15
|
+
<div className="section-header">
|
|
16
|
+
<div>
|
|
17
|
+
{eyebrow && <div className="eyebrow">{eyebrow}</div>}
|
|
18
|
+
{title && <h2>{title}</h2>}
|
|
19
|
+
</div>
|
|
20
|
+
{description && <p className="section-desc">{description}</p>}
|
|
21
|
+
</div>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default Section;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Segmented — radio-style horizontal selector
|
|
5
|
+
* @param items [{ value, label, icon? }]
|
|
6
|
+
* @param value selected value
|
|
7
|
+
* @param onChange function(nextValue)
|
|
8
|
+
*/
|
|
9
|
+
export function Segmented({ items = [], value, onChange, className = '' }) {
|
|
10
|
+
return (
|
|
11
|
+
<div className={`segmented ${className}`.trim()}>
|
|
12
|
+
{items.map(it => (
|
|
13
|
+
<button
|
|
14
|
+
key={it.value}
|
|
15
|
+
type="button"
|
|
16
|
+
className={it.value === value ? 'active' : ''}
|
|
17
|
+
onClick={() => onChange?.(it.value)}
|
|
18
|
+
>
|
|
19
|
+
{it.icon}{it.label}
|
|
20
|
+
</button>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default Segmented;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sidebar — 240px, bg canvas, com slot para brand/logo no topo.
|
|
5
|
+
*
|
|
6
|
+
* @param logo ReactNode — passe <img src="..." className="zk-sidebar-logo"/>
|
|
7
|
+
* @param children qualquer conteúdo (use SidebarLink + SidebarSection)
|
|
8
|
+
* @param footer ReactNode opcional fixo no bottom
|
|
9
|
+
*/
|
|
10
|
+
export function Sidebar({ logo, footer, children }) {
|
|
11
|
+
return (
|
|
12
|
+
<aside className="zk-sidebar">
|
|
13
|
+
{logo && <div className="zk-sidebar-brand">{logo}</div>}
|
|
14
|
+
{children}
|
|
15
|
+
{footer && (
|
|
16
|
+
<div style={{ marginTop: 'auto', paddingTop: 12, borderTop: '1px solid var(--line-200)' }}>
|
|
17
|
+
{footer}
|
|
18
|
+
</div>
|
|
19
|
+
)}
|
|
20
|
+
</aside>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Section header — eyebrow uppercase mono pra agrupar links */
|
|
25
|
+
export function SidebarSection({ children }) {
|
|
26
|
+
return <div className="zk-sidebar-eyebrow">{children}</div>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SidebarLink — item de navegação
|
|
31
|
+
* @param active boolean
|
|
32
|
+
* @param icon ReactNode
|
|
33
|
+
* @param children label
|
|
34
|
+
* @param shortcut ReactNode opcional (ex: <Kbd>⌥D</Kbd>)
|
|
35
|
+
*/
|
|
36
|
+
export function SidebarLink({ active = false, icon, shortcut, onClick, href, children }) {
|
|
37
|
+
const cls = `zk-sidebar-link ${active ? 'active' : ''}`.trim();
|
|
38
|
+
const content = (
|
|
39
|
+
<>
|
|
40
|
+
{icon && <span style={{ display: 'inline-flex', color: active ? 'var(--brand-600)' : 'var(--ink-400)' }}>{icon}</span>}
|
|
41
|
+
<span style={{ flex: 1 }}>{children}</span>
|
|
42
|
+
{shortcut}
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
if (href) return <a href={href} className={cls} onClick={onClick}>{content}</a>;
|
|
46
|
+
return <button type="button" className={cls} onClick={onClick} style={{ background: 'inherit', border: 'none', textAlign: 'left', font: 'inherit' }}>{content}</button>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default Sidebar;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Spec — linha label/value separada por dashed line.
|
|
5
|
+
* Usar dentro de Panel para listar specs técnicas (heights, paddings, tokens).
|
|
6
|
+
*
|
|
7
|
+
* @param label string
|
|
8
|
+
* @param value string ou ReactNode (renderizado em mono ink-600)
|
|
9
|
+
*/
|
|
10
|
+
export function Spec({ label, value, className = '' }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={`spec ${className}`.trim()}>
|
|
13
|
+
<span className="l">{label}</span>
|
|
14
|
+
<span className="v">{value}</span>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default Spec;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StatCard — KPI unificado (substitui as 4 variantes que tinham no CRM)
|
|
5
|
+
*
|
|
6
|
+
* @param label uppercase mono label
|
|
7
|
+
* @param value número/valor (Inter, NUNCA Space Grotesk)
|
|
8
|
+
* @param sub texto auxiliar abaixo do valor
|
|
9
|
+
* @param icon ReactNode rendered no quadrado colorido
|
|
10
|
+
* @param iconColor 'green' | 'orange' | 'blue' | 'purple' | 'red' | 'ghost'
|
|
11
|
+
* @param progress 0–100 (renderiza barra de progresso)
|
|
12
|
+
* @param critical boolean — borda vermelha + bg danger-50
|
|
13
|
+
*/
|
|
14
|
+
export function StatCard({
|
|
15
|
+
label,
|
|
16
|
+
value,
|
|
17
|
+
sub,
|
|
18
|
+
icon,
|
|
19
|
+
iconColor = 'orange',
|
|
20
|
+
progress,
|
|
21
|
+
critical = false,
|
|
22
|
+
className = '',
|
|
23
|
+
...rest
|
|
24
|
+
}) {
|
|
25
|
+
const cls = ['stat', critical && 'is-critical', className].filter(Boolean).join(' ');
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className={cls} {...rest}>
|
|
29
|
+
<div className="stat-head">
|
|
30
|
+
<span className="stat-label">{label}</span>
|
|
31
|
+
{icon && <span className={`stat-icon ${iconColor}`}>{icon}</span>}
|
|
32
|
+
</div>
|
|
33
|
+
<div className="stat-value">{value}</div>
|
|
34
|
+
{sub && <div className="stat-sub">{sub}</div>}
|
|
35
|
+
{typeof progress === 'number' && (
|
|
36
|
+
<div className="stat-progress">
|
|
37
|
+
<span style={{ width: `${Math.max(0, Math.min(100, progress))}%` }}/>
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default StatCard;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TableActions — linha de ações em data-table (check / edit / delete)
|
|
5
|
+
* Substitui ícones soltos coloridos por botões com border + hover state.
|
|
6
|
+
*
|
|
7
|
+
* @param onConfirm function (botão verde de check, ex: confirmar pagamento)
|
|
8
|
+
* @param onEdit function (lápis cinza)
|
|
9
|
+
* @param onDelete function (lixeira vermelha — sempre pedir confirmação no handler)
|
|
10
|
+
* @param showConfirm boolean — esconde o check (default true)
|
|
11
|
+
*/
|
|
12
|
+
export function TableActions({ onConfirm, onEdit, onDelete, showConfirm = true, className = '' }) {
|
|
13
|
+
return (
|
|
14
|
+
<span className={`t-actions ${className}`.trim()}>
|
|
15
|
+
{showConfirm && onConfirm && (
|
|
16
|
+
<button className="t-act t-confirm" onClick={onConfirm} title="Confirmar">
|
|
17
|
+
<svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 9l4 4 6-8"/></svg>
|
|
18
|
+
</button>
|
|
19
|
+
)}
|
|
20
|
+
{onEdit && (
|
|
21
|
+
<button className="t-act t-edit" onClick={onEdit} title="Editar">
|
|
22
|
+
<svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 3l3 3-9 9H3v-3l9-9z"/></svg>
|
|
23
|
+
</button>
|
|
24
|
+
)}
|
|
25
|
+
{onDelete && (
|
|
26
|
+
<button className="t-act t-delete" onClick={onDelete} title="Excluir">
|
|
27
|
+
<svg width="12" height="12" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M3 5h12M7 5V3h4v2M5 5v10a1 1 0 001 1h6a1 1 0 001-1V5"/></svg>
|
|
28
|
+
</button>
|
|
29
|
+
)}
|
|
30
|
+
</span>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default TableActions;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tag — label sem dot indicator
|
|
5
|
+
* @param tone default · ember · mono · success · warning · danger · info
|
|
6
|
+
* @param size xs · sm · md
|
|
7
|
+
* @param square boolean — raio --r-xs (mais sharp ainda)
|
|
8
|
+
*/
|
|
9
|
+
export function Tag({ tone = 'default', size = 'sm', square = false, className = '', children, ...rest }) {
|
|
10
|
+
const cls = [
|
|
11
|
+
'tag',
|
|
12
|
+
`tag-${size}`,
|
|
13
|
+
tone !== 'default' && `tag-${tone}`,
|
|
14
|
+
square && 'tag-square',
|
|
15
|
+
className,
|
|
16
|
+
].filter(Boolean).join(' ');
|
|
17
|
+
|
|
18
|
+
return <span className={cls} {...rest}>{children}</span>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Tag;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TagDot — tag com dot indicator à esquerda. Usado pra status, prioridade,
|
|
5
|
+
* espaço/cliente. SEMPRE usar este componente em vez do Tag puro quando
|
|
6
|
+
* representar um estado semântico (não decorativo).
|
|
7
|
+
*
|
|
8
|
+
* @param kind 'status' | 'priority' | 'space'
|
|
9
|
+
* @param value status: 'todo' | 'progress' | 'review' | 'done' | 'canceled' | 'overdue' | 'paused' | 'followup'
|
|
10
|
+
* priority: 'urgent' | 'high' | 'med' | 'low'
|
|
11
|
+
* space: 'zark' | 'allsec' | 'vipcar' | 'limppe' | 'gerais' | 'mixshop' | 'vipseg'
|
|
12
|
+
*
|
|
13
|
+
* Ordem recomendada num row de tarefa: space → status → priority
|
|
14
|
+
*/
|
|
15
|
+
export function TagDot({ kind, value, className = '', children, ...rest }) {
|
|
16
|
+
const prefixMap = { status: 's-', priority: 'p-', space: 'sp ' };
|
|
17
|
+
const prefix = prefixMap[kind] || '';
|
|
18
|
+
const variantClass = kind === 'space'
|
|
19
|
+
? `sp sp-${value}`
|
|
20
|
+
: `${prefix}${value}`;
|
|
21
|
+
|
|
22
|
+
const cls = ['tag-dot', variantClass, className].filter(Boolean).join(' ');
|
|
23
|
+
return <span className={cls} {...rest}>{children}</span>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default TagDot;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Toast — pill (na semântica) com bg ink-700 ou success-500, raio --r-md
|
|
5
|
+
* Usar em micro-confirmações (copied, saved, welcome).
|
|
6
|
+
*
|
|
7
|
+
* @param tone 'default' | 'success' | 'danger'
|
|
8
|
+
* @param icon ReactNode (default: nenhum) — ex: <img src="zark-icon.png"/>
|
|
9
|
+
*/
|
|
10
|
+
export function Toast({ tone = 'default', icon, className = '', children, ...rest }) {
|
|
11
|
+
const cls = [
|
|
12
|
+
'toast',
|
|
13
|
+
tone === 'success' && 'toast-success',
|
|
14
|
+
tone === 'danger' && 'toast-danger',
|
|
15
|
+
className,
|
|
16
|
+
].filter(Boolean).join(' ');
|
|
17
|
+
return (
|
|
18
|
+
<div className={cls} {...rest}>
|
|
19
|
+
{children}
|
|
20
|
+
{icon}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default Toast;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Toggle — square switch (no pills)
|
|
5
|
+
* @param on boolean — controlled state
|
|
6
|
+
* @param onChange function(nextValue: boolean)
|
|
7
|
+
* @param size 'sm' | 'md'
|
|
8
|
+
*/
|
|
9
|
+
export function Toggle({ on = false, onChange, size = 'md', className = '', ...rest }) {
|
|
10
|
+
const cls = [
|
|
11
|
+
'toggle',
|
|
12
|
+
size === 'sm' && 'toggle-sm',
|
|
13
|
+
on && 'on',
|
|
14
|
+
className,
|
|
15
|
+
].filter(Boolean).join(' ');
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
className={cls}
|
|
21
|
+
onClick={() => onChange?.(!on)}
|
|
22
|
+
{...rest}
|
|
23
|
+
>
|
|
24
|
+
<span className="knob"/>
|
|
25
|
+
</button>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default Toggle;
|