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; +}