diff --git a/package.json b/package.json
index 860cff737..d286d2771 100644
--- a/package.json
+++ b/package.json
@@ -87,12 +87,14 @@
"license": "Apache-2.0",
"devDependencies": {
"@ai-sdk/azure": "^2.0.53",
- "@emotion/css": "^11.13.5",
"@ai-sdk/google": "^2.0.23",
"@ai-sdk/mcp": "^0.0.8",
"@ai-sdk/openai": "^2.0.52",
+ "@emotion/css": "^11.13.5",
"@eslint/js": "^9.34.0",
"@leafygreen-ui/table": "^15.2.2",
+ "@leafygreen-ui/tokens": "^4.2.0",
+ "@leafygreen-ui/typography": "^22.2.3",
"@modelcontextprotocol/inspector": "^0.17.1",
"@mongodb-js/oidc-mock-provider": "^0.12.0",
"@redocly/cli": "^2.0.8",
@@ -101,8 +103,6 @@
"@types/proper-lockfile": "^4.1.4",
"@types/react": "^18.3.0",
"@types/react-dom": "^19.2.3",
- "react": "^18.3.0",
- "react-dom": "^18.3.0",
"@types/semver": "^7.7.0",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/parser": "^8.44.0",
@@ -123,6 +123,8 @@
"openapi-typescript": "^7.9.1",
"prettier": "^3.6.2",
"proper-lockfile": "^4.1.2",
+ "react": "^18.3.0",
+ "react-dom": "^18.3.0",
"semver": "^7.7.2",
"simple-git": "^3.28.0",
"testcontainers": "^11.7.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 99d17540e..f20d4d011 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,6 +97,12 @@ importers:
'@leafygreen-ui/table':
specifier: ^15.2.2
version: 15.2.2(@leafygreen-ui/leafygreen-provider@5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@leafygreen-ui/tokens':
+ specifier: ^4.2.0
+ version: 4.2.0(react@18.3.1)
+ '@leafygreen-ui/typography':
+ specifier: ^22.2.3
+ version: 22.2.3(@leafygreen-ui/leafygreen-provider@5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@modelcontextprotocol/inspector':
specifier: ^0.17.1
version: 0.17.2(@types/node@24.10.1)(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(typescript@5.9.3)
@@ -1052,6 +1058,11 @@ packages:
peerDependencies:
react: ^17.0.0 || ^18.0.0
+ '@leafygreen-ui/lib@15.7.0':
+ resolution: {integrity: sha512-qHv7oN2uN7ywdG9lUN/ayFJzzgJELLgNxIo24m64Sl9c8gSWQeCW+NDj3ukoNVH7ME7QPoULseNd1fElonH8Vg==}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0
+
'@leafygreen-ui/palette@5.0.2':
resolution: {integrity: sha512-+PrfGeJSv4goxm/vKpfJJDOP7t/uElj+14K8jiIyu3qR3TcFRIZ5h1VMvICTUgqvRc8W+xIZYQwsLa2XCu2lvw==}
@@ -1063,8 +1074,8 @@ packages:
peerDependencies:
'@leafygreen-ui/leafygreen-provider': ^5.0.0 || ^4.0.0 || ^3.2.0
- '@leafygreen-ui/tokens@4.1.0':
- resolution: {integrity: sha512-5GfNFP0iRT4O+CnqYHpvtCUiT3aStUa2EhrV3tkrTwffemHN10M4G5nc/DhLGLNp2aQDP1+ppAtjZI5zczDSiA==}
+ '@leafygreen-ui/tokens@4.2.0':
+ resolution: {integrity: sha512-5Bp1mhh8PKi7ZKr17/VNiSz0LL7ipFgR/Ayj7tIW79sbuGmBFt2rlu2lMnH6hQAvYrHWbW/FDqyeq85jOxIdTQ==}
'@leafygreen-ui/typography@22.2.3':
resolution: {integrity: sha512-zOfPAFyUrUU0G9wPv7lFSMr4FvKi2NiulDMHhy5zea+J4gDn1yuiU0mItG6cwbWxpWSiBeF0gfsZoDuHgKppUg==}
@@ -6353,7 +6364,7 @@ snapshots:
'@leafygreen-ui/leafygreen-provider': 5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@leafygreen-ui/lib': 15.6.2(react@18.3.1)
'@leafygreen-ui/palette': 5.0.2
- '@leafygreen-ui/tokens': 4.1.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
'@leafygreen-ui/typography': 22.2.3(@leafygreen-ui/leafygreen-provider@5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@lg-tools/test-harnesses': 0.3.4
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -6372,7 +6383,7 @@ snapshots:
'@leafygreen-ui/hooks@9.3.0(react@18.3.1)':
dependencies:
'@leafygreen-ui/lib': 15.6.2(react@18.3.1)
- '@leafygreen-ui/tokens': 4.1.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
lodash: 4.17.21
transitivePeerDependencies:
- react
@@ -6387,7 +6398,7 @@ snapshots:
'@leafygreen-ui/lib': 15.6.2(react@18.3.1)
'@leafygreen-ui/palette': 5.0.2
'@leafygreen-ui/polymorphic': 3.1.0(react@18.3.1)
- '@leafygreen-ui/tokens': 4.1.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
polished: 4.3.1
transitivePeerDependencies:
- react
@@ -6417,6 +6428,11 @@ snapshots:
lodash: 4.17.21
react: 18.3.1
+ '@leafygreen-ui/lib@15.7.0(react@18.3.1)':
+ dependencies:
+ lodash: 4.17.21
+ react: 18.3.1
+
'@leafygreen-ui/palette@5.0.2': {}
'@leafygreen-ui/polymorphic@3.1.0(react@18.3.1)':
@@ -6437,7 +6453,7 @@ snapshots:
'@leafygreen-ui/lib': 15.6.2(react@18.3.1)
'@leafygreen-ui/palette': 5.0.2
'@leafygreen-ui/polymorphic': 3.1.0(react@18.3.1)
- '@leafygreen-ui/tokens': 4.1.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
'@leafygreen-ui/typography': 22.2.3(@leafygreen-ui/leafygreen-provider@5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@lg-tools/test-harnesses': 0.3.4
'@tanstack/react-table': 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -6451,10 +6467,10 @@ snapshots:
- react-dom
- supports-color
- '@leafygreen-ui/tokens@4.1.0(react@18.3.1)':
+ '@leafygreen-ui/tokens@4.2.0(react@18.3.1)':
dependencies:
'@leafygreen-ui/emotion': 5.1.0
- '@leafygreen-ui/lib': 15.6.2(react@18.3.1)
+ '@leafygreen-ui/lib': 15.7.0(react@18.3.1)
'@leafygreen-ui/palette': 5.0.2
transitivePeerDependencies:
- react
@@ -6468,7 +6484,7 @@ snapshots:
'@leafygreen-ui/lib': 15.6.2(react@18.3.1)
'@leafygreen-ui/palette': 5.0.2
'@leafygreen-ui/polymorphic': 3.1.0(react@18.3.1)
- '@leafygreen-ui/tokens': 4.1.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
transitivePeerDependencies:
- react
- supports-color
diff --git a/src/ui/build/mount.tsx b/src/ui/build/mount.tsx
index 3bcf27a52..7c53a26bb 100644
--- a/src/ui/build/mount.tsx
+++ b/src/ui/build/mount.tsx
@@ -1,4 +1,5 @@
///
+import "../styles/fonts.css";
import React from "react";
import { createRoot } from "react-dom/client";
diff --git a/src/ui/components/ListDatabases/ListDatabases.styles.ts b/src/ui/components/ListDatabases/ListDatabases.styles.ts
index 678abb66a..ae56f756f 100644
--- a/src/ui/components/ListDatabases/ListDatabases.styles.ts
+++ b/src/ui/components/ListDatabases/ListDatabases.styles.ts
@@ -1,5 +1,13 @@
import { css } from "@emotion/css";
+import { color, InteractionState, Property, spacing, Variant } from "@leafygreen-ui/tokens";
-export const tableStyles = css`
- background: white;
+export const getContainerStyles = (darkMode: boolean): string => css`
+ background-color: ${color[darkMode ? "dark" : "light"][Property.Background][Variant.Primary][
+ InteractionState.Default
+ ]};
+ padding: ${spacing[200]}px;
+`;
+
+export const AmountTextStyles = css`
+ margin-bottom: ${spacing[400]}px;
`;
diff --git a/src/ui/components/ListDatabases/ListDatabases.tsx b/src/ui/components/ListDatabases/ListDatabases.tsx
index b115d7622..9fb8e08c7 100644
--- a/src/ui/components/ListDatabases/ListDatabases.tsx
+++ b/src/ui/components/ListDatabases/ListDatabases.tsx
@@ -1,5 +1,5 @@
-import React from "react";
-import { useRenderData } from "../../hooks/index.js";
+import React, { type ComponentPropsWithoutRef, type FC, type ReactElement } from "react";
+import { useDarkMode, useRenderData } from "../../hooks/index.js";
import {
Cell as LGCell,
HeaderCell as LGHeaderCell,
@@ -9,12 +9,20 @@ import {
TableBody,
TableHead,
} from "@leafygreen-ui/table";
-import { tableStyles } from "./ListDatabases.styles.js";
+import { Body } from "@leafygreen-ui/typography";
import type { ListDatabasesOutput } from "../../../tools/mongodb/metadata/listDatabases.js";
+import { AmountTextStyles, getContainerStyles } from "./ListDatabases.styles.js";
-const HeaderCell = LGHeaderCell as React.FC>;
-const Cell = LGCell as React.FC>;
-const Row = LGRow as React.FC>;
+const HeaderCell = LGHeaderCell as FC>;
+const Cell = LGCell as FC>;
+const Row = LGRow as FC>;
+
+export type Database = ListDatabasesOutput["databases"][number];
+
+interface ListDatabasesProps {
+ databases?: Database[];
+ darkMode?: boolean;
+}
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 Bytes";
@@ -26,37 +34,49 @@ function formatBytes(bytes: number): string {
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
}
-export const ListDatabases = (): React.ReactElement | null => {
- const { data, isLoading, error } = useRenderData();
+export const ListDatabases = ({
+ databases: propDatabases,
+ darkMode: darkModeProp,
+}: ListDatabasesProps): ReactElement | null => {
+ const darkMode = useDarkMode(darkModeProp);
+ const { data: hookData, isLoading, error } = useRenderData();
+ const databases = propDatabases ?? hookData?.databases;
- if (isLoading) {
- return Loading...
;
- }
+ if (!propDatabases) {
+ if (isLoading) {
+ return Loading...
;
+ }
- if (error) {
- return Error: {error}
;
+ if (error) {
+ return Error: {error}
;
+ }
}
- if (!data) {
+ if (!databases) {
return null;
}
return (
-
-
-
- DB Name
- DB Size
-
-
-
- {data.databases.map((db) => (
-
- | {db.name} |
- {formatBytes(db.size)} |
-
- ))}
-
-
+
+
+ Your cluster has
{databases.length} databases:
+
+
+
+
+ Database
+ Size
+
+
+
+ {databases.map((db) => (
+
+ | {db.name} |
+ {formatBytes(db.size)} |
+
+ ))}
+
+
+
);
};
diff --git a/src/ui/hooks/index.ts b/src/ui/hooks/index.ts
index e9d3be4c8..6693ad55c 100644
--- a/src/ui/hooks/index.ts
+++ b/src/ui/hooks/index.ts
@@ -1 +1,2 @@
+export { useDarkMode } from "./useDarkMode.js";
export { useRenderData } from "./useRenderData.js";
diff --git a/src/ui/hooks/useDarkMode.ts b/src/ui/hooks/useDarkMode.ts
new file mode 100644
index 000000000..b438df73a
--- /dev/null
+++ b/src/ui/hooks/useDarkMode.ts
@@ -0,0 +1,20 @@
+import { useSyncExternalStore } from "react";
+
+function subscribeToPrefersColorScheme(callback: () => void): () => void {
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ mediaQuery.addEventListener("change", callback);
+ return () => mediaQuery.removeEventListener("change", callback);
+}
+
+function getPrefersDarkMode(): boolean {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+}
+
+function getServerSnapshot(): boolean {
+ return false;
+}
+
+export function useDarkMode(override?: boolean): boolean {
+ const prefersDarkMode = useSyncExternalStore(subscribeToPrefersColorScheme, getPrefersDarkMode, getServerSnapshot);
+ return override ?? prefersDarkMode;
+}
diff --git a/src/ui/index.ts b/src/ui/index.ts
index b426f9425..ff156e337 100644
--- a/src/ui/index.ts
+++ b/src/ui/index.ts
@@ -1 +1,3 @@
export { UIRegistry } from "./registry/index.js";
+export { ListDatabases } from "./components/ListDatabases/index.js";
+export type { Database } from "./components/ListDatabases/ListDatabases.js";
diff --git a/src/ui/styles/fonts.css b/src/ui/styles/fonts.css
new file mode 100644
index 000000000..cdb68309a
--- /dev/null
+++ b/src/ui/styles/fonts.css
@@ -0,0 +1,128 @@
+/* Euclid Circular A - Semibold */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Semibold-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Semibold-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Semibold.ttf")
+ format("truetype");
+ font-weight: 700;
+ font-style: normal;
+}
+
+/* Euclid Circular A - Semibold Italic */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-SemiboldItalic-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-SemiboldItalic-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-SemiboldItalic.ttf")
+ format("truetype");
+ font-weight: 700;
+ font-style: italic;
+}
+
+/* Euclid Circular A - Medium */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Medium-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Medium-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Medium.ttf") format("truetype");
+ font-weight: 500;
+ font-style: normal;
+}
+
+/* Euclid Circular A - Medium Italic */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-MediumItalic-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-MediumItalic-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-MediumItalic.ttf")
+ format("truetype");
+ font-weight: 500;
+ font-style: italic;
+}
+
+/* Euclid Circular A - Normal */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Regular-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Regular-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-Regular.ttf")
+ format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+
+/* Euclid Circular A - Italic */
+@font-face {
+ font-family: "Euclid Circular A";
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-RegularItalic-WebXL.woff")
+ format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-RegularItalic-WebXL.woff2")
+ format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/euclid-circular/EuclidCircularA-RegularItalic.ttf")
+ format("truetype");
+ font-weight: 400;
+ font-style: italic;
+}
+
+/* MongoDB Value Serif - Bold */
+@font-face {
+ font-family: "MongoDB Value Serif";
+ font-weight: 700;
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Bold.woff") format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Bold.woff2") format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Bold.ttf") format("truetype");
+}
+
+/* MongoDB Value Serif - Medium */
+@font-face {
+ font-family: "MongoDB Value Serif";
+ font-weight: 500;
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Medium.woff") format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Medium.woff2") format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Medium.ttf") format("truetype");
+}
+
+/* MongoDB Value Serif - Normal */
+@font-face {
+ font-family: "MongoDB Value Serif";
+ font-weight: 400;
+ src:
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Regular.woff") format("woff"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Regular.woff2") format("woff2"),
+ url("https://d2va9gm4j17fy9.cloudfront.net/fonts/value-serif/MongoDBValueSerif-Regular.ttf") format("truetype");
+}
+
+html {
+ font-family: "Euclid Circular A", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ font-style: normal;
+}
+
+body {
+ margin: 0;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+}