@asteby/metacore-runtime-react 35.0.0 → 36.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,18 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 36.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6fb08b7]
8
+ - @asteby/metacore-ui@2.17.0
9
+
10
+ ## 35.1.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 431f813: Role chips: Lucide icon + modern HSL color picker (popover SV plane / hue / hex) in PermissionsManager; ColorPickerField also used for DynamicForm color fields.
15
+
3
16
  ## 35.0.0
4
17
 
5
18
  ### Minor Changes
@@ -0,0 +1,13 @@
1
+ export declare const DEFAULT_ROLE_COLOR = "#3b82f6";
2
+ export interface ColorPickerFieldProps {
3
+ value?: string;
4
+ onChange: (hex: string) => void;
5
+ /** Optional label for a11y on the trigger. */
6
+ 'aria-label'?: string;
7
+ className?: string;
8
+ disabled?: boolean;
9
+ }
10
+ /** Normalize to #rrggbb or '' if invalid. */
11
+ export declare function normalizeHex(raw: unknown): string;
12
+ export declare function ColorPickerField({ value, onChange, 'aria-label': ariaLabel, className, disabled, }: ColorPickerFieldProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=color-picker-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-picker-field.d.ts","sourceRoot":"","sources":["../src/color-picker-field.tsx"],"names":[],"mappings":"AAcA,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAE3C,MAAM,WAAW,qBAAqB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAQD,6CAA6C;AAC7C,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAajD;AAwED,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,YAAY,EAAE,SAAmB,EACjC,SAAS,EACT,QAAQ,GACX,EAAE,qBAAqB,+BA4KvB"}
@@ -0,0 +1,181 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ColorPickerField — modern popover color control (SV plane + hue + hex).
3
+ // Used by PermissionsManager role dialog and DynamicForm `type: "color"`.
4
+ // Value is always a #rrggbb string (empty → falls back to DEFAULT_HEX visually).
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { Pipette } from 'lucide-react';
7
+ import { cn } from '@asteby/metacore-ui/lib';
8
+ import { Button, Input, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui/primitives';
9
+ export const DEFAULT_ROLE_COLOR = '#3b82f6';
10
+ function clamp(n, min, max) {
11
+ return Math.min(max, Math.max(min, n));
12
+ }
13
+ /** Normalize to #rrggbb or '' if invalid. */
14
+ export function normalizeHex(raw) {
15
+ if (typeof raw !== 'string')
16
+ return '';
17
+ let s = raw.trim();
18
+ if (!s)
19
+ return '';
20
+ if (s[0] !== '#')
21
+ s = `#${s}`;
22
+ if (/^#[0-9a-fA-F]{3}$/.test(s)) {
23
+ const r = s[1], g = s[2], b = s[3];
24
+ s = `#${r}${r}${g}${g}${b}${b}`;
25
+ }
26
+ if (!/^#[0-9a-fA-F]{6}$/.test(s))
27
+ return '';
28
+ return s.toLowerCase();
29
+ }
30
+ function hexToRgb(hex) {
31
+ const n = normalizeHex(hex);
32
+ if (!n)
33
+ return null;
34
+ return {
35
+ r: parseInt(n.slice(1, 3), 16),
36
+ g: parseInt(n.slice(3, 5), 16),
37
+ b: parseInt(n.slice(5, 7), 16),
38
+ };
39
+ }
40
+ function rgbToHex(r, g, b) {
41
+ const h = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');
42
+ return `#${h(r)}${h(g)}${h(b)}`;
43
+ }
44
+ function rgbToHsv(r, g, b) {
45
+ r /= 255;
46
+ g /= 255;
47
+ b /= 255;
48
+ const max = Math.max(r, g, b);
49
+ const min = Math.min(r, g, b);
50
+ const d = max - min;
51
+ let h = 0;
52
+ if (d !== 0) {
53
+ if (max === r)
54
+ h = ((g - b) / d) % 6;
55
+ else if (max === g)
56
+ h = (b - r) / d + 2;
57
+ else
58
+ h = (r - g) / d + 4;
59
+ h *= 60;
60
+ if (h < 0)
61
+ h += 360;
62
+ }
63
+ const s = max === 0 ? 0 : d / max;
64
+ return { h, s, v: max };
65
+ }
66
+ function hsvToRgb(h, s, v) {
67
+ const c = v * s;
68
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
69
+ const m = v - c;
70
+ let rp = 0, gp = 0, bp = 0;
71
+ if (h < 60)
72
+ [rp, gp, bp] = [c, x, 0];
73
+ else if (h < 120)
74
+ [rp, gp, bp] = [x, c, 0];
75
+ else if (h < 180)
76
+ [rp, gp, bp] = [0, c, x];
77
+ else if (h < 240)
78
+ [rp, gp, bp] = [0, x, c];
79
+ else if (h < 300)
80
+ [rp, gp, bp] = [x, 0, c];
81
+ else
82
+ [rp, gp, bp] = [c, 0, x];
83
+ return {
84
+ r: (rp + m) * 255,
85
+ g: (gp + m) * 255,
86
+ b: (bp + m) * 255,
87
+ };
88
+ }
89
+ function hexToHsv(hex) {
90
+ const rgb = hexToRgb(hex) || hexToRgb(DEFAULT_ROLE_COLOR);
91
+ return rgbToHsv(rgb.r, rgb.g, rgb.b);
92
+ }
93
+ function hsvToHex(hsv) {
94
+ const { r, g, b } = hsvToRgb(hsv.h, hsv.s, hsv.v);
95
+ return rgbToHex(r, g, b);
96
+ }
97
+ function hueCss(h) {
98
+ const { r, g, b } = hsvToRgb(h, 1, 1);
99
+ return rgbToHex(r, g, b);
100
+ }
101
+ export function ColorPickerField({ value, onChange, 'aria-label': ariaLabel = 'Color', className, disabled, }) {
102
+ const hex = normalizeHex(value) || DEFAULT_ROLE_COLOR;
103
+ const [open, setOpen] = useState(false);
104
+ const [hsv, setHsv] = useState(() => hexToHsv(hex));
105
+ const [hexDraft, setHexDraft] = useState(hex);
106
+ const svRef = useRef(null);
107
+ const dragging = useRef(false);
108
+ // Sync from external value when popover closed (or first open).
109
+ useEffect(() => {
110
+ if (dragging.current)
111
+ return;
112
+ const next = normalizeHex(value) || DEFAULT_ROLE_COLOR;
113
+ setHsv(hexToHsv(next));
114
+ setHexDraft(next);
115
+ }, [value, open]);
116
+ const commit = useCallback((next) => {
117
+ setHsv(next);
118
+ const out = hsvToHex(next);
119
+ setHexDraft(out);
120
+ onChange(out);
121
+ }, [onChange]);
122
+ const setFromPointer = useCallback((clientX, clientY) => {
123
+ const el = svRef.current;
124
+ if (!el)
125
+ return;
126
+ const rect = el.getBoundingClientRect();
127
+ const s = clamp((clientX - rect.left) / rect.width, 0, 1);
128
+ const v = clamp(1 - (clientY - rect.top) / rect.height, 0, 1);
129
+ commit({ ...hsv, s, v });
130
+ }, [commit, hsv]);
131
+ useEffect(() => {
132
+ if (!open)
133
+ return;
134
+ const onMove = (e) => {
135
+ if (!dragging.current)
136
+ return;
137
+ setFromPointer(e.clientX, e.clientY);
138
+ };
139
+ const onUp = () => {
140
+ dragging.current = false;
141
+ };
142
+ window.addEventListener('pointermove', onMove);
143
+ window.addEventListener('pointerup', onUp);
144
+ return () => {
145
+ window.removeEventListener('pointermove', onMove);
146
+ window.removeEventListener('pointerup', onUp);
147
+ };
148
+ }, [open, setFromPointer]);
149
+ const svStyle = useMemo(() => ({
150
+ background: `
151
+ linear-gradient(to top, #000, transparent),
152
+ linear-gradient(to right, #fff, ${hueCss(hsv.h)})
153
+ `,
154
+ }), [hsv.h]);
155
+ const applyHexDraft = () => {
156
+ const n = normalizeHex(hexDraft);
157
+ if (!n) {
158
+ setHexDraft(hsvToHex(hsv));
159
+ return;
160
+ }
161
+ setHsv(hexToHsv(n));
162
+ setHexDraft(n);
163
+ onChange(n);
164
+ };
165
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", disabled: disabled, "aria-label": ariaLabel, className: cn('h-10 w-full justify-start gap-3 px-2.5 font-normal', className), children: [_jsx("span", { className: "h-6 w-6 shrink-0 rounded-md border border-black/10 shadow-sm ring-1 ring-black/5 dark:border-white/10", style: { background: hex }, "aria-hidden": true }), _jsx("span", { className: "font-mono text-sm uppercase tracking-wide text-foreground", children: hex }), _jsx(Pipette, { className: "ml-auto h-3.5 w-3.5 text-muted-foreground" })] }) }), _jsx(PopoverContent, { className: "w-[260px] p-3", align: "start", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { ref: svRef, role: "slider", "aria-label": "Saturaci\u00F3n y brillo", tabIndex: 0, className: "relative h-36 w-full cursor-crosshair touch-none overflow-hidden rounded-lg border border-border/60", style: svStyle, onPointerDown: (e) => {
166
+ dragging.current = true;
167
+ e.target.setPointerCapture?.(e.pointerId);
168
+ setFromPointer(e.clientX, e.clientY);
169
+ }, children: _jsx("span", { className: "pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md ring-1 ring-black/30", style: {
170
+ left: `${hsv.s * 100}%`,
171
+ top: `${(1 - hsv.v) * 100}%`,
172
+ background: hsvToHex(hsv),
173
+ } }) }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Tono" }), _jsx("input", { type: "range", min: 0, max: 360, step: 1, value: Math.round(hsv.h), "aria-label": "Tono", onChange: (e) => commit({ ...hsv, h: Number(e.target.value) }), className: "h-3 w-full cursor-pointer appearance-none rounded-full", style: {
174
+ background: 'linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)',
175
+ } })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-9 w-9 shrink-0 rounded-md border border-black/10 shadow-inner ring-1 ring-black/5", style: { background: hsvToHex(hsv) }, "aria-hidden": true }), _jsx(Input, { value: hexDraft, spellCheck: false, "aria-label": "Hexadecimal", className: "h-9 font-mono uppercase", onChange: (e) => setHexDraft(e.target.value), onBlur: applyHexDraft, onKeyDown: (e) => {
176
+ if (e.key === 'Enter') {
177
+ e.preventDefault();
178
+ applyHexDraft();
179
+ }
180
+ } })] })] }) })] }));
181
+ }
@@ -7,6 +7,7 @@ export { DynamicSelectField } from './dynamic-select-field';
7
7
  export { DynamicDateField } from './dynamic-date-field';
