@lindaui/blocks 0.1.0 → 0.3.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.
@@ -0,0 +1,44 @@
1
+ import type { ReactNode } from "react";
2
+ import { type TableColumnDef } from "@lindaui/ui/table";
3
+ export interface TablePaneFilter {
4
+ key: string;
5
+ label: string;
6
+ count?: number;
7
+ }
8
+ /**
9
+ * Tabular sibling of `list-pane`: the same controlled chrome (filter tabs +
10
+ * search + loading/error/empty states) wrapped around `@lindaui/ui/table`,
11
+ * plus a `toolbarExtra` slot and an optional footer. Zero data-fetching — the
12
+ * app passes already-filtered `rows` (each cell is a `ReactNode`, so domain
13
+ * chips/avatars live in the consumer) and owns all state.
14
+ */
15
+ export interface TablePaneProps {
16
+ columns: TableColumnDef[];
17
+ rows: Record<string, ReactNode>[];
18
+ ariaLabel?: string;
19
+ filters?: TablePaneFilter[];
20
+ activeFilter?: string;
21
+ onFilterChange?: (key: string) => void;
22
+ searchValue?: string;
23
+ onSearchChange?: (value: string) => void;
24
+ searchPlaceholder?: string;
25
+ searchLabel?: string;
26
+ toolbarExtra?: ReactNode;
27
+ loading?: boolean;
28
+ error?: string | null;
29
+ onRetry?: () => void;
30
+ errorTitle?: string;
31
+ retryLabel?: string;
32
+ isEmpty?: boolean;
33
+ emptyTitle?: string;
34
+ emptyDescription?: ReactNode;
35
+ emptyIcon?: ReactNode;
36
+ footer?: ReactNode;
37
+ footerCount?: {
38
+ shown: number;
39
+ total: number;
40
+ label?: string;
41
+ };
42
+ }
43
+ export declare function TablePane({ columns, rows, ariaLabel, filters, activeFilter, onFilterChange, searchValue, onSearchChange, searchPlaceholder, searchLabel, toolbarExtra, loading, error, onRetry, errorTitle, retryLabel, isEmpty, emptyTitle, emptyDescription, emptyIcon, footer, footerCount, }: TablePaneProps): import("react").JSX.Element;
44
+ //# sourceMappingURL=table-pane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-pane.d.ts","sourceRoot":"","sources":["../src/table-pane.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAS,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAE7B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAGvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,YAAY,CAAC,EAAE,SAAS,CAAC;IAGzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC;IAGtB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAChE;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,IAAI,EACJ,SAAmB,EACnB,OAAO,EACP,YAAY,EACZ,cAAc,EACd,WAAgB,EAChB,cAAc,EACd,iBAA4B,EAC5B,WAAsB,EACtB,YAAY,EACZ,OAAe,EACf,KAAY,EACZ,OAAO,EACP,UAAgC,EAChC,UAAyB,EACzB,OAAO,EACP,UAA6B,EAC7B,gBAAgB,EAChB,SAAS,EACT,MAAM,EACN,WAAW,GACZ,EAAE,cAAc,+BAiHhB"}
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { AlertCircle, RotateCcw, FileText } from 'lucide-react';
4
+ import { Button } from '@lindaui/ui/button';
5
+ import { SearchField } from '@lindaui/ui/search-field';
6
+ import { Skeleton } from '@lindaui/ui/skeleton';
7
+ import { Table } from '@lindaui/ui/table';
8
+
9
+ function TablePane({ columns, rows, ariaLabel = "Tabla", filters, activeFilter, onFilterChange, searchValue = "", onSearchChange, searchPlaceholder = "Buscar", searchLabel = "Buscar", toolbarExtra, loading = false, error = null, onRetry, errorTitle = "No se pudo cargar", retryLabel = "Reintentar", isEmpty, emptyTitle = "Sin resultados", emptyDescription, emptyIcon, footer, footerCount }) {
10
+ const showEmpty = !loading && !error && (isEmpty !== undefined ? isEmpty : rows.length === 0);
11
+ const showFooter = !loading && !error && (footer != null || footerCount != null);
12
+ return /*#__PURE__*/ jsxs("div", {
13
+ className: "flex flex-col overflow-hidden rounded-xl border border-border bg-card",
14
+ children: [
15
+ filters && filters.length > 0 && /*#__PURE__*/ jsx("div", {
16
+ className: "flex flex-wrap gap-2 overflow-x-auto border-b border-border p-3",
17
+ children: filters.map((f)=>/*#__PURE__*/ jsxs(Button, {
18
+ variant: activeFilter === f.key ? "primary" : "ghost",
19
+ onPress: ()=>onFilterChange?.(f.key),
20
+ size: "sm",
21
+ children: [
22
+ f.label,
23
+ f.count != null && /*#__PURE__*/ jsx("span", {
24
+ "aria-hidden": true,
25
+ className: "ml-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-muted-foreground/15 px-1 text-[11px] font-semibold tabular-nums text-muted-foreground",
26
+ children: f.count
27
+ })
28
+ ]
29
+ }, f.key))
30
+ }),
31
+ (onSearchChange || toolbarExtra) && /*#__PURE__*/ jsxs("div", {
32
+ className: "flex flex-wrap items-center gap-3 border-b border-border p-4",
33
+ children: [
34
+ onSearchChange && /*#__PURE__*/ jsx("div", {
35
+ className: "min-w-[240px] flex-1",
36
+ children: /*#__PURE__*/ jsx(SearchField, {
37
+ value: searchValue,
38
+ onChange: onSearchChange,
39
+ placeholder: searchPlaceholder,
40
+ "aria-label": searchLabel
41
+ })
42
+ }),
43
+ toolbarExtra
44
+ ]
45
+ }),
46
+ /*#__PURE__*/ jsxs("div", {
47
+ className: "min-h-0 flex-1 overflow-x-auto",
48
+ children: [
49
+ loading && /*#__PURE__*/ jsx("div", {
50
+ role: "status",
51
+ "aria-label": "Cargando",
52
+ className: "space-y-2 p-3",
53
+ children: Array.from({
54
+ length: 6
55
+ }).map((_, i)=>/*#__PURE__*/ jsx(Skeleton, {
56
+ className: "h-12 w-full"
57
+ }, i))
58
+ }),
59
+ !loading && error && /*#__PURE__*/ jsxs("div", {
60
+ className: "m-3 rounded-xl border border-destructive/35 bg-destructive/5 p-6 text-center",
61
+ children: [
62
+ /*#__PURE__*/ jsx(AlertCircle, {
63
+ className: "mx-auto h-6 w-6 text-destructive"
64
+ }),
65
+ /*#__PURE__*/ jsx("p", {
66
+ className: "mt-2 text-sm font-semibold",
67
+ children: errorTitle
68
+ }),
69
+ /*#__PURE__*/ jsx("p", {
70
+ className: "mt-1 text-xs text-muted-foreground",
71
+ children: error
72
+ }),
73
+ onRetry && /*#__PURE__*/ jsxs(Button, {
74
+ variant: "secondary",
75
+ size: "sm",
76
+ onPress: onRetry,
77
+ className: "mt-3",
78
+ children: [
79
+ /*#__PURE__*/ jsx(RotateCcw, {
80
+ className: "h-3.5 w-3.5"
81
+ }),
82
+ retryLabel
83
+ ]
84
+ })
85
+ ]
86
+ }),
87
+ showEmpty && /*#__PURE__*/ jsxs("div", {
88
+ className: "py-16 text-center",
89
+ children: [
90
+ /*#__PURE__*/ jsx("div", {
91
+ className: "mx-auto grid h-11 w-11 place-items-center rounded-xl bg-secondary",
92
+ children: emptyIcon ?? /*#__PURE__*/ jsx(FileText, {
93
+ className: "h-5 w-5 text-muted-foreground"
94
+ })
95
+ }),
96
+ /*#__PURE__*/ jsx("p", {
97
+ className: "mt-3 text-sm font-semibold",
98
+ children: emptyTitle
99
+ }),
100
+ emptyDescription && /*#__PURE__*/ jsx("div", {
101
+ className: "mt-1 text-xs text-muted-foreground",
102
+ children: emptyDescription
103
+ })
104
+ ]
105
+ }),
106
+ !loading && !error && !showEmpty && /*#__PURE__*/ jsx(Table, {
107
+ "aria-label": ariaLabel,
108
+ columns: columns,
109
+ rows: rows
110
+ })
111
+ ]
112
+ }),
113
+ showFooter && (footer ?? /*#__PURE__*/ jsx("div", {
114
+ className: "flex items-center justify-between border-t border-border px-5 py-3 text-xs text-muted-foreground",
115
+ children: /*#__PURE__*/ jsxs("span", {
116
+ children: [
117
+ footerCount.label ?? "Mostrando",
118
+ " ",
119
+ /*#__PURE__*/ jsx("span", {
120
+ className: "font-medium text-foreground",
121
+ children: footerCount.shown
122
+ }),
123
+ " ",
124
+ "de",
125
+ " ",
126
+ /*#__PURE__*/ jsx("span", {
127
+ className: "font-medium text-foreground",
128
+ children: footerCount.total
129
+ })
130
+ ]
131
+ })
132
+ }))
133
+ ]
134
+ });
135
+ }
136
+
137
+ export { TablePane };
package/llms.txt CHANGED
@@ -55,6 +55,17 @@ Filterable/searchable list shell with loading/empty/error states. Controlled:
55
55
  `onSearchChange`, `loading`, `error`, `onRetry`, `isEmpty`, empty/error slots. Items go
