Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion apps/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { StorybookConfig } from "@storybook/react-vite";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { StorybookConfig } from "@storybook/react-vite";
import tailwindcss from "@tailwindcss/vite";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand Down Expand Up @@ -30,6 +31,8 @@ const reactScanHook = isDev
)
: "";

const apolloWindSrc = resolve(__dirname, "../../../packages/apollo-wind/src");

const config: StorybookConfig = {
stories: [
// For now only include canvas stories
Expand All @@ -45,6 +48,42 @@ const config: StorybookConfig = {
options: {},
},

viteFinal: async (config) => {
config.plugins = config.plugins || [];
config.plugins.push(tailwindcss());

// Resolve apollo-wind to source for HMR in dev.
config.resolve = config.resolve || {};
config.resolve.alias = [
...(Array.isArray(config.resolve.alias)
? config.resolve.alias
: Object.entries(config.resolve.alias || {}).map(
([find, replacement]) => ({
find,
replacement,
}),
)),
{
find: /^@uipath\/apollo-wind\/tailwind\.css$/,
replacement: resolve(apolloWindSrc, "styles/tailwind.consumer.css"),
},
{
find: /^@uipath\/apollo-wind$/,
replacement: resolve(apolloWindSrc, "index.ts"),
},
{
find: /^@uipath\/apollo-wind\/(?!.*\.css$)(.*)/,
replacement: `${apolloWindSrc}/$1`,
},
{
find: /^@\/(.*)/,
replacement: `${apolloWindSrc}/$1`,
},
];

return config;
},

previewBody: isDev
? (body) =>
`<script>delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;${reactScanHook}</script>\n${body}`
Expand Down
8 changes: 1 addition & 7 deletions apps/storybook/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider } from "@mui/material/styles";
import type { Preview } from "@storybook/react";
import {
Expand All @@ -9,7 +8,6 @@ import {
} from "@uipath/apollo-react/material/theme";
// biome-ignore lint/correctness/noUnusedImports: needed
import React, { useEffect } from "react";
import { GlobalStyles } from "./GlobalStyles";

const isDev = import.meta.env.MODE !== "production";

Expand All @@ -28,11 +26,9 @@ import "@uipath/apollo-react/core/tokens/css/theme-variables.css";
import "@uipath/apollo-react/core/fonts/font.css";

import "@uipath/apollo-react/canvas/styles/variables.css";
import "@uipath/apollo-react/canvas/styles/tailwind.canvas.css";
import "@uipath/apollo-react/canvas/xyflow/style.css";

// Import Apollo Wind pre-compiled styles for apollo-wind components used in stories
import "@uipath/apollo-wind/styles.css";

// Theme type definition
type ThemeMode = "light" | "dark" | "light-hc" | "dark-hc";

Expand Down Expand Up @@ -153,8 +149,6 @@ const preview: Preview = {

return (
<ThemeProvider theme={muiTheme}>
<CssBaseline />
<GlobalStyles />
<div
style={{
height: "100%",
Expand Down
2 changes: 2 additions & 0 deletions apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
"@storybook/addon-links": "^10.2.15",
"@storybook/react": "^10.2.15",
"@storybook/react-vite": "^10.2.15",
"@tailwindcss/vite": "^4.1.17",
"prettier": "^3.8.1",
"react-scan": "^0.5.3",
"storybook": "^10.2.15",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"vite": "^7.3.2"
}
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ import { Breadcrumb } from '@uipath/apollo-react/canvas/controls';
import { ReactFlow, Panel } from '@uipath/apollo-react/canvas/xyflow/react';
```

> Canvas components are styled with [apollo-wind](../apollo-wind) (Tailwind CSS v4). See the [Canvas Tailwind Integration Guide](./src/canvas/README.md) for how to wire up the required CSS in standard and Shadow DOM apps.

### ApChat Component

```typescript
Expand Down
12 changes: 6 additions & 6 deletions packages/apollo-react/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
"useIgnoreFile": true
},
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!**/src/icons/**"
]
"includes": ["**", "!**/node_modules", "!**/dist", "!**/src/icons/**"]
},
"formatter": {
"enabled": true,
Expand All @@ -27,6 +22,11 @@
"arrowParentheses": "always"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"linter": {
"enabled": true,
"rules": {
Expand Down
4 changes: 3 additions & 1 deletion packages/apollo-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@
"dist"
],
"scripts": {
"build": "pnpm run build:icons && pnpm run i18n:compile && rslib build",
"build": "pnpm run build:icons && pnpm run i18n:compile && rslib build && pnpm run build:css:canvas",
"build:css:canvas": "tailwindcss -i ./src/canvas/styles/tailwind.canvas.css -o ./dist/canvas/styles/tailwind.canvas.css --minify",
Comment thread
BenGSchulz marked this conversation as resolved.
"build:analyze": "ANALYZE=true pnpm run build",
"build:icons": "tsx scripts/icons-build.ts",
"dev": "rslib build --watch",
Expand Down Expand Up @@ -223,6 +224,7 @@
"zustand": "^5.0.9"
},
"devDependencies": {
"@tailwindcss/cli": "^4.1.17",
"@lingui/cli": "^5.6.1",
"@lingui/conf": "^5.6.1",
"@lingui/format-json": "^5.6.1",
Expand Down
197 changes: 197 additions & 0 deletions packages/apollo-react/src/canvas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Canvas Tailwind CSS — Consumer Integration Guide

Apollo React canvas components use [apollo-wind](../../../apollo-wind) (Tailwind CSS v4) for styling. This guide covers how consumers should integrate the required CSS depending on their application architecture.

## Background

Canvas components import from `@uipath/apollo-wind` and use Tailwind utility classes (e.g., `p-4`, `bg-surface`, `text-foreground`, `border-border`). These utilities depend on:

1. **Tailwind CSS v4 utility class definitions** — the actual `.p-4 { padding: 1rem }` rules
2. **Apollo-core theme variables** (`--color-foreground`, `--color-border`, etc.) — defined per-theme on `body.light`, `body.dark`, etc.
3. **Apollo-wind bridge variables** (`--surface`, `--brand`, `--foreground`, etc.) — map semantic names to core variables, also scoped to theme selectors

### What apollo-react ships

Apollo-react exports a pre-compiled CSS file at:

```
@uipath/apollo-react/canvas/styles/tailwind.canvas.css
```

This contains all three layers above, compiled and minified (~160KB). It is **not** auto-injected — consumers must explicitly integrate it based on their architecture.

---

## Integration Patterns

### Pattern A: Standard App (no Shadow DOM)

**Example:** PO.Frontend — Rsbuild app with SCSS, styled-components, and MUI.

#### The challenge

Tailwind v4 wraps all utility classes in `@layer utilities`. Per the CSS specification, **un-layered CSS always takes precedence over layered CSS**, regardless of source order or specificity. If your app has un-layered styles (MUI, styled-components, global SCSS), they will override Tailwind utilities on any conflicting properties.

Simply importing the pre-compiled `tailwind.canvas.css` will not work — the utilities will be overridden by your existing styles.

#### Solution: Set up Tailwind CSS processing via PostCSS

By processing Tailwind at build time through PostCSS, Tailwind owns the CSS cascade from the top of your stylesheet. This ensures utility classes work correctly while your existing styles continue to function (un-layered styles still take precedence, which is desirable for your app's own components).

**1. Install dependencies**

```bash
npm install @uipath/apollo-wind postcss
```

**2. Create `postcss.config.js`** (project root)

```js
import apolloWindConfig from "@uipath/apollo-wind/postcss";

export default {
plugins: [...apolloWindConfig.plugins],
};
```

Rsbuild automatically detects and uses `postcss.config.js` — no additional build config is needed.

**3. Create a CSS entry point** (e.g., `src/tailwind.css`)

```css
@import "@uipath/apollo-wind/tailwind.css";

/* Scan apollo-wind and apollo-react canvas components for Tailwind class usage */
@source "../node_modules/@uipath/apollo-wind";
@source "../node_modules/@uipath/apollo-react/dist/canvas";
```

The `@source` directives tell Tailwind which files to scan for class names. Only utilities actually used by these components are included in the output.

**4. Import in your app entry** (e.g., `bootstrap.tsx`)

```tsx
import "./tailwind.css";
```

Import it early, before component code, so the CSS is available when components render.

#### How specificity works after setup

| Style source | Layered? | Priority |
|---|---|---|
| MUI / styled-components | No (un-layered) | Highest — always wins |
| App SCSS / global CSS | No (un-layered) | High |
| Tailwind `@layer utilities` | Yes | Normal — wins over other layers |
| Tailwind `@layer base` | Yes | Low |
| CSS reset (`@layer reset`) | Yes | Lowest |

Apollo-wind components in the canvas use **only** Tailwind classes. As long as your app's MUI/styled-component styles don't target the same elements, there are no conflicts. The two styling systems coexist on separate parts of the DOM.

---

### Pattern B: Shadow DOM App

**Example:** Agents/frontend-sw — Emotion/MUI app rendering canvas inside a Shadow DOM boundary.

#### The challenge

Shadow DOM provides style encapsulation — CSS from the outer document does not apply inside a shadow root. This creates two problems:

1. **Utility classes must be injected into the shadow root.** Global `<style>` or `<link>` tags in the document head won't reach shadow DOM content.

2. **Theme CSS variables must be available inside the shadow root.** The apollo-core theme variables (`--color-foreground`, `--color-border`, etc.) are defined on `body.light` / `body.dark` in the outer document. CSS custom properties **do inherit** through the shadow DOM boundary, so these are available. However, the **bridge variables** (`--surface`, `--brand`, `--foreground`, etc.) are scoped to `.light` / `.dark` selectors in the Tailwind CSS — these selectors won't match inside the shadow DOM.

#### Solution: Inline CSS injection with theme class wrapper

**1. Import the pre-compiled CSS as an inline string**

Using your bundler's `?inline` query (Rsbuild/Vite):

```tsx
import TailwindCanvasCSS from "@uipath/apollo-react/canvas/styles/tailwind.canvas.css?inline";
```

**2. Ensure a theme class wrapper exists inside the shadow DOM**

The bridge variables are defined under selectors like `body.light, .light { ... }` and `body.dark, .dark { ... }`. For these to activate inside the shadow DOM, an ancestor element within the shadow root must have the appropriate theme class.

Add a wrapper element with `apollo-design` and the active theme class:

```tsx
// Inside your shadow DOM component
<div className={`apollo-design ${theme}`}>
{/* Canvas and other content */}
</div>
```

The compiled CSS includes selectors for both `body.light` and `.apollo-design.light` (and their dark/hc variants), so this wrapper activates all theme variable definitions.

**3. Inject the CSS into the shadow root**

Using Emotion's `<Global>` component (if your Emotion cache targets the shadow root):

```tsx
<Global styles={[TailwindCanvasCSS, /* other CSS */]} />
```

Or by creating a `<style>` element directly:

```tsx
const styleElement = document.createElement("style");
styleElement.textContent = TailwindCanvasCSS;
shadowRoot.prepend(styleElement);
```

#### Variable resolution chain inside Shadow DOM

```
Outer document: body.light { --color-foreground: #273139; ... }
↓ (CSS custom property inheritance)
Shadow root: <div class="apollo-design light">
→ .apollo-design.light { --color-foreground: #273139; } (core vars, redundant but ensures availability)
→ .light { --surface: var(--color-background); ... } (bridge vars, NOW active)
→ .bg-surface { background-color: var(--surface); } (utility classes, NOW resolved)
```

#### Existing canvas variables

If you already use `@uipath/apollo-react/canvas/styles/variables.css` with shadow DOM re-scoping (as in `canvas-styles.utils.ts`), that pattern continues to work alongside the Tailwind CSS. The canvas variables (`--uix-canvas-*`) and the bridge variables (`--surface`, `--brand`, etc.) are independent — both reference the same underlying `--color-*` tokens.

---

## Exported CSS files

| Export path | Contents | Use case |
|---|---|---|
| `@uipath/apollo-react/canvas/styles/tailwind.canvas.css` | Pre-compiled Tailwind utilities + theme variables (~160KB) | Shadow DOM consumers (inline import) |
| `@uipath/apollo-react/canvas/styles/variables.css` | Canvas-specific CSS variable mappings (`--uix-canvas-*`) | All consumers (canvas component theming) |
| `@uipath/apollo-react/canvas/xyflow/style.css` | React Flow base styles | All consumers (canvas rendering) |
| `@uipath/apollo-wind/tailwind.css` | Tailwind CSS v4 entry point with Apollo theme | Standard apps via PostCSS processing |
| `@uipath/apollo-wind/postcss` | PostCSS plugin config export | Standard apps (postcss.config.js) |

## Troubleshooting

### Tailwind utility classes have no effect

**Symptom:** Elements have Tailwind classes (e.g., `p-4`) but the styles don't appear in DevTools, or appear but are overridden.

**Cause:** Un-layered CSS from MUI, styled-components, or global SCSS overrides `@layer utilities`.

**Fix:** Use Pattern A (PostCSS setup). Do not import the pre-compiled CSS directly in non-shadow-DOM apps.

### CSS variables are undefined inside Shadow DOM

**Symptom:** Elements show `var(--surface)` or `var(--color-foreground)` as undefined in computed styles.

**Cause:** Bridge variables are scoped to `.light` / `.dark` selectors that don't match inside the shadow root.

**Fix:** Add a wrapper element with the theme class inside the shadow DOM (see Pattern B, step 2).

### Borders or backgrounds look wrong

**Symptom:** Unexpected border colors or missing backgrounds on canvas components.

**Cause:** The Tailwind base layer includes `* { border-color: var(--color-border-de-emp) }` and `body { background-color: var(--background) }`. These may conflict with existing component styles.

**Fix:** These rules are in `@layer base`, so they lose to un-layered styles. If conflicts persist in Shadow DOM, override with more specific selectors in your injected CSS.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import type { NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
import { Handle, Position } from '@uipath/apollo-react/canvas/xyflow/react';
import { ApIcon } from '@uipath/apollo-react/material/components';
import type React from 'react';

import { DEFAULT_NODE_SIZE } from '../../constants';
import { CanvasIcon } from '../../utils/icon-registry';

const PreviewContainer = styled.div<{ selected?: boolean; width?: number; height?: number }>`
width: ${(props) => props.width ?? DEFAULT_NODE_SIZE}px;
Expand All @@ -29,10 +30,10 @@ export interface AddNodePreviewData {

const getIcon = (iconName?: string): React.ReactElement => {
if (iconName) {
return <ApIcon color="var(--uix-canvas-foreground-de-emp)" name={iconName} size="40px" />;
return <CanvasIcon icon={iconName} size={40} color="var(--uix-canvas-foreground-de-emp)" />;
}

return <ApIcon color="var(--uix-canvas-foreground-de-emp)" name="more_horiz" size="40px" />;
return <CanvasIcon icon="ellipsis" size={40} color="var(--uix-canvas-foreground-de-emp)" />;
};

export const AddNodePreview: React.FC<NodeProps> = ({ selected, data, width, height }) => {
Expand Down
Loading
Loading