Skip to content

Commit 723bea0

Browse files
committed
feat(masonry): [brick] update other compoenents to reuse wrapper componenent
Signed-off-by: karan-palan <[email protected]>
1 parent 84ba115 commit 723bea0

File tree

4 files changed

+90
-416
lines changed

4 files changed

+90
-416
lines changed
Lines changed: 35 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,56 @@
1-
import React, { useState, useEffect, useMemo } from 'react';
1+
import React from 'react';
22
import type { TBrickRenderPropsCompound } from '../../@types/brick';
3-
import { generateBrickData } from '../../utils/path';
4-
5-
const FONT_HEIGHT = 16;
6-
7-
const PADDING = {
8-
top: 4,
9-
right: 8,
10-
bottom: 4,
11-
left: 8,
12-
};
13-
14-
function toCssColor(color: string | ['rgb' | 'hsl', number, number, number]) {
15-
if (typeof color === 'string') return color;
16-
const [mode, a, b, c] = color;
17-
return mode === 'rgb' ? `rgb(${a},${b},${c})` : `hsl(${a},${b}%,${c}%)`;
18-
}
19-
20-
function measureLabel(label: string, fontSize: number) {
21-
const canvas = document.createElement('canvas');
22-
const ctx = canvas.getContext('2d')!;
23-
ctx.font = `${fontSize}px sans-serif`;
24-
25-
const m = ctx.measureText(label);
26-
const ascent = m.actualBoundingBoxAscent ?? fontSize * 0.8;
27-
const descent = m.actualBoundingBoxDescent ?? fontSize * 0.2;
28-
const height = ascent + descent;
29-
30-
return {
31-
w: m.width + 8,
32-
h: height,
33-
ascent,
34-
descent,
35-
};
36-
}
37-
38-
type TConnectionPoints = {
39-
top?: { x: number; y: number };
40-
right: { x: number; y: number }[];
41-
bottom?: { x: number; y: number };
42-
left?: { x: number; y: number };
43-
};
3+
import { BrickWrapper } from './BrickWrapper';
4+
import type { TConnectionPoints } from '../utils/common';
445

456
type PropsWithMetrics = TBrickRenderPropsCompound & {
467
RenderMetrics?: (bbox: { w: number; h: number }, connectionPoints: TConnectionPoints) => void;
478
};
489

4910
export const CompoundBrickView: React.FC<PropsWithMetrics> = (props) => {
5011
const {
51-
label,
52-
labelType,
53-
colorBg,
54-
colorFg,
55-
strokeColor,
56-
strokeWidth,
57-
scale,
58-
shadow,
59-
tooltip,
60-
bboxArgs,
61-
visualState,
62-
isActionMenuOpen,
63-
isVisible,
6412
topNotch,
6513
bottomNotch,
6614
bboxNest,
6715
isFolded,
68-
RenderMetrics,
69-
} = props;
70-
71-
// Memoize bBoxLabel to prevent unnecessary recalculations
72-
const bBoxLabel = useMemo(() => {
73-
const { w: labelW, h: labelH } = measureLabel(label, FONT_HEIGHT);
74-
return { w: labelW, h: labelH };
75-
}, [label]);
76-
77-
const bBoxNesting = bboxNest;
78-
79-
const [shape, setShape] = useState<{ path: string; w: number; h: number }>(() => {
80-
const cfg = {
81-
type: 'type3' as const,
82-
strokeWidth,
83-
scaleFactor: scale,
84-
bBoxLabel,
85-
bBoxArgs: bboxArgs,
86-
hasNotchAbove: topNotch,
87-
hasNotchBelow: bottomNotch,
88-
bBoxNesting,
89-
secondaryLabel: !isFolded,
90-
};
91-
const brickData = generateBrickData(cfg);
92-
return {
93-
path: brickData.path,
94-
w: brickData.boundingBox.w,
95-
h: brickData.boundingBox.h,
96-
};
97-
});
98-
99-
useEffect(() => {
100-
const cfg = {
101-
type: 'type3' as const,
102-
strokeWidth,
103-
scaleFactor: scale,
104-
bBoxLabel,
105-
bBoxArgs: bboxArgs,
106-
hasNotchAbove: topNotch,
107-
hasNotchBelow: bottomNotch,
108-
bBoxNesting,
109-
secondaryLabel: !isFolded,
110-
};
111-
const brickData = generateBrickData(cfg);
112-
if (RenderMetrics) {
113-
RenderMetrics(brickData.boundingBox, brickData.connectionPoints);
114-
}
115-
setShape({ path: brickData.path, w: brickData.boundingBox.w, h: brickData.boundingBox.h });
116-
}, [
117-
label,
11816
strokeWidth,
11917
scale,
12018
bboxArgs,
121-
topNotch,
122-
bottomNotch,
123-
bboxNest,
124-
isFolded,
125-
bBoxLabel,
126-
bBoxNesting,
127-
RenderMetrics,
128-
]);
129-
130-
if (!isVisible) return null;
19+
...commonProps
20+
} = props;
13121

