@lindaui/blocks 0.1.0 → 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/dist/table-pane.d.ts +44 -0
- package/dist/table-pane.d.ts.map +1 -0
- package/dist/table-pane.js +137 -0
- package/llms.txt +11 -0
- package/package.json +7 -3
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lindaui/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -106,6 +106,10 @@
|
|
|
106
106
|
"./workspace-back-button": {
|
|
107
107
|
"types": "./dist/workspace-back-button.d.ts",
|
|
108
108
|
"import": "./dist/workspace-back-button.js"
|
|
109
|
+
},
|
|
110
|
+
"./table-pane": {
|
|
111
|
+
"types": "./dist/table-pane.d.ts",
|
|
112
|
+
"import": "./dist/table-pane.js"
|
|
109
113
|
}
|
|
110
114
|
},
|
|
111
115
|
"files": [
|
|
@@ -142,8 +146,8 @@
|
|
|
142
146
|
"zod": "^3.23.0",
|
|
143
147
|
"@hookform/resolvers": "^3.9.0",
|
|
144
148
|
"typescript": "^6.0.3",
|
|
145
|
-
"@lindaui/
|
|
146
|
-
"@lindaui/
|
|
149
|
+
"@lindaui/tokens": "0.2.2",
|
|
150
|
+
"@lindaui/ui": "0.2.0"
|
|
147
151
|
},
|
|
148
152
|
"scripts": {
|
|
149
153
|
"build": "bunchee --no-dts && tsc -p tsconfig.build.json",
|