diff --git a/packages/compass-components/src/components/leafygreen.tsx b/packages/compass-components/src/components/leafygreen.tsx index 96f8df17870..7cbf9d75001 100644 --- a/packages/compass-components/src/components/leafygreen.tsx +++ b/packages/compass-components/src/components/leafygreen.tsx @@ -53,8 +53,11 @@ import { TableBody, flexRender, useLeafyGreenTable, + useLeafyGreenVirtualTable, + type LeafyGreenVirtualItem, getExpandedRowModel, getFilteredRowModel, + type TableProps, } from '@leafygreen-ui/table'; import type { Row as LgTableRowType } from '@tanstack/table-core'; // TODO(COMPASS-8437): import from LG @@ -197,6 +200,9 @@ export { InfoSprinkle, flexRender, useLeafyGreenTable, + useLeafyGreenVirtualTable, + type LeafyGreenVirtualItem, + type TableProps, getExpandedRowModel, getFilteredRowModel, type LgTableRowType, diff --git a/packages/databases-collections-list/src/collections.tsx b/packages/databases-collections-list/src/collections.tsx index 5c8e0e199fa..8e7ccdd7378 100644 --- a/packages/databases-collections-list/src/collections.tsx +++ b/packages/databases-collections-list/src/collections.tsx @@ -1,28 +1,102 @@ -import React from 'react'; -import { css, spacing } from '@mongodb-js/compass-components'; +import React, { useCallback } from 'react'; +import { + Badge, + type BadgeVariant, + cx, + css, + type GlyphName, + Icon, + spacing, + type LGColumnDef, + Tooltip, + palette, + useDarkMode, +} from '@mongodb-js/compass-components'; import { compactBytes, compactNumber } from './format'; -import type { BadgeProp } from './namespace-card'; -import { NamespaceItemCard } from './namespace-card'; -import { ItemsGrid } from './items-grid'; +import { ItemsTable } from './items-table'; import type { CollectionProps } from 'mongodb-collection-model'; import { usePreference } from 'compass-preferences-model/provider'; -const COLLECTION_CARD_WIDTH = spacing[1600] * 4; +type BadgeProp = { + id: string; + name: string; + variant?: BadgeVariant; + icon?: GlyphName; + hint?: React.ReactNode; +}; + +const collectionBadgesStyles = css({ + display: 'flex', + gap: spacing[200], + // Preserving space for when cards with and without badges are mixed in a + // single row + minHeight: 20, +}); + +const CollectionBadges: React.FunctionComponent = ({ children }) => { + return
{children}
; +}; + +const collectionBadgeStyles = css({ + gap: spacing[100], +}); -const COLLECTION_CARD_HEIGHT = 238; -const COLLECTION_CARD_WITHOUT_STATS_HEIGHT = COLLECTION_CARD_HEIGHT - 150; +const viewOnStyles = css({ + fontWeight: 'bold', +}); + +const viewOnLightStyles = css({ + color: palette.white, +}); -const COLLECTION_CARD_LIST_HEIGHT = 118; -const COLLECTION_CARD_LIST_WITHOUT_STATS_HEIGHT = - COLLECTION_CARD_LIST_HEIGHT - 50; +const viewOnDarkStyles = css({ + color: palette.black, +}); -function collectionPropertyToBadge({ +const CollectionBadge: React.FunctionComponent = ({ id, - options, -}: { - id: string; - options?: Record; -}): BadgeProp { + name, + icon, + variant, + hint, +}) => { + const badge = useCallback( + ({ className, children, ...props } = {}) => { + return ( + + {icon && } + {name} + {/* Tooltip will be rendered here */} + {children} + + ); + }, + [id, icon, name, variant] + ); + + if (hint) { + return {hint}; + } + + return badge(); +}; + +function collectionPropertyToBadge( + collection: CollectionProps, + darkMode: boolean | undefined, + { + id, + options, + }: { + id: string; + options?: Record; + } +): BadgeProp { switch (id) { case 'collation': return { @@ -43,7 +117,25 @@ function collectionPropertyToBadge({ ), }; case 'view': - return { id, name: id, variant: 'darkgray', icon: 'Visibility' }; + return { + id, + name: id, + variant: 'darkgray', + icon: 'Visibility', + hint: ( + <> + Derived from{' '} + + {collection.view_on} + + + ), + }; case 'capped': return { id, name: id, variant: 'darkgray' }; case 'timeseries': @@ -62,18 +154,118 @@ function collectionPropertyToBadge({ } } -const pageContainerStyles = css({ - height: 'auto', - width: '100%', +const collectionNameStyles = css({ display: 'flex', - flexDirection: 'column', + gap: spacing[100], + flexWrap: 'wrap', + alignItems: 'anchor-center', + wordBreak: 'break-word', }); +function collectionColumns( + darkMode: boolean | undefined, + enableDbAndCollStats: boolean +): LGColumnDef[] { + return [ + { + accessorKey: 'name', + header: 'Collection name', + enableSorting: true, + size: 300, + cell: (info) => { + const name = info.getValue() as string; + + const badges = info.row.original.properties + .filter((prop) => prop.id !== 'read-only') + .map((prop) => { + return collectionPropertyToBadge(info.row.original, darkMode, prop); + }); + + return ( +
+ {name} + + {badges.map((badge) => { + return ( + + ); + })} + +
+ ); + }, + }, + { + accessorKey: 'calculated_storage_size', + header: 'Storage size', + enableSorting: true, + cell: (info) => { + const type = info.row.original.type as string; + if (type === 'view') { + return '-'; + } + const size = info.getValue() as number | undefined; + return enableDbAndCollStats && size !== undefined + ? compactBytes(size) + : '-'; + }, + }, + { + accessorKey: 'avg_document_size', + header: 'Avg. document size', + enableSorting: true, + cell: (info) => { + const type = info.row.original.type as string; + if (type === 'view' || type === 'timeseries') { + return '-'; + } + + const size = info.getValue() as number | undefined; + return enableDbAndCollStats && size !== undefined + ? compactBytes(size) + : '-'; + }, + }, + { + accessorKey: 'Indexes', + header: 'Indexes', + enableSorting: true, + cell: (info) => { + const type = info.row.original.type as string; + if (type === 'view' || type === 'timeseries') { + return '-'; + } + + const index_count = info.getValue() as number | undefined; + return enableDbAndCollStats && index_count !== undefined + ? compactNumber(index_count) + : '-'; + }, + }, + { + accessorKey: 'index_size', + header: 'Total index size', + enableSorting: true, + cell: (info) => { + const type = info.row.original.type as string; + if (type === 'view' || type === 'timeseries') { + return '-'; + } + + const size = info.getValue() as number | undefined; + return enableDbAndCollStats && size !== undefined + ? compactBytes(size) + : '-'; + }, + }, + ]; +} + +// TODO: we removed delete click functionality, we removed the header hint functionality const CollectionsList: React.FunctionComponent<{ namespace: string; collections: CollectionProps[]; onCollectionClick: (id: string) => void; - onDeleteCollectionClick?: (id: string) => void; onCreateCollectionClick?: () => void; onRefreshClick?: () => void; }> = ({ @@ -81,128 +273,24 @@ const CollectionsList: React.FunctionComponent<{ collections, onCollectionClick, onCreateCollectionClick, - onDeleteCollectionClick, onRefreshClick, }) => { const enableDbAndCollStats = usePreference('enableDbAndCollStats'); + const darkMode = useDarkMode(); + const columns = React.useMemo( + () => collectionColumns(darkMode, enableDbAndCollStats), + [darkMode, enableDbAndCollStats] + ); return ( -
- { - const data = - coll.type === 'view' - ? [{ label: 'View on', value: coll.source?.name }] - : coll.type === 'timeseries' - ? [ - { - label: 'Storage size', - value: - coll.calculated_storage_size !== undefined - ? compactBytes(coll.calculated_storage_size) - : 'N/A', - hint: - coll.document_size !== undefined && - `Uncompressed data size: ${compactBytes( - coll.document_size - )}`, - }, - ] - : [ - { - label: 'Storage size', - value: - coll.calculated_storage_size !== undefined - ? compactBytes(coll.calculated_storage_size) - : 'N/A', - hint: - coll.document_size !== undefined && - `Uncompressed data size: ${compactBytes( - coll.document_size - )}`, - }, - { - label: 'Documents', - value: - coll.document_count !== undefined - ? compactNumber(coll.document_count) - : 'N/A', - }, - { - label: 'Avg. document size', - value: - coll.avg_document_size !== undefined - ? compactBytes(coll.avg_document_size) - : 'N/A', - }, - { - label: 'Indexes', - value: - coll.index_count !== undefined - ? compactNumber(coll.index_count) - : 'N/A', - }, - { - label: 'Total index size', - value: - coll.index_size !== undefined - ? compactBytes(coll.index_size) - : 'N/A', - }, - ]; - - const badges = coll.properties.map((prop) => { - return collectionPropertyToBadge(prop); - }); - - return ( - - ); - }} - > -
+ ); }; diff --git a/packages/databases-collections-list/src/databases.tsx b/packages/databases-collections-list/src/databases.tsx index 8e9bad3cbdb..69df736f198 100644 --- a/packages/databases-collections-list/src/databases.tsx +++ b/packages/databases-collections-list/src/databases.tsx @@ -1,24 +1,70 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { PerformanceSignals, spacing } from '@mongodb-js/compass-components'; +// TODO: don't forget about performance insights? +//import { PerformanceSignals, spacing } from '@mongodb-js/compass-components'; import { compactBytes, compactNumber } from './format'; -import { NamespaceItemCard } from './namespace-card'; -import { ItemsGrid } from './items-grid'; +import { ItemsTable } from './items-table'; import type { DatabaseProps } from 'mongodb-database-model'; import { usePreference } from 'compass-preferences-model/provider'; +import { css, type LGColumnDef } from '@mongodb-js/compass-components'; -const DATABASE_CARD_WIDTH = spacing[1600] * 4; +const databaseNameStyles = css({ + wordBreak: 'break-word', +}); -const DATABASE_CARD_HEIGHT = 154; -const DATABASE_CARD_WITHOUT_STATS_HEIGHT = DATABASE_CARD_HEIGHT - 85; - -const DATABASE_CARD_LIST_HEIGHT = 118; -const DATABASE_CARD_LIST_WITHOUT_STATS_HEIGHT = DATABASE_CARD_LIST_HEIGHT - 50; +function databaseColumns( + enableDbAndCollStats: boolean +): LGColumnDef[] { + return [ + { + accessorKey: 'name', + header: 'Database name', + enableSorting: true, + cell: (info) => { + const name = info.getValue() as string; + return {name}; + }, + }, + { + accessorKey: 'calculated_storage_size', + header: 'Storage size', + enableSorting: true, + cell: (info) => { + // TODO: shouldn't this just have the right type rather than unknown? + const size = info.getValue() as number | undefined; + return enableDbAndCollStats && size !== undefined + ? compactBytes(size) + : '-'; + }, + }, + { + accessorKey: 'collectionsLength', + header: 'Collections', + enableSorting: true, + cell: (info) => { + return enableDbAndCollStats + ? compactNumber(info.getValue() as number) + : '-'; + }, + }, + { + accessorKey: 'index_count', + header: 'Indexes', + enableSorting: true, + cell: (info) => { + const index_count = info.getValue() as number | undefined; + return enableDbAndCollStats && index_count !== undefined + ? compactNumber(index_count) + : '-'; + }, + }, + ]; +} +// TODO: we removed delete click functionality, we removed the header hint functionality const DatabasesList: React.FunctionComponent<{ databases: DatabaseProps[]; onDatabaseClick: (id: string) => void; - onDeleteDatabaseClick?: (id: string) => void; onCreateDatabaseClick?: () => void; onRefreshClick?: () => void; renderLoadSampleDataBanner?: () => React.ReactNode; @@ -26,90 +72,24 @@ const DatabasesList: React.FunctionComponent<{ databases, onDatabaseClick, onCreateDatabaseClick, - onDeleteDatabaseClick, onRefreshClick, renderLoadSampleDataBanner, }) => { const enableDbAndCollStats = usePreference('enableDbAndCollStats'); + const columns = React.useMemo( + () => databaseColumns(enableDbAndCollStats), + [enableDbAndCollStats] + ); return ( - { - return ( - = 10_000 - ? PerformanceSignals.get('too-many-collections') - : undefined, - }, - { - label: 'Indexes', - value: - enableDbAndCollStats && db.index_count !== undefined - ? compactNumber(db.index_count) - : 'N/A', - }, - ]} - onItemClick={onItemClick} - onItemDeleteClick={onDeleteItemClick} - {...props} - > - ); - }} renderLoadSampleDataBanner={renderLoadSampleDataBanner} - > + > ); }; diff --git a/packages/databases-collections-list/src/items-grid.tsx b/packages/databases-collections-list/src/items-table.tsx similarity index 58% rename from packages/databases-collections-list/src/items-grid.tsx rename to packages/databases-collections-list/src/items-table.tsx index fa1300cfed1..62d0263895d 100644 --- a/packages/databases-collections-list/src/items-grid.tsx +++ b/packages/databases-collections-list/src/items-table.tsx @@ -1,92 +1,48 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { Fragment, useMemo } from 'react'; import { css, cx, spacing, - VirtualGrid, - useSortControls, - useSortedItems, WorkspaceContainer, Button, Icon, Breadcrumbs, + Table, + TableHead, + TableBody, + useLeafyGreenVirtualTable, + type LGColumnDef, + type HeaderGroup, + HeaderRow, + HeaderCell, + flexRender, + ExpandedContent, + Row, + Cell, + //type LeafyGreenTableRow, + type LeafyGreenVirtualItem, } from '@mongodb-js/compass-components'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; -import type { NamespaceItemCardProps } from './namespace-card'; -import { useViewTypeControls } from './use-view-type'; -import type { ViewType } from './use-view-type'; import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import toNS from 'mongodb-ns'; import { getConnectionTitle } from '@mongodb-js/connection-info'; import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; -import { useConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import { usePreferences } from 'compass-preferences-model/provider'; type Item = { _id: string } & Record; -const rowStyles = css({ - paddingLeft: spacing[400], - paddingRight: spacing[400], - paddingBottom: spacing[100], - paddingTop: spacing[100], - columnGap: spacing[200], -}); - -const containerStyles = css({ - width: '100%', - height: '100%', - overflow: 'hidden', - display: 'grid', - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '100%', - // This element is focusable only to handle virtual list and will immediately - // pass focus to its children. This can take a frame though so to avoid - // outline on the container showing up, we are completely disabling it - outline: 'none', -}); - -const gridStyles = { - container: containerStyles, - row: rowStyles, -}; - export const createButtonStyles = css({ whiteSpace: 'nowrap', }); -type CallbackProps = { - onItemClick: (id: string) => void; - onCreateItemClick?: () => void; - onDeleteItemClick?: (id: string) => void; -}; - -interface RenderItem { - ( - props: { - item: T; - viewType: ViewType; - } & Omit & - Omit< - React.HTMLProps, - Extract - > - ): React.ReactElement; -} - type ItemsGridProps = { namespace?: string; itemType: 'collection' | 'database'; - itemGridWidth: number; - itemGridHeight: number; - itemListWidth?: number; - itemListHeight?: number; + columns: LGColumnDef[]; items: T[]; - sortBy?: { name: Extract; label: string }[]; onItemClick: (id: string) => void; - onDeleteItemClick?: (id: string) => void; onCreateItemClick?: () => void; onRefreshClick?: () => void; - renderItem: RenderItem; renderLoadSampleDataBanner?: () => React.ReactNode; }; @@ -141,19 +97,15 @@ function buildChartsUrl( return url.toString(); } -const GridControls: React.FunctionComponent<{ +const TableControls: React.FunctionComponent<{ namespace?: string; itemType: string; - sortControls?: React.ReactNode; - viewTypeControls?: React.ReactNode; onCreateItemClick?: () => void; onRefreshClick?: () => void; renderLoadSampleDataBanner?: () => React.ReactNode; }> = ({ namespace, itemType, - sortControls, - viewTypeControls, onCreateItemClick, onRefreshClick, renderLoadSampleDataBanner, @@ -274,14 +226,6 @@ const GridControls: React.FunctionComponent<{ )} - {sortControls && viewTypeControls && ( -
-
{sortControls}
-
- {viewTypeControls} -
-
- )} {banner &&
{banner}
} ); @@ -292,95 +236,106 @@ const itemsGridContainerStyles = css({ height: '100%', }); -export const ItemsGrid = ({ +const virtualScrollingContainerHeight = css({ + height: 'calc(100vh - 100px)', + padding: `0 ${spacing[400]}px`, +}); + +export const ItemsTable = ({ namespace, itemType, - itemGridWidth, - itemGridHeight, - itemListWidth = itemGridWidth, - itemListHeight = itemGridHeight, + columns, items, - sortBy = [], onItemClick, - onDeleteItemClick, onCreateItemClick, onRefreshClick, - renderItem: _renderItem, renderLoadSampleDataBanner, }: ItemsGridProps): React.ReactElement => { - const track = useTelemetry(); - const connectionInfoRef = useConnectionInfoRef(); - const onViewTypeChange = useCallback( - (newType: ViewType) => { - track( - 'Switch View Type', - { view_type: newType, item_type: itemType }, - connectionInfoRef.current - ); + const tableContainerRef = React.useRef(null); + + const table = useLeafyGreenVirtualTable({ + containerRef: tableContainerRef, + data: items, + columns, + virtualizerOptions: { + estimateSize: () => 50, + overscan: 10, }, - [itemType, track, connectionInfoRef] - ); - - const [sortControls, sortState] = useSortControls(sortBy); - const [viewTypeControls, viewType] = useViewTypeControls({ - onChange: onViewTypeChange, }); - const sortedItems = useSortedItems(items, sortState); - - const itemWidth = viewType === 'grid' ? itemGridWidth : itemListWidth; - const itemHeight = viewType === 'grid' ? itemGridHeight : itemListHeight; - - const shouldShowControls = items.length > 0; - - const renderItem: React.ComponentProps['renderItem'] = - useCallback( - ({ index, ...props }) => { - const item = sortedItems[index]; - return _renderItem({ - item, - viewType, - onItemClick, - onDeleteItemClick, - ...props, - }); - }, - [_renderItem, onDeleteItemClick, onItemClick, sortedItems, viewType] - ); return (
+ > } > - {(scrollTriggerRef) => { - return ( - { - return
; - }} - headerHeight={0} - itemKey={(index: number) => sortedItems[index]._id} - classNames={gridStyles} - resetActiveItemOnBlur={false} - data-testid={`${itemType}-grid`} - >
- ); - }} + + + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.virtual.getVirtualItems() && + table.virtual + .getVirtualItems() + .map((virtualRow: LeafyGreenVirtualItem) => { + const row = virtualRow.row; + const isExpandedContent = row.isExpandedContent ?? false; + + return ( + + {!isExpandedContent && ( + // row is required + + onItemClick(row.original._id as string) + } + > + {row.getVisibleCells().map((cell: any) => { + return ( + // cell is required + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ); + })} + + )} + {isExpandedContent && } + + ); + })} + +
); diff --git a/packages/databases-collections-list/src/namespace-card.tsx b/packages/databases-collections-list/src/namespace-card.tsx deleted file mode 100644 index f2be5888adc..00000000000 --- a/packages/databases-collections-list/src/namespace-card.tsx +++ /dev/null @@ -1,359 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { useCallback, useMemo } from 'react'; -import { - Card, - css, - Icon, - spacing, - Subtitle, - useHoverState, - Badge, - Tooltip, - cx, - useFocusState, - FocusState, - palette, - mergeProps, - useDefaultAction, - ItemActionControls, - useDarkMode, -} from '@mongodb-js/compass-components'; -import type { - BadgeVariant, - GlyphName, - ItemAction, - SignalPopover, -} from '@mongodb-js/compass-components'; -import { NamespaceParam } from './namespace-param'; -import type { ViewType } from './use-view-type'; -import { usePreferences } from 'compass-preferences-model/provider'; - -const cardTitleGroup = css({ - display: 'flex', - alignItems: 'center', - gap: spacing[400], -}); - -const CardTitleGroup: React.FunctionComponent = ({ children }) => { - return
{children}
; -}; - -const inferredFromPrivilegesLightStyles = css({ - color: palette.gray.dark1, -}); - -const inferredFromPrivilegesDarkStyles = css({ - color: palette.gray.base, -}); - -const inactiveCardStyles = css({ - borderStyle: 'dashed', - borderWidth: spacing[50], - '&:hover': { - borderStyle: 'dashed', - borderWidth: spacing[50], - }, -}); - -const tooltipTriggerStyles = css({ - display: 'flex', -}); - -const cardNameWrapper = css({ - // Workaround for uncollapsible text in flex children - minWidth: 0, -}); - -const cardNameDark = css({ - color: palette.green.light2, -}); - -const cardNameLight = css({ - color: palette.green.dark2, -}); - -const cardName = css({ - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - // To make container 28px to match leafygreen buttons - paddingTop: 2, - paddingBottom: 2, - // TS is very confused if fontWeight is not a number even though it's a valid - // CSS value - fontWeight: '600 !important' as unknown as number, -}); - -const CardName: React.FunctionComponent<{ - children: string; - inferredFromPrivileges: boolean; -}> = ({ children, inferredFromPrivileges }) => { - const darkMode = useDarkMode(); - return ( -
- - {children} - -
- ); -}; - -const cardActionContainer = css({ - marginLeft: 'auto', - flex: 'none', -}); - -const cardBadges = css({ - display: 'flex', - gap: spacing[200], - // Preserving space for when cards with and without badges are mixed in a - // single row - minHeight: 20, -}); - -const CardBadges: React.FunctionComponent = ({ children }) => { - return
{children}
; -}; - -const cardBadge = css({ - gap: spacing[100], -}); - -const cardBadgeLabel = css({}); - -export type BadgeProp = { - id: string; - name: string; - variant?: BadgeVariant; - icon?: GlyphName; - hint?: React.ReactNode; -}; - -const CardBadge: React.FunctionComponent = ({ - id, - name, - icon, - variant, - hint, -}) => { - const badge = useCallback( - ({ className, children, ...props } = {}) => { - return ( - - {icon && } - {name} - {/* Tooltip will be rendered here */} - {children} - - ); - }, - [id, icon, name, variant] - ); - - if (hint) { - return {hint}; - } - - return badge(); -}; - -const card = css({ - padding: spacing[400], -}); - -export type DataProp = { - label: React.ReactNode; - value: React.ReactNode; - hint?: React.ReactNode; - insights?: React.ComponentProps['signals']; -}; - -export type NamespaceItemCardProps = { - id: string; - type: 'database' | 'collection'; - viewType: ViewType; - name: string; - status: 'initial' | 'fetching' | 'refreshing' | 'ready' | 'error'; - data: DataProp[]; - badges?: BadgeProp[] | null; - inferredFromPrivileges: boolean; - onItemClick: (id: string) => void; - onItemDeleteClick?: (id: string) => void; -}; - -const namespaceDataGroup = css({ - display: 'flex', - gap: spacing[200], - marginTop: spacing[400], -}); - -const column = css({ - flexDirection: 'column', -}); - -type NamespaceAction = 'delete'; - -export const NamespaceItemCard: React.FunctionComponent< - NamespaceItemCardProps & - Omit< - React.HTMLProps, - Extract - > -> = ({ - id, - type, - name, - status, - data, - onItemClick, - onItemDeleteClick, - badges = null, - viewType, - inferredFromPrivileges, - ...props -}) => { - const { readOnly, enableDbAndCollStats } = usePreferences([ - 'readOnly', - 'enableDbAndCollStats', - ]); - const darkMode = useDarkMode(); - const [hoverProps, isHovered] = useHoverState(); - const [focusProps, focusState] = useFocusState(); - - const onDefaultAction = useCallback(() => { - onItemClick(id); - }, [onItemClick, id]); - - const hasDeleteHandler = !!onItemDeleteClick; - const cardActions: ItemAction[] = useMemo(() => { - return readOnly || !hasDeleteHandler || inferredFromPrivileges - ? [] - : [ - { - action: 'delete', - label: `Delete ${type}`, - icon: 'Trash', - }, - ]; - }, [type, readOnly, inferredFromPrivileges, hasDeleteHandler]); - - const defaultActionProps = useDefaultAction(onDefaultAction); - - const onAction = useCallback( - (action: NamespaceAction) => { - if (action === 'delete') { - onItemDeleteClick?.(id); - } - }, - [onItemDeleteClick, id] - ); - - const badgesGroup = badges && ( - - {badges.map((badge) => { - return ; - })} - - ); - - const cardProps = mergeProps( - { - className: cx( - card, - inferredFromPrivileges && [ - !darkMode && inferredFromPrivilegesLightStyles, - darkMode && inferredFromPrivilegesDarkStyles, - inactiveCardStyles, - ] - ), - }, - defaultActionProps, - hoverProps, - focusProps, - props - ); - - const isButtonVisible = - [FocusState.FocusVisible, FocusState.FocusWithinVisible].includes( - focusState - ) || isHovered; - - return ( - // @ts-expect-error the error here is caused by passing children to Card - // component, even though it's allowed on the implementation level the types - // are super confused and don't allow that - - - - {name} - - - {inferredFromPrivileges && ( - - - - } - > - Your privileges grant you access to this namespace, but it might not - currently exist - - )} - - {viewType === 'list' && badgesGroup} - - 0} - actions={cardActions} - onAction={onAction} - className={cardActionContainer} - > - - - {viewType === 'grid' && badgesGroup} - - {enableDbAndCollStats && ( -
- {data.map(({ label, value, hint, insights }, idx) => { - return ( - - ); - })} -
- )} -
- ); -}; diff --git a/packages/databases-collections-list/src/namespace-param.tsx b/packages/databases-collections-list/src/namespace-param.tsx deleted file mode 100644 index fbbeaf188f6..00000000000 --- a/packages/databases-collections-list/src/namespace-param.tsx +++ /dev/null @@ -1,175 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { useMemo } from 'react'; -import { - InlineDefinition, - spacing, - css, - cx, - ContentWithFallback, - Placeholder, - keyframes, - SignalPopover, -} from '@mongodb-js/compass-components'; -import type { ViewType } from './use-view-type'; -import { usePreference } from 'compass-preferences-model/provider'; - -const namespaceParam = css({ - display: 'flex', - gap: '1ch', - flex: 1, - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - minWidth: 0, - maxWidth: spacing[1600] * 4, -}); - -const multiline = css({ - display: 'flex', - flexDirection: 'column', - gap: 0, -}); - -const namespaceParamLabel = css({ - fontWeight: 'bold', -}); - -const namespaceParamValueContainer = css({ - position: 'relative', - width: '100%', - // Keeping container height for the placeholder to appear - minHeight: 20, -}); - -const namespaceParamValueContainerWithInsights = css({ - display: 'flex', - alignItems: 'center', - gap: spacing[200], -}); - -const namespaceParamValue = css({ - opacity: 1, - transition: 'opacity .16s linear', -}); - -const namespaceParamValueRefreshing = css({ - opacity: 0.3, -}); - -const namespaceParamValueMissing = css({ - opacity: 0.3, -}); - -const namespaceParamValuePlaceholder = css({ - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - right: 0, - display: 'flex', - opacity: 0, - transition: 'opacity .16s ease-out', -}); - -const visible = css({ - opacity: 1, - transitionTimingFunction: 'ease-in', -}); - -const fadeInAnimation = keyframes({ - from: { - opacity: 0, - }, - to: { - opacity: 1, - }, -}); - -const fadeIn = css({ - animation: `${fadeInAnimation} .16s ease-out`, -}); - -export const NamespaceParam: React.FunctionComponent<{ - label: React.ReactNode; - value: React.ReactNode; - status: 'initial' | 'fetching' | 'refreshing' | 'ready' | 'error'; - hint?: React.ReactNode; - viewType: ViewType; - insights?: React.ComponentProps['signals']; -}> = ({ label, value, status, hint, viewType, insights }) => { - const showInsights = usePreference('showInsights'); - - const renderedValue = useMemo(() => { - const isReady = status !== 'initial' && status !== 'fetching'; - return ( - { - if (!shouldRender) { - return null; - } - - // eslint-disable-next-line eqeqeq - const missingValue = value == null || status === 'error'; - - return ( - - {missingValue ? '—' : value} - - ); - }} - fallback={(shouldRender) => ( - - - - )} - > - ); - }, [value, status]); - - return ( -
- - {hint ? ( - - {label}: - - ) : ( - <>{label}: - )} - - - {renderedValue} - {showInsights && insights && ( - - )} - -
- ); -}; diff --git a/packages/databases-collections-list/src/use-view-type.tsx b/packages/databases-collections-list/src/use-view-type.tsx deleted file mode 100644 index 765e9c7e9af..00000000000 --- a/packages/databases-collections-list/src/use-view-type.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - SegmentedControl, - SegmentedControlOption, - Icon, - Label, - css, - spacing, - useId, -} from '@mongodb-js/compass-components'; - -export type ViewType = 'grid' | 'list'; - -const VIEW_TYPE_SETTINGS_KEY = 'compass_items_grid_view_type'; - -function getViewTypeSettingsFromSessionStorage( - defaultType: ViewType = 'grid' -): ViewType { - try { - return ( - (window.sessionStorage.getItem(VIEW_TYPE_SETTINGS_KEY) as ViewType) ?? - defaultType - ); - } catch { - return defaultType; - } -} - -function setViewTypeSettingsFromSessionStorage(val: ViewType) { - try { - window.sessionStorage.setItem(VIEW_TYPE_SETTINGS_KEY, val); - } catch { - // noop - } -} - -const controlsContainer = css({ - display: 'flex', - alignItems: 'center', - gap: spacing[200], -}); - -const label = css({ - // Because leafygreen - margin: '0 !important', - padding: '0 !important', -}); - -export function useViewTypeControls({ - defaultViewType = 'list', - onChange = () => { - // noop - }, -}: { - defaultViewType?: ViewType; - onChange?: (newType: ViewType) => void; -}): [React.ReactElement, ViewType] { - const [viewType, setViewType] = useState(() => - getViewTypeSettingsFromSessionStorage(defaultViewType) - ); - useEffect(() => { - setViewTypeSettingsFromSessionStorage(viewType); - }, [viewType]); - const onViewTypeChange = useCallback( - (val: ViewType) => { - onChange(val); - setViewType(val); - }, - [onChange] - ); - const labelId = useId(); - const controlId = useId(); - const viewControls = useMemo(() => { - return ( -
- - void} - > - } - /> - } - /> - -
- ); - }, [labelId, controlId, viewType, onViewTypeChange]); - return [viewControls, viewType]; -}