@bsuite/page-builder 0.2.4 → 0.2.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAGxC,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAkEnE,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EACT,WAAmB,EACnB,aAAsC,EACtC,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,GACvB,EAAE,mBAAmB,2CA2RrB"}
1
+ {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAGxC,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAiFnE,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EACT,WAAmB,EACnB,aAAsC,EACtC,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,GACvB,EAAE,mBAAmB,2CAuTrB"}
@@ -11,14 +11,24 @@ const DEFAULT_RESIZE_BOUNDS = { minW: 2, minH: 1, maxW: 12, maxH: 16 };
11
11
  const DEFAULT_RESIZE_HANDLES = ['se'];
12
12
  const DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES = ['bsu-add-entity-widget', 'crm7-add-entity-widget'];
13
13
  const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label, onRemove, className: injectedClassName, style: injectedStyle, ...rest }, ref) {
14
- return (_jsx("div", { ref: ref, className: cn('relative group', injectedClassName), style: injectedStyle, ...rest, children: _jsxs("div", { className: "h-full w-full relative", children: [isEditing && _jsx("div", { className: "drag-handle absolute inset-0 z-20 cursor-move bg-transparent" }), isEditing && (_jsx("div", { className: "absolute inset-0 z-10 pointer-events-none rounded-3xl border-2 border-transparent group-hover:border-primary/50 transition-colors bg-black/5" })), isEditing && (_jsx("div", { className: "absolute top-2 left-2 z-30 flex items-center gap-1 pointer-events-none", children: _jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded-md opacity-80 font-medium bg-muted text-muted-foreground", children: label }) })), isEditing && (_jsx("button", { className: "absolute top-2 right-2 z-30 h-6 w-6 rounded-full flex items-center justify-center bg-destructive/80 hover:bg-destructive text-white shadow transition-colors", onClick: (event) => {
14
+ // DnD root-cause fix (2026-05-07): react-draggable@4 (used internally by
15
+ // react-grid-layout@2) checks the drag handle via
16
+ // `matchesSelectorAndParentsTo(target, handle, baseNode)` — it walks UP
17
+ // the ancestor chain from the click target. The previous implementation
18
+ // rendered `.drag-handle` as a SIBLING of the card content, so clicks on
19
+ // any visible card content never matched (ancestors are: card-content →
20
+ // .rounded-3xl → .h-full.w-full.relative → .relative.group; none carry
21
+ // the class). Drag silently failed.
22
+ //
23
+ // Fix: put `.drag-handle` on the outer container. Now any click inside
24
+ // the GridItem has it as an ancestor → drag fires. Interactive elements
25
+ // (buttons, inputs, textarea, select, links, [data-no-drag]) are still
26
+ // protected via the `cancel` selector below in the <Responsive> render.
27
+ const outerClass = cn('relative group', isEditing && 'drag-handle cursor-move', injectedClassName);
28
+ return (_jsx("div", { ref: ref, className: outerClass, style: injectedStyle, ...rest, children: _jsxs("div", { className: "h-full w-full relative", children: [isEditing && (_jsx("div", { className: "absolute inset-0 z-10 pointer-events-none rounded-3xl border-2 border-transparent group-hover:border-primary/50 transition-colors bg-black/5" })), isEditing && (_jsx("div", { className: "absolute top-2 left-2 z-30 flex items-center gap-1 pointer-events-none", children: _jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded-md opacity-80 font-medium bg-muted text-muted-foreground", children: label }) })), isEditing && (_jsx("button", { className: "absolute top-2 right-2 z-30 h-6 w-6 rounded-full flex items-center justify-center bg-destructive/80 hover:bg-destructive text-white shadow transition-colors", "data-no-drag": true, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
15
29
  event.stopPropagation();
16
30
  onRemove(id);
17
- }, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })), _jsx("div", { className: "h-full w-full rounded-3xl transition-all flex flex-col", style: {
18
- background: 'var(--bg-panel, var(--background, var(--card, #fff)))',
19
- boxShadow: 'var(--shadow-shell-glow, 0 1px 3px rgb(0 0 0 / 0.1))',
20
- contain: 'paint',
21
- }, children: content })] }) }));
31
+ }, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })), _jsx("div", { className: "h-full w-full rounded-3xl transition-all flex flex-col bg-card shadow-sm", style: { contain: 'paint' }, children: content })] }) }));
22
32
  }));
