react-antd-admin-panel
Version:
Modern TypeScript-first React admin panel builder with Ant Design 6
527 lines (526 loc) • 14.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import React from "react";
import { Popconfirm, Button, Tooltip, Space, Row, Col, Tag, Input as Input$1, Table, Avatar } from "antd";
import { SearchOutlined, PlusOutlined, ReloadOutlined } from "@ant-design/icons";
import { B as BaseBuilder } from "../BaseBuilder-dOpE6Uh1.js";
class List extends BaseBuilder {
constructor() {
super();
// These are used by selectable() for initial state, but actual state is managed in ListComponent
__publicField(this, "_selectedRowKeys", []);
this._config.columns = [];
this._config.actions = [];
this._config.bulkActions = [];
}
/**
* Set the data source
*/
dataSource(data) {
this._config.dataSource = data;
return this;
}
/**
* Set the row key field or function
*/
rowKey(key) {
this._config.rowKey = key;
return this;
}
/**
* Add a column to the table
* Supports multiple signatures:
* - column(key, title) - basic column
* - column(key, title, config) - column with options
* - column(key, title, render) - column with custom render
* - column(key, title, render, config) - column with render and options
*/
column(key, title, renderOrConfig, configIfRender) {
const columnConfig = {
key,
title,
dataIndex: key
};
if (typeof renderOrConfig === "function") {
columnConfig.render = renderOrConfig;
if (configIfRender) {
Object.assign(columnConfig, configIfRender);
}
} else if (renderOrConfig) {
Object.assign(columnConfig, renderOrConfig);
}
this._config.columns.push(columnConfig);
return this;
}
/**
* Add an avatar column
*/
avatarColumn(key, title = "Avatar", size = 40) {
this._config.columns.push({
key,
title,
dataIndex: key,
width: size + 32,
render: (url) => React.createElement(Avatar, { src: url, size })
});
return this;
}
/**
* Add a tag column
*/
tagColumn(key, title, colorMap, options) {
this._config.columns.push({
key,
title,
dataIndex: key,
...options,
render: (value) => {
const color = (colorMap == null ? void 0 : colorMap[value]) || "default";
return React.createElement(Tag, { color }, value);
}
});
return this;
}
/**
* Add a date column with formatting
*/
dateColumn(key, title, format = "date", options) {
this._config.columns.push({
key,
title,
dataIndex: key,
...options,
render: (value) => {
if (!value) return "-";
const date = new Date(value);
if (format === "datetime") {
return date.toLocaleString();
} else if (format === "relative") {
const diff = Date.now() - date.getTime();
const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
if (days === 0) return "Today";
if (days === 1) return "Yesterday";
if (days < 7) return `${days} days ago`;
return date.toLocaleDateString();
}
return date.toLocaleDateString();
}
});
return this;
}
/**
* Add a boolean column with Yes/No or custom labels
*/
booleanColumn(key, title, options) {
const { trueLabel = "Yes", falseLabel = "No", trueColor = "green", falseColor = "default" } = options || {};
this._config.columns.push({
key,
title,
dataIndex: key,
render: (value) => React.createElement(Tag, { color: value ? trueColor : falseColor }, value ? trueLabel : falseLabel)
});
return this;
}
/**
* Add an action button
*/
action(key, label, onClick, options) {
this._config.actions.push({
key,
label,
onClick,
...options
});
return this;
}
/**
* Set loading state
*/
loading(value = true) {
this._config.loading = value;
return this;
}
/**
* Configure pagination
*/
pagination(config) {
this._config.pagination = config;
return this;
}
/**
* Set table size
*/
size(value) {
this._config.size = value;
return this;
}
/**
* Enable bordered style
*/
bordered(value = true) {
this._config.bordered = value;
return this;
}
/**
* Show/hide header
*/
showHeader(value = true) {
this._config.showHeader = value;
return this;
}
/**
* Enable sticky header
*/
sticky(value = true) {
this._config.sticky = value;
return this;
}
/**
* Set scroll dimensions
*/
scroll(x, y) {
this._config.scroll = { x, y };
return this;
}
/**
* Enable row selection
*/
rowSelection(config) {
this._config.rowSelection = config;
return this;
}
/**
* Enable row expansion
*/
expandable(config) {
this._config.expandable = config;
return this;
}
/**
* Enable row expansion with a render function
*/
expandableRow(render, isExpandable) {
this._config.expandable = {
expandedRowRender: render,
rowExpandable: isExpandable
};
return this;
}
/**
* Set row event handlers
*/
onRow(handler) {
this._config.onRow = handler;
return this;
}
/**
* Configure header controls (search, create, refresh, etc.)
*/
header(config) {
this._config.header = config;
return this;
}
/**
* Enable simple row selection (state is managed by ListComponent)
*/
selectable(type = "checkbox") {
this._config.rowSelection = {
type,
selectedRowKeys: this._selectedRowKeys,
onChange: (keys) => {
this._selectedRowKeys = keys;
}
};
return this;
}
/**
* Add a bulk action (for selected rows)
*/
bulkAction(key, label, onClick, options) {
this._config.bulkActions.push({
key,
label,
onClick,
...options
});
return this;
}
/**
* Set a Get request for data fetching
*/
get(request) {
this._config.get = request;
return this;
}
/**
* Set empty state text
*/
emptyText(text) {
this._config.emptyText = text;
return this;
}
/**
* Enable virtual scrolling for large datasets
* @param height - The height of the virtual scroll container (default 400)
* @param scrollX - The horizontal scroll width (default 'max-content'). Set to calculated width for best performance.
*/
virtual(height = 400, scrollX) {
this._config.virtual = true;
this._config.virtualHeight = height;
if (scrollX !== void 0) {
this._config.virtualScrollX = scrollX;
}
return this;
}
/**
* Get the current configuration (for advanced use cases)
*/
getConfig() {
return this._config;
}
/**
* Render the table component
*/
render() {
if (this._config.hidden) {
return null;
}
return React.createElement(ListComponent, { list: this });
}
}
const ListComponent = React.memo(function ListComponent2({ list }) {
const config = list.getConfig();
const [selectedRowKeys, setSelectedRowKeys] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState([]);
const [searchValue, setSearchValue] = React.useState("");
const columns = React.useMemo(() => {
const cols = [];
for (const col of config.columns || []) {
if (col.hidden) continue;
const column = {
key: col.key,
title: col.title,
dataIndex: col.dataIndex,
width: col.width,
fixed: col.fixed,
align: col.align,
ellipsis: col.ellipsis,
render: col.render
};
if (col.sorter) {
column.sorter = col.sorter;
}
if (col.filters) {
column.filters = col.filters;
column.onFilter = (value, record) => {
const dataIndex = col.dataIndex;
return record[dataIndex] === value;
};
}
cols.push(column);
}
if (config.actions && config.actions.length > 0) {
cols.push({
key: "_actions",
title: "Actions",
fixed: "right",
width: config.actions.length * 80,
render: (_, record, index) => {
const buttons = config.actions.map((action) => {
const isDisabled = typeof action.disabled === "function" ? action.disabled(record) : action.disabled;
if (action.confirm) {
return React.createElement(
Popconfirm,
{
key: action.key,
title: action.confirm,
onConfirm: () => action.onClick(record, index),
okText: "Yes",
cancelText: "No"
},
React.createElement(
Button,
{
type: "link",
size: "small",
danger: action.danger,
disabled: isDisabled,
icon: action.icon
},
action.label
)
);
}
const button = React.createElement(
Button,
{
key: action.key,
type: "link",
size: "small",
danger: action.danger,
disabled: isDisabled,
icon: action.icon,
onClick: () => action.onClick(record, index)
},
action.label
);
if (action.tooltip) {
return React.createElement(Tooltip, { key: action.key, title: action.tooltip }, button);
}
return button;
});
return React.createElement(Space, { size: "small" }, ...buttons);
}
});
}
return cols;
}, [config.columns, config.actions]);
const handleRowSelectionChange = React.useCallback((keys, rows) => {
var _a, _b;
setSelectedRowKeys(keys);
setSelectedRows(rows);
(_b = (_a = config.rowSelection) == null ? void 0 : _a.onChange) == null ? void 0 : _b.call(_a, keys, rows);
}, [config.rowSelection]);
const rowSelection = config.rowSelection ? {
...config.rowSelection,
selectedRowKeys,
onChange: handleRowSelectionChange
} : void 0;
const headerElement = React.useMemo(() => {
const header = config.header;
if (!header) return null;
const hasSelection = selectedRowKeys.length > 0;
const bulkActions = config.bulkActions || [];
return React.createElement(
Row,
{
justify: "space-between",
align: "middle",
style: { marginBottom: 16 }
},
// Left side: title and bulk actions
React.createElement(
Col,
{},
React.createElement(
Space,
{},
header.title && React.createElement("span", {
style: { fontSize: 16, fontWeight: 600 }
}, header.title),
hasSelection && React.createElement(
Tag,
{ color: "blue" },
`${selectedRowKeys.length} selected`
),
hasSelection && bulkActions.map((action) => {
if (action.confirm) {
return React.createElement(
Popconfirm,
{
key: action.key,
title: action.confirm,
onConfirm: () => action.onClick(selectedRows, selectedRowKeys),
okText: "Yes",
cancelText: "No"
},
React.createElement(
Button,
{
size: "small",
danger: action.danger,
icon: action.icon
},
action.label
)
);
}
return React.createElement(
Button,
{
key: action.key,
size: "small",
danger: action.danger,
icon: action.icon,
onClick: () => action.onClick(selectedRows, selectedRowKeys)
},
action.label
);
})
)
),
// Right side: search, create, refresh
React.createElement(
Col,
{},
React.createElement(
Space,
{},
header.showSearch && React.createElement(
Input$1,
{
placeholder: header.searchPlaceholder || "Search...",
prefix: React.createElement(SearchOutlined),
value: searchValue,
onChange: (e) => {
var _a;
setSearchValue(e.target.value);
(_a = header.onSearch) == null ? void 0 : _a.call(header, e.target.value);
},
allowClear: true,
style: { width: 200 },
"aria-label": "Search"
}
),
header.showCreate && React.createElement(
Button,
{
type: "primary",
icon: React.createElement(PlusOutlined),
onClick: header.onCreate,
"aria-label": header.createLabel ? void 0 : "Create new item"
},
header.createLabel || "Create"
),
header.showRefresh && React.createElement(
Button,
{
icon: React.createElement(ReloadOutlined),
onClick: header.onRefresh,
"aria-label": "Refresh list"
}
),
header.extra
)
)
);
}, [config.header, config.bulkActions, selectedRowKeys, selectedRows, searchValue]);
const tableProps = {
dataSource: config.dataSource,
columns,
rowKey: config.rowKey || "id",
loading: config.loading,
pagination: config.pagination,
size: config.size,
bordered: config.bordered,
showHeader: config.showHeader,
sticky: config.sticky,
scroll: config.virtual ? { x: config.virtualScrollX || "max-content", y: config.virtualHeight || 400, ...config.scroll } : config.scroll,
rowSelection,
expandable: config.expandable,
onRow: config.onRow,
locale: config.emptyText ? { emptyText: config.emptyText } : void 0,
virtual: config.virtual
};
return React.createElement(
React.Fragment,
{},
headerElement,
React.createElement(Table, tableProps)
);
});
export {
List
};
//# sourceMappingURL=index.js.map