Migrate to Mantine v8 and Design Changes (#961)

* mantine v8 migration

* various design changes and improvements
This commit is contained in:
Jeff
2025-06-24 00:04:36 -07:00
committed by GitHub
parent bea55d48a8
commit c1330d92b2
473 changed files with 12469 additions and 11607 deletions
@@ -0,0 +1,98 @@
.container {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 2rem);
overflow: hidden;
pointer-events: auto;
cursor: pointer;
border-radius: var(--theme-radius-md);
&:hover {
background: var(--theme-colors-surface-hover);
}
}
.container.is-hidden {
opacity: 0;
}
.inner-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 1rem;
overflow: hidden;
background: lighten(var(--theme-colors-surface), 3%);
.card-controls {
opacity: 0;
}
&:hover .card-controls {
opacity: 1;
}
&:hover * {
&::before {
opacity: 0.5;
}
}
}
.image-container {
position: relative;
display: flex;
align-items: center;
height: 100%;
aspect-ratio: 1/1;
overflow: hidden;
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
user-select: none;
content: '';
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
}
.image-container.is-favorite {
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
}
.image {
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
border-radius: var(--theme-radius-md);
img {
height: 100%;
object-fit: var(--theme-image-fit);
}
}
.detail-container {
margin-top: 0.5rem;
}
@@ -1,13 +1,15 @@
import { Center, Stack } from '@mantine/core';
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
import clsx from 'clsx';
import { useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img';
import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components';
import { CardRows } from '/@/renderer/components/card';
import { Skeleton } from '/@/renderer/components/skeleton';
import styles from './default-card.module.css';
import { CardRows } from '/@/renderer/components/card/card-rows';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack';
import {
Album,
AlbumArtist,
@@ -39,105 +41,6 @@ interface BaseGridCardProps {
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
}
const DefaultCardContainer = styled.div<{ $isHidden?: boolean; $itemGap: number }>`
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 2rem);
margin: ${({ $itemGap }) => $itemGap}px;
overflow: hidden;
pointer-events: auto;
cursor: pointer;
background: var(--card-default-bg);
border-radius: var(--card-default-radius);
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
&:hover {
background: var(--card-default-bg-hover);
}
`;
const InnerCardContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 1rem;
overflow: hidden;
.card-controls {
opacity: 0;
}
&:hover .card-controls {
opacity: 1;
}
&:hover * {
&::before {
opacity: 0.5;
}
}
`;
const ImageContainer = styled.div<{ $isFavorite?: boolean }>`
position: relative;
display: flex;
align-items: center;
height: 100%;
aspect-ratio: 1/1;
overflow: hidden;
background: var(--placeholder-bg);
border-radius: var(--card-default-radius);
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
content: '';
user-select: none;
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
${(props) =>
props.$isFavorite &&
`
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
background-color: var(--primary-color);
box-shadow: 0 0 10px 8px rgba(0, 0, 0, 80%);
transform: rotate(-45deg);
content: '';
pointer-events: none;
}
`}
`;
const Image = styled(SimpleImg)`
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
img {
height: 100%;
object-fit: var(--image-fit);
}
`;
const DetailContainer = styled.div`
margin-top: 0.5rem;
`;
export const DefaultCard = ({
columnIndex,
controls,
@@ -147,6 +50,8 @@ export const DefaultCard = ({
}: BaseGridCardProps) => {
const navigate = useNavigate();
const [isHovered, setIsHovered] = useState(false);
if (data) {
const path = generatePath(
controls.route.route as string,
@@ -158,101 +63,68 @@ export const DefaultCard = ({
}, {}),
);
let Placeholder = RiAlbumFill;
switch (controls.itemType) {
case LibraryItem.ALBUM:
Placeholder = RiAlbumFill;
break;
case LibraryItem.ALBUM_ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.PLAYLIST:
Placeholder = RiPlayListFill;
break;
default:
Placeholder = RiAlbumFill;
break;
}
return (
<DefaultCardContainer
$itemGap={controls.itemGap}
<div
className={clsx(styles.container, isHidden && styles.isHidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
onClick={() => navigate(path)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
margin: controls.itemGap,
}}
>
<InnerCardContainer>
<ImageContainer $isFavorite={data?.userFavorite}>
{data?.imageUrl ? (
<Image
importance="auto"
placeholder={data?.imagePlaceholderUrl || 'var(--placeholder-bg)'}
src={data?.imageUrl}
/>
) : (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)',
height: '100%',
width: '100%',
}}
>
<Placeholder
color="var(--placeholder-fg)"
size={35}
/>
</Center>
<div className={styles.innerContainer}>
<div
className={clsx(
styles.imageContainer,
data?.userFavorite && styles.isFavorite,
)}
>
<Image
className={styles.image}
src={data?.imageUrl}
/>
<GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd}
isHovered={isHovered}
itemData={data}
itemType={controls.itemType}
resetInfiniteLoaderCache={controls.resetInfiniteLoaderCache}
/>
</ImageContainer>
<DetailContainer>
</div>
<div className={styles.detailContainer}>
<CardRows
data={data}
rows={controls.cardRows}
/>
</DetailContainer>
</InnerCardContainer>
</DefaultCardContainer>
</div>
</div>
</div>
);
}
return (
<DefaultCardContainer
$isHidden={isHidden}
$itemGap={controls.itemGap}
<div
className={clsx(styles.container, isHidden && styles.isHidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
style={{
margin: controls.itemGap,
}}
>
<InnerCardContainer>
<ImageContainer>
<Skeleton
radius="sm"
visible
/>
</ImageContainer>
<DetailContainer>
<Stack spacing="sm">
<div className={styles.innerContainer}>
<div className={styles.imageContainer}>
<Skeleton className={styles.image} />
</div>
<div className={styles.detailContainer}>
<Stack gap="xs">
{(controls?.cardRows || []).map((row, index) => (
<Skeleton
height={14}
key={`${index}-${columnIndex}-${row.arrayProperty}`}
radius="sm"
visible
/>
<Skeleton key={`${index}-${columnIndex}-${row.arrayProperty}`} />
))}
</Stack>
</DetailContainer>
</InnerCardContainer>
</DefaultCardContainer>
</div>
</div>
</div>
);
};
@@ -0,0 +1,84 @@
.play-button {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
background-color: var(--theme-colors-white);
border: none;
border-radius: 50%;
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.1s ease-in-out;
&:hover {
background-color: var(--theme-colors-white);
opacity: 1;
}
&:active {
opacity: 1;
}
svg {
fill: var(--theme-colors-black);
stroke: var(--theme-colors-black);
}
}
.secondary-button {
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.2s linear;
&:hover {
opacity: 1;
}
&:active {
opacity: 1;
}
}
.grid-card-controls-container {
position: absolute;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.favorite-banner {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
.favorite-wrapper {
svg {
fill: var(--theme-colors-primary-filled);
}
}
.bottom-controls {
position: absolute;
bottom: 0;
display: flex;
gap: var(--theme-spacing-md);
align-items: flex-end;
justify-content: flex-end;
width: 100%;
height: calc(100% / 3);
padding: 1rem 0.5rem;
}
@@ -1,10 +1,8 @@
import type { UnstyledButtonProps } from '@mantine/core';
import clsx from 'clsx';
import { MouseEvent, useState } from 'react';
import React, { MouseEvent, useState } from 'react';
import { RiHeartFill, RiHeartLine, RiMoreFill, RiPlayFill } from 'react-icons/ri';
import styled from 'styled-components';
import styles from './grid-card-controls.module.css';
import { _Button } from '/@/renderer/components/button';
import {
ALBUM_CONTEXT_MENU_ITEMS,
ARTIST_CONTEXT_MENU_ITEMS,
@@ -12,105 +10,16 @@ import {
} from '/@/renderer/features/context-menu/context-menu-items';
import { useHandleGridContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Button } from '/@/shared/components/button/button';
import { Icon } from '/@/shared/components/icon/icon';
import { LibraryItem } from '/@/shared/types/domain-types';
import { Play, PlayQueueAddOptions } from '/@/shared/types/types';
type PlayButtonType = React.ComponentPropsWithoutRef<'button'> & UnstyledButtonProps;
const PlayButton = styled.button<PlayButtonType>`
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
background-color: rgb(255 255 255);
border: none;
border-radius: 50%;
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.1s ease-in-out;
&:hover {
opacity: 1;
scale: 1.1;
}
&:active {
opacity: 1;
scale: 1;
}
svg {
fill: rgb(0 0 0);
stroke: rgb(0 0 0);
}
`;
const SecondaryButton = styled(_Button)`
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.2s linear;
&:hover {
opacity: 1;
scale: 1.1;
}
&:active {
opacity: 1;
scale: 1;
}
`;
const GridCardControlsContainer = styled.div<{ $isFavorite?: boolean }>`
position: absolute;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`;
const FavoriteBanner = styled.div`
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--primary-color);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
`;
const ControlsRow = styled.div`
width: 100%;
height: calc(100% / 3);
`;
const BottomControls = styled(ControlsRow)`
position: absolute;
bottom: 0;
display: flex;
gap: 0.5rem;
align-items: flex-end;
justify-content: flex-end;
padding: 1rem 0.5rem;
`;
const FavoriteWrapper = styled.span<{ isFavorite: boolean }>`
svg {
fill: ${(props) => props.isFavorite && 'var(--primary-color)'};
}
`;
export const GridCardControls = ({
handleFavorite,
handlePlayQueueAdd,
isHovered,
itemData,
itemType,
resetInfiniteLoaderCache,
@@ -122,6 +31,7 @@ export const GridCardControls = ({
serverId: string;
}) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
isHovered?: boolean;
itemData: any;
itemType: LibraryItem;
resetInfiniteLoaderCache?: () => void;
@@ -168,50 +78,46 @@ export const GridCardControls = ({
return (
<>
{isFavorite ? <FavoriteBanner /> : null}
<GridCardControlsContainer
$isFavorite
className="card-controls"
>
<PlayButton onClick={handlePlay}>
<RiPlayFill size={25} />
</PlayButton>
<BottomControls>
{itemType !== LibraryItem.PLAYLIST && (
<SecondaryButton
onClick={(e) => handleFavorites(e, itemData?.serverId)}
p={5}
variant="subtle"
>
<FavoriteWrapper isFavorite={itemData?.isFavorite}>
{isFavorite ? (
<RiHeartFill size={20} />
) : (
<RiHeartLine
color="white"
size={20}
/>
)}
</FavoriteWrapper>
</SecondaryButton>
)}
<SecondaryButton
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleContextMenu(e, [itemData]);
}}
p={5}
variant="subtle"
{isFavorite ? <div className={styles.favoriteBanner} /> : null}
{isHovered && (
<div className={clsx(styles.gridCardControlsContainer)}>
<Button
classNames={{ root: styles.playButton }}
onClick={handlePlay}
variant="filled"
>
<RiMoreFill
color="white"
size={20}
<Icon
icon="mediaPlay"
size="xl"
/>
</SecondaryButton>
</BottomControls>
</GridCardControlsContainer>
</Button>
<div className={styles.bottomControls}>
{itemType !== LibraryItem.PLAYLIST && (
<ActionIcon
classNames={{ root: styles.secondaryButton }}
icon={isFavorite ? 'favorite' : 'favorite'}
iconProps={{
fill: isFavorite ? 'primary' : undefined,
}}
onClick={(e) => handleFavorites(e, itemData?.serverId)}
size="sm"
variant="transparent"
/>
)}
<ActionIcon
classNames={{ root: styles.secondaryButton }}
icon="ellipsisHorizontal"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleContextMenu(e, [itemData]);
}}
size="sm"
variant="transparent"
/>
</div>
</div>
)}
</>
);
};
@@ -0,0 +1,90 @@
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: auto;
&:global(.card-controls) {
opacity: 0;
}
}
.container.hidden {
opacity: 0;
}
.link-container {
cursor: pointer;
}
.image-container {
position: relative;
display: flex;
align-items: center;
aspect-ratio: 1/1;
overflow: hidden;
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
user-select: none;
content: '';
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
&:hover {
&::before {
opacity: 0.5;
}
}
&:hover .card-controls {
opacity: 1;
}
}
.image-container.is-favorite {
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
}
.image {
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
border-radius: var(--theme-radius-md);
img {
height: 100%;
object-fit: var(--theme-image-fit);
}
}
.detail-container {
margin-top: 0.5rem;
}
.placeholder-wrapper {
width: 100%;
height: 100%;
}
@@ -1,13 +1,15 @@
import { Center, Stack } from '@mantine/core';
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
import clsx from 'clsx';
import { useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img';
import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components';
import { CardRows } from '/@/renderer/components/card';
import { Skeleton } from '/@/renderer/components/skeleton';
import styles from './poster-card.module.css';
import { CardRows } from '/@/renderer/components/card/card-rows';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack';
import {
Album,
AlbumArtist,
@@ -39,93 +41,6 @@ interface BaseGridCardProps {
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
}
const PosterCardContainer = styled.div<{ $isHidden?: boolean; $itemGap: number }>`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin: ${({ $itemGap }) => $itemGap}px;
overflow: hidden;
pointer-events: auto;
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
.card-controls {
opacity: 0;
}
`;
const LinkContainer = styled.div`
cursor: pointer;
`;
const ImageContainer = styled.div<{ $isFavorite?: boolean }>`
position: relative;
display: flex;
align-items: center;
aspect-ratio: 1/1;
overflow: hidden;
background: var(--card-default-bg);
border-radius: var(--card-poster-radius);
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
content: '';
user-select: none;
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
${(props) =>
props.$isFavorite &&
`
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
background-color: var(--primary-color);
box-shadow: 0 0 10px 8px rgba(0, 0, 0, 80%);
transform: rotate(-45deg);
content: '';
pointer-events: none;
}
`}
&:hover {
&::before {
opacity: 0.5;
}
}
&:hover .card-controls {
opacity: 1;
}
`;
const Image = styled(SimpleImg)`
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
img {
height: 100%;
object-fit: var(--image-fit);
}
`;
const DetailContainer = styled.div`
margin-top: 0.5rem;
`;
export const PosterCard = ({
columnIndex,
controls,
@@ -135,6 +50,8 @@ export const PosterCard = ({
}: BaseGridCardProps) => {
const navigate = useNavigate();
const [isHovered, setIsHovered] = useState(false);
if (data) {
const path = generatePath(
controls.route.route as string,
@@ -146,97 +63,68 @@ export const PosterCard = ({
}, {}),
);
let Placeholder = RiAlbumFill;
switch (controls.itemType) {
case LibraryItem.ALBUM:
Placeholder = RiAlbumFill;
break;
case LibraryItem.ALBUM_ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.PLAYLIST:
Placeholder = RiPlayListFill;
break;
default:
Placeholder = RiAlbumFill;
break;
}
return (
<PosterCardContainer
$itemGap={controls.itemGap}
<div
className={styles.container}
key={`card-${columnIndex}-${listChildProps.index}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
margin: controls.itemGap,
}}
>
<LinkContainer onClick={() => navigate(path)}>
<ImageContainer $isFavorite={data?.userFavorite}>
{data?.imageUrl ? (
<Image
importance="auto"
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
src={data?.imageUrl}
/>
) : (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)',
height: '100%',
width: '100%',
}}
>
<Placeholder
color="var(--placeholder-fg)"
size={35}
/>
</Center>
)}
<div
className={styles.linkContainer}
onClick={() => navigate(path)}
>
<div
className={`${styles.imageContainer} ${data?.userFavorite ? styles.isFavorite : ''}`}
>
<Image
className={styles.image}
src={data?.imageUrl}
/>
<GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd}
isHovered={isHovered}
itemData={data}
itemType={controls.itemType}
resetInfiniteLoaderCache={controls.resetInfiniteLoaderCache}
/>
</ImageContainer>
</LinkContainer>
<DetailContainer>
</div>
</div>
<div className={styles.detailContainer}>
<CardRows
data={data}
rows={controls.cardRows}
/>
</DetailContainer>
</PosterCardContainer>
</div>
</div>
);
}
return (
<PosterCardContainer
$isHidden={isHidden}
$itemGap={controls.itemGap}
<div
className={clsx(styles.container, isHidden && styles.hidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
style={{
margin: controls.itemGap,
}}
>
<Skeleton
radius="sm"
visible
>
<ImageContainer />
</Skeleton>
<DetailContainer>
<Stack spacing="sm">
<div className={styles.imageContainer}>
<Skeleton className={styles.image} />
</div>
<div className={styles.detailContainer}>
<Stack gap="xs">
{(controls?.cardRows || []).map((row, index) => (
<Skeleton
height={14}
className={styles.row}
key={`${index}-${columnIndex}-${row.arrayProperty}`}
radius="sm"
visible
/>
))}
</Stack>
</DetailContainer>
</PosterCardContainer>
</div>
</div>
);
};
@@ -0,0 +1,9 @@
.virtual-grid-container {
display: flex;
flex-direction: column;
height: 100%;
}
.virtual-grid-auto-sizer-container {
flex: 1;
}
@@ -10,7 +10,8 @@ import type { FixedSizeListProps } from 'react-window';
import debounce from 'lodash/debounce';
import memoize from 'memoize-one';
import { FixedSizeList } from 'react-window';
import styled from 'styled-components';
import styles from './virtual-grid-wrapper.module.css';
import { GridCard } from '/@/renderer/components/virtual-grid/grid-card';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/shared/types/domain-types';
@@ -128,12 +129,14 @@ export const VirtualGridWrapper = ({
);
};
export const VirtualGridContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
interface VirtualGridContainerProps {
children: React.ReactNode;
}
export const VirtualGridAutoSizerContainer = styled.div`
flex: 1;
`;
export const VirtualGridContainer = ({ children }: VirtualGridContainerProps) => {
return <div className={styles.virtualGridContainer}>{children}</div>;
};
export const VirtualGridAutoSizerContainer = ({ children }: VirtualGridContainerProps) => {
return <div className={styles.virtualGridAutoSizerContainer}>{children}</div>;
};