23
33
  export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className, isResizable = false, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, }) {
24
34
  const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleCompact, handleReset, addWidget, removeWidget, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
@@ -100,18 +110,24 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
100
110
  ]);
101
111
  const visibleKeys = useMemo(() => new Set((activeLayouts.lg ?? []).map((item) => item.i)), [activeLayouts.lg]);
102
112
  const hiddenWidgetKeys = useMemo(() => [...renderableWidgetKeys].filter((key) => !visibleKeys.has(key)), [renderableWidgetKeys, visibleKeys]);
103
- return (_jsxs("div", { className: className, children: [isEditing && (_jsxs("div", { className: "flex flex-col gap-3 p-4 rounded-xl shadow-lg border-2 mb-4", style: { backgroundColor: 'var(--bg-panel, var(--card, #fff))', borderColor: 'var(--accent-primary, var(--primary, #2563eb))' }, children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "h-10 w-10 rounded-full flex items-center justify-center animate-pulse", style: { backgroundColor: 'var(--bg-shell-accent, rgb(37 99 235 / 0.1))' }, children: _jsx(Settings2, { className: "h-5 w-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-lg", style: { color: 'var(--text-heading, var(--foreground, #111827))' }, children: "Canvas Editor Active" }), _jsx("span", { className: "text-sm", style: { color: 'var(--text-secondary, var(--muted-foreground, #6b7280))' }, children: "Drag anywhere on a card to move it. Resize with the bottom-right handle." })] })] }), _jsxs("button", { type: "button", className: "shrink-0 inline-flex items-center rounded-md px-3 py-2 text-sm font-medium bg-primary text-primary-foreground shadow", onClick: () => startTransition(() => setIsEditing(false)), children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save & Exit"] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 pt-2 border-t", style: { borderColor: 'var(--border-shell, var(--border, #e5e7eb))' }, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LayoutGrid, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Columns:" }), _jsx("input", { type: "range", min: 1, max: 24, value: layoutCols, onChange: (event) => handleColumnChange(Number(event.target.value)), className: "w-24 cursor-pointer accent-primary", "aria-label": "Column count" }), _jsx("span", { className: "text-sm font-mono w-5 text-center tabular-nums", children: layoutCols })] }), _jsx("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Column presets", children: [1, 2, 3, 4, 6, 12].map((columnCount) => (_jsx("button", { type: "button", onClick: () => handleColumnChange(columnCount), className: "px-2 py-0.5 rounded text-xs font-medium transition-colors", style: {
104
- backgroundColor: layoutCols === columnCount ? 'var(--accent-primary, var(--primary, #2563eb))' : 'var(--bg-tertiary, var(--muted, #f3f4f6))',
105
- color: layoutCols === columnCount ? '#fff' : 'var(--text-secondary, var(--muted-foreground, #6b7280))',
106
- border: '1px solid var(--border-shell, var(--border, #e5e7eb))',
107
- }, "aria-pressed": layoutCols === columnCount, children: columnCount }, columnCount))) }), hiddenWidgetKeys.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Add widget:" }), _jsx("div", { className: "flex flex-wrap gap-1", children: hiddenWidgetKeys.map((key) => {
113
+ return (_jsxs("div", { className: className, children: [isEditing && (_jsxs("div", { className: "flex flex-col gap-3 p-4 rounded-xl shadow-lg border-2 mb-4 bg-card border-primary", children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "h-10 w-10 rounded-full flex items-center justify-center animate-pulse bg-primary/10", children: _jsx(Settings2, { className: "h-5 w-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-lg text-foreground", children: "Canvas Editor Active" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Drag anywhere on a card to move it. Resize with the bottom-right handle." })] })] }), _jsxs("button", { type: "button", className: "shrink-0 inline-flex items-center rounded-md px-3 py-2 text-sm font-medium bg-primary text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", onClick: () => startTransition(() => setIsEditing(false)), children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save & Exit"] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 pt-2 border-t border-border", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LayoutGrid, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Columns:" }), _jsx("input", { type: "range", min: 1, max: 24, value: layoutCols, onChange: (event) => handleColumnChange(Number(event.target.value)), className: "w-24 cursor-pointer accent-primary", "aria-label": "Column count" }), _jsx("span", { className: "text-sm font-mono w-5 text-center tabular-nums", children: layoutCols })] }), _jsx("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Column presets", children: [1, 2, 3, 4, 6, 12].map((columnCount) => (_jsx("button", { type: "button", onClick: () => handleColumnChange(columnCount), "data-active": layoutCols === columnCount || undefined, "aria-pressed": layoutCols === columnCount, className: cn('px-2 py-0.5 rounded text-xs font-medium transition-colors border', 'bg-muted text-muted-foreground border-border', 'hover:bg-muted/80 hover:text-foreground', 'data-[active]:bg-primary data-[active]:text-primary-foreground data-[active]:border-primary', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: columnCount }, columnCount))) }), hiddenWidgetKeys.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Add widget:" }), _jsx("div", { className: "flex flex-wrap gap-1", children: hiddenWidgetKeys.map((key) => {
108
114
  const meta = widgetMeta?.[key];
109
115
  const Icon = meta?.icon;
110
- return (_jsxs("button", { type: "button", onClick: () => addWidget(key, meta?.defaultSize), className: "flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium border transition-colors hover:border-primary/60 bg-muted text-muted-foreground", children: [_jsx(Plus, { className: "h-3 w-3" }), Icon && _jsx(Icon, { className: "h-3 w-3" }), meta?.label ?? key] }, key));
111
- }) })] })), _jsxs("button", { type: "button", onClick: handleCompact, className: "ml-auto inline-flex items-center rounded-md border px-2 py-1 text-sm text-muted-foreground", children: [_jsx(Layers, { className: "h-4 w-4 mr-1" }), "Compact Layout"] }), _jsxs("button", { type: "button", onClick: () => setResetConfirmOpen(true), className: "inline-flex items-center rounded-md border px-2 py-1 text-sm text-muted-foreground", children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset to Default"] })] })] })), resetConfirmOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4", role: "dialog", "aria-modal": "true", "aria-labelledby": "page-grid-reset-title", children: _jsxs("div", { className: "max-w-md rounded-lg bg-card p-5 shadow-lg border", children: [_jsx("h2", { id: "page-grid-reset-title", className: "text-lg font-semibold", children: "Reset page layout?" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "This will discard your current layout and restore all widgets to their default positions." }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx("button", { type: "button", className: "rounded-md border px-3 py-2 text-sm", onClick: () => setResetConfirmOpen(false), children: "Cancel" }), _jsx("button", { type: "button", className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground", onClick: () => {
116
+ return (_jsxs("button", { type: "button", onClick: () => addWidget(key, meta?.defaultSize), className: cn('flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium border transition-colors', 'bg-muted text-muted-foreground border-border', 'hover:bg-muted/80 hover:text-foreground hover:border-primary/60', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Plus, { className: "h-3 w-3" }), Icon && _jsx(Icon, { className: "h-3 w-3" }), meta?.label ?? key] }, key));
117
+ }) })] })), _jsxs("button", { type: "button", onClick: handleCompact, className: cn('ml-auto inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Layers, { className: "h-4 w-4 mr-1" }), "Compact Layout"] }), _jsxs("button", { type: "button", onClick: () => setResetConfirmOpen(true), className: cn('inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset to Default"] })] })] })), resetConfirmOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4", role: "dialog", "aria-modal": "true", "aria-labelledby": "page-grid-reset-title", children: _jsxs("div", { className: "max-w-md rounded-lg bg-card p-5 shadow-lg border", children: [_jsx("h2", { id: "page-grid-reset-title", className: "text-lg font-semibold", children: "Reset page layout?" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "This will discard your current layout and restore all widgets to their default positions." }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx("button", { type: "button", className: "rounded-md border px-3 py-2 text-sm", onClick: () => setResetConfirmOpen(false), children: "Cancel" }), _jsx("button", { type: "button", className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground", onClick: () => {
112
118
  handleReset();
113
119
  setResetConfirmOpen(false);
114
- }, children: "Reset to Default" })] })] }) })), _jsx("div", { ref: containerRef, className: isEditing ? 'min-h-[200px]' : '', style: { backgroundColor: isEditing ? 'rgb(0 0 0 / 0.03)' : 'transparent' }, children: _jsx("div", { style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: 32, onLayoutChange: onLayoutChange, dragConfig: { enabled: isEditing, handle: '.drag-handle', bounded: false, cancel: '.react-resizable-handle' }, resizeConfig: { enabled: resizeEnabled, handles: resizeHandles }, constraints: resizeEnabled ? resizeConstraints : undefined, compactor: activeCompactor, cols: activeCols, margin: [6, 6], children: activeLayouts.lg.map((layoutItem) => {
120
+ }, children: "Reset to Default" })] })] }) })), _jsx("div", { ref: containerRef, className: isEditing ? 'min-h-[200px]' : '', style: { backgroundColor: isEditing ? 'rgb(0 0 0 / 0.03)' : 'transparent' }, children: _jsx("div", { style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: 32, onLayoutChange: onLayoutChange, dragConfig: {
121
+ enabled: isEditing,
122
+ handle: '.drag-handle',
123
+ bounded: false,
124
+ // Cancel any interactive descendant — react-draggable's
125
+ // matchesSelectorAndParentsTo walks up; if any ancestor matches
126
+ // the cancel selector, drag is suppressed. This lets users
127
+ // click buttons / type in inputs / interact with form controls
128
+ // inside cards while still being able to drag the card surface.
129
+ cancel: '.react-resizable-handle, button, input, textarea, select, [contenteditable="true"], [data-no-drag], a[href], [role="button"], [role="combobox"], [role="menuitem"], [role="tab"], [role="checkbox"], [role="switch"], [role="slider"], [role="textbox"]',
130
+ }, resizeConfig: { enabled: resizeEnabled, handles: resizeHandles }, constraints: resizeEnabled ? resizeConstraints : undefined, compactor: activeCompactor, cols: activeCols, margin: [6, 6], children: activeLayouts.lg.map((layoutItem) => {
115
131
  const content = allWidgets[layoutItem.i];
116
132
  if (content === undefined || content === null)
117
133
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,3 +1,17 @@
1
+ /*
2
+ * BSuite page-builder — react-grid-layout style overrides + base handle styles
3
+ *
4
+ * react-grid-layout requires base CSS from `react-grid-layout/css/styles.css`
5
+ * + `react-resizable/css/styles.css` to render the resize handle visibly.
6
+ * Consumer apps usually don't import those, so we inline the handle styles
7
+ * here. Without this, the handle has zero size and corner-drag is silently
8
+ * non-functional (issue #539, 2026-05-06).
9
+ *
10
+ * The handle is only rendered into the DOM when the grid is in edit mode
11
+ * (rgl reads `isResizable` per item), so we don't need a separate edit-mode
12
+ * gate — the styles below apply only when the handle is present.
13
+ */
14
+
1
15
  .react-grid-layout {
2
16
  position: relative;
3
17
  transition: height 200ms ease;
@@ -13,6 +27,44 @@
13
27
  opacity: 0.16;
14
28
  }
15
29
 
30
+ /* Resize handle (bottom-right corner). Visible 20×20 hit target with a
31
+ * diagonal-arrow marker. z-index sits above widget content but below
32
+ * modal overlays. */
16
33
  .react-grid-item > .react-resizable-handle {
34
+ position: absolute;
35
+ width: 20px;
36
+ height: 20px;
37
+ bottom: 0;
38
+ right: 0;
39
+ cursor: se-resize;
17
40
  z-index: 35;
41
+ padding: 0 3px 3px 0;
42
+ box-sizing: border-box;
43
+ background-color: transparent;
44
+ /* Subtle by default — opacity ramps on widget hover. */
45
+ opacity: 0.5;
46
+ transition: opacity 120ms ease;
47
+ }
48
+
49
+ /* Stronger handle when hovering the parent widget so users discover it. */
50
+ .react-grid-item:hover > .react-resizable-handle {
51
+ opacity: 1;
52
+ }
53
+
54
+ /* Diagonal-arrow marker (two L-shaped border lines). */
55
+ .react-grid-item > .react-resizable-handle::after {
56
+ content: "";
57
+ position: absolute;
58
+ right: 3px;
59
+ bottom: 3px;
60
+ width: 6px;
61
+ height: 6px;
62
+ border-right: 2px solid var(--text-secondary, rgba(0, 0, 0, 0.45));
63
+ border-bottom: 2px solid var(--text-secondary, rgba(0, 0, 0, 0.45));
64
+ }
65
+
66
+ /* Highlight the marker when hovering the handle itself. */
67
+ .react-grid-item > .react-resizable-handle:hover::after {
68
+ border-right-color: var(--accent-primary, var(--primary, #2563eb));
69
+ border-bottom-color: var(--accent-primary, var(--primary, #2563eb));
18
70
  }