diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 6c6b596c..2665abfa 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -1,5 +1,6 @@ import Basic from '../examples/Basic'; import Backgrounds from '../examples/Backgrounds'; +import ColorMode from '../examples/ColorMode'; import ControlledUncontrolled from '../examples/ControlledUncontrolled'; import ControlledViewport from '../examples/ControlledViewport'; import CustomConnectionLine from '../examples/CustomConnectionLine'; @@ -62,6 +63,11 @@ const routes: IRoute[] = [ path: 'backgrounds', component: Backgrounds, }, + { + name: 'Color Mode', + path: 'color-mode', + component: ColorMode, + }, { name: 'Cancel Connection', path: 'cancel-connection', diff --git a/examples/react/src/examples/ColorMode/index.tsx b/examples/react/src/examples/ColorMode/index.tsx new file mode 100644 index 00000000..f867c650 --- /dev/null +++ b/examples/react/src/examples/ColorMode/index.tsx @@ -0,0 +1,88 @@ +import { ChangeEventHandler, useCallback, useState } from 'react'; +import { + ReactFlow, + addEdge, + Node, + useNodesState, + useEdgesState, + OnConnect, + Edge, + MiniMap, + Background, + Controls, + Panel, + ColorMode, + Position, +} from '@xyflow/react'; + +const nodeDefaults = { + sourcePosition: Position.Right, + targetPosition: Position.Left, +}; + +const initialNodes: Node[] = [ + { id: 'A', type: 'input', position: { x: 0, y: 150 }, data: { label: 'A' }, ...nodeDefaults }, + { id: 'B', position: { x: 250, y: 0 }, data: { label: 'B' }, ...nodeDefaults }, + { id: 'C', position: { x: 250, y: 150 }, data: { label: 'C' }, ...nodeDefaults }, + { id: 'D', position: { x: 250, y: 300 }, data: { label: 'D' }, ...nodeDefaults }, +]; + +const initialEdges: Edge[] = [ + { + id: 'A-B', + source: 'A', + target: 'B', + }, + { + id: 'A-C', + source: 'A', + target: 'C', + }, + { + id: 'A-D', + source: 'A', + target: 'D', + }, +]; + +const ColorModeFlow = () => { + const [colorMode, setColorMode] = useState('light'); + const [nodes, , onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + const onConnect: OnConnect = useCallback( + (params) => { + console.log('on connect', params); + setEdges((eds) => addEdge(params, eds)); + }, + [setEdges] + ); + + const onChange: ChangeEventHandler = (evt) => setColorMode(evt.target.value as ColorMode); + + return ( + + + + + + + + + + ); +}; + +export default ColorModeFlow; diff --git a/examples/react/src/examples/EasyConnect/index.tsx b/examples/react/src/examples/EasyConnect/index.tsx index 7e600848..11e347f1 100644 --- a/examples/react/src/examples/EasyConnect/index.tsx +++ b/examples/react/src/examples/EasyConnect/index.tsx @@ -5,7 +5,6 @@ import CustomNode from './CustomNode'; import FloatingEdge from './FloatingEdge'; import CustomConnectionLine from './CustomConnectionLine'; -import '@xyflow/react/dist/style.css'; import './style.css'; const initialNodes: Node[] = [ diff --git a/examples/react/src/examples/NodeResizer/index.tsx b/examples/react/src/examples/NodeResizer/index.tsx index e276996c..11ce29d2 100644 --- a/examples/react/src/examples/NodeResizer/index.tsx +++ b/examples/react/src/examples/NodeResizer/index.tsx @@ -16,8 +16,6 @@ import CustomResizer from './CustomResizer'; import VerticalResizer from './VerticalResizer'; import HorizontalResizer from './HorizontalResizer'; -import '@xyflow/react/dist/style.css'; - const nodeTypes = { defaultResizer: DefaultResizer, customResizer: CustomResizer, diff --git a/examples/react/src/examples/Overview/index.tsx b/examples/react/src/examples/Overview/index.tsx index 09110529..e4879ace 100644 --- a/examples/react/src/examples/Overview/index.tsx +++ b/examples/react/src/examples/Overview/index.tsx @@ -23,7 +23,7 @@ const onNodeDragStop = (_: ReactMouseEvent, node: Node, nodes: Node[]) => consol const onNodeDoubleClick = (_: ReactMouseEvent, node: Node) => console.log('node double click', node); const onPaneClick = (event: ReactMouseEvent) => console.log('pane click', event); const onPaneScroll = (event?: ReactMouseEvent) => console.log('pane scroll', event); -const onPaneContextMenu = (event: ReactMouseEvent) => console.log('pane context menu', event); +const onPaneContextMenu = (event: ReactMouseEvent | MouseEvent) => console.log('pane context menu', event); const onSelectionDrag = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag', nodes); const onSelectionDragStart = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag start', nodes); const onSelectionDragStop = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag stop', nodes); @@ -230,9 +230,9 @@ const OverviewFlow = () => { onEdgesDelete={onEdgesDelete} onPaneMouseMove={onPaneMouseMove} > - + - + ); }; diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx index 663a29b5..4fdcd68a 100644 --- a/examples/react/src/main.tsx +++ b/examples/react/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import App from './App'; +import App from './App/index'; import '@xyflow/react/dist/style.css'; import './index.css'; diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte index fde20817..7d229796 100644 --- a/examples/svelte/src/components/Header/Header.svelte +++ b/examples/svelte/src/components/Header/Header.svelte @@ -4,6 +4,7 @@ const routes = [ 'add-node-on-drop', + 'colormode', 'custom-connection-line', 'customnode', 'dagre', diff --git a/examples/svelte/src/routes/examples/color-mode/+page.svelte b/examples/svelte/src/routes/examples/color-mode/+page.svelte new file mode 100644 index 00000000..59ddfdfd --- /dev/null +++ b/examples/svelte/src/routes/examples/color-mode/+page.svelte @@ -0,0 +1,54 @@ + + + + + + + + + + + diff --git a/examples/svelte/src/routes/examples/edges/+page.svelte b/examples/svelte/src/routes/examples/edges/+page.svelte index fc118333..403b0ef6 100644 --- a/examples/svelte/src/routes/examples/edges/+page.svelte +++ b/examples/svelte/src/routes/examples/edges/+page.svelte @@ -145,7 +145,7 @@ ]); - + diff --git a/packages/react/src/additional-components/Background/Patterns.tsx b/packages/react/src/additional-components/Background/Patterns.tsx index 1fc71ae5..61715c5a 100644 --- a/packages/react/src/additional-components/Background/Patterns.tsx +++ b/packages/react/src/additional-components/Background/Patterns.tsx @@ -26,6 +26,6 @@ type DotPatternProps = { export function DotPattern({ radius, className }: DotPatternProps) { return ( - + ); } diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index f56ae57f..33848836 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useRef, type MouseEvent, useCallback } from 'react'; +import { memo, useEffect, useRef, type MouseEvent, useCallback, CSSProperties } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; import { getNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system'; @@ -40,15 +40,15 @@ const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; function MiniMap({ style, className, - nodeStrokeColor = 'transparent', - nodeColor = '#e2e2e2', + nodeStrokeColor, + nodeColor, nodeClassName = '', nodeBorderRadius = 5, - nodeStrokeWidth = 2, + nodeStrokeWidth, // We need to rename the prop to be `CapitalCase` so that JSX will render it as // a component properly. nodeComponent, - maskColor = 'rgb(240, 240, 240, 0.6)', + maskColor, maskStrokeColor = 'none', maskStrokeWidth = 1, position = 'bottom-right', @@ -126,7 +126,15 @@ function MiniMap({ return ( @@ -153,7 +161,6 @@ function MiniMap({ className="react-flow__minimap-mask" d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`} - fill={maskColor} fillRule="evenodd" stroke={maskStrokeColor} strokeWidth={maskStrokeWidth} diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNode.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNode.tsx index 5fe70462..76edf5e6 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNode.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNode.tsx @@ -31,9 +31,11 @@ function MiniMapNode({ ry={borderRadius} width={width} height={height} - fill={fill} - stroke={strokeColor} - strokeWidth={strokeWidth} + style={{ + fill, + stroke: strokeColor, + strokeWidth, + }} shapeRendering={shapeRendering} onClick={onClick ? (event) => onClick(event, id) : undefined} /> diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index d72700a7..80ac09be 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -19,11 +19,11 @@ const selectorNodes = (s: ReactFlowState) => const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); function MiniMapNodes({ - nodeStrokeColor = 'transparent', - nodeColor = '#e2e2e2', + nodeStrokeColor, + nodeColor, nodeClassName = '', nodeBorderRadius = 5, - nodeStrokeWidth = 2, + nodeStrokeWidth, // We need to rename the prop to be `CapitalCase` so that JSX will render it as // a component properly. nodeComponent: NodeComponent = MiniMapNode, @@ -41,6 +41,8 @@ function MiniMapNodes({ <> {nodes.map((node) => { const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute; + const color = nodeColor === undefined ? undefined : nodeColorFunc(node); + const strokeColor = nodeStrokeColor === undefined ? undefined : nodeStrokeColorFunc(node); return ( void; diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index d7995a53..1b3dc796 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -23,6 +23,7 @@ import A11yDescriptions from '../../components/A11yDescriptions'; import GraphView from '../GraphView'; import Wrapper from './Wrapper'; import type { EdgeTypes, NodeTypes, ReactFlowProps, ReactFlowRefType } from '../../types'; +import useColorModeClass from '../../hooks/useColorModeClass'; const defaultNodeTypes: NodeTypes = { input: InputNode, @@ -169,18 +170,20 @@ const ReactFlow = forwardRef( onViewportChange, width, height, + colorMode = 'light', ...rest }, ref ) => { const rfId = id || '1'; + const colorModeClassName = useColorModeClass(colorMode); return (
diff --git a/packages/react/src/hooks/useColorModeClass.ts b/packages/react/src/hooks/useColorModeClass.ts new file mode 100644 index 00000000..b66f8cea --- /dev/null +++ b/packages/react/src/hooks/useColorModeClass.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; +import type { ColorMode, ColorModeClass } from '@xyflow/system'; + +function getMediaQuery() { + if (typeof window === 'undefined' || !window.matchMedia) { + return null; + } + + return window.matchMedia('(prefers-color-scheme: dark)'); +} + +export default function useColorModeClass(colorMode: ColorMode): ColorModeClass { + const [colorModeClass, setColorModeClass] = useState( + colorMode === 'system' ? null : colorMode + ); + + useEffect(() => { + if (colorMode !== 'system') { + setColorModeClass(colorMode); + return; + } + + const mediaQuery = getMediaQuery(); + const updateColorModeClass = () => setColorModeClass(mediaQuery?.matches ? 'dark' : 'light'); + + updateColorModeClass(); + mediaQuery?.addEventListener('change', updateColorModeClass); + + return () => { + mediaQuery?.removeEventListener('change', updateColorModeClass); + }; + }, [colorMode]); + + return colorModeClass !== null ? colorModeClass : getMediaQuery()?.matches ? 'dark' : 'light'; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index be3de1b5..4fe922e5 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -73,6 +73,8 @@ export { type Box, type Transform, type CoordinateExtent, + type ColorMode, + type ColorModeClass, } from '@xyflow/system'; // system utils diff --git a/packages/react/src/styles/base.css b/packages/react/src/styles/base.css index d6be5b10..4a7281dc 100644 --- a/packages/react/src/styles/base.css +++ b/packages/react/src/styles/base.css @@ -1,3 +1,21 @@ /* this will be exported as base.css and can be used for a basic styling */ @import '../../../system/src/styles/init.css'; @import '../../../system/src/styles/base.css'; + +.react-flow { + --edge-label-background-color-default: #ffffff; + --edge-label-color-default: inherit; +} + +.react-flow.dark { + --edge-label-background-color-default: #141414; + --edge-label-color-default: #f8f8f8; +} + +.react-flow__edge-textbg { + fill: var(--edge-label-background-color, var(--edge-label-background-color-default)); +} + +.react-flow__edge-text { + fill: var(--edge-label-color, var(--edge-label-color-default)); +} diff --git a/packages/react/src/styles/style.css b/packages/react/src/styles/style.css index 406af4e9..2ff7f6c1 100644 --- a/packages/react/src/styles/style.css +++ b/packages/react/src/styles/style.css @@ -2,3 +2,21 @@ @import '../../../system/src/styles/init.css'; @import '../../../system/src/styles/style.css'; @import '../../../system/src/styles/node-resizer.css'; + +.react-flow { + --edge-label-background-color-default: #ffffff; + --edge-label-color-default: inherit; +} + +.react-flow.dark { + --edge-label-background-color-default: #141414; + --edge-label-color-default: #f8f8f8; +} + +.react-flow__edge-textbg { + fill: var(--edge-label-background-color, var(--edge-label-background-color-default)); +} + +.react-flow__edge-text { + fill: var(--edge-label-color, var(--edge-label-color-default)); +} diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 583b3d86..770432f5 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -19,6 +19,7 @@ import type { SelectionMode, OnError, IsValidConnection, + ColorMode, } from '@xyflow/system'; import type { @@ -155,6 +156,7 @@ export type ReactFlowProps = Omit, 'onError'> & { nodeDragThreshold?: number; width?: number; height?: number; + colorMode?: ColorMode; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index 10c1751e..dd72ec3b 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -15,8 +15,14 @@ import { Attribution } from '$lib/components/Attribution'; import { key, useStore, createStoreContext } from '$lib/store'; import type { SvelteFlowProps } from './types'; - import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils'; + import { + updateStore, + updateStoreByKeys, + type UpdatableStoreProps, + getColorModeClass + } from './utils'; import { get } from 'svelte/store'; + import { useColorModeClass } from '$lib/hooks/useColorModeClass'; type $$Props = SvelteFlowProps; @@ -70,6 +76,7 @@ export let defaultEdgeOptions: $$Props['defaultEdgeOptions'] = undefined; export let width: $$Props['width'] = undefined; export let height: $$Props['height'] = undefined; + export let colorMode: $$Props['colorMode'] = 'light'; export let defaultMarkerColor = '#b1b1b7'; @@ -156,6 +163,8 @@ maxZoom, translateExtent }); + + $: colorModeClass = useColorModeClass(colorMode);
& { defaultEdgeOptions?: DefaultEdgeOptions; width?: number; height?: number; + colorMode?: ColorMode; class?: string; style?: string; diff --git a/packages/svelte/src/lib/container/SvelteFlow/utils.ts b/packages/svelte/src/lib/container/SvelteFlow/utils.ts index fe5a15aa..9abdb3e9 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/utils.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/utils.ts @@ -1,6 +1,6 @@ import type { SvelteFlowStore } from '$lib/store/types'; import type { EdgeTypes, NodeTypes } from '$lib/types'; -import type { CoordinateExtent } from '@xyflow/system'; +import type { ColorMode, CoordinateExtent } from '@xyflow/system'; import type { Writable } from 'svelte/store'; // this is helper function for updating the store @@ -77,3 +77,15 @@ export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStorePr } }); } + +export function getColorModeClass(colorMode?: ColorMode) { + if (colorMode !== 'system') { + return colorMode; + } + + if (!colorMode || typeof window === 'undefined' || !window.matchMedia) { + return 'light'; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} diff --git a/packages/svelte/src/lib/hooks/useColorModeClass.ts b/packages/svelte/src/lib/hooks/useColorModeClass.ts new file mode 100644 index 00000000..ad163a3f --- /dev/null +++ b/packages/svelte/src/lib/hooks/useColorModeClass.ts @@ -0,0 +1,32 @@ +import type { ColorMode, ColorModeClass } from '@xyflow/system'; +import { readable, type Readable } from 'svelte/store'; + +function getMediaQuery() { + if (typeof window === 'undefined' || !window.matchMedia) { + return null; + } + + return window.matchMedia('(prefers-color-scheme: dark)'); +} + +export function useColorModeClass(colorMode: ColorMode = 'light'): Readable { + const colorModeClass = readable('light', (set) => { + if (colorMode !== 'system') { + set(colorMode); + + return; + } + + const mediaQuery = getMediaQuery(); + const updateColorModeClass = () => set(mediaQuery?.matches ? 'dark' : 'light'); + + set(mediaQuery?.matches ? 'dark' : 'light'); + mediaQuery?.addEventListener('change', updateColorModeClass); + + return () => { + mediaQuery?.removeEventListener('change', updateColorModeClass); + }; + }); + + return colorModeClass; +} diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 88bc483f..d9e0885f 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -75,7 +75,9 @@ export { type Rect, type Box, type Transform, - type CoordinateExtent + type CoordinateExtent, + type ColorMode, + type ColorModeClass } from '@xyflow/system'; // system utils diff --git a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte index b331a04e..67f93698 100644 --- a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte +++ b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte @@ -20,7 +20,7 @@ export let position: $$Props['position'] = 'bottom-right'; export let ariaLabel: $$Props['ariaLabel'] = 'Mini map'; export let nodeStrokeColor: $$Props['nodeStrokeColor'] = 'transparent'; - export let nodeColor: $$Props['nodeColor'] = '#e2e2e2'; + export let nodeColor: $$Props['nodeColor'] = undefined; export let nodeClass: $$Props['nodeClass'] = ''; export let nodeBorderRadius: $$Props['nodeBorderRadius'] = 5; export let nodeStrokeWidth: $$Props['nodeStrokeWidth'] = 2; @@ -51,7 +51,7 @@ translateExtent } = useStore(); - const nodeColorFunc = getAttrFunction(nodeColor); + const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor); const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); const nodeClassFunc = getAttrFunction(nodeClass); const shapeRendering = @@ -122,7 +122,7 @@ width={node.computed?.width ?? node.width ?? 0} height={node.computed?.height ?? node.height ?? 0} selected={node.selected} - color={nodeColorFunc(node)} + color={nodeColorFunc?.(node)} borderRadius={nodeBorderRadius} strokeColor={nodeStrokeColorFunc(node)} strokeWidth={nodeStrokeWidth} @@ -142,27 +142,3 @@ {/if} - - diff --git a/packages/svelte/src/lib/plugins/Minimap/MinimapNode.svelte b/packages/svelte/src/lib/plugins/Minimap/MinimapNode.svelte index ff0b1f0a..b17165a0 100644 --- a/packages/svelte/src/lib/plugins/Minimap/MinimapNode.svelte +++ b/packages/svelte/src/lib/plugins/Minimap/MinimapNode.svelte @@ -6,9 +6,9 @@ export let width: number = 0; export let height: number = 0; export let borderRadius: number = 5; - export let color: string; + export let color: string | undefined = undefined; export let shapeRendering: string; - export let strokeColor: string; + export let strokeColor: string | undefined = undefined; export let strokeWidth: number = 2; export let selected: boolean = false; let className: string = ''; @@ -24,8 +24,8 @@ ry={borderRadius} {width} {height} - fill={color} - stroke={strokeColor} - stroke-width={strokeWidth} + style={`${color ? `fill: ${color};` : ''}${strokeColor ? `stroke: ${strokeColor};` : ''}${ + strokeWidth ? `stroke-width: ${strokeWidth};` : '' + }`} shape-rendering={shapeRendering} /> diff --git a/packages/svelte/src/styles/base.css b/packages/svelte/src/styles/base.css index 80bc953d..b9861c98 100644 --- a/packages/svelte/src/styles/base.css +++ b/packages/svelte/src/styles/base.css @@ -2,7 +2,16 @@ @import '../../../system/src/styles/init.css'; @import '../../../system/src/styles/base.css'; +.svelte-flow { + --edge-label-color-default: inherit; +} + +.svelte-flow.dark { + --edge-label-color-default: #f8f8f8; +} + .svelte-flow__edge-label { text-align: center; position: absolute; + color: var(--edge-label-color, var(--edge-label-color-default)); } diff --git a/packages/svelte/src/styles/style.css b/packages/svelte/src/styles/style.css index c65dc982..cd89ee84 100644 --- a/packages/svelte/src/styles/style.css +++ b/packages/svelte/src/styles/style.css @@ -2,10 +2,19 @@ @import '../../../system/src/styles/init.css'; @import '../../../system/src/styles/style.css'; +.svelte-flow { + --edge-label-color-default: inherit; +} + +.svelte-flow.dark { + --edge-label-color-default: #f8f8f8; +} + .svelte-flow__edge-label { text-align: center; position: absolute; font-size: 10px; + color: var(--edge-label-color, var(--edge-label-color-default)); } .svelte-flow__nodes { diff --git a/packages/system/src/styles/base.css b/packages/system/src/styles/base.css index 31afe71a..1cb026b6 100644 --- a/packages/system/src/styles/base.css +++ b/packages/system/src/styles/base.css @@ -8,6 +8,10 @@ --selection-border-default: 1px dotted rgba(155, 155, 155, 0.8); } +.xy-flow.dark { + --node-color-default: #f8f8f8; +} + .xy-flow__handle { background-color: var(--handle-background-color, var(--handle-background-color-default)); } @@ -17,6 +21,7 @@ .xy-flow__node-output, .xy-flow__node-group { border: var(--node-border, var(--node-border-default)); + color: var(--node-color, var(--node-color-default)); &.selected, &:focus, diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 9fed0d7b..474b1322 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -11,12 +11,43 @@ --attribution-background-color-default: rgba(255, 255, 255, 0.5); --minimap-background-color-default: #fff; + --minimap-mask-background-color-default: rgb(240, 240, 240, 0.6); + --minimap-node-background-color-default: #e2e2e2; + --minimap-node-stroke-color-default: transparent; + --minimap-node-stroke-width-default: 2; - --background-pattern-dot-color-default: #91919a; - --background-pattern-line-color-default: #eee; + --background-color-default: transparent; + --background-pattern-dots-color-default: #91919a; + --background-pattern-lines-color-default: #eee; --background-pattern-cross-color-default: #e2e2e2; } +.xy-flow.dark { + --edge-stroke-default: #3c3c3c; + --edge-stroke-width-default: 1; + --edge-stroke-selected-default: #727272; + + --connectionline-stroke-default: #b1b1b7; + --connectionline-stroke-width-default: 1; + + --attribution-background-color-default: rgba(150, 150, 150, 0.25); + + --minimap-background-color-default: #141414; + --minimap-mask-background-color-default: rgb(60, 60, 60, 0.6); + --minimap-node-background-color-default: #2b2b2b; + --minimap-node-stroke-color-default: transparent; + --minimap-node-stroke-width-default: 2; + + --background-color-default: #141414; + --background-pattern-dots-color-default: #777; + --background-pattern-lines-color-default: #777; + --background-pattern-cross-color-default: #777; +} + +.xy-flow { + background-color: var(--background-color-props, var(--background-color-default, 'transparent')); +} + .xy-flow__container { position: absolute; width: 100%; @@ -108,16 +139,11 @@ pointer-events: all; } - &-textbg { - fill: white; - } - .xy-flow__edge-text { pointer-events: none; user-select: none; } } - .xy-flow__connection { pointer-events: none; @@ -268,26 +294,47 @@ .xy-flow__minimap { background: var(--minimap-background-color, var(--minimap-background-color-default)); + + &-mask { + fill: var( + --minimap-mask-background-color-props, + var(--minimap-mask-background-color, var(--minimap-mask-background-color-default)) + ); + } + + &-node { + fill: var( + --minimap-node-background-color-props, + var(--minimap-node-background-color, var(--minimap-node-background-color-default)) + ); + stroke: var( + --minimap-node-stroke-color-props, + var(--minimap-node-stroke-color, var(--minimap-node-stroke-color-default)) + ); + stroke-width: var( + --minimap-node-stroke-width-props, + var(--minimap-node-stroke-width, var(--minimap-node-stroke-width-default)) + ); + } } .xy-flow__background { pointer-events: none; z-index: -1; - background-color: var(--background-color-props, 'transparent'); } .xy-flow__background-pattern { &.dots { fill: var( --background-pattern-color-props, - var(--background-pattern-color, var(--background-pattern-dot-color-default)) + var(--background-pattern-color, var(--background-pattern-dots-color-default)) ); } &.lines { stroke: var( --background-pattern-color-props, - var(--background-pattern-color, var(--background-pattern-line-color-default)) + var(--background-pattern-color, var(--background-pattern-lines-color-default)) ); } @@ -312,6 +359,7 @@ width: 100%; max-width: 12px; max-height: 12px; + fill: currentColor; } } } diff --git a/packages/system/src/styles/style.css b/packages/system/src/styles/style.css index 99fa8876..e88cc3f1 100644 --- a/packages/system/src/styles/style.css +++ b/packages/system/src/styles/style.css @@ -5,6 +5,7 @@ --node-group-background-color-default: rgba(240, 240, 240, 0.25); --node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08); --node-boxshadow-selected-default: 0 0 0 0.5px #1a192b; + --node-border-radius-default: 3px; --handle-background-color-default: #1a192b; --handle-border-color-default: #fff; @@ -20,6 +21,28 @@ --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); } +.xy-flow.dark { + --node-color-default: #f8f8f8; + --node-border-default: 1px solid #3c3c3c; + --node-background-color-default: #1e1e1e; + --node-group-background-color-default: rgba(240, 240, 240, 0.25); + --node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08); + --node-boxshadow-selected-default: 0 0 0 0.5px #999; + + --handle-background-color-default: #bebebe; + --handle-border-color-default: #1e1e1e; + + --selection-background-color-default: rgba(200, 200, 220, 0.08); + --selection-border-default: 1px dotted rgba(200, 200, 220, 0.8); + + --controls-button-background-color-default: #2b2b2b; + --controls-button-background-color-hover-default: #3e3e3e; + --controls-button-color-default: #f8f8f8; + --controls-button-color-hover-default: #fff; + --controls-button-border-color-default: #5b5b5b; + --controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08); +} + .xy-flow__edge { &.updating { .xy-flow__edge-path { @@ -44,7 +67,7 @@ .xy-flow__node-output, .xy-flow__node-group { padding: 10px; - border-radius: 3px; + border-radius: var(--node-border-radius, var(--node-border-radius-default)); width: 150px; font-size: 12px; color: var(--node-color, var(--node-color-default)); @@ -110,7 +133,7 @@ ); color: var( --controls-button-color-hover-props, - var(--controls-button-hover-color, var(--controls-button-hover-color-default)) + var(--controls-button-color-hover, var(--controls-button-color-hover-default)) ); } @@ -122,4 +145,8 @@ } } } + + &-button:last-child { + border-bottom: none; + } } diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index bc86442d..22ad889e 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -136,3 +136,6 @@ export type UpdateConnection = (params: { connectionStartHandle: ConnectingHandle | null; connectionEndHandle: ConnectingHandle | null; }) => void; + +export type ColorModeClass = 'light' | 'dark'; +export type ColorMode = ColorModeClass | 'system'; diff --git a/tests/playwright/e2e/edges.spec.ts b/tests/playwright/e2e/edges.spec.ts index af2cf980..16695e63 100644 --- a/tests/playwright/e2e/edges.spec.ts +++ b/tests/playwright/e2e/edges.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; import { FRAMEWORK } from './constants'; -test.describe('EDGES', () => { +test.describe('Edges', () => { test.beforeEach(async ({ page }) => { // Go to the starting url before each test. await page.goto('/tests/generic/edges/general'); diff --git a/tests/playwright/e2e/node-toolbar.spec.ts b/tests/playwright/e2e/node-toolbar.spec.ts index daf76070..38722e16 100644 --- a/tests/playwright/e2e/node-toolbar.spec.ts +++ b/tests/playwright/e2e/node-toolbar.spec.ts @@ -1,4 +1,4 @@ -import { test, expect, Locator } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import { FRAMEWORK } from './constants'; type Position = 'top' | 'right' | 'bottom' | 'left'; diff --git a/tests/playwright/e2e/nodes.spec.ts b/tests/playwright/e2e/nodes.spec.ts index 5afa41de..decd2c4f 100644 --- a/tests/playwright/e2e/nodes.spec.ts +++ b/tests/playwright/e2e/nodes.spec.ts @@ -2,7 +2,7 @@ import { test, expect, Locator } from '@playwright/test'; import { FRAMEWORK } from './constants'; -test.describe('NODES', () => { +test.describe('Nodes', () => { test.beforeEach(async ({ page }) => { // Go to the starting url before each test. await page.goto('/tests/generic/nodes/general'); diff --git a/tests/playwright/e2e/pane.spec.ts b/tests/playwright/e2e/pane.spec.ts index 15a79655..d4d66988 100644 --- a/tests/playwright/e2e/pane.spec.ts +++ b/tests/playwright/e2e/pane.spec.ts @@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test'; import { FRAMEWORK } from './constants'; import { getTransform } from './utils'; -test.describe('PANE DEFAULT', () => { +test.describe('Pane default', () => { test.beforeEach(async ({ page }) => { // Go to the starting url before each test. await page.goto('/tests/generic/pane/general'); @@ -126,7 +126,7 @@ test.describe('PANE DEFAULT', () => { }); }); -test.describe('PANE NON-DEFAULT', () => { +test.describe('Pane non-default', () => { test.beforeEach(async ({ page }) => { // Go to the starting url before each test. await page.goto('/tests/generic/pane/non-defaults'); @@ -168,7 +168,7 @@ test.describe('PANE NON-DEFAULT', () => { }); }); -test.describe('PANE ACTIVATION KEYS', () => { +test.describe('Pane activation keys', () => { test.beforeEach(async ({ page }) => { // Go to the starting url before each test. await page.goto('/tests/generic/pane/activation-keys'); diff --git a/tests/playwright/e2e/props.spec.ts b/tests/playwright/e2e/props.spec.ts new file mode 100644 index 00000000..b8104633 --- /dev/null +++ b/tests/playwright/e2e/props.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test'; + +import { FRAMEWORK } from './constants'; + +test.describe('Props', () => { + test.describe('colorMode', async () => { + test('render default light color mode', async ({ page }) => { + await page.goto('/examples/color-mode'); + const locator = page.locator(`.${FRAMEWORK}-flow`); + + await expect(locator).not.toHaveClass(/dark/); + }); + + test('render dark color mode', async ({ page }) => { + await page.goto('/examples/color-mode'); + const locator = page.locator(`.${FRAMEWORK}-flow`); + await page.getByTestId('colormode-select').selectOption({ label: 'dark' }); + + await expect(locator).toHaveClass(/dark/); + }); + }); +});