Skip to content

Commit 3b0b62c

Browse files
committed
Merge branch 'main' of https://github.com/finos/architecture-as-code into deeplink
2 parents aeea3cb + fdaa86f commit 3b0b62c

File tree

9 files changed

+73
-73
lines changed

9 files changed

+73
-73
lines changed

calm-hub-ui/.prettierrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
"singleQuote": true,
66
"bracketSpacing": true,
77
"endOfLine": "auto",
8-
"printWidth": 100
9-
}
8+
"printWidth": 140
9+
}

calm-hub-ui/src/hub/Hub.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ vi.mock('./components/tree-navigation/TreeNavigation', () => ({
88
TreeNavigation: ({
99
onDataLoad,
1010
onAdrLoad,
11+
onCollapse,
1112
}: {
1213
onDataLoad: (data: unknown) => void;
1314
onAdrLoad: (adr: unknown) => void;
15+
onCollapse?: () => void;
1416
}) => (
1517
<div data-testid="tree-navigation">
1618
<div>Tree Navigation</div>
19+
{onCollapse && (
20+
<button aria-label="Collapse sidebar" onClick={onCollapse}>
21+
Collapse
22+
</button>
23+
)}
1724
<button
1825
onClick={() =>
1926
onDataLoad({

calm-hub-ui/src/hub/Hub.tsx

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useMemo, useState } from 'react';
2-
import { IoChevronBackOutline, IoChevronForwardOutline } from 'react-icons/io5';
2+
import { IoChevronForwardOutline } from 'react-icons/io5';
33
import { TreeNavigation } from './components/tree-navigation/TreeNavigation.js';
44
import { Data, Adr } from '../model/calm.js';
55
import { Navbar } from '../components/navbar/Navbar.js';
@@ -33,20 +33,9 @@ export default function Hub() {
3333
<div className={`${isSidebarOpen ? 'w-1/4' : 'w-12'} p-4 pr-2 transition-all duration-300`}>
3434
<div className="h-full bg-base-100 rounded-2xl overflow-hidden shadow-xl flex flex-col">
3535
{isSidebarOpen ? (
36-
<>
37-
<div className="flex items-center justify-end px-2 pt-2">
38-
<button
39-
aria-label="Collapse sidebar"
40-
className="btn btn-ghost btn-xs btn-circle"
41-
onClick={() => setIsSidebarOpen(false)}
42-
>
43-
<IoChevronBackOutline />
44-
</button>
45-
</div>
46-
<div className="flex-1 min-h-0 overflow-hidden">
47-
<TreeNavigation onDataLoad={memoizedDataLoad} onAdrLoad={memoizedAdrLoad} />
48-
</div>
49-
</>
36+
<div className="flex-1 min-h-0 overflow-hidden">
37+
<TreeNavigation onDataLoad={memoizedDataLoad} onAdrLoad={memoizedAdrLoad} onCollapse={() => setIsSidebarOpen(false)} />
38+
</div>
5039
) : (
5140
<div className="flex items-center justify-center pt-3">
5241
<button

calm-hub-ui/src/hub/components/tree-navigation/TreeNavigation.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useCallback, useEffect, useMemo, useState } from 'react';
2-
import { IoCompassOutline } from 'react-icons/io5';
2+
import { IoCompassOutline, IoChevronBackOutline } from 'react-icons/io5';
33
import {
44
fetchNamespaces,
55
fetchPatternIDs,
@@ -62,6 +62,7 @@ const EMPTY_STR_VALUE = '';
6262
interface TreeNavigationProps {
6363
onDataLoad: (data: Data) => void;
6464
onAdrLoad: (adr: Adr) => void;
65+
onCollapse?: () => void;
6566
}
6667

6768
interface VersionItemProps {
@@ -339,7 +340,7 @@ function loadResource({
339340
}
340341
}
341342

342-
export function TreeNavigation({ onDataLoad, onAdrLoad }: TreeNavigationProps) {
343+
export function TreeNavigation({ onDataLoad, onAdrLoad, onCollapse }: TreeNavigationProps) {
343344
const navigate = useNavigate();
344345
const params = useParams<HubParams>();
345346

@@ -481,11 +482,20 @@ export function TreeNavigation({ onDataLoad, onAdrLoad }: TreeNavigationProps) {
481482

482483
return (
483484
<div className="h-full w-full flex flex-col">
484-
<div className="bg-base-200 px-6 py-4 border-b border-base-300">
485+
<div className="bg-base-200 px-6 py-4 border-b border-base-300 flex items-center justify-between">
485486
<h2 className="text-xl font-semibold flex items-center gap-2">
486487
<IoCompassOutline className="text-accent" />
487488
Explore
488489
</h2>
490+
{onCollapse && (
491+
<button
492+
aria-label="Collapse sidebar"
493+
className="btn btn-ghost btn-xs btn-circle"
494+
onClick={onCollapse}
495+
>
496+
<IoChevronBackOutline />
497+
</button>
498+
)}
489499
</div>
490500

491501
<div className="flex-1 overflow-auto p-4">

calm-hub-ui/src/index.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
}
55

66
/*
7-
* Theme colors are defined in src/theme/colors.ts (TypeScript)
8-
* CSS styles below use hardcoded hex values matching that file.
7+
* Brand colors (--color-primary, --color-accent, --color-accent-light)
8+
* are set at runtime from src/theme/colors.ts via initThemeCssVars().
99
*/
1010

1111
#root {

calm-hub-ui/src/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import ReactDOM from 'react-dom/client';
44
import ProtectedRoute from './ProtectedRoute.js';
55
import { isAuthServiceEnabled } from './authService.js';
66
import App from './App.js';
7+
import { initThemeCssVars } from './theme/colors.js';
78
import { LogoutButton } from './components/logout-button/LogoutButton.js';
89

10+
initThemeCssVars();
11+
912
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
1013

1114
const isAuthenticationEnabled = isAuthServiceEnabled();

calm-hub-ui/src/service/calm-service.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ export async function fetchNamespaces(setNamespaces: (namespaces: string[]) => v
1212
headers,
1313
});
1414
const data = await res.json();
15-
const values = Array.isArray(data?.values) ? data.values : [];
16-
const namespaces = values
15+
const namespaces = (data?.values ?? [])
1716
.map((v: { name?: string }) => v?.name)
18-
.filter((name): name is string => typeof name === 'string');
17+
.filter((name: string | undefined): name is string => !!name);
1918
setNamespaces(namespaces);
2019
} catch (error) {
2120
console.error('Error fetching namespaces:', error);

calm-hub-ui/src/theme/colors.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
* This file defines all color values used throughout the application.
55
* Components should import from this file rather than defining colors inline.
66
*
7+
* Brand colors are also exposed as CSS custom properties via initThemeCssVars()
8+
* called at app startup, so they can be used in Tailwind/DaisyUI contexts.
9+
*
710
* Note: ReactFlow and inline styles need direct hex values because:
811
* 1. They can't use CSS variables in some contexts (e.g., SVG markers)
912
* 2. We need to create transparent variants (e.g., `${color}20`)
@@ -109,3 +112,15 @@ export const colors = {
109112
} as const;
110113

111114
export type Colors = typeof colors;
115+
116+
/**
117+
* Sets CSS custom properties on the document root from colors.brand,
118+
* so Tailwind/DaisyUI classes can reference them without duplicating values.
119+
* Call once at app startup (e.g., in index.tsx).
120+
*/
121+
export function initThemeCssVars(): void {
122+
const root = document.documentElement;
123+
root.style.setProperty('--color-primary', colors.brand.primary);
124+
root.style.setProperty('--color-accent', colors.brand.accent);
125+
root.style.setProperty('--color-accent-light', colors.brand.accentLight);
126+
}

calm-hub-ui/src/visualizer/components/drawer/Drawer.tsx

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { useCallback, useEffect, useMemo, useState } from 'react';
2-
import {
3-
CalmArchitectureSchema,
4-
CalmNodeSchema,
5-
CalmRelationshipSchema,
6-
} from '@finos/calm-models/types';
2+
import { CalmArchitectureSchema, CalmNodeSchema, CalmRelationshipSchema } from '@finos/calm-models/types';
73
import { useDropzone } from 'react-dropzone';
84
import { ReactFlowVisualizer } from '../reactflow/ReactFlowVisualizer.js';
95
import { PatternVisualizer } from '../reactflow/PatternVisualizer.js';
106
import { Sidebar } from '../sidebar/Sidebar.js';
117
import { MetadataPanel } from '../reactflow/MetadataPanel.js';
128
import { toSidebarNodeData, toSidebarEdgeData } from '../reactflow/utils/patternClickHandlers.js';
13-
import { THEME } from '../reactflow/theme.js';
149
import type { DrawerProps, SelectedItem, Flow, Control } from '../../contracts/contracts.js';
1510

1611
/**
@@ -21,8 +16,7 @@ function isPatternData(data: unknown): boolean {
2116
if (!data || typeof data !== 'object') return false;
2217
const obj = data as Record<string, unknown>;
2318
const props = obj['properties'] as Record<string, unknown> | undefined;
24-
return !!(props?.['nodes'] && typeof props['nodes'] === 'object' &&
25-
(props['nodes'] as Record<string, unknown>)['prefixItems']);
19+
return !!(props?.['nodes'] && typeof props['nodes'] === 'object' && (props['nodes'] as Record<string, unknown>)['prefixItems']);
2620
}
2721

2822
/**
@@ -37,7 +31,6 @@ export function Drawer({ data }: DrawerProps) {
3731
const [patternInstance, setPatternInstance] = useState<Record<string, unknown> | undefined>(undefined);
3832
const [fileInstance, setFileInstance] = useState<Record<string, unknown> | undefined>(undefined);
3933
const [selectedItem, setSelectedItem] = useState<SelectedItem>(null);
40-
const [title, setTitle] = useState<string>('');
4134
// Default to collapsed as per user request
4235
const [isMetadataCollapsed, setIsMetadataCollapsed] = useState(true);
4336
// Height of the metadata panel when expanded (in pixels)
@@ -48,24 +41,17 @@ export function Drawer({ data }: DrawerProps) {
4841
const fileText = await acceptedFiles[0].text();
4942
const parsed = JSON.parse(fileText);
5043
setFileInstance(parsed);
51-
setTitle(acceptedFiles[0].name);
5244
}
5345
}, []);
5446

5547
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
5648

5749
useEffect(() => {
5850
const source = fileInstance ?? data?.data;
59-
const isPattern = !!source && (
60-
isPatternData(source) || (!fileInstance && data?.calmType === 'Patterns')
61-
);
51+
const isPattern = !!source && (isPatternData(source) || (!fileInstance && data?.calmType === 'Patterns'));
6252

6353
setPatternInstance(isPattern ? (source as Record<string, unknown>) : undefined);
6454
setCALMInstance(isPattern ? undefined : (source as CalmArchitectureSchema | undefined));
65-
66-
if (data?.name && data?.id && data?.version) {
67-
setTitle(`${data.name}/${data.id}/${data.version}`);
68-
}
6955
}, [fileInstance, data]);
7056

7157
// Extract flows from CALM data
@@ -76,7 +62,9 @@ export function Drawer({ data }: DrawerProps) {
7662

7763
// Extract controls from CALM data (from root, nodes, and relationships)
7864
const controls = useMemo((): Record<string, Control> => {
79-
const calmData = calmInstance as CalmArchitectureSchema & { controls?: Record<string, Control> };
65+
const calmData = calmInstance as CalmArchitectureSchema & {
66+
controls?: Record<string, Control>;
67+
};
8068
if (!calmData) return {};
8169

8270
const rootControls: Record<string, Control> = calmData.controls || {};
@@ -147,20 +135,26 @@ export function Drawer({ data }: DrawerProps) {
147135
}, []);
148136

149137
// Handle transition click from flows panel - highlight the relationship
150-
const handleTransitionClick = useCallback((relationshipId: string) => {
151-
const relationship = calmInstance?.relationships?.find((r) => r['unique-id'] === relationshipId);
152-
if (relationship) {
153-
handleEdgeClick(relationship);
154-
}
155-
}, [calmInstance, handleEdgeClick]);
138+
const handleTransitionClick = useCallback(
139+
(relationshipId: string) => {
140+
const relationship = calmInstance?.relationships?.find((r) => r['unique-id'] === relationshipId);
141+
if (relationship) {
142+
handleEdgeClick(relationship);
143+
}
144+
},
145+
[calmInstance, handleEdgeClick]
146+
);
156147

157148
// Handle node click from controls panel
158-
const handleControlNodeClick = useCallback((nodeId: string) => {
159-
const node = calmInstance?.nodes?.find((n) => n['unique-id'] === nodeId);
160-
if (node) {
161-
handleNodeClick(node);
162-
}
163-
}, [calmInstance, handleNodeClick]);
149+
const handleControlNodeClick = useCallback(
150+
(nodeId: string) => {
151+
const node = calmInstance?.nodes?.find((n) => n['unique-id'] === nodeId);
152+
if (node) {
153+
handleNodeClick(node);
154+
}
155+
},
156+
[calmInstance, handleNodeClick]
157+
);
164158

165159
return (
166160
<div {...getRootProps()} className="flex-1 flex overflow-hidden h-full">
@@ -176,28 +170,11 @@ export function Drawer({ data }: DrawerProps) {
176170
<div className="drawer-content h-full flex flex-col">
177171
{hasContent ? (
178172
<>
179-
{title && (
180-
<div
181-
style={{
182-
padding: '8px 16px',
183-
borderBottom: `1px solid ${THEME.colors.border}`,
184-
backgroundColor: THEME.colors.backgroundSecondary,
185-
fontSize: '14px',
186-
fontWeight: 500,
187-
color: THEME.colors.foreground,
188-
flexShrink: 0,
189-
}}
190-
>
191-
{title}
192-
</div>
193-
)}
194173
<div
195174
style={{
196175
flex: 1,
197176
minHeight: 0,
198-
...(hasMetadata && !isMetadataCollapsed
199-
? { height: `calc(100% - ${metadataPanelHeight}px)` }
200-
: {}),
177+
...(hasMetadata && !isMetadataCollapsed ? { height: `calc(100% - ${metadataPanelHeight}px)` } : {}),
201178
}}
202179
>
203180
{patternInstance ? (

0 commit comments

Comments
 (0)