8
8
  export { UploadField } from './upload-field';
9
9
  export { IconPickerField } from './icon-picker-field';
10
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field';
10
11
  export interface DynamicFormProps {
11
12
  fields: ActionFieldDef[];
12
13
  initialValues?: Record<string, any>;
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAcrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;AAU9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,EAChB,UAAU,GACb,EAAE,gBAAgB,+BA8LlB"}
1
+ {"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAcrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;AAW9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEzF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,EAChB,UAAU,GACb,EAAE,gBAAgB,+BA8LlB"}
@@ -15,12 +15,14 @@ import { DynamicSelectField } from './dynamic-select-field';
15
15
  import { DynamicDateField } from './dynamic-date-field';
16
16
  import { UploadField } from './upload-field';
17
17
  import { IconPickerField } from './icon-picker-field';
18
+ import { ColorPickerField } from './color-picker-field';
18
19
  export { buildZodSchema, resolveWidget };
19
20
  export { DynamicLineItems } from './dynamic-line-items';
20
21
  export { DynamicSelectField } from './dynamic-select-field';
21
22
  export { DynamicDateField } from './dynamic-date-field';
22
23
  export { UploadField } from './upload-field';
23
24
  export { IconPickerField } from './icon-picker-field';
25
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field';
24
26
  export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, formLayout, }) {
25
27
  const [values, setValues] = useState({});
26
28
  const [errors, setErrors] = useState({});
@@ -230,7 +232,7 @@ function FieldRenderer({ field, value, onChange, initialValues, effectiveOptions
230
232
  // / future MDX editor pick it up without breaking the contract.
231
233
  return _jsx(Textarea, { id: field.key, "data-widget": "richtext", value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
232
234
  case 'color':
233
- return _jsx(Input, { id: field.key, type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value) });
235
+ return (_jsx(ColorPickerField, { "aria-label": field.label || field.key, value: typeof value === 'string' ? value : '', onChange: onChange }));
234
236
  case 'select':
235
237
  return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: (effectiveOptions ?? field.options)?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
236
238
  case 'switch':
package/dist/index.d.ts CHANGED
@@ -28,7 +28,8 @@ export * from './slot';
28
28
  export * from './capability-gate';
29
29
  export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, type CanFn, type PermissionsProviderProps, } from './permissions-context';
30
30
  export { useDynamicRowActions, type UseDynamicRowActionsParams, type DynamicRowActions, } from './dynamic-row-actions';
31
- export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, groupModuleActions, type PermissionsManagerProps, type PermissionsCatalog, type GroupedPermissionsCatalog, type FlatPermissionsCatalog, type ModuleGroup, type PermissionModuleDef, type PermissionActionDef, type GeneralPermissionDef, type RoleDef, type RoleInput, } from './permissions-manager';
31
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field';
32
+ export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, suggestRoleIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, groupModuleActions, type PermissionsManagerProps, type PermissionsCatalog, type GroupedPermissionsCatalog, type FlatPermissionsCatalog, type ModuleGroup, type PermissionModuleDef, type PermissionActionDef, type GeneralPermissionDef, type RoleDef, type RoleInput, } from './permissions-manager';
32
33
  export * from './org-runtime-context';
33
34
  export * from './org-runtime-provider';
34
35
  export * from './navigation-builder';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -33,7 +33,8 @@ export * from './slot';
33
33
  export * from './capability-gate';
34
34
  export { PermissionsProvider, useCan, usePermissionsActive, makeCan, capabilityForActionKey, modelCapability, gateTableMetadata, resolveRowActions, } from './permissions-context';
35
35
  export { useDynamicRowActions, } from './dynamic-row-actions';
36
- export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, groupModuleActions, } from './permissions-manager';
36
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field';
37
+ export { PermissionsManager, moduleActionCapability, moduleCapabilities, grantedCountForModule, capabilitySetsEqual, defaultActionIcon, suggestRoleIcon, normalizeCatalogGroups, flattenGroups, filterModuleGroups, groupModuleActions, } from './permissions-manager';
37
38
  export * from './org-runtime-context';
