experimental web port of the Union style engine + adapters
styling theming css solidjs react union kde
0

Configure Feed

Select the types of activity you want to include in your feed.

union-web / docs / react-plugin.md
6.3 kB 157 lines
1# @union-web/react-plugin 2 3The React plugin provides runtime style resolution for Union themes. It adapts Union's C++ core concepts (elements, selectors, queries) into React hooks and context, enabling components to resolve their styles on the fly based on role, state, and position in the element tree. 4 5> **Note:** This project uses a ratcheting development style. The SolidJS plugin is the 6> currently active runtime. The react-plugin may lag behind in architecture: 7> 8> - **Ancestor state propagation** — Solid's `AncestorElementData` includes `states`; 9> the react-plugin omits it, breaking selectors like `toolbar:disabled > button`. 10> - **Typed style props** — Solid uses `UnionStyleProps` + `StyledRect` (preserves Union's 11> semantic model); the react-plugin uses flattened `styleGroupToCss` → raw CSS objects. 12> - **`unionIR`/`unionLint`** — These Vite plugins have been extracted to 13> `@union-web/vite-plugin` and re-exported from here. They are shared, not duplicated. 14 15## How It Adapts Union's API 16 17The web has no built-in element tree. The plugin recovers hierarchy through **explicit ancestor context**: 18 19| Union Concept | React Plugin Equivalent | 20|-------------|------------------------| 21| `Union::Element` | `createElement(role, opts)` inside `useUnion()` | 22| `Union::ElementQuery` | `resolveElement()` from core | 23| Widget tree (implicit in Qt) | `UnionProvider` chain via React context | 24| `Union::StyleRule` collection | `StyleRule[]` from `UnionIRContext` | 25 26Components call `useUnion()` and wrap children in the returned `UnionProvider` to extend the ancestor chain. This enables selectors like `textarea > .placeholder` to match because `Placeholder` can see `textarea` in its ancestor context. Any third-party component can opt in to Union theming by calling `useUnion()` with the appropriate role. 27 28## API Reference 29 30### UseUnionOptions 31 32```ts 33interface UseUnionOptions { 34 role: string; 35 context?: string[] | null; 36 state?: string[]; 37 hints?: string[]; 38 attributes?: Record<string, string>; 39 ids?: string[]; 40 debug?: boolean; 41} 42``` 43 44| Option | Description | 45|--------|-------------| 46| `role` | Element type for matching (e.g., `'button'`, `'textarea'`). Required. | 47| `context` | Explicit ancestor roles. If `null`, becomes a root element (no ancestors). If unset, inherits from nearest `UnionProvider`. | 48| `state` | Active states (e.g., `['hovered']`, `['visual-focus']`). | 49| `hints` | Semantic hints for matching (parallels CSS `.hint` selectors). | 50| `attributes` | Key-value attribute pairs for attribute selectors. | 51| `ids` | Element IDs for ID selectors. | 52| `debug` | Enable console group logging of selector matching results. | 53 54### UseUnionResult 55 56```ts 57interface UseUnionResult { 58 styles: Record<string, StylePropertyGroup>; 59 current: StylePropertyGroup; 60 UnionProvider: ({ children }: { children: React.ReactNode }) => React.ReactElement; 61} 62``` 63 64| Return | Description | 65|--------|-------------| 66| `styles` | Pre-computed `StylePropertyGroup` for all standard state combinations (base, hovered, pressed, checked, visual-focus, active-focus, disabled, highlighted, plus checked combos). | 67| `current` | The `StylePropertyGroup` for the element's current state. | 68| `UnionProvider` | A component wrapping `UnionAncestorContext.Provider`. Pushes this element into the ancestor chain for descendant `useUnion()` calls. | 69 70### useUnion() 71 72```ts 73function useUnion(options: UseUnionOptions): UseUnionResult 74``` 75 76The main hook. It: 77 781. Reads `StyleRule[]` from `UnionIRContext` (set by `UnionIRProvider` at the app root). 792. Reads ancestor chain from `UnionAncestorContext` (set by parent `UnionProvider`s). 803. Pre-computes all standard state combinations via `resolveElement()`. 814. Returns `{ styles, current, UnionProvider }`. 82 83If a `UnionProvider` is not rendered (leaf component), the hook still resolves styles correctly — it just doesn't extend the ancestor chain for any deeper components. 84 85### UnionIRProvider 86 87```ts 88function UnionIRProvider({ rules, children }: { rules: StyleRule[]; children: React.ReactNode }): React.ReactElement 89``` 90 91Root provider. Supplies the parsed CSS rules to all `useUnion` calls in the tree. Must be placed at the top of the application. 92 93### UnionIRContext / UnionAncestorContext 94 95```ts 96UnionIRContext: React.Context<{ rules: StyleRule[] }> // style rules from theme 97UnionAncestorContext: React.Context<{ elements: AncestorElementData[] }> // ancestor chain 98``` 99 100### unionIR (Vite Plugin) 101 102Now lives in `@union-web/vite-plugin`, re-exported from `@union-web/react-plugin/vite`. 103 104```ts 105import { unionIR } from '@union-web/react-plugin/vite'; 106// or directly: 107import { unionIR } from '@union-web/vite-plugin/vite'; 108``` 109 110Build-time plugin that: 111 1121. Loads and parses defaults CSS, theme CSS, and `.colors` file. 1132. Resolves all variable expressions (`var()`, `custom-color()`, `modify-color()`, `mix()`) using `KColorSchemeResolver` and `VariableResolver`. 1143. Provides a `virtual:union-ir` module exporting the resolved `StyleRule[]` and CSS variable map. 1154. Injects resolved CSS variables into HTML `<head>`. 1165. Watches input files for HMR. 117 118```ts 119interface UnionIROptions { 120 theme: string; // path to theme style.css 121 colors: string; // path to .colors file 122 defaults: string; // path to defaults CSS 123} 124``` 125 126## Using `UnionProvider` 127 128Render a `UnionProvider` for any component that has descendants that will call `useUnion()`: 129 130```tsx 131function TextArea(props) { 132 const { current, UnionProvider } = useUnion({ role: 'textarea', state: [...] }); 133 return ( 134 <UnionProvider> 135 <textarea style={styleGroupToReact(current)} /> 136 {!value && placeholder && <Placeholder text={placeholder} />} 137 </UnionProvider> 138 ); 139} 140 141// Leaf — no UnionProvider needed 142function Placeholder({ text }) { 143 const { current } = useUnion({ role: 'placeholder', hints: ['placeholder'] }); 144 return <div style={styleGroupToReact(current)}>{text}</div>; 145} 146``` 147 148## Debugging 149 150Pass `debug: true` to see selector matching in the browser console: 151 152``` 153[useUnion] textarea (base) ancestors: [applicationwindow, page, columnlayout] 154 ✓ textarea (spec: 0.1.0) { background-color: #2a2a2a; ... } 155 ✓ > .placeholder (spec: 0.2.0) { color: #808080; } 156result: { layout: ..., background: { color: "#2a2a2a" } } 157```