Skip to content

Commit fccbdcb

Browse files
committed
feat(apollo-react): full canvas wind integration
Replace all Ap- components in canvas with apollo-wind counterparts, add status variants for badge, componentize dropdown menu, and configure apollo-wind tailwind/HMR support in storybook.
1 parent 2df721d commit fccbdcb

61 files changed

Lines changed: 1223 additions & 954 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/storybook/.storybook/main.ts

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import type { StorybookConfig } from "@storybook/react-vite";
2-
import { readFileSync } from "node:fs";
3-
import { dirname, resolve } from "node:path";
4-
import { fileURLToPath } from "node:url";
1+
import { readFileSync } from 'node:fs';
2+
import { dirname, resolve } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import type { StorybookConfig } from '@storybook/react-vite';
5+
import tailwindcss from '@tailwindcss/vite';
56

67
const __filename = fileURLToPath(import.meta.url);
78
const __dirname = dirname(__filename);
89

910
// react-scan is a dev-only tool — skip it in production Storybook builds.
1011
// main.ts runs in Node (Storybook CLI) so we use process.env.
1112
// preview.tsx runs in the browser (Vite) so it uses import.meta.env.MODE instead.
12-
const isDev = process.env.NODE_ENV !== "production";
13+
const isDev = process.env.NODE_ENV !== 'production';
1314

1415
// react-scan must install its devtools hook before React initializes.
1516
// Two Storybook behaviors interfere with this:
@@ -22,29 +23,62 @@ const isDev = process.env.NODE_ENV !== "production";
2223
// (React is loaded via type="module" scripts, which are deferred).
2324
const reactScanHook = isDev
2425
? readFileSync(
25-
resolve(
26-
__dirname,
27-
"../node_modules/react-scan/dist/install-hook.global.js",
28-
),
29-
"utf-8",
26+
resolve(__dirname, '../node_modules/react-scan/dist/install-hook.global.js'),
27+
'utf-8'
3028
)
31-
: "";
29+
: '';
30+
31+
const apolloWindSrc = resolve(__dirname, '../../../packages/apollo-wind/src');
3232