38
39
  export * from './org-runtime-provider';
39
40
  export * from './navigation-builder';
@@ -91,11 +91,14 @@ export interface RoleDef {
91
91
  label?: string;
92
92
  /** Accent color (hex) for the role chip. */
93
93
  color?: string;
94
+ /** Lucide PascalCase name (or image path) for the role chip. */
95
+ icon?: string;
94
96
  }
95
97
  export interface RoleInput {
96
98
  name: string;
97
99
  label?: string;
98
100
  color?: string;
101
+ icon?: string;
99
102
  }
100
103
  export interface PermissionsManagerProps {
101
104
  /** Loads the module×action universe + general flags (grouped or flat). */
@@ -136,6 +139,8 @@ export declare function grantedCountForModule(granted: ReadonlySet<string>, modu
136
139
  export declare function capabilitySetsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean;
137
140
  /** Default lucide icon when the manifest action doesn't declare one. */
138
141
  export declare function defaultActionIcon(actionKey: string, kind?: string): string;
142
+ /** Suggest a Lucide icon from the role label (create-dialog default). */
143
+ export declare function suggestRoleIcon(label: string): string;
139
144
  /**
140
145
  * Normalize whatever `loadModules` returned into the canonical grouped shape.
141
146
  *
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-manager.d.ts","sourceRoot":"","sources":["../src/permissions-manager.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions-manager.d.ts","sourceRoot":"","sources":["../src/permissions-manager.tsx"],"names":[],"mappings":"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"}
@@ -30,6 +30,8 @@ import { toast } from 'sonner';
30
30
  import { cn } from '@asteby/metacore-ui/lib';
31
31
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Separator, Skeleton, Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@asteby/metacore-ui/primitives';
32
32
  import { DynamicIcon } from './dynamic-icon';
33
+ import { IconPickerField } from './icon-picker-field';
34
+ import { ColorPickerField, DEFAULT_ROLE_COLOR } from './color-picker-field';
33
35
  // ---------------------------------------------------------------------------
34
36
  // Pure helpers (exported for hosts/tests)
35
37
  // ---------------------------------------------------------------------------
@@ -133,17 +135,29 @@ function slugify(label) {
133
135
  .replace(/[^a-z0-9]+/g, '_')
134
136
  .replace(/^_+|_+$/g, '');
135
137
  }
136
- const ROLE_COLORS = [
137
- '#ef4444',
138
- '#f97316',
139
- '#eab308',
140
- '#22c55e',
141
- '#06b6d4',
142
- '#3b82f6',
143
- '#8b5cf6',
144
- '#ec4899',
145
- '#6b7280',
146
- ];
138
+ /** Suggest a Lucide icon from the role label (create-dialog default). */
139
+ export function suggestRoleIcon(label) {
140
+ const s = fold(label);
141
+ const rules = [
142
+ [/cajer|cash|teller|bank/, 'Banknote'],
143
+ [/vend|seller|sales|shop|mostrador/, 'ShoppingBag'],
144
+ [/mecanic|technic|wrench|taller/, 'Wrench'],
145
+ [/jefe|lead|manager|gerente|supervisor/, 'HardHat'],
146
+ [/almacen|ware|stock|invent/, 'Warehouse'],
147
+ [/compr|purch|buyer/, 'Truck'],
148
+ [/contad|account|ledger/, 'Calculator'],
149
+ [/nomina|payroll|sueldo/, 'Wallet'],
150
+ [/rh|hr|human|emplead|people/, 'Users'],
151
+ [/fleet|flota|vehic/, 'Car'],
152
+ [/admin|dueño|owner/, 'ShieldCheck'],
153
+ [/view|observa|lectura|read/, 'Eye'],
154
+ ];
155
+ for (const [re, icon] of rules) {
156
+ if (re.test(s))
157
+ return icon;
158
+ }
159
+ return 'Shield';
160
+ }
147
161
  /**
148
162
  * Normalize whatever `loadModules` returned into the canonical grouped shape.
149
163
  *
@@ -237,7 +251,14 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
237
251
  const [moduleOpen, setModuleOpen] = React.useState(false);
238
252
  // Pending role switch while there are unsaved changes.
239
253
  const [pendingRoleId, setPendingRoleId] = React.useState(null);
240
- const [roleDialog, setRoleDialog] = React.useState({ open: false, mode: 'create', label: '', color: ROLE_COLORS[5], grantAll: false });
254
+ const [roleDialog, setRoleDialog] = React.useState({
255
+ open: false,
256
+ mode: 'create',
257
+ label: '',
258
+ color: DEFAULT_ROLE_COLOR,
259
+ icon: 'Shield',
260
+ grantAll: false,
261
+ });
241
262
  const [roleSaving, setRoleSaving] = React.useState(false);
242
263
  const [deleteOpen, setDeleteOpen] = React.useState(false);
243
264
  const [deleting, setDeleting] = React.useState(false);
@@ -389,6 +410,7 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
389
410
  name: slugify(label),
390
411
  label,
391
412
  color: roleDialog.color,
413
+ icon: roleDialog.icon || suggestRoleIcon(label),
392
414
  });
393
415
  const rs = await loadRoles();
394
416
  setRoles(rs);
@@ -412,6 +434,7 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
412
434
  name: activeRole.name,
413
435
  label,
414
436
  color: roleDialog.color,
437
+ icon: roleDialog.icon || suggestRoleIcon(label),
415
438
  });
416
439
  await refreshRoles(activeRole.id);
417
440
  toast.success('Rol actualizado');
@@ -451,7 +474,8 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
451
474
  open: true,
452
475
  mode: 'edit',
453
476
  label: activeRole.label || activeRole.name,
454
- color: activeRole.color || ROLE_COLORS[5],
477
+ color: activeRole.color || DEFAULT_ROLE_COLOR,
478
+ icon: activeRole.icon || suggestRoleIcon(activeRole.label || activeRole.name),
455
479
  grantAll: false,
456
480
  });
457
481
  };
@@ -476,18 +500,21 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
476
500
  open: true,
477
501
  mode: 'create',
478
502
  label: '',
479
- color: ROLE_COLORS[5],
503
+ color: DEFAULT_ROLE_COLOR,
504
+ icon: 'Shield',
480
505
  grantAll: false,
481
- }), children: [_jsx(Plus, { className: "mr-1.5 h-4 w-4" }), " Nuevo rol"] })), _jsxs(Button, { variant: "outline", onClick: () => setAllPermissions(true), disabled: checksDisabled || !allCatalogCapabilities.length, title: "Marca todos los permisos de todos los m\u00F3dulos", children: [_jsx(CheckCheck, { className: "mr-1.5 h-4 w-4" }), " Otorgar todo"] }), _jsxs(Button, { variant: "outline", onClick: () => setAllPermissions(false), disabled: checksDisabled || !draft?.size, title: "Quita todos los permisos del rol", children: [_jsx(Eraser, { className: "mr-1.5 h-4 w-4" }), " Quitar todo"] }), _jsxs(Button, { onClick: handleSave, disabled: !dirty || saving || !activeRole, className: "bg-emerald-600 text-white hover:bg-emerald-700", children: [_jsx(Save, { className: "mr-1.5 h-4 w-4" }), saving ? 'Guardando…' : 'Guardar permisos'] })] })] }), _jsxs("div", { className: "grid items-start gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Rol" }), _jsx(CardDescription, { children: "Selecciona el rol a configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: roleOpen, onOpenChange: setRoleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": roleOpen, className: "min-w-0 flex-1 justify-between font-normal", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [activeRole && (_jsx("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: {
482
- background: activeRole.color || '#6b7280',
483
- }, "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: activeRole
506
+ }), children: [_jsx(Plus, { className: "mr-1.5 h-4 w-4" }), " Nuevo rol"] })), _jsxs(Button, { variant: "outline", onClick: () => setAllPermissions(true), disabled: checksDisabled || !allCatalogCapabilities.length, title: "Marca todos los permisos de todos los m\u00F3dulos", children: [_jsx(CheckCheck, { className: "mr-1.5 h-4 w-4" }), " Otorgar todo"] }), _jsxs(Button, { variant: "outline", onClick: () => setAllPermissions(false), disabled: checksDisabled || !draft?.size, title: "Quita todos los permisos del rol", children: [_jsx(Eraser, { className: "mr-1.5 h-4 w-4" }), " Quitar todo"] }), _jsxs(Button, { onClick: handleSave, disabled: !dirty || saving || !activeRole, className: "bg-emerald-600 text-white hover:bg-emerald-700", children: [_jsx(Save, { className: "mr-1.5 h-4 w-4" }), saving ? 'Guardando…' : 'Guardar permisos'] })] })] }), _jsxs("div", { className: "grid items-start gap-4 lg:grid-cols-[340px_1fr]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "Rol" }), _jsx(CardDescription, { children: "Selecciona el rol a configurar." })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: roleOpen, onOpenChange: setRoleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": roleOpen, className: "min-w-0 flex-1 justify-between font-normal", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [activeRole && (_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md", style: {
507
+ background: `${activeRole.color || '#64748b'}22`,
508
+ color: activeRole.color || '#64748b',
509
+ }, "aria-hidden": "true", children: _jsx(DynamicIcon, { name: activeRole.icon || 'Shield', className: "h-3.5 w-3.5" }) })), _jsx("span", { className: "truncate", children: activeRole
484
510
  ? activeRole.label || activeRole.name
485
511
  : 'Seleccionar rol…' })] }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[280px] p-0", align: "start", children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Buscar rol\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "Sin resultados." }), _jsx(CommandGroup, { children: (roles ?? []).map((role) => (_jsxs(CommandItem, { value: `${role.label || ''} ${role.name}`, onSelect: () => {
486
512
  requestRoleSwitch(role.id);
487
513
  setRoleOpen(false);
488
- }, children: [_jsx("span", { className: "mr-2 h-2 w-2 shrink-0 rounded-full", style: {
489
- background: role.color || '#6b7280',
490
- }, "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: role.label || role.name }), role.id === activeRoleId && (_jsx(Check, { className: "ml-auto h-4 w-4" }))] }, role.id))) })] })] }) })] }), updateRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0", "aria-label": "Editar rol", disabled: !activeRole, onClick: openEditRole, children: _jsx(Pencil, { className: "h-4 w-4" }) })), deleteRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0 text-destructive hover:text-destructive", "aria-label": "Eliminar rol", disabled: !activeRole, onClick: () => setDeleteOpen(true), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), (general?.length ?? 0) > 0 && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-semibold", children: "Permisos Generales" }), _jsx("div", { className: "flex flex-col gap-2", children: general.map((g) => (_jsx(CapabilityCheck, { checked: draft?.has(g.key) ?? false, disabled: checksDisabled, onToggle: () => toggleCapability(g.key), label: g.label, description: g.description }, g.key))) })] })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "M\u00F3dulo" }), _jsx(CardDescription, { children: "Elige el m\u00F3dulo cuyas acciones quieres configurar." })] }), _jsx(CardContent, { children: _jsxs(Popover, { open: moduleOpen, onOpenChange: setModuleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": moduleOpen, className: "w-full justify-between font-normal", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [activeModule && (_jsx(DynamicIcon, { name: activeModule.icon ||
514
+ }, children: [_jsx("span", { className: "mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md", style: {
515
+ background: `${role.color || '#64748b'}22`,
516
+ color: role.color || '#64748b',
517
+ }, "aria-hidden": "true", children: _jsx(DynamicIcon, { name: role.icon || 'Shield', className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "truncate", children: role.label || role.name }), role.id === activeRoleId && (_jsx(Check, { className: "ml-auto h-4 w-4" }))] }, role.id))) })] })] }) })] }), updateRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0", "aria-label": "Editar rol", disabled: !activeRole, onClick: openEditRole, children: _jsx(Pencil, { className: "h-4 w-4" }) })), deleteRole && (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0 text-destructive hover:text-destructive", "aria-label": "Eliminar rol", disabled: !activeRole, onClick: () => setDeleteOpen(true), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), (general?.length ?? 0) > 0 && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-semibold", children: "Permisos Generales" }), _jsx("div", { className: "flex flex-col gap-2", children: general.map((g) => (_jsx(CapabilityCheck, { checked: draft?.has(g.key) ?? false, disabled: checksDisabled, onToggle: () => toggleCapability(g.key), label: g.label, description: g.description }, g.key))) })] })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: "M\u00F3dulo" }), _jsx(CardDescription, { children: "Elige el m\u00F3dulo cuyas acciones quieres configurar." })] }), _jsx(CardContent, { children: _jsxs(Popover, { open: moduleOpen, onOpenChange: setModuleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": moduleOpen, className: "w-full justify-between font-normal", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [activeModule && (_jsx(DynamicIcon, { name: activeModule.icon ||
491
518
  (activeModule.kind === 'screen'
492
519
  ? 'Eye'
493
520
  : 'Square'), className: "h-4 w-4 shrink-0 opacity-70" })), _jsx("span", { className: "truncate", children: activeModule
@@ -506,9 +533,26 @@ export function PermissionsManager({ loadModules, loadRoles, loadRolePermissions
506
533
  : 'Configura los permisos del módulo seleccionado.' })] }), activeRole && activeModule && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", className: "tabular-nums", children: [moduleGranted, "/", moduleTotal] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === moduleTotal, onClick: () => setModuleAll(true), children: [_jsx(CheckCheck, { className: "mr-1.5 h-3.5 w-3.5" }), " Marcar todo"] }), _jsxs(Button, { variant: "outline", size: "sm", className: "h-8", disabled: checksDisabled || moduleGranted === 0, onClick: () => setModuleAll(false), children: [_jsx(Eraser, { className: "mr-1.5 h-3.5 w-3.5" }), " Limpiar"] })] }))] }) }), _jsx(CardContent, { children: !activeRole ? (_jsx(EmptyHint, { text: "Selecciona un rol para configurar sus permisos." })) : loadingPerms ? (_jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Skeleton, { className: "h-14 w-full" }, i))) })) : !activeModule ? (_jsx(EmptyHint, { text: "Selecciona un m\u00F3dulo de la lista para ver sus acciones." })) : (_jsx(ModuleActionsPanel, { module: activeModule, draft: draft, checksDisabled: checksDisabled, onToggle: toggleCapability })) })] })] }), _jsx(AlertDialog, { open: pendingRoleId !== null, onOpenChange: (open) => !open && setPendingRoleId(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Cambios sin guardar" }), _jsx(AlertDialogDescription, { children: "Tienes cambios sin guardar en este rol. Si cambias de rol se descartar\u00E1n." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Cancelar" }), _jsx(AlertDialogAction, { onClick: () => {
507
534
  setActiveRoleId(pendingRoleId);
508
535
  setPendingRoleId(null);
509
- }, children: "Descartar y cambiar" })] })] }) }), _jsx(Dialog, { open: roleDialog.open, onOpenChange: (open) => setRoleDialog((d) => ({ ...d, open })), children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: roleDialog.mode === 'create' ? 'Nuevo rol' : 'Editar rol' }) }), _jsxs("div", { className: "flex flex-col gap-4 py-2", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "pm-role-name", children: "Nombre del rol" }), _jsx(Input, { id: "pm-role-name", value: roleDialog.label, placeholder: "Ej. Cajero", onChange: (e) => setRoleDialog((d) => ({ ...d, label: e.target.value })) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Color" }), _jsx("div", { className: "flex flex-wrap gap-2", children: ROLE_COLORS.map((c) => (_jsx("button", { type: "button", "aria-label": `Color ${c}`, onClick: () => setRoleDialog((d) => ({ ...d, color: c })), className: cn('h-7 w-7 rounded-full border-2 transition-transform', roleDialog.color === c
510
- ? 'scale-110 border-foreground'
511
- : 'border-transparent hover:scale-105'), style: { background: c } }, c))) })] }), roleDialog.mode === 'create' && (_jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-sm hover:bg-muted/40", children: [_jsx("input", { type: "checkbox", className: "mt-0.5 size-4 accent-emerald-600", checked: roleDialog.grantAll, onChange: (e) => setRoleDialog((d) => ({
536
+ }, children: "Descartar y cambiar" })] })] }) }), _jsx(Dialog, { open: roleDialog.open, onOpenChange: (open) => setRoleDialog((d) => ({ ...d, open })), children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: roleDialog.mode === 'create' ? 'Nuevo rol' : 'Editar rol' }) }), _jsxs("div", { className: "flex flex-col gap-4 py-2", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "pm-role-name", children: "Nombre del rol" }), _jsx(Input, { id: "pm-role-name", value: roleDialog.label, placeholder: "Ej. Cajero", onChange: (e) => {
537
+ const label = e.target.value;
538
+ setRoleDialog((d) => ({
539
+ ...d,
540
+ label,
541
+ icon: d.icon === suggestRoleIcon(d.label) ||
542
+ d.icon === 'Shield' ||
543
+ !d.icon
544
+ ? suggestRoleIcon(label)
545
+ : d.icon,
546
+ }));
547
+ } })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "\u00CDcono" }), _jsx(IconPickerField, { field: {
548
+ key: 'icon',
549
+ label: 'Ícono',
550
+ type: 'text',
551
+ widget: 'icon',
552
+ }, value: roleDialog.icon, onChange: (v) => setRoleDialog((d) => ({
553
+ ...d,
554
+ icon: typeof v === 'string' && v ? v : 'Shield',
555
+ })) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Color" }), _jsx(ColorPickerField, { "aria-label": "Color del rol", value: roleDialog.color, onChange: (color) => setRoleDialog((d) => ({ ...d, color })) })] }), roleDialog.mode === 'create' && (_jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-sm hover:bg-muted/40", children: [_jsx("input", { type: "checkbox", className: "mt-0.5 size-4 accent-emerald-600", checked: roleDialog.grantAll, onChange: (e) => setRoleDialog((d) => ({
512
556
  ...d,
513
557
  grantAll: e.target.checked,
514
558
  })) }), _jsxs("span", { children: [_jsx("span", { className: "font-medium", children: "Otorgar todos los permisos" }), _jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: "Acceso completo a todos los m\u00F3dulos del cat\u00E1logo (como un admin). Puedes quitar permisos despu\u00E9s." })] })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setRoleDialog((d) => ({ ...d, open: false })), disabled: roleSaving, children: "Cancelar" }), _jsx(Button, { onClick: handleRoleSubmit, disabled: roleSaving || !roleDialog.label.trim(), children: roleSaving
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "35.0.0",
3
+ "version": "36.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "sonner": ">=1.7",
39
39
  "zustand": ">=5",
40
40
  "@asteby/metacore-sdk": "^3.6.0",
41
- "@asteby/metacore-ui": "^2.16.0"
41
+ "@asteby/metacore-ui": "^2.17.0"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@tanstack/react-router": {
@@ -68,7 +68,7 @@
68
68
  "vitest": "^4.0.0",
69
69
  "zustand": "^5.0.0",
70
70
  "@asteby/metacore-sdk": "3.6.0",
71
- "@asteby/metacore-ui": "2.16.0"
71
+ "@asteby/metacore-ui": "2.17.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { normalizeHex, DEFAULT_ROLE_COLOR } from '../color-picker-field'
3
+
4
+ describe('normalizeHex', () => {
5
+ it('accepts #rgb and expands', () => {
6
+ expect(normalizeHex('#0af')).toBe('#00aaff')
7
+ })
8
+ it('lowercases #rrggbb', () => {
9
+ expect(normalizeHex('#3B82F6')).toBe('#3b82f6')
10
+ })
11
+ it('rejects garbage', () => {
12
+ expect(normalizeHex('blue')).toBe('')
13
+ expect(normalizeHex('#gg0000')).toBe('')
14
+ })
15
+ it('default constant is valid', () => {
16
+ expect(normalizeHex(DEFAULT_ROLE_COLOR)).toBe('#3b82f6')
17
+ })
18
+ })
@@ -0,0 +1,296 @@
1
+ // ColorPickerField — modern popover color control (SV plane + hue + hex).
2
+ // Used by PermissionsManager role dialog and DynamicForm `type: "color"`.
3
+ // Value is always a #rrggbb string (empty → falls back to DEFAULT_HEX visually).
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5
+ import { Pipette } from 'lucide-react'
6
+ import { cn } from '@asteby/metacore-ui/lib'
7
+ import {
8
+ Button,
9
+ Input,
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from '@asteby/metacore-ui/primitives'
14
+
15
+ export const DEFAULT_ROLE_COLOR = '#3b82f6'
16
+
17
+ export interface ColorPickerFieldProps {
18
+ value?: string
19
+ onChange: (hex: string) => void
20
+ /** Optional label for a11y on the trigger. */
21
+ 'aria-label'?: string
22
+ className?: string
23
+ disabled?: boolean
24
+ }
25
+
26
+ type HSV = { h: number; s: number; v: number }
27
+
28
+ function clamp(n: number, min: number, max: number) {
29
+ return Math.min(max, Math.max(min, n))
30
+ }
31
+
32
+ /** Normalize to #rrggbb or '' if invalid. */
33
+ export function normalizeHex(raw: unknown): string {
34
+ if (typeof raw !== 'string') return ''
35
+ let s = raw.trim()
36
+ if (!s) return ''
37
+ if (s[0] !== '#') s = `#${s}`
38
+ if (/^#[0-9a-fA-F]{3}$/.test(s)) {
39
+ const r = s[1],
40
+ g = s[2],
41
+ b = s[3]
42
+ s = `#${r}${r}${g}${g}${b}${b}`
43
+ }
44
+ if (!/^#[0-9a-fA-F]{6}$/.test(s)) return ''
45
+ return s.toLowerCase()
46
+ }
47
+
48
+ function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
49
+ const n = normalizeHex(hex)
50
+ if (!n) return null
51
+ return {
52
+ r: parseInt(n.slice(1, 3), 16),
53
+ g: parseInt(n.slice(3, 5), 16),
54
+ b: parseInt(n.slice(5, 7), 16),
55
+ }
56
+ }
57
+
58
+ function rgbToHex(r: number, g: number, b: number): string {
59
+ const h = (n: number) =>
60
+ clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0')
61
+ return `#${h(r)}${h(g)}${h(b)}`
62
+ }
63
+
64
+ function rgbToHsv(r: number, g: number, b: number): HSV {
65
+ r /= 255
66
+ g /= 255
67
+ b /= 255
68
+ const max = Math.max(r, g, b)
69
+ const min = Math.min(r, g, b)
70
+ const d = max - min
71
+ let h = 0
72
+ if (d !== 0) {
73
+ if (max === r) h = ((g - b) / d) % 6
74
+ else if (max === g) h = (b - r) / d + 2
75
+ else h = (r - g) / d + 4
76
+ h *= 60
77
+ if (h < 0) h += 360
78
+ }
79
+ const s = max === 0 ? 0 : d / max
80
+ return { h, s, v: max }
81
+ }
82
+
83
+ function hsvToRgb(h: number, s: number, v: number): { r: number; g: number; b: number } {
84
+ const c = v * s
85
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
86
+ const m = v - c
87
+ let rp = 0,
88
+ gp = 0,
89
+ bp = 0
90
+ if (h < 60) [rp, gp, bp] = [c, x, 0]
91
+ else if (h < 120) [rp, gp, bp] = [x, c, 0]
92
+ else if (h < 180) [rp, gp, bp] = [0, c, x]
93
+ else if (h < 240) [rp, gp, bp] = [0, x, c]
94
+ else if (h < 300) [rp, gp, bp] = [x, 0, c]
95
+ else [rp, gp, bp] = [c, 0, x]
96
+ return {
97
+ r: (rp + m) * 255,
98
+ g: (gp + m) * 255,
99
+ b: (bp + m) * 255,
100
+ }
101
+ }
102
+
103
+ function hexToHsv(hex: string): HSV {
104
+ const rgb = hexToRgb(hex) || hexToRgb(DEFAULT_ROLE_COLOR)!
105
+ return rgbToHsv(rgb.r, rgb.g, rgb.b)
106
+ }
107
+
108
+ function hsvToHex(hsv: HSV): string {
109
+ const { r, g, b } = hsvToRgb(hsv.h, hsv.s, hsv.v)
110
+ return rgbToHex(r, g, b)
111
+ }
112
+
113
+ function hueCss(h: number): string {
114
+ const { r, g, b } = hsvToRgb(h, 1, 1)
115
+ return rgbToHex(r, g, b)
116
+ }
117
+
118
+ export function ColorPickerField({
119
+ value,
120
+ onChange,
121
+ 'aria-label': ariaLabel = 'Color',
122
+ className,
123
+ disabled,
124
+ }: ColorPickerFieldProps) {
125
+ const hex = normalizeHex(value) || DEFAULT_ROLE_COLOR
126
+ const [open, setOpen] = useState(false)
127
+ const [hsv, setHsv] = useState<HSV>(() => hexToHsv(hex))
128
+ const [hexDraft, setHexDraft] = useState(hex)
129
+ const svRef = useRef<HTMLDivElement>(null)
130
+ const dragging = useRef(false)
131
+
132
+ // Sync from external value when popover closed (or first open).
133
+ useEffect(() => {
134
+ if (dragging.current) return
135
+ const next = normalizeHex(value) || DEFAULT_ROLE_COLOR
136
+ setHsv(hexToHsv(next))
137
+ setHexDraft(next)
138
+ }, [value, open])
139
+
140
+ const commit = useCallback(
141
+ (next: HSV) => {
142
+ setHsv(next)
143
+ const out = hsvToHex(next)
144
+ setHexDraft(out)
145
+ onChange(out)
146
+ },
147
+ [onChange],
148
+ )
149
+
150
+ const setFromPointer = useCallback(
151
+ (clientX: number, clientY: number) => {
152
+ const el = svRef.current
153
+ if (!el) return
154
+ const rect = el.getBoundingClientRect()
155
+ const s = clamp((clientX - rect.left) / rect.width, 0, 1)
156
+ const v = clamp(1 - (clientY - rect.top) / rect.height, 0, 1)
157
+ commit({ ...hsv, s, v })
158
+ },
159
+ [commit, hsv],
160
+ )
161
+
162
+ useEffect(() => {
163
+ if (!open) return
164
+ const onMove = (e: PointerEvent) => {
165
+ if (!dragging.current) return
166
+ setFromPointer(e.clientX, e.clientY)
167
+ }
168
+ const onUp = () => {
169
+ dragging.current = false
170
+ }
171
+ window.addEventListener('pointermove', onMove)
172
+ window.addEventListener('pointerup', onUp)
173
+ return () => {
174
+ window.removeEventListener('pointermove', onMove)
175
+ window.removeEventListener('pointerup', onUp)
176
+ }
177
+ }, [open, setFromPointer])
178
+
179
+ const svStyle = useMemo(
180
+ () => ({
181
+ background: `
182
+ linear-gradient(to top, #000, transparent),
183
+ linear-gradient(to right, #fff, ${hueCss(hsv.h)})
184
+ `,
185
+ }),
186
+ [hsv.h],
187
+ )
188
+
189
+ const applyHexDraft = () => {
190
+ const n = normalizeHex(hexDraft)
191
+ if (!n) {
192
+ setHexDraft(hsvToHex(hsv))
193
+ return
194
+ }
195
+ setHsv(hexToHsv(n))
196
+ setHexDraft(n)
197
+ onChange(n)
198
+ }
199
+
200
+ return (
201
+ <Popover open={open} onOpenChange={setOpen}>
202
+ <PopoverTrigger asChild>
203
+ <Button
204
+ type="button"
205
+ variant="outline"
206
+ disabled={disabled}
207
+ aria-label={ariaLabel}
208
+ className={cn(
209
+ 'h-10 w-full justify-start gap-3 px-2.5 font-normal',
210
+ className,
211
+ )}
212
+ >
213
+ <span
214
+ className="h-6 w-6 shrink-0 rounded-md border border-black/10 shadow-sm ring-1 ring-black/5 dark:border-white/10"
215
+ style={{ background: hex }}
216
+ aria-hidden
217
+ />
218
+ <span className="font-mono text-sm uppercase tracking-wide text-foreground">
219
+ {hex}
220
+ </span>
221
+ <Pipette className="ml-auto h-3.5 w-3.5 text-muted-foreground" />
222
+ </Button>
223
+ </PopoverTrigger>
224
+ <PopoverContent className="w-[260px] p-3" align="start">
225
+ <div className="flex flex-col gap-3">
226
+ <div
227
+ ref={svRef}
228
+ role="slider"
229
+ aria-label="Saturación y brillo"
230
+ tabIndex={0}
231
+ className="relative h-36 w-full cursor-crosshair touch-none overflow-hidden rounded-lg border border-border/60"
232
+ style={svStyle}
233
+ onPointerDown={(e) => {
234
+ dragging.current = true
235
+ ;(e.target as HTMLElement).setPointerCapture?.(e.pointerId)
236
+ setFromPointer(e.clientX, e.clientY)
237
+ }}
238
+ >
239
+ <span
240
+ className="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md ring-1 ring-black/30"
241
+ style={{
242
+ left: `${hsv.s * 100}%`,
243
+ top: `${(1 - hsv.v) * 100}%`,
244
+ background: hsvToHex(hsv),
245
+ }}
246
+ />
247
+ </div>
248
+
249
+ <div className="flex flex-col gap-1.5">
250
+ <label className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
251
+ Tono
252
+ </label>
253
+ <input
254
+ type="range"
255
+ min={0}
256
+ max={360}
257
+ step={1}
258
+ value={Math.round(hsv.h)}
259
+ aria-label="Tono"
260
+ onChange={(e) =>
261
+ commit({ ...hsv, h: Number(e.target.value) })
262
+ }
263
+ className="h-3 w-full cursor-pointer appearance-none rounded-full"
264
+ style={{
265
+ background:
266
+ 'linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)',
267
+ }}
268
+ />
269
+ </div>
270
+
271
+ <div className="flex items-center gap-2">
272
+ <span
273
+ className="h-9 w-9 shrink-0 rounded-md border border-black/10 shadow-inner ring-1 ring-black/5"
274
+ style={{ background: hsvToHex(hsv) }}
275
+ aria-hidden
276
+ />
277
+ <Input
278
+ value={hexDraft}
279
+ spellCheck={false}
280
+ aria-label="Hexadecimal"
281
+ className="h-9 font-mono uppercase"
282
+ onChange={(e) => setHexDraft(e.target.value)}
283
+ onBlur={applyHexDraft}
284
+ onKeyDown={(e) => {
285
+ if (e.key === 'Enter') {
286
+ e.preventDefault()
287
+ applyHexDraft()
288
+ }
289
+ }}
290
+ />
291
+ </div>
292
+ </div>
293
+ </PopoverContent>
294
+ </Popover>
295
+ )
296
+ }
@@ -35,6 +35,7 @@ import { DynamicSelectField } from './dynamic-select-field'
35
35
  import { DynamicDateField } from './dynamic-date-field'
36
36
  import { UploadField } from './upload-field'
37
37
  import { IconPickerField } from './icon-picker-field'
38
+ import { ColorPickerField } from './color-picker-field'
38
39
 
39
40
  export { buildZodSchema, resolveWidget }
40
41
  export { DynamicLineItems } from './dynamic-line-items'
@@ -42,6 +43,7 @@ export { DynamicSelectField } from './dynamic-select-field'
42
43
  export { DynamicDateField } from './dynamic-date-field'
43
44
  export { UploadField } from './upload-field'
44
45
  export { IconPickerField } from './icon-picker-field'
46
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field'
45
47
 
46
48
  export interface DynamicFormProps {
47
49
  fields: ActionFieldDef[]
@@ -399,7 +401,13 @@ function FieldRenderer({
399
401
  // / future MDX editor pick it up without breaking the contract.
400
402
  return <Textarea id={field.key} data-widget="richtext" value={value || ''} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)} placeholder={field.placeholder} />
401
403
  case 'color':
402
- return <Input id={field.key} type="color" value={value || '#000000'} onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)} />
404
+ return (
405
+ <ColorPickerField
406
+ aria-label={field.label || field.key}
407
+ value={typeof value === 'string' ? value : ''}
408
+ onChange={onChange}
409
+ />
410
+ )
403
411
  case 'select':
