@dtcode/ui 0.0.0-stage → 0.2.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/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @dtcode/ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Componentes React e tokens do Design System dtcode, para projetos com React 19 e Tailwind CSS v4.
4
+ Documentação completa, com exemplos e props de cada componente, no site do Design System.
5
+
6
+ ## Instalação
7
+
8
+ ```bash
9
+ npm install @dtcode/ui
10
+ ```
11
+
12
+ O pacote é público no npm, sem token. Ele também é publicado no GitHub Packages da organização com o nome
13
+ `@appsdtcode/ui` (lá o escopo precisa ser o dono do repositório), para quem usa o registro do GitHub.
14
+
15
+ ## Estilos
16
+
17
+ No CSS global, depois do Tailwind:
18
+
19
+ ```css
20
+ @import "tailwindcss";
21
+ @import "@dtcode/ui/styles.css";
22
+ ```
23
+
24
+ O `styles.css` traz os tokens (tema claro e escuro, com `data-theme="dark"` no `<html>`), a base visual e
25
+ os ícones, e já avisa o Tailwind para gerar as classes usadas pelos componentes.
26
+
27
+ Carregue as fontes no `<head>`: Lexend, Public Sans, JetBrains Mono e Material Symbols Rounded.
28
+
29
+ ## Uso
30
+
31
+ ```tsx
32
+ import { Button, Field, Input } from "@dtcode/ui";
33
+
34
+ <Field label="E-mail" htmlFor="email">
35
+ <Input id="email" type="email" icon="mail" />
36
+ </Field>
37
+ <Button>Salvar</Button>
38
+ ```
39
+
40
+ ## Desenvolvimento
41
+
42
+ O código fica em `packages/ui/src` no repositório do Design System; o site de documentação usa esse
43
+ código direto. `npm run build:ui` na raiz gera o `dist/`. Para publicar, suba o `version` em
44
+ `packages/ui/package.json` e faça o merge na `main`: o workflow `publish-ui` publica a versão nova no npm e no GitHub Packages.
@@ -0,0 +1,8 @@
1
+ export type AlertTone = "info" | "success" | "warning" | "danger";
2
+ export declare function Alert({ tone, title, icon, className, children, }: {
3
+ tone?: AlertTone;
4
+ title?: React.ReactNode;
5
+ icon?: string;
6
+ className?: string;
7
+ children?: React.ReactNode;
8
+ }): import("react").JSX.Element;
package/dist/alert.js ADDED
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ import { Icon } from "./icon.js";
4
+ const tones = {
5
+ info: { cls: "bg-info-bg border-info-border text-info-fg", icon: "info" },
6
+ success: { cls: "bg-success-bg border-success-border text-success-fg", icon: "check_circle" },
7
+ warning: { cls: "bg-warning-bg border-warning-border text-warning-fg", icon: "warning" },
8
+ danger: { cls: "bg-danger-bg border-danger-border text-danger-fg", icon: "error" },
9
+ };
10
+ export function Alert({ tone = "info", title, icon, className, children, }) {
11
+ const t = tones[tone];
12
+ return (_jsxs("div", { role: tone === "danger" || tone === "warning" ? "alert" : "status", className: cn("flex items-start gap-3 rounded-md border px-4 py-3.5 text-sm", t.cls, className), children: [_jsx(Icon, { name: icon ?? t.icon, size: 20, className: "mt-px" }), _jsxs("div", { className: "grid gap-0.5", children: [title && _jsx("strong", { className: "font-display font-semibold", children: title }), children && _jsx("div", { children: children })] })] }));
13
+ }
@@ -0,0 +1,7 @@
1
+ export type BadgeTone = "neutral" | "brand" | "accent" | "success" | "warning" | "danger" | "info";
2
+ export declare function Badge({ tone, icon, className, children, }: {
3
+ tone?: BadgeTone;
4
+ icon?: string;
5
+ className?: string;
6
+ children: React.ReactNode;
7
+ }): import("react").JSX.Element;
package/dist/badge.js ADDED
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ import { Icon } from "./icon.js";
4
+ const tones = {
5
+ neutral: "bg-surface-sunken text-fg-2",
6
+ brand: "bg-tint text-tint-fg",
7
+ accent: "bg-magenta-100 text-magenta-800 dark:bg-magenta-500/15 dark:text-magenta-200",
8
+ success: "bg-success-bg text-success-fg",
9
+ warning: "bg-warning-bg text-warning-fg",
10
+ danger: "bg-danger-bg text-danger-fg",
11
+ info: "bg-info-bg text-info-fg",
12
+ };
13
+ export function Badge({ tone = "neutral", icon, className, children, }) {
14
+ return (_jsxs("span", { className: cn("inline-flex h-[22px] items-center gap-1 rounded-pill px-2.5 font-display text-[11px] font-semibold tracking-[.06em] uppercase", tones[tone], className), children: [icon && _jsx(Icon, { name: icon, size: 13 }), children] }));
15
+ }
@@ -0,0 +1,22 @@
1
+ export type ButtonVariant = "primary" | "secondary" | "accent" | "ghost" | "danger";
2
+ export type ButtonSize = "sm" | "md" | "lg";
3
+ export declare function buttonClasses({ variant, size, block, loading, className, }?: {
4
+ variant?: ButtonVariant;
5
+ size?: ButtonSize;
6
+ block?: boolean;
7
+ loading?: boolean;
8
+ className?: string;
9
+ }): string;
10
+ export type ButtonProps = React.ComponentProps<"button"> & {
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ /** Material Symbol exibido antes do texto. */
14
+ icon?: string;
15
+ /** Material Symbol exibido depois do texto. */
16
+ iconRight?: string;
17
+ loading?: boolean;
18
+ /** Texto anunciado por leitores de tela durante o carregamento. */
19
+ loadingLabel?: string;
20
+ block?: boolean;
21
+ };
22
+ export declare function Button({ variant, size, icon, iconRight, loading, loadingLabel, block, className, children, disabled, type, ...props }: ButtonProps): import("react").JSX.Element;
package/dist/button.js ADDED
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ import { Icon } from "./icon.js";
4
+ import { Loader } from "./loader.js";
5
+ const base = "group/btn inline-flex items-center justify-center gap-2 whitespace-nowrap border border-transparent font-semibold " +
6
+ "transition-[translate,scale,box-shadow,background-color,color,opacity] duration-150 ease-brand " +
7
+ "hover:-translate-y-px active:translate-y-0 active:scale-[.97] " +
8
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus";
9
+ const disabledCls = "disabled:pointer-events-none disabled:opacity-45 disabled:shadow-none";
10
+ // Carregando (como no login): mantém a cor do botão, sem esmaecer como desabilitado, e só bloqueia o clique.
11
+ const loadingCls = "pointer-events-none cursor-progress";
12
+ const variants = {
13
+ primary: "bg-action text-on-action shadow-2 hover:bg-action-hover hover:shadow-3",
14
+ secondary: "bg-surface text-fg border-border-strong shadow-1 hover:bg-surface-subtle",
15
+ // Gradiente da marca com texto roxo: branco sobre magenta não atinge 4,5:1.
16
+ accent: "bg-[image:var(--grad-marca)] text-roxo-900 shadow-[0_8px_20px_-6px_rgb(239_47_177/.55)] hover:shadow-[0_12px_24px_-6px_rgb(239_47_177/.6)]",
17
+ ghost: "bg-transparent text-fg hover:bg-surface-subtle",
18
+ danger: "bg-vermelho-600 text-white shadow-2 hover:bg-vermelho-800",
19
+ };
20
+ const sizes = {
21
+ sm: "h-8 px-3 text-[13px] rounded-md",
22
+ md: "h-10 px-[18px] text-sm rounded-md",
23
+ lg: "h-13 px-6 text-[17px] font-bold rounded-lg",
24
+ };
25
+ const iconSize = { sm: 16, md: 18, lg: 20 };
26
+ export function buttonClasses({ variant = "primary", size = "md", block, loading, className, } = {}) {
27
+ return cn(base, variants[variant], sizes[size], loading ? loadingCls : disabledCls, block && "w-full", className);
28
+ }
29
+ export function Button({ variant, size = "md", icon, iconRight, loading, loadingLabel, block, className, children, disabled, type = "button", ...props }) {
30
+ return (_jsxs("button", { type: type, className: buttonClasses({ variant, size, block, loading, className }), disabled: disabled || loading, "aria-busy": loading || undefined, ...props, children: [loading ? (
31
+ // No accent o fundo já é o gradiente da marca: os losangos ficam na cor do texto para aparecer.
32
+ _jsx(Loader, { size: "sm", label: loadingLabel, className: variant === "accent" ? "[&_i]:bg-current [&_i]:bg-none" : undefined })) : (icon && _jsx(Icon, { name: icon, size: iconSize[size] })), children, iconRight && !loading && (_jsx(Icon, { name: iconRight, size: iconSize[size], className: "transition-[translate] duration-200 ease-brand group-hover/btn:translate-x-0.5" }))] }));
33
+ }
package/dist/card.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function Card({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
2
+ /** Classes de cartão clicável (para usar em <Link>). */
3
+ export declare const interactiveCard: string;
package/dist/card.js ADDED
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ export function Card({ className, ...props }) {
4
+ return _jsx("div", { className: cn("rounded-lg border border-border bg-surface shadow-1", className), ...props });
5
+ }
6
+ /** Classes de cartão clicável (para usar em <Link>). */
7
+ export const interactiveCard = "group rounded-lg border border-border bg-surface shadow-1 transition-[translate,box-shadow,border-color] duration-200 ease-brand " +
8
+ "hover:-translate-y-0.5 hover:border-border-strong hover:shadow-3 active:translate-y-0";
package/dist/cn.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
package/dist/cn.js ADDED
@@ -0,0 +1,4 @@
1
+ import clsx from "clsx";
2
+ export function cn(...inputs) {
3
+ return clsx(inputs);
4
+ }
package/dist/icon.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ type IconProps = {
2
+ /** Nome do Material Symbol (ex.: "home", "search"). Sempre preenchido, conforme o MIV. */
3
+ name: string;
4
+ size?: number;
5
+ className?: string;
6
+ /** Rótulo acessível. Sem ele o ícone é decorativo. */
7
+ label?: string;
8
+ };
9
+ export declare function Icon({ name, size, className, label }: IconProps): import("react").JSX.Element;
10
+ export {};
package/dist/icon.js ADDED
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ export function Icon({ name, size = 20, className, label }) {
4
+ return (_jsx("span", { className: cn("icon", className), style: { fontSize: size }, "aria-hidden": label ? undefined : true, role: label ? "img" : undefined, "aria-label": label, children: name }));
5
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./alert.js";
2
+ export * from "./badge.js";
3
+ export * from "./button.js";
4
+ export * from "./card.js";
5
+ export * from "./icon.js";
6
+ export * from "./input.js";
7
+ export * from "./loader.js";
8
+ export * from "./segmented.js";
9
+ export * from "./switch.js";
10
+ export * from "./tabs.js";
11
+ export * from "./cn.js";
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export * from "./alert.js";
2
+ export * from "./badge.js";
3
+ export * from "./button.js";
4
+ export * from "./card.js";
5
+ export * from "./icon.js";
6
+ export * from "./input.js";
7
+ export * from "./loader.js";
8
+ export * from "./segmented.js";
9
+ export * from "./switch.js";
10
+ export * from "./tabs.js";
11
+ export * from "./cn.js";
@@ -0,0 +1,16 @@
1
+ export type InputProps = React.ComponentProps<"input"> & {
2
+ icon?: string;
3
+ invalid?: boolean;
4
+ };
5
+ export declare function Input({ icon, invalid, className, ...props }: InputProps): import("react").JSX.Element;
6
+ type FieldProps = {
7
+ label: React.ReactNode;
8
+ htmlFor: string;
9
+ hint?: React.ReactNode;
10
+ error?: React.ReactNode;
11
+ action?: React.ReactNode;
12
+ children: React.ReactNode;
13
+ };
14
+ /** Agrupa rótulo, controle, dica e erro com a associação de acessibilidade correta. */
15
+ export declare function Field({ label, htmlFor, hint, error, action, children }: FieldProps): import("react").JSX.Element;
16
+ export {};
package/dist/input.js ADDED
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ import { Icon } from "./icon.js";
4
+ export function Input({ icon, invalid, className, ...props }) {
5
+ const input = (_jsx("input", { "aria-invalid": invalid || undefined, className: cn("peer h-10 w-full rounded-md border bg-surface px-3 text-sm text-fg placeholder:text-muted", "border-border-strong transition-[border-color,box-shadow,background-color] duration-200 hover:border-neutro-400 dark:hover:border-white/30", "focus:border-magenta-500 focus:shadow-[0_0_0_3px_rgb(239_47_177/.18)] focus:outline-none", "disabled:cursor-not-allowed disabled:bg-surface-sunken disabled:opacity-70", invalid && "border-danger focus:border-danger focus:shadow-[0_0_0_3px_rgb(209_42_31/.18)]", icon && "pl-9", className), ...props }));
6
+ if (!icon)
7
+ return input;
8
+ return (_jsxs("div", { className: "relative", children: [input, _jsx(Icon, { name: icon, size: 18, className: "pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-muted transition-colors peer-focus:text-accent" })] }));
9
+ }
10
+ /** Agrupa rótulo, controle, dica e erro com a associação de acessibilidade correta. */
11
+ export function Field({ label, htmlFor, hint, error, action, children }) {
12
+ return (_jsxs("div", { className: "group/field grid gap-1.5", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("label", { htmlFor: htmlFor, className: "text-[13px] font-semibold text-fg transition-colors group-focus-within/field:text-accent", children: label }), action] }), children, error ? (_jsxs("p", { id: `${htmlFor}-error`, className: "flex animate-fade-in items-center gap-1 text-xs font-medium text-danger", children: [_jsx(Icon, { name: "error", size: 14 }), error] })) : (hint && (_jsx("p", { id: `${htmlFor}-hint`, className: "text-xs text-muted", children: hint })))] }));
13
+ }
@@ -0,0 +1,6 @@
1
+ /** Os três losangos do símbolo dtcode, pulsando em sequência. */
2
+ export declare function Loader({ size, className, label }: {
3
+ size?: "sm" | "md" | "lg";
4
+ className?: string;
5
+ label?: string;
6
+ }): import("react").JSX.Element;
package/dist/loader.js ADDED
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./cn.js";
3
+ /** Os três losangos do símbolo dtcode, pulsando em sequência. */
4
+ export function Loader({ size = "md", className, label = "Carregando" }) {
5
+ const s = { sm: 5, md: 9, lg: 14 }[size];
6
+ const gap = { sm: 2, md: 3, lg: 4 }[size];
7
+ return (_jsxs("span", { role: "status", "aria-label": label, className: cn("inline-grid rotate-45", className), style: { gridTemplateColumns: `repeat(2, ${s}px)`, gridTemplateRows: `repeat(2, ${s}px)`, gap }, children: [[
8
+ { area: "1 / 1", delay: "0s" },
9
+ { area: "1 / 2", delay: ".15s" },
10
+ { area: "2 / 2", delay: ".3s" },
11
+ ].map((d) => (_jsx("i", { className: "animate-[ds-pulse_1.2s_cubic-bezier(.2,.7,.2,1)_infinite] rounded-[28%] bg-[image:var(--grad-marca)]", style: { gridArea: d.area, animationDelay: d.delay } }, d.area))), _jsx("style", { children: `@keyframes ds-pulse{0%,100%{opacity:.25;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}` })] }));
12
+ }
@@ -0,0 +1,12 @@
1
+ type Option<T extends string> = {
2
+ value: T;
3
+ label: string;
4
+ };
5
+ /** Grupo de opções exclusivas (radiogroup) — usado nos controles do playground. */
6
+ export declare function Segmented<T extends string>({ value, onChange, options, label, }: {
7
+ value: T;
8
+ onChange: (v: T) => void;
9
+ options: Option<T>[];
10
+ label: string;
11
+ }): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from "./cn.js";
4
+ /** Grupo de opções exclusivas (radiogroup) — usado nos controles do playground. */
5
+ export function Segmented({ value, onChange, options, label, }) {
6
+ return (_jsx("div", { role: "radiogroup", "aria-label": label, className: "inline-flex flex-wrap gap-0.5 rounded-md border border-border bg-surface-sunken p-[3px]", children: options.map((o) => {
7
+ const on = o.value === value;
8
+ return (_jsx("button", { type: "button", role: "radio", "aria-checked": on, onClick: () => onChange(o.value), className: cn("rounded-[6px] px-2.5 py-1.5 text-xs font-semibold transition-[background-color,color,box-shadow,scale] duration-200 active:scale-95", on ? "bg-surface text-fg shadow-1" : "text-fg-2 hover:text-fg"), children: o.label }, o.value));
9
+ }) }));
10
+ }
@@ -0,0 +1,355 @@
1
+ /* @dtcode/ui: tokens (claro e escuro), base, movimento e ícones. Importe depois do Tailwind. */
2
+ @source "./";
3
+
4
+ /* Arquivo gerado por scripts/build-tokens.mjs. Não edite à mão: altere src/tokens/tokens.json. */
5
+
6
+ :root {
7
+ --roxo-50: #F4F0FB;
8
+ --roxo-100: #E9E1F7;
9
+ --roxo-200: #D2C2EF;
10
+ --roxo-300: #B096E2;
11
+ --roxo-400: #8A63D0;
12
+ --roxo-500: #6A3DBA;
13
+ --roxo-600: #5126A0;
14
+ --roxo-700: #3D1883;
15
+ --roxo-800: #2C0D69;
16
+ --roxo-900: #1F0052;
17
+ --roxo-950: #12002F;
18
+ --magenta-50: #FEF0F8;
19
+ --magenta-100: #FDDCF0;
20
+ --magenta-200: #FAB8E1;
21
+ --magenta-300: #F68AD0;
22
+ --magenta-400: #F25CBF;
23
+ --magenta-500: #EF2FB1;
24
+ --magenta-600: #CF1F96;
25
+ --magenta-700: #A8167A;
26
+ --magenta-800: #82115F;
27
+ --magenta-900: #5E0C45;
28
+ --coral-400: #FF9199;
29
+ --neutro-0: #FFFFFF;
30
+ --neutro-25: #FAFAFC;
31
+ --neutro-50: #F5F4F8;
32
+ --neutro-100: #EDECF2;
33
+ --neutro-200: #DEDDE5;
34
+ --neutro-300: #C4C2CF;
35
+ --neutro-400: #9C99AB;
36
+ --neutro-500: #75728A;
37
+ --neutro-600: #57546B;
38
+ --neutro-700: #3F3C52;
39
+ --neutro-800: #2A2738;
40
+ --neutro-900: #181624;
41
+ --verde-50: #E7F6EF;
42
+ --verde-200: #BFE6D3;
43
+ --verde-600: #12805C;
44
+ --verde-800: #0C5A40;
45
+ --ambar-50: #FFF4E0;
46
+ --ambar-200: #F6D9A6;
47
+ --ambar-600: #A35A00;
48
+ --ambar-800: #6E3D00;
49
+ --vermelho-50: #FDECEA;
50
+ --vermelho-200: #F7C4BF;
51
+ --vermelho-600: #D12A1F;
52
+ --vermelho-800: #8F1A12;
53
+ --azul-50: #EAF1FE;
54
+ --azul-200: #C9DAFB;
55
+ --azul-600: #2D63D8;
56
+ --azul-800: #1D3F8A;
57
+ --grad-marca: linear-gradient(90deg, var(--coral-400) 0%, var(--magenta-500) 100%);
58
+ --grad-hero: radial-gradient(120% 90% at 85% 10%, rgb(239 47 177 / .35) 0%, rgb(239 47 177 / 0) 55%), radial-gradient(90% 80% at 0% 100%, rgb(106 61 186 / .55) 0%, rgb(106 61 186 / 0) 60%), linear-gradient(160deg, var(--roxo-950) 0%, var(--roxo-900) 55%, var(--roxo-800) 100%);
59
+ --r-sm: 6px;
60
+ --r-md: 8px;
61
+ --r-lg: 16px;
62
+ --r-xl: 24px;
63
+ --r-pill: 999px;
64
+ }
65
+
66
+ :root {
67
+ color-scheme: light;
68
+ --bg: var(--neutro-25);
69
+ --surface: var(--neutro-0);
70
+ --surface-subtle: var(--neutro-50);
71
+ --surface-sunken: var(--neutro-100);
72
+ --border: var(--neutro-200);
73
+ --border-strong: var(--neutro-300);
74
+ --fg: var(--roxo-900);
75
+ --fg-2: var(--neutro-600);
76
+ --muted: var(--neutro-500);
77
+ --link: var(--magenta-700);
78
+ --action: var(--roxo-900);
79
+ --action-hover: var(--roxo-800);
80
+ --on-action: var(--neutro-0);
81
+ --accent: var(--magenta-600);
82
+ --sidebar: var(--roxo-900);
83
+ --sidebar-fg: rgb(255 255 255 / .72);
84
+ --sidebar-active: rgb(255 255 255 / .08);
85
+ --canvas-dot: rgb(31 0 82 / .12);
86
+ --focus: var(--magenta-500);
87
+ --tint: var(--roxo-50);
88
+ --tint-fg: var(--roxo-700);
89
+ --success-bg: var(--verde-50);
90
+ --success-border: var(--verde-200);
91
+ --success-fg: var(--verde-800);
92
+ --success: var(--verde-600);
93
+ --warning-bg: var(--ambar-50);
94
+ --warning-border: var(--ambar-200);
95
+ --warning-fg: var(--ambar-800);
96
+ --warning: var(--ambar-600);
97
+ --danger-bg: var(--vermelho-50);
98
+ --danger-border: var(--vermelho-200);
99
+ --danger-fg: var(--vermelho-800);
100
+ --danger: var(--vermelho-600);
101
+ --info-bg: var(--azul-50);
102
+ --info-border: var(--azul-200);
103
+ --info-fg: var(--azul-800);
104
+ --info: var(--azul-600);
105
+ --elev-1: 0 1px 2px rgb(31 0 82 / .06), 0 1px 3px rgb(31 0 82 / .08);
106
+ --elev-2: 0 4px 6px rgb(31 0 82 / .07), 0 1px 3px rgb(31 0 82 / .08);
107
+ --elev-3: 0 15px 35px rgb(31 0 82 / .10), 0 5px 15px rgb(0 0 0 / .06);
108
+ --elev-4: 0 30px 60px -12px rgb(31 0 82 / .25), 0 18px 36px -18px rgb(0 0 0 / .2);
109
+ }
110
+
111
+ [data-theme="dark"] {
112
+ color-scheme: dark;
113
+ --bg: #0B0019;
114
+ --surface: #140230;
115
+ --surface-subtle: #1A0739;
116
+ --surface-sunken: #0F0024;
117
+ --border: rgb(255 255 255 / .10);
118
+ --border-strong: rgb(255 255 255 / .18);
119
+ --fg: #F6F2FF;
120
+ --fg-2: #C9C0E0;
121
+ --muted: #A79DC2;
122
+ --link: var(--magenta-300);
123
+ --action: var(--roxo-50);
124
+ --action-hover: var(--neutro-0);
125
+ --on-action: var(--roxo-900);
126
+ --accent: var(--magenta-400);
127
+ --sidebar: #0F0024;
128
+ --sidebar-fg: rgb(255 255 255 / .72);
129
+ --sidebar-active: rgb(255 255 255 / .07);
130
+ --canvas-dot: rgb(255 255 255 / .08);
131
+ --focus: var(--magenta-400);
132
+ --tint: rgb(176 150 226 / .14);
133
+ --tint-fg: var(--roxo-200);
134
+ --success-bg: rgb(18 128 92 / .14);
135
+ --success-border: rgb(18 128 92 / .4);
136
+ --success-fg: #A6EBCB;
137
+ --success: #5FD3A2;
138
+ --warning-bg: rgb(163 90 0 / .16);
139
+ --warning-border: rgb(163 90 0 / .45);
140
+ --warning-fg: #FFD7A0;
141
+ --warning: #FFB547;
142
+ --danger-bg: rgb(209 42 31 / .16);
143
+ --danger-border: rgb(209 42 31 / .45);
144
+ --danger-fg: #FFB8B2;
145
+ --danger: #FF7A70;
146
+ --info-bg: rgb(45 99 216 / .14);
147
+ --info-border: rgb(45 99 216 / .35);
148
+ --info-fg: #B9CEFA;
149
+ --info: #7FA6F5;
150
+ --elev-1: none;
151
+ --elev-2: none;
152
+ --elev-3: 0 0 0 1px rgb(255 255 255 / .06);
153
+ --elev-4: 0 30px 60px -12px rgb(0 0 0 / .6);
154
+ }
155
+
156
+ @theme inline {
157
+ --color-roxo-50: var(--roxo-50);
158
+ --color-roxo-100: var(--roxo-100);
159
+ --color-roxo-200: var(--roxo-200);
160
+ --color-roxo-300: var(--roxo-300);
161
+ --color-roxo-400: var(--roxo-400);
162
+ --color-roxo-500: var(--roxo-500);
163
+ --color-roxo-600: var(--roxo-600);
164
+ --color-roxo-700: var(--roxo-700);
165
+ --color-roxo-800: var(--roxo-800);
166
+ --color-roxo-900: var(--roxo-900);
167
+ --color-roxo-950: var(--roxo-950);
168
+ --color-magenta-50: var(--magenta-50);
169
+ --color-magenta-100: var(--magenta-100);
170
+ --color-magenta-200: var(--magenta-200);
171
+ --color-magenta-300: var(--magenta-300);
172
+ --color-magenta-400: var(--magenta-400);
173
+ --color-magenta-500: var(--magenta-500);
174
+ --color-magenta-600: var(--magenta-600);
175
+ --color-magenta-700: var(--magenta-700);
176
+ --color-magenta-800: var(--magenta-800);
177
+ --color-magenta-900: var(--magenta-900);
178
+ --color-coral-400: var(--coral-400);
179
+ --color-neutro-0: var(--neutro-0);
180
+ --color-neutro-25: var(--neutro-25);
181
+ --color-neutro-50: var(--neutro-50);
182
+ --color-neutro-100: var(--neutro-100);
183
+ --color-neutro-200: var(--neutro-200);
184
+ --color-neutro-300: var(--neutro-300);
185
+ --color-neutro-400: var(--neutro-400);
186
+ --color-neutro-500: var(--neutro-500);
187
+ --color-neutro-600: var(--neutro-600);
188
+ --color-neutro-700: var(--neutro-700);
189
+ --color-neutro-800: var(--neutro-800);
190
+ --color-neutro-900: var(--neutro-900);
191
+ --color-verde-50: var(--verde-50);
192
+ --color-verde-200: var(--verde-200);
193
+ --color-verde-600: var(--verde-600);
194
+ --color-verde-800: var(--verde-800);
195
+ --color-ambar-50: var(--ambar-50);
196
+ --color-ambar-200: var(--ambar-200);
197
+ --color-ambar-600: var(--ambar-600);
198
+ --color-ambar-800: var(--ambar-800);
199
+ --color-vermelho-50: var(--vermelho-50);
200
+ --color-vermelho-200: var(--vermelho-200);
201
+ --color-vermelho-600: var(--vermelho-600);
202
+ --color-vermelho-800: var(--vermelho-800);
203
+ --color-azul-50: var(--azul-50);
204
+ --color-azul-200: var(--azul-200);
205
+ --color-azul-600: var(--azul-600);
206
+ --color-azul-800: var(--azul-800);
207
+ --color-bg: var(--bg);
208
+ --color-surface: var(--surface);
209
+ --color-surface-subtle: var(--surface-subtle);
210
+ --color-surface-sunken: var(--surface-sunken);
211
+ --color-border: var(--border);
212
+ --color-border-strong: var(--border-strong);
213
+ --color-fg: var(--fg);
214
+ --color-fg-2: var(--fg-2);
215
+ --color-muted: var(--muted);
216
+ --color-link: var(--link);
217
+ --color-action: var(--action);
218
+ --color-action-hover: var(--action-hover);
219
+ --color-on-action: var(--on-action);
220
+ --color-accent: var(--accent);
221
+ --color-sidebar: var(--sidebar);
222
+ --color-sidebar-fg: var(--sidebar-fg);
223
+ --color-sidebar-active: var(--sidebar-active);
224
+ --color-canvas-dot: var(--canvas-dot);
225
+ --color-focus: var(--focus);
226
+ --color-tint: var(--tint);
227
+ --color-tint-fg: var(--tint-fg);
228
+ --color-success-bg: var(--success-bg);
229
+ --color-success-border: var(--success-border);
230
+ --color-success-fg: var(--success-fg);
231
+ --color-success: var(--success);
232
+ --color-warning-bg: var(--warning-bg);
233
+ --color-warning-border: var(--warning-border);
234
+ --color-warning-fg: var(--warning-fg);
235
+ --color-warning: var(--warning);
236
+ --color-danger-bg: var(--danger-bg);
237
+ --color-danger-border: var(--danger-border);
238
+ --color-danger-fg: var(--danger-fg);
239
+ --color-danger: var(--danger);
240
+ --color-info-bg: var(--info-bg);
241
+ --color-info-border: var(--info-border);
242
+ --color-info-fg: var(--info-fg);
243
+ --color-info: var(--info);
244
+ --shadow-1: var(--elev-1);
245
+ --shadow-2: var(--elev-2);
246
+ --shadow-3: var(--elev-3);
247
+ --shadow-4: var(--elev-4);
248
+ --radius-sm: var(--r-sm);
249
+ --radius-md: var(--r-md);
250
+ --radius-lg: var(--r-lg);
251
+ --radius-xl: var(--r-xl);
252
+ --radius-pill: var(--r-pill);
253
+ }
254
+
255
+ /* Base do Design System dtcode: tema escuro, fontes, movimento, estilos de base e ícones.
256
+ Vai no dist/styles.css do pacote @dtcode/ui, no globals.css do site e no ds.css do kit para copiar (scripts/build-templates.mjs).
257
+ Só entra aqui o que os componentes do pacote precisam; estilos do app ficam em globals.css. */
258
+
259
+ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
260
+
261
+ @theme inline {
262
+ --font-display: var(--font-lexend, "Lexend"), system-ui, sans-serif;
263
+ --font-sans: var(--font-public-sans, "Public Sans"), system-ui, sans-serif;
264
+ --font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, monospace;
265
+ --ease-brand: cubic-bezier(0.2, 0.7, 0.2, 1);
266
+
267
+ /* Movimento — curtas e suaves; todas respeitam prefers-reduced-motion (ver fim do arquivo) */
268
+ --animate-fade-in: fade-in 0.2s ease-out backwards;
269
+ --animate-fade-up: fade-up 0.28s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
270
+ --animate-scale-in: scale-in 0.18s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
271
+ --animate-slide-in-left: slide-in-left 0.28s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
272
+ --animate-pop: pop 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
273
+ --animate-spin-in: spin-in 0.35s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
274
+ --animate-shake: shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97) backwards;
275
+ /* Usa a propriedade `translate` (não `transform`) para não anular o rotate-45 dos losangos */
276
+ --animate-float: float 6s ease-in-out infinite;
277
+
278
+ @keyframes fade-in {
279
+ from { opacity: 0; }
280
+ }
281
+ @keyframes fade-up {
282
+ from { opacity: 0; translate: 0 6px; }
283
+ }
284
+ @keyframes scale-in {
285
+ from { opacity: 0; scale: 0.96; }
286
+ }
287
+ @keyframes slide-in-left {
288
+ from { translate: -100% 0; }
289
+ }
290
+ @keyframes pop {
291
+ 0% { scale: 0.92; opacity: 0.6; }
292
+ 60% { scale: 1.03; opacity: 1; }
293
+ 100% { scale: 1; }
294
+ }
295
+ @keyframes spin-in {
296
+ from { rotate: -90deg; opacity: 0; scale: 0.6; }
297
+ }
298
+ @keyframes shake {
299
+ 20%, 60% { translate: -4px 0; }
300
+ 40%, 80% { translate: 4px 0; }
301
+ }
302
+ @keyframes float {
303
+ 0%, 100% { translate: 0 0; }
304
+ 50% { translate: 0 -10px; }
305
+ }
306
+ }
307
+
308
+ @layer base {
309
+ html {
310
+ -webkit-text-size-adjust: 100%;
311
+ }
312
+ body {
313
+ background: var(--bg);
314
+ color: var(--fg);
315
+ font-family: var(--font-sans);
316
+ -webkit-font-smoothing: antialiased;
317
+ }
318
+ h1, h2, h3, h4 {
319
+ font-family: var(--font-display);
320
+ letter-spacing: -0.01em;
321
+ }
322
+ :focus-visible {
323
+ outline: 2px solid var(--focus);
324
+ outline-offset: 2px;
325
+ }
326
+ ::selection {
327
+ background: var(--magenta-200);
328
+ color: var(--roxo-900);
329
+ }
330
+ }
331
+
332
+ @layer components {
333
+ /* Ícones preenchidos (regra do MIV): Material Symbols Rounded */
334
+ .icon {
335
+ font-family: "Material Symbols Rounded";
336
+ font-weight: normal;
337
+ font-style: normal;
338
+ line-height: 1;
339
+ display: inline-block;
340
+ white-space: nowrap;
341
+ direction: ltr;
342
+ font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24;
343
+ -webkit-font-smoothing: antialiased;
344
+ user-select: none;
345
+ flex: none;
346
+ }
347
+ }
348
+
349
+ @media (prefers-reduced-motion: reduce) {
350
+ *, *::before, *::after {
351
+ animation-duration: 0.01ms !important;
352
+ animation-iteration-count: 1 !important;
353
+ transition-duration: 0.01ms !important;
354
+ }
355
+ }
@@ -0,0 +1,2 @@
1
+ import * as RadixSwitch from "@radix-ui/react-switch";
2
+ export declare function Switch({ className, ...props }: React.ComponentProps<typeof RadixSwitch.Root>): import("react").JSX.Element;
package/dist/switch.js ADDED
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as RadixSwitch from "@radix-ui/react-switch";
4
+ import { cn } from "./cn.js";
5
+ export function Switch({ className, ...props }) {
6
+ return (_jsx(RadixSwitch.Root, { className: cn("group/switch relative h-[22px] w-[38px] flex-none cursor-pointer rounded-pill bg-neutro-300 transition-colors duration-200", "data-[state=checked]:bg-magenta-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white/20", className), ...props, children: _jsx(RadixSwitch.Thumb, { className: "block h-4 w-4 translate-x-[3px] group-active/switch:w-5 data-[state=checked]:group-active/switch:translate-x-[15px] rounded-full bg-white shadow-[0_1px_2px_rgb(0_0_0/.25)] transition-[translate,width] duration-200 ease-brand data-[state=checked]:translate-x-[19px]" }) }));
7
+ }
package/dist/tabs.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import * as RadixTabs from "@radix-ui/react-tabs";
2
+ export declare const Tabs: import("react").ForwardRefExoticComponent<RadixTabs.TabsProps & import("react").RefAttributes<HTMLDivElement>>;
3
+ export declare function TabsList({ className, ...props }: React.ComponentProps<typeof RadixTabs.List>): import("react").JSX.Element;
4
+ export declare function TabsTrigger({ icon, className, children, ...props }: React.ComponentProps<typeof RadixTabs.Trigger> & {
5
+ icon?: string;
6
+ }): import("react").JSX.Element;
7
+ export declare function TabsContent({ className, ...props }: React.ComponentProps<typeof RadixTabs.Content>): import("react").JSX.Element;
package/dist/tabs.js ADDED
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as RadixTabs from "@radix-ui/react-tabs";
4
+ import { cn } from "./cn.js";
5
+ import { Icon } from "./icon.js";
6
+ export const Tabs = RadixTabs.Root;
7
+ export function TabsList({ className, ...props }) {
8
+ return _jsx(RadixTabs.List, { className: cn("flex gap-1 overflow-x-auto border-b border-border", className), ...props });
9
+ }
10
+ export function TabsTrigger({ icon, className, children, ...props }) {
11
+ return (_jsxs(RadixTabs.Trigger, { className: cn("relative -mb-px flex items-center gap-1.5 px-3 py-2.5 text-[13px] font-semibold whitespace-nowrap text-fg-2", "transition-colors hover:text-fg data-[state=active]:text-fg", "after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:rounded-full after:bg-[image:var(--grad-marca)] after:opacity-0 after:scale-x-50 after:transition-[opacity,scale] after:duration-250 after:ease-brand data-[state=active]:after:opacity-100 data-[state=active]:after:scale-x-100", className), ...props, children: [icon && _jsx(Icon, { name: icon, size: 17 }), children] }));
12
+ }
13
+ export function TabsContent({ className, ...props }) {
14
+ return _jsx(RadixTabs.Content, { className: cn("data-[state=active]:animate-fade-in", className), ...props });
15
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@dtcode/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Componentes React e tokens do Design System dtcode (Tailwind CSS v4).",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/appsdtcode/Design-System.git",
9
+ "directory": "packages/ui"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "*.css"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./styles.css": "./dist/styles.css"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "node scripts/build.mjs",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "peerDependencies": {
30
+ "react": ">=19",
31
+ "react-dom": ">=19",
32
+ "tailwindcss": ">=4"
33
+ },
34
+ "dependencies": {
35
+ "@radix-ui/react-switch": "^1.4.1",
36
+ "@radix-ui/react-tabs": "^1.2.1",
37
+ "clsx": "^2.1.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^19",
41
+ "typescript": "^5"
42
+ }
43
+ }