mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Update base components
This commit is contained in:
+8
-10
@@ -7,12 +7,10 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import ReactPlayer, { ReactPlayerProps } from 'react-player';
|
import ReactPlayer, { ReactPlayerProps } from 'react-player';
|
||||||
import {
|
import {
|
||||||
CrossfadeStyle,
|
crossfadeHandler,
|
||||||
PlaybackStyle,
|
gaplessHandler,
|
||||||
PlayerStatus,
|
} from '@/renderer/components/audio-player/utils/list-handlers';
|
||||||
Song,
|
import { CrossfadeStyle, PlaybackStyle, PlayerStatus, Song } from '@/types';
|
||||||
} from '../../../types';
|
|
||||||
import { crossfadeHandler, gaplessHandler } from './utils/listenHandlers';
|
|
||||||
|
|
||||||
interface AudioPlayerProps extends ReactPlayerProps {
|
interface AudioPlayerProps extends ReactPlayerProps {
|
||||||
crossfadeDuration: number;
|
crossfadeDuration: number;
|
||||||
@@ -125,13 +123,13 @@ export const AudioPlayer = forwardRef(
|
|||||||
return gaplessHandler({
|
return gaplessHandler({
|
||||||
currentTime: e.playedSeconds,
|
currentTime: e.playedSeconds,
|
||||||
duration: getDuration(player1Ref),
|
duration: getDuration(player1Ref),
|
||||||
isFlac: player1?.suffix === 'flac',
|
isFlac: player1?.container === 'flac',
|
||||||
isTransitioning,
|
isTransitioning,
|
||||||
nextPlayerRef: player2Ref,
|
nextPlayerRef: player2Ref,
|
||||||
setIsTransitioning,
|
setIsTransitioning,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isTransitioning, player1?.suffix]
|
[isTransitioning, player1?.container]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleGapless2 = useCallback(
|
const handleGapless2 = useCallback(
|
||||||
@@ -139,13 +137,13 @@ export const AudioPlayer = forwardRef(
|
|||||||
return gaplessHandler({
|
return gaplessHandler({
|
||||||
currentTime: e.playedSeconds,
|
currentTime: e.playedSeconds,
|
||||||
duration: getDuration(player2Ref),
|
duration: getDuration(player2Ref),
|
||||||
isFlac: player2?.suffix === 'flac',
|
isFlac: player2?.container === 'flac',
|
||||||
isTransitioning,
|
isTransitioning,
|
||||||
nextPlayerRef: player1Ref,
|
nextPlayerRef: player1Ref,
|
||||||
setIsTransitioning,
|
setIsTransitioning,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isTransitioning, player2?.suffix]
|
[isTransitioning, player2?.container]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { Button as MantineButton } from '@mantine/core';
|
|
||||||
|
|
||||||
interface ButtonProps {
|
|
||||||
icon?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Button = ({ icon }: ButtonProps) => {
|
|
||||||
return <MantineButton>Button</MantineButton>;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import React, { forwardRef, Ref } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import {
|
||||||
|
Button as MantineButton,
|
||||||
|
ButtonProps as MantineButtonProps,
|
||||||
|
createPolymorphicComponent,
|
||||||
|
TooltipProps,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { Tooltip } from '@/renderer/components/tooltip';
|
||||||
|
|
||||||
|
interface ButtonProps extends MantineButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
|
tooltip?: Omit<TooltipProps, 'children'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StyledButtonProps extends ButtonProps {
|
||||||
|
ref: Ref<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledButton = styled(MantineButton)<StyledButtonProps>`
|
||||||
|
color: ${(props) => {
|
||||||
|
switch (props.variant) {
|
||||||
|
case 'default':
|
||||||
|
return 'var(--btn-default-fg)';
|
||||||
|
case 'filled':
|
||||||
|
return 'var(--btn-primary-fg)';
|
||||||
|
case 'subtle':
|
||||||
|
return 'var(--btn-subtle-fg)';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
background-color: ${(props) => {
|
||||||
|
switch (props.variant) {
|
||||||
|
case 'default':
|
||||||
|
return 'var(--btn-default-bg)';
|
||||||
|
case 'filled':
|
||||||
|
return 'var(--btn-primary-bg)';
|
||||||
|
case 'subtle':
|
||||||
|
return 'var(--btn-subtle-bg)';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${(props) => {
|
||||||
|
switch (props.variant) {
|
||||||
|
case 'default':
|
||||||
|
return 'var(--btn-default-fg-hover)';
|
||||||
|
case 'filled':
|
||||||
|
return 'var(--btn-primary-fg-hover)';
|
||||||
|
case 'subtle':
|
||||||
|
return 'var(--btn-subtle-fg-hover)';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
background-color: ${(props) => {
|
||||||
|
switch (props.variant) {
|
||||||
|
case 'default':
|
||||||
|
return 'var(--btn-default-bg-hover)';
|
||||||
|
case 'filled':
|
||||||
|
return 'var(--btn-primary-bg-hover)';
|
||||||
|
case 'subtle':
|
||||||
|
return 'var(--btn-subtle-bg-hover)';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
border: 1px var(--primary-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mantine-Button-root &:focus-visible {
|
||||||
|
outline: --var-primary;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const pButton = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ children, tooltip, ...props }: ButtonProps, ref) => {
|
||||||
|
if (tooltip) {
|
||||||
|
return (
|
||||||
|
<Tooltip {...tooltip}>
|
||||||
|
<StyledButton ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</StyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Button = createPolymorphicComponent<'button', ButtonProps>(
|
||||||
|
pButton
|
||||||
|
);
|
||||||
|
|
||||||
|
pButton.defaultProps = {
|
||||||
|
onClick: undefined,
|
||||||
|
tooltip: undefined,
|
||||||
|
};
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { useDraggable } from '@dnd-kit/core';
|
|
||||||
|
|
||||||
export const Draggable = ({ element, id, children, key }: any) => {
|
|
||||||
const Element = element || 'div';
|
|
||||||
const { attributes, listeners, setNodeRef } = useDraggable({
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Element key={key} ref={setNodeRef} {...listeners} {...attributes}>
|
|
||||||
{children}
|
|
||||||
</Element>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import {
|
||||||
|
Menu as MantineMenu,
|
||||||
|
MenuProps as MantineMenuProps,
|
||||||
|
MenuItemProps as MantineMenuItemProps,
|
||||||
|
MenuLabelProps as MantineMenuLabelProps,
|
||||||
|
MenuDividerProps as MantineMenuDividerProps,
|
||||||
|
MenuDropdownProps as MantineMenuDropdownProps,
|
||||||
|
createPolymorphicComponent,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
type MenuProps = MantineMenuProps;
|
||||||
|
type MenuLabelProps = MantineMenuLabelProps;
|
||||||
|
type MenuItemProps = MantineMenuItemProps;
|
||||||
|
type MenuDividerProps = MantineMenuDividerProps;
|
||||||
|
type MenuDropdownProps = MantineMenuDropdownProps;
|
||||||
|
|
||||||
|
const StyledMenu = styled(MantineMenu)<MenuProps>``;
|
||||||
|
|
||||||
|
const StyledMenuLabel = styled(MantineMenu.Label)`
|
||||||
|
font-family: var(--label-font-family);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMenuItem = styled(MantineMenu.Item)`
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: var(--label-font-family);
|
||||||
|
background-color: var(--dropdown-menu-bg);
|
||||||
|
|
||||||
|
& .mantine-Menu-itemIcon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMenuDropdown = styled(MantineMenu.Dropdown)`
|
||||||
|
background: var(--dropdown-menu-bg);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMenuDivider = styled(MantineMenu.Divider)`
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DropdownMenu = ({ children, ...props }: MenuProps) => {
|
||||||
|
return <StyledMenu {...props}>{children}</StyledMenu>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MenuLabel = ({ children, ...props }: MenuLabelProps) => {
|
||||||
|
return <StyledMenuLabel {...props}>{children}</StyledMenuLabel>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pMenuItem = ({ children, ...props }: MenuItemProps) => {
|
||||||
|
return <StyledMenuItem {...props}>{children}</StyledMenuItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MenuDropdown = ({ children, ...props }: MenuDropdownProps) => {
|
||||||
|
return <StyledMenuDropdown {...props}>{children}</StyledMenuDropdown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MenuItem = createPolymorphicComponent<'button', MenuItemProps>(pMenuItem);
|
||||||
|
|
||||||
|
const MenuDivider = ({ ...props }: MenuDividerProps) => {
|
||||||
|
return <StyledMenuDivider {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
DropdownMenu.Label = MenuLabel;
|
||||||
|
DropdownMenu.Item = MenuItem;
|
||||||
|
DropdownMenu.Target = MantineMenu.Target;
|
||||||
|
DropdownMenu.Dropdown = MenuDropdown;
|
||||||
|
DropdownMenu.Divider = MenuDivider;
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
||||||
import { ActionIcon, ActionIconProps, TooltipProps } from '@mantine/core';
|
|
||||||
import { Tooltip } from '../tooltip/Tooltip';
|
|
||||||
|
|
||||||
type MantineIconButtonProps = ActionIconProps &
|
|
||||||
ComponentPropsWithoutRef<'button'>;
|
|
||||||
|
|
||||||
interface IconButtonProps extends MantineIconButtonProps {
|
|
||||||
active?: boolean;
|
|
||||||
icon: ReactNode;
|
|
||||||
tooltip?: Omit<TooltipProps, 'children'>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IconButton = ({
|
|
||||||
active,
|
|
||||||
tooltip,
|
|
||||||
icon,
|
|
||||||
...rest
|
|
||||||
}: IconButtonProps) => {
|
|
||||||
if (tooltip) {
|
|
||||||
return (
|
|
||||||
<Tooltip {...tooltip}>
|
|
||||||
<ActionIcon {...rest}>{icon}</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ActionIcon {...rest}>{icon}</ActionIcon>;
|
|
||||||
};
|
|
||||||
|
|
||||||
IconButton.defaultProps = {
|
|
||||||
active: false,
|
|
||||||
tooltip: undefined,
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
export * from './tooltip/Tooltip';
|
export * from './tooltip';
|
||||||
export * from './audio-player/AudioPlayer';
|
export * from './audio-player';
|
||||||
export * from './icon-button/IconButton';
|
export * from './text';
|
||||||
export * from './text/Text';
|
export * from './button';
|
||||||
|
export * from './virtual-grid';
|
||||||
|
export * from './modal';
|
||||||
|
export * from './input';
|
||||||
|
export * from './segmented-control';
|
||||||
|
export * from './dropdown-menu';
|
||||||
|
export * from './toast';
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import {
|
||||||
|
TextInput as MantineTextInput,
|
||||||
|
TextInputProps as MantineTextInputProps,
|
||||||
|
PasswordInput as MantinePasswordInput,
|
||||||
|
PasswordInputProps as MantinePasswordInputProps,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
interface TextInputProps extends MantineTextInputProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PasswordInputProps extends MantinePasswordInputProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledTextInput = styled(MantineTextInput)<TextInputProps>`
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-TextInput-wrapper {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-TextInput-input {
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-TextInput-required {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-TextInput-label {
|
||||||
|
font-family: var(--label-font-faimly);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledPasswordInput = styled(MantinePasswordInput)<PasswordInputProps>`
|
||||||
|
& .mantine-PasswordInput-input {
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-PasswordInput-required {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-PasswordInput-label {
|
||||||
|
font-family: var(--label-font-faimly);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
||||||
|
({ children, ...props }: TextInputProps, ref) => {
|
||||||
|
return (
|
||||||
|
<StyledTextInput ref={ref} spellCheck={false} {...props}>
|
||||||
|
{children}
|
||||||
|
</StyledTextInput>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
|
||||||
|
({ children, ...props }: PasswordInputProps, ref) => {
|
||||||
|
return (
|
||||||
|
<StyledPasswordInput ref={ref} spellCheck={false} {...props}>
|
||||||
|
{children}
|
||||||
|
</StyledPasswordInput>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import {
|
|
||||||
Modal as MantineModal,
|
|
||||||
ModalProps as MantineModalProps,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
|
||||||
|
|
||||||
interface ModalProps extends MantineModalProps {
|
|
||||||
condition: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Modal = ({ condition, children, ...rest }: ModalProps) => {
|
|
||||||
const [opened, handlers] = useDisclosure(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{condition && (
|
|
||||||
<MantineModal
|
|
||||||
{...rest}
|
|
||||||
opened={opened}
|
|
||||||
onClose={() => handlers.close()}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</MantineModal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Modal as MantineModal,
|
||||||
|
ModalProps as MantineModalProps,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
export interface ModalProps extends Omit<MantineModalProps, 'onClose'> {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
handlers: {
|
||||||
|
close: () => void;
|
||||||
|
open: () => void;
|
||||||
|
toggle: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Modal = ({ children, handlers, ...rest }: ModalProps) => {
|
||||||
|
return (
|
||||||
|
<MantineModal
|
||||||
|
overlayBlur={2}
|
||||||
|
overlayOpacity={0.2}
|
||||||
|
{...rest}
|
||||||
|
onClose={handlers.close}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MantineModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Modal.defaultProps = {
|
||||||
|
children: undefined,
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { forwardRef } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import {
|
||||||
|
SegmentedControl as MantineSegmentedControl,
|
||||||
|
SegmentedControlProps as MantineSegmentedControlProps,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
type SegmentedControlProps = MantineSegmentedControlProps;
|
||||||
|
|
||||||
|
const StyledSegmentedControl = styled(
|
||||||
|
MantineSegmentedControl
|
||||||
|
)<MantineSegmentedControlProps>`
|
||||||
|
&:focus,
|
||||||
|
&:focus-within,
|
||||||
|
&:active {
|
||||||
|
outline: 1px var(--primary-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-SegmentedControl-label {
|
||||||
|
font-family: var(--label-font-family);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SegmentedControl = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
SegmentedControlProps
|
||||||
|
>(({ ...props }: SegmentedControlProps, ref) => {
|
||||||
|
return <StyledSegmentedControl ref={ref} {...props} />;
|
||||||
|
});
|
||||||
@@ -3,14 +3,13 @@ import {
|
|||||||
ComponentPropsWithRef,
|
ComponentPropsWithRef,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
Text as MantineText,
|
Text as MantineText,
|
||||||
TextProps as MantineTextProps,
|
TextProps as MantineTextProps,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import { Font, textEllipsis } from '@/renderer/styles';
|
||||||
import { Font } from '../../styles';
|
|
||||||
import { textEllipsis } from '../../styles/mixins';
|
|
||||||
|
|
||||||
type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>;
|
type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>;
|
||||||
type MantineTextLinkProps = MantineTextProps & ComponentPropsWithRef<'link'>;
|
type MantineTextLinkProps = MantineTextProps & ComponentPropsWithRef<'link'>;
|
||||||
@@ -42,7 +41,7 @@ const BaseText = styled(MantineText)<any>`
|
|||||||
? 'var(--playerbar-text-secondary-color)'
|
? 'var(--playerbar-text-secondary-color)'
|
||||||
: 'var(--playerbar-text-primary-color)'};
|
: 'var(--playerbar-text-primary-color)'};
|
||||||
font-family: ${(props) => props.font || Font.GOTHAM};
|
font-family: ${(props) => props.font || Font.GOTHAM};
|
||||||
cursor: ${(props) => (props.link ? 'cursor' : 'default')};
|
/* cursor: ${(props) => (props.link ? 'cursor' : 'default')}; */
|
||||||
user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')};
|
user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')};
|
||||||
${(props) => props.overflow === 'hidden' && textEllipsis}
|
${(props) => props.overflow === 'hidden' && textEllipsis}
|
||||||
`;
|
`;
|
||||||
@@ -63,7 +62,7 @@ export const Text = ({
|
|||||||
}: TextProps) => {
|
}: TextProps) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
return (
|
return (
|
||||||
<StyledLinkText<typeof Link>
|
<StyledLinkText
|
||||||
$noSelect={noSelect}
|
$noSelect={noSelect}
|
||||||
$secondary={secondary}
|
$secondary={secondary}
|
||||||
component={Link}
|
component={Link}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
showNotification,
|
||||||
|
NotificationProps as MantineNotificationProps,
|
||||||
|
updateNotification,
|
||||||
|
hideNotification,
|
||||||
|
cleanNotifications,
|
||||||
|
cleanNotificationsQueue,
|
||||||
|
} from '@mantine/notifications';
|
||||||
|
|
||||||
|
interface NotificationProps extends MantineNotificationProps {
|
||||||
|
type?: 'success' | 'error' | 'warning' | 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
const showToast = ({ type, ...props }: NotificationProps) => {
|
||||||
|
const color =
|
||||||
|
type === 'success'
|
||||||
|
? 'var(--success-color)'
|
||||||
|
: type === 'warning'
|
||||||
|
? 'var(--warning-color)'
|
||||||
|
: type === 'error'
|
||||||
|
? 'var(--danger-color)'
|
||||||
|
: 'var(--primary-color)';
|
||||||
|
|
||||||
|
const defaultTitle =
|
||||||
|
type === 'success'
|
||||||
|
? 'Success'
|
||||||
|
: type === 'warning'
|
||||||
|
? 'Warning'
|
||||||
|
: type === 'error'
|
||||||
|
? 'Error'
|
||||||
|
: 'Info';
|
||||||
|
|
||||||
|
const defaultDuration = type === 'error' ? 3500 : 2000;
|
||||||
|
|
||||||
|
return showNotification({
|
||||||
|
autoClose: defaultDuration,
|
||||||
|
disallowClose: true,
|
||||||
|
styles: () => ({
|
||||||
|
closeButton: {},
|
||||||
|
description: {
|
||||||
|
color: 'var(--toast-description-fg)',
|
||||||
|
fontSize: '.9em',
|
||||||
|
},
|
||||||
|
loader: {
|
||||||
|
margin: '1rem',
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
'&::before': { backgroundColor: color },
|
||||||
|
background: 'var(--toast-bg)',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: 'var(--toast-title-fg)',
|
||||||
|
fontSize: '1em',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
title: defaultTitle,
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toast = {
|
||||||
|
clean: cleanNotifications,
|
||||||
|
cleanQueue: cleanNotificationsQueue,
|
||||||
|
hide: hideNotification,
|
||||||
|
show: showToast,
|
||||||
|
update: updateNotification,
|
||||||
|
};
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
.body {
|
|
||||||
padding: 5px;
|
|
||||||
color: var(--tooltip-text-color);
|
|
||||||
background: var(--tooltip-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
background: var(--tooltip-bg);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core';
|
|
||||||
|
|
||||||
export const Tooltip = ({ children, ...rest }: TooltipProps) => {
|
|
||||||
return (
|
|
||||||
<MantineTooltip
|
|
||||||
radius="xs"
|
|
||||||
styles={{
|
|
||||||
arrow: {
|
|
||||||
background: 'var(--tooltip-bg)',
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
background: 'var(--tooltip-bg)',
|
|
||||||
color: 'var(--tooltip-text-color)',
|
|
||||||
padding: '5px',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</MantineTooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Tooltip.defaultProps = {
|
|
||||||
openDelay: 0,
|
|
||||||
position: 'top',
|
|
||||||
transition: 'fade',
|
|
||||||
transitionDuration: 250,
|
|
||||||
withArrow: true,
|
|
||||||
withinPortal: true,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core';
|
||||||
|
|
||||||
|
const StyledTooltip = styled(MantineTooltip)`
|
||||||
|
& .mantine-Tooltip-arrow {
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-Tooltip-tooltip {
|
||||||
|
margin: 20px;
|
||||||
|
color: var(--tooltip-text-color);
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Tooltip = ({ children, ...rest }: TooltipProps) => {
|
||||||
|
return (
|
||||||
|
<StyledTooltip pl={10} pr={10} py={5} radius="xs" {...rest}>
|
||||||
|
{children}
|
||||||
|
</StyledTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Tooltip.defaultProps = {
|
||||||
|
openDelay: 0,
|
||||||
|
position: 'top',
|
||||||
|
transition: 'fade',
|
||||||
|
transitionDuration: 250,
|
||||||
|
withArrow: true,
|
||||||
|
withinPortal: true,
|
||||||
|
};
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { DragOverlay, useDndMonitor } from '@dnd-kit/core';
|
|
||||||
import { snapCenterToCursor } from '@dnd-kit/modifiers';
|
|
||||||
import { Card, Skeleton } from '@mantine/core';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { CardRow } from '../../types';
|
|
||||||
import { Draggable } from '../drag-drop/Draggable';
|
|
||||||
import { Text } from '../text/Text';
|
|
||||||
import { GridCardControls } from './GridCardControls';
|
|
||||||
|
|
||||||
const CardWrapper = styled(motion.div)<{
|
|
||||||
itemGap: number;
|
|
||||||
itemHeight: number;
|
|
||||||
itemWidth: number;
|
|
||||||
}>`
|
|
||||||
flex: ${({ itemWidth }) => `0 0 ${itemWidth}px`};
|
|
||||||
width: ${({ itemWidth }) => `${itemWidth}px`};
|
|
||||||
height: ${({ itemHeight }) => `${itemHeight}px`};
|
|
||||||
margin: ${({ itemGap }) => `0 ${itemGap / 2}px`};
|
|
||||||
border-radius: 3px;
|
|
||||||
filter: drop-shadow(0 4px 4px #000);
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: auto; // https://github.com/bvaughn/react-window/issues/128#issuecomment-460166682
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: 1px solid #fff;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCard = styled(Card)`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0;
|
|
||||||
background-color: rgb(50, 50, 50, 50%);
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background-color 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(50, 50, 50, 60%);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ImageSection = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ImageProps {
|
|
||||||
height: number;
|
|
||||||
src: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Image = styled(motion.div).attrs((props: ImageProps) => ({
|
|
||||||
style: {
|
|
||||||
background: `url(${props.src})`,
|
|
||||||
},
|
|
||||||
}))<ImageProps>`
|
|
||||||
height: ${({ height }) => `${height}px`};
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
border: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ControlsContainer = styled.div`
|
|
||||||
display: none;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
${StyledCard}:hover & {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DetailSection = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Row = styled.div`
|
|
||||||
height: 25px;
|
|
||||||
padding: 0 0.2rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GridCard = ({ data, index, style }: any) => {
|
|
||||||
const {
|
|
||||||
itemHeight,
|
|
||||||
itemWidth,
|
|
||||||
columnCount,
|
|
||||||
itemGap,
|
|
||||||
itemCount,
|
|
||||||
cardControls,
|
|
||||||
handlePlayQueueAdd,
|
|
||||||
cardRows,
|
|
||||||
itemData,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
const startIndex = index * columnCount;
|
|
||||||
const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1);
|
|
||||||
const cards = [];
|
|
||||||
|
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
|
|
||||||
useDndMonitor({
|
|
||||||
onDragCancel: () => setIsDragging(false),
|
|
||||||
onDragEnd: () => setIsDragging(false),
|
|
||||||
onDragStart: () => setIsDragging(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = startIndex; i <= stopIndex; i += 1) {
|
|
||||||
cards.push(
|
|
||||||
<React.Fragment key={`card-${i}-${index}`}>
|
|
||||||
<Draggable id={`${i}-${index}`}>
|
|
||||||
<CardWrapper
|
|
||||||
itemGap={itemGap}
|
|
||||||
itemHeight={itemHeight}
|
|
||||||
itemWidth={itemWidth}
|
|
||||||
>
|
|
||||||
<Skeleton visible={!itemData[i]}>
|
|
||||||
<StyledCard>
|
|
||||||
<ImageSection>
|
|
||||||
<Image height={itemWidth} src={itemData[i]?.image}>
|
|
||||||
<ControlsContainer>
|
|
||||||
<GridCardControls
|
|
||||||
cardControls={cardControls}
|
|
||||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
|
||||||
itemData={itemData[i]}
|
|
||||||
/>
|
|
||||||
</ControlsContainer>
|
|
||||||
</Image>
|
|
||||||
</ImageSection>
|
|
||||||
<DetailSection>
|
|
||||||
{cardRows.map((row: CardRow) => (
|
|
||||||
<Row key={`row-${row.prop}`}>
|
|
||||||
<Text overflow="hidden" weight={500}>
|
|
||||||
{itemData[i] && itemData[i][row.prop]}
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
))}
|
|
||||||
</DetailSection>
|
|
||||||
</StyledCard>
|
|
||||||
</Skeleton>
|
|
||||||
</CardWrapper>
|
|
||||||
</Draggable>
|
|
||||||
{createPortal(
|
|
||||||
<DragOverlay dropAnimation={null} modifiers={[snapCenterToCursor]}>
|
|
||||||
{isDragging ? <div>OVERLAY</div> : null}
|
|
||||||
</DragOverlay>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...style,
|
|
||||||
alignItems: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'start',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cards}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+1
-1
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import { Button, UnstyledButton, UnstyledButtonProps } from '@mantine/core';
|
import { Button, UnstyledButton, UnstyledButtonProps } from '@mantine/core';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { RiPlayFill } from 'react-icons/ri';
|
import { RiPlayFill } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Play } from '../../../types';
|
import { Play } from '../../../types';
|
||||||
|
|
||||||
type PlayButtonType = UnstyledButtonProps &
|
type PlayButtonType = UnstyledButtonProps &
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { Skeleton } from '@mantine/core';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { CardRow } from '../../types';
|
||||||
|
import { Text } from '../text';
|
||||||
|
import { GridCardControls } from './grid-card-controls';
|
||||||
|
|
||||||
|
const CardWrapper = styled(motion.div)<{
|
||||||
|
itemGap: number;
|
||||||
|
itemHeight: number;
|
||||||
|
itemWidth: number;
|
||||||
|
}>`
|
||||||
|
flex: ${({ itemWidth }) => `0 0 ${itemWidth}px`};
|
||||||
|
width: ${({ itemWidth }) => `${itemWidth}px`};
|
||||||
|
height: ${({ itemHeight }) => `${itemHeight}px`};
|
||||||
|
margin: ${({ itemGap }) => `0 ${itemGap / 2}px`};
|
||||||
|
filter: drop-shadow(0 4px 4px #000);
|
||||||
|
transition: border 0.2s ease-in-out;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: auto; // https://github.com/bvaughn/react-window/issues/128#issuecomment-460166682
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 1px solid #fff;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCard = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--grid-card-bg);
|
||||||
|
border-radius: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageSection = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ImageProps {
|
||||||
|
height: number;
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const Image = styled(motion.div).attrs((props: ImageProps) => ({
|
||||||
|
// style: {
|
||||||
|
// background: `url(${props.src})`,
|
||||||
|
// backgroundPosition: 'center',
|
||||||
|
// backgroundSize: 'cover',
|
||||||
|
// },
|
||||||
|
// }))<ImageProps>`
|
||||||
|
// height: ${({ height }) => `${height}px`};
|
||||||
|
// background-position: center;
|
||||||
|
// background-size: cover;
|
||||||
|
// border: 0;
|
||||||
|
// `;
|
||||||
|
|
||||||
|
const Image = styled(motion.div)<ImageProps>`
|
||||||
|
height: ${({ height }) => `${height}px`};
|
||||||
|
background: ${({ src }) => `url(${src})`};
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
border: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ControlsContainer = styled.div`
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DetailSection = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Row = styled.div`
|
||||||
|
height: 25px;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GridCard = ({ data, index, style }: any) => {
|
||||||
|
const {
|
||||||
|
itemHeight,
|
||||||
|
itemWidth,
|
||||||
|
columnCount,
|
||||||
|
itemGap,
|
||||||
|
itemCount,
|
||||||
|
cardControls,
|
||||||
|
handlePlayQueueAdd,
|
||||||
|
cardRows,
|
||||||
|
itemData,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
const startIndex = index * columnCount;
|
||||||
|
const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1);
|
||||||
|
const cards = [];
|
||||||
|
|
||||||
|
for (let i = startIndex; i <= stopIndex; i += 1) {
|
||||||
|
cards.push(
|
||||||
|
<React.Fragment key={`card-${i}-${index}`}>
|
||||||
|
<CardWrapper
|
||||||
|
itemGap={itemGap}
|
||||||
|
itemHeight={itemHeight}
|
||||||
|
itemWidth={itemWidth}
|
||||||
|
>
|
||||||
|
<Skeleton visible={!itemData[i]}>
|
||||||
|
<StyledCard>
|
||||||
|
<ImageSection>
|
||||||
|
<Image height={itemWidth} src={itemData[i]?.imageUrl}>
|
||||||
|
<ControlsContainer>
|
||||||
|
<GridCardControls
|
||||||
|
cardControls={cardControls}
|
||||||
|
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||||
|
itemData={itemData[i]}
|
||||||
|
/>
|
||||||
|
</ControlsContainer>
|
||||||
|
</Image>
|
||||||
|
</ImageSection>
|
||||||
|
<DetailSection>
|
||||||
|
{cardRows.map((row: CardRow) => (
|
||||||
|
<Row key={`row-${row.prop}`}>
|
||||||
|
<Text overflow="hidden" weight={500}>
|
||||||
|
{itemData[i] && itemData[i][row.prop]}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</DetailSection>
|
||||||
|
</StyledCard>
|
||||||
|
</Skeleton>
|
||||||
|
</CardWrapper>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cards}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './virtual-grid-wrapper';
|
||||||
|
export * from './virtual-infinite-grid';
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { Ref, useMemo } from 'react';
|
import { Ref, useMemo } from 'react';
|
||||||
import { FixedSizeList, FixedSizeListProps } from 'react-window';
|
import { FixedSizeList, FixedSizeListProps } from 'react-window';
|
||||||
import { usePlayQueueHandler } from '../../features/player/hooks/usePlayQueueHandler';
|
import { GridCard } from '@/renderer/components/virtual-grid/grid-card';
|
||||||
import { CardRow } from '../../types';
|
import { usePlayQueueHandler } from '@/renderer/features/player/hooks/usePlayQueueHandler';
|
||||||
import { GridCard } from './GridCard';
|
import { CardRow } from '@/renderer/types';
|
||||||
|
|
||||||
export const VirtualGridWrapper = ({
|
export const VirtualGridWrapper = ({
|
||||||
refInstance,
|
refInstance,
|
||||||
+11
-6
@@ -2,8 +2,8 @@ import { useState, useEffect, useRef, useMemo } from 'react';
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { FixedSizeListProps } from 'react-window';
|
import { FixedSizeListProps } from 'react-window';
|
||||||
import InfiniteLoader from 'react-window-infinite-loader';
|
import InfiniteLoader from 'react-window-infinite-loader';
|
||||||
import { CardRow } from '../../types';
|
import { VirtualGridWrapper } from '@/renderer/components/virtual-grid/virtual-grid-wrapper';
|
||||||
import { VirtualGridWrapper } from './VirtualGridWrapper';
|
import { CardRow } from '@/renderer/types';
|
||||||
|
|
||||||
interface VirtualGridProps
|
interface VirtualGridProps
|
||||||
extends Omit<FixedSizeListProps, 'children' | 'itemSize'> {
|
extends Omit<FixedSizeListProps, 'children' | 'itemSize'> {
|
||||||
@@ -12,8 +12,9 @@ interface VirtualGridProps
|
|||||||
itemGap?: number;
|
itemGap?: number;
|
||||||
itemSize: number;
|
itemSize: number;
|
||||||
minimumBatchSize?: number;
|
minimumBatchSize?: number;
|
||||||
query: (props: any) => Promise<any>;
|
query: ({ serverId }: { serverId: string }, props: any) => Promise<any>;
|
||||||
queryParams?: Record<string, any>;
|
queryParams?: Record<string, any>;
|
||||||
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VirtualInfiniteGrid = ({
|
export const VirtualInfiniteGrid = ({
|
||||||
@@ -26,6 +27,7 @@ export const VirtualInfiniteGrid = ({
|
|||||||
query,
|
query,
|
||||||
queryParams,
|
queryParams,
|
||||||
height,
|
height,
|
||||||
|
serverId,
|
||||||
width,
|
width,
|
||||||
}: VirtualGridProps) => {
|
}: VirtualGridProps) => {
|
||||||
const [itemData, setItemData] = useState<any[]>([]);
|
const [itemData, setItemData] = useState<any[]>([]);
|
||||||
@@ -58,11 +60,14 @@ export const VirtualInfiniteGrid = ({
|
|||||||
const start = startIndex * columnCount;
|
const start = startIndex * columnCount;
|
||||||
const end = stopIndex * columnCount + columnCount;
|
const end = stopIndex * columnCount + columnCount;
|
||||||
|
|
||||||
const t = await query({
|
const t = await query(
|
||||||
|
{ serverId },
|
||||||
|
{
|
||||||
skip: start,
|
skip: start,
|
||||||
take: end - start,
|
take: end - start,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const newData: any[] = [...itemData];
|
const newData: any[] = [...itemData];
|
||||||
|
|
||||||
@@ -109,7 +114,7 @@ export const VirtualInfiniteGrid = ({
|
|||||||
itemCount={itemCount || 0}
|
itemCount={itemCount || 0}
|
||||||
itemData={itemData}
|
itemData={itemData}
|
||||||
itemGap={itemGap!}
|
itemGap={itemGap!}
|
||||||
itemHeight={itemHeight!}
|
itemHeight={itemHeight! + itemGap! / 2}
|
||||||
itemWidth={itemSize}
|
itemWidth={itemSize}
|
||||||
refInstance={(list) => {
|
refInstance={(list) => {
|
||||||
infiniteLoaderRef(list);
|
infiniteLoaderRef(list);
|
||||||
Reference in New Issue
Block a user