Приводил к стилу consta UI

This commit is contained in:
NikitaKalitov
2026-07-22 09:42:26 +03:00
parent 52e28e8dc6
commit 4852e55964
7 changed files with 478 additions and 75 deletions
+25
View File
@@ -106,4 +106,29 @@
--accent-violet: rgb(145, 82, 230);
--accent-lavender: rgb(157, 154, 247);
--accent-pink: rgb(232, 143, 219);
/* ============================================================
ЦВЕТА ТЕКСТА
============================================================ */
--text-color-blue: rgb(0, 121, 194);
--text-color-dark: rgb(82, 90, 100);
--text-color-gray: rgb(146, 154, 164);
--text-color-white: rgb(255, 255, 255);
/* ============================================================
ОСНОВНЫЕ ЦВЕТА ДЛЯ МЕЛКОЙ ГРАФИКИ, ЛИНИЙ
Два тональных ряда: синий (от --text-color-blue) и серый
(от --text-color-gray до белого)
============================================================ */
--graphic-blue-1: rgb(0, 121, 194); /* = --text-color-blue */
--graphic-blue-2: rgb(67, 183, 247);
--graphic-blue-3: rgb(137, 207, 255);
--graphic-blue-4: rgb(117, 170, 197);
--graphic-blue-5: rgb(143, 215, 228);
--graphic-blue-6: rgb(0, 65, 129);
--graphic-gray-1: rgb(146, 154, 164); /* = --text-color-gray */
--graphic-gray-2: rgb(206, 210, 214);
--graphic-gray-3: rgb(233, 235, 237);
--graphic-gray-4: rgb(255, 255, 255); /* = --text-color-white */
}
+53 -3
View File
@@ -32,7 +32,7 @@ import { nodeTypes, NodesWithChildrenContext, ModeContext } from './nodes';
import { NodeInspector } from './NodeInspector';
import { EdgeInspector } from './EdgeInspector';
import { debounce, serializeDocument } from './serialize';
import type { DiagramDocument, DiagramNodeData, NodeKind } from './types';
import type { DiagramDocument, DiagramEdgeData, DiagramNodeData, EdgeLineStyle, NodeKind } from './types';
import './diagram.css';
interface DiagramEditorProps {
@@ -42,6 +42,16 @@ interface DiagramEditorProps {
const DEFAULT_EDGE_TYPE = 'smoothstep';
const DEFAULT_MARKER_END = { type: MarkerType.ArrowClosed };
const DEFAULT_EDGE_STROKE_WIDTH = 1;
const DEFAULT_EDGE_BORDER_RADIUS = 5; // matches getSmoothStepPath's own default
const DEFAULT_EDGE_COLOR = '#b1b1b7'; // xyflow's own default edge color
const DEFAULT_EDGE_TEXT_COLOR = '#FFFFFF';
const DEFAULT_EDGE_FONT_SIZE = 11;
const EDGE_DASH_ARRAY: Record<EdgeLineStyle, string | undefined> = {
solid: undefined,
dashed: '8 4',
dotted: '1.5 4',
};
const MAX_HISTORY = 50;
const TYPING_HISTORY_THROTTLE_MS = 1000;
@@ -240,7 +250,32 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
const visibleEdges = useMemo(() => {
const visibleIds = new Set(visibleNodes.map((node) => node.id));
return edges.filter((edge) => visibleIds.has(edge.source) && visibleIds.has(edge.target));
return edges
.filter((edge) => visibleIds.has(edge.source) && visibleIds.has(edge.target))
.map((edge) => {
const edgeData = edge.data as DiagramEdgeData | undefined;
const edgeColor = edgeData?.color ?? DEFAULT_EDGE_COLOR;
return {
...edge,
style: {
...edge.style,
stroke: edgeColor,
strokeWidth: edgeData?.strokeWidth ?? DEFAULT_EDGE_STROKE_WIDTH,
strokeDasharray: EDGE_DASH_ARRAY[edgeData?.lineStyle ?? 'solid'],
},
pathOptions: { borderRadius: edgeData?.borderRadius ?? DEFAULT_EDGE_BORDER_RADIUS },
markerEnd: { type: MarkerType.ArrowClosed, color: edgeColor },
// Label renders as its own small chip — same color as the line, configurable text color.
labelBgStyle: { fill: edgeColor },
labelStyle: {
fill: edgeData?.textColor ?? DEFAULT_EDGE_TEXT_COLOR,
fontWeight: 600,
fontSize: edgeData?.fontSize ?? DEFAULT_EDGE_FONT_SIZE,
},
labelBgPadding: [6, 4] as [number, number],
labelBgBorderRadius: 4,
};
});
}, [edges, visibleNodes]);
const breadcrumbPath = useMemo(() => {
@@ -417,6 +452,19 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
[selectedEdgeId, nodes, edges, emitChange, pushHistoryThrottled],
);
const updateSelectedEdgeData = useCallback(
(patch: Partial<DiagramEdgeData>) => {
if (!selectedEdgeId) return;
pushHistoryThrottled(nodes, edges);
const next = edges.map((edge) =>
edge.id === selectedEdgeId ? { ...edge, data: { ...edge.data, ...patch } } : edge,
);
setEdges(next);
emitChange(nodes, next);
},
[selectedEdgeId, nodes, edges, emitChange, pushHistoryThrottled],
);
const getViewportCenter = useCallback(() => {
const rect = canvasWrapperRef.current?.getBoundingClientRect();
if (!rect) return { x: 0, y: 0 };
@@ -696,7 +744,9 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
<EdgeInspector
edgeId={selectedEdge.id}
label={typeof selectedEdge.label === 'string' ? selectedEdge.label : ''}
onChange={updateSelectedEdgeLabel}
data={(selectedEdge.data as DiagramEdgeData) ?? {}}
onLabelChange={updateSelectedEdgeLabel}
onDataChange={updateSelectedEdgeData}
onDelete={deleteSelectedEdge}
onBringToFront={bringSelectedEdgeToFront}
onSendToBack={sendSelectedEdgeToBack}
+188 -3
View File
@@ -1,11 +1,70 @@
import { useRef, useState, type RefObject } from 'react';
import { Button } from '@consta/uikit/Button';
import { TextField } from '@consta/uikit/TextField';
import { ChoiceGroup } from '@consta/uikit/ChoiceGroup';
import { ColorPicker, hexModel } from '@consta/uikit/ColorPicker';
import { Text } from '@consta/uikit/Text';
import type { DiagramEdgeData, EdgeLineStyle } from './types';
const LINE_STYLE_ITEMS: { label: string; style: EdgeLineStyle }[] = [
{ label: 'Сплошная', style: 'solid' },
{ label: 'Пунктирная', style: 'dashed' },
{ label: 'Точечная', style: 'dotted' },
];
// "Основные цвета для мелкой графики, линий" — two tonal rows (blue, gray)
// meant specifically for lines, shown first.
const GRAPHIC_LINE_PRESETS: { hex: string; label: string }[] = [
{ hex: '#0079C2', label: 'Синий 1' },
{ hex: '#43B7F7', label: 'Синий 2' },
{ hex: '#89CFFF', label: 'Синий 3' },
{ hex: '#75AAC5', label: 'Синий 4' },
{ hex: '#8FD7E4', label: 'Синий 5' },
{ hex: '#004181', label: 'Синий 6' },
{ hex: '#929AA4', label: 'Серый 1' },
{ hex: '#CED2D6', label: 'Серый 2' },
{ hex: '#E9EBED', label: 'Серый 3' },
{ hex: '#FFFFFF', label: 'Серый 4' },
];
// "Дополнительные цвета" — the section palette.css marks as meant for
// lines/small graphics too (single accents), shown after the row above.
const ACCENT_LINE_PRESETS: { hex: string; label: string }[] = [
{ hex: '#B5309A', label: 'Пурпурный' },
{ hex: '#EC5665', label: 'Красный' },
{ hex: '#FFAB66', label: 'Оранжевый' },
{ hex: '#C2DC5B', label: 'Оливковый' },
{ hex: '#32CA88', label: 'Зелёный' },
{ hex: '#22926D', label: 'Тёмно-зелёный' },
{ hex: '#0EBAD5', label: 'Синий' },
{ hex: '#4EE7F4', label: 'Циан' },
{ hex: '#9152E6', label: 'Виолет' },
{ hex: '#9D9AF7', label: 'Лавандовый' },
{ hex: '#E88FDB', label: 'Розовый' },
];
const LINE_COLOR_PRESETS = [...GRAPHIC_LINE_PRESETS, ...ACCENT_LINE_PRESETS];
// "Цвета текста" from palette.css.
const TEXT_COLOR_PRESETS: { hex: string; label: string }[] = [
{ hex: '#0079C2', label: 'Синий' },
{ hex: '#525A64', label: 'Тёмный' },
{ hex: '#929AA4', label: 'Серый' },
{ hex: '#FFFFFF', label: 'Белый' },
];
const DEFAULT_STROKE_WIDTH = 1;
const DEFAULT_BORDER_RADIUS = 5;
const DEFAULT_EDGE_COLOR = '#b1b1b7';
const DEFAULT_TEXT_COLOR = '#FFFFFF';
const DEFAULT_FONT_SIZE = 11;
interface EdgeInspectorProps {
edgeId: string;
label: string;
onChange: (label: string) => void;
data: DiagramEdgeData;
onLabelChange: (label: string) => void;
onDataChange: (patch: Partial<DiagramEdgeData>) => void;
onDelete: () => void;
onBringToFront: () => void;
onSendToBack: () => void;
@@ -14,11 +73,21 @@ interface EdgeInspectorProps {
export function EdgeInspector({
edgeId,
label,
onChange,
data,
onLabelChange,
onDataChange,
onDelete,
onBringToFront,
onSendToBack,
}: EdgeInspectorProps) {
const [colorPickerOpen, setColorPickerOpen] = useState(false);
const colorButtonRef = useRef<HTMLDivElement>(null) as RefObject<HTMLDivElement>;
const currentColor = data.color ?? DEFAULT_EDGE_COLOR;
const [textColorPickerOpen, setTextColorPickerOpen] = useState(false);
const textColorButtonRef = useRef<HTMLDivElement>(null) as RefObject<HTMLDivElement>;
const currentTextColor = data.textColor ?? DEFAULT_TEXT_COLOR;
return (
<div className="node-inspector">
<div className="node-inspector__header">
@@ -39,7 +108,123 @@ export function EdgeInspector({
<Button size="s" view="secondary" label="На задний план" onClick={onSendToBack} />
</div>
<TextField label="Подпись" size="s" value={label} onChange={(value) => onChange(value ?? '')} />
<TextField label="Подпись" size="s" value={label} onChange={(value) => onLabelChange(value ?? '')} />
<div className="node-inspector__field">
<Text size="xs" view="secondary">
Цвет линии
</Text>
<div className="node-inspector__presets">
{LINE_COLOR_PRESETS.map(({ hex, label: presetLabel }) => (
<button
key={hex}
type="button"
title={presetLabel}
aria-label={presetLabel}
className={`node-inspector__preset${currentColor === hex ? ' node-inspector__preset--active' : ''}`}
style={{ background: hex }}
onClick={() => onDataChange({ color: hex })}
/>
))}
</div>
<div ref={colorButtonRef} className="node-inspector__color-trigger">
<Button size="s" view="secondary" label="Настроить цвет" onClick={() => setColorPickerOpen(true)} />
</div>
{colorPickerOpen && (
<ColorPicker
model={hexModel}
value={currentColor}
onChange={(value) => onDataChange({ color: value })}
anchorRef={colorButtonRef}
open={colorPickerOpen}
onOpen={setColorPickerOpen}
/>
)}
</div>
<div className="node-inspector__field">
<Text size="xs" view="secondary">
Цвет текста
</Text>
<div className="node-inspector__presets">
{TEXT_COLOR_PRESETS.map(({ hex, label: presetLabel }) => (
<button
key={hex}
type="button"
title={presetLabel}
aria-label={presetLabel}
className={`node-inspector__preset${currentTextColor === hex ? ' node-inspector__preset--active' : ''}`}
style={{ background: hex }}
onClick={() => onDataChange({ textColor: hex })}
/>
))}
</div>
<div ref={textColorButtonRef} className="node-inspector__color-trigger">
<Button size="s" view="secondary" label="Настроить цвет" onClick={() => setTextColorPickerOpen(true)} />
</div>
{textColorPickerOpen && (
<ColorPicker
model={hexModel}
value={currentTextColor}
onChange={(value) => onDataChange({ textColor: value })}
anchorRef={textColorButtonRef}
open={textColorPickerOpen}
onOpen={setTextColorPickerOpen}
/>
)}
</div>
<TextField
label="Размер текста"
type="number"
size="s"
min={8}
max={32}
value={String(data.fontSize ?? DEFAULT_FONT_SIZE)}
onChange={(value) => {
const n = Number(value);
if (value && !Number.isNaN(n)) onDataChange({ fontSize: n });
}}
/>
<TextField
label="Толщина линии"
type="number"
size="s"
min={1}
max={8}
value={String(data.strokeWidth ?? DEFAULT_STROKE_WIDTH)}
onChange={(value) => {
const n = Number(value);
if (value && !Number.isNaN(n)) onDataChange({ strokeWidth: n });
}}
/>
<div className="node-inspector__field">
<Text size="xs" view="secondary">
Стиль линии
</Text>
<ChoiceGroup
size="xs"
name={`edge-line-style-${edgeId}`}
items={LINE_STYLE_ITEMS}
value={LINE_STYLE_ITEMS.find((item) => item.style === (data.lineStyle ?? 'solid')) ?? null}
onChange={(item) => onDataChange({ lineStyle: item.style })}
/>
</div>
<TextField
label="Скругление углов"
type="number"
size="s"
min={0}
max={20}
value={String(data.borderRadius ?? DEFAULT_BORDER_RADIUS)}
onChange={(value) => {
const n = Number(value);
if (value && !Number.isNaN(n)) onDataChange({ borderRadius: n });
}}
/>
</div>
);
}
+135 -30
View File
@@ -1,26 +1,50 @@
import { useRef, useState, type RefObject } from 'react';
import { Button } from '@consta/uikit/Button';
import { TextField } from '@consta/uikit/TextField';
import { ColorControl, ColorPicker, hexModel } from '@consta/uikit/ColorPicker';
import { ColorPicker, hexModel } from '@consta/uikit/ColorPicker';
import { Text } from '@consta/uikit/Text';
import { DEFAULT_NODE_COLOR } from './nodes';
import {
DEFAULT_CAPTION_TEXT_COLOR,
DEFAULT_NODE_BACKGROUND,
DEFAULT_NODE_COLOR,
DEFAULT_NODE_FONT_SIZE,
DEFAULT_NODE_TEXT_COLOR,
DEFAULT_TASK_BORDER_RADIUS,
} from './nodes';
import type { DiagramNodeData, NodeKind } from './types';
// "Дополнительные цвета" from palette.css — the section the palette itself
// marks as meant for single-color accents (as opposed to the gradients used
// for shape defaults, which a flat-hex picker can't represent).
const NODE_COLOR_PRESETS = [
'#B5309A',
'#EC5665',
'#FFAB66',
'#C2DC5B',
'#32CA88',
'#22926D',
'#0EBAD5',
'#4EE7F4',
'#9152E6',
'#9D9AF7',
'#E88FDB',
// Every gradient defined in palette.css, across its three "for diagrams /
// flowcharts / semantic highlights" sections. Consta's ColorPicker `palette`
// prop can't render these — it goes through hexModel, which only understands
// flat colors — so these render as our own swatch row instead.
const GRADIENT_PRESETS: { name: string; label: string }[] = [
{ name: 'navy', label: 'Тёмно-синий' },
{ name: 'azure', label: 'Лазурный' },
{ name: 'sky', label: 'Небесный' },
{ name: 'teal-dark', label: 'Тёмная бирюза' },
{ name: 'teal-light', label: 'Светлая бирюза' },
{ name: 'navy-dark', label: 'Глубокий синий' },
{ name: 'ice', label: 'Лёд' },
{ name: 'frost', label: 'Иней' },
{ name: 'purple', label: 'Пурпурный' },
{ name: 'red', label: 'Красный' },
{ name: 'orange', label: 'Оранжевый' },
{ name: 'olive', label: 'Оливковый' },
{ name: 'green', label: 'Зелёный' },
{ name: 'green-dark', label: 'Тёмно-зелёный' },
{ name: 'blue', label: 'Синий' },
{ name: 'cyan', label: 'Циан' },
{ name: 'violet', label: 'Виолет' },
{ name: 'lavender', label: 'Лавандовый' },
{ name: 'pink', label: 'Розовый' },
];
// "Цвета текста" from palette.css.
const TEXT_COLOR_PRESETS: { hex: string; label: string }[] = [
{ hex: '#0079C2', label: 'Синий' },
{ hex: '#525A64', label: 'Тёмный' },
{ hex: '#929AA4', label: 'Серый' },
{ hex: '#FFFFFF', label: 'Белый' },
];
const TYPE_LABELS: Record<NodeKind, string> = {
@@ -62,7 +86,18 @@ export function NodeInspector({
}: NodeInspectorProps) {
const [colorPickerOpen, setColorPickerOpen] = useState(false);
// Consta's ref props still use the pre-React-19 non-nullable RefObject shape.
const colorMarkerRef = useRef<HTMLButtonElement>(null) as RefObject<HTMLButtonElement>;
const colorButtonRef = useRef<HTMLDivElement>(null) as RefObject<HTMLDivElement>;
// data.color can be a `var(--gradient-x)` reference from the preset row —
// hexModel tries to parse that as a hex color and produces NaN, so the
// hex-only ColorControl/ColorPicker always need a real flat hex fallback.
const flatColor = data.color && !data.color.startsWith('var(') ? data.color : DEFAULT_NODE_COLOR[nodeType];
const [textColorPickerOpen, setTextColorPickerOpen] = useState(false);
const textColorButtonRef = useRef<HTMLDivElement>(null) as RefObject<HTMLDivElement>;
// Text sits directly on the colored fill for tasks (needs a light default)
// but on the plain canvas for every other shape's caption (needs a dark one).
const defaultTextColor = nodeType === 'task' ? DEFAULT_NODE_TEXT_COLOR : DEFAULT_CAPTION_TEXT_COLOR;
const currentTextColor = data.textColor ?? defaultTextColor;
if (mode === 'view') {
const hasMeta = Boolean(data.responsible || data.duration);
@@ -138,28 +173,83 @@ export function NodeInspector({
<Text size="xs" view="secondary">
Цвет фона
</Text>
<ColorControl
markerRef={colorMarkerRef}
model={hexModel}
value={data.color ?? DEFAULT_NODE_COLOR[nodeType]}
onChange={(value) => onChange({ color: value })}
size="s"
onClick={() => setColorPickerOpen(true)}
/>
<div className="node-inspector__presets">
{GRADIENT_PRESETS.map(({ name, label }) => {
const value = `var(--gradient-${name})`;
const isActive = (data.color ?? DEFAULT_NODE_BACKGROUND[nodeType]) === value;
return (
<button
key={name}
type="button"
title={label}
aria-label={label}
className={`node-inspector__preset${isActive ? ' node-inspector__preset--active' : ''}`}
style={{ background: value }}
onClick={() => onChange({ color: value })}
/>
);
})}
</div>
<div ref={colorButtonRef} className="node-inspector__color-trigger">
<Button size="s" view="secondary" label="Настроить цвет" onClick={() => setColorPickerOpen(true)} />
</div>
{colorPickerOpen && (
<ColorPicker
model={hexModel}
value={data.color ?? DEFAULT_NODE_COLOR[nodeType]}
value={flatColor}
onChange={(value) => onChange({ color: value })}
anchorRef={colorMarkerRef}
anchorRef={colorButtonRef}
open={colorPickerOpen}
onOpen={setColorPickerOpen}
palette={NODE_COLOR_PRESETS}
paletteTitle="Пресеты"
/>
)}
</div>
<div className="node-inspector__field">
<Text size="xs" view="secondary">
Цвет текста
</Text>
<div className="node-inspector__presets">
{TEXT_COLOR_PRESETS.map(({ hex, label: presetLabel }) => (
<button
key={hex}
type="button"
title={presetLabel}
aria-label={presetLabel}
className={`node-inspector__preset${currentTextColor === hex ? ' node-inspector__preset--active' : ''}`}
style={{ background: hex }}
onClick={() => onChange({ textColor: hex })}
/>
))}
</div>
<div ref={textColorButtonRef} className="node-inspector__color-trigger">
<Button size="s" view="secondary" label="Настроить цвет" onClick={() => setTextColorPickerOpen(true)} />
</div>
{textColorPickerOpen && (
<ColorPicker
model={hexModel}
value={currentTextColor}
onChange={(value) => onChange({ textColor: value })}
anchorRef={textColorButtonRef}
open={textColorPickerOpen}
onOpen={setTextColorPickerOpen}
/>
)}
</div>
<TextField
label="Размер текста"
type="number"
size="s"
min={8}
max={32}
value={String(data.fontSize ?? DEFAULT_NODE_FONT_SIZE)}
onChange={(value) => {
const n = Number(value);
if (value && !Number.isNaN(n)) onChange({ fontSize: n });
}}
/>
<TextField
label="Название"
type="textarea"
@@ -192,6 +282,21 @@ export function NodeInspector({
onChange={(value) => onChange({ duration: value ?? '' })}
/>
{nodeType === 'task' && (
<TextField
label="Скругление углов"
type="number"
size="s"
min={0}
max={25}
value={String(data.borderRadius ?? DEFAULT_TASK_BORDER_RADIUS)}
onChange={(value) => {
const n = Number(value);
if (value && !Number.isNaN(n)) onChange({ borderRadius: n });
}}
/>
)}
<Button size="s" view="ghost" label="Войти в под-процесс →" onClick={() => onEnterSubProcess(nodeId)} />
<Text size="xs" view="secondary" className="node-inspector__hint">
{childCount > 0
+24 -27
View File
@@ -48,8 +48,6 @@
width: 100%;
height: 100%;
padding: 6px 10px;
border: 1px solid #555;
border-radius: 6px;
background: canvas;
color: canvastext;
font-size: 12px;
@@ -61,7 +59,6 @@
}
.diagram-node__label {
color: white;
white-space: pre-wrap;
overflow-wrap: break-word;
}
@@ -90,9 +87,7 @@
transform: translateX(-50%);
margin-top: 4px;
max-width: 120px;
font-size: 11px;
text-align: center;
color: canvastext;
white-space: pre-wrap;
}
@@ -101,26 +96,6 @@
border-radius: 50%;
}
.diagram-node--startEvent {
border-width: 1px;
}
.diagram-node--endEvent {
border-width: 3px;
}
.diagram-node--intermediateEvent {
border-width: 1px;
}
.diagram-node--intermediateEvent::after {
content: '';
position: absolute;
inset: 3px;
border: 1px solid #555;
border-radius: 50%;
}
.diagram-node--gateway-outer {
position: relative;
width: 100%;
@@ -134,8 +109,6 @@
.diagram-node--diamond-bg {
position: absolute;
inset: 0;
border: 1px solid #555;
box-sizing: border-box;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
@@ -201,6 +174,30 @@
gap: 4px;
}
.node-inspector__presets {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.node-inspector__preset {
width: 22px;
height: 22px;
padding: 0;
border: 1px solid color-mix(in srgb, canvastext 25%, transparent);
border-radius: 50%;
cursor: pointer;
}
.node-inspector__preset--active {
outline: 2px solid canvastext;
outline-offset: 2px;
}
.node-inspector__color-trigger {
align-self: flex-start;
}
.node-inspector__label,
.node-inspector__description {
white-space: pre-wrap;
+36 -11
View File
@@ -35,6 +35,16 @@ export const DEFAULT_NODE_COLOR: Record<NodeKind, string> = {
inclusiveGateway: '#BBBD39',
};
export const DEFAULT_TASK_BORDER_RADIUS = 6;
// Text on the colored shape body (task label, gateway icon) needs a light
// default; the caption below events/gateways sits on the plain canvas
// background instead and needs a dark one. Both are overridable via the
// same `data.textColor`/`data.fontSize`.
export const DEFAULT_NODE_TEXT_COLOR = '#FFFFFF';
export const DEFAULT_CAPTION_TEXT_COLOR = '#525A64';
export const DEFAULT_NODE_FONT_SIZE = 12;
const HANDLE_POSITIONS: { id: string; position: Position }[] = [
{ id: 'top', position: Position.Top },
{ id: 'right', position: Position.Right },
@@ -91,32 +101,42 @@ function ShapeResizer({
function TaskNode({ id, data, selected }: NodeProps<ShapeNode>) {
return (
<div className="diagram-node diagram-node--task" style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND.task }}>
<div
className="diagram-node diagram-node--task"
style={{
background: data.color ?? DEFAULT_NODE_BACKGROUND.task,
borderRadius: data.borderRadius ?? DEFAULT_TASK_BORDER_RADIUS,
}}
>
<ShapeResizer selected={selected} minWidth={70} minHeight={32} keepAspectRatio={false} />
<ShapeHandles />
<SubProcessBadge id={id} />
<span className="diagram-node__label">{data.label}</span>
<span
className="diagram-node__label"
style={{ color: data.textColor ?? DEFAULT_NODE_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
>
{data.label}
</span>
</div>
);
}
const EVENT_BORDER_CLASS: Record<'startEvent' | 'intermediateEvent' | 'endEvent', string> = {
startEvent: 'diagram-node--startEvent',
intermediateEvent: 'diagram-node--intermediateEvent',
endEvent: 'diagram-node--endEvent',
};
function EventNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
const kind = (type as 'startEvent' | 'intermediateEvent' | 'endEvent') ?? 'startEvent';
return (
<div
className={`diagram-node diagram-node--circle ${EVENT_BORDER_CLASS[kind]}`}
className="diagram-node diagram-node--circle"
style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND[kind] }}
>
<ShapeResizer selected={selected} minWidth={24} minHeight={24} />
<ShapeHandles />
<SubProcessBadge id={id} />
<span className="diagram-node__caption">{data.label}</span>
<span
className="diagram-node__caption"
style={{ color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
>
{data.label}
</span>
</div>
);
}
@@ -149,7 +169,12 @@ function GatewayNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
<ShapeHandles />
<SubProcessBadge id={id} />
<span className="diagram-node__icon">{GATEWAY_ICON[kind]}</span>
<span className="diagram-node__caption">{data.label}</span>
<span
className="diagram-node__caption"
style={{ color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
>
{data.label}
</span>
</div>
);
}
+17 -1
View File
@@ -13,6 +13,10 @@ export interface DiagramNodeData extends Record<string, unknown> {
responsible?: string;
duration?: string;
color?: string;
/** Corner rounding, in px — only meaningful for the rectangular task shape. */
borderRadius?: number;
textColor?: string;
fontSize?: number;
}
export interface DiagramNode {
@@ -28,7 +32,19 @@ export interface DiagramNode {
zIndex?: number;
}
export interface DiagramEdgeData extends Record<string, unknown> {}
export type EdgeLineStyle = 'solid' | 'dashed' | 'dotted';
export interface DiagramEdgeData extends Record<string, unknown> {
strokeWidth?: number;
lineStyle?: EdgeLineStyle;
/** Corner rounding for the smoothstep path, in px. */
borderRadius?: number;
/** Stroke color — also tints the arrowhead marker and the label's background chip. */
color?: string;
/** Label text color — the chip's own background still follows `color`. */
textColor?: string;
fontSize?: number;
}
export interface DiagramEdge {
id: string;