Feat: dark mode (#3652)
* feat(react/svelte): add dark mode defaults * refactor(darkmode): minimap, edges, edge labels * chore(style): edge label color * feat(colorMode): add colorMode prop light/dark/system * chore(examples): cleanup * test(colorMode): add tests * chore(base.css): add dark base * chore(examples): cleanup
This commit is contained in:
@@ -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);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -163,7 +172,7 @@
|
||||
bind:clientWidth
|
||||
bind:clientHeight
|
||||
{style}
|
||||
class={cc(['svelte-flow', className])}
|
||||
class={cc(['svelte-flow', className, $colorModeClass])}
|
||||
data-testid="svelte-flow__wrapper"
|
||||
on:dragover
|
||||
on:drop
|
||||
|
||||
@@ -14,7 +14,8 @@ import type {
|
||||
OnError,
|
||||
ConnectionMode,
|
||||
PanelPosition,
|
||||
ProOptions
|
||||
ProOptions,
|
||||
ColorMode
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -75,6 +76,7 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||
width?: number;
|
||||
height?: number;
|
||||
colorMode?: ColorMode;
|
||||
|
||||
class?: string;
|
||||
style?: string;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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<ColorModeClass> {
|
||||
const colorModeClass = readable<ColorModeClass>('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;
|
||||
}
|
||||
@@ -75,7 +75,9 @@ export {
|
||||
type Rect,
|
||||
type Box,
|
||||
type Transform,
|
||||
type CoordinateExtent
|
||||
type CoordinateExtent,
|
||||
type ColorMode,
|
||||
type ColorModeClass
|
||||
} from '@xyflow/system';
|
||||
|
||||
// system utils
|
||||
|
||||
@@ -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 @@
|
||||
</svg>
|
||||
{/if}
|
||||
</Panel>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
background-color: var(
|
||||
--minimap-background-color-props,
|
||||
var(--minimap-background-color, var(--minimap-background-color-default))
|
||||
);
|
||||
}
|
||||
|
||||
.svelte-flow__minimap-mask {
|
||||
fill: var(
|
||||
--minimap-mask-color-props,
|
||||
var(--minimap-mask-color, var(--minimap-mask-color-default))
|
||||
);
|
||||
stroke: var(
|
||||
--minimap-mask-stroke-color-props,
|
||||
var(--minimap-mask-stroke-color, var(--minimap-mask-stroke-color-default))
|
||||
);
|
||||
stroke-width: var(
|
||||
--minimap-mask-stroke-width-props,
|
||||
var(--minimap-mask-stroke-width, var(--minimap-mask-stroke-width-default))
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user