404
412
  return (
405
413
  <Select value={value || ''} onValueChange={onChange}>
package/src/index.ts CHANGED
@@ -178,6 +178,7 @@ export {
178
178
  type UseDynamicRowActionsParams,
179
179
  type DynamicRowActions,
180
180
  } from './dynamic-row-actions'
181
+ export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field'
181
182
  export {
182
183
  PermissionsManager,
183
184
  moduleActionCapability,
@@ -185,6 +186,7 @@ export {
185
186
  grantedCountForModule,
186
187
  capabilitySetsEqual,
187
188
  defaultActionIcon,
189
+ suggestRoleIcon,
188
190
  normalizeCatalogGroups,
189
191
  flattenGroups,
190
192
  filterModuleGroups,
@@ -79,6 +79,9 @@ import {
79
79
  CollapsibleTrigger,
80
80
  } from '@asteby/metacore-ui/primitives'
81
81
  import { DynamicIcon } from './dynamic-icon'
82
+ import { IconPickerField } from './icon-picker-field'
83
+ import { ColorPickerField, DEFAULT_ROLE_COLOR } from './color-picker-field'
84
+ import type { ActionFieldDef } from './types'
82
85
 
83
86
  // ---------------------------------------------------------------------------
84
87
  // Types (mirror of `GET /api/permissions/modules` + the host sidebar nav)
@@ -183,12 +186,15 @@ export interface RoleDef {
183
186
  label?: string
184
187
  /** Accent color (hex) for the role chip. */
185
188
  color?: string
189
+ /** Lucide PascalCase name (or image path) for the role chip. */
190
+ icon?: string
186
191
  }
187
192
 
188
193
  export interface RoleInput {
189
194
  name: string
190
195
  label?: string
191
196
  color?: string
197
+ icon?: string
192
198
  }
193
199
 
194
200
  export interface PermissionsManagerProps {
@@ -330,17 +336,28 @@ function slugify(label: string): string {
330
336
  .replace(/^_+|_+$/g, '')
331
337
  }
332
338
 
333
- const ROLE_COLORS = [
334
- '#ef4444',
335
- '#f97316',
336
- '#eab308',
337
- '#22c55e',
338
- '#06b6d4',
339
- '#3b82f6',
340
- '#8b5cf6',
341
- '#ec4899',
342
- '#6b7280',
343
- ]
339
+ /** Suggest a Lucide icon from the role label (create-dialog default). */
340
+ export function suggestRoleIcon(label: string): string {
341
+ const s = fold(label)
342
+ const rules: Array<[RegExp, string]> = [
343
+ [/cajer|cash|teller|bank/, 'Banknote'],
344
+ [/vend|seller|sales|shop|mostrador/, 'ShoppingBag'],
345
+ [/mecanic|technic|wrench|taller/, 'Wrench'],
346
+ [/jefe|lead|manager|gerente|supervisor/, 'HardHat'],
347
+ [/almacen|ware|stock|invent/, 'Warehouse'],
348
+ [/compr|purch|buyer/, 'Truck'],
349
+ [/contad|account|ledger/, 'Calculator'],
350
+ [/nomina|payroll|sueldo/, 'Wallet'],
351
+ [/rh|hr|human|emplead|people/, 'Users'],
352
+ [/fleet|flota|vehic/, 'Car'],
353
+ [/admin|dueño|owner/, 'ShieldCheck'],
354
+ [/view|observa|lectura|read/, 'Eye'],
355
+ ]
356
+ for (const [re, icon] of rules) {
357
+ if (re.test(s)) return icon
358
+ }
359
+ return 'Shield'
360
+ }
344
361
 
345
362
  /**
346
363
  * Normalize whatever `loadModules` returned into the canonical grouped shape.
@@ -548,8 +565,16 @@ export function PermissionsManager({
548
565
  mode: 'create' | 'edit'
549
566
  label: string
550
567
  color: string
568
+ icon: string
551
569
  grantAll: boolean
552
- }>({ open: false, mode: 'create', label: '', color: ROLE_COLORS[5], grantAll: false })
570
+ }>({
571
+ open: false,
572
+ mode: 'create',
573
+ label: '',
574
+ color: DEFAULT_ROLE_COLOR,
575
+ icon: 'Shield',
576
+ grantAll: false,
577
+ })
553
578
  const [roleSaving, setRoleSaving] = React.useState(false)
554
579
  const [deleteOpen, setDeleteOpen] = React.useState(false)
555
580
  const [deleting, setDeleting] = React.useState(false)
@@ -711,6 +736,7 @@ export function PermissionsManager({
711
736
  name: slugify(label),
712
737
  label,
713
738
  color: roleDialog.color,
739
+ icon: roleDialog.icon || suggestRoleIcon(label),
714
740
  })
715
741
  const rs = await loadRoles()
716
742
  setRoles(rs)
@@ -732,6 +758,7 @@ export function PermissionsManager({
732
758
  name: activeRole.name,
733
759
  label,
734
760
  color: roleDialog.color,
761
+ icon: roleDialog.icon || suggestRoleIcon(label),
735
762
  })
736
763
  await refreshRoles(activeRole.id)
737
764
  toast.success('Rol actualizado')
@@ -769,7 +796,8 @@ export function PermissionsManager({
769
796
  open: true,
770
797
  mode: 'edit',
771
798
  label: activeRole.label || activeRole.name,
772
- color: activeRole.color || ROLE_COLORS[5],
799
+ color: activeRole.color || DEFAULT_ROLE_COLOR,
800
+ icon: activeRole.icon || suggestRoleIcon(activeRole.label || activeRole.name),
773
801
  grantAll: false,
774
802
  })
775
803
  }
@@ -843,7 +871,8 @@ export function PermissionsManager({
843
871
  open: true,
844
872
  mode: 'create',
845
873
  label: '',
846
- color: ROLE_COLORS[5],
874
+ color: DEFAULT_ROLE_COLOR,
875
+ icon: 'Shield',
847
876
  grantAll: false,
848
877
  })
849
878
  }
@@ -901,12 +930,18 @@ export function PermissionsManager({
901
930
  <span className="flex min-w-0 items-center gap-2">
902
931
  {activeRole && (
903
932
  <span
904
- className="h-2.5 w-2.5 shrink-0 rounded-full"
933
+ className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md"
905
934
  style={{
906
- background: activeRole.color || '#6b7280',
935
+ background: `${activeRole.color || '#64748b'}22`,
936
+ color: activeRole.color || '#64748b',
907
937
  }}
908
938
  aria-hidden="true"
909
- />
939
+ >
940
+ <DynamicIcon
941
+ name={activeRole.icon || 'Shield'}
942
+ className="h-3.5 w-3.5"
943
+ />
944
+ </span>
910
945
  )}
911
946
  <span className="truncate">
912
947
  {activeRole
@@ -933,12 +968,18 @@ export function PermissionsManager({
933
968
  }}
934
969
  >
935
970
  <span
936
- className="mr-2 h-2 w-2 shrink-0 rounded-full"
971
+ className="mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md"
937
972
  style={{
938
- background: role.color || '#6b7280',
973
+ background: `${role.color || '#64748b'}22`,
974
+ color: role.color || '#64748b',
939
975
  }}
940
976
  aria-hidden="true"
941
- />
977
+ >
978
+ <DynamicIcon
979
+ name={role.icon || 'Shield'}
980
+ className="h-3.5 w-3.5"
981
+ />
982
+ </span>
942
983
  <span className="truncate">
943
984
  {role.label || role.name}
944
985
  </span>
@@ -1224,30 +1265,48 @@ export function PermissionsManager({
1224
1265
  id="pm-role-name"
1225
1266
  value={roleDialog.label}
1226
1267
  placeholder="Ej. Cajero"
1227
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
1228
- setRoleDialog((d) => ({ ...d, label: e.target.value }))
1268
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
1269
+ const label = e.target.value
1270
+ setRoleDialog((d) => ({
1271
+ ...d,
1272
+ label,
1273
+ icon:
1274
+ d.icon === suggestRoleIcon(d.label) ||
1275
+ d.icon === 'Shield' ||
1276
+ !d.icon
1277
+ ? suggestRoleIcon(label)
1278
+ : d.icon,
1279
+ }))
1280
+ }}
1281
+ />
1282
+ </div>
1283
+ <div className="flex flex-col gap-2">
1284
+ <Label>Ícono</Label>
1285
+ <IconPickerField
1286
+ field={
1287
+ {
1288
+ key: 'icon',
1289
+ label: 'Ícono',
1290
+ type: 'text',
1291
+ widget: 'icon',
1292
+ } as ActionFieldDef
1293
+ }
1294
+ value={roleDialog.icon}
1295
+ onChange={(v) =>
1296
+ setRoleDialog((d) => ({
1297
+ ...d,
1298
+ icon: typeof v === 'string' && v ? v : 'Shield',
1299
+ }))
1229
1300
  }
1230
1301
  />
1231
1302
  </div>
1232
1303
  <div className="flex flex-col gap-2">
1233
1304
  <Label>Color</Label>
1234
- <div className="flex flex-wrap gap-2">
1235
- {ROLE_COLORS.map((c) => (
1236
- <button
1237
- key={c}
1238
- type="button"
1239
- aria-label={`Color ${c}`}
1240
- onClick={() => setRoleDialog((d) => ({ ...d, color: c }))}
1241
- className={cn(
1242
- 'h-7 w-7 rounded-full border-2 transition-transform',
1243
- roleDialog.color === c
1244
- ? 'scale-110 border-foreground'
1245
- : 'border-transparent hover:scale-105',
1246
- )}
1247
- style={{ background: c }}
1248
- />
1249
- ))}
1250
- </div>
1305
+ <ColorPickerField
1306
+ aria-label="Color del rol"
1307
+ value={roleDialog.color}
1308
+ onChange={(color) => setRoleDialog((d) => ({ ...d, color }))}
1309
+ />
1251
1310
  </div>
1252
1311
  {roleDialog.mode === 'create' && (
1253
1312
  <label className="flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-sm hover:bg-muted/40">