56
56
  in via `children`. Does NOT filter for you — filter your data (use `@lindaui/ui/search`).
57
57
 
58
+ ### @lindaui/blocks/table-pane
59
+ Tabular sibling of `list-pane`: same controlled chrome (filter tabs + search +
60
+ loading/error/empty) around `@lindaui/ui/table`. Props: `columns: {key,label}[]`,
61
+ `rows: Record<string,ReactNode>[]` (each cell a ReactNode → domain chips/avatars live
62
+ in your app), `ariaLabel?`, the same `filters`/`activeFilter`/`onFilterChange` +
63
+ `searchValue`/`onSearchChange`/`searchLabel` + `loading`/`error`/`onRetry`/`isEmpty` +
64
+ empty slots as list-pane, plus `toolbarExtra?` (extra filter controls right of the
65
+ search, e.g. a `<Select>`), `footer?` (escape hatch) and `footerCount?: {shown,total,
66
+ label?}` (default "Mostrando X de Y"). Does NOT filter/fetch — pass already-filtered
67
+ `rows` and own the state.
68
+
58
69
  ### @lindaui/blocks/detail-panel
59
70
  Master-detail right side: empty state + header + meta card + CTA. Controlled.
60
71
 
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@lindaui/blocks",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Composed UI sections (auth, lists, master-detail, charts) built on @lindaui/ui — importable npm package, not a copy-paste registry.",
5
5
  "author": "Franco Carballar <francocarballar@gmail.com>",
