Skip to content

Commit 8956a43

Browse files
committed
feat(client): improve layout, add secondary arrows
1 parent 1fc466b commit 8956a43

File tree

5 files changed

+115
-104
lines changed

5 files changed

+115
-104
lines changed

libs/blog/roadmap/feature-roadmap/src/lib/feature-roadmap.component.html

+68-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
<svg width="100%" height="100%" xmlns:svg="http://www.w3.org/1999/html">
2-
<svg:foreignObject height="0" width="0" overflow="visible">
1+
<svg
2+
class="roadmap-container"
3+
width="100%"
4+
height="100%"
5+
xmlns:svg="http://www.w3.org/1999/html"
6+
>
7+
<svg:foreignObject
8+
style="transform: translateX(calc(50% - {{ layoutEl.clientWidth / 2 }}px))"
9+
height="0"
10+
width="0"
11+
overflow="visible"
12+
>
313
<div
4-
class="relative flex h-fit w-fit translate-x-80 translate-y-16 flex-col items-center gap-16"
14+
#layoutEl
15+
class="relative flex h-fit w-fit translate-y-16 flex-col items-center gap-16"
516
>
617
@for (layer of roadmapLayers(); track layer.parentNode.id) {
718
<div #layerEl class="flex w-full flex-col items-center gap-16">
@@ -14,7 +25,7 @@
1425
width="100"
1526
xmlns="http://www.w3.org/2000/svg"
1627
>
17-
<svg:defs>
28+
<defs>
1829
<marker
1930
id="arrowhead"
2031
markerWidth="6"
@@ -25,7 +36,7 @@
2536
>
2637
<polygon points="0 0, 6 4, 0 8" fill="#FDF5FD" />
2738
</marker>
28-
</svg:defs>
39+
</defs>
2940
<line
3041
[attr.y2]="layerHeightWithGap"
3142
x1="50"
@@ -49,15 +60,40 @@
4960

5061
<!-- layer child nodes-->
5162
@if (layer.childNodes?.length) {
63+
@let shift =
64+
(allChildNodesEl.clientWidth / 2 - leftChildNodesEl.clientWidth ||
65+
0) - 32;
5266
<div
5367
#allChildNodesEl
54-
class="translate-x-[{{
55-
allChildNodesEl.clientWidth - leftChildNodesEl.clientWidth
56-
}}px] flex gap-12"
68+
class="flex gap-16"
69+
style="transform: translate({{ shift }}px)"
5770
>
5871
<div #leftChildNodesEl class="flex gap-12">
72+
@let centerShift = layerEl.clientWidth / 2 - shift;
5973
@for (node of layer.childNodes | leftSlice; track node.id) {
60-
<div class="flex gap-10">
74+
@let arrowShift =
75+
secondaryNode.offsetLeft -
76+
centerShift +
77+
secondaryNode.clientWidth / 2;
78+
<svg
79+
[attr.height]="64"
80+
style="position: absolute; top: -64px; left: {{
81+
centerShift
82+
}}px"
83+
overflow="visible"
84+
width="1"
85+
xmlns="http://www.w3.org/2000/svg"
86+
>
87+
<svg:path
88+
[attr.d]="arrowShift | secondaryArrow: $first"
89+
fill="none"
90+
stroke="#FDF5FD"
91+
stroke-width="2"
92+
stroke-linecap="round"
93+
stroke-linejoin="round"
94+
/>
95+
</svg>
96+
<div #secondaryNode>
6197
@if (node.nodeType === 'secondary') {
6298
<al-ui-roadmap-secondary-node [node]="node" />
6399
} @else if (node.nodeType === 'cluster') {
@@ -69,7 +105,29 @@
69105
}
70106
</div>
71107
@for (node of layer.childNodes | rightSlice; track node.id) {
72-
<div class="flex gap-10">
108+
@let arrowShift =
109+
secondaryNode.offsetLeft -
110+
centerShift +
111+
secondaryNode.clientWidth / 2;
112+
<svg
113+
[attr.height]="64"
114+
style="position: absolute; top: -64px; left: {{
115+
centerShift
116+
}}px"
117+
overflow="visible"
118+
width="1"
119+
xmlns="http://www.w3.org/2000/svg"
120+
>
121+
<svg:path
122+
[attr.d]="arrowShift | secondaryArrow: $last"
123+
fill="none"
124+
stroke="#FDF5FD"
125+
stroke-width="2"
126+
stroke-linecap="round"
127+
stroke-linejoin="round"
128+
/>
129+
</svg>
130+
<div #secondaryNode>
73131
@if (node.nodeType === 'secondary') {
74132
<al-ui-roadmap-secondary-node [node]="node" />
75133
} @else if (node.nodeType === 'cluster') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.roadmap-container {
2+
animation: fadeIn 1s ease-in-out;
3+
}
4+
5+
@keyframes fadeIn {
6+
from {
7+
opacity: 0;
8+
}
9+
to {
10+
opacity: 1;
11+
}
12+
}

libs/blog/roadmap/feature-roadmap/src/lib/feature-roadmap.component.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
signal,
66
} from '@angular/core';
77

8+
import { SecondaryArrowPipe } from './secondary-arrow.pipe';
89
import { LeftSlicePipe, RightSlicePipe } from './slice.pipes';
910
import { UiRoadmapAngularLoveNodeComponent } from './ui/ui-roadmap-angular-love-node.component';
1011
import { UiRoadmapClusterComponent } from './ui/ui-roadmap-cluster.component';
@@ -45,6 +46,7 @@ export interface RoadmapLayer {
4546
UiRoadmapPrimaryNodeComponent,
4647
UiRoadmapAngularLoveNodeComponent,
4748
UiRoadmapSecondaryNodeComponent,
49+
SecondaryArrowPipe,
4850
],
4951
templateUrl: './feature-roadmap.component.html',
5052
styleUrl: './feature-roadmap.component.scss',
@@ -72,11 +74,6 @@ export class FeatureRoadmapComponent {
7274
parentNodeId: '2',
7375
title: 'Styling',
7476
},
75-
{
76-
id: '11',
77-
parentNodeId: '2',
78-
title: 'Ble ble ble',
79-
},
8077
{
8178
id: '6',
8279
parentNodeId: '4',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
3+
@Pipe({
4+
name: 'secondaryArrow',
5+
})
6+
export class SecondaryArrowPipe implements PipeTransform {
7+
transform(horizontalShift: number, withArc = true): string {
8+
const radius = withArc ? 12 : 0; // Corner radius
9+
const height = 64;
10+
11+
let path = `M 0 0 `;
12+
13+
// Line down
14+
path += `L 0 ${height / 2} `;
15+
16+
// Line right
17+
path += `L ${horizontalShift + (horizontalShift > 0 ? -radius : radius)} ${height / 2} `;
18+
19+
if (withArc) {
20+
// Proper arc for the curved corner - this creates a 90° turn with exact radius
21+
if (horizontalShift > 0) {
22+
path += `A ${radius} ${radius} 0 0 1 ${horizontalShift} ${height / 2 + radius} `;
23+
} else {
24+
path += `A ${radius} ${radius} 0 0 0 ${horizontalShift} ${height / 2 + radius} `;
25+
}
26+
}
27+
28+
// Line down after the curve
29+
path += `L ${horizontalShift} ${height}`; // Subtract 7 to account for arrowhead
30+
31+
return path;
32+
}
33+
}

libs/blog/roadmap/feature-roadmap/src/lib/temp.component.ts

-89
This file was deleted.

0 commit comments

Comments
 (0)