@asteby/metacore-runtime-react 24.0.1 → 24.0.2
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/CHANGELOG.md +14 -0
- package/dist/collection-cell.d.ts.map +1 -1
- package/dist/collection-cell.js +11 -4
- package/dist/display-value.d.ts +9 -0
- package/dist/display-value.d.ts.map +1 -1
- package/dist/display-value.js +1 -1
- package/dist/dynamic-columns.d.ts +9 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +23 -2
- package/package.json +5 -5
- package/src/collection-cell.tsx +25 -4
- package/src/display-value.tsx +22 -2
- package/src/dynamic-columns.tsx +32 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 24.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a27d1a2: feat(relations): secondary identifier (SKU/email) under a reference chip
|
|
8
|
+
|
|
9
|
+
Reference chips (resolved FK cells, endpoint-option badges, jsonb line-item refs,
|
|
10
|
+
confirm-modal items) now render a muted secondary line under the label — a
|
|
11
|
+
product's SKU, a user's email — so a resolved record reads "Name / SKU" instead
|
|
12
|
+
of a bare name. Declarative: the SDK reads the backend-projected `subtitle` /
|
|
13
|
+
`description` on the option or resolved sibling (never a hardcoded column). Absent
|
|
14
|
+
→ single-line as before. The select picker already surfaced `description`; this
|
|
15
|
+
brings the resolved-chip surfaces in line.
|
|
16
|
+
|
|
3
17
|
## 24.0.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;
|
|
1
|
+
{"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAwOD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAkOD,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,CAAC,CAAC,EAAE,SAAS,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,EACV,WAAW,EACX,OAAiB,GACpB,EAAE,mBAAmB,qBA2HrB"}
|
package/dist/collection-cell.js
CHANGED
|
@@ -55,12 +55,16 @@ function resolvedRefFor(field, row) {
|
|
|
55
55
|
const obj = sibling;
|
|
56
56
|
const label = obj.label;
|
|
57
57
|
if (label !== undefined && label !== null && label !== '') {
|
|
58
|
+
const sub = obj.subtitle ?? obj.description;
|
|
58
59
|
return {
|
|
59
60
|
label: String(label),
|
|
60
61
|
value: obj.value,
|
|
61
62
|
image: typeof obj.image === 'string' && obj.image !== ''
|
|
62
63
|
? obj.image
|
|
63
64
|
: undefined,
|
|
65
|
+
subtitle: sub !== undefined && sub !== null && sub !== ''
|
|
66
|
+
? String(sub)
|
|
67
|
+
: undefined,
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -92,10 +96,12 @@ function resolvedRefObject(v) {
|
|
|
92
96
|
const label = v.label;
|
|
93
97
|
if (label === undefined || label === null || label === '')
|
|
94
98
|
return null;
|
|
99
|
+
const sub = v.subtitle ?? v.description;
|
|
95
100
|
return {
|
|
96
101
|
label: String(label),
|
|
97
102
|
value: v.value,
|
|
98
103
|
image: typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
|
|
104
|
+
subtitle: sub !== undefined && sub !== null && sub !== '' ? String(sub) : undefined,
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
107
|
/**
|
|
@@ -104,9 +110,10 @@ function resolvedRefObject(v) {
|
|
|
104
110
|
* resolved name. The exact look the FK table columns use, so resolved relations
|
|
105
111
|
* read consistently everywhere (table popover, detail view, edit, line items).
|
|
106
112
|
*/
|
|
107
|
-
function RefChip({ label, image, getImageUrl, }) {
|
|
113
|
+
function RefChip({ label, image, subtitle, getImageUrl, }) {
|
|
108
114
|
const isDark = useIsDarkTheme();
|
|
109
|
-
|
|
115
|
+
const size = subtitle ? 24 : 18;
|
|
116
|
+
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", style: relationChipStyles(label, { isDark }), title: subtitle ? `${label} · ${subtitle}` : label, children: [image ? (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: size, height: size }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(image) : image, alt: label, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(label) })] })) : (_jsx(Box, { className: "h-3 w-3 shrink-0 opacity-70" })), subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight min-w-0", children: [_jsx("span", { className: "truncate", children: label }), _jsx("span", { className: "truncate text-[0.65rem] opacity-70", children: subtitle })] })) : (_jsx("span", { className: "truncate", children: label }))] }));
|
|
110
117
|
}
|
|
111
118
|
/**
|
|
112
119
|
* Visual cell for one declared item-field. A resolved `ref` renders as a
|
|
@@ -117,7 +124,7 @@ function RefChip({ label, image, getImageUrl, }) {
|
|
|
117
124
|
function ItemFieldCell({ field, row, getImageUrl, }) {
|
|
118
125
|
const ref = resolvedRefFor(field, row);
|
|
119
126
|
if (ref?.label) {
|
|
120
|
-
return (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl }));
|
|
127
|
+
return (_jsx(RefChip, { label: ref.label, image: ref.image, subtitle: ref.subtitle, getImageUrl: getImageUrl }));
|
|
121
128
|
}
|
|
122
129
|
return _jsx(_Fragment, { children: formatScalar(row[field.key]) });
|
|
123
130
|
}
|
|
@@ -311,7 +318,7 @@ function MiniTable({ rows, locale, t, itemFields, getImageUrl, }) {
|
|
|
311
318
|
const ref = refKeys.has(key)
|
|
312
319
|
? resolvedRefObject(row[key])
|
|
313
320
|
: null;
|
|
314
|
-
return (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: ref ? (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl })) : (formatScalar(row[key])) }, key));
|
|
321
|
+
return (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: ref ? (_jsx(RefChip, { label: ref.label, image: ref.image, subtitle: ref.subtitle, getImageUrl: getImageUrl })) : (formatScalar(row[key])) }, key));
|
|
315
322
|
}) }, i))) })] }));
|
|
316
323
|
}
|
|
317
324
|
function ScalarList({ values }) {
|
package/dist/display-value.d.ts
CHANGED
|
@@ -39,6 +39,15 @@ export interface DisplayOption {
|
|
|
39
39
|
icon?: string;
|
|
40
40
|
color?: string;
|
|
41
41
|
image?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Secondary identifier shown UNDER the label (muted, smaller) — a product's
|
|
44
|
+
* SKU/barcode, a user's email, etc. Lets a reference chip read like
|
|
45
|
+
* "Camiseta / SKU-001" instead of a bare name, so a resolved record is
|
|
46
|
+
* unambiguous. Populated declaratively: the backend projects it as the
|
|
47
|
+
* option/relation `description` (SearchConfig/FieldOptionsConfig.Description,
|
|
48
|
+
* or a column's `label_description`). Absent → single-line label as before.
|
|
49
|
+
*/
|
|
50
|
+
subtitle?: string;
|
|
42
51
|
}
|
|
43
52
|
/**
|
|
44
53
|
* Colored option badge — the canonical "pro" pill for a select/status/badge
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAcA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAcA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CA8BA,CAAA"}
|
package/dist/display-value.js
CHANGED
|
@@ -70,5 +70,5 @@ export const OptionBadge = ({ option, getImageUrl }) => {
|
|
|
70
70
|
const isDark = useIsDarkTheme();
|
|
71
71
|
const colorSource = option.color || optionColor(option.value || option.label);
|
|
72
72
|
const colorStyles = generateBadgeStyles(colorSource, { isDark });
|
|
73
|
-
return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.image ? (_jsx(RelationThumbnail, { src: option.image, alt: option.label, getImageUrl: getImageUrl, size: 16 })) : (option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" })), _jsx("span", { children: option.label })] }));
|
|
73
|
+
return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.image ? (_jsx(RelationThumbnail, { src: option.image, alt: option.label, getImageUrl: getImageUrl, size: option.subtitle ? 22 : 16 })) : (option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" })), option.subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight text-start", children: [_jsx("span", { children: option.label }), _jsx("span", { className: "text-[0.7rem] opacity-70", children: option.subtitle })] })) : (_jsx("span", { children: option.label }))] }));
|
|
74
74
|
};
|
|
@@ -112,6 +112,15 @@ export declare const resolveRelationLabel: (col: ColumnDefinition, row: any) =>
|
|
|
112
112
|
* the chip then renders text-only, exactly as before.
|
|
113
113
|
*/
|
|
114
114
|
export declare const resolveRelationImage: (col: ColumnDefinition, row: any) => string;
|
|
115
|
+
/**
|
|
116
|
+
* Reads a secondary identifier the backend stamps on a resolved FK sibling — a
|
|
117
|
+
* product's SKU, a user's email — projected as `subtitle`/`description` (the
|
|
118
|
+
* relational twin of `image` via the column's `label_description`). Rendered
|
|
119
|
+
* muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
|
|
120
|
+
* only the generic `subtitle`/`description` keys are read (never a hardcoded
|
|
121
|
+
* `sku`/`code`), so the author picks the column declaratively. '' when absent.
|
|
122
|
+
*/
|
|
123
|
+
export declare const resolveRelationSubtitle: (col: ColumnDefinition, row: any) => string;
|
|
115
124
|
/**
|
|
116
125
|
* Builds the canonical column factory used by `<DynamicTable>` when the host
|
|
117
126
|
* does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAuC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AAyCD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAiB5D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OACqB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAuC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AAyCD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAiB5D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OACqB,CAAA;AAwFhF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOzE,CAAA;AA8ID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAqmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -213,8 +213,11 @@ const BadgeWithEndpointOptions = ({ endpoint, value, getImageUrl }) => {
|
|
|
213
213
|
const { optionsMap } = React.useContext(OptionsContext);
|
|
214
214
|
const options = optionsMap.get(endpoint) || [];
|
|
215
215
|
const option = options.find((opt) => opt.value === value);
|
|
216
|
+
// Reference options carry the backend-projected `description` (the SKU/email
|
|
217
|
+
// the author pointed the options config at) — surface it as the chip subtitle
|
|
218
|
+
// so a resolved record reads "Name / SKU" instead of a bare name.
|
|
216
219
|
if (option)
|
|
217
|
-
return _jsx(OptionBadge, { option: option, fallback: String(value), getImageUrl: getImageUrl });
|
|
220
|
+
return _jsx(OptionBadge, { option: { ...option, subtitle: option.subtitle ?? option.description }, fallback: String(value), getImageUrl: getImageUrl });
|
|
218
221
|
// No declared option matched → humanize the raw token as a safety net so a
|
|
219
222
|
// cell never shows `in_progress` verbatim (option.label still wins above).
|
|
220
223
|
return _jsx(Badge, { variant: "outline", children: humanizeToken(value) });
|
|
@@ -336,6 +339,23 @@ export const resolveRelationImage = (col, row) => {
|
|
|
336
339
|
}
|
|
337
340
|
return '';
|
|
338
341
|
};
|
|
342
|
+
/**
|
|
343
|
+
* Reads a secondary identifier the backend stamps on a resolved FK sibling — a
|
|
344
|
+
* product's SKU, a user's email — projected as `subtitle`/`description` (the
|
|
345
|
+
* relational twin of `image` via the column's `label_description`). Rendered
|
|
346
|
+
* muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
|
|
347
|
+
* only the generic `subtitle`/`description` keys are read (never a hardcoded
|
|
348
|
+
* `sku`/`code`), so the author picks the column declaratively. '' when absent.
|
|
349
|
+
*/
|
|
350
|
+
export const resolveRelationSubtitle = (col, row) => {
|
|
351
|
+
const sibling = getNestedValue(row, relationKeyFor(col));
|
|
352
|
+
if (sibling && typeof sibling === 'object') {
|
|
353
|
+
const sub = sibling.subtitle ?? sibling.description;
|
|
354
|
+
if (sub !== undefined && sub !== null && sub !== '')
|
|
355
|
+
return String(sub);
|
|
356
|
+
}
|
|
357
|
+
return '';
|
|
358
|
+
};
|
|
339
359
|
/**
|
|
340
360
|
* Renders a resolved FK relation as a clean, truncated chip. Reads the
|
|
341
361
|
* backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
|
|
@@ -350,12 +370,13 @@ const RelationCell = ({ col, row, getImageUrl }) => {
|
|
|
350
370
|
if (!display)
|
|
351
371
|
return _jsx(EmptyCell, {});
|
|
352
372
|
const image = resolveRelationImage(col, row);
|
|
373
|
+
const subtitle = resolveRelationSubtitle(col, row);
|
|
353
374
|
// Deterministic, SUBTLE color keyed on the resolved label — lighter than
|
|
354
375
|
// enum badges (soft tint, no heavy fill) so category/brand chips read as
|
|
355
376
|
// alive yet stay visually distinct from option/status badges. Inline style
|
|
356
377
|
// (hex-derived) bypasses the host tailwind safelist.
|
|
357
378
|
const chipStyles = relationChipStyles(display, { isDark });
|
|
358
|
-
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: display, children: [image && (_jsx(RelationThumbnail, { src: image, alt: display, getImageUrl: getImageUrl, size: 18 })), _jsx("span", { className: "truncate", children: display })] }));
|
|
379
|
+
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: subtitle ? `${display} · ${subtitle}` : display, children: [image && (_jsx(RelationThumbnail, { src: image, alt: display, getImageUrl: getImageUrl, size: subtitle ? 24 : 18 })), subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight min-w-0", children: [_jsx("span", { className: "truncate", children: display }), _jsx("span", { className: "truncate text-[0.7rem] opacity-70", children: subtitle })] })) : (_jsx("span", { className: "truncate", children: display }))] }));
|
|
359
380
|
};
|
|
360
381
|
/**
|
|
361
382
|
* Renders a SAP-style polymorphic source-document reference as a navigable
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "24.0.
|
|
3
|
+
"version": "24.0.2",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"react-i18next": ">=13",
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
|
-
"@asteby/metacore-sdk": "^3.2.
|
|
41
|
-
"@asteby/metacore-ui": "^2.10.
|
|
40
|
+
"@asteby/metacore-sdk": "^3.2.1",
|
|
41
|
+
"@asteby/metacore-ui": "^2.10.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^6.0.0",
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
|
-
"@asteby/metacore-sdk": "3.2.
|
|
71
|
-
"@asteby/metacore-ui": "2.10.
|
|
70
|
+
"@asteby/metacore-sdk": "3.2.1",
|
|
71
|
+
"@asteby/metacore-ui": "2.10.1"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
package/src/collection-cell.tsx
CHANGED
|
@@ -92,6 +92,8 @@ interface ResolvedRef {
|
|
|
92
92
|
value?: unknown
|
|
93
93
|
/** Optional thumbnail (product photo, logo, avatar) resolved by the backend. */
|
|
94
94
|
image?: string
|
|
95
|
+
/** Optional secondary identifier (SKU/email) shown muted under the label. */
|
|
96
|
+
subtitle?: string
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/**
|
|
@@ -110,6 +112,7 @@ function resolvedRefFor(
|
|
|
110
112
|
const obj = sibling as Record<string, unknown>
|
|
111
113
|
const label = obj.label
|
|
112
114
|
if (label !== undefined && label !== null && label !== '') {
|
|
115
|
+
const sub = obj.subtitle ?? obj.description
|
|
113
116
|
return {
|
|
114
117
|
label: String(label),
|
|
115
118
|
value: obj.value,
|
|
@@ -117,6 +120,10 @@ function resolvedRefFor(
|
|
|
117
120
|
typeof obj.image === 'string' && obj.image !== ''
|
|
118
121
|
? obj.image
|
|
119
122
|
: undefined,
|
|
123
|
+
subtitle:
|
|
124
|
+
sub !== undefined && sub !== null && sub !== ''
|
|
125
|
+
? String(sub)
|
|
126
|
+
: undefined,
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
} else if (typeof sibling === 'string' && sibling !== '') {
|
|
@@ -148,11 +155,14 @@ function resolvedRefObject(
|
|
|
148
155
|
if (!isPlainObject(v)) return null
|
|
149
156
|
const label = v.label
|
|
150
157
|
if (label === undefined || label === null || label === '') return null
|
|
158
|
+
const sub = v.subtitle ?? v.description
|
|
151
159
|
return {
|
|
152
160
|
label: String(label),
|
|
153
161
|
value: v.value,
|
|
154
162
|
image:
|
|
155
163
|
typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
|
|
164
|
+
subtitle:
|
|
165
|
+
sub !== undefined && sub !== null && sub !== '' ? String(sub) : undefined,
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
|
|
@@ -165,23 +175,26 @@ function resolvedRefObject(
|
|
|
165
175
|
function RefChip({
|
|
166
176
|
label,
|
|
167
177
|
image,
|
|
178
|
+
subtitle,
|
|
168
179
|
getImageUrl,
|
|
169
180
|
}: {
|
|
170
181
|
label: string
|
|
171
182
|
image?: string
|
|
183
|
+
subtitle?: string
|
|
172
184
|
getImageUrl?: (path: string) => string
|
|
173
185
|
}): React.ReactElement {
|
|
174
186
|
const isDark = useIsDarkTheme()
|
|
187
|
+
const size = subtitle ? 24 : 18
|
|
175
188
|
return (
|
|
176
189
|
<span
|
|
177
190
|
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
|
|
178
191
|
style={relationChipStyles(label, { isDark })}
|
|
179
|
-
title={label}
|
|
192
|
+
title={subtitle ? `${label} · ${subtitle}` : label}
|
|
180
193
|
>
|
|
181
194
|
{image ? (
|
|
182
195
|
<Avatar
|
|
183
196
|
className="shrink-0 rounded-sm ring-1 ring-border/40"
|
|
184
|
-
style={{ width:
|
|
197
|
+
style={{ width: size, height: size }}
|
|
185
198
|
>
|
|
186
199
|
<AvatarImage
|
|
187
200
|
src={getImageUrl ? getImageUrl(image) : image}
|
|
@@ -195,7 +208,14 @@ function RefChip({
|
|
|
195
208
|
) : (
|
|
196
209
|
<Box className="h-3 w-3 shrink-0 opacity-70" />
|
|
197
210
|
)}
|
|
198
|
-
|
|
211
|
+
{subtitle ? (
|
|
212
|
+
<span className="flex flex-col leading-tight min-w-0">
|
|
213
|
+
<span className="truncate">{label}</span>
|
|
214
|
+
<span className="truncate text-[0.65rem] opacity-70">{subtitle}</span>
|
|
215
|
+
</span>
|
|
216
|
+
) : (
|
|
217
|
+
<span className="truncate">{label}</span>
|
|
218
|
+
)}
|
|
199
219
|
</span>
|
|
200
220
|
)
|
|
201
221
|
}
|
|
@@ -218,7 +238,7 @@ function ItemFieldCell({
|
|
|
218
238
|
const ref = resolvedRefFor(field, row)
|
|
219
239
|
if (ref?.label) {
|
|
220
240
|
return (
|
|
221
|
-
<RefChip label={ref.label} image={ref.image} getImageUrl={getImageUrl} />
|
|
241
|
+
<RefChip label={ref.label} image={ref.image} subtitle={ref.subtitle} getImageUrl={getImageUrl} />
|
|
222
242
|
)
|
|
223
243
|
}
|
|
224
244
|
return <>{formatScalar(row[field.key])}</>
|
|
@@ -495,6 +515,7 @@ function MiniTable({
|
|
|
495
515
|
<RefChip
|
|
496
516
|
label={ref.label}
|
|
497
517
|
image={ref.image}
|
|
518
|
+
subtitle={ref.subtitle}
|
|
498
519
|
getImageUrl={getImageUrl}
|
|
499
520
|
/>
|
|
500
521
|
) : (
|
package/src/display-value.tsx
CHANGED
|
@@ -98,6 +98,15 @@ export interface DisplayOption {
|
|
|
98
98
|
icon?: string
|
|
99
99
|
color?: string
|
|
100
100
|
image?: string
|
|
101
|
+
/**
|
|
102
|
+
* Secondary identifier shown UNDER the label (muted, smaller) — a product's
|
|
103
|
+
* SKU/barcode, a user's email, etc. Lets a reference chip read like
|
|
104
|
+
* "Camiseta / SKU-001" instead of a bare name, so a resolved record is
|
|
105
|
+
* unambiguous. Populated declaratively: the backend projects it as the
|
|
106
|
+
* option/relation `description` (SearchConfig/FieldOptionsConfig.Description,
|
|
107
|
+
* or a column's `label_description`). Absent → single-line label as before.
|
|
108
|
+
*/
|
|
109
|
+
subtitle?: string
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
/**
|
|
@@ -123,12 +132,23 @@ export const OptionBadge: React.FC<{
|
|
|
123
132
|
src={option.image}
|
|
124
133
|
alt={option.label}
|
|
125
134
|
getImageUrl={getImageUrl}
|
|
126
|
-
size={16}
|
|
135
|
+
size={option.subtitle ? 22 : 16}
|
|
127
136
|
/>
|
|
128
137
|
) : (
|
|
129
138
|
option.icon && <DynamicIcon name={option.icon} className="h-3.5 w-3.5" />
|
|
130
139
|
)}
|
|
131
|
-
|
|
140
|
+
{option.subtitle ? (
|
|
141
|
+
// Two-line identity: label on top, the secondary identifier
|
|
142
|
+
// (SKU/email/…) muted underneath. `leading-tight` keeps the pill
|
|
143
|
+
// compact; the subtitle inherits the badge's fg at reduced opacity
|
|
144
|
+
// so it reads as secondary on any color.
|
|
145
|
+
<span className="flex flex-col leading-tight text-start">
|
|
146
|
+
<span>{option.label}</span>
|
|
147
|
+
<span className="text-[0.7rem] opacity-70">{option.subtitle}</span>
|
|
148
|
+
</span>
|
|
149
|
+
) : (
|
|
150
|
+
<span>{option.label}</span>
|
|
151
|
+
)}
|
|
132
152
|
</Badge>
|
|
133
153
|
)
|
|
134
154
|
}
|
package/src/dynamic-columns.tsx
CHANGED
|
@@ -332,7 +332,10 @@ const BadgeWithEndpointOptions: React.FC<{
|
|
|
332
332
|
const { optionsMap } = React.useContext(OptionsContext)
|
|
333
333
|
const options = optionsMap.get(endpoint) || []
|
|
334
334
|
const option = options.find((opt: any) => opt.value === value)
|
|
335
|
-
|
|
335
|
+
// Reference options carry the backend-projected `description` (the SKU/email
|
|
336
|
+
// the author pointed the options config at) — surface it as the chip subtitle
|
|
337
|
+
// so a resolved record reads "Name / SKU" instead of a bare name.
|
|
338
|
+
if (option) return <OptionBadge option={{ ...option, subtitle: option.subtitle ?? (option as any).description }} fallback={String(value)} getImageUrl={getImageUrl} />
|
|
336
339
|
// No declared option matched → humanize the raw token as a safety net so a
|
|
337
340
|
// cell never shows `in_progress` verbatim (option.label still wins above).
|
|
338
341
|
return <Badge variant="outline">{humanizeToken(value)}</Badge>
|
|
@@ -461,6 +464,23 @@ export const resolveRelationImage = (col: ColumnDefinition, row: any): string =>
|
|
|
461
464
|
return ''
|
|
462
465
|
}
|
|
463
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Reads a secondary identifier the backend stamps on a resolved FK sibling — a
|
|
469
|
+
* product's SKU, a user's email — projected as `subtitle`/`description` (the
|
|
470
|
+
* relational twin of `image` via the column's `label_description`). Rendered
|
|
471
|
+
* muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
|
|
472
|
+
* only the generic `subtitle`/`description` keys are read (never a hardcoded
|
|
473
|
+
* `sku`/`code`), so the author picks the column declaratively. '' when absent.
|
|
474
|
+
*/
|
|
475
|
+
export const resolveRelationSubtitle = (col: ColumnDefinition, row: any): string => {
|
|
476
|
+
const sibling = getNestedValue(row, relationKeyFor(col))
|
|
477
|
+
if (sibling && typeof sibling === 'object') {
|
|
478
|
+
const sub = sibling.subtitle ?? sibling.description
|
|
479
|
+
if (sub !== undefined && sub !== null && sub !== '') return String(sub)
|
|
480
|
+
}
|
|
481
|
+
return ''
|
|
482
|
+
}
|
|
483
|
+
|
|
464
484
|
/**
|
|
465
485
|
* Renders a resolved FK relation as a clean, truncated chip. Reads the
|
|
466
486
|
* backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
|
|
@@ -478,6 +498,7 @@ const RelationCell: React.FC<{
|
|
|
478
498
|
const display = resolveRelationLabel(col, row)
|
|
479
499
|
if (!display) return <EmptyCell />
|
|
480
500
|
const image = resolveRelationImage(col, row)
|
|
501
|
+
const subtitle = resolveRelationSubtitle(col, row)
|
|
481
502
|
// Deterministic, SUBTLE color keyed on the resolved label — lighter than
|
|
482
503
|
// enum badges (soft tint, no heavy fill) so category/brand chips read as
|
|
483
504
|
// alive yet stay visually distinct from option/status badges. Inline style
|
|
@@ -487,12 +508,19 @@ const RelationCell: React.FC<{
|
|
|
487
508
|
<span
|
|
488
509
|
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium"
|
|
489
510
|
style={chipStyles}
|
|
490
|
-
title={display}
|
|
511
|
+
title={subtitle ? `${display} · ${subtitle}` : display}
|
|
491
512
|
>
|
|
492
513
|
{image && (
|
|
493
|
-
<RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={18} />
|
|
514
|
+
<RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={subtitle ? 24 : 18} />
|
|
515
|
+
)}
|
|
516
|
+
{subtitle ? (
|
|
517
|
+
<span className="flex flex-col leading-tight min-w-0">
|
|
518
|
+
<span className="truncate">{display}</span>
|
|
519
|
+
<span className="truncate text-[0.7rem] opacity-70">{subtitle}</span>
|
|
520
|
+
</span>
|
|
521
|
+
) : (
|
|
522
|
+
<span className="truncate">{display}</span>
|
|
494
523
|
)}
|
|
495
|
-
<span className="truncate">{display}</span>
|
|
496
524
|
</span>
|
|
497
525
|
)
|
|
498
526
|
}
|