6
6
  "license": "MIT",
7
+ "homepage": "https://github.com/francocarballar/lindaui#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/francocarballar/lindaui/issues"
10
+ },
11
+ "keywords": [
12
+ "react",
13
+ "react-components",
14
+ "design-system",
15
+ "blocks",
16
+ "sections",
17
+ "charts",
18
+ "heroui"
19
+ ],
7
20
  "repository": {
8
21
  "type": "git",
9
22
  "url": "git+https://github.com/francocarballar/lindaui.git",
@@ -106,6 +119,10 @@
106
119
  "./workspace-back-button": {
107
120
  "types": "./dist/workspace-back-button.d.ts",
108
121
  "import": "./dist/workspace-back-button.js"
122
+ },
123
+ "./table-pane": {
124
+ "types": "./dist/table-pane.d.ts",
125
+ "import": "./dist/table-pane.js"
109
126
  }
110
127
  },
111
128
  "files": [
@@ -120,12 +137,12 @@
120
137
  "react-hook-form": ">=7",
121
138
  "lucide-react": ">=0.4",
122
139
  "recharts": ">=3",
123
- "@lindaui/ui": "*",
124
- "@lindaui/tokens": "*"
140
+ "@lindaui/ui": ">=0.2.0",
141
+ "@lindaui/tokens": ">=0.2.0"
125
142
  },
126
143
  "devDependencies": {
127
144
  "bunchee": "^6.0.0",
128
- "vitest": "^2.0.0",
145
+ "vitest": "^3.2.6",
129
146
  "vite": "^6.4.3",
130
147
  "@vitejs/plugin-react": "^4.3.0",
131
148
  "@testing-library/react": "^16.0.0",
@@ -142,12 +159,13 @@
142
159
  "zod": "^3.23.0",
143
160
  "@hookform/resolvers": "^3.9.0",
144
161
  "typescript": "^6.0.3",
145
- "@lindaui/ui": "0.1.0",
146
- "@lindaui/tokens": "0.2.1"
162
+ "@lindaui/ui": "0.3.0",
163
+ "@lindaui/tokens": "0.3.0"
147
164
  },
148
165
  "scripts": {
149
166
  "build": "bunchee --no-dts && tsc -p tsconfig.build.json",
150
167
  "dev": "bunchee --watch",
168
+ "typecheck": "tsc -p tsconfig.json --noEmit",
151
169
  "test": "vitest run",
152
170
  "test:watch": "vitest"
153
171
  }