-
Notifications
You must be signed in to change notification settings - Fork 176
feat(mcp-ui): polish up ListDatabases UI and enable it to render as standalone component
#811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a7a8076
f149a5c
da3e14a
bf77dea
680863a
8a870fa
8a71318
792b50d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| `; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<React.ComponentPropsWithoutRef<"th">>; | ||
| const Cell = LGCell as React.FC<React.ComponentPropsWithoutRef<"td">>; | ||
| const Row = LGRow as React.FC<React.ComponentPropsWithoutRef<"tr">>; | ||
| const HeaderCell = LGHeaderCell as FC<ComponentPropsWithoutRef<"th">>; | ||
| const Cell = LGCell as FC<ComponentPropsWithoutRef<"td">>; | ||
| const Row = LGRow as FC<ComponentPropsWithoutRef<"tr">>; | ||
|
Comment on lines
+16
to
+18
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious why this is necessary? |
||
|
|
||
| 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<ListDatabasesOutput>(); | ||
| export const ListDatabases = ({ | ||
| databases: propDatabases, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when would this come in as a prop?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, if the client is rendering this directly, right? |
||
| darkMode: darkModeProp, | ||
| }: ListDatabasesProps): ReactElement | null => { | ||
| const darkMode = useDarkMode(darkModeProp); | ||
| const { data: hookData, isLoading, error } = useRenderData<ListDatabasesOutput>(); | ||
| const databases = propDatabases ?? hookData?.databases; | ||
|
|
||
| if (isLoading) { | ||
| return <div>Loading...</div>; | ||
| } | ||
| if (!propDatabases) { | ||
| if (isLoading) { | ||
| return <div>Loading...</div>; | ||
| } | ||
|
|
||
| if (error) { | ||
| return <div>Error: {error}</div>; | ||
| if (error) { | ||
| return <div>Error: {error}</div>; | ||
| } | ||
| } | ||
|
|
||
| if (!data) { | ||
| if (!databases) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <Table className={tableStyles}> | ||
| <TableHead> | ||
| <HeaderRow> | ||
| <HeaderCell>DB Name</HeaderCell> | ||
| <HeaderCell>DB Size</HeaderCell> | ||
| </HeaderRow> | ||
| </TableHead> | ||
| <TableBody> | ||
| {data.databases.map((db) => ( | ||
| <Row key={db.name}> | ||
| <Cell>{db.name}</Cell> | ||
| <Cell>{formatBytes(db.size)}</Cell> | ||
| </Row> | ||
| ))} | ||
| </TableBody> | ||
| </Table> | ||
| <div className={getContainerStyles(darkMode)}> | ||
| <Body className={AmountTextStyles} darkMode={darkMode}> | ||
| Your cluster has <strong>{databases.length} databases</strong>: | ||
| </Body> | ||
| <Table darkMode={darkMode}> | ||
| <TableHead> | ||
| <HeaderRow> | ||
| <HeaderCell>Database</HeaderCell> | ||
| <HeaderCell>Size</HeaderCell> | ||
| </HeaderRow> | ||
| </TableHead> | ||
| <TableBody> | ||
| {databases.map((db) => ( | ||
| <Row key={db.name}> | ||
| <Cell>{db.name}</Cell> | ||
| <Cell>{formatBytes(db.size)}</Cell> | ||
| </Row> | ||
| ))} | ||
| </TableBody> | ||
| </Table> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| export { useDarkMode } from "./useDarkMode.js"; | ||
| export { useRenderData } from "./useRenderData.js"; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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); | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we expect to define
Suggested change
|
||||||
| return override ?? prefersDarkMode; | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| export { UIRegistry } from "./registry/index.js"; | ||
| export { ListDatabases } from "./components/ListDatabases/index.js"; | ||
| export type { Database } from "./components/ListDatabases/ListDatabases.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth importing lg
Themeenum?