Небольшие правки

This commit is contained in:
NikitaKalitov
2026-07-22 11:23:05 +03:00
parent 4852e55964
commit 5b8aaf0577
3 changed files with 71 additions and 44 deletions
-7
View File
@@ -1,10 +1,3 @@
/*
Извлечено со скриншотов брендбука/стайлгайда.
Угол градиента в источнике указан как «60%» — трактован как 60deg
(стандартная запись угла в CSS linear-gradient). Проверьте по оригиналу,
если это принципиально.
*/
:root {
/* ============================================================
ОСНОВНЫЕ ГРАДИЕНТЫ (синие) — для инфоблоков, диаграмм, блок-схем
+2
View File
@@ -61,6 +61,7 @@
.diagram-node__label {
white-space: pre-wrap;
overflow-wrap: break-word;
font-weight: 600;
}
.diagram-node__badge {
@@ -89,6 +90,7 @@
max-width: 120px;
text-align: center;
white-space: pre-wrap;
font-weight: 600;
}
.diagram-node--circle {
+69 -37
View File
@@ -1,6 +1,12 @@
import { createContext, useContext } from 'react';
import { Handle, NodeResizer, Position, type Node, type NodeProps } from '@xyflow/react';
import type { DiagramNodeData, NodeKind } from './types';
import { createContext, useContext } from "react";
import {
Handle,
NodeResizer,
Position,
type Node,
type NodeProps,
} from "@xyflow/react";
import type { DiagramNodeData, NodeKind } from "./types";
type ShapeNode = Node<DiagramNodeData, NodeKind>;
@@ -8,31 +14,31 @@ type ShapeNode = Node<DiagramNodeData, NodeKind>;
export const NodesWithChildrenContext = createContext<Set<string>>(new Set());
/** Current editor mode — resize handles and connection points only show in 'edit'. */
export const ModeContext = createContext<'edit' | 'view'>('edit');
export const ModeContext = createContext<"edit" | "view">("edit");
// Gradients from palette.css's "ОСНОВНАЯ ПАЛИТРА" section — the one the
// palette itself labels as meant for diagrams/flowcharts/semantic highlights.
// This is what nodes actually render with until a user picks a custom color.
export const DEFAULT_NODE_BACKGROUND: Record<NodeKind, string> = {
task: 'var(--gradient-blue)',
startEvent: 'var(--gradient-green)',
intermediateEvent: 'var(--gradient-orange)',
endEvent: 'var(--gradient-red)',
exclusiveGateway: 'var(--gradient-purple)',
parallelGateway: 'var(--gradient-cyan)',
inclusiveGateway: 'var(--gradient-olive)',
task: "var(--gradient-blue)",
startEvent: "var(--gradient-green)",
intermediateEvent: "var(--gradient-orange)",
endEvent: "var(--gradient-red)",
exclusiveGateway: "var(--gradient-purple)",
parallelGateway: "var(--gradient-cyan)",
inclusiveGateway: "var(--gradient-olive)",
};
// The color picker only understands flat hex, so it needs a solid fallback
// distinct from the gradient rendered on canvas — the gradient's start color.
export const DEFAULT_NODE_COLOR: Record<NodeKind, string> = {
task: '#0790CB',
startEvent: '#2CB587',
intermediateEvent: '#FF8E58',
endEvent: '#E13042',
exclusiveGateway: '#A12C7E',
parallelGateway: '#1BD3F7',
inclusiveGateway: '#BBBD39',
task: "#0790CB",
startEvent: "#2CB587",
intermediateEvent: "#FF8E58",
endEvent: "#E13042",
exclusiveGateway: "#A12C7E",
parallelGateway: "#1BD3F7",
inclusiveGateway: "#BBBD39",
};
export const DEFAULT_TASK_BORDER_RADIUS = 6;
@@ -41,15 +47,15 @@ export const DEFAULT_TASK_BORDER_RADIUS = 6;
// 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_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 },
{ id: 'bottom', position: Position.Bottom },
{ id: 'left', position: Position.Left },
{ id: "top", position: Position.Top },
{ id: "right", position: Position.Right },
{ id: "bottom", position: Position.Bottom },
{ id: "left", position: Position.Left },
];
/**
@@ -71,7 +77,10 @@ function SubProcessBadge({ id }: { id: string }) {
const withChildren = useContext(NodesWithChildrenContext);
if (!withChildren.has(id)) return null;
return (
<span className="diagram-node__badge" title="Есть вложенные узлы — двойной клик, чтобы открыть">
<span
className="diagram-node__badge"
title="Есть вложенные узлы — двойной клик, чтобы открыть"
>
⊞
</span>
);
@@ -91,7 +100,7 @@ function ShapeResizer({
const mode = useContext(ModeContext);
return (
<NodeResizer
isVisible={selected && mode === 'edit'}
isVisible={selected && mode === "edit"}
minWidth={minWidth}
minHeight={minHeight}
keepAspectRatio={keepAspectRatio}
@@ -108,12 +117,20 @@ function TaskNode({ id, data, selected }: NodeProps<ShapeNode>) {
borderRadius: data.borderRadius ?? DEFAULT_TASK_BORDER_RADIUS,
}}
>
<ShapeResizer selected={selected} minWidth={70} minHeight={32} keepAspectRatio={false} />
<ShapeResizer
selected={selected}
minWidth={70}
minHeight={32}
keepAspectRatio={false}
/>
<ShapeHandles />
<SubProcessBadge id={id} />
<span
className="diagram-node__label"
style={{ color: data.textColor ?? DEFAULT_NODE_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
style={{
color: data.textColor ?? DEFAULT_NODE_TEXT_COLOR,
fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE,
}}
>
{data.label}
</span>
@@ -122,7 +139,8 @@ function TaskNode({ id, data, selected }: NodeProps<ShapeNode>) {
}
function EventNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
const kind = (type as 'startEvent' | 'intermediateEvent' | 'endEvent') ?? 'startEvent';
const kind =
(type as "startEvent" | "intermediateEvent" | "endEvent") ?? "startEvent";
return (
<div
className="diagram-node diagram-node--circle"
@@ -133,7 +151,10 @@ function EventNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
<SubProcessBadge id={id} />
<span
className="diagram-node__caption"
style={{ color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
style={{
color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR,
fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE,
}}
>
{data.label}
</span>
@@ -141,14 +162,19 @@ function EventNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
);
}
const GATEWAY_ICON: Record<'exclusiveGateway' | 'parallelGateway' | 'inclusiveGateway', string> = {
exclusiveGateway: '✕',
parallelGateway: '+',
inclusiveGateway: '○',
const GATEWAY_ICON: Record<
"exclusiveGateway" | "parallelGateway" | "inclusiveGateway",
string
> = {
exclusiveGateway: "✕",
parallelGateway: "+",
inclusiveGateway: "○",
};
function GatewayNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
const kind = (type as 'exclusiveGateway' | 'parallelGateway' | 'inclusiveGateway') ?? 'exclusiveGateway';
const kind =
(type as "exclusiveGateway" | "parallelGateway" | "inclusiveGateway") ??
"exclusiveGateway";
return (
<div className="diagram-node--gateway-outer">
<ShapeResizer selected={selected} minWidth={32} minHeight={32} />
@@ -165,13 +191,19 @@ function GatewayNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
of it, since clip-path would otherwise cut off anything of theirs
that renders outside the diamond outline.
*/}
<div className="diagram-node--diamond-bg" style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND[kind] }} />
<div
className="diagram-node--diamond-bg"
style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND[kind] }}
/>
<ShapeHandles />
<SubProcessBadge id={id} />
<span className="diagram-node__icon">{GATEWAY_ICON[kind]}</span>
<span
className="diagram-node__caption"
style={{ color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR, fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE }}
style={{
color: data.textColor ?? DEFAULT_CAPTION_TEXT_COLOR,
fontSize: data.fontSize ?? DEFAULT_NODE_FONT_SIZE,
}}
>
{data.label}
</span>