@almadar/ui 5.8.1 → 5.9.1

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.
@@ -28941,6 +28941,318 @@ var init_Lightbox = __esm({
28941
28941
  Lightbox.displayName = "Lightbox";
28942
28942
  }
28943
28943
  });
28944
+ function columnLabel(col) {
28945
+ return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
28946
+ }
28947
+ function statusVariant4(value) {
28948
+ const v = value.toLowerCase();
28949
+ if (["active", "completed", "done", "approved", "published", "resolved", "open", "online", "ok"].includes(v)) return "success";
28950
+ if (["pending", "in_progress", "in-progress", "review", "draft", "processing", "warn", "warning"].includes(v)) return "warning";
28951
+ if (["inactive", "deleted", "rejected", "failed", "error", "blocked", "closed", "offline"].includes(v)) return "error";
28952
+ if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
28953
+ return "default";
28954
+ }
28955
+ function formatCell(value, format) {
28956
+ if (value === void 0 || value === null) return "";
28957
+ switch (format) {
28958
+ case "date": {
28959
+ const d = new Date(String(value));
28960
+ return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
28961
+ }
28962
+ case "currency":
28963
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
28964
+ case "number":
28965
+ return typeof value === "number" ? value.toLocaleString() : String(value);
28966
+ case "percent":
28967
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
28968
+ case "boolean":
28969
+ return value ? "Yes" : "No";
28970
+ default:
28971
+ return String(value);
28972
+ }
28973
+ }
28974
+ function groupData2(items, field) {
28975
+ const groups = /* @__PURE__ */ new Map();
28976
+ for (const item of items) {
28977
+ const key = String(getNestedValue(item, field) ?? "");
28978
+ const group = groups.get(key);
28979
+ if (group) group.push(item);
28980
+ else groups.set(key, [item]);
28981
+ }
28982
+ return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
28983
+ }
28984
+ function TableView({
28985
+ entity,
28986
+ columns,
28987
+ fields,
28988
+ itemActions,
28989
+ selectable = false,
28990
+ selectEvent,
28991
+ selectedIds,
28992
+ sortEvent,
28993
+ sortColumn,
28994
+ sortDirection,
28995
+ className,
28996
+ emptyMessage,
28997
+ isLoading = false,
28998
+ error = null,
28999
+ groupBy,
29000
+ pageSize = 0,
29001
+ children,
29002
+ renderItem: _schemaRenderItem,
29003
+ look = "dense",
29004
+ // DnD props consumed by useDataDnd.
29005
+ dragGroup,
29006
+ accepts,
29007
+ sortable,
29008
+ dropEvent,
29009
+ reorderEvent,
29010
+ positionEvent,
29011
+ dndItemIdField,
29012
+ dndRoot
29013
+ }) {
29014
+ const eventBus = useEventBus();
29015
+ const { t } = useTranslate();
29016
+ const [visibleCount, setVisibleCount] = React85__namespace.default.useState(pageSize > 0 ? pageSize : Infinity);
29017
+ const [localSelected, setLocalSelected] = React85__namespace.default.useState(/* @__PURE__ */ new Set());
29018
+ const colDefs = columns ?? fields ?? [];
29019
+ const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
29020
+ const dnd = useDataDnd({
29021
+ items: allDataRaw,
29022
+ layout: "list",
29023
+ dragGroup,
29024
+ accepts,
29025
+ sortable,
29026
+ dropEvent,
29027
+ reorderEvent,
29028
+ positionEvent,
29029
+ dndItemIdField,
29030
+ dndRoot
29031
+ });
29032
+ const ordered = dnd.orderedItems;
29033
+ const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
29034
+ const hasMore = pageSize > 0 && visibleCount < ordered.length;
29035
+ const hasRenderProp = typeof children === "function";
29036
+ const idField = dndItemIdField ?? "id";
29037
+ const selected = selectedIds ? new Set(selectedIds) : localSelected;
29038
+ const emitSelection = (next) => {
29039
+ if (!selectedIds) setLocalSelected(next);
29040
+ if (selectEvent) {
29041
+ const payload = { selectedIds: Array.from(next) };
29042
+ eventBus.emit(`UI:${selectEvent}`, payload);
29043
+ }
29044
+ };
29045
+ const allSelected = selectable && data.length > 0 && data.every((r, i) => selected.has(String(r[idField] ?? i)));
29046
+ const toggleAll = () => {
29047
+ if (allSelected) emitSelection(/* @__PURE__ */ new Set());
29048
+ else emitSelection(new Set(data.map((r, i) => String(r[idField] ?? i))));
29049
+ };
29050
+ const toggleRow = (id) => {
29051
+ const next = new Set(selected);
29052
+ if (next.has(id)) next.delete(id);
29053
+ else next.add(id);
29054
+ emitSelection(next);
29055
+ };
29056
+ const handleSort = (col) => {
29057
+ if (!col.sortable || !sortEvent) return;
29058
+ const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
29059
+ eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
29060
+ };
29061
+ const handleActionClick = (action, row) => (e) => {
29062
+ e.stopPropagation();
29063
+ const payload = {
29064
+ id: row.id,
29065
+ row
29066
+ };
29067
+ eventBus.emit(`UI:${action.event}`, payload);
29068
+ };
29069
+ if (isLoading) {
29070
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") || "Loading\u2026" }) });
29071
+ }
29072
+ if (error) {
29073
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
29074
+ }
29075
+ if (data.length === 0) {
29076
+ const emptyNode = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") || "No records" }) });
29077
+ return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
29078
+ }
29079
+ const lk = LOOKS[look];
29080
+ const hasActions = Boolean(itemActions && itemActions.length > 0);
29081
+ const gridTemplateColumns = [
29082
+ selectable ? "auto" : null,
29083
+ ...colDefs.map((c) => c.width ?? "minmax(0, 1fr)"),
29084
+ hasActions ? "auto" : null
29085
+ ].filter(Boolean).join(" ");
29086
+ const header = /* @__PURE__ */ jsxRuntime.jsxs(
29087
+ Box,
29088
+ {
29089
+ role: "row",
29090
+ style: { gridTemplateColumns },
29091
+ className: cn(
29092
+ "grid items-center gap-3 sticky top-0 z-10",
29093
+ "bg-[var(--color-surface-subtle)] border-b border-[var(--color-border)]",
29094
+ "text-[var(--color-text-muted)] uppercase text-xs font-semibold tracking-wide",
29095
+ lk.headPad
29096
+ ),
29097
+ children: [
29098
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { checked: allSelected, onChange: toggleAll, "aria-label": "Select all rows" }) }),
29099
+ colDefs.map((col) => {
29100
+ const active = sortColumn === (col.field ?? col.key);
29101
+ return /* @__PURE__ */ jsxRuntime.jsxs(
29102
+ Box,
29103
+ {
29104
+ role: "columnheader",
29105
+ onClick: () => handleSort(col),
29106
+ className: cn(
29107
+ "flex items-center gap-1 min-w-0",
29108
+ alignClass[col.align ?? "left"],
29109
+ col.sortable && sortEvent && "cursor-pointer select-none hover:text-foreground"
29110
+ ),
29111
+ children: [
29112
+ col.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: col.icon, size: "xs" }),
29113
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: columnLabel(col) }),
29114
+ col.sortable && sortEvent && /* @__PURE__ */ jsxRuntime.jsx(
29115
+ Icon,
29116
+ {
29117
+ name: active ? sortDirection === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
29118
+ size: "xs",
29119
+ className: cn("flex-shrink-0", active ? "text-foreground" : "opacity-40")
29120
+ }
29121
+ )
29122
+ ]
29123
+ },
29124
+ col.key
29125
+ );
29126
+ }),
29127
+ hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true })
29128
+ ]
29129
+ }
29130
+ );
29131
+ const renderRow = (row, index) => {
29132
+ const id = String(row[idField] ?? index);
29133
+ const rowInner = /* @__PURE__ */ jsxRuntime.jsxs(
29134
+ Box,
29135
+ {
29136
+ role: "row",
29137
+ "data-entity-row": true,
29138
+ "data-entity-id": id,
29139
+ style: !hasRenderProp ? { gridTemplateColumns } : void 0,
29140
+ className: cn(
29141
+ "group items-center gap-3 transition-colors duration-fast",
29142
+ hasRenderProp ? "flex" : "grid",
29143
+ lk.rowPad,
29144
+ lk.divider && "border-b border-[var(--color-border)]",
29145
+ lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
29146
+ "hover:bg-[var(--color-surface-subtle)]",
29147
+ look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
29148
+ ),
29149
+ children: [
29150
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
29151
+ Checkbox,
29152
+ {
29153
+ checked: selected.has(id),
29154
+ onChange: () => toggleRow(id),
29155
+ "aria-label": `Select row ${id}`
29156
+ }
29157
+ ) }),
29158
+ hasRenderProp ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 min-w-0", children: children(row, index) }) : colDefs.map((col) => {
29159
+ const raw = getNestedValue(row, col.field ?? col.key);
29160
+ const cellBase = cn(
29161
+ "flex items-center min-w-0",
29162
+ alignClass[col.align ?? "left"],
29163
+ weightClass[col.weight ?? "normal"],
29164
+ col.className
29165
+ );
29166
+ if (col.format === "badge" && raw != null && raw !== "") {
29167
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", children: String(raw) }) }, col.key);
29168
+ }
29169
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: formatCell(raw, col.format) }) }, col.key);
29170
+ }),
29171
+ itemActions && itemActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 opacity-60 group-hover:opacity-100 transition-opacity", children: itemActions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
29172
+ Button,
29173
+ {
29174
+ variant: action.variant ?? "ghost",
29175
+ size: "sm",
29176
+ onClick: handleActionClick(action, row),
29177
+ "data-testid": `action-${action.event}`,
29178
+ "data-row-id": String(row.id),
29179
+ className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
29180
+ children: [
29181
+ action.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
29182
+ action.label
29183
+ ]
29184
+ },
29185
+ i
29186
+ )) })
29187
+ ]
29188
+ }
29189
+ );
29190
+ return dnd.isZone ? /* @__PURE__ */ jsxRuntime.jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsxRuntime.jsx(React85__namespace.default.Fragment, { children: rowInner }, id);
29191
+ };
29192
+ const items = data.map((row) => row);
29193
+ const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
29194
+ let runningIndex = 0;
29195
+ const body = /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React85__namespace.default.Fragment, { children: [
29196
+ group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
29197
+ group.items.map((row) => renderRow(row, runningIndex++))
29198
+ ] }, gi)) });
29199
+ return /* @__PURE__ */ jsxRuntime.jsxs(
29200
+ Box,
29201
+ {
29202
+ role: "table",
29203
+ className: cn("w-full text-sm", className),
29204
+ children: [
29205
+ header,
29206
+ dnd.wrapContainer(body),
29207
+ hasMore && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p2) => p2 + (pageSize || 5)), children: [
29208
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
29209
+ t("common.showMore"),
29210
+ " (",
29211
+ ordered.length - visibleCount,
29212
+ " remaining)"
29213
+ ] }) })
29214
+ ]
29215
+ }
29216
+ );
29217
+ }
29218
+ var alignClass, weightClass, LOOKS;
29219
+ var init_TableView = __esm({
29220
+ "components/molecules/TableView.tsx"() {
29221
+ "use client";
29222
+ init_cn();
29223
+ init_getNestedValue();
29224
+ init_useEventBus();
29225
+ init_useTranslate();
29226
+ init_Box();
29227
+ init_Stack();
29228
+ init_Typography();
29229
+ init_Badge();
29230
+ init_Button();
29231
+ init_Icon();
29232
+ init_Checkbox();
29233
+ init_Divider();
29234
+ init_useDataDnd();
29235
+ logger.createLogger("almadar:ui:table-view");
29236
+ alignClass = {
29237
+ left: "justify-start text-left",
29238
+ center: "justify-center text-center",
29239
+ right: "justify-end text-right"
29240
+ };
29241
+ weightClass = {
29242
+ normal: "",
29243
+ medium: "font-medium",
29244
+ semibold: "font-semibold"
29245
+ };
29246
+ LOOKS = {
29247
+ dense: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true },
29248
+ spacious: { rowPad: "px-5 py-4", headPad: "px-5 py-3", striped: false, divider: true },
29249
+ striped: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: true, divider: false },
29250
+ borderless: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: false },
29251
+ bordered: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true }
29252
+ };
29253
+ TableView.displayName = "TableView";
29254
+ }
29255
+ });
28944
29256
  function formatNumber(value, format) {
28945
29257
  if (value == null) return "0";
28946
29258
  const v = typeof value === "number" ? value : value;
@@ -45879,6 +46191,7 @@ var init_component_registry_generated = __esm({
45879
46191
  init_Switch();
45880
46192
  init_TabbedContainer();
45881
46193
  init_Table();
46194
+ init_TableView();
45882
46195
  init_Tabs();
45883
46196
  init_TagCloud();
45884
46197
  init_TagInput();
@@ -46196,6 +46509,7 @@ var init_component_registry_generated = __esm({
46196
46509
  "Switch": Switch,
46197
46510
  "TabbedContainer": TabbedContainer,
46198
46511
  "Table": Table,
46512
+ "TableView": TableView,
46199
46513
  "Tabs": Tabs,
46200
46514
  "TagCloud": TagCloud,
46201
46515
  "TagInput": TagInput,
@@ -28892,6 +28892,318 @@ var init_Lightbox = __esm({
28892
28892
  Lightbox.displayName = "Lightbox";
28893
28893
  }
28894
28894
  });
28895
+ function columnLabel(col) {
28896
+ return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
28897
+ }
28898
+ function statusVariant4(value) {
28899
+ const v = value.toLowerCase();
28900
+ if (["active", "completed", "done", "approved", "published", "resolved", "open", "online", "ok"].includes(v)) return "success";
28901
+ if (["pending", "in_progress", "in-progress", "review", "draft", "processing", "warn", "warning"].includes(v)) return "warning";
28902
+ if (["inactive", "deleted", "rejected", "failed", "error", "blocked", "closed", "offline"].includes(v)) return "error";
28903
+ if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
28904
+ return "default";
28905
+ }
28906
+ function formatCell(value, format) {
28907
+ if (value === void 0 || value === null) return "";
28908
+ switch (format) {
28909
+ case "date": {
28910
+ const d = new Date(String(value));
28911
+ return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
28912
+ }
28913
+ case "currency":
28914
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
28915
+ case "number":
28916
+ return typeof value === "number" ? value.toLocaleString() : String(value);
28917
+ case "percent":
28918
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
28919
+ case "boolean":
28920
+ return value ? "Yes" : "No";
28921
+ default:
28922
+ return String(value);
28923
+ }
28924
+ }
28925
+ function groupData2(items, field) {
28926
+ const groups = /* @__PURE__ */ new Map();
28927
+ for (const item of items) {
28928
+ const key = String(getNestedValue(item, field) ?? "");
28929
+ const group = groups.get(key);
28930
+ if (group) group.push(item);
28931
+ else groups.set(key, [item]);
28932
+ }
28933
+ return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
28934
+ }
28935
+ function TableView({
28936
+ entity,
28937
+ columns,
28938
+ fields,
28939
+ itemActions,
28940
+ selectable = false,
28941
+ selectEvent,
28942
+ selectedIds,
28943
+ sortEvent,
28944
+ sortColumn,
28945
+ sortDirection,
28946
+ className,
28947
+ emptyMessage,
28948
+ isLoading = false,
28949
+ error = null,
28950
+ groupBy,
28951
+ pageSize = 0,
28952
+ children,
28953
+ renderItem: _schemaRenderItem,
28954
+ look = "dense",
28955
+ // DnD props consumed by useDataDnd.
28956
+ dragGroup,
28957
+ accepts,
28958
+ sortable,
28959
+ dropEvent,
28960
+ reorderEvent,
28961
+ positionEvent,
28962
+ dndItemIdField,
28963
+ dndRoot
28964
+ }) {
28965
+ const eventBus = useEventBus();
28966
+ const { t } = useTranslate();
28967
+ const [visibleCount, setVisibleCount] = React85__default.useState(pageSize > 0 ? pageSize : Infinity);
28968
+ const [localSelected, setLocalSelected] = React85__default.useState(/* @__PURE__ */ new Set());
28969
+ const colDefs = columns ?? fields ?? [];
28970
+ const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
28971
+ const dnd = useDataDnd({
28972
+ items: allDataRaw,
28973
+ layout: "list",
28974
+ dragGroup,
28975
+ accepts,
28976
+ sortable,
28977
+ dropEvent,
28978
+ reorderEvent,
28979
+ positionEvent,
28980
+ dndItemIdField,
28981
+ dndRoot
28982
+ });
28983
+ const ordered = dnd.orderedItems;
28984
+ const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
28985
+ const hasMore = pageSize > 0 && visibleCount < ordered.length;
28986
+ const hasRenderProp = typeof children === "function";
28987
+ const idField = dndItemIdField ?? "id";
28988
+ const selected = selectedIds ? new Set(selectedIds) : localSelected;
28989
+ const emitSelection = (next) => {
28990
+ if (!selectedIds) setLocalSelected(next);
28991
+ if (selectEvent) {
28992
+ const payload = { selectedIds: Array.from(next) };
28993
+ eventBus.emit(`UI:${selectEvent}`, payload);
28994
+ }
28995
+ };
28996
+ const allSelected = selectable && data.length > 0 && data.every((r, i) => selected.has(String(r[idField] ?? i)));
28997
+ const toggleAll = () => {
28998
+ if (allSelected) emitSelection(/* @__PURE__ */ new Set());
28999
+ else emitSelection(new Set(data.map((r, i) => String(r[idField] ?? i))));
29000
+ };
29001
+ const toggleRow = (id) => {
29002
+ const next = new Set(selected);
29003
+ if (next.has(id)) next.delete(id);
29004
+ else next.add(id);
29005
+ emitSelection(next);
29006
+ };
29007
+ const handleSort = (col) => {
29008
+ if (!col.sortable || !sortEvent) return;
29009
+ const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
29010
+ eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
29011
+ };
29012
+ const handleActionClick = (action, row) => (e) => {
29013
+ e.stopPropagation();
29014
+ const payload = {
29015
+ id: row.id,
29016
+ row
29017
+ };
29018
+ eventBus.emit(`UI:${action.event}`, payload);
29019
+ };
29020
+ if (isLoading) {
29021
+ return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") || "Loading\u2026" }) });
29022
+ }
29023
+ if (error) {
29024
+ return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
29025
+ }
29026
+ if (data.length === 0) {
29027
+ const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") || "No records" }) });
29028
+ return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
29029
+ }
29030
+ const lk = LOOKS[look];
29031
+ const hasActions = Boolean(itemActions && itemActions.length > 0);
29032
+ const gridTemplateColumns = [
29033
+ selectable ? "auto" : null,
29034
+ ...colDefs.map((c) => c.width ?? "minmax(0, 1fr)"),
29035
+ hasActions ? "auto" : null
29036
+ ].filter(Boolean).join(" ");
29037
+ const header = /* @__PURE__ */ jsxs(
29038
+ Box,
29039
+ {
29040
+ role: "row",
29041
+ style: { gridTemplateColumns },
29042
+ className: cn(
29043
+ "grid items-center gap-3 sticky top-0 z-10",
29044
+ "bg-[var(--color-surface-subtle)] border-b border-[var(--color-border)]",
29045
+ "text-[var(--color-text-muted)] uppercase text-xs font-semibold tracking-wide",
29046
+ lk.headPad
29047
+ ),
29048
+ children: [
29049
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsx(Checkbox, { checked: allSelected, onChange: toggleAll, "aria-label": "Select all rows" }) }),
29050
+ colDefs.map((col) => {
29051
+ const active = sortColumn === (col.field ?? col.key);
29052
+ return /* @__PURE__ */ jsxs(
29053
+ Box,
29054
+ {
29055
+ role: "columnheader",
29056
+ onClick: () => handleSort(col),
29057
+ className: cn(
29058
+ "flex items-center gap-1 min-w-0",
29059
+ alignClass[col.align ?? "left"],
29060
+ col.sortable && sortEvent && "cursor-pointer select-none hover:text-foreground"
29061
+ ),
29062
+ children: [
29063
+ col.icon && /* @__PURE__ */ jsx(Icon, { name: col.icon, size: "xs" }),
29064
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: columnLabel(col) }),
29065
+ col.sortable && sortEvent && /* @__PURE__ */ jsx(
29066
+ Icon,
29067
+ {
29068
+ name: active ? sortDirection === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
29069
+ size: "xs",
29070
+ className: cn("flex-shrink-0", active ? "text-foreground" : "opacity-40")
29071
+ }
29072
+ )
29073
+ ]
29074
+ },
29075
+ col.key
29076
+ );
29077
+ }),
29078
+ hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true })
29079
+ ]
29080
+ }
29081
+ );
29082
+ const renderRow = (row, index) => {
29083
+ const id = String(row[idField] ?? index);
29084
+ const rowInner = /* @__PURE__ */ jsxs(
29085
+ Box,
29086
+ {
29087
+ role: "row",
29088
+ "data-entity-row": true,
29089
+ "data-entity-id": id,
29090
+ style: !hasRenderProp ? { gridTemplateColumns } : void 0,
29091
+ className: cn(
29092
+ "group items-center gap-3 transition-colors duration-fast",
29093
+ hasRenderProp ? "flex" : "grid",
29094
+ lk.rowPad,
29095
+ lk.divider && "border-b border-[var(--color-border)]",
29096
+ lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
29097
+ "hover:bg-[var(--color-surface-subtle)]",
29098
+ look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
29099
+ ),
29100
+ children: [
29101
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsx(
29102
+ Checkbox,
29103
+ {
29104
+ checked: selected.has(id),
29105
+ onChange: () => toggleRow(id),
29106
+ "aria-label": `Select row ${id}`
29107
+ }
29108
+ ) }),
29109
+ hasRenderProp ? /* @__PURE__ */ jsx(Box, { className: "flex-1 min-w-0", children: children(row, index) }) : colDefs.map((col) => {
29110
+ const raw = getNestedValue(row, col.field ?? col.key);
29111
+ const cellBase = cn(
29112
+ "flex items-center min-w-0",
29113
+ alignClass[col.align ?? "left"],
29114
+ weightClass[col.weight ?? "normal"],
29115
+ col.className
29116
+ );
29117
+ if (col.format === "badge" && raw != null && raw !== "") {
29118
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", children: String(raw) }) }, col.key);
29119
+ }
29120
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: formatCell(raw, col.format) }) }, col.key);
29121
+ }),
29122
+ itemActions && itemActions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 opacity-60 group-hover:opacity-100 transition-opacity", children: itemActions.map((action, i) => /* @__PURE__ */ jsxs(
29123
+ Button,
29124
+ {
29125
+ variant: action.variant ?? "ghost",
29126
+ size: "sm",
29127
+ onClick: handleActionClick(action, row),
29128
+ "data-testid": `action-${action.event}`,
29129
+ "data-row-id": String(row.id),
29130
+ className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
29131
+ children: [
29132
+ action.icon && /* @__PURE__ */ jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
29133
+ action.label
29134
+ ]
29135
+ },
29136
+ i
29137
+ )) })
29138
+ ]
29139
+ }
29140
+ );
29141
+ return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React85__default.Fragment, { children: rowInner }, id);
29142
+ };
29143
+ const items = data.map((row) => row);
29144
+ const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
29145
+ let runningIndex = 0;
29146
+ const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
29147
+ group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
29148
+ group.items.map((row) => renderRow(row, runningIndex++))
29149
+ ] }, gi)) });
29150
+ return /* @__PURE__ */ jsxs(
29151
+ Box,
29152
+ {
29153
+ role: "table",
29154
+ className: cn("w-full text-sm", className),
29155
+ children: [
29156
+ header,
29157
+ dnd.wrapContainer(body),
29158
+ hasMore && /* @__PURE__ */ jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p2) => p2 + (pageSize || 5)), children: [
29159
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
29160
+ t("common.showMore"),
29161
+ " (",
29162
+ ordered.length - visibleCount,
29163
+ " remaining)"
29164
+ ] }) })
29165
+ ]
29166
+ }
29167
+ );
29168
+ }
29169
+ var alignClass, weightClass, LOOKS;
29170
+ var init_TableView = __esm({
29171
+ "components/molecules/TableView.tsx"() {
29172
+ "use client";
29173
+ init_cn();
29174
+ init_getNestedValue();
29175
+ init_useEventBus();
29176
+ init_useTranslate();
29177
+ init_Box();
29178
+ init_Stack();
29179
+ init_Typography();
29180
+ init_Badge();
29181
+ init_Button();
29182
+ init_Icon();
29183
+ init_Checkbox();
29184
+ init_Divider();
29185
+ init_useDataDnd();
29186
+ createLogger("almadar:ui:table-view");
29187
+ alignClass = {
29188
+ left: "justify-start text-left",
29189
+ center: "justify-center text-center",
29190
+ right: "justify-end text-right"
29191
+ };
29192
+ weightClass = {
29193
+ normal: "",
29194
+ medium: "font-medium",
29195
+ semibold: "font-semibold"
29196
+ };
29197
+ LOOKS = {
29198
+ dense: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true },
29199
+ spacious: { rowPad: "px-5 py-4", headPad: "px-5 py-3", striped: false, divider: true },
29200
+ striped: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: true, divider: false },
29201
+ borderless: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: false },
29202
+ bordered: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true }
29203
+ };
29204
+ TableView.displayName = "TableView";
29205
+ }
29206
+ });
28895
29207
  function formatNumber(value, format) {
28896
29208
  if (value == null) return "0";
28897
29209
  const v = typeof value === "number" ? value : value;
@@ -45830,6 +46142,7 @@ var init_component_registry_generated = __esm({
45830
46142
  init_Switch();
45831
46143
  init_TabbedContainer();
45832
46144
  init_Table();
46145
+ init_TableView();
45833
46146
  init_Tabs();
45834
46147
  init_TagCloud();
45835
46148
  init_TagInput();
@@ -46147,6 +46460,7 @@ var init_component_registry_generated = __esm({
46147
46460
  "Switch": Switch,
46148
46461
  "TabbedContainer": TabbedContainer,
46149
46462
  "Table": Table,
46463
+ "TableView": TableView,
46150
46464
  "Tabs": Tabs,
46151
46465
  "TagCloud": TagCloud,
46152
46466
  "TagInput": TagInput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.8.1",
3
+ "version": "5.9.1",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [