@asteby/metacore-runtime-react 14.0.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 16.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5f864d9: Make declarative dynamic-table option badges and relation chips feel alive.
8
+
9
+ Options/select/status badges that ship without an explicit `color` from the
10
+ backend now get a deterministic, cohesive color derived from the option value
11
+ (fallback label) instead of rendering as dead gray text. Same value always maps
12
+ to the same hue, and equal words share a color.
13
+ - `@asteby/metacore-ui/lib` adds `optionColor(key)` (curated 16-hue Tailwind-500
14
+ palette, FNV-1a hash, light/dark safe), plus `optionColorBadgeStyles`,
15
+ `relationChipStyles`, and the exported `OPTION_PALETTE`.
16
+ - `OptionBadge` uses the explicit `color` when present, otherwise derives one via
17
+ `optionColor`, and renders the option's lucide `icon` before the label.
18
+ - `RelationCell` (resolved FK chips for category/brand/supplier/…) now gets a
19
+ subtle deterministic color keyed on the related label — kept lighter than enum
20
+ badges (soft tint, no heavy fill) so the two remain distinguishable.
21
+
22
+ All colors come from hex-derived inline styles, so they render correctly
23
+ regardless of the host's tailwind safelist.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [5f864d9]
28
+ - @asteby/metacore-ui@2.4.0
29
+
30
+ ## 15.0.0
31
+
32
+ ### Minor Changes
33
+
34
+ - ab41d75: Make declarative dynamic-table option badges and relation chips feel alive.
35
+
36
+ Options/select/status badges that ship without an explicit `color` from the
37
+ backend now get a deterministic, cohesive color derived from the option value
38
+ (fallback label) instead of rendering as dead gray text. Same value always maps
39
+ to the same hue, and equal words share a color.
40
+ - `@asteby/metacore-ui/lib` adds `optionColor(key)` (curated 16-hue Tailwind-500
41
+ palette, FNV-1a hash, light/dark safe), plus `optionColorBadgeStyles`,
42
+ `relationChipStyles`, and the exported `OPTION_PALETTE`.
43
+ - `OptionBadge` uses the explicit `color` when present, otherwise derives one via
44
+ `optionColor`, and renders the option's lucide `icon` before the label.
45
+ - `RelationCell` (resolved FK chips for category/brand/supplier/…) now gets a
46
+ subtle deterministic color keyed on the related label — kept lighter than enum
47
+ badges (soft tint, no heavy fill) so the two remain distinguishable.
48
+
49
+ All colors come from hex-derived inline styles, so they render correctly
50
+ regardless of the host's tailwind safelist.
51
+
52
+ ### Patch Changes
53
+
54
+ - Updated dependencies [ab41d75]
55
+ - @asteby/metacore-ui@2.3.0
56
+
3
57
  ## 14.0.0
4
58
 
5
59
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAyCA,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;AAgGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAmHD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAStE,CAAA;AA0DD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAgmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AA8CA,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;AAgGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAwHD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAStE,CAAA;AAiED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAgmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -19,7 +19,7 @@ import * as icons from 'lucide-react';
19
19
  import { MoreHorizontal } from 'lucide-react';
20
20
  import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@asteby/metacore-ui';
21
21
  import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
22
- import { generateBadgeStyles, getInitials } from '@asteby/metacore-ui/lib';
22
+ import { generateBadgeStyles, getInitials, optionColor, relationChipStyles, } from '@asteby/metacore-ui/lib';
23
23
  import { Progress } from './dialogs/_primitives';
24
24
  import { OptionsContext } from './options-context';
25
25
  import { DynamicIcon } from './dynamic-icon';
@@ -169,7 +169,12 @@ const renderRelationBadges = (items, col) => {
169
169
  };
170
170
  const OptionBadge = ({ option }) => {
171
171
  const isDark = useIsDarkTheme();
172
- const colorStyles = option.color ? generateBadgeStyles(option.color, { isDark }) : {};
172
+ // Explicit backend color wins; otherwise derive a stable, cohesive color
173
+ // from the option's value (fallback label) so "dead" gray badges come
174
+ // alive. Inline style (hex-derived) so it works regardless of the host's
175
+ // tailwind safelist — addon-arbitrary classes aren't in the host scan.
176
+ const colorSource = option.color || optionColor(option.value || option.label);
177
+ const colorStyles = generateBadgeStyles(colorSource, { isDark });
173
178
  return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" }), _jsx("span", { children: option.label })] }));
174
179
  };
175
180
  const BadgeWithEndpointOptions = ({ endpoint, value }) => {
@@ -218,10 +223,16 @@ export const resolveRelationLabel = (col, row) => {
218
223
  * `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
219
224
  */
220
225
  const RelationCell = ({ col, row }) => {
226
+ const isDark = useIsDarkTheme();
221
227
  const display = resolveRelationLabel(col, row);
222
228
  if (!display)
223
229
  return _jsx(EmptyCell, {});
224
- return (_jsx("span", { className: "inline-flex max-w-[220px] items-center truncate rounded-md bg-muted px-2 py-0.5 text-sm font-medium text-foreground/80", title: display, children: _jsx("span", { className: "truncate", children: display }) }));
230
+ // Deterministic, SUBTLE color keyed on the resolved label lighter than
231
+ // enum badges (soft tint, no heavy fill) so category/brand chips read as
232
+ // alive yet stay visually distinct from option/status badges. Inline style
233
+ // (hex-derived) bypasses the host tailwind safelist.
234
+ const chipStyles = relationChipStyles(display, { isDark });
235
+ return (_jsx("span", { className: "inline-flex max-w-[220px] items-center truncate rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: display, children: _jsx("span", { className: "truncate", children: display }) }));
225
236
  };
226
237
  /**
227
238
  * Generic avatar-style cell: round/rounded photo (or initials fallback) +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "14.0.0",
3
+ "version": "16.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "date-fns": ">=3",
35
35
  "react-day-picker": ">=8",
36
36
  "@asteby/metacore-sdk": "^3.1.0",
37
- "@asteby/metacore-ui": "^2.2.0"
37
+ "@asteby/metacore-ui": "^2.4.0"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@tanstack/react-router": {
@@ -62,7 +62,7 @@
62
62
  "vitest": "^4.0.0",
63
63
  "zustand": "^5.0.0",
64
64
  "@asteby/metacore-sdk": "3.1.0",
65
- "@asteby/metacore-ui": "2.2.0"
65
+ "@asteby/metacore-ui": "2.4.0"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,61 @@
1
+ // Determinism + spread coverage for the curated option-color palette that
2
+ // makes option/relation badges "alive" instead of dead gray. These utilities
3
+ // live in @asteby/metacore-ui/lib and are consumed by the OptionBadge and
4
+ // RelationCell renderers in dynamic-columns.tsx.
5
+ import { describe, it, expect } from 'vitest'
6
+ import { optionColor, OPTION_PALETTE } from '@asteby/metacore-ui/lib'
7
+
8
+ describe('optionColor', () => {
9
+ it('is deterministic — same input always yields the same color', () => {
10
+ const a = optionColor('storable')
11
+ const b = optionColor('storable')
12
+ expect(a).toBe(b)
13
+ // Stable across many repeats (no Math.random / time dependence).
14
+ for (let i = 0; i < 50; i++) {
15
+ expect(optionColor('in_progress')).toBe(optionColor('in_progress'))
16
+ }
17
+ })
18
+
19
+ it('normalizes case/whitespace so equal words collapse to one color', () => {
20
+ expect(optionColor('Active')).toBe(optionColor('active'))
21
+ expect(optionColor(' active ')).toBe(optionColor('active'))
22
+ })
23
+
24
+ it('always returns a color from the curated palette', () => {
25
+ const keys = [
26
+ 'storable', 'consumable', 'service', 'active', 'inactive', 'pending',
27
+ 'frenos', 'llantas', 'suspension', 'aceite', 'filtros', 'baterias',
28
+ 'uuid-1234', 'category', 'brand', 'supplier', 'warehouse', '',
29
+ ]
30
+ for (const key of keys) {
31
+ expect(OPTION_PALETTE).toContain(optionColor(key))
32
+ }
33
+ })
34
+
35
+ it('returns a 6-digit hex with no leading #', () => {
36
+ expect(optionColor('anything')).toMatch(/^[0-9a-f]{6}$/)
37
+ })
38
+
39
+ it('falls back to a stable palette color for empty/nullish input', () => {
40
+ expect(optionColor('')).toBe(OPTION_PALETTE[0])
41
+ // @ts-expect-error exercising the nullish guard
42
+ expect(optionColor(undefined)).toBe(OPTION_PALETTE[0])
43
+ })
44
+
45
+ it('spreads distinct inputs across the palette (not one color for all)', () => {
46
+ const inputs = Array.from({ length: 200 }, (_, i) => `option_${i}`)
47
+ const used = new Set(inputs.map(optionColor))
48
+ // With 16 hues and 200 distinct keys we expect broad coverage; require
49
+ // at least half the palette to be exercised so a degenerate hash that
50
+ // collapses everything is caught.
51
+ expect(used.size).toBeGreaterThanOrEqual(OPTION_PALETTE.length / 2)
52
+ })
53
+
54
+ it('different similar inputs do not all collide to one hue', () => {
55
+ const c1 = optionColor('red')
56
+ const c2 = optionColor('blue')
57
+ const c3 = optionColor('green')
58
+ const distinct = new Set([c1, c2, c3])
59
+ expect(distinct.size).toBeGreaterThan(1)
60
+ })
61
+ })
@@ -35,7 +35,12 @@ import {
35
35
  FilterableColumnHeader,
36
36
  type ColumnFilterMeta,
37
37
  } from '@asteby/metacore-ui/data-table'
38
- import { generateBadgeStyles, getInitials } from '@asteby/metacore-ui/lib'
38
+ import {
39
+ generateBadgeStyles,
40
+ getInitials,
41
+ optionColor,
42
+ relationChipStyles,
43
+ } from '@asteby/metacore-ui/lib'
39
44
  import { Progress } from './dialogs/_primitives'
40
45
  import { OptionsContext } from './options-context'
41
46
  import { DynamicIcon } from './dynamic-icon'
@@ -272,7 +277,12 @@ interface OptionBadgeProps {
272
277
 
273
278
  const OptionBadge: React.FC<OptionBadgeProps> = ({ option }) => {
274
279
  const isDark = useIsDarkTheme()
275
- const colorStyles = option.color ? generateBadgeStyles(option.color, { isDark }) : {}
280
+ // Explicit backend color wins; otherwise derive a stable, cohesive color
281
+ // from the option's value (fallback label) so "dead" gray badges come
282
+ // alive. Inline style (hex-derived) so it works regardless of the host's
283
+ // tailwind safelist — addon-arbitrary classes aren't in the host scan.
284
+ const colorSource = option.color || optionColor(option.value || option.label)
285
+ const colorStyles = generateBadgeStyles(colorSource, { isDark })
276
286
  return (
277
287
  <Badge variant="outline" className="flex items-center gap-1 border-0" style={colorStyles}>
278
288
  {option.icon && <DynamicIcon name={option.icon} className="h-3.5 w-3.5" />}
@@ -329,11 +339,18 @@ export const resolveRelationLabel = (col: ColumnDefinition, row: any): string =>
329
339
  * `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
330
340
  */
331
341
  const RelationCell: React.FC<{ col: ColumnDefinition; row: any }> = ({ col, row }) => {
342
+ const isDark = useIsDarkTheme()
332
343
  const display = resolveRelationLabel(col, row)
333
344
  if (!display) return <EmptyCell />
345
+ // Deterministic, SUBTLE color keyed on the resolved label — lighter than
346
+ // enum badges (soft tint, no heavy fill) so category/brand chips read as
347
+ // alive yet stay visually distinct from option/status badges. Inline style
348
+ // (hex-derived) bypasses the host tailwind safelist.
349
+ const chipStyles = relationChipStyles(display, { isDark })
334
350
  return (
335
351
  <span
336
- className="inline-flex max-w-[220px] items-center truncate rounded-md bg-muted px-2 py-0.5 text-sm font-medium text-foreground/80"
352
+ className="inline-flex max-w-[220px] items-center truncate rounded-md px-2 py-0.5 text-sm font-medium"
353
+ style={chipStyles}
337
354
  title={display}
338
355
  >
339
356
  <span className="truncate">{display}</span>