@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.
@@ -32824,6 +32824,318 @@ var init_Lightbox = __esm({
32824
32824
  Lightbox.displayName = "Lightbox";
32825
32825
  }
32826
32826
  });
32827
+ function columnLabel(col) {
32828
+ return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
32829
+ }
32830
+ function statusVariant4(value) {
32831
+ const v = value.toLowerCase();
32832
+ if (["active", "completed", "done", "approved", "published", "resolved", "open", "online", "ok"].includes(v)) return "success";
32833
+ if (["pending", "in_progress", "in-progress", "review", "draft", "processing", "warn", "warning"].includes(v)) return "warning";
32834
+ if (["inactive", "deleted", "rejected", "failed", "error", "blocked", "closed", "offline"].includes(v)) return "error";
32835
+ if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
32836
+ return "default";
32837
+ }
32838
+ function formatCell(value, format) {
32839
+ if (value === void 0 || value === null) return "";
32840
+ switch (format) {
32841
+ case "date": {
32842
+ const d = new Date(String(value));
32843
+ return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
32844
+ }
32845
+ case "currency":
32846
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
32847
+ case "number":
32848
+ return typeof value === "number" ? value.toLocaleString() : String(value);
32849
+ case "percent":
32850
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
32851
+ case "boolean":
32852
+ return value ? "Yes" : "No";
32853
+ default:
32854
+ return String(value);
32855
+ }
32856
+ }
32857
+ function groupData2(items, field) {
32858
+ const groups = /* @__PURE__ */ new Map();
32859
+ for (const item of items) {
32860
+ const key = String(getNestedValue(item, field) ?? "");
32861
+ const group = groups.get(key);
32862
+ if (group) group.push(item);
32863
+ else groups.set(key, [item]);
32864
+ }
32865
+ return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
32866
+ }
32867
+ function TableView({
32868
+ entity,
32869
+ columns,
32870
+ fields,
32871
+ itemActions,
32872
+ selectable = false,
32873
+ selectEvent,
32874
+ selectedIds,
32875
+ sortEvent,
32876
+ sortColumn,
32877
+ sortDirection,
32878
+ className,
32879
+ emptyMessage,
32880
+ isLoading = false,
32881
+ error = null,
32882
+ groupBy,
32883
+ pageSize = 0,
32884
+ children,
32885
+ renderItem: _schemaRenderItem,
32886
+ look = "dense",
32887
+ // DnD props consumed by useDataDnd.
32888
+ dragGroup,
32889
+ accepts,
32890
+ sortable,
32891
+ dropEvent,
32892
+ reorderEvent,
32893
+ positionEvent,
32894
+ dndItemIdField,
32895
+ dndRoot
32896
+ }) {
32897
+ const eventBus = useEventBus();
32898
+ const { t } = useTranslate();
32899
+ const [visibleCount, setVisibleCount] = React98__namespace.default.useState(pageSize > 0 ? pageSize : Infinity);
32900
+ const [localSelected, setLocalSelected] = React98__namespace.default.useState(/* @__PURE__ */ new Set());
32901
+ const colDefs = columns ?? fields ?? [];
32902
+ const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
32903
+ const dnd = useDataDnd({
32904
+ items: allDataRaw,
32905
+ layout: "list",
32906
+ dragGroup,
32907
+ accepts,
32908
+ sortable,
32909
+ dropEvent,
32910
+ reorderEvent,
32911
+ positionEvent,
32912
+ dndItemIdField,
32913
+ dndRoot
32914
+ });
32915
+ const ordered = dnd.orderedItems;
32916
+ const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
32917
+ const hasMore = pageSize > 0 && visibleCount < ordered.length;
32918
+ const hasRenderProp = typeof children === "function";
32919
+ const idField = dndItemIdField ?? "id";
32920
+ const selected = selectedIds ? new Set(selectedIds) : localSelected;
32921
+ const emitSelection = (next) => {
32922
+ if (!selectedIds) setLocalSelected(next);
32923
+ if (selectEvent) {
32924
+ const payload = { selectedIds: Array.from(next) };
32925
+ eventBus.emit(`UI:${selectEvent}`, payload);
32926
+ }
32927
+ };
32928
+ const allSelected = selectable && data.length > 0 && data.every((r2, i) => selected.has(String(r2[idField] ?? i)));
32929
+ const toggleAll = () => {
32930
+ if (allSelected) emitSelection(/* @__PURE__ */ new Set());
32931
+ else emitSelection(new Set(data.map((r2, i) => String(r2[idField] ?? i))));
32932
+ };
32933
+ const toggleRow = (id) => {
32934
+ const next = new Set(selected);
32935
+ if (next.has(id)) next.delete(id);
32936
+ else next.add(id);
32937
+ emitSelection(next);
32938
+ };
32939
+ const handleSort = (col) => {
32940
+ if (!col.sortable || !sortEvent) return;
32941
+ const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
32942
+ eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
32943
+ };
32944
+ const handleActionClick = (action, row) => (e) => {
32945
+ e.stopPropagation();
32946
+ const payload = {
32947
+ id: row.id,
32948
+ row
32949
+ };
32950
+ eventBus.emit(`UI:${action.event}`, payload);
32951
+ };
32952
+ if (isLoading) {
32953
+ 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" }) });
32954
+ }
32955
+ if (error) {
32956
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
32957
+ }
32958
+ if (data.length === 0) {
32959
+ 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" }) });
32960
+ return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
32961
+ }
32962
+ const lk = LOOKS[look];
32963
+ const hasActions = Boolean(itemActions && itemActions.length > 0);
32964
+ const gridTemplateColumns = [
32965
+ selectable ? "auto" : null,
32966
+ ...colDefs.map((c) => c.width ?? "minmax(0, 1fr)"),
32967
+ hasActions ? "auto" : null
32968
+ ].filter(Boolean).join(" ");
32969
+ const header = /* @__PURE__ */ jsxRuntime.jsxs(
32970
+ Box,
32971
+ {
32972
+ role: "row",
32973
+ style: { gridTemplateColumns },
32974
+ className: cn(
32975
+ "grid items-center gap-3 sticky top-0 z-10",
32976
+ "bg-[var(--color-surface-subtle)] border-b border-[var(--color-border)]",
32977
+ "text-[var(--color-text-muted)] uppercase text-xs font-semibold tracking-wide",
32978
+ lk.headPad
32979
+ ),
32980
+ children: [
32981
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { checked: allSelected, onChange: toggleAll, "aria-label": "Select all rows" }) }),
32982
+ colDefs.map((col) => {
32983
+ const active = sortColumn === (col.field ?? col.key);
32984
+ return /* @__PURE__ */ jsxRuntime.jsxs(
32985
+ Box,
32986
+ {
32987
+ role: "columnheader",
32988
+ onClick: () => handleSort(col),
32989
+ className: cn(
32990
+ "flex items-center gap-1 min-w-0",
32991
+ alignClass[col.align ?? "left"],
32992
+ col.sortable && sortEvent && "cursor-pointer select-none hover:text-foreground"
32993
+ ),
32994
+ children: [
32995
+ col.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: col.icon, size: "xs" }),
32996
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: columnLabel(col) }),
32997
+ col.sortable && sortEvent && /* @__PURE__ */ jsxRuntime.jsx(
32998
+ Icon,
32999
+ {
33000
+ name: active ? sortDirection === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
33001
+ size: "xs",
33002
+ className: cn("flex-shrink-0", active ? "text-foreground" : "opacity-40")
33003
+ }
33004
+ )
33005
+ ]
33006
+ },
33007
+ col.key
33008
+ );
33009
+ }),
33010
+ hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true })
33011
+ ]
33012
+ }
33013
+ );
33014
+ const renderRow = (row, index) => {
33015
+ const id = String(row[idField] ?? index);
33016
+ const rowInner = /* @__PURE__ */ jsxRuntime.jsxs(
33017
+ Box,
33018
+ {
33019
+ role: "row",
33020
+ "data-entity-row": true,
33021
+ "data-entity-id": id,
33022
+ style: !hasRenderProp ? { gridTemplateColumns } : void 0,
33023
+ className: cn(
33024
+ "group items-center gap-3 transition-colors duration-fast",
33025
+ hasRenderProp ? "flex" : "grid",
33026
+ lk.rowPad,
33027
+ lk.divider && "border-b border-[var(--color-border)]",
33028
+ lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
33029
+ "hover:bg-[var(--color-surface-subtle)]",
33030
+ look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
33031
+ ),
33032
+ children: [
33033
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
33034
+ Checkbox,
33035
+ {
33036
+ checked: selected.has(id),
33037
+ onChange: () => toggleRow(id),
33038
+ "aria-label": `Select row ${id}`
33039
+ }
33040
+ ) }),
33041
+ hasRenderProp ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 min-w-0", children: children(row, index) }) : colDefs.map((col) => {
33042
+ const raw = getNestedValue(row, col.field ?? col.key);
33043
+ const cellBase = cn(
33044
+ "flex items-center min-w-0",
33045
+ alignClass[col.align ?? "left"],
33046
+ weightClass[col.weight ?? "normal"],
33047
+ col.className
33048
+ );
33049
+ if (col.format === "badge" && raw != null && raw !== "") {
33050
+ 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);
33051
+ }
33052
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: formatCell(raw, col.format) }) }, col.key);
33053
+ }),
33054
+ 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(
33055
+ Button,
33056
+ {
33057
+ variant: action.variant ?? "ghost",
33058
+ size: "sm",
33059
+ onClick: handleActionClick(action, row),
33060
+ "data-testid": `action-${action.event}`,
33061
+ "data-row-id": String(row.id),
33062
+ className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
33063
+ children: [
33064
+ action.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
33065
+ action.label
33066
+ ]
33067
+ },
33068
+ i
33069
+ )) })
33070
+ ]
33071
+ }
33072
+ );
33073
+ return dnd.isZone ? /* @__PURE__ */ jsxRuntime.jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsxRuntime.jsx(React98__namespace.default.Fragment, { children: rowInner }, id);
33074
+ };
33075
+ const items = data.map((row) => row);
33076
+ const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
33077
+ let runningIndex = 0;
33078
+ const body = /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React98__namespace.default.Fragment, { children: [
33079
+ group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
33080
+ group.items.map((row) => renderRow(row, runningIndex++))
33081
+ ] }, gi)) });
33082
+ return /* @__PURE__ */ jsxRuntime.jsxs(
33083
+ Box,
33084
+ {
33085
+ role: "table",
33086
+ className: cn("w-full text-sm", className),
33087
+ children: [
33088
+ header,
33089
+ dnd.wrapContainer(body),
33090
+ 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: [
33091
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
33092
+ t("common.showMore"),
33093
+ " (",
33094
+ ordered.length - visibleCount,
33095
+ " remaining)"
33096
+ ] }) })
33097
+ ]
33098
+ }
33099
+ );
33100
+ }
33101
+ var alignClass, weightClass, LOOKS;
33102
+ var init_TableView = __esm({
33103
+ "components/molecules/TableView.tsx"() {
33104
+ "use client";
33105
+ init_cn();
33106
+ init_getNestedValue();
33107
+ init_useEventBus();
33108
+ init_useTranslate();
33109
+ init_Box();
33110
+ init_Stack();
33111
+ init_Typography();
33112
+ init_Badge();
33113
+ init_Button();
33114
+ init_Icon();
33115
+ init_Checkbox();
33116
+ init_Divider();
33117
+ init_useDataDnd();
33118
+ logger.createLogger("almadar:ui:table-view");
33119
+ alignClass = {
33120
+ left: "justify-start text-left",
33121
+ center: "justify-center text-center",
33122
+ right: "justify-end text-right"
33123
+ };
33124
+ weightClass = {
33125
+ normal: "",
33126
+ medium: "font-medium",
33127
+ semibold: "font-semibold"
33128
+ };
33129
+ LOOKS = {
33130
+ dense: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true },
33131
+ spacious: { rowPad: "px-5 py-4", headPad: "px-5 py-3", striped: false, divider: true },
33132
+ striped: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: true, divider: false },
33133
+ borderless: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: false },
33134
+ bordered: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true }
33135
+ };
33136
+ TableView.displayName = "TableView";
33137
+ }
33138
+ });
32827
33139
  function formatNumber(value, format) {
32828
33140
  if (value == null) return "0";
32829
33141
  const v = typeof value === "number" ? value : value;
@@ -54986,6 +55298,7 @@ var init_component_registry_generated = __esm({
54986
55298
  init_Switch();
54987
55299
  init_TabbedContainer();
54988
55300
  init_Table();
55301
+ init_TableView();
54989
55302
  init_Tabs();
54990
55303
  init_TagCloud();
54991
55304
  init_TagInput();
@@ -55303,6 +55616,7 @@ var init_component_registry_generated = __esm({
55303
55616
  "Switch": Switch,
55304
55617
  "TabbedContainer": TabbedContainer,
55305
55618
  "Table": Table,
55619
+ "TableView": TableView,
55306
55620
  "Tabs": Tabs,
55307
55621
  "TagCloud": TagCloud,
55308
55622
  "TagInput": TagInput,
package/dist/avl/index.js CHANGED
@@ -32775,6 +32775,318 @@ var init_Lightbox = __esm({
32775
32775
  Lightbox.displayName = "Lightbox";
32776
32776
  }
32777
32777
  });
32778
+ function columnLabel(col) {
32779
+ return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
32780
+ }
32781
+ function statusVariant4(value) {
32782
+ const v = value.toLowerCase();
32783
+ if (["active", "completed", "done", "approved", "published", "resolved", "open", "online", "ok"].includes(v)) return "success";
32784
+ if (["pending", "in_progress", "in-progress", "review", "draft", "processing", "warn", "warning"].includes(v)) return "warning";
32785
+ if (["inactive", "deleted", "rejected", "failed", "error", "blocked", "closed", "offline"].includes(v)) return "error";
32786
+ if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
32787
+ return "default";
32788
+ }
32789
+ function formatCell(value, format) {
32790
+ if (value === void 0 || value === null) return "";
32791
+ switch (format) {
32792
+ case "date": {
32793
+ const d = new Date(String(value));
32794
+ return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
32795
+ }
32796
+ case "currency":
32797
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
32798
+ case "number":
32799
+ return typeof value === "number" ? value.toLocaleString() : String(value);
32800
+ case "percent":
32801
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
32802
+ case "boolean":
32803
+ return value ? "Yes" : "No";
32804
+ default:
32805
+ return String(value);
32806
+ }
32807
+ }
32808
+ function groupData2(items, field) {
32809
+ const groups = /* @__PURE__ */ new Map();
32810
+ for (const item of items) {
32811
+ const key = String(getNestedValue(item, field) ?? "");
32812
+ const group = groups.get(key);
32813
+ if (group) group.push(item);
32814
+ else groups.set(key, [item]);
32815
+ }
32816
+ return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
32817
+ }
32818
+ function TableView({
32819
+ entity,
32820
+ columns,
32821
+ fields,
32822
+ itemActions,
32823
+ selectable = false,
32824
+ selectEvent,
32825
+ selectedIds,
32826
+ sortEvent,
32827
+ sortColumn,
32828
+ sortDirection,
32829
+ className,
32830
+ emptyMessage,
32831
+ isLoading = false,
32832
+ error = null,
32833
+ groupBy,
32834
+ pageSize = 0,
32835
+ children,
32836
+ renderItem: _schemaRenderItem,
32837
+ look = "dense",
32838
+ // DnD props consumed by useDataDnd.
32839
+ dragGroup,
32840
+ accepts,
32841
+ sortable,
32842
+ dropEvent,
32843
+ reorderEvent,
32844
+ positionEvent,
32845
+ dndItemIdField,
32846
+ dndRoot
32847
+ }) {
32848
+ const eventBus = useEventBus();
32849
+ const { t } = useTranslate();
32850
+ const [visibleCount, setVisibleCount] = React98__default.useState(pageSize > 0 ? pageSize : Infinity);
32851
+ const [localSelected, setLocalSelected] = React98__default.useState(/* @__PURE__ */ new Set());
32852
+ const colDefs = columns ?? fields ?? [];
32853
+ const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
32854
+ const dnd = useDataDnd({
32855
+ items: allDataRaw,
32856
+ layout: "list",
32857
+ dragGroup,
32858
+ accepts,
32859
+ sortable,
32860
+ dropEvent,
32861
+ reorderEvent,
32862
+ positionEvent,
32863
+ dndItemIdField,
32864
+ dndRoot
32865
+ });
32866
+ const ordered = dnd.orderedItems;
32867
+ const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
32868
+ const hasMore = pageSize > 0 && visibleCount < ordered.length;
32869
+ const hasRenderProp = typeof children === "function";
32870
+ const idField = dndItemIdField ?? "id";
32871
+ const selected = selectedIds ? new Set(selectedIds) : localSelected;
32872
+ const emitSelection = (next) => {
32873
+ if (!selectedIds) setLocalSelected(next);
32874
+ if (selectEvent) {
32875
+ const payload = { selectedIds: Array.from(next) };
32876
+ eventBus.emit(`UI:${selectEvent}`, payload);
32877
+ }
32878
+ };
32879
+ const allSelected = selectable && data.length > 0 && data.every((r2, i) => selected.has(String(r2[idField] ?? i)));
32880
+ const toggleAll = () => {
32881
+ if (allSelected) emitSelection(/* @__PURE__ */ new Set());
32882
+ else emitSelection(new Set(data.map((r2, i) => String(r2[idField] ?? i))));
32883
+ };
32884
+ const toggleRow = (id) => {
32885
+ const next = new Set(selected);
32886
+ if (next.has(id)) next.delete(id);
32887
+ else next.add(id);
32888
+ emitSelection(next);
32889
+ };
32890
+ const handleSort = (col) => {
32891
+ if (!col.sortable || !sortEvent) return;
32892
+ const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
32893
+ eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
32894
+ };
32895
+ const handleActionClick = (action, row) => (e) => {
32896
+ e.stopPropagation();
32897
+ const payload = {
32898
+ id: row.id,
32899
+ row
32900
+ };
32901
+ eventBus.emit(`UI:${action.event}`, payload);
32902
+ };
32903
+ if (isLoading) {
32904
+ return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") || "Loading\u2026" }) });
32905
+ }
32906
+ if (error) {
32907
+ return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
32908
+ }
32909
+ if (data.length === 0) {
32910
+ 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" }) });
32911
+ return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
32912
+ }
32913
+ const lk = LOOKS[look];
32914
+ const hasActions = Boolean(itemActions && itemActions.length > 0);
32915
+ const gridTemplateColumns = [
32916
+ selectable ? "auto" : null,
32917
+ ...colDefs.map((c) => c.width ?? "minmax(0, 1fr)"),
32918
+ hasActions ? "auto" : null
32919
+ ].filter(Boolean).join(" ");
32920
+ const header = /* @__PURE__ */ jsxs(
32921
+ Box,
32922
+ {
32923
+ role: "row",
32924
+ style: { gridTemplateColumns },
32925
+ className: cn(
32926
+ "grid items-center gap-3 sticky top-0 z-10",
32927
+ "bg-[var(--color-surface-subtle)] border-b border-[var(--color-border)]",
32928
+ "text-[var(--color-text-muted)] uppercase text-xs font-semibold tracking-wide",
32929
+ lk.headPad
32930
+ ),
32931
+ children: [
32932
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsx(Checkbox, { checked: allSelected, onChange: toggleAll, "aria-label": "Select all rows" }) }),
32933
+ colDefs.map((col) => {
32934
+ const active = sortColumn === (col.field ?? col.key);
32935
+ return /* @__PURE__ */ jsxs(
32936
+ Box,
32937
+ {
32938
+ role: "columnheader",
32939
+ onClick: () => handleSort(col),
32940
+ className: cn(
32941
+ "flex items-center gap-1 min-w-0",
32942
+ alignClass[col.align ?? "left"],
32943
+ col.sortable && sortEvent && "cursor-pointer select-none hover:text-foreground"
32944
+ ),
32945
+ children: [
32946
+ col.icon && /* @__PURE__ */ jsx(Icon, { name: col.icon, size: "xs" }),
32947
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: columnLabel(col) }),
32948
+ col.sortable && sortEvent && /* @__PURE__ */ jsx(
32949
+ Icon,
32950
+ {
32951
+ name: active ? sortDirection === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
32952
+ size: "xs",
32953
+ className: cn("flex-shrink-0", active ? "text-foreground" : "opacity-40")
32954
+ }
32955
+ )
32956
+ ]
32957
+ },
32958
+ col.key
32959
+ );
32960
+ }),
32961
+ hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true })
32962
+ ]
32963
+ }
32964
+ );
32965
+ const renderRow = (row, index) => {
32966
+ const id = String(row[idField] ?? index);
32967
+ const rowInner = /* @__PURE__ */ jsxs(
32968
+ Box,
32969
+ {
32970
+ role: "row",
32971
+ "data-entity-row": true,
32972
+ "data-entity-id": id,
32973
+ style: !hasRenderProp ? { gridTemplateColumns } : void 0,
32974
+ className: cn(
32975
+ "group items-center gap-3 transition-colors duration-fast",
32976
+ hasRenderProp ? "flex" : "grid",
32977
+ lk.rowPad,
32978
+ lk.divider && "border-b border-[var(--color-border)]",
32979
+ lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
32980
+ "hover:bg-[var(--color-surface-subtle)]",
32981
+ look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
32982
+ ),
32983
+ children: [
32984
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsx(
32985
+ Checkbox,
32986
+ {
32987
+ checked: selected.has(id),
32988
+ onChange: () => toggleRow(id),
32989
+ "aria-label": `Select row ${id}`
32990
+ }
32991
+ ) }),
32992
+ hasRenderProp ? /* @__PURE__ */ jsx(Box, { className: "flex-1 min-w-0", children: children(row, index) }) : colDefs.map((col) => {
32993
+ const raw = getNestedValue(row, col.field ?? col.key);
32994
+ const cellBase = cn(
32995
+ "flex items-center min-w-0",
32996
+ alignClass[col.align ?? "left"],
32997
+ weightClass[col.weight ?? "normal"],
32998
+ col.className
32999
+ );
33000
+ if (col.format === "badge" && raw != null && raw !== "") {
33001
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", children: String(raw) }) }, col.key);
33002
+ }
33003
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: formatCell(raw, col.format) }) }, col.key);
33004
+ }),
33005
+ 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(
33006
+ Button,
33007
+ {
33008
+ variant: action.variant ?? "ghost",
33009
+ size: "sm",
33010
+ onClick: handleActionClick(action, row),
33011
+ "data-testid": `action-${action.event}`,
33012
+ "data-row-id": String(row.id),
33013
+ className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
33014
+ children: [
33015
+ action.icon && /* @__PURE__ */ jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
33016
+ action.label
33017
+ ]
33018
+ },
33019
+ i
33020
+ )) })
33021
+ ]
33022
+ }
33023
+ );
33024
+ return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React98__default.Fragment, { children: rowInner }, id);
33025
+ };
33026
+ const items = data.map((row) => row);
33027
+ const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
33028
+ let runningIndex = 0;
33029
+ const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React98__default.Fragment, { children: [
33030
+ group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
33031
+ group.items.map((row) => renderRow(row, runningIndex++))
33032
+ ] }, gi)) });
33033
+ return /* @__PURE__ */ jsxs(
33034
+ Box,
33035
+ {
33036
+ role: "table",
33037
+ className: cn("w-full text-sm", className),
33038
+ children: [
33039
+ header,
33040
+ dnd.wrapContainer(body),
33041
+ 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: [
33042
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
33043
+ t("common.showMore"),
33044
+ " (",
33045
+ ordered.length - visibleCount,
33046
+ " remaining)"
33047
+ ] }) })
33048
+ ]
33049
+ }
33050
+ );
33051
+ }
33052
+ var alignClass, weightClass, LOOKS;
33053
+ var init_TableView = __esm({
33054
+ "components/molecules/TableView.tsx"() {
33055
+ "use client";
33056
+ init_cn();
33057
+ init_getNestedValue();
33058
+ init_useEventBus();
33059
+ init_useTranslate();
33060
+ init_Box();
33061
+ init_Stack();
33062
+ init_Typography();
33063
+ init_Badge();
33064
+ init_Button();
33065
+ init_Icon();
33066
+ init_Checkbox();
33067
+ init_Divider();
33068
+ init_useDataDnd();
33069
+ createLogger("almadar:ui:table-view");
33070
+ alignClass = {
33071
+ left: "justify-start text-left",
33072
+ center: "justify-center text-center",
33073
+ right: "justify-end text-right"
33074
+ };
33075
+ weightClass = {
33076
+ normal: "",
33077
+ medium: "font-medium",
33078
+ semibold: "font-semibold"
33079
+ };
33080
+ LOOKS = {
33081
+ dense: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true },
33082
+ spacious: { rowPad: "px-5 py-4", headPad: "px-5 py-3", striped: false, divider: true },
33083
+ striped: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: true, divider: false },
33084
+ borderless: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: false },
33085
+ bordered: { rowPad: "px-3 py-2", headPad: "px-3 py-2", striped: false, divider: true }
33086
+ };
33087
+ TableView.displayName = "TableView";
33088
+ }
33089
+ });
32778
33090
  function formatNumber(value, format) {
32779
33091
  if (value == null) return "0";
32780
33092
  const v = typeof value === "number" ? value : value;
@@ -54937,6 +55249,7 @@ var init_component_registry_generated = __esm({
54937
55249
  init_Switch();
54938
55250
  init_TabbedContainer();
54939
55251
  init_Table();
55252
+ init_TableView();
54940
55253
  init_Tabs();
54941
55254
  init_TagCloud();
54942
55255
  init_TagInput();
@@ -55254,6 +55567,7 @@ var init_component_registry_generated = __esm({
55254
55567
  "Switch": Switch,
55255
55568
  "TabbedContainer": TabbedContainer,
55256
55569
  "Table": Table,
55570
+ "TableView": TableView,
55257
55571
  "Tabs": Tabs,
55258
55572
  "TagCloud": TagCloud,
55259
55573
  "TagInput": TagInput,
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  export type GraphicAnimation = 'draw' | 'fill' | 'pulse' | 'morph';
3
3
  export interface AnimatedGraphicProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ /** Additional CSS classes applied to the root element. */
5
+ className?: string;
4
6
  /** URL to an SVG file. Fetched and inlined to enable stroke/fill animations. */
5
7
  src?: string;
6
8
  /** Inline SVG string. Takes precedence over src if both provided. */