diff --git a/package.json b/package.json
index 59938d2e3..37e521241 100644
--- a/package.json
+++ b/package.json
@@ -92,7 +92,10 @@
"@ai-sdk/openai": "^2.0.52",
"@emotion/css": "^11.13.5",
"@eslint/js": "^9.34.0",
+ "@leafygreen-ui/lib": "^15.7.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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f037dbfc7..f3ade655c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -94,9 +94,18 @@ importers:
'@eslint/js':
specifier: ^9.34.0
version: 9.39.1
+ '@leafygreen-ui/lib':
+ specifier: ^15.7.0
+ version: 15.7.0(react@18.3.1)
'@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)
@@ -1097,11 +1106,6 @@ packages:
'@leafygreen-ui/leafygreen-provider@5.0.4':
resolution: {integrity: sha512-VDlmjTiIqlITVhq4VKUDq8FLySWnHkTxSV2n1sxOanLNPuatOXjxsPmCkPUBXhmQKk/fBf4yQnDKOwJvkyzE6Q==}
- '@leafygreen-ui/lib@15.6.2':
- resolution: {integrity: sha512-HsjXXovBqyrXL3Y7V09g2bEidGn58AK/mfoIPMlen/hEcWlbXbefoCzXF7AQqWLhd0F/lS2XgX7+BqVUHXx2/Q==}
- peerDependencies:
- react: ^17.0.0 || ^18.0.0
-
'@leafygreen-ui/lib@15.7.0':
resolution: {integrity: sha512-qHv7oN2uN7ywdG9lUN/ayFJzzgJELLgNxIo24m64Sl9c8gSWQeCW+NDj3ukoNVH7ME7QPoULseNd1fElonH8Vg==}
peerDependencies:
@@ -1118,8 +1122,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==}
@@ -6543,7 +6547,7 @@ snapshots:
dependencies:
'@leafygreen-ui/emotion': 5.1.0
'@leafygreen-ui/hooks': 9.3.0(react@18.3.1)
- '@leafygreen-ui/lib': 15.6.2(react@18.3.1)
+ '@leafygreen-ui/lib': 15.7.0(react@18.3.1)
transitivePeerDependencies:
- react
- supports-color
@@ -6554,9 +6558,9 @@ snapshots:
'@leafygreen-ui/emotion': 5.1.0
'@leafygreen-ui/hooks': 9.3.0(react@18.3.1)
'@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/lib': 15.7.0(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)
@@ -6574,8 +6578,8 @@ 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/lib': 15.7.0(react@18.3.1)
+ '@leafygreen-ui/tokens': 4.2.0(react@18.3.1)
lodash: 4.17.21
transitivePeerDependencies:
- react
@@ -6587,10 +6591,10 @@ snapshots:
'@leafygreen-ui/emotion': 5.1.0
'@leafygreen-ui/icon': 14.7.1(react@18.3.1)
'@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/lib': 15.7.0(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
@@ -6615,11 +6619,6 @@ snapshots:
- react-dom
- supports-color
- '@leafygreen-ui/lib@15.6.2(react@18.3.1)':
- dependencies:
- lodash: 4.17.21
- react: 18.3.1
-
'@leafygreen-ui/lib@15.7.0(react@18.3.1)':
dependencies:
lodash: 4.17.21
@@ -6629,7 +6628,7 @@ snapshots:
'@leafygreen-ui/polymorphic@3.1.0(react@18.3.1)':
dependencies:
- '@leafygreen-ui/lib': 15.6.2(react@18.3.1)
+ '@leafygreen-ui/lib': 15.7.0(react@18.3.1)
lodash: 4.17.21
transitivePeerDependencies:
- react
@@ -6642,10 +6641,10 @@ snapshots:
'@leafygreen-ui/icon': 14.7.1(react@18.3.1)
'@leafygreen-ui/icon-button': 17.1.4(@leafygreen-ui/leafygreen-provider@5.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@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/lib': 15.7.0(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)
@@ -6659,10 +6658,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
@@ -6673,10 +6672,10 @@ snapshots:
'@leafygreen-ui/emotion': 5.1.0
'@leafygreen-ui/icon': 14.7.1(react@18.3.1)
'@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/lib': 15.7.0(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..c5f2d0485 100644
--- a/src/ui/components/ListDatabases/ListDatabases.styles.ts
+++ b/src/ui/components/ListDatabases/ListDatabases.styles.ts
@@ -1,5 +1,14 @@
import { css } from "@emotion/css";
+import { color, InteractionState, Property, spacing, Variant } from "@leafygreen-ui/tokens";
+import { Theme } from "@leafygreen-ui/lib";
-export const tableStyles = css`
- background: white;
+export const getContainerStyles = (darkMode: boolean): string => css`
+ background-color: ${color[darkMode ? Theme.Dark : Theme.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..c22ba2723 100644
--- a/src/ui/components/ListDatabases/ListDatabases.tsx
+++ b/src/ui/components/ListDatabases/ListDatabases.tsx
@@ -1,20 +1,16 @@
-import React from "react";
-import { useRenderData } from "../../hooks/index.js";
-import {
- Cell as LGCell,
- HeaderCell as LGHeaderCell,
- HeaderRow,
- Row as LGRow,
- Table,
- TableBody,
- TableHead,
-} from "@leafygreen-ui/table";
-import { tableStyles } from "./ListDatabases.styles.js";
+import { type ReactElement } from "react";
+import { useDarkMode, useRenderData } from "../../hooks/index.js";
+import { Cell, HeaderCell, HeaderRow, Row, Table, TableBody, TableHead } from "@leafygreen-ui/table";
+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>;
+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 +22,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 168af5dd2..8d2147377 100644
--- a/src/ui/hooks/index.ts
+++ b/src/ui/hooks/index.ts
@@ -1,2 +1,3 @@
+export { useDarkMode } from "./useDarkMode.js";
export { useRenderData } from "./useRenderData.js";
export { useHostCommunication } from "./useHostCommunication.js";
diff --git a/src/ui/hooks/useDarkMode.ts b/src/ui/hooks/useDarkMode.ts
new file mode 100644
index 000000000..5afc8c5c1
--- /dev/null
+++ b/src/ui/hooks/useDarkMode.ts
@@ -0,0 +1,16 @@
+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;
+}
+
+export function useDarkMode(override?: boolean): boolean {
+ const prefersDarkMode = useSyncExternalStore(subscribeToPrefersColorScheme, getPrefersDarkMode);
+ 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;
+}