@bsuite/page-builder 0.2.1 → 0.2.5

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.
@@ -100,7 +100,7 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
100
100
  ]);
101
101
  const visibleKeys = useMemo(() => new Set((activeLayouts.lg ?? []).map((item) => item.i)), [activeLayouts.lg]);
102
102
  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: 2, 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: [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: {
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
104
  backgroundColor: layoutCols === columnCount ? 'var(--accent-primary, var(--primary, #2563eb))' : 'var(--bg-tertiary, var(--muted, #f3f4f6))',
105
105
  color: layoutCols === columnCount ? '#fff' : 'var(--text-secondary, var(--muted-foreground, #6b7280))',
106
106
  border: '1px solid var(--border-shell, var(--border, #e5e7eb))',
@@ -111,7 +111,7 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
111
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: () => {
112
112
  handleReset();
113
113
  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: resizeConstraints, compactor: activeCompactor, cols: activeCols, margin: [6, 6], children: activeLayouts.lg.map((layoutItem) => {
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) => {
115
115
  const content = allWidgets[layoutItem.i];
116
116
  if (content === undefined || content === null)
117
117
  return null;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { PageGridLayout } from './PageGridLayout.js';
2
2
  export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES } from './usePageGridLayout.js';
3
3
  export { rescaleLayout } from './rescaleLayout.js';
4
+ export { WYSIWYG_REQUIRED_PRIMITIVES, WYSIWYG_SURFACE_CONTRACTS, assertWysiwygPrimitiveCoverage, getWysiwygSurfaceContract, } from './wysiwygContract.js';
5
+ export type { WysiwygPrimitive, WysiwygSurfaceContract, WysiwygSurfaceId, } from './wysiwygContract.js';
4
6
  export { buildResponsiveLayouts, RESPONSIVE_BREAKPOINTS, } from './buildResponsiveLayouts.js';
5
7
  export type { BuildResponsiveLayoutsOptions, ResponsiveBreakpoint, } from './buildResponsiveLayouts.js';
6
8
  export { useLocalPreference, defaultPreferenceAdapter } from './preferences.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAChF,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAChF,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { PageGridLayout } from './PageGridLayout.js';
2
2
  export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES } from './usePageGridLayout.js';
3
3
  export { rescaleLayout } from './rescaleLayout.js';
4
+ export { WYSIWYG_REQUIRED_PRIMITIVES, WYSIWYG_SURFACE_CONTRACTS, assertWysiwygPrimitiveCoverage, getWysiwygSurfaceContract, } from './wysiwygContract.js';
4
5
  export { buildResponsiveLayouts, RESPONSIVE_BREAKPOINTS, } from './buildResponsiveLayouts.js';
5
6
  export { useLocalPreference, defaultPreferenceAdapter } from './preferences.js';
@@ -62,8 +62,8 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
62
62
  setSavedLayout,
63
63
  setSavedLayoutVersion,
64
64
  ]);
65
- const layoutCols = Math.max(2, savedLayoutCols ?? defaultCols);
66
- const baseCols = Math.max(2, savedBaseCols ?? defaultCols);
65
+ const layoutCols = Math.max(1, savedLayoutCols ?? defaultCols);
66
+ const baseCols = Math.max(1, savedBaseCols ?? defaultCols);
67
67
  const rawLayouts = useMemo(() => {
68
68
  if (Object.keys(savedLayout || {}).length === 0)
69
69
  return defaultLayouts;
@@ -0,0 +1,43 @@
1
+ export type WysiwygPrimitive = 'react-flow' | 'tanstack-query' | 'resizable-cards' | 'dnd-kit';
2
+ export type WysiwygSurfaceId = 'schema-builder' | 'page-builder' | 'form-layout-builder' | 'custom-page-renderer';
3
+ export interface WysiwygSurfaceContract {
4
+ id: WysiwygSurfaceId;
5
+ ownerPackage: '@bsuite/schema-builder' | '@bsuite/page-builder' | 'crm7';
6
+ requiredPrimitives: readonly WysiwygPrimitive[];
7
+ interactionPrimitive: WysiwygPrimitive | 'read-only';
8
+ persistencePrimitive: WysiwygPrimitive | 'custom-pages-read-only' | 'page-grid-preference-adapter';
9
+ }
10
+ export declare const WYSIWYG_REQUIRED_PRIMITIVES: readonly ["react-flow", "tanstack-query", "resizable-cards", "dnd-kit"];
11
+ export declare const WYSIWYG_SURFACE_CONTRACTS: {
12
+ readonly 'schema-builder': {
13
+ readonly id: "schema-builder";
14
+ readonly ownerPackage: "@bsuite/schema-builder";
15
+ readonly requiredPrimitives: readonly ["react-flow", "tanstack-query"];
16
+ readonly interactionPrimitive: "react-flow";
17
+ readonly persistencePrimitive: "tanstack-query";
18
+ };
19
+ readonly 'page-builder': {
20
+ readonly id: "page-builder";
21
+ readonly ownerPackage: "@bsuite/page-builder";
22
+ readonly requiredPrimitives: readonly ["resizable-cards", "tanstack-query"];
23
+ readonly interactionPrimitive: "resizable-cards";
24
+ readonly persistencePrimitive: "page-grid-preference-adapter";
25
+ };
26
+ readonly 'form-layout-builder': {
27
+ readonly id: "form-layout-builder";
28
+ readonly ownerPackage: "crm7";
29
+ readonly requiredPrimitives: readonly ["dnd-kit", "tanstack-query"];
30
+ readonly interactionPrimitive: "dnd-kit";
31
+ readonly persistencePrimitive: "tanstack-query";
32
+ };
33
+ readonly 'custom-page-renderer': {
34
+ readonly id: "custom-page-renderer";
35
+ readonly ownerPackage: "@bsuite/page-builder";
36
+ readonly requiredPrimitives: readonly ["tanstack-query"];
37
+ readonly interactionPrimitive: "read-only";
38
+ readonly persistencePrimitive: "custom-pages-read-only";
39
+ };
40
+ };
41
+ export declare function getWysiwygSurfaceContract(surfaceId: WysiwygSurfaceId): WysiwygSurfaceContract;
42
+ export declare function assertWysiwygPrimitiveCoverage(contracts?: Record<WysiwygSurfaceId, WysiwygSurfaceContract>): void;
43
+ //# sourceMappingURL=wysiwygContract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wysiwygContract.d.ts","sourceRoot":"","sources":["../src/wysiwygContract.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE/F,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,cAAc,GACd,qBAAqB,GACrB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,gBAAgB,CAAC;IACrB,YAAY,EAAE,wBAAwB,GAAG,sBAAsB,GAAG,MAAM,CAAC;IACzE,kBAAkB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,oBAAoB,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACrD,oBAAoB,EAAE,gBAAgB,GAAG,wBAAwB,GAAG,8BAA8B,CAAC;CACpG;AAED,eAAO,MAAM,2BAA2B,yEAKQ,CAAC;AAEjD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B+B,CAAC;AAEtE,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,gBAAgB,GAAG,sBAAsB,CAE7F;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,GAAE,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAA6B,GACtF,IAAI,CAcN"}
@@ -0,0 +1,51 @@
1
+ export const WYSIWYG_REQUIRED_PRIMITIVES = [
2
+ 'react-flow',
3
+ 'tanstack-query',
4
+ 'resizable-cards',
5
+ 'dnd-kit',
6
+ ];
7
+ export const WYSIWYG_SURFACE_CONTRACTS = {
8
+ 'schema-builder': {
9
+ id: 'schema-builder',
10
+ ownerPackage: '@bsuite/schema-builder',
11
+ requiredPrimitives: ['react-flow', 'tanstack-query'],
12
+ interactionPrimitive: 'react-flow',
13
+ persistencePrimitive: 'tanstack-query',
14
+ },
15
+ 'page-builder': {
16
+ id: 'page-builder',
17
+ ownerPackage: '@bsuite/page-builder',
18
+ requiredPrimitives: ['resizable-cards', 'tanstack-query'],
19
+ interactionPrimitive: 'resizable-cards',
20
+ persistencePrimitive: 'page-grid-preference-adapter',
21
+ },
22
+ 'form-layout-builder': {
23
+ id: 'form-layout-builder',
24
+ ownerPackage: 'crm7',
25
+ requiredPrimitives: ['dnd-kit', 'tanstack-query'],
26
+ interactionPrimitive: 'dnd-kit',
27
+ persistencePrimitive: 'tanstack-query',
28
+ },
29
+ 'custom-page-renderer': {
30
+ id: 'custom-page-renderer',
31
+ ownerPackage: '@bsuite/page-builder',
32
+ requiredPrimitives: ['tanstack-query'],
33
+ interactionPrimitive: 'read-only',
34
+ persistencePrimitive: 'custom-pages-read-only',
35
+ },
36
+ };
37
+ export function getWysiwygSurfaceContract(surfaceId) {
38
+ return WYSIWYG_SURFACE_CONTRACTS[surfaceId];
39
+ }
40
+ export function assertWysiwygPrimitiveCoverage(contracts = WYSIWYG_SURFACE_CONTRACTS) {
41
+ const covered = new Set();
42
+ for (const contract of Object.values(contracts)) {
43
+ for (const primitive of contract.requiredPrimitives) {
44
+ covered.add(primitive);
45
+ }
46
+ }
47
+ const missing = WYSIWYG_REQUIRED_PRIMITIVES.filter((primitive) => !covered.has(primitive));
48
+ if (missing.length > 0) {
49
+ throw new Error(`WYSIWYG primitive coverage missing: ${missing.join(', ')}`);
50
+ }
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -58,13 +58,14 @@
58
58
  "@types/react": "^19.2.14",
59
59
  "@types/react-dom": "^19.2.3",
60
60
  "@vitejs/plugin-react": "^5.2.0",
61
- "jsdom": "^28.1.0",
62
- "lucide-react": "^0.576.0",
63
- "react": "^19.2.4",
64
- "react-dom": "^19.2.4",
65
- "react-grid-layout": "^2.2.2",
66
- "react-resizable": "^3.0.5",
67
- "typescript": "~5.7.3",
68
- "vitest": "^3.2.4"
61
+ "jsdom": "^29.1.1",
62
+ "lucide-react": "^1.14.0",
63
+ "react": "^19.2.5",
64
+ "react-dom": "^19.2.5",
65
+ "react-grid-layout": "^2.2.3",
66
+ "react-resizable": "^3.1.3",
67
+ "typescript": "~6.0.3",
68
+ "vite": "^6.4.2",
69
+ "vitest": "^4.1.5"
69
70
  }
70
71
  }
@@ -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
  }