Skip to content

Commit f66ee7b

Browse files
authored
feat: introduce CSSVariablesLayer (#142)
1 parent 81ea6c2 commit f66ee7b

File tree

11 files changed

+1320
-2
lines changed

11 files changed

+1320
-2
lines changed

docs/plugins/cssVariables.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# CSS Variables Plugin
2+
3+
## Overview
4+
5+
The `CSSVariablesLayer` plugin enables seamless synchronization between CSS variables and graph appearance settings (colors and constants). Instead of using JavaScript API methods like `graph.setColors()` or `graph.setConstants()`, you can control the graph's visual styling through standard CSS variables.
6+
7+
This layer creates an invisible HTML div element with a specified CSS class and monitors CSS variable changes using the `style-observer` package. When CSS variables change (e.g., through theme switching, media queries, or dynamic style updates), the layer automatically maps these changes to the corresponding `TGraphColors` and `TGraphConstants` properties and applies them to the graph.
8+
9+
## Features
10+
11+
1. **CSS-based Styling:**
12+
* Control graph appearance using CSS variables instead of JavaScript API.
13+
* Enables integration with CSS frameworks, design systems, and theme providers.
14+
* Supports dynamic theme switching without JavaScript code changes.
15+
16+
2. **Real-time Synchronization:**
17+
* Automatically detects CSS variable changes using `style-observer`.
18+
* Instantly applies changes to graph colors and constants.
19+
* No manual update calls required.
20+
21+
3. **Comprehensive Variable Support:**
22+
* Colors: canvas, blocks, anchors, connections, connection labels, selection.
23+
* Constants: block dimensions, grid size, camera settings, text properties.
24+
* See [Supported CSS Variables](#supported-css-variables) section for the complete list.
25+
26+
4. **Type Safety:**
27+
* Automatic type conversion (color strings, floats, integers).
28+
* Validates and converts CSS values to appropriate graph property types.
29+
30+
5. **Developer-friendly:**
31+
* Optional debug mode for logging changes.
32+
* Change callback for custom handling.
33+
* Lightweight invisible container with no visual impact.
34+
35+
## Usage
36+
37+
The `CSSVariablesLayer` can be added to the graph like any other layer.
38+
39+
### 1. Via Graph Configuration
40+
41+
```typescript
42+
import { Graph, CSSVariablesLayer } from "@gravity-ui/graph";
43+
44+
const graph = new Graph({
45+
// ... other graph settings
46+
layers: [
47+
// Other layers...
48+
[CSSVariablesLayer, {
49+
containerClass: "graph-theme", // CSS class for the container
50+
debug: false, // Enable debug logging
51+
onChange: (changes) => {
52+
console.log("CSS variables changed:", changes);
53+
}
54+
}]
55+
]
56+
});
57+
```
58+
59+
### 2. Dynamic Addition
60+
61+
```typescript
62+
import { CSSVariablesLayer } from "@gravity-ui/graph";
63+
64+
// Assuming 'graph' is an existing Graph instance
65+
graph.addLayer(CSSVariablesLayer, {
66+
containerClass: "graph-theme",
67+
debug: true,
68+
});
69+
```
70+
71+
### 3. With React (`useLayer` Hook)
72+
73+
This is the recommended way when using the React bindings.
74+
75+
```tsx
76+
import React from 'react';
77+
import { useGraph, GraphCanvas, useLayer, CSSVariablesLayer } from '@gravity-ui/graph';
78+
79+
function MyGraphComponent() {
80+
const { graph } = useGraph({ /* ... initial config ... */ });
81+
82+
// Add the CSS Variables layer using the hook
83+
useLayer(graph, CSSVariablesLayer, {
84+
containerClass: "graph-theme", // Match your theme container class
85+
debug: false,
86+
onChange: (changes) => {
87+
// Optional: handle changes
88+
console.log("CSS variables updated:", changes);
89+
},
90+
});
91+
92+
// ... useEffect to load data and start graph ...
93+
94+
return (
95+
<div className="graph-theme graph-theme-light">
96+
<GraphCanvas graph={graph} renderBlock={/* ... */} />
97+
</div>
98+
);
99+
}
100+
```
101+
102+
## Configuration Options (`CSSVariablesLayerProps`)
103+
104+
The layer accepts the following options (extending base `LayerProps`), with defaults defined in `DEFAULT_CSS_VARIABLES_LAYER_PROPS`:
105+
106+
| Prop | Type | Default | Description |
107+
| :------------------------- | :-------------------------------------------- | :----------------------------- | :-------------------------------------------------------------------------- |
108+
| `containerClass` | `string` | **(required)** | CSS class name applied to the invisible container div. Used to observe CSS variables. |
109+
| `onChange` | `(changes: CSSVariableChange[]) => void` | `undefined` | Optional callback invoked when CSS variables change. Receives array of changes. |
110+
| `debug` | `boolean` | `false` | Enables debug logging to console for CSS variable changes and layer operations. |
111+
112+
*Note: Base `LayerProps` like `graph`, `camera`, `root`, `emitter` are provided automatically when using `graph.addLayer` or `useLayer`.*
113+
114+
## Supported CSS Variables
115+
116+
The layer supports the following CSS variables, automatically mapped to graph properties:
117+
118+
### Canvas Colors
119+
120+
| CSS Variable | Graph Property | Type |
121+
| :------------------------------------ | :------------------------------ | :------ |
122+
| `--graph-canvas-background` | `canvas.layerBackground` | Color |
123+
| `--graph-canvas-below-background` | `canvas.belowLayerBackground` | Color |
124+
| `--graph-canvas-dots` | `canvas.dots` | Color |
125+
| `--graph-canvas-border` | `canvas.border` | Color |
126+
127+
### Block Colors
128+
129+
| CSS Variable | Graph Property | Type |
130+
| :------------------------------------ | :------------------------------ | :------ |
131+
| `--graph-block-background` | `block.background` | Color |
132+
| `--graph-block-border` | `block.border` | Color |
133+
| `--graph-block-text` | `block.text` | Color |
134+
| `--graph-block-selected-border` | `block.selectedBorder` | Color |
135+
136+
### Anchor Colors
137+
138+
| CSS Variable | Graph Property | Type |
139+
| :------------------------------------ | :------------------------------ | :------ |
140+
| `--graph-anchor-background` | `anchor.background` | Color |
141+
| `--graph-anchor-selected-border` | `anchor.selectedBorder` | Color |
142+
143+
### Connection Colors
144+
145+
| CSS Variable | Graph Property | Type |
146+
| :------------------------------------ | :------------------------------ | :------ |
147+
| `--graph-connection-background` | `connection.background` | Color |
148+
| `--graph-connection-selected-background` | `connection.selectedBackground` | Color |
149+
150+
### Connection Label Colors
151+
152+
| CSS Variable | Graph Property | Type |
153+
| :------------------------------------ | :------------------------------ | :------ |
154+
| `--graph-connection-label-background` | `connectionLabel.background` | Color |
155+
| `--graph-connection-label-hover-background` | `connectionLabel.hoverBackground` | Color |
156+
| `--graph-connection-label-selected-background` | `connectionLabel.selectedBackground` | Color |
157+
| `--graph-connection-label-text` | `connectionLabel.text` | Color |
158+
| `--graph-connection-label-hover-text` | `connectionLabel.hoverText` | Color |
159+
| `--graph-connection-label-selected-text` | `connectionLabel.selectedText` | Color |
160+
161+
### Selection Colors
162+
163+
| CSS Variable | Graph Property | Type |
164+
| :------------------------------------ | :------------------------------ | :------ |
165+
| `--graph-selection-background` | `selection.background` | Color |
166+
| `--graph-selection-border` | `selection.border` | Color |
167+
168+
### Block Constants
169+
170+
| CSS Variable | Graph Property | Type |
171+
| :------------------------------------ | :------------------------------ | :------ |
172+
| `--graph-block-width` | `block.WIDTH` | Float |
173+
| `--graph-block-height` | `block.HEIGHT` | Float |
174+
| `--graph-block-width-min` | `block.WIDTH_MIN` | Float |
175+
| `--graph-block-head-height` | `block.HEAD_HEIGHT` | Float |
176+
| `--graph-block-body-padding` | `block.BODY_PADDING` | Float |
177+
178+
### System Constants
179+
180+
| CSS Variable | Graph Property | Type |
181+
| :------------------------------------ | :------------------------------ | :------ |
182+
| `--graph-grid-size` | `system.GRID_SIZE` | Int |
183+
| `--graph-usable-rect-gap` | `system.USABLE_RECT_GAP` | Float |
184+
185+
### Camera Constants
186+
187+
| CSS Variable | Graph Property | Type |
188+
| :------------------------------------ | :------------------------------ | :------ |
189+
| `--graph-camera-speed` | `camera.SPEED` | Float |
190+
| `--graph-camera-step` | `camera.STEP` | Float |
191+
192+
### Text Constants
193+
194+
| CSS Variable | Graph Property | Type |
195+
| :------------------------------------ | :------------------------------ | :------ |
196+
| `--graph-text-base-font-size` | `text.BASE_FONT_SIZE` | Float |
197+
| `--graph-text-padding` | `text.PADDING` | Float |
198+
199+
## CSS Example
200+
201+
Here's an example of defining graph styles using CSS variables:
202+
203+
```css
204+
/* Light theme */
205+
.graph-theme-light {
206+
--graph-canvas-background: #ffffff;
207+
--graph-block-background: #f5f5f5;
208+
--graph-block-border: #e0e0e0;
209+
--graph-block-selected-border: #ffbe5c;
210+
--graph-connection-background: rgba(0, 0, 0, 0.3);
211+
--graph-connection-selected-background: #eac94a;
212+
}
213+
214+
/* Dark theme */
215+
.graph-theme-dark {
216+
--graph-canvas-background: #1a1a1a;
217+
--graph-block-background: #2d2d2d;
218+
--graph-block-border: #404040;
219+
--graph-block-selected-border: #ffbe5c;
220+
--graph-connection-background: rgba(255, 255, 255, 0.5);
221+
--graph-connection-selected-background: #eac94a;
222+
}
223+
224+
/* Custom block sizes */
225+
.graph-theme {
226+
--graph-block-width: 240px;
227+
--graph-block-height: 120px;
228+
--graph-block-head-height: 32px;
229+
--graph-grid-size: 20;
230+
}
231+
```
232+
233+
## Best Practices
234+
235+
1. **Container Class Naming:** Use a consistent CSS class name that matches your theme structure (e.g., `graph-theme`).
236+
2. **Theme Organization:** Group related variables together in your CSS files for better maintainability.
237+
3. **Debug Mode:** Enable `debug: true` during development to track variable changes in the console.
238+
4. **Performance:** The layer is optimized and uses efficient observers, but avoid excessive CSS variable changes in tight loops.
239+
5. **Fallbacks:** Define default values in your CSS to ensure proper rendering if variables are not set.
240+
6. **Type Consistency:** Use appropriate CSS value formats:
241+
* Colors: `#hex`, `rgb()`, `rgba()`, or named colors
242+
* Numbers: Include units for dimensions (`px`, `em`, etc.) or use unitless values
243+
* Integers: Whole numbers for properties like `GRID_SIZE`
244+
245+
## Integration with Design Systems
246+
247+
The CSS Variables Layer integrates seamlessly with design system tokens and theme providers:
248+
249+
```tsx
250+
// Example with a design system
251+
import { ThemeProvider } from '@my-design-system/react';
252+
253+
function App() {
254+
const { graph } = useGraph();
255+
256+
useLayer(graph, CSSVariablesLayer, {
257+
containerClass: 'my-design-system-theme',
258+
});
259+
260+
return (
261+
<ThemeProvider theme="light">
262+
<div className="my-design-system-theme">
263+
<GraphCanvas graph={graph} />
264+
</div>
265+
</ThemeProvider>
266+
);
267+
}
268+
```
269+
270+
## Troubleshooting
271+
272+
**Variables not updating:**
273+
- Ensure the `containerClass` matches an actual CSS class in your DOM.
274+
- Check that CSS variables are properly defined with the `--graph-` prefix.
275+
- Enable `debug: true` to see which variables are being detected.
276+
277+
**Wrong colors/values:**
278+
- Verify CSS variable values use correct formats (valid colors, numbers with units).
279+
- Check the browser DevTools to inspect computed CSS variable values.
280+
- Ensure variable names exactly match the supported list.
281+
282+
**Performance issues:**
283+
- Avoid changing too many variables simultaneously.
284+
- Consider debouncing rapid theme changes.
285+
- Use the `onChange` callback to batch updates if needed.
286+

package-lock.json

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@
7878
"@preact/signals-core": "^1.5.1",
7979
"intersects": "^2.7.2",
8080
"lodash": "^4.17.21",
81-
"rbush": "^3.0.1"
81+
"rbush": "^3.0.1",
82+
"style-observer": "^0.1.1"
8283
},
8384
"devDependencies": {
8485
"@babel/plugin-proposal-class-properties": "^7.18.6",

0 commit comments

Comments
 (0)