@fldev-solution/ui 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fldev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @fldev-solution/ui
2
+
3
+ Primitivos e hooks do kernel de UI da fldev. Blocos compostos (login,
4
+ DataTable, LineChart…) não vêm por aqui: vêm pelo registry do shadcn, em
5
+ <https://ui.fldev.com.br/r/index.json>. O que todo produto usa igual vai por
6
+ npm; o que todo produto ajusta vai por registry.
7
+
8
+ ```bash
9
+ npm i @fldev-solution/ui
10
+ ```
11
+
12
+ ```tsx
13
+ import { Button, Alert, Field, Input, useModo } from "@fldev-solution/ui";
14
+ ```
15
+
16
+ ## CSS: a ordem importa
17
+
18
+ Os componentes não carregam cor própria: leem tokens semânticos que a marca
19
+ define. Sem o `tokens.css` de uma marca importado **antes**, nada tem cor.
20
+
21
+ ```css
22
+ @import "tailwindcss";
23
+ @import "@fontsource-variable/inter"; /* a fonte que a marca declara */
24
+ @import "./design-system/tokens/tokens.css"; /* valores — da marca */
25
+ @import "@fldev-solution/ui/styles.css"; /* nomes de utility — do kernel */
26
+ @source "../node_modules/@fldev-solution/ui/dist";
27
+ ```
28
+
29
+ O `@source` é obrigatório: o Tailwind v4 não varre `node_modules`, e sem ele
30
+ as classes usadas dentro dos componentes não entram no CSS final. Ajuste o
31
+ caminho relativo ao arquivo CSS.
32
+
33
+ O tema de uma marca também vem pelo registry:
34
+
35
+ ```bash
36
+ npx shadcn add https://ui.fldev.com.br/r/brand-demo-teal.json
37
+ ```
38
+
39
+ ## Modo e marca
40
+
41
+ `useModo` alterna claro/escuro. `useBrand` também troca a marca na raiz — use
42
+ só em white-label, e nunca em página que mostra duas marcas ao mesmo tempo:
43
+ escrever `data-brand` na raiz faz o documento inteiro casar com o seletor de
44
+ todas as marcas.
45
+
46
+ ## Requisitos
47
+
48
+ React 18+ e Tailwind CSS v4.
@@ -0,0 +1,258 @@
1
+ import { ClassValue } from 'clsx';
2
+ import * as _tanstack_react_table from '@tanstack/react-table';
3
+ import { RowData } from '@tanstack/react-table';
4
+ import * as react from 'react';
5
+ import { ButtonHTMLAttributes, HTMLAttributes, ReactNode, InputHTMLAttributes, ReactElement, TdHTMLAttributes, ThHTMLAttributes } from 'react';
6
+ import * as class_variance_authority_types from 'class-variance-authority/types';
7
+ import { VariantProps } from 'class-variance-authority';
8
+
9
+ declare function cn(...inputs: ClassValue[]): string;
10
+
11
+ /**
12
+ * Conjunto de features do DataTable do kernel: ordenação, filtro de coluna,
13
+ * filtro global e paginação — nada além. Em v9 a feature precisa ser
14
+ * registrada para o estado dela existir; registrar o que não se usa é peso
15
+ * morto no bundle.
16
+ *
17
+ * Slots de row model vêm DEPOIS da feature que os requer.
18
+ */
19
+ declare const dataTableFeatures: {
20
+ rowSortingFeature: _tanstack_react_table.TableFeature;
21
+ sortedRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
22
+ columnFilteringFeature: _tanstack_react_table.TableFeature;
23
+ globalFilteringFeature: _tanstack_react_table.TableFeature;
24
+ filteredRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
25
+ rowPaginationFeature: _tanstack_react_table.TableFeature;
26
+ paginatedRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
27
+ };
28
+ type DataTableFeatures = typeof dataTableFeatures;
29
+ /**
30
+ * Column helper já amarrado às features do kernel. Sem isso, cada consumidor
31
+ * escreve `createColumnHelper<typeof dataTableFeatures, Linha>()` à mão e
32
+ * erra o primeiro parâmetro — que é justamente o que o v9 usa para saber
33
+ * quais APIs de coluna existem.
34
+ */
35
+ declare function criarColumnHelper<TLinha extends RowData>(): _tanstack_react_table.ColumnHelper<{
36
+ rowSortingFeature: _tanstack_react_table.TableFeature;
37
+ sortedRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
38
+ columnFilteringFeature: _tanstack_react_table.TableFeature;
39
+ globalFilteringFeature: _tanstack_react_table.TableFeature;
40
+ filteredRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
41
+ rowPaginationFeature: _tanstack_react_table.TableFeature;
42
+ paginatedRowModel: (table: _tanstack_react_table.Table<any, any>) => () => _tanstack_react_table.RowModel<any, any>;
43
+ }, TLinha>;
44
+
45
+ type Modo = "light" | "dark";
46
+ /**
47
+ * Alterna claro/escuro no elemento raiz. É o que quase todo produto precisa:
48
+ * a marca é fixa, só o modo muda.
49
+ */
50
+ declare function useModo(inicial?: Modo): {
51
+ modo: Modo;
52
+ setModo: react.Dispatch<react.SetStateAction<Modo>>;
53
+ alternarModo: () => void;
54
+ };
55
+ /**
56
+ * Para white-label: aplica marca E modo no elemento raiz.
57
+ *
58
+ * Cuidado: escrever `data-brand` na raiz faz o documento inteiro casar com
59
+ * `[data-brand="x"]`. Se a página mostra mais de uma marca ao mesmo tempo,
60
+ * use `useModo` e ponha `data-brand` no contêiner de cada marca — senão
61
+ * seletores escopados passam a casar tudo.
62
+ */
63
+ declare function useBrand(marcaInicial: string, modoInicial?: Modo): {
64
+ marca: string;
65
+ setMarca: react.Dispatch<react.SetStateAction<string>>;
66
+ modo: Modo;
67
+ setModo: react.Dispatch<react.SetStateAction<Modo>>;
68
+ alternarModo: () => void;
69
+ };
70
+
71
+ declare const botao: (props?: ({
72
+ variante?: "primary" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
73
+ tamanho?: "sm" | "md" | "lg" | "icon" | null | undefined;
74
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
75
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof botao> {
76
+ carregando?: boolean;
77
+ }
78
+ declare function Button({ className, variante, tamanho, carregando, disabled, children, ...props }: ButtonProps): react.JSX.Element;
79
+
80
+ /**
81
+ * As quatro famílias são estruturalmente idênticas — só o token muda. Isso é
82
+ * de propósito: alerta que difere em layout entre níveis ensina o usuário a
83
+ * ler layout em vez de cor, e aí o nível deixa de comunicar.
84
+ */
85
+ declare const alerta: (props?: ({
86
+ nivel?: "success" | "info" | "warning" | "danger" | null | undefined;
87
+ variante?: "surface" | "solid" | null | undefined;
88
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
89
+ interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title">, VariantProps<typeof alerta> {
90
+ titulo?: ReactNode;
91
+ icone?: ReactNode;
92
+ }
93
+ declare function Alert({ className, nivel, variante, titulo, icone, children, ...props }: AlertProps): react.JSX.Element;
94
+
95
+ interface DialogProps {
96
+ aberto: boolean;
97
+ onFechar: () => void;
98
+ titulo: ReactNode;
99
+ descricao?: ReactNode;
100
+ children?: ReactNode;
101
+ rodape?: ReactNode;
102
+ tamanho?: "sm" | "md" | "lg" | "full";
103
+ /** Impede fechar por Esc ou clique no backdrop. Use só em fluxo destrutivo. */
104
+ persistente?: boolean;
105
+ className?: string;
106
+ }
107
+ /**
108
+ * Modal sobre o <dialog> nativo — sem dependência de biblioteca.
109
+ *
110
+ * O elemento nativo já entrega, correto e de graça: armadilha de foco, o
111
+ * resto da página inerte, Esc para fechar, camada de topo acima de qualquer
112
+ * z-index, e ::backdrop. Reimplementar isso em React é como a maioria das
113
+ * libs de modal nasce com bug de acessibilidade.
114
+ *
115
+ * O que ele NÃO entrega e está resolvido aqui: travar o scroll do body,
116
+ * fechar por clique no backdrop, e o modo persistente.
117
+ */
118
+ declare function Dialog({ aberto, onFechar, titulo, descricao, children, rodape, tamanho, persistente, className, }: DialogProps): react.JSX.Element;
119
+
120
+ declare const selo: (props?: ({
121
+ nivel?: "brand" | "success" | "info" | "warning" | "danger" | "neutral" | null | undefined;
122
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
123
+ interface BadgeProps extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof selo> {
124
+ }
125
+ declare function Badge({ className, nivel, ...props }: BadgeProps): react.JSX.Element;
126
+
127
+ interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
128
+ label: ReactNode;
129
+ ajuda?: ReactNode;
130
+ }
131
+ /**
132
+ * Checkbox nativo com aparência própria via `appearance-none`, não um `<div>`
133
+ * fingindo ser checkbox. Mantém de graça: teclado, formulário, leitores de
134
+ * tela, autofill e estado indeterminado.
135
+ */
136
+ declare function Checkbox({ label, ajuda, className, id: idExterno, ...props }: CheckboxProps): react.JSX.Element;
137
+
138
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
139
+ invalido?: boolean;
140
+ }
141
+ declare function Input({ className, invalido, ...props }: InputProps): react.JSX.Element;
142
+
143
+ interface FieldProps {
144
+ label: ReactNode;
145
+ /** Texto de apoio. Some quando há erro — dois textos competindo confunde. */
146
+ ajuda?: ReactNode;
147
+ erro?: ReactNode;
148
+ obrigatorio?: boolean;
149
+ className?: string;
150
+ /** Recebe id, aria-describedby e aria-invalid já ligados. */
151
+ children: (props: {
152
+ id: string;
153
+ "aria-describedby": string | undefined;
154
+ "aria-invalid": boolean | undefined;
155
+ }) => ReactElement;
156
+ }
157
+ /**
158
+ * Amarra label, ajuda e erro ao controle com os ids corretos. Existe porque
159
+ * `aria-describedby` escrito à mão em cada formulário erra — e erra silencioso.
160
+ */
161
+ declare function Field({ label, ajuda, erro, obrigatorio, className, children }: FieldProps): react.JSX.Element;
162
+
163
+ interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
164
+ invalido?: boolean;
165
+ /** "new-password" em cadastro e redefinição; "current-password" no login. */
166
+ autoComplete?: "current-password" | "new-password";
167
+ }
168
+ /**
169
+ * Campo de senha com alternância de visibilidade.
170
+ *
171
+ * O `autoComplete` é obrigatório e tipado: gerenciador de senha que não sabe
172
+ * distinguir login de cadastro salva a senha errada, e isso aparece como
173
+ * "o site quebrou meu gerenciador", nunca como bug de UI.
174
+ */
175
+ declare function PasswordInput({ className, invalido, autoComplete, ...props }: PasswordInputProps): react.JSX.Element;
176
+
177
+ declare function Table({ className, ...props }: HTMLAttributes<HTMLTableElement>): react.JSX.Element;
178
+ declare function THead({ className, ...props }: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
179
+ declare function TBody({ className, ...props }: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
180
+ declare function TR({ className, selecionada, ...props }: HTMLAttributes<HTMLTableRowElement> & {
181
+ selecionada?: boolean;
182
+ }): react.JSX.Element;
183
+ declare function TH({ className, numerica, ...props }: ThHTMLAttributes<HTMLTableCellElement> & {
184
+ numerica?: boolean;
185
+ }): react.JSX.Element;
186
+ declare function TD({ className, numerica, ...props }: TdHTMLAttributes<HTMLTableCellElement> & {
187
+ numerica?: boolean;
188
+ }): react.JSX.Element;
189
+
190
+ interface EmptyStateProps {
191
+ titulo: ReactNode;
192
+ descricao?: ReactNode;
193
+ acao?: ReactNode;
194
+ icone?: ReactNode;
195
+ className?: string;
196
+ }
197
+ /**
198
+ * Estado vazio pede três coisas: dizer o que falta, por que está vazio, e o
199
+ * próximo passo. "Nenhum resultado" sozinho é um beco sem saída.
200
+ */
201
+ declare function EmptyState({ titulo, descricao, acao, icone, className }: EmptyStateProps): react.JSX.Element;
202
+
203
+ interface PaginationProps {
204
+ pagina: number;
205
+ totalPaginas: number;
206
+ totalLinhas: number;
207
+ podeVoltar: boolean;
208
+ podeAvancar: boolean;
209
+ onVoltar: () => void;
210
+ onAvancar: () => void;
211
+ className?: string;
212
+ }
213
+ declare function Pagination({ pagina, totalPaginas, totalLinhas, podeVoltar, podeAvancar, onVoltar, onAvancar, className, }: PaginationProps): react.JSX.Element;
214
+
215
+ interface SparklineProps {
216
+ valores: number[];
217
+ /** Token de cor da série. Padrão: a cor primária da marca. */
218
+ cor?: string;
219
+ largura?: number;
220
+ altura?: number;
221
+ className?: string;
222
+ rotulo: string;
223
+ }
224
+ /**
225
+ * Linha de 2px com marcador de fim. Sem eixo, sem grade — sparkline é forma,
226
+ * não gráfico: mostra direção, não valor. O valor fica na métrica ao lado.
227
+ */
228
+ declare function Sparkline({ valores, cor, largura, altura, className, rotulo, }: SparklineProps): react.JSX.Element | null;
229
+
230
+ declare function Spinner({ className }: {
231
+ className?: string;
232
+ }): react.JSX.Element;
233
+
234
+ type DirecaoDelta = "subiu" | "caiu" | "estavel";
235
+ type SentidoDelta = "bom" | "ruim" | "neutro";
236
+ interface StatTileProps {
237
+ rotulo: ReactNode;
238
+ valor: ReactNode;
239
+ unidade?: ReactNode;
240
+ delta?: {
241
+ texto: string;
242
+ direcao: DirecaoDelta;
243
+ sentido: SentidoDelta;
244
+ };
245
+ apoio?: ReactNode;
246
+ grafico?: ReactNode;
247
+ className?: string;
248
+ }
249
+ /**
250
+ * Métrica sem gráfico. Quando a história é UM número, gráfico é ruído — esta
251
+ * é a forma certa, não um gráfico de uma barra.
252
+ *
253
+ * O número usa algarismos proporcionais de propósito: tabular-nums em número
254
+ * grande e solto deixa "121" frouxo. Tabular só onde há alinhamento vertical.
255
+ */
256
+ declare function StatTile({ rotulo, valor, unidade, delta, apoio, grafico, className }: StatTileProps): react.JSX.Element;
257
+
258
+ export { Alert, type AlertProps, Badge, type BadgeProps, Button, type ButtonProps, Checkbox, type CheckboxProps, type DataTableFeatures, Dialog, type DialogProps, type DirecaoDelta, EmptyState, type EmptyStateProps, Field, type FieldProps, Input, type InputProps, type Modo, Pagination, type PaginationProps, PasswordInput, type PasswordInputProps, type SentidoDelta, Sparkline, type SparklineProps, Spinner, StatTile, type StatTileProps, TBody, TD, TH, THead, TR, Table, alerta as alertVariants, selo as badgeVariants, botao as buttonVariants, cn, criarColumnHelper, dataTableFeatures, useBrand, useModo };
package/dist/index.js ADDED
@@ -0,0 +1,523 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { tableFeatures, rowPaginationFeature, globalFilteringFeature, columnFilteringFeature, rowSortingFeature, createPaginatedRowModel, createFilteredRowModel, createSortedRowModel, createColumnHelper } from '@tanstack/react-table';
4
+ import { useState, useEffect, useCallback, useRef, useId } from 'react';
5
+ import { cva } from 'class-variance-authority';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ // src/lib/cn.ts
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ var dataTableFeatures = tableFeatures({
13
+ rowSortingFeature,
14
+ sortedRowModel: createSortedRowModel(),
15
+ columnFilteringFeature,
16
+ globalFilteringFeature,
17
+ filteredRowModel: createFilteredRowModel(),
18
+ rowPaginationFeature,
19
+ paginatedRowModel: createPaginatedRowModel()
20
+ });
21
+ function criarColumnHelper() {
22
+ return createColumnHelper();
23
+ }
24
+ function useModo(inicial = "light") {
25
+ const [modo, setModo] = useState(inicial);
26
+ useEffect(() => {
27
+ document.documentElement.classList.toggle("dark", modo === "dark");
28
+ }, [modo]);
29
+ const alternarModo = useCallback(
30
+ () => setModo((m) => m === "dark" ? "light" : "dark"),
31
+ []
32
+ );
33
+ return { modo, setModo, alternarModo };
34
+ }
35
+ function useBrand(marcaInicial, modoInicial = "light") {
36
+ const [marca, setMarca] = useState(marcaInicial);
37
+ const { modo, setModo, alternarModo } = useModo(modoInicial);
38
+ useEffect(() => {
39
+ document.documentElement.dataset.brand = marca;
40
+ }, [marca]);
41
+ return { marca, setMarca, modo, setModo, alternarModo };
42
+ }
43
+ function Spinner({ className }) {
44
+ return /* @__PURE__ */ jsx(
45
+ "span",
46
+ {
47
+ role: "status",
48
+ "aria-label": "Carregando",
49
+ className: cn(
50
+ "inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent motion-reduce:animate-none",
51
+ className
52
+ )
53
+ }
54
+ );
55
+ }
56
+ var botao = cva(
57
+ // O anel de foco usa ring-ring/ring-offset-background: foco visível é
58
+ // requisito WCAG, não enfeite — por isso não é removível por variante.
59
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
60
+ {
61
+ variants: {
62
+ variante: {
63
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
64
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
65
+ outline: "border border-input bg-background text-foreground hover:bg-muted",
66
+ ghost: "text-foreground hover:bg-muted",
67
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
68
+ },
69
+ tamanho: {
70
+ sm: "h-8 rounded-md px-3 text-[0.8125rem]",
71
+ md: "h-9 rounded-md px-4 text-sm",
72
+ lg: "h-10 rounded-lg px-6 text-sm",
73
+ icon: "size-9 rounded-md"
74
+ }
75
+ },
76
+ defaultVariants: { variante: "primary", tamanho: "md" }
77
+ }
78
+ );
79
+ function Button({
80
+ className,
81
+ variante,
82
+ tamanho,
83
+ carregando = false,
84
+ disabled,
85
+ children,
86
+ ...props
87
+ }) {
88
+ return /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ className: cn(botao({ variante, tamanho }), className),
92
+ disabled: disabled ?? carregando,
93
+ "aria-busy": carregando || void 0,
94
+ ...props,
95
+ children: [
96
+ carregando ? /* @__PURE__ */ jsx(Spinner, {}) : null,
97
+ children
98
+ ]
99
+ }
100
+ );
101
+ }
102
+ var alerta = cva(
103
+ "relative flex w-full gap-3 rounded-lg border px-4 py-3 text-sm [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0",
104
+ {
105
+ variants: {
106
+ nivel: {
107
+ success: "",
108
+ info: "",
109
+ warning: "",
110
+ danger: ""
111
+ },
112
+ variante: { surface: "", solid: "" }
113
+ },
114
+ compoundVariants: [
115
+ { nivel: "success", variante: "surface", class: "bg-success border-success-border text-success-foreground" },
116
+ { nivel: "info", variante: "surface", class: "bg-info border-info-border text-info-foreground" },
117
+ { nivel: "warning", variante: "surface", class: "bg-warning border-warning-border text-warning-foreground" },
118
+ { nivel: "danger", variante: "surface", class: "bg-danger border-danger-border text-danger-foreground" },
119
+ { nivel: "success", variante: "solid", class: "bg-success-solid border-transparent text-success-solid-foreground" },
120
+ { nivel: "info", variante: "solid", class: "bg-info-solid border-transparent text-info-solid-foreground" },
121
+ { nivel: "warning", variante: "solid", class: "bg-warning-solid border-transparent text-warning-solid-foreground" },
122
+ { nivel: "danger", variante: "solid", class: "bg-danger-solid border-transparent text-danger-solid-foreground" }
123
+ ],
124
+ defaultVariants: { nivel: "info", variante: "surface" }
125
+ }
126
+ );
127
+ function Alert({ className, nivel, variante, titulo, icone, children, ...props }) {
128
+ return /* @__PURE__ */ jsxs(
129
+ "div",
130
+ {
131
+ role: nivel === "danger" ? "alert" : "status",
132
+ className: cn(alerta({ nivel, variante }), className),
133
+ ...props,
134
+ children: [
135
+ icone,
136
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
137
+ titulo ? /* @__PURE__ */ jsx("p", { className: "font-semibold", children: titulo }) : null,
138
+ children ? /* @__PURE__ */ jsx("div", { className: cn(titulo && "mt-1 opacity-90"), children }) : null
139
+ ] })
140
+ ]
141
+ }
142
+ );
143
+ }
144
+ var TAMANHO = {
145
+ sm: "max-w-sm",
146
+ md: "max-w-lg",
147
+ lg: "max-w-3xl",
148
+ full: "max-w-[min(96vw,80rem)] h-[min(92vh,60rem)]"
149
+ };
150
+ function Dialog({
151
+ aberto,
152
+ onFechar,
153
+ titulo,
154
+ descricao,
155
+ children,
156
+ rodape,
157
+ tamanho = "md",
158
+ persistente = false,
159
+ className
160
+ }) {
161
+ const ref = useRef(null);
162
+ const id = useId();
163
+ const idTitulo = `${id}-titulo`;
164
+ const idDescricao = `${id}-descricao`;
165
+ useEffect(() => {
166
+ const el = ref.current;
167
+ if (!el) return;
168
+ if (aberto && !el.open) el.showModal();
169
+ if (!aberto && el.open) el.close();
170
+ }, [aberto]);
171
+ useEffect(() => {
172
+ if (!aberto) return;
173
+ const anterior = document.body.style.overflow;
174
+ document.body.style.overflow = "hidden";
175
+ return () => {
176
+ document.body.style.overflow = anterior;
177
+ };
178
+ }, [aberto]);
179
+ return /* @__PURE__ */ jsxs(
180
+ "dialog",
181
+ {
182
+ ref,
183
+ "aria-labelledby": idTitulo,
184
+ "aria-describedby": descricao ? idDescricao : void 0,
185
+ onCancel: (e) => {
186
+ if (persistente) e.preventDefault();
187
+ else onFechar();
188
+ },
189
+ onClose: () => {
190
+ if (aberto) onFechar();
191
+ },
192
+ onClick: (e) => {
193
+ if (!persistente && e.target === ref.current) onFechar();
194
+ },
195
+ className: cn(
196
+ "m-auto w-[calc(100vw-2rem)] rounded-lg border border-border bg-card p-0 text-card-foreground shadow-lg",
197
+ "backdrop:bg-overlay backdrop:backdrop-blur-[2px]",
198
+ TAMANHO[tamanho],
199
+ // open:flex, não flex. Um <dialog> fechado é display:none pela folha
200
+ // do navegador; uma classe com display:flex sobrescreve isso e o modal
201
+ // passa a renderizar no fluxo da página o tempo todo.
202
+ tamanho === "full" && "open:flex open:flex-col",
203
+ className
204
+ ),
205
+ children: [
206
+ /* @__PURE__ */ jsxs("header", { className: "flex items-start justify-between gap-4 border-b border-border px-5 py-3", children: [
207
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
208
+ /* @__PURE__ */ jsx("h2", { id: idTitulo, className: "text-base font-semibold", children: titulo }),
209
+ descricao ? /* @__PURE__ */ jsx("p", { id: idDescricao, className: "mt-0.5 text-sm text-muted-foreground", children: descricao }) : null
210
+ ] }),
211
+ persistente ? null : /* @__PURE__ */ jsx(Button, { variante: "ghost", tamanho: "icon", onClick: onFechar, "aria-label": "Fechar", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", className: "size-4", fill: "none", stroke: "currentColor", strokeWidth: "1.8", children: /* @__PURE__ */ jsx("path", { d: "M5 5l10 10M15 5L5 15", strokeLinecap: "round" }) }) })
212
+ ] }),
213
+ /* @__PURE__ */ jsx("div", { className: cn("px-5 py-4", tamanho === "full" && "min-h-0 flex-1 overflow-auto"), children }),
214
+ rodape ? /* @__PURE__ */ jsx("footer", { className: "flex justify-end gap-2 border-t border-border px-5 py-3", children: rodape }) : null
215
+ ]
216
+ }
217
+ );
218
+ }
219
+ var selo = cva(
220
+ "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap",
221
+ {
222
+ variants: {
223
+ nivel: {
224
+ neutral: "bg-muted border-border text-muted-foreground",
225
+ brand: "bg-accent border-transparent text-accent-foreground",
226
+ success: "bg-success border-success-border text-success-foreground",
227
+ info: "bg-info border-info-border text-info-foreground",
228
+ warning: "bg-warning border-warning-border text-warning-foreground",
229
+ danger: "bg-danger border-danger-border text-danger-foreground"
230
+ }
231
+ },
232
+ defaultVariants: { nivel: "neutral" }
233
+ }
234
+ );
235
+ function Badge({ className, nivel, ...props }) {
236
+ return /* @__PURE__ */ jsx("span", { className: cn(selo({ nivel }), className), ...props });
237
+ }
238
+ function Checkbox({ label, ajuda, className, id: idExterno, ...props }) {
239
+ const gerado = useId();
240
+ const id = idExterno ?? gerado;
241
+ const idAjuda = `${id}-ajuda`;
242
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", className), children: [
243
+ /* @__PURE__ */ jsxs("span", { className: "relative mt-0.5 inline-grid shrink-0 place-items-center", children: [
244
+ /* @__PURE__ */ jsx(
245
+ "input",
246
+ {
247
+ type: "checkbox",
248
+ id,
249
+ "aria-describedby": ajuda ? idAjuda : void 0,
250
+ className: cn(
251
+ "peer size-4 appearance-none rounded border border-input bg-background",
252
+ "checked:border-primary checked:bg-primary",
253
+ "indeterminate:border-primary indeterminate:bg-primary",
254
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
255
+ "disabled:cursor-not-allowed disabled:opacity-50"
256
+ ),
257
+ ...props
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsx(
261
+ "svg",
262
+ {
263
+ viewBox: "0 0 16 16",
264
+ "aria-hidden": "true",
265
+ className: "pointer-events-none absolute size-3 text-primary-foreground opacity-0 peer-checked:opacity-100",
266
+ fill: "none",
267
+ stroke: "currentColor",
268
+ strokeWidth: "2.5",
269
+ strokeLinecap: "round",
270
+ strokeLinejoin: "round",
271
+ children: /* @__PURE__ */ jsx("path", { d: "M3 8.5l3.5 3.5L13 5" })
272
+ }
273
+ )
274
+ ] }),
275
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
276
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-sm text-foreground", children: label }),
277
+ ajuda ? /* @__PURE__ */ jsx("p", { id: idAjuda, className: "text-xs text-muted-foreground", children: ajuda }) : null
278
+ ] })
279
+ ] });
280
+ }
281
+ function Input({ className, invalido, ...props }) {
282
+ return /* @__PURE__ */ jsx(
283
+ "input",
284
+ {
285
+ className: cn(
286
+ "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm text-foreground transition-colors",
287
+ "placeholder:text-muted-foreground",
288
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
289
+ "disabled:cursor-not-allowed disabled:opacity-50",
290
+ invalido && "border-danger-solid ring-danger-solid/20",
291
+ className
292
+ ),
293
+ "aria-invalid": invalido || void 0,
294
+ ...props
295
+ }
296
+ );
297
+ }
298
+ function Field({ label, ajuda, erro, obrigatorio, className, children }) {
299
+ const id = useId();
300
+ const idAjuda = `${id}-ajuda`;
301
+ const idErro = `${id}-erro`;
302
+ const descritores = [erro ? idErro : null, ajuda && !erro ? idAjuda : null].filter(Boolean).join(" ");
303
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", className), children: [
304
+ /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "block text-sm font-medium text-foreground", children: [
305
+ label,
306
+ obrigatorio ? /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-danger-solid", "aria-hidden": "true", children: "*" }) : null
307
+ ] }),
308
+ children({
309
+ id,
310
+ "aria-describedby": descritores || void 0,
311
+ "aria-invalid": erro ? true : void 0
312
+ }),
313
+ erro ? /* @__PURE__ */ jsx("p", { id: idErro, className: "text-xs text-danger-foreground", children: erro }) : ajuda ? /* @__PURE__ */ jsx("p", { id: idAjuda, className: "text-xs text-muted-foreground", children: ajuda }) : null
314
+ ] });
315
+ }
316
+ var OLHO = "M2 10s3-5.5 8-5.5S18 10 18 10s-3 5.5-8 5.5S2 10 2 10Z";
317
+ var RISCO = "M4 16 16 4";
318
+ function PasswordInput({ className, invalido, autoComplete, ...props }) {
319
+ const [visivel, setVisivel] = useState(false);
320
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
321
+ /* @__PURE__ */ jsx(
322
+ "input",
323
+ {
324
+ type: visivel ? "text" : "password",
325
+ autoComplete,
326
+ "aria-invalid": invalido || void 0,
327
+ className: cn(
328
+ "flex h-9 w-full rounded-md border border-input bg-background py-1 pr-10 pl-3 text-sm text-foreground transition-colors",
329
+ "placeholder:text-muted-foreground",
330
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
331
+ "disabled:cursor-not-allowed disabled:opacity-50",
332
+ invalido && "border-danger-solid",
333
+ className
334
+ ),
335
+ ...props
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsx(
339
+ "button",
340
+ {
341
+ type: "button",
342
+ onClick: () => setVisivel((v) => !v),
343
+ "aria-pressed": visivel,
344
+ "aria-label": visivel ? "Ocultar senha" : "Mostrar senha",
345
+ className: cn(
346
+ "absolute top-1/2 right-1 -translate-y-1/2 rounded p-1.5 text-muted-foreground",
347
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
348
+ ),
349
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 20", className: "size-4", fill: "none", stroke: "currentColor", strokeWidth: "1.6", "aria-hidden": "true", children: [
350
+ /* @__PURE__ */ jsx("path", { d: OLHO }),
351
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "2.2" }),
352
+ visivel ? /* @__PURE__ */ jsx("path", { d: RISCO, strokeLinecap: "round" }) : null
353
+ ] })
354
+ }
355
+ )
356
+ ] });
357
+ }
358
+ function Table({ className, ...props }) {
359
+ return /* @__PURE__ */ jsx("div", { className: "relative w-full overflow-x-auto", children: /* @__PURE__ */ jsx("table", { className: cn("w-full caption-bottom border-collapse text-sm", className), ...props }) });
360
+ }
361
+ function THead({ className, ...props }) {
362
+ return /* @__PURE__ */ jsx("thead", { className: cn("[&_tr]:border-b [&_tr]:border-border", className), ...props });
363
+ }
364
+ function TBody({ className, ...props }) {
365
+ return /* @__PURE__ */ jsx("tbody", { className: cn("[&_tr:last-child]:border-0", className), ...props });
366
+ }
367
+ function TR({
368
+ className,
369
+ selecionada,
370
+ ...props
371
+ }) {
372
+ return /* @__PURE__ */ jsx(
373
+ "tr",
374
+ {
375
+ "data-selecionada": selecionada || void 0,
376
+ "aria-selected": selecionada,
377
+ className: cn(
378
+ "border-b border-border transition-colors hover:bg-muted/60",
379
+ selecionada && "bg-accent/60",
380
+ className
381
+ ),
382
+ ...props
383
+ }
384
+ );
385
+ }
386
+ function TH({
387
+ className,
388
+ numerica,
389
+ ...props
390
+ }) {
391
+ return /* @__PURE__ */ jsx(
392
+ "th",
393
+ {
394
+ className: cn(
395
+ "h-9 px-3 text-left align-middle text-xs font-semibold text-muted-foreground",
396
+ numerica && "text-right",
397
+ className
398
+ ),
399
+ ...props
400
+ }
401
+ );
402
+ }
403
+ function TD({
404
+ className,
405
+ numerica,
406
+ ...props
407
+ }) {
408
+ return /* @__PURE__ */ jsx(
409
+ "td",
410
+ {
411
+ className: cn(
412
+ "px-3 py-2 align-middle leading-[1.4] text-foreground",
413
+ numerica && "text-right tabular-nums",
414
+ className
415
+ ),
416
+ ...props
417
+ }
418
+ );
419
+ }
420
+ function EmptyState({ titulo, descricao, acao, icone, className }) {
421
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center gap-2 px-6 py-12 text-center", className), children: [
422
+ icone ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: icone }) : null,
423
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: titulo }),
424
+ descricao ? /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: descricao }) : null,
425
+ acao ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: acao }) : null
426
+ ] });
427
+ }
428
+ function Pagination({
429
+ pagina,
430
+ totalPaginas,
431
+ totalLinhas,
432
+ podeVoltar,
433
+ podeAvancar,
434
+ onVoltar,
435
+ onAvancar,
436
+ className
437
+ }) {
438
+ return /* @__PURE__ */ jsxs(
439
+ "nav",
440
+ {
441
+ "aria-label": "Pagina\xE7\xE3o",
442
+ className: cn("flex items-center justify-between gap-4 border-t border-border px-3 py-2", className),
443
+ children: [
444
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground tabular-nums", "aria-live": "polite", children: [
445
+ "P\xE1gina ",
446
+ pagina,
447
+ " de ",
448
+ totalPaginas,
449
+ " \xB7 ",
450
+ totalLinhas,
451
+ " registros"
452
+ ] }),
453
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
454
+ /* @__PURE__ */ jsx(Button, { variante: "outline", tamanho: "sm", onClick: onVoltar, disabled: !podeVoltar, children: "Anterior" }),
455
+ /* @__PURE__ */ jsx(Button, { variante: "outline", tamanho: "sm", onClick: onAvancar, disabled: !podeAvancar, children: "Pr\xF3xima" })
456
+ ] })
457
+ ]
458
+ }
459
+ );
460
+ }
461
+ function Sparkline({
462
+ valores,
463
+ cor = "var(--primary)",
464
+ largura = 120,
465
+ altura = 32,
466
+ className,
467
+ rotulo
468
+ }) {
469
+ const id = useId();
470
+ if (valores.length < 2) return null;
471
+ const min = Math.min(...valores);
472
+ const max = Math.max(...valores);
473
+ const span = max - min || 1;
474
+ const pad = 4;
475
+ const x = (i) => pad + i / (valores.length - 1) * (largura - pad * 2);
476
+ const y = (v) => altura - pad - (v - min) / span * (altura - pad * 2);
477
+ const d = valores.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(v).toFixed(2)}`).join(" ");
478
+ const ultimoX = x(valores.length - 1);
479
+ const ultimoY = y(valores[valores.length - 1]);
480
+ return /* @__PURE__ */ jsxs(
481
+ "svg",
482
+ {
483
+ viewBox: `0 0 ${largura} ${altura}`,
484
+ width: largura,
485
+ height: altura,
486
+ className: cn("overflow-visible", className),
487
+ role: "img",
488
+ "aria-labelledby": id,
489
+ children: [
490
+ /* @__PURE__ */ jsx("title", { id, children: rotulo }),
491
+ /* @__PURE__ */ jsx("path", { d, fill: "none", stroke: cor, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
492
+ /* @__PURE__ */ jsx("circle", { cx: ultimoX, cy: ultimoY, r: "4", fill: cor, stroke: "var(--card)", strokeWidth: "2" })
493
+ ]
494
+ }
495
+ );
496
+ }
497
+ var SETA = { subiu: "M10 4v12M10 4l-5 5M10 4l5 5", caiu: "M10 16V4M10 16l-5-5M10 16l5-5", estavel: "M4 10h12" };
498
+ function StatTile({ rotulo, valor, unidade, delta, apoio, grafico, className }) {
499
+ const corDelta = delta?.sentido === "bom" ? "text-success-foreground" : delta?.sentido === "ruim" ? "text-danger-foreground" : "text-muted-foreground";
500
+ return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card p-4", className), children: [
501
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: rotulo }),
502
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-end justify-between gap-3", children: [
503
+ /* @__PURE__ */ jsxs("p", { className: "text-3xl leading-none font-semibold text-foreground", children: [
504
+ valor,
505
+ unidade ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-base font-normal text-muted-foreground", children: unidade }) : null
506
+ ] }),
507
+ grafico
508
+ ] }),
509
+ delta || apoio ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2 text-xs", children: [
510
+ delta ? (
511
+ /* Ícone + texto, nunca cor sozinha: quem não distingue verde de
512
+ vermelho precisa da seta e da palavra. */
513
+ /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1 font-medium", corDelta), children: [
514
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", className: "size-3.5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: SETA[delta.direcao] }) }),
515
+ delta.texto
516
+ ] })
517
+ ) : null,
518
+ apoio ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: apoio }) : null
519
+ ] }) : null
520
+ ] });
521
+ }
522
+
523
+ export { Alert, Badge, Button, Checkbox, Dialog, EmptyState, Field, Input, Pagination, PasswordInput, Sparkline, Spinner, StatTile, TBody, TD, TH, THead, TR, Table, alerta as alertVariants, selo as badgeVariants, botao as buttonVariants, cn, criarColumnHelper, dataTableFeatures, useBrand, useModo };
@@ -0,0 +1,89 @@
1
+ /* Ponte entre os tokens semânticos (que o brand-forge escreve em :root) e as
2
+ utilities do Tailwind v4. Divisão de responsabilidade: o brand-forge é dono
3
+ dos VALORES, o kernel é dono dos NOMES de utility que seus componentes usam.
4
+
5
+ Importe DEPOIS do tokens.css da marca. */
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --color-card: var(--card);
11
+ --color-card-foreground: var(--card-foreground);
12
+ --color-popover: var(--popover);
13
+ --color-popover-foreground: var(--popover-foreground);
14
+ --color-primary: var(--primary);
15
+ --color-primary-foreground: var(--primary-foreground);
16
+ --color-secondary: var(--secondary);
17
+ --color-secondary-foreground: var(--secondary-foreground);
18
+ --color-muted: var(--muted);
19
+ --color-muted-foreground: var(--muted-foreground);
20
+ --color-accent: var(--accent);
21
+ --color-accent-foreground: var(--accent-foreground);
22
+ --color-destructive: var(--destructive);
23
+ --color-destructive-foreground: var(--destructive-foreground);
24
+ --color-border: var(--border);
25
+ --color-input: var(--input);
26
+ --color-ring: var(--ring);
27
+
28
+ --color-success: var(--success);
29
+ --color-success-border: var(--success-border);
30
+ --color-success-foreground: var(--success-foreground);
31
+ --color-success-solid: var(--success-solid);
32
+ --color-success-solid-foreground: var(--success-solid-foreground);
33
+ --color-info: var(--info);
34
+ --color-info-border: var(--info-border);
35
+ --color-info-foreground: var(--info-foreground);
36
+ --color-info-solid: var(--info-solid);
37
+ --color-info-solid-foreground: var(--info-solid-foreground);
38
+ --color-warning: var(--warning);
39
+ --color-warning-border: var(--warning-border);
40
+ --color-warning-foreground: var(--warning-foreground);
41
+ --color-warning-solid: var(--warning-solid);
42
+ --color-warning-solid-foreground: var(--warning-solid-foreground);
43
+ --color-danger: var(--danger);
44
+ --color-danger-border: var(--danger-border);
45
+ --color-danger-foreground: var(--danger-foreground);
46
+ --color-danger-solid: var(--danger-solid);
47
+ --color-danger-solid-foreground: var(--danger-solid-foreground);
48
+
49
+ /* Véu atrás de modal. A marca pode emitir --overlay; enquanto não emite, o
50
+ fallback é preto a 50% — véu escurece nos dois modos, então não segue
51
+ --background nem --foreground. Fica aqui, na ponte, e não no componente. */
52
+ --color-overlay: var(--overlay, oklch(0% 0 0 / 0.5));
53
+
54
+ --color-chart-1: var(--chart-1);
55
+ --color-chart-2: var(--chart-2);
56
+ --color-chart-3: var(--chart-3);
57
+ --color-chart-4: var(--chart-4);
58
+ --color-chart-5: var(--chart-5);
59
+
60
+ --radius-sm: calc(var(--radius) - 4px);
61
+ --radius-md: calc(var(--radius) - 2px);
62
+ --radius-lg: var(--radius);
63
+ --radius-xl: calc(var(--radius) + 4px);
64
+ }
65
+
66
+ @layer base {
67
+ * {
68
+ border-color: var(--border);
69
+ }
70
+ body {
71
+ background-color: var(--background);
72
+ color: var(--foreground);
73
+ -webkit-font-smoothing: antialiased;
74
+ }
75
+
76
+ /* A família precisa ser reaplicada em cada contêiner de marca, não só no
77
+ body: em página multi-marca a marca vive abaixo do body, e font-family
78
+ herdada já vem resolvida — o token da marca nunca chegaria. */
79
+ /* Guarda contra o mesmo erro em qualquer dialog do produto: nenhuma
80
+ utility de display deve conseguir revelar um dialog fechado. */
81
+ dialog:not([open]) {
82
+ display: none;
83
+ }
84
+
85
+ body,
86
+ [data-brand] {
87
+ font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif);
88
+ }
89
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@fldev-solution/ui",
3
+ "version": "0.1.0",
4
+ "description": "Kernel de UI compartilhado entre os SaaS da fldev. Primitivos, hooks e utilitários; blocos compostos vão pelo registry.",
5
+ "license": "MIT",
6
+ "homepage": "https://ui.fldev.com.br",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/FLDEV-Solution/fldev-ui.git",
10
+ "directory": "packages/ui"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "tailwindcss",
15
+ "design-system",
16
+ "shadcn",
17
+ "multi-brand"
18
+ ],
19
+ "type": "module",
20
+ "sideEffects": false,
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./styles.css": "./dist/styles.css"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "typecheck": "tsc --noEmit",
37
+ "prepublishOnly": "pnpm build"
38
+ },
39
+ "peerDependencies": {
40
+ "react": ">=18",
41
+ "react-dom": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "@tanstack/react-table": "^9.2.4",
45
+ "class-variance-authority": "^0.7.1",
46
+ "clsx": "^2.1.1",
47
+ "tailwind-merge": "^3.4.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^19.3.0",
51
+ "@types/react-dom": "^19.3.0",
52
+ "react": "^19.3.0",
53
+ "react-dom": "^19.3.0",
54
+ "tsup": "^8.5.0",
55
+ "typescript": "^5.9.3"
56
+ }
57
+ }