+ )
+}
\ No newline at end of file
diff --git a/tools/clarodocs/src/components/module_tree/BUILD b/tools/clarodocs/src/components/module_tree/BUILD
new file mode 100644
index 00000000..158f0305
--- /dev/null
+++ b/tools/clarodocs/src/components/module_tree/BUILD
@@ -0,0 +1,19 @@
+load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
+load("//tools/clarodocs:defs.bzl", "TRANSPILER")
+
+ts_project(
+ name = "module_tree",
+ srcs = ["ModuleTree.tsx"],
+ declaration = True,
+ resolve_json_module = True,
+ transpiler = TRANSPILER,
+ tsconfig = "//tools/clarodocs:tsconfig",
+ visibility = ["//tools/clarodocs:__subpackages__"],
+ deps = [
+ "//tools/clarodocs:node_modules/antd",
+ "//tools/clarodocs:node_modules/@ant-design/icons",
+ "//tools/clarodocs:node_modules/@types/react",
+ "//tools/clarodocs:node_modules/react",
+ "//tools/clarodocs/src/claro_module_apis",
+ ],
+)
diff --git a/tools/clarodocs/src/components/module_tree/ModuleTree.tsx b/tools/clarodocs/src/components/module_tree/ModuleTree.tsx
new file mode 100644
index 00000000..d00e3aad
--- /dev/null
+++ b/tools/clarodocs/src/components/module_tree/ModuleTree.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { Tree } from 'antd';
+import type { TreeDataNode, TreeProps } from 'antd';
+import { DownOutlined } from '@ant-design/icons';
+
+import { getClaroModules } from '../../claro_module_apis/demo';
+
+
+const treeData: TreeDataNode[] = generateTreeData(getClaroModules(null)[0]);
+
+export function ModuleTree({ style, selectedModule, setSelectedModule }): React.FC {
+ const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
+ if (info.node.key.indexOf(':') > 0) {
+ setSelectedModule(info.node.key);
+ }
+ };
+
+ return (
+
}
+ defaultExpandAll={true}
+ defaultSelectedKeys={[selectedModule]}
+ onSelect={onSelect}
+ treeData={treeData}
+ style={style}
+ />
+ );
+};
+
+function generateTreeData(modules) {
+ const treeData: TreeDataNode[] = [];
+
+ const sortedModules =
+ Object.keys(modules)
+ .toSorted((m1, m2) => m1.localeCompare(m2));
+
+ for (let target of sortedModules) {
+ const path = target.split(/\/|:/).filter(s => s !== '')
+ let treeLevel = treeData;
+ let currKey = '/';
+ for (let i = 0; i < path.length; i++) {
+ const pathPart = path[i];
+ currKey += `${i === path.length - 1 ? ':' : '/'}${pathPart}`;
+ const existing = treeLevel.filter(e => e.title === pathPart)[0];
+ if (existing) {
+ treeLevel = existing.children;
+ } else {
+ const newEl = {
+ title: pathPart,
+ key: currKey,
+ children: [],
+ selectable: i === path.length - 1,
+ isLeaf: i === path.length - 1,
+ };
+ treeLevel.push(newEl);
+ treeLevel = newEl.children;
+ }
+ }
+ }
+
+ return treeData;
+}
\ No newline at end of file
diff --git a/tools/clarodocs/src/index.css b/tools/clarodocs/src/index.css
new file mode 100644
index 00000000..ec2585e8
--- /dev/null
+++ b/tools/clarodocs/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/tools/clarodocs/src/index.tsx b/tools/clarodocs/src/index.tsx
new file mode 100644
index 00000000..49bfa652
--- /dev/null
+++ b/tools/clarodocs/src/index.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+ <>
+
+
+ >
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+// eslint-disable-next-line no-new-func
+reportWebVitals(Function());
diff --git a/tools/clarodocs/src/reportWebVitals.ts b/tools/clarodocs/src/reportWebVitals.ts
new file mode 100644
index 00000000..532f29b0
--- /dev/null
+++ b/tools/clarodocs/src/reportWebVitals.ts
@@ -0,0 +1,13 @@
+const reportWebVitals = (onPerfEntry) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/tools/clarodocs/tsconfig.json b/tools/clarodocs/tsconfig.json
new file mode 100644
index 00000000..0b9e0827
--- /dev/null
+++ b/tools/clarodocs/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "noImplicitAny": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "ES2020",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "declaration": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/tools/clarodocs/vite.config.js b/tools/clarodocs/vite.config.js
new file mode 100644
index 00000000..d160a58c
--- /dev/null
+++ b/tools/clarodocs/vite.config.js
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import svgr from 'vite-plugin-svgr'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ base: '/',
+ plugins: [svgr(), react()],
+ test: {
+ globals: true,
+ css: true,
+ reporters: ['verbose']
+ },
+})
\ No newline at end of file