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