132-
const svgWidth = shape.w + PADDING.left + PADDING.right;
133-
const svgHeight = shape.h + PADDING.top + PADDING.bottom;
22+
const getBrickConfig = (bBoxLabel: { w: number; h: number }) => ({
23+
type: 'type3' as const,
24+
strokeWidth,
25+
scaleFactor: scale,
26+
bBoxLabel,
27+
bBoxArgs: bboxArgs,
28+
hasNotchAbove: topNotch,
29+
hasNotchBelow: bottomNotch,
30+
bBoxNesting: bboxNest,
31+
secondaryLabel: true,
32+
});
13433

13534
return (
136-
<svg
137-
width={svgWidth}
138-
height={svgHeight}
139-
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
140-
data-visual-state={visualState}
141-
data-action-menu-open={isActionMenuOpen}
142-
style={{ overflow: 'visible' }}
35+
<BrickWrapper
36+
{...commonProps}
37+
strokeWidth={strokeWidth}
38+
scale={scale}
39+
bboxArgs={bboxArgs}
40+
getBrickConfig={getBrickConfig}
14341
>
144-
<g transform={`translate(${PADDING.left},${PADDING.top})`}>
145-
{/* Brick outline */}
146-
<path
147-
d={shape.path}
148-
fill={toCssColor(colorBg)}
149-
stroke={toCssColor(strokeColor)}
150-
strokeWidth={strokeWidth}
151-
filter={shadow ? 'drop-shadow(0 2px 2px rgba(0,0,0,0.2))' : undefined}
152-
/>
153-
154-
{/* Text label */}
155-
{labelType === 'text' && (
156-
<text
157-
x={strokeWidth + 4}
158-
y={bBoxLabel.h * 0.8 + strokeWidth / 2}
159-
fill={toCssColor(colorFg)}
160-
fontSize={FONT_HEIGHT}
161-
style={{ userSelect: 'none', pointerEvents: 'none' }}
162-
>
163-
{label}
164-
</text>
165-
)}
166-
167-
{/* Tooltip */}
168-
{tooltip && <title>{tooltip}</title>}
169-
</g>
170-
</svg>
42+
{/* Compound-specific content */}
43+
{isFolded && (
44+
<text
45+
x={strokeWidth + 4}
46+
y={40}
47+
fill="rgba(0,0,0,0.5)"
48+
fontSize={10}
49+
style={{ userSelect: 'none', pointerEvents: 'none' }}
50+
>
51+
[...]
52+
</text>
53+
)}
54+
</BrickWrapper>
17155
);
17256
};
Lines changed: 32 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,46 @@
1-
import React, { useState, useEffect, useMemo } from 'react';
1+
import React from 'react';
22
import type { TBrickRenderPropsExpression } from '../../@types/brick';
3-
import { generateBrickData } from '../../utils/path';
4-
5-
const FONT_HEIGHT = 16;
6-
7-
const PADDING = {
8-
top: 4,
9-
right: 8,
10-
bottom: 4,
11-
left: 8,
12-
};
13-
14-
function toCssColor(color: string | ['rgb' | 'hsl', number, number, number]) {
15-
if (typeof color === 'string') return color;
16-
const [mode, a, b, c] = color;
17-
return mode === 'rgb' ? `rgb(${a},${b},${c})` : `hsl(${a},${b}%,${c}%)`;
18-
}
19-
20-
function measureLabel(label: string, fontSize: number) {
21-
const canvas = document.createElement('canvas');
22-
const ctx = canvas.getContext('2d')!;
23-
ctx.font = `${fontSize}px sans-serif`;
24-
25-
const m = ctx.measureText(label);
26-
const ascent = m.actualBoundingBoxAscent ?? fontSize * 0.8;
27-
const descent = m.actualBoundingBoxDescent ?? fontSize * 0.2;
28-
29-
return {
30-
w: m.width + 8,
31-
h: ascent + descent,
32-
ascent,
33-
descent,
34-
};
35-
}
36-
37-
type TConnectionPoints = {
38-
top?: { x: number; y: number };
39-
right: { x: number; y: number }[];
40-
bottom?: { x: number; y: number };
41-
left?: { x: number; y: number };
42-
};
3+
import { BrickWrapper } from './BrickWrapper';
4+
import type { TConnectionPoints } from '../utils/common';
435

446
type PropsWithMetrics = TBrickRenderPropsExpression & {
457
RenderMetrics?: (bbox: { w: number; h: number }, connectionPoints: TConnectionPoints) => void;
468
};
479