3333
const config: StorybookConfig = {
3434
stories: [
3535
// For now only include canvas stories
3636
{
37-
directory: "../../../packages/apollo-react/src/canvas",
38-
files: "**/*.stories.@(tsx|ts|jsx|js|mdx)",
39-
titlePrefix: "Canvas",
37+
directory: '../../../packages/apollo-react/src/canvas',
38+
files: '**/*.stories.@(tsx|ts|jsx|js|mdx)',
39+
titlePrefix: 'Canvas',
4040
},
4141
],
4242
addons: [],
4343
framework: {
44-
name: "@storybook/react-vite",
44+
name: '@storybook/react-vite',
4545
options: {},
4646
},
4747

48+
viteFinal: async (config) => {
49+
config.plugins = config.plugins || [];
50+
config.plugins.push(tailwindcss());
51+
52+
// Resolve apollo-wind to source for HMR in dev.
53+
config.resolve = config.resolve || {};
54+
config.resolve.alias = [
55+
...(Array.isArray(config.resolve.alias)
56+
? config.resolve.alias
57+
: Object.entries(config.resolve.alias || {}).map(([find, replacement]) => ({
58+
find,
59+
replacement,
60+
}))),
61+
{
62+
find: /^@uipath\/apollo-wind\/tailwind\.css$/,
63+
replacement: resolve(apolloWindSrc, 'styles/tailwind.consumer.css'),
64+
},
65+
{
66+
find: /^@uipath\/apollo-wind$/,
67+
replacement: resolve(apolloWindSrc, 'index.ts'),
68+
},
69+
{
70+
find: /^@uipath\/apollo-wind\/(?!.*\.css$)(.*)/,
71+
replacement: `${apolloWindSrc}/$1`,
72+
},
73+
{
74+
find: /^@\/(.*)/,
75+
replacement: `${apolloWindSrc}/$1`,
76+
},
77+
];
78+
79+
return config;
80+
},
81+
4882
previewBody: isDev
4983
? (body) =>
5084
`<script>delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;${reactScanHook}</script>\n${body}`

apps/storybook/.storybook/preview.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import CssBaseline from "@mui/material/CssBaseline";
21
import { ThemeProvider } from "@mui/material/styles";
32
import type { Preview } from "@storybook/react";
43
import {
@@ -9,7 +8,6 @@ import {
98
} from "@uipath/apollo-react/material/theme";
109
// biome-ignore lint/correctness/noUnusedImports: needed
1110
import React, { useEffect } from "react";
12-
import { GlobalStyles } from "./GlobalStyles";
1311

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

@@ -30,9 +28,6 @@ import "@uipath/apollo-react/core/fonts/font.css";
3028
import "@uipath/apollo-react/canvas/styles/variables.css";
3129
import "@uipath/apollo-react/canvas/xyflow/style.css";
3230

33-
// Import Apollo Wind pre-compiled styles for apollo-wind components used in stories
34-
import "@uipath/apollo-wind/styles.css";
35-
3631
// Theme type definition
3732
type ThemeMode = "light" | "dark" | "light-hc" | "dark-hc";
3833

@@ -153,8 +148,6 @@ const preview: Preview = {
153148

154149
return (
155150
<ThemeProvider theme={muiTheme}>
156-
<CssBaseline />
157-
<GlobalStyles />
158151
<div
159152
style={{
160153
height: "100%",

apps/storybook/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
"@storybook/addon-links": "^10.2.15",
2727
"@storybook/react": "^10.2.15",
2828
"@storybook/react-vite": "^10.2.15",
29+
"@tailwindcss/vite": "^4.1.17",
2930
"prettier": "^3.8.1",
3031
"react-scan": "^0.5.3",
3132
"storybook": "^10.2.15",
33+
"tailwindcss": "^4.1.17",
3234
"typescript": "^5.9.3",
3335
"vite": "^7.3.2"
3436
}

packages/apollo-react/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@
129129
"dist"
130130
],
131131
"scripts": {
132-
"build": "pnpm run build:icons && pnpm run i18n:compile && rslib build",
132+
"build": "pnpm run build:icons && pnpm run i18n:compile && rslib build && pnpm run build:css:canvas",
133+
"build:css:canvas": "tailwindcss -i ./src/canvas/styles/tailwind.canvas.css -o ./dist/canvas/styles/wind.css --minify",
133134
"build:analyze": "ANALYZE=true pnpm run build",
134135
"build:icons": "tsx scripts/icons-build.ts",
135136
"dev": "rslib build --watch",
@@ -223,6 +224,7 @@
223224
"zustand": "^5.0.9"
224225
},
225226
"devDependencies": {
227+
"@tailwindcss/cli": "^4.1.17",
226228
"@lingui/cli": "^5.6.1",
227229
"@lingui/conf": "^5.6.1",
228230
"@lingui/format-json": "^5.6.1",

packages/apollo-react/src/canvas/components/AddNodePanel/AddNodePreview.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import styled from '@emotion/styled';
22
import type { NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
33
import { Handle, Position } from '@uipath/apollo-react/canvas/xyflow/react';
4-
import { ApIcon } from '@uipath/apollo-react/material/components';
54
import type React from 'react';
5+
66
import { DEFAULT_NODE_SIZE } from '../../constants';
7+
import { CanvasIcon } from '../../utils/icon-registry';
78

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

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

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

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

packages/apollo-react/src/canvas/components/AgentCanvas/components/SuggestionGroupPanel.tsx

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { useTheme } from '@mui/material';
2-
import { FontVariantToken, Spacing } from '@uipath/apollo-core';
1+
import { Spacing } from '@uipath/apollo-core';
32
import { Column, Row } from '@uipath/apollo-react/canvas/layouts';
43
import { useStore } from '@uipath/apollo-react/canvas/xyflow/react';
5-
import { ApButton, ApIcon, ApIconButton, ApTypography } from '@uipath/apollo-react/material';
4+
import { Button, cn } from '@uipath/apollo-wind';
65
import { useState } from 'react';
7-
6+
import { CANVAS_COMPACT_BREAKPOINT } from '../../../constants';
87
import type { AgentFlowSuggestionGroup } from '../../../types';
8+
import { CanvasIcon } from '../../../utils/icon-registry';
99

1010
interface SuggestionGroupPanelProps {
1111
suggestionGroup?: AgentFlowSuggestionGroup | null;
@@ -23,25 +23,26 @@ interface AcceptRejectAllButtonProps {
2323
}
2424

2525
const RejectAllButton = ({ suggestionGroup, onClick, compact }: AcceptRejectAllButtonProps) => (
26-
<ApButton
27-
variant="tertiary"
28-
size="small"
29-
label="Reject all"
30-
startIcon={<ApIcon variant="outlined" name="close" />}
26+
<Button
27+
variant="ghost"
28+
size="sm"
3129
onClick={() => onClick(suggestionGroup.id)}
32-
sx={compact ? { fontSize: '0.75rem', minWidth: 'auto' } : undefined}
33-
/>
30+
className={compact ? 'text-xs min-w-0' : undefined}
31+
>
32+
<CanvasIcon icon="x" size={16} />
33+
Reject all
34+
</Button>
3435
);
3536

3637
const AcceptAllButton = ({ suggestionGroup, onClick, compact }: AcceptRejectAllButtonProps) => (
37-
<ApButton
38-
variant="primary"
39-
size="small"
40-
label="Accept all"
41-
startIcon={<ApIcon variant="outlined" name="check" />}
38+
<Button
39+
size="sm"
4240
onClick={() => onClick(suggestionGroup.id)}
43-
sx={compact ? { fontSize: '0.75rem', minWidth: 'auto' } : undefined}
44-
/>
41+
className={compact ? 'text-xs min-w-0' : undefined}
42+
>
43+
<CanvasIcon icon="check" size={16} />
44+
Accept all
45+
</Button>
4546
);
4647

4748
const Divider = () => (
@@ -72,7 +73,7 @@ const SuggestionGroupNavigator = ({
7273
const [isHoveringUp, setIsHoveringUp] = useState(false);
7374
const [isHoveringDown, setIsHoveringDown] = useState(false);
7475

75-
const iconSize = compact ? '16px' : '20px';
76+
const iconSize = compact ? 16 : 20;
7677

7778
return (
7879
<div
@@ -83,36 +84,41 @@ const SuggestionGroupNavigator = ({
8384
minWidth: compact ? '80px' : '100px',
8485
}}
8586
>
86-
<ApIconButton
87+
<Button
88+
variant="ghost"
89+
size="icon"
90+
className="h-8 w-8"
8791
onMouseEnter={() => setIsHoveringUp(true)}
8892
onMouseLeave={() => setIsHoveringUp(false)}
8993
onClick={onNavigatePrevious}
9094
>
91-
<ApIcon
92-
name="keyboard_arrow_up"
95+
<CanvasIcon
96+
icon="chevron-up"
9397
color={isHoveringUp ? 'var(--uix-canvas-primary)' : 'var(--uix-canvas-foreground-de-emp)'}
9498
size={iconSize}
9599
/>
96-
</ApIconButton>
97-
<ApTypography
98-
variant={compact ? FontVariantToken.fontSizeSBold : FontVariantToken.fontSizeMBold}
99-
color="var(--uix-canvas-foreground-de-emp)"
100+
</Button>
101+
<span
102+
className={cn('font-bold text-[var(--uix-canvas-foreground-de-emp)]', compact ? 'text-xs' : 'text-sm')}
100103
>
101104
{currentIndex + 1} of {total}
102-
</ApTypography>
103-
<ApIconButton
105+
</span>
106+
<Button
107+
variant="ghost"
108+
size="icon"
109+
className="h-8 w-8"
104110
onMouseEnter={() => setIsHoveringDown(true)}
105111
onMouseLeave={() => setIsHoveringDown(false)}
106112
onClick={onNavigateNext}
107113
>
108-
<ApIcon
109-
name="keyboard_arrow_down"
114+
<CanvasIcon
115+
icon="chevron-down"
110116
color={
111117
isHoveringDown ? 'var(--uix-canvas-primary)' : 'var(--uix-canvas-foreground-de-emp)'
112118
}
113119
size={iconSize}
114120
/>
115-
</ApIconButton>
121+
</Button>
116122
</div>
117123
);
118124
};
@@ -125,10 +131,8 @@ export const SuggestionGroupPanel = ({
125131
onNavigateNext,
126132
onNavigatePrevious,
127133
}: SuggestionGroupPanelProps) => {
128-
const theme = useTheme();
129134
const canvasWidth = useStore((state) => state.width);
130-
const smBreakpoint = theme.breakpoints.values.sm;
131-
const isCompact = canvasWidth < smBreakpoint;
135+
const isCompact = canvasWidth < CANVAS_COMPACT_BREAKPOINT;
132136

133137
// Filter out standalone suggestions - they are interactive placeholders that shouldn't appear in the panel
134138
const nonStandaloneSuggestions =

packages/apollo-react/src/canvas/components/AgentCanvas/components/TimelinePlayer.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { FontVariantToken, Spacing } from '@uipath/apollo-core';
1+
import { Spacing } from '@uipath/apollo-core';
22
import * as Icons from '@uipath/apollo-react/canvas/icons';
33
import { Column, Row } from '@uipath/apollo-react/canvas/layouts';
4-
import { ApIconButton, ApTypography } from '@uipath/apollo-react/material';
4+
import { Button } from '@uipath/apollo-wind';
55
import { DateTime } from 'luxon';
66
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
77

@@ -480,25 +480,25 @@ export const TimelinePlayer: React.FC<{
480480
}}
481481
>
482482
<Row align="center" gap={Spacing.SpacingXs}>
483-
<ApIconButton size="large" onClick={handlePlayPause}>
483+
<Button variant="ghost" size="icon" className="h-10 w-10" onClick={handlePlayPause}>
484484
{playing ? (
485485
<Icons.TimelinePauseIcon w={24} h={24} color="var(--uix-canvas-icon-default)" />
486486
) : (
487487
<Icons.TimelinePlayIcon w={24} h={24} color="var(--uix-canvas-icon-default)" />
488488
)}
489-
</ApIconButton>
489+
</Button>
490490

491-
<ApTypography>
491+
<span>
492492
{currentTimeFormatted} / {totalTimeFormatted}
493-
</ApTypography>
493+
</span>
494494

495-
<ApTypography>{mostActiveSpan?.Name}</ApTypography>
495+
<span>{mostActiveSpan?.Name}</span>
496496

497497
<div style={{ flex: 1 }} />
498498

499-
<ApIconButton size="large" onClick={handleSpeedChange}>
500-
<ApTypography variant={FontVariantToken.fontSizeSBold}>{speedLevel}x</ApTypography>
501-
</ApIconButton>
499+
<Button variant="ghost" size="icon" className="h-10 w-10" onClick={handleSpeedChange}>
500+
<span className="text-xs font-bold">{speedLevel}x</span>
501+
</Button>
502502
</Row>
503503

504504
<Row align="center" style={{ marginLeft: '15px' }}>

packages/apollo-react/src/canvas/components/AgentCanvas/nodes/AgentNode.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import * as Icons from '@uipath/apollo-react/canvas/icons';
22
import type { Node, NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
33
import { Position } from '@uipath/apollo-react/canvas/xyflow/react';
4-
import { ApIcon } from '@uipath/apollo-react/material/components';
54
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
6-
import { FloatingCanvasPanel } from '../../FloatingCanvasPanel';
5+
import type { HandleGroupManifest } from '../../../schema/node-definition';
76
import {
87
type AgentNodeTranslations,
98
DefaultSuggestionTranslations,
109
type SuggestionTranslations,
1110
type SuggestionType,
1211
} from '../../../types';
12+
import { CanvasIcon } from '../../../utils/icon-registry';
1313
import { BaseNode } from '../../BaseNode/BaseNode';
1414
import type { BaseNodeData } from '../../BaseNode/BaseNode.types';
1515
import {
1616
BaseNodeOverrideConfigProvider,
1717
type BaseNodeOverrideConfig,
1818
} from '../../BaseNode/BaseNodeConfigContext';
1919
import type { ButtonHandleConfig, HandleActionEvent } from '../../ButtonHandle/ButtonHandle';
20-
import type { HandleGroupManifest } from '../../../schema/node-definition';
20+
import { FloatingCanvasPanel } from '../../FloatingCanvasPanel';
2121
import type { NodeToolbarConfig, ToolbarAction } from '../../Toolbar';
2222
import { ResourceNodeType } from '../AgentFlow.constants';
2323
import { useAgentFlowStore } from '../store/agent-flow-store';
@@ -413,7 +413,7 @@ const AgentNodeComponent = memo((props: NodeProps<Node<AgentNodeData>> & AgentNo
413413
onAddInstructions();
414414
}}
415415
>
416-
<ApIcon name="add" size="14px" />
416+
<CanvasIcon icon="plus" size={14} />
417417
{translations.addInstructions}
418418
</AddInstructionsButton>
419419
),

0 commit comments

Comments
 (0)