4810
export const ExpressionBrickView: React.FC<PropsWithMetrics> = (props) => {
49-
const {
50-
label,
51-
labelType,
52-
colorBg,
53-
colorFg,
54-
strokeColor,
55-
strokeWidth,
56-
scale,
57-
shadow,
58-
tooltip,
59-
bboxArgs,
60-
visualState,
61-
isActionMenuOpen,
62-
isVisible,
63-
RenderMetrics,
64-
// value, isValueSelectOpen, // if needed later
65-
} = props;
66-
67-
// Memoize bBoxLabel to prevent unnecessary recalculations
68-
const bBoxLabel = useMemo(() => {
69-
const { w: labelW, h: labelH } = measureLabel(label, FONT_HEIGHT);
70-
return { w: labelW, h: labelH };
71-
}, [label]);
11+
const { value, isValueSelectOpen, strokeWidth, scale, bboxArgs, ...commonProps } = props;
7212

73-
const [shape, setShape] = useState<{ path: string; w: number; h: number }>(() => {
74-
const cfg = {
75-
type: 'type2' as const,
76-
strokeWidth,
77-
scaleFactor: scale,
78-
bBoxLabel,
79-
bBoxArgs: bboxArgs,
80-
};
81-
const brickData = generateBrickData(cfg);
82-
return {
83-
path: brickData.path,
84-
w: brickData.boundingBox.w,
85-
h: brickData.boundingBox.h,
86-
};
13+
const getBrickConfig = (bBoxLabel: { w: number; h: number }) => ({
14+
type: 'type2' as const,
15+
strokeWidth,
16+
scaleFactor: scale,
17+
bBoxLabel,
18+
bBoxArgs: bboxArgs,
8719
});
8820

89-
useEffect(() => {
90-
const cfg = {
91-
type: 'type2' as const,
92-
strokeWidth,
93-
scaleFactor: scale,
94-
bBoxLabel,
95-
bBoxArgs: bboxArgs,
96-
};
97-
const brickData = generateBrickData(cfg);
98-
if (RenderMetrics) {
99-
RenderMetrics(brickData.boundingBox, brickData.connectionPoints);
100-
}
101-
setShape({ path: brickData.path, w: brickData.boundingBox.w, h: brickData.boundingBox.h });
102-
}, [label, strokeWidth, scale, bboxArgs, bBoxLabel, RenderMetrics]);
103-
104-
if (!isVisible) return null;
105-
106-
const svgWidth = shape.w + PADDING.left + PADDING.right;
107-
const svgHeight = shape.h + PADDING.top + PADDING.bottom;
108-
10921
return (
110-
<svg
111-
width={svgWidth}
112-
height={svgHeight}
113-
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
114-
data-visual-state={visualState}
115-
data-action-menu-open={isActionMenuOpen}
116-
style={{ overflow: 'visible' }}
22+
<BrickWrapper
23+
{...commonProps}
24+
strokeWidth={strokeWidth}
25+
scale={scale}
26+
bboxArgs={bboxArgs}
27+
getBrickConfig={getBrickConfig}
11728
>
118-
<g transform={`translate(${PADDING.left},${PADDING.top})`}>
119-
{/* Brick background outline */}
120-
<path
121-
d={shape.path}
122-
fill={toCssColor(colorBg)}
123-
stroke={toCssColor(strokeColor)}
124-
strokeWidth={strokeWidth}
125-
filter={shadow ? 'drop-shadow(0 2px 2px rgba(0,0,0,0.2))' : undefined}
126-
/>
127-
128-
{/* Text label */}
129-
{labelType === 'text' && (
130-
<text
131-
x={strokeWidth + 4}
132-
y={bBoxLabel.h * 0.8 + strokeWidth / 2}
133-
fill={toCssColor(colorFg)}
134-
fontSize={FONT_HEIGHT}
135-
style={{ userSelect: 'none', pointerEvents: 'none' }}
136-
>
137-
{label}
138-
</text>
139-
)}
140-
</g>
141-
142-
{/* Accessibility tooltip */}
143-
{tooltip && <title>{tooltip}</title>}
144-
</svg>
29+
{/* Expression-specific content */}
30+
{value !== undefined && (
31+
<text
32+
x={strokeWidth + 60}
33+
y={16 * 0.8 + strokeWidth / 2}
34+
fill="rgba(0,0,0,0.6)"
35+
fontSize={12}
36+
style={{ userSelect: 'none', pointerEvents: 'none' }}
37+
>
38+
{String(value)}
39+
</text>
40+
)}
41+
{isValueSelectOpen && (
42+
<circle cx={strokeWidth + 80} cy={10} r={3} fill="orange" opacity={0.8} />
43+
)}
44+
</BrickWrapper>
14545
);
14646
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { BrickWrapper } from './BrickWrapper';
2+
export { SimpleBrickView } from './simple';
3+
export { ExpressionBrickView } from './expression';
4+
export { CompoundBrickView } from './compound';

0 commit comments

Comments
 (0)