Begin migration (round 1)

This commit is contained in:
jeffvli 2024-05-24 22:33:38 -07:00
parent 875d302a2f
commit c4378e3f11
140 changed files with 2058 additions and 3537 deletions

2830
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -217,7 +217,7 @@
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6", "@types/react-window-infinite-loader": "^1.0.6",
"@types/sanitize-html": "^2.11.0", "@types/sanitize-html": "^2.11.0",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.34",
"@types/terser-webpack-plugin": "^5.0.4", "@types/terser-webpack-plugin": "^5.0.4",
"@types/webpack-bundle-analyzer": "^4.4.1", "@types/webpack-bundle-analyzer": "^4.4.1",
"@types/webpack-env": "^1.16.3", "@types/webpack-env": "^1.16.3",
@ -342,15 +342,15 @@
"react-i18next": "^11.18.6", "react-i18next": "^11.18.6",
"react-icons": "^4.10.1", "react-icons": "^4.10.1",
"react-player": "^2.11.0", "react-player": "^2.11.0",
"react-router": "^6.16.0", "react-router": "^6.23.0",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.23.0",
"react-simple-img": "^3.0.0", "react-simple-img": "^3.0.0",
"react-virtualized-auto-sizer": "^1.0.17", "react-virtualized-auto-sizer": "^1.0.17",
"react-window": "^1.8.9", "react-window": "^1.8.9",
"react-window-infinite-loader": "^1.0.9", "react-window-infinite-loader": "^1.0.9",
"sanitize-html": "^2.13.0", "sanitize-html": "^2.13.0",
"semver": "^7.5.4", "semver": "^7.5.4",
"styled-components": "^6.0.8", "styled-components": "^6.1.0",
"swiper": "^9.3.1", "swiper": "^9.3.1",
"zod": "^3.22.3", "zod": "^3.22.3",
"zustand": "^4.3.9" "zustand": "^4.3.9"

View File

@ -14,10 +14,8 @@ export const App = () => {
return ( return (
<MantineProvider <MantineProvider
withGlobalStyles forceColorScheme={isDark ? 'dark' : 'light'}
withNormalizeCSS
theme={{ theme={{
colorScheme: isDark ? 'dark' : 'light',
components: { components: {
AppShell: { AppShell: {
styles: { styles: {
@ -44,18 +42,17 @@ export const App = () => {
}, },
}, },
defaultRadius: 'xs', defaultRadius: 'xs',
dir: 'ltr',
focusRing: 'auto', focusRing: 'auto',
focusRingStyles: { // focusRingStyles: {
inputStyles: () => ({ // inputStyles: () => ({
border: '1px solid var(--primary-color)', // border: '1px solid var(--primary-color)',
}), // }),
resetStyles: () => ({ outline: 'none' }), // resetStyles: () => ({ outline: 'none' }),
styles: () => ({ // styles: () => ({
outline: '1px solid var(--primary-color)', // outline: '1px solid var(--primary-color)',
outlineOffset: '-1px', // outlineOffset: '-1px',
}), // }),
}, // },
fontFamily: 'var(--content-font-family)', fontFamily: 'var(--content-font-family)',
fontSizes: { fontSizes: {
lg: '1.1rem', lg: '1.1rem',
@ -66,7 +63,7 @@ export const App = () => {
}, },
headings: { headings: {
fontFamily: 'var(--content-font-family)', fontFamily: 'var(--content-font-family)',
fontWeight: 700, fontWeight: '700',
}, },
other: {}, other: {},
spacing: { spacing: {

View File

@ -45,7 +45,7 @@ export const RemoteContainer = () => {
<Title order={2}>Album: {song.album}</Title> <Title order={2}>Album: {song.album}</Title>
<Title order={2}>Artist: {song.artistName}</Title> <Title order={2}>Artist: {song.artistName}</Title>
</Group> </Group>
<Group position="apart"> <Group justify="space-between">
<Title order={3}>Duration: {formatDuration(song.duration)}</Title> <Title order={3}>Duration: {formatDuration(song.duration)}</Title>
{song.releaseDate && ( {song.releaseDate && (
<Title order={3}> <Title order={3}>
@ -58,7 +58,7 @@ export const RemoteContainer = () => {
)} )}
<Group <Group
grow grow
spacing={0} gap={0}
> >
<RemoteButton <RemoteButton
disabled={!song} disabled={!song}
@ -97,7 +97,7 @@ export const RemoteContainer = () => {
</Group> </Group>
<Group <Group
grow grow
spacing={0} gap={0}
> >
<RemoteButton <RemoteButton
$active={shuffle || false} $active={shuffle || false}
@ -145,7 +145,7 @@ export const RemoteContainer = () => {
openDelay={1000} openDelay={1000}
> >
<Rating <Rating
sx={{ margin: 'auto' }} style={{ margin: 'auto' }}
value={song.userRating ?? 0} value={song.userRating ?? 0}
onChange={debouncedSetRating} onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)} onDoubleClick={() => debouncedSetRating(0)}
@ -159,8 +159,8 @@ export const RemoteContainer = () => {
max={100} max={100}
rightLabel={ rightLabel={
<Text <Text
fw={600}
size="xs" size="xs"
weight={600}
> >
{volume ?? 0} {volume ?? 0}
</Text> </Text>

View File

@ -1,65 +1,57 @@
import { import { AppShell, Container, Flex, Grid, Image, Skeleton } from '@mantine/core';
AppShell,
Container,
Flex,
Grid,
Header,
Image,
MediaQuery,
Skeleton,
Title,
} from '@mantine/core';
import { ThemeButton } from '/@/remote/components/buttons/theme-button'; import { ThemeButton } from '/@/remote/components/buttons/theme-button';
import { ImageButton } from '/@/remote/components/buttons/image-button'; import { ImageButton } from '/@/remote/components/buttons/image-button';
import { RemoteContainer } from '/@/remote/components/remote-container'; import { RemoteContainer } from '/@/remote/components/remote-container';
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button'; import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
import { useConnected } from '/@/remote/store'; import { useConnected } from '/@/remote/store';
// TODO: Fix media query
export const Shell = () => { export const Shell = () => {
const connected = useConnected(); const connected = useConnected();
return ( return (
<AppShell <AppShell
header={ header={{ height: 60 }}
<Header height={60}>
<Grid>
<Grid.Col span="auto">
<div>
<Image
fit="contain"
height={60}
src="/favicon.ico"
width={60}
/>
</div>
</Grid.Col>
<MediaQuery
smallerThan="sm"
styles={{ display: 'none' }}
>
<Grid.Col
sm={6}
xs={0}
>
<Title ta="center">Feishin Remote</Title>
</Grid.Col>
</MediaQuery>
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
>
<ReconnectButton />
<ImageButton />
<ThemeButton />
</Flex>
</Grid.Col>
</Grid>
</Header>
}
padding="md" padding="md"
> >
<AppShell.Header>
<Grid>
<Grid.Col span="auto">
<div>
<Image
fit="contain"
height={60}
src="/favicon.ico"
width={60}
/>
</div>
</Grid.Col>
{/* <MediaQuery
smallerThan="sm"
styles={{ display: 'none' }}
>
<Grid.Col
span={{
sm: 6,
xs: 0,
}}
>
<Title ta="center">Feishin Remote</Title>
</Grid.Col>
</MediaQuery> */}
<Grid.Col span="auto">
<Flex
direction="row"
justify="right"
>
<ReconnectButton />
<ImageButton />
<ThemeButton />
</Flex>
</Grid.Col>
</Grid>
</AppShell.Header>
<Container> <Container>
{connected ? ( {connected ? (
<RemoteContainer /> <RemoteContainer />

View File

@ -1,5 +1,5 @@
import { hideNotification, showNotification } from '@mantine/notifications'; import { hideNotification, showNotification } from '@mantine/notifications';
import type { NotificationProps as MantineNotificationProps } from '@mantine/notifications'; import type { NotificationData as MantineNotificationProps } from '@mantine/notifications';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';

View File

@ -1,30 +1,32 @@
import { useEffect, useMemo, useRef } from 'react';
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'; import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
import { ModuleRegistry } from '@ag-grid-community/core'; import { ModuleRegistry } from '@ag-grid-community/core';
import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model'; import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model';
import '@ag-grid-community/styles/ag-grid.css';
import { MantineProvider } from '@mantine/core';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { initSimpleImg } from 'react-simple-img'; import { initSimpleImg } from 'react-simple-img';
import { toast } from './components'; import { useTheme } from '/@/renderer/hooks';
import { useTheme } from './hooks'; import { AppRouter } from '/@/renderer/router/app-router';
import { IsUpdatedDialog } from './is-updated-dialog'; import { useRef, useEffect, useMemo } from 'react';
import { AppRouter } from './router/app-router';
import {
useHotkeySettings,
usePlaybackSettings,
useRemoteSettings,
useSettingsStore,
} from './store/settings.store';
import './styles/global.scss';
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
import { PlayerState, usePlayerStore, useQueueControls } from '/@/renderer/store';
import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
import '@ag-grid-community/styles/ag-grid.css';
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { toast } from '/@/renderer/components';
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
import { useServerVersion } from '/@/renderer/hooks/use-server-version'; import { useServerVersion } from '/@/renderer/hooks/use-server-version';
import {
useSettingsStore,
usePlaybackSettings,
useHotkeySettings,
useQueueControls,
useRemoteSettings,
usePlayerStore,
PlayerState,
} from '/@/renderer/store';
import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
import { IsUpdatedDialog } from '/@/renderer/is-updated-dialog';
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]); ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
@ -189,13 +191,13 @@ export const App = () => {
}, [language]); }, [language]);
return ( return (
<> <MantineProvider forceColorScheme={theme as 'light' | 'dark'}>
<PlayQueueHandlerContext.Provider value={providerValue}> <PlayQueueHandlerContext.Provider value={providerValue}>
<ContextMenuProvider> <ContextMenuProvider>
<AppRouter /> <AppRouter />
</ContextMenuProvider> </ContextMenuProvider>
</PlayQueueHandlerContext.Provider> </PlayQueueHandlerContext.Provider>
<IsUpdatedDialog /> <IsUpdatedDialog />
</> </MantineProvider>
); );
}; };

View File

@ -0,0 +1,64 @@
import type { ActionIconProps as MantineActionIconProps } from '@mantine/core';
import { createPolymorphicComponent, ActionIcon as MantineActionIcon } from '@mantine/core';
import styled from 'styled-components';
import { BadgeProps } from '/@/renderer/components/badge';
export type ActionIconProps = MantineActionIconProps;
const StyledActionIcon = styled(MantineActionIcon)<ActionIconProps>`
color: ${(props) => `var(--btn-${props.variant}-fg)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
border: ${(props) => `var(--btn-${props.variant}-border)`};
border-radius: ${(props) => `var(--btn-${props.variant}-radius)`};
transition: background 0.2s ease-in-out, color 0.2s ease-in-out, border 0.2s ease-in-out;
svg {
fill: ${(props) => `var(--btn-${props.variant}-fg)`};
transition: fill 0.2s ease-in-out;
}
&:disabled {
color: ${(props) => `var(--btn-${props.variant}-fg)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
opacity: 0.6;
}
&:not([data-disabled])&:hover {
color: ${(props) => `var(--btn-${props.variant}-fg) !important`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
filter: brightness(85%);
border: ${(props) => `var(--btn-${props.variant}-border-hover)`};
svg {
fill: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
}
}
&:not([data-disabled])&:focus-visible {
color: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
filter: brightness(85%);
}
& .mantine-ActionIcon-centerLoader {
display: none;
}
`;
const _ActionIcon = ({ children, ...props }: ActionIconProps) => {
return (
<StyledActionIcon
radius="md"
size="sm"
styles={{
root: { background: 'var(--badge-bg)' },
}}
{...props}
>
{children}
</StyledActionIcon>
);
};
export const ActionIcon = createPolymorphicComponent<'button', BadgeProps>(_ActionIcon);

View File

@ -0,0 +1,136 @@
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
import styled, { CSSProperties } from 'styled-components';
import { StyledComponentsStyleProps, StyleProps } from '/@/renderer/components/shared';
interface BoxProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
align?: CSSProperties['alignItems'];
children?: ReactNode;
columnGap?: CSSProperties['columnGap'];
direction?: CSSProperties['flexDirection'];
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
justify?: CSSProperties['justifyContent'];
rowGap?: CSSProperties['rowGap'];
wrap?: CSSProperties['flexWrap'];
}
export const Box = forwardRef<HTMLDivElement, BoxProps>(({ children, ...props }: BoxProps, ref) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return (
<StyledBox
ref={ref}
{...styleProps}
{...htmlProps}
>
{children}
</StyledBox>
);
});
const StyledBox = styled.div<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -92,7 +92,6 @@ export const _Button = forwardRef<HTMLButtonElement, ButtonProps>(
> >
<StyledButton <StyledButton
ref={ref} ref={ref}
loaderPosition="center"
{...props} {...props}
> >
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper> <ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
@ -109,7 +108,6 @@ export const _Button = forwardRef<HTMLButtonElement, ButtonProps>(
return ( return (
<StyledButton <StyledButton
ref={ref} ref={ref}
loaderPosition="center"
{...props} {...props}
> >
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper> <ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
@ -171,7 +169,7 @@ export const TimeoutButton = ({ timeoutProps, ...props }: HoldButtonProps) => {
return ( return (
<Button <Button
sx={{ color: 'var(--danger-color)' }} style={{ color: 'var(--danger-color)' }}
onClick={startTimeout} onClick={startTimeout}
{...props} {...props}
> >

View File

@ -156,7 +156,7 @@ export const AlbumCard = ({
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: `${size}px`, height: `${size}px`,

View File

@ -1,7 +1,6 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import React from 'react'; import React from 'react';
import type { UnstyledButtonProps } from '@mantine/core'; import type { UnstyledButtonProps } from '@mantine/core';
import { Group } from '@mantine/core';
import { RiPlayFill, RiMore2Fill, RiHeartFill, RiHeartLine } from 'react-icons/ri'; import { RiPlayFill, RiMore2Fill, RiHeartFill, RiHeartLine } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
import { _Button } from '/@/renderer/components/button'; import { _Button } from '/@/renderer/components/button';
@ -14,6 +13,7 @@ import {
ALBUM_CONTEXT_MENU_ITEMS, ALBUM_CONTEXT_MENU_ITEMS,
ARTIST_CONTEXT_MENU_ITEMS, ARTIST_CONTEXT_MENU_ITEMS,
} from '/@/renderer/features/context-menu/context-menu-items'; } from '/@/renderer/features/context-menu/context-menu-items';
import { Group } from '/@/renderer/components/group';
type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>; type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>;
@ -137,11 +137,11 @@ export const CardControls = ({
<PlayButton onClick={handlePlay}> <PlayButton onClick={handlePlay}>
<RiPlayFill size={25} /> <RiPlayFill size={25} />
</PlayButton> </PlayButton>
<Group spacing="xs"> <Group gap="xs">
<SecondaryButton <SecondaryButton
disabled disabled
p={5} p={5}
sx={{ svg: { fill: 'white !important' } }} style={{ svg: { fill: 'white !important' } }}
variant="subtle" variant="subtle"
> >
<FavoriteWrapper isFavorite={itemData?.isFavorite}> <FavoriteWrapper isFavorite={itemData?.isFavorite}>
@ -157,7 +157,7 @@ export const CardControls = ({
</SecondaryButton> </SecondaryButton>
<SecondaryButton <SecondaryButton
p={5} p={5}
sx={{ svg: { fill: 'white !important' } }} style={{ svg: { fill: 'white !important' } }}
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -40,7 +40,7 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
<Text <Text
$noSelect $noSelect
$secondary $secondary
sx={{ style={{
display: 'inline-block', display: 'inline-block',
padding: '0 2px 0 1px', padding: '0 2px 0 1px',
}} }}

View File

@ -151,7 +151,7 @@ export const PosterCard = ({
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: '100%', height: '100%',
@ -190,7 +190,7 @@ export const PosterCard = ({
<ImageContainerSkeleton /> <ImageContainerSkeleton />
</Skeleton> </Skeleton>
<DetailContainer> <DetailContainer>
<Stack spacing="sm"> <Stack gap="sm">
{controls.cardRows.map((row, index) => ( {controls.cardRows.map((row, index) => (
<Skeleton <Skeleton
key={`${index}-${row.arrayProperty}`} key={`${index}-${row.arrayProperty}`}

View File

@ -0,0 +1,134 @@
import { forwardRef, ReactNode, HTMLAttributes } from 'react';
import { Center as MantineCenter } from '@mantine/core';
import styled from 'styled-components';
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
interface CenterProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
children?: ReactNode;
inline?: boolean;
}
export const Center = forwardRef<HTMLDivElement, CenterProps>(
({ inline, children, ...props }: CenterProps, ref) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return (
<StyledCenter
ref={ref}
inline={inline}
{...styleProps}
{...htmlProps}
>
{children}
</StyledCenter>
);
},
);
const StyledCenter = styled(MantineCenter)<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -1,7 +1,8 @@
import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react'; import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react';
import { Box, Group, UnstyledButton, UnstyledButtonProps } from '@mantine/core'; import { UnstyledButton, UnstyledButtonProps } from '@mantine/core';
import { motion, Variants } from 'framer-motion'; import { motion, Variants } from 'framer-motion';
import styled from 'styled-components'; import styled from 'styled-components';
import { Group } from '/@/renderer/components/group';
interface ContextMenuProps { interface ContextMenuProps {
children: ReactNode; children: ReactNode;
@ -80,12 +81,12 @@ export const ContextMenuButton = forwardRef(
disabled={props.disabled} disabled={props.disabled}
onClick={props.onClick} onClick={props.onClick}
> >
<Group position="apart"> <Group justify="space-between">
<Group spacing="md"> <Group gap="md">
<Box>{leftIcon}</Box> <div>{leftIcon}</div>
<Box mr="2rem">{children}</Box> <div style={{ marginRight: '2rem' }}>{children}</div>
</Group> </Group>
<Box>{rightIcon}</Box> <div>{rightIcon}</div>
</Group> </Group>
</StyledContextMenuButton> </StyledContextMenuButton>
); );

View File

@ -38,7 +38,7 @@ export const DatePicker = ({ width, maxWidth, ...props }: DatePickerProps) => {
return ( return (
<StyledDatePicker <StyledDatePicker
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
/> />
); );
}; };

View File

@ -0,0 +1,44 @@
import { forwardRef, HTMLAttributes, CSSProperties } from 'react';
import { Divider as MantineDivider } from '@mantine/core';
import styled from 'styled-components';
interface DividerProps extends HTMLAttributes<HTMLDivElement> {
color?: string;
label?: string;
labelPosition?: 'left' | 'center' | 'right';
marginX?: CSSProperties['margin'];
marginY?: CSSProperties['margin'];
orientation?: 'horizontal' | 'vertical';
}
export const Divider = forwardRef<HTMLDivElement, DividerProps>(
(
{ color, label, labelPosition, marginX, marginY, orientation, ...props }: DividerProps,
ref,
) => {
return (
<StyledDivider
ref={ref}
$color={color}
$marginX={marginX}
$marginY={marginY}
label={label}
labelPosition={labelPosition}
orientation={orientation}
{...props}
/>
);
},
);
const StyledDivider = styled(MantineDivider)<{
$color?: DividerProps['color'];
$marginX?: DividerProps['marginX'];
$marginY?: DividerProps['marginY'];
}>`
margin-top: ${(props) => props.$marginY};
margin-bottom: ${(props) => props.$marginY};
margin-left: ${(props) => props.$marginX};
margin-right: ${(props) => props.$marginX};
border-color: ${(props) => props.$color || 'var(--generic-border-color)'} !important;
`;

View File

@ -1,6 +1,6 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { Group, Image, Stack } from '@mantine/core'; import { Image } from '@mantine/core';
import type { Variants } from 'framer-motion'; import type { Variants } from 'framer-motion';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,6 +15,8 @@ import { AppRoute } from '/@/renderer/router/routes';
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
import { Play } from '/@/renderer/types'; import { Play } from '/@/renderer/types';
import { usePlayButtonBehavior } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store';
import { Group } from '/@/renderer/components/group';
import { Stack } from '/@/renderer/components/stack';
const Carousel = styled(motion.div)` const Carousel = styled(motion.div)`
position: relative; position: relative;
@ -166,21 +168,21 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
placeholder="var(--card-default-bg)" placeholder="var(--card-default-bg)"
radius="md" radius="md"
src={data[itemIndex]?.imageUrl} src={data[itemIndex]?.imageUrl}
sx={{ objectFit: 'cover' }} style={{ objectFit: 'cover' }}
width={225} width={225}
/> />
</ImageColumn> </ImageColumn>
<InfoColumn> <InfoColumn>
<Stack <Stack
spacing="md" gap="md"
sx={{ width: '100%' }} style={{ width: '100%' }}
> >
<TitleWrapper> <TitleWrapper>
<TextTitle <TextTitle
lh="3.5rem" lh="3.5rem"
order={1} order={1}
overflow="hidden" overflow="hidden"
sx={{ fontSize: '3.5rem' }} style={{ fontSize: '3.5rem' }}
weight={900} weight={900}
> >
{currentItem?.name} {currentItem?.name}
@ -209,7 +211,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
<Badge size="lg">{currentItem?.releaseYear}</Badge> <Badge size="lg">{currentItem?.releaseYear}</Badge>
<Badge size="lg">{currentItem?.songCount} tracks</Badge> <Badge size="lg">{currentItem?.songCount} tracks</Badge>
</Group> </Group>
<Group position="apart"> <Group justify="space-between">
<Button <Button
size="lg" size="lg"
style={{ borderRadius: '5rem' }} style={{ borderRadius: '5rem' }}
@ -237,7 +239,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
{ postProcess: 'titleCase' }, { postProcess: 'titleCase' },
)} )}
</Button> </Button>
<Group spacing="sm"> <Group gap="sm">
<Button <Button
radius="lg" radius="lg"
size="sm" size="sm"

View File

@ -0,0 +1,149 @@
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
import { Flex as MantineFlex } from '@mantine/core';
import styled, { CSSProperties } from 'styled-components';
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
interface FlexProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
align?: CSSProperties['alignItems'];
children?: ReactNode;
columnGap?: CSSProperties['columnGap'];
direction?: CSSProperties['flexDirection'];
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
justify?: CSSProperties['justifyContent'];
rowGap?: CSSProperties['rowGap'];
wrap?: CSSProperties['flexWrap'];
}
export const Flex = forwardRef<HTMLDivElement, FlexProps>(
(
{ align, columnGap, direction, gap, justify, rowGap, wrap, children, ...props }: FlexProps,
ref,
) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return (
<StyledFlex
ref={ref}
align={align}
columnGap={columnGap}
direction={direction}
gap={gap}
justify={justify}
rowGap={rowGap}
wrap={wrap}
{...styleProps}
{...htmlProps}
>
{children}
</StyledFlex>
);
},
);
const StyledFlex = styled(MantineFlex)<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -9,7 +9,6 @@ import {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { Group, Stack } from '@mantine/core';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri'; import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
@ -25,6 +24,8 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
import { usePlayButtonBehavior } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store';
import { CardRoute, CardRow } from '/@/renderer/types'; import { CardRoute, CardRow } from '/@/renderer/types';
import { Group } from '/@/renderer/components/group';
import { Stack } from '/@/renderer/components/stack';
const getSlidesPerView = (windowWidth: number) => { const getSlidesPerView = (windowWidth: number) => {
if (windowWidth < 400) return 2; if (windowWidth < 400) return 2;
@ -53,7 +54,7 @@ interface TitleProps {
const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => { const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => {
return ( return (
<Group position="apart"> <Group justify="space-between">
{isValidElement(label) ? ( {isValidElement(label) ? (
label label
) : ( ) : (
@ -65,20 +66,18 @@ const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => {
</TextTitle> </TextTitle>
)} )}
<Group spacing="sm"> <Group gap="sm">
<Button <Button
compact
disabled={!pagination.hasPreviousPage} disabled={!pagination.hasPreviousPage}
size="lg" size="compact-lg"
variant="default" variant="default"
onClick={handlePrev} onClick={handlePrev}
> >
<RiArrowLeftSLine /> <RiArrowLeftSLine />
</Button> </Button>
<Button <Button
compact
disabled={!pagination.hasNextPage} disabled={!pagination.hasNextPage}
size="lg" size="compact-lg"
variant="default" variant="default"
onClick={handleNext} onClick={handleNext}
> >
@ -280,8 +279,7 @@ export const SwiperGridCarousel = ({
return ( return (
<CarouselContainer <CarouselContainer
ref={containerRef} ref={containerRef}
className="grid-carousel" gap="md"
spacing="md"
> >
{title ? ( {title ? (
<Title <Title

View File

@ -0,0 +1,147 @@
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
import { Group as MantineGroup } from '@mantine/core';
import styled, { CSSProperties } from 'styled-components';
import { StyledComponentsStyleProps, StyleProps } from '/@/renderer/components/shared';
interface GroupProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
align?: CSSProperties['alignItems'];
children?: ReactNode;
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
grow?: boolean;
justify?: CSSProperties['justifyContent'];
preventGrowOverflow?: boolean;
wrap?: CSSProperties['flexWrap'];
}
export const Group = forwardRef<HTMLDivElement, GroupProps>(
(
{ align, gap, grow, justify, preventGrowOverflow, wrap, children, ...props }: GroupProps,
ref,
) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return (
<StyledGroup
ref={ref}
align={align}
gap={gap}
grow={grow}
justify={justify}
preventGrowOverflow={preventGrowOverflow}
wrap={wrap}
{...styleProps}
{...htmlProps}
>
{children}
</StyledGroup>
);
},
);
const StyledGroup = styled(MantineGroup)<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -34,3 +34,11 @@ export * from './text';
export * from './text-title'; export * from './text-title';
export * from './toast'; export * from './toast';
export * from './tooltip'; export * from './tooltip';
export * from './group';
export * from './stack';
export * from './flex';
export * from './center';
export * from './divider';
export * from './portal';
export * from './box';
export * from './action-icon';

View File

@ -282,7 +282,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
ref={ref} ref={ref}
spellCheck={false} spellCheck={false}
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
> >
{children} {children}
</StyledTextInput> </StyledTextInput>
@ -298,7 +298,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
hideControls hideControls
spellCheck={false} spellCheck={false}
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
> >
{children} {children}
</StyledNumberInput> </StyledNumberInput>
@ -312,7 +312,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
<StyledPasswordInput <StyledPasswordInput
ref={ref} ref={ref}
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
> >
{children} {children}
</StyledPasswordInput> </StyledPasswordInput>
@ -326,12 +326,7 @@ export const FileInput = forwardRef<HTMLButtonElement, FileInputProps>(
<StyledFileInput <StyledFileInput
ref={ref} ref={ref}
{...props} {...props}
styles={{ style={{ maxWidth, width }}
placeholder: {
color: 'var(--input-placeholder-fg)',
},
}}
sx={{ maxWidth, width }}
> >
{children} {children}
</StyledFileInput> </StyledFileInput>
@ -345,7 +340,7 @@ export const JsonInput = forwardRef<HTMLTextAreaElement, JsonInputProps>(
<StyledJsonInput <StyledJsonInput
ref={ref} ref={ref}
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
> >
{children} {children}
</StyledJsonInput> </StyledJsonInput>
@ -359,7 +354,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
<StyledTextarea <StyledTextarea
ref={ref} ref={ref}
{...props} {...props}
sx={{ maxWidth, width }} style={{ maxWidth, width }}
> >
{children} {children}
</StyledTextarea> </StyledTextarea>

View File

@ -1,13 +1,10 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { import { ModalProps as MantineModalProps, Modal as MantineModal } from '@mantine/core';
ModalProps as MantineModalProps,
Stack,
Modal as MantineModal,
Flex,
Group,
} from '@mantine/core';
import { closeAllModals, ContextModalProps } from '@mantine/modals'; import { closeAllModals, ContextModalProps } from '@mantine/modals';
import { Button } from '/@/renderer/components/button'; import { Button } from '/@/renderer/components/button';
import { Flex } from '/@/renderer/components/flex';
import { Group } from '/@/renderer/components/group';
import { Stack } from '/@/renderer/components/stack';
export interface ModalProps extends Omit<MantineModalProps, 'onClose'> { export interface ModalProps extends Omit<MantineModalProps, 'onClose'> {
children?: ReactNode; children?: ReactNode;
@ -77,7 +74,7 @@ export const ConfirmModal = ({
return ( return (
<Stack> <Stack>
<Flex>{children}</Flex> <Flex>{children}</Flex>
<Group position="right"> <Group align="flex-end">
<Button <Button
data-focus data-focus
variant="default" variant="default"

View File

@ -1,7 +1,7 @@
import { Flex, Group, Stack } from '@mantine/core'; import { Flex, Group, Stack } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
export const MotionFlex = motion(Flex); export const MotionFlex = motion(Flex as any);
export const MotionGroup = motion(Group); export const MotionGroup = motion(Group);

View File

@ -1,4 +1,4 @@
import { Flex, FlexProps } from '@mantine/core'; import { FlexProps } from '@mantine/core';
import { AnimatePresence, motion, Variants } from 'framer-motion'; import { AnimatePresence, motion, Variants } from 'framer-motion';
import { ReactNode, useRef } from 'react'; import { ReactNode, useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@ -6,10 +6,11 @@ import { useShouldPadTitlebar, useTheme } from '/@/renderer/hooks';
import { useWindowSettings } from '/@/renderer/store/settings.store'; import { useWindowSettings } from '/@/renderer/store/settings.store';
import { Platform } from '/@/renderer/types'; import { Platform } from '/@/renderer/types';
const Container = styled(motion(Flex))<{ const Container = styled(motion.div)<{
$height?: string; $height?: string;
$position?: string; $position?: string;
}>` }>`
display: flex;
position: ${(props) => props.$position || 'relative'}; position: ${(props) => props.$position || 'relative'};
z-index: 200; z-index: 200;
width: 100%; width: 100%;
@ -98,7 +99,6 @@ export const PageHeader = ({
backgroundColor, backgroundColor,
isHidden, isHidden,
children, children,
...props
}: PageHeaderProps) => { }: PageHeaderProps) => {
const ref = useRef(null); const ref = useRef(null);
const padRight = useShouldPadTitlebar(); const padRight = useShouldPadTitlebar();
@ -111,7 +111,6 @@ export const PageHeader = ({
ref={ref} ref={ref}
$height={height} $height={height}
$position={position} $position={position}
{...props}
> >
<Header <Header
$isDraggable={windowBarStyle === Platform.WEB} $isDraggable={windowBarStyle === Platform.WEB}

View File

@ -0,0 +1,10 @@
import { HTMLAttributes, ReactNode } from 'react';
import { Portal as MantinePortal } from '@mantine/core';
interface PortalProps extends HTMLAttributes<HTMLDivElement> {
children: ReactNode;
}
export const Portal = ({ children, ...props }: PortalProps) => {
return <MantinePortal {...props}>{children}</MantinePortal>;
};

View File

@ -1,4 +1,3 @@
import { Group, Stack } from '@mantine/core';
import { Select } from '/@/renderer/components/select'; import { Select } from '/@/renderer/components/select';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { RiAddFill, RiAddLine, RiDeleteBinFill, RiMore2Line, RiRestartLine } from 'react-icons/ri'; import { RiAddFill, RiAddLine, RiDeleteBinFill, RiMore2Line, RiRestartLine } from 'react-icons/ri';
@ -7,6 +6,8 @@ import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option'; import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types'; import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { Group } from '/@/renderer/components/group';
import { Stack } from '/@/renderer/components/stack';
const FILTER_GROUP_OPTIONS_DATA = [ const FILTER_GROUP_OPTIONS_DATA = [
{ {
@ -98,10 +99,10 @@ export const QueryBuilder = ({
return ( return (
<Stack <Stack
ml={`${level * 10}px`} gap="sm"
spacing="sm" style={{ marginLeft: `${level * 10}px` }}
> >
<Group spacing="sm"> <Group gap="sm">
<Select <Select
data={FILTER_GROUP_OPTIONS_DATA} data={FILTER_GROUP_OPTIONS_DATA}
maxWidth={175} maxWidth={175}
@ -131,7 +132,7 @@ export const QueryBuilder = ({
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddFill />} leftSection={<RiAddFill />}
onClick={handleAddRuleGroup} onClick={handleAddRuleGroup}
> >
Add rule group Add rule group
@ -139,7 +140,7 @@ export const QueryBuilder = ({
{level > 0 && ( {level > 0 && (
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiDeleteBinFill />} leftSection={<RiDeleteBinFill />}
onClick={handleDeleteRuleGroup} onClick={handleDeleteRuleGroup}
> >
Remove rule group Remove rule group
@ -150,14 +151,14 @@ export const QueryBuilder = ({
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Item <DropdownMenu.Item
$danger $danger
icon={<RiRestartLine color="var(--danger-color)" />} leftSection={<RiRestartLine color="var(--danger-color)" />}
onClick={onResetFilters} onClick={onResetFilters}
> >
Reset to default Reset to default
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
$danger $danger
icon={<RiDeleteBinFill color="var(--danger-color)" />} leftSection={<RiDeleteBinFill color="var(--danger-color)" />}
onClick={onClearFilters} onClick={onClearFilters}
> >
Clear filters Clear filters

View File

@ -1,10 +1,10 @@
import { Group } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { RiSubtractLine } from 'react-icons/ri'; import { RiSubtractLine } from 'react-icons/ri';
import { Button } from '/@/renderer/components/button'; import { Button } from '/@/renderer/components/button';
import { NumberInput, TextInput } from '/@/renderer/components/input'; import { NumberInput, TextInput } from '/@/renderer/components/input';
import { Select } from '/@/renderer/components/select'; import { Select } from '/@/renderer/components/select';
import { QueryBuilderRule } from '/@/renderer/types'; import { QueryBuilderRule } from '/@/renderer/types';
import { Group } from '/@/renderer/components/group';
type DeleteArgs = { type DeleteArgs = {
groupIndex: number[]; groupIndex: number[];
@ -69,7 +69,7 @@ const QueryValueInput = ({ onChange, type, data, ...props }: any) => {
maxWidth={81} maxWidth={81}
width="10%" width="10%"
onChange={(e) => { onChange={(e) => {
const newRange = [e || 0, numberRange[1]]; const newRange = [Number(e) || 0, numberRange[1]];
setNumberRange(newRange); setNumberRange(newRange);
onChange(newRange); onChange(newRange);
}} }}
@ -80,7 +80,7 @@ const QueryValueInput = ({ onChange, type, data, ...props }: any) => {
maxWidth={81} maxWidth={81}
width="10%" width="10%"
onChange={(e) => { onChange={(e) => {
const newRange = [numberRange[0], e || 0]; const newRange = [numberRange[0], Number(e) || 0];
setNumberRange(newRange); setNumberRange(newRange);
onChange(newRange); onChange(newRange);
}} }}
@ -188,8 +188,8 @@ export const QueryBuilderOption = ({
return ( return (
<Group <Group
ml={ml} gap="sm"
spacing="sm" style={{ marginLeft: `${ml}px` }}
> >
<Select <Select
searchable searchable

View File

@ -1,10 +1,11 @@
import { ChangeEvent, KeyboardEvent } from 'react'; import { ChangeEvent, KeyboardEvent } from 'react';
import { ActionIcon, TextInputProps } from '@mantine/core'; import { TextInputProps } from '@mantine/core';
import { useFocusWithin, useHotkeys, useMergedRef } from '@mantine/hooks'; import { useFocusWithin, useHotkeys, useMergedRef } from '@mantine/hooks';
import { RiCloseFill, RiSearchLine } from 'react-icons/ri'; import { RiCloseFill, RiSearchLine } from 'react-icons/ri';
import { TextInput } from '/@/renderer/components/input'; import { TextInput } from '/@/renderer/components/input';
import { useSettingsStore } from '/@/renderer/store'; import { useSettingsStore } from '/@/renderer/store';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { ActionIcon } from '/@/renderer/components/action-icon';
interface SearchInputProps extends TextInputProps { interface SearchInputProps extends TextInputProps {
initialWidth?: number; initialWidth?: number;
@ -39,7 +40,7 @@ export const SearchInput = ({
<TextInput <TextInput
ref={mergedRef} ref={mergedRef}
{...props} {...props}
icon={showIcon && <RiSearchLine />} leftSection={showIcon && <RiSearchLine />}
rightSection={ rightSection={
isOpened ? ( isOpened ? (
<ActionIcon <ActionIcon
@ -55,13 +56,13 @@ export const SearchInput = ({
} }
size="md" size="md"
styles={{ styles={{
icon: { svg: { fill: 'var(--titlebar-fg)' } },
input: { input: {
backgroundColor: isOpened ? 'inherit' : 'transparent !important', backgroundColor: isOpened ? 'inherit' : 'transparent !important',
border: 'none !important', border: 'none !important',
cursor: isOpened ? 'text' : 'pointer', cursor: isOpened ? 'text' : 'pointer',
padding: isOpened ? '10px' : 0, padding: isOpened ? '10px' : 0,
}, },
section: { svg: { fill: 'var(--titlebar-fg)' }, userSelect: 'none' },
}} }}
width={isOpened ? openedWidth || 150 : initialWidth || 35} width={isOpened ? openedWidth || 150 : initialWidth || 35}
onChange={onChange} onChange={onChange}

View File

@ -40,7 +40,11 @@ const StyledSelect = styled(MantineSelect)`
export const Select = ({ width, maxWidth, ...props }: SelectProps) => { export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
return ( return (
<StyledSelect <StyledSelect
withinPortal comboboxProps={{
transitionProps: { duration: 100, transition: 'fade' },
withinPortal: true,
}}
style={{ maxWidth, width }}
styles={{ styles={{
dropdown: { dropdown: {
background: 'var(--dropdown-menu-bg)', background: 'var(--dropdown-menu-bg)',
@ -50,14 +54,14 @@ export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
background: 'var(--input-bg)', background: 'var(--input-bg)',
color: 'var(--input-fg)', color: 'var(--input-fg)',
}, },
item: { option: {
'&:hover': { '&:hover': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
'&[data-hovered]': { '&[dataHovered]': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
'&[data-selected="true"]': { '&[dataSelected="true"]': {
'&:hover': { '&:hover': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
@ -68,8 +72,6 @@ export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
padding: '.3rem', padding: '.3rem',
}, },
}} }}
sx={{ maxWidth, width }}
transitionProps={{ duration: 100, transition: 'fade' }}
{...props} {...props}
/> />
); );
@ -95,7 +97,11 @@ const StyledMultiSelect = styled(MantineMultiSelect)`
export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) => { export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) => {
return ( return (
<StyledMultiSelect <StyledMultiSelect
withinPortal comboboxProps={{
transitionProps: { duration: 100, transition: 'fade' },
withinPortal: true,
}}
style={{ maxWidth, width }}
styles={{ styles={{
dropdown: { dropdown: {
background: 'var(--dropdown-menu-bg)', background: 'var(--dropdown-menu-bg)',
@ -105,14 +111,14 @@ export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) =>
background: 'var(--input-bg)', background: 'var(--input-bg)',
color: 'var(--input-fg)', color: 'var(--input-fg)',
}, },
item: { option: {
'&:hover': { '&:hover': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
'&[data-hovered]': { '&[dataHovered]': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
'&[data-selected="true"]': { '&[dataSelected="true"]': {
'&:hover': { '&:hover': {
background: 'var(--dropdown-menu-bg-hover)', background: 'var(--dropdown-menu-bg-hover)',
}, },
@ -122,15 +128,13 @@ export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) =>
color: 'var(--dropdown-menu-fg)', color: 'var(--dropdown-menu-fg)',
padding: '.5rem .1rem', padding: '.5rem .1rem',
}, },
value: { pill: {
margin: '.2rem', margin: '.2rem',
paddingBottom: '1rem', paddingBottom: '1rem',
paddingLeft: '1rem', paddingLeft: '1rem',
paddingTop: '1rem', paddingTop: '1rem',
}, },
}} }}
sx={{ maxWidth, width }}
transitionProps={{ duration: 100, transition: 'fade' }}
{...props} {...props}
/> />
); );

View File

@ -0,0 +1,114 @@
import { CSSProperties } from 'react';
import styled from 'styled-components';
export type StyleProps = {
bg?: CSSProperties['backgroundColor'];
bottom?: CSSProperties['bottom'];
display?: CSSProperties['display'];
fw?: CSSProperties['fontWeight'];
h?: CSSProperties['height'];
left?: CSSProperties['left'];
lh?: CSSProperties['lineHeight'];
lts?: CSSProperties['letterSpacing'];
m?: CSSProperties['margin'];
mah?: CSSProperties['maxHeight'];
maw?: CSSProperties['maxWidth'];
mb?: CSSProperties['marginBottom'];
mih?: CSSProperties['minHeight'];
miw?: CSSProperties['minWidth'];
ml?: CSSProperties['marginLeft'];
mr?: CSSProperties['marginRight'];
mt?: CSSProperties['marginTop'];
mx?: CSSProperties['marginLeft'] | CSSProperties['marginRight'];
my?: CSSProperties['marginTop'] | CSSProperties['marginBottom'];
p?: CSSProperties['padding'];
pb?: CSSProperties['paddingBottom'];
pl?: CSSProperties['paddingLeft'];
pos?: CSSProperties['position'];
pr?: CSSProperties['paddingRight'];
pt?: CSSProperties['paddingTop'];
px?: CSSProperties['paddingLeft'] | CSSProperties['paddingRight'];
py?: CSSProperties['paddingTop'] | CSSProperties['paddingBottom'];
right?: CSSProperties['right'];
ta?: CSSProperties['textAlign'];
td?: CSSProperties['textDecoration'];
top?: CSSProperties['top'];
tt?: CSSProperties['textTransform'];
w?: CSSProperties['width'];
};
export type StyledComponentsStyleProps = {
$bg?: CSSProperties['backgroundColor'];
$bottom?: CSSProperties['bottom'];
$display?: CSSProperties['display'];
$fw?: CSSProperties['fontWeight'];
$h?: CSSProperties['height'];
$left?: CSSProperties['left'];
$lh?: CSSProperties['lineHeight'];
$lts?: CSSProperties['letterSpacing'];
$m?: CSSProperties['margin'];
$mah?: CSSProperties['maxHeight'];
$maw?: CSSProperties['maxWidth'];
$mb?: CSSProperties['marginBottom'];
$mih?: CSSProperties['minHeight'];
$miw?: CSSProperties['minWidth'];
$ml?: CSSProperties['marginLeft'];
$mr?: CSSProperties['marginRight'];
$mt?: CSSProperties['marginTop'];
$mx?: CSSProperties['marginLeft'] | CSSProperties['marginRight'];
$my?: CSSProperties['marginTop'] | CSSProperties['marginBottom'];
$p?: CSSProperties['padding'];
$pb?: CSSProperties['paddingBottom'];
$pl?: CSSProperties['paddingLeft'];
$pos?: CSSProperties['position'];
$pr?: CSSProperties['paddingRight'];
$pt?: CSSProperties['paddingTop'];
$px?: CSSProperties['paddingLeft'] | CSSProperties['paddingRight'];
$py?: CSSProperties['paddingTop'] | CSSProperties['paddingBottom'];
$right?: CSSProperties['right'];
$ta?: CSSProperties['textAlign'];
$td?: CSSProperties['textDecoration'];
$top?: CSSProperties['top'];
$tt?: CSSProperties['textTransform'];
$w?: CSSProperties['width'];
};
export const DivWithStyleProps = styled.div<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lh && `line-height: ${props.$lh};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -1,8 +1,8 @@
import { Center } from '@mantine/core';
import type { IconType } from 'react-icons'; import type { IconType } from 'react-icons';
import { RiLoader5Fill } from 'react-icons/ri'; import { RiLoader5Fill } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
import { rotating } from '/@/renderer/styles'; import { rotating } from '/@/renderer/styles';
import { Center } from '/@/renderer/components/center';
interface SpinnerProps extends IconType { interface SpinnerProps extends IconType {
color?: string; color?: string;
@ -18,21 +18,23 @@ export const SpinnerIcon = styled(RiLoader5Fill)`
export const Spinner = ({ ...props }: SpinnerProps) => { export const Spinner = ({ ...props }: SpinnerProps) => {
if (props.container) { if (props.container) {
return ( return (
<Center <SpinnerContainer>
h="100%"
w="100%"
>
<SpinnerIcon <SpinnerIcon
color={props.color} color={props.color}
size={props.size} size={props.size}
/> />
</Center> </SpinnerContainer>
); );
} }
return <SpinnerIcon {...props} />; return <SpinnerIcon {...props} />;
}; };
const SpinnerContainer = styled(Center)`
height: 100%;
width: 100%;
`;
Spinner.defaultProps = { Spinner.defaultProps = {
color: undefined, color: undefined,
size: 15, size: 15,

View File

@ -0,0 +1,138 @@
import { forwardRef, HTMLAttributes, ReactNode, CSSProperties } from 'react';
import { Stack as MantineStack } from '@mantine/core';
import styled from 'styled-components';
import { StyleProps, StyledComponentsStyleProps } from '../shared';
interface StackProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
align?: CSSProperties['alignItems'];
children?: ReactNode;
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
justify?: CSSProperties['justifyContent'];
}
export const Stack = forwardRef<HTMLDivElement, StackProps>(
({ align, gap, justify, children, ...props }: StackProps, ref) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return (
<StyledStack
ref={ref}
align={align}
gap={gap}
justify={justify}
{...styleProps}
{...htmlProps}
>
{children}
</StyledStack>
);
},
);
const StyledStack = styled(MantineStack)<StyledComponentsStyleProps>`
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`;

View File

@ -1,22 +1,21 @@
import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { TitleProps as MantineTitleProps } from '@mantine/core';
import { createPolymorphicComponent, Title as MantineHeader } from '@mantine/core'; import { createPolymorphicComponent, Title as MantineHeader } from '@mantine/core';
import styled from 'styled-components'; import styled from 'styled-components';
import { textEllipsis } from '/@/renderer/styles'; import { textEllipsis } from '/@/renderer/styles';
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
type MantineTextTitleDivProps = MantineTitleProps & ComponentPropsWithoutRef<'div'>; export interface TextTitleProps extends StyleProps {
interface TextTitleProps extends MantineTextTitleDivProps {
$link?: boolean; $link?: boolean;
$noSelect?: boolean; $noSelect?: boolean;
$secondary?: boolean; $secondary?: boolean;
children?: ReactNode; children?: ReactNode;
order?: number;
overflow?: 'hidden' | 'visible'; overflow?: 'hidden' | 'visible';
to?: string; to?: string;
weight?: number; weight?: number;
} }
const StyledTextTitle = styled(MantineHeader)<TextTitleProps>` const StyledTextTitle = styled(MantineHeader)<TextTitleProps & StyledComponentsStyleProps>`
overflow: ${(props) => props.overflow}; overflow: ${(props) => props.overflow};
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')}; color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
cursor: ${(props) => props.$link && 'cursor'}; cursor: ${(props) => props.$link && 'cursor'};
@ -28,15 +27,132 @@ const StyledTextTitle = styled(MantineHeader)<TextTitleProps>`
color: ${(props) => props.$link && 'var(--main-fg)'}; color: ${(props) => props.$link && 'var(--main-fg)'};
text-decoration: ${(props) => (props.$link ? 'underline' : 'none')}; text-decoration: ${(props) => (props.$link ? 'underline' : 'none')};
} }
${(props) => props.$bg && `background-color: ${props.$bg};`}
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
${(props) => props.$display && `display: ${props.$display};`}
${(props) => props.$fw && `font-weight: ${props.$fw};`}
${(props) => props.$h && `height: ${props.$h};`}
${(props) => props.$left && `left: ${props.$left};`}
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
${(props) => props.$m && `margin: ${props.$m};`}
${(props) => props.$mah && `max-height: ${props.$mah};`}
${(props) => props.$maw && `max-width: ${props.$maw};`}
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
${(props) => props.$mih && `min-height: ${props.$mih};`}
${(props) => props.$miw && `min-width: ${props.$miw};`}
${(props) => props.$ml && `margin-left: ${props.$ml};`}
${(props) => props.$mr && `margin-right: ${props.$mr};`}
${(props) => props.$mt && `margin-top: ${props.$mt};`}
${(props) => props.$mx && `margin-left: ${props.$mx};`}
${(props) => props.$mx && `margin-right: ${props.$mx};`}
${(props) => props.$my && `margin-top: ${props.$my};`}
${(props) => props.$my && `margin-bottom: ${props.$my};`}
${(props) => props.$p && `padding: ${props.$p};`}
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
${(props) => props.$pl && `padding-left: ${props.$pl};`}
${(props) => props.$pos && `position: ${props.$pos};`}
${(props) => props.$pr && `padding-right: ${props.$pr};`}
${(props) => props.$pt && `padding-top: ${props.$pt};`}
${(props) => props.$px && `padding-left: ${props.$px};`}
${(props) => props.$px && `padding-right: ${props.$px};`}
${(props) => props.$py && `padding-top: ${props.$py};`}
${(props) => props.$py && `padding-bottom: ${props.$py};`}
${(props) => props.$right && `right: ${props.$right};`}
${(props) => props.$ta && `text-align: ${props.$ta};`}
${(props) => props.$td && `text-decoration: ${props.$td};`}
${(props) => props.$top && `top: ${props.$top};`}
${(props) => props.$tt && `text-transform: ${props.$tt};`}
${(props) => props.$w && `width: ${props.$w};`}
`; `;
const _TextTitle = ({ children, $secondary, overflow, $noSelect, ...rest }: TextTitleProps) => { const _TextTitle = ({
children,
order,
$secondary,
overflow,
$noSelect,
...props
}: TextTitleProps) => {
const {
bg,
bottom,
display,
fw,
h,
left,
lts,
m,
mah,
maw,
mb,
mih,
miw,
ml,
mr,
mt,
mx,
my,
p,
pb,
pl,
pos,
pr,
pt,
px,
py,
right,
ta,
td,
top,
tt,
w,
...htmlProps
} = props;
const styleProps = {
$bg: bg,
$bottom: bottom,
$display: display,
$fw: fw,
$h: h,
$left: left,
$lts: lts,
$m: m,
$mah: mah,
$maw: maw,
$mb: mb,
$mih: mih,
$miw: miw,
$ml: ml,
$mr: mr,
$mt: mt,
$mx: mx,
$my: my,
$p: p,
$pb: pb,
$pl: pl,
$pos: pos,
$pr: pr,
$pt: pt,
$px: px,
$py: py,
$right: right,
$ta: ta,
$td: td,
$top: top,
$tt: tt,
$w: w,
};
return ( return (
<StyledTextTitle <StyledTextTitle
$noSelect={$noSelect} $noSelect={$noSelect}
$secondary={$secondary} $secondary={$secondary}
order={order}
overflow={overflow} overflow={overflow}
{...rest} {...styleProps}
{...htmlProps}
> >
{children} {children}
</StyledTextTitle> </StyledTextTitle>

View File

@ -8,6 +8,7 @@ import { textEllipsis } from '/@/renderer/styles';
type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>; type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>;
interface TextProps extends MantineTextDivProps { interface TextProps extends MantineTextDivProps {
$align?: 'left' | 'center' | 'right' | 'justify';
$link?: boolean; $link?: boolean;
$noSelect?: boolean; $noSelect?: boolean;
$secondary?: boolean; $secondary?: boolean;
@ -24,6 +25,7 @@ const StyledText = styled(MantineText)<TextProps>`
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')}; color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
cursor: ${(props) => props.$link && 'cursor'}; cursor: ${(props) => props.$link && 'cursor'};
user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')}; user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')};
text-align: ${(props) => props.$align};
${(props) => props.overflow === 'hidden' && !props.lineClamp && textEllipsis} ${(props) => props.overflow === 'hidden' && !props.lineClamp && textEllipsis}
&:hover { &:hover {
@ -32,9 +34,18 @@ const StyledText = styled(MantineText)<TextProps>`
} }
`; `;
export const _Text = ({ children, $secondary, overflow, font, $noSelect, ...rest }: TextProps) => { export const _Text = ({
children,
$secondary,
overflow,
font,
$noSelect,
$align,
...rest
}: TextProps) => {
return ( return (
<StyledText <StyledText
$align={$align}
$noSelect={$noSelect} $noSelect={$noSelect}
$secondary={$secondary} $secondary={$secondary}
font={font} font={font}

View File

@ -1,4 +1,3 @@
import type { NotificationProps as MantineNotificationProps } from '@mantine/notifications';
import { import {
showNotification, showNotification,
updateNotification, updateNotification,
@ -7,7 +6,13 @@ import {
cleanNotificationsQueue, cleanNotificationsQueue,
} from '@mantine/notifications'; } from '@mantine/notifications';
interface NotificationProps extends MantineNotificationProps { interface NotificationProps {
autoClose?: boolean | number;
id?: string;
message: string;
onClick?: (e: any) => void;
onClose?: (e: any) => void;
title?: string;
type?: 'success' | 'error' | 'warning' | 'info'; type?: 'success' | 'error' | 'warning' | 'info';
} }

View File

@ -0,0 +1,13 @@
export const Center = ({ children }: { children: React.ReactNode }) => {
return (
<div
style={{
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
}}
>
{children}
</div>
);
};

View File

@ -1,4 +1,3 @@
import { Center, Stack } from '@mantine/core';
import { RiAlbumFill, RiUserVoiceFill, RiPlayListFill } from 'react-icons/ri'; import { RiAlbumFill, RiUserVoiceFill, RiPlayListFill } from 'react-icons/ri';
import { generatePath, useNavigate } from 'react-router-dom'; import { generatePath, useNavigate } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img'; import { SimpleImg } from 'react-simple-img';
@ -9,6 +8,8 @@ import { CardRows } from '/@/renderer/components/card';
import { Skeleton } from '/@/renderer/components/skeleton'; import { Skeleton } from '/@/renderer/components/skeleton';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types'; import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
import { Center } from '/@/renderer/components/center';
import { Stack } from '/@/renderer/components/stack';
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
@ -186,7 +187,7 @@ export const DefaultCard = ({
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: '100%', height: '100%',
@ -233,7 +234,7 @@ export const DefaultCard = ({
/> />
</ImageContainer> </ImageContainer>
<DetailContainer> <DetailContainer>
<Stack spacing="sm"> <Stack gap="sm">
{controls.cardRows.map((row, index) => ( {controls.cardRows.map((row, index) => (
<Skeleton <Skeleton
key={`${index}-${columnIndex}-${row.arrayProperty}`} key={`${index}-${columnIndex}-${row.arrayProperty}`}

View File

@ -1,4 +1,3 @@
import { Center, Stack } from '@mantine/core';
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri'; import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
import { generatePath, useNavigate } from 'react-router-dom'; import { generatePath, useNavigate } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img'; import { SimpleImg } from 'react-simple-img';
@ -9,6 +8,8 @@ import { CardRows } from '/@/renderer/components/card';
import { Skeleton } from '/@/renderer/components/skeleton'; import { Skeleton } from '/@/renderer/components/skeleton';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types'; import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
import { Center } from '/@/renderer/components/center';
import { Stack } from '/@/renderer/components/stack';
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
@ -173,7 +174,7 @@ export const PosterCard = ({
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: '100%', height: '100%',
@ -218,7 +219,7 @@ export const PosterCard = ({
<ImageContainer /> <ImageContainer />
</Skeleton> </Skeleton>
<DetailContainer> <DetailContainer>
<Stack spacing="sm"> <Stack gap="sm">
{controls.cardRows.map((row, index) => ( {controls.cardRows.map((row, index) => (
<Skeleton <Skeleton
key={`${index}-${columnIndex}-${row.arrayProperty}`} key={`${index}-${columnIndex}-${row.arrayProperty}`}

View File

@ -7,7 +7,7 @@ export const ActionsCell = ({ context, api }: ICellRendererParams) => {
return ( return (
<CellContainer $position="center"> <CellContainer $position="center">
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View File

@ -1,6 +1,5 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { ICellRendererParams } from '@ag-grid-community/core'; import type { ICellRendererParams } from '@ag-grid-community/core';
import { Center } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { RiAlbumFill } from 'react-icons/ri'; import { RiAlbumFill } from 'react-icons/ri';
import { generatePath } from 'react-router'; import { generatePath } from 'react-router';
@ -12,6 +11,7 @@ import { Text } from '/@/renderer/components/text';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { Skeleton } from '/@/renderer/components/skeleton'; import { Skeleton } from '/@/renderer/components/skeleton';
import { SEPARATOR_STRING } from '/@/renderer/api/utils'; import { SEPARATOR_STRING } from '/@/renderer/api/utils';
import { Center } from '/@/renderer/components/center';
const CellContainer = styled(motion.div)<{ height: number }>` const CellContainer = styled(motion.div)<{ height: number }>`
display: grid; display: grid;
@ -89,7 +89,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: `${(node.rowHeight || 40) - 10}px`, height: `${(node.rowHeight || 40) - 10}px`,
@ -127,7 +127,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
component={Link} component={Link}
overflow="hidden" overflow="hidden"
size="md" size="md"
sx={{ width: 'fit-content' }} style={{ width: 'fit-content' }}
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: artist.id, albumArtistId: artist.id,
})} })}
@ -139,7 +139,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
$secondary $secondary
overflow="hidden" overflow="hidden"
size="md" size="md"
sx={{ width: 'fit-content' }} style={{ width: 'fit-content' }}
> >
{artist.name} {artist.name}
</Text> </Text>

View File

@ -48,8 +48,8 @@ export const FavoriteCell = ({ value, data, node }: ICellRendererParams) => {
return ( return (
<CellContainer $position="center"> <CellContainer $position="center">
<Button <Button
compact size="compact-md"
sx={{ style={{
svg: { svg: {
fill: !value fill: !value
? 'var(--main-fg-secondary) !important' ? 'var(--main-fg-secondary) !important'

View File

@ -1,18 +1,11 @@
import { useState } from 'react'; import { useState } from 'react';
import { ICellRendererParams } from '@ag-grid-community/core'; import { ICellRendererParams } from '@ag-grid-community/core';
import { Group } from '@mantine/core';
import { RiCheckboxBlankLine, RiCheckboxLine } from 'react-icons/ri'; import { RiCheckboxBlankLine, RiCheckboxLine } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
import { Button } from '/@/renderer/components/button'; import { Button } from '/@/renderer/components/button';
import { Paper } from '/@/renderer/components/paper'; import { Paper } from '/@/renderer/components/paper';
import { getNodesByDiscNumber, setNodeSelection } from '../utils'; import { getNodesByDiscNumber, setNodeSelection } from '../utils';
import { Group } from '/@/renderer/components/group';
const Container = styled(Paper)`
display: flex;
height: 100%;
padding: 0.5rem 1rem;
border: 1px solid transparent;
`;
export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => { export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => {
const [isSelected, setIsSelected] = useState(false); const [isSelected, setIsSelected] = useState(false);
@ -28,20 +21,27 @@ export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => {
return ( return (
<Container> <Container>
<Group <ButtonContainer>
position="apart"
w="100%"
>
<Button <Button
compact leftSection={isSelected ? <RiCheckboxLine /> : <RiCheckboxBlankLine />}
leftIcon={isSelected ? <RiCheckboxLine /> : <RiCheckboxBlankLine />} size="compact-md"
size="md"
variant="subtle" variant="subtle"
onClick={handleToggleDiscNodes} onClick={handleToggleDiscNodes}
> >
{data.name} {data.name}
</Button> </Button>
</Group> </ButtonContainer>
</Container> </Container>
); );
}; };
const Container = styled(Paper)`
display: flex;
height: 100%;
padding: 0.5rem 1rem;
border: 1px solid transparent;
`;
const ButtonContainer = styled(Group)`
width: 100%;
`;

View File

@ -149,7 +149,7 @@ export const RowIndexCell = ({ value, eGridCell }: ICellRendererParams) => {
) : null)} ) : null)}
<Text <Text
$secondary $secondary
align="right" $align="right"
className="current-song-child current-song-index" className="current-song-child current-song-index"
overflow="hidden" overflow="hidden"
size="md" size="md"

View File

@ -288,7 +288,7 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const tableConfig = useSettingsStore((state) => state.tables); const tableConfig = useSettingsStore((state) => state.tables);
const handleAddOrRemoveColumns = (values: TableColumn[]) => { const handleAddOrRemoveColumns = (values: TableColumn[] | any[]) => {
const existingColumns = tableConfig[type].columns; const existingColumns = tableConfig[type].columns;
if (values.length === 0) { if (values.length === 0) {
@ -409,9 +409,11 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
<Option.Control> <Option.Control>
<MultiSelect <MultiSelect
clearable clearable
comboboxProps={{
position: 'bottom',
}}
data={SONG_TABLE_COLUMNS} data={SONG_TABLE_COLUMNS}
defaultValue={tableConfig[type]?.columns.map((column) => column.column)} defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
dropdownPosition="bottom"
width={300} width={300}
onChange={handleAddOrRemoveColumns} onChange={handleAddOrRemoveColumns}
/> />

View File

@ -1,6 +1,5 @@
import { MutableRefObject } from 'react'; import { MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Group } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -14,6 +13,7 @@ import { Text } from '/@/renderer/components/text';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { TablePagination as TablePaginationType } from '/@/renderer/types'; import { TablePagination as TablePaginationType } from '/@/renderer/types';
import { ListKey } from '/@/renderer/store'; import { ListKey } from '/@/renderer/store';
import { Group } from '/@/renderer/components/group';
interface TablePaginationProps { interface TablePaginationProps {
pageKey: ListKey; pageKey: ListKey;
@ -72,13 +72,15 @@ export const TablePagination = ({
<MotionFlex <MotionFlex
ref={containerQuery.ref} ref={containerQuery.ref}
layout layout
align="center"
animate={{ y: 0 }} animate={{ y: 0 }}
exit={{ y: 50 }} exit={{ y: 50 }}
initial={{ y: 50 }} initial={{ y: 50 }}
justify="space-between" style={{
p="1rem" alignContent: 'center',
sx={{ borderTop: '1px solid var(--generic-border-color)' }} borderTop: '1px solid var(--generic-border-color)',
justifyContent: 'space-between',
padding: '1rem',
}}
> >
<Text <Text
$secondary $secondary
@ -103,8 +105,8 @@ export const TablePagination = ({
</Text> </Text>
<Group <Group
ref={containerQuery.ref} ref={containerQuery.ref}
noWrap gap="sm"
spacing="sm" wrap="nowrap"
> >
<Popover <Popover
trapFocus trapFocus
@ -116,7 +118,7 @@ export const TablePagination = ({
<Button <Button
radius="sm" radius="sm"
size="sm" size="sm"
sx={{ height: '26px', padding: '0', width: '26px' }} style={{ height: '26px', padding: '0', width: '26px' }}
tooltip={{ tooltip={{
label: t('action.goToPage', { postProcess: 'sentenceCase' }), label: t('action.goToPage', { postProcess: 'sentenceCase' }),
}} }}
@ -147,7 +149,6 @@ export const TablePagination = ({
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>
<Pagination <Pagination
noWrap
$hideDividers={!containerQuery.isSm} $hideDividers={!containerQuery.isSm}
boundaries={1} boundaries={1}
radius="sm" radius="sm"

View File

@ -1,6 +1,5 @@
import { Stack, Group } from '@mantine/core';
import { RiAlertFill } from 'react-icons/ri'; import { RiAlertFill } from 'react-icons/ri';
import { Text } from '/@/renderer/components'; import { Group, Stack, Text } from '/@/renderer/components';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
interface ActionRequiredContainerProps { interface ActionRequiredContainerProps {
@ -9,7 +8,7 @@ interface ActionRequiredContainerProps {
} }
export const ActionRequiredContainer = ({ title, children }: ActionRequiredContainerProps) => ( export const ActionRequiredContainer = ({ title, children }: ActionRequiredContainerProps) => (
<Stack sx={{ cursor: 'default', maxWidth: '700px' }}> <Stack style={{ cursor: 'default', maxWidth: '700px' }}>
<Group> <Group>
<RiAlertFill <RiAlertFill
color="var(--warning-color)" color="var(--warning-color)"
@ -17,7 +16,7 @@ export const ActionRequiredContainer = ({ title, children }: ActionRequiredConta
/> />
<Text <Text
size="xl" size="xl"
sx={{ textTransform: 'uppercase' }} style={{ textTransform: 'uppercase' }}
> >
{title} {title}
</Text> </Text>

View File

@ -1,22 +1,17 @@
import { Box, Center, Group, Stack } from '@mantine/core';
import type { FallbackProps } from 'react-error-boundary'; import type { FallbackProps } from 'react-error-boundary';
import { RiErrorWarningLine } from 'react-icons/ri'; import { RiErrorWarningLine } from 'react-icons/ri';
import { useRouteError } from 'react-router'; import { useRouteError } from 'react-router';
import styled from 'styled-components'; import styled from 'styled-components';
import { Button, Text } from '/@/renderer/components'; import { Button, Center, Group, Stack, Text } from '/@/renderer/components';
const Container = styled(Box)`
background: var(--main-bg);
`;
export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => { export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
const error = useRouteError() as any; const error = useRouteError() as any;
return ( return (
<Container> <Container>
<Center sx={{ height: '100vh' }}> <Center style={{ height: '100vh' }}>
<Stack sx={{ maxWidth: '50%' }}> <Stack style={{ maxWidth: '50%' }}>
<Group spacing="xs"> <Group gap="xs">
<RiErrorWarningLine <RiErrorWarningLine
color="var(--danger-color)" color="var(--danger-color)"
size={30} size={30}
@ -35,3 +30,7 @@ export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
</Container> </Container>
); );
}; };
const Container = styled.div`
background: var(--main-bg);
`;

View File

@ -14,7 +14,8 @@ export const MpvRequired = () => {
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const handleSetMpvPath = (e: File) => { const handleSetMpvPath = (e: File | null) => {
if (!e) return;
localSettings?.set('mpv_path', e.path); localSettings?.set('mpv_path', e.path);
}; };

View File

@ -1,9 +1,9 @@
import { Center, Stack, Group, Divider, Box } from '@mantine/core';
import { RiArrowLeftSLine, RiErrorWarningLine, RiHome4Line, RiMenuFill } from 'react-icons/ri'; import { RiArrowLeftSLine, RiErrorWarningLine, RiHome4Line, RiMenuFill } from 'react-icons/ri';
import { useNavigate, useRouteError } from 'react-router'; import { useNavigate, useRouteError } from 'react-router';
import { Button, DropdownMenu, Text } from '/@/renderer/components'; import { Button, Center, Divider, DropdownMenu, Group, Stack, Text } from '/@/renderer/components';
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu'; import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import styled from 'styled-components';
const RouteErrorBoundary = () => { const RouteErrorBoundary = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -23,9 +23,9 @@ const RouteErrorBoundary = () => {
}; };
return ( return (
<Box bg="var(--main-bg)"> <Container>
<Center sx={{ height: '100vh' }}> <Center style={{ height: '100vh' }}>
<Stack sx={{ maxWidth: '50%' }}> <Stack style={{ maxWidth: '50%' }}>
<Group> <Group>
<Button <Button
px={10} px={10}
@ -40,16 +40,16 @@ const RouteErrorBoundary = () => {
/> />
<Text size="lg">Something went wrong</Text> <Text size="lg">Something went wrong</Text>
</Group> </Group>
<Divider my={5} /> <Divider marginY={5} />
<Text size="sm">{error?.message}</Text> <Text size="sm">{error?.message}</Text>
<Group <Group
grow grow
spacing="sm" gap="sm"
> >
<Button <Button
leftIcon={<RiHome4Line />} leftSection={<RiHome4Line />}
size="md" size="md"
sx={{ flex: 0.5 }} style={{ flex: 0.5 }}
variant="default" variant="default"
onClick={handleHome} onClick={handleHome}
> >
@ -58,9 +58,9 @@ const RouteErrorBoundary = () => {
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
leftIcon={<RiMenuFill />} leftSection={<RiMenuFill />}
size="md" size="md"
sx={{ flex: 0.5 }} style={{ flex: 0.5 }}
variant="default" variant="default"
> >
Menu Menu
@ -82,8 +82,12 @@ const RouteErrorBoundary = () => {
</Group> </Group>
</Stack> </Stack>
</Center> </Center>
</Box> </Container>
); );
}; };
export default RouteErrorBoundary; export default RouteErrorBoundary;
const Container = styled.div`
background: var(--main-bg);
`;

View File

@ -9,7 +9,7 @@ export const ServerRequired = () => {
<DropdownMenu> <DropdownMenu>
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
leftIcon={<RiMenuFill />} leftSection={<RiMenuFill />}
variant="filled" variant="filled"
> >
Open menu Open menu

View File

@ -1,8 +1,7 @@
import { Center, Group, Stack } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RiCheckFill, RiEdit2Line, RiHome4Line } from 'react-icons/ri'; import { RiCheckFill, RiEdit2Line, RiHome4Line } from 'react-icons/ri';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button, PageHeader, Text } from '/@/renderer/components'; import { Button, Center, Group, PageHeader, Stack, Text } from '/@/renderer/components';
import { ActionRequiredContainer } from '/@/renderer/features/action-required/components/action-required-container'; import { ActionRequiredContainer } from '/@/renderer/features/action-required/components/action-required-container';
import { ServerCredentialRequired } from '/@/renderer/features/action-required/components/server-credential-required'; import { ServerCredentialRequired } from '/@/renderer/features/action-required/components/server-credential-required';
import { ServerRequired } from '/@/renderer/features/action-required/components/server-required'; import { ServerRequired } from '/@/renderer/features/action-required/components/server-required';
@ -44,24 +43,24 @@ const ActionRequiredRoute = () => {
return ( return (
<AnimatedPage> <AnimatedPage>
<PageHeader /> <PageHeader />
<Center sx={{ height: '100%', width: '100vw' }}> <Center style={{ height: '100%', width: '100vw' }}>
<Stack <Stack
spacing="xl" gap="xl"
sx={{ maxWidth: '50%' }} style={{ maxWidth: '50%' }}
> >
<Group noWrap> <Group wrap="nowrap">
{displayedCheck && ( {displayedCheck && (
<ActionRequiredContainer title={displayedCheck.title}> <ActionRequiredContainer title={displayedCheck.title}>
{displayedCheck?.component} {displayedCheck?.component}
</ActionRequiredContainer> </ActionRequiredContainer>
)} )}
</Group> </Group>
<Stack mt="2rem"> <Stack style={{ marginTop: '2rem' }}>
{canReturnHome && ( {canReturnHome && (
<> <>
<Group <Group
noWrap justify="center"
position="center" wrap="nowrap"
> >
<RiCheckFill <RiCheckFill
color="var(--success-color)" color="var(--success-color)"
@ -72,7 +71,7 @@ const ActionRequiredRoute = () => {
<Button <Button
component={Link} component={Link}
disabled={!canReturnHome} disabled={!canReturnHome}
leftIcon={<RiHome4Line />} leftSection={<RiHome4Line />}
to={AppRoute.HOME} to={AppRoute.HOME}
variant="filled" variant="filled"
> >
@ -82,12 +81,12 @@ const ActionRequiredRoute = () => {
)} )}
{!displayedCheck && ( {!displayedCheck && (
<Group <Group
noWrap justify="center"
position="center" wrap="nowrap"
> >
<Button <Button
fullWidth fullWidth
leftIcon={<RiEdit2Line />} leftSection={<RiEdit2Line />}
variant="filled" variant="filled"
onClick={handleManageServersModal} onClick={handleManageServersModal}
> >

View File

@ -1,7 +1,6 @@
import { Center, Group, Stack } from '@mantine/core';
import { RiQuestionLine } from 'react-icons/ri'; import { RiQuestionLine } from 'react-icons/ri';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { Button, Text } from '/@/renderer/components'; import { Button, Center, Group, Stack, Text } from '/@/renderer/components';
import { AnimatedPage } from '/@/renderer/features/shared'; import { AnimatedPage } from '/@/renderer/features/shared';
const InvalidRoute = () => { const InvalidRoute = () => {
@ -10,11 +9,11 @@ const InvalidRoute = () => {
return ( return (
<AnimatedPage> <AnimatedPage>
<Center sx={{ height: '100%', width: '100%' }}> <Center style={{ height: '100%', width: '100%' }}>
<Stack> <Stack>
<Group <Group
noWrap justify="center"
position="center" wrap="nowrap"
> >
<RiQuestionLine <RiQuestionLine
color="var(--warning-color)" color="var(--warning-color)"

View File

@ -1,7 +1,6 @@
import { MutableRefObject, useCallback, useMemo } from 'react'; import { MutableRefObject, useCallback, useMemo } from 'react';
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core'; import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Box, Group, Stack } from '@mantine/core';
import { useSetState } from '@mantine/hooks'; import { useSetState } from '@mantine/hooks';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaLastfmSquare } from 'react-icons/fa'; import { FaLastfmSquare } from 'react-icons/fa';
@ -12,7 +11,7 @@ import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types'; import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types';
import { Button, Popover, Spoiler } from '/@/renderer/components'; import { Button, Group, Popover, Spoiler, Stack } from '/@/renderer/components';
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel'; import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
import { import {
TableConfigDropdown, TableConfigDropdown,
@ -328,19 +327,19 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
<ContentContainer> <ContentContainer>
<LibraryBackgroundOverlay $backgroundColor={background} /> <LibraryBackgroundOverlay $backgroundColor={background} />
<DetailContainer> <DetailContainer>
<Box component="section"> <section>
<Group <Group
position="apart" gap="sm"
spacing="sm" justify="space-between"
> >
<Group> <Group>
<PlayButton onClick={() => handlePlay(playButtonBehavior)} /> <PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Button <Button
compact
loading={ loading={
createFavoriteMutation.isLoading || createFavoriteMutation.isLoading ||
deleteFavoriteMutation.isLoading deleteFavoriteMutation.isLoading
} }
size="compact-md"
variant="subtle" variant="subtle"
onClick={handleFavorite} onClick={handleFavorite}
> >
@ -354,7 +353,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
)} )}
</Button> </Button>
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
if (!detailQuery?.data) return; if (!detailQuery?.data) return;
@ -368,8 +367,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
<Popover position="bottom-end"> <Popover position="bottom-end">
<Popover.Target> <Popover.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiSettings2Fill size={20} /> <RiSettings2Fill size={20} />
@ -380,17 +378,16 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>
</Group> </Group>
</Box> </section>
{showGenres && ( {showGenres && (
<Box component="section"> <section>
<Group spacing="sm"> <Group gap="sm">
{detailQuery?.data?.genres?.map((genre) => ( {detailQuery?.data?.genres?.map((genre) => (
<Button <Button
key={`genre-${genre.id}`} key={`genre-${genre.id}`}
compact
component={Link} component={Link}
radius={0} radius={0}
size="md" size="compact-md"
to={generatePath(genreRoute, { to={generatePath(genreRoute, {
genreId: genre.id, genreId: genre.id,
})} })}
@ -400,20 +397,19 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
</Button> </Button>
))} ))}
</Group> </Group>
</Box> </section>
)} )}
{externalLinks ? ( {externalLinks ? (
<Box component="section"> <section>
<Group spacing="sm"> <Group gap="sm">
<Button <Button
compact
component="a" component="a"
href={`https://www.last.fm/music/${encodeURIComponent( href={`https://www.last.fm/music/${encodeURIComponent(
detailQuery?.data?.albumArtist || '', detailQuery?.data?.albumArtist || '',
)}/${encodeURIComponent(detailQuery.data?.name || '')}`} )}/${encodeURIComponent(detailQuery.data?.name || '')}`}
radius="md" radius="md"
rel="noopener noreferrer" rel="noopener noreferrer"
size="md" size="compact-md"
target="_blank" target="_blank"
tooltip={{ tooltip={{
label: t('action.openIn.lastfm'), label: t('action.openIn.lastfm'),
@ -424,12 +420,11 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
</Button> </Button>
{mbzId ? ( {mbzId ? (
<Button <Button
compact
component="a" component="a"
href={`https://musicbrainz.org/release/${mbzId}`} href={`https://musicbrainz.org/release/${mbzId}`}
radius="md" radius="md"
rel="noopener noreferrer" rel="noopener noreferrer"
size="md" size="compact-md"
target="_blank" target="_blank"
tooltip={{ tooltip={{
label: t('action.openIn.musicbrainz'), label: t('action.openIn.musicbrainz'),
@ -440,14 +435,14 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
</Button> </Button>
) : null} ) : null}
</Group> </Group>
</Box> </section>
) : null} ) : null}
{comment && ( {comment && (
<Box component="section"> <section>
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler> <Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
</Box> </section>
)} )}
<Box style={{ minHeight: '300px' }}> <div style={{ minHeight: '300px' }}>
<VirtualTable <VirtualTable
key={`table-${tableConfig.rowHeight}`} key={`table-${tableConfig.rowHeight}`}
ref={tableRef} ref={tableRef}
@ -482,11 +477,11 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
onColumnMoved={onColumnMoved} onColumnMoved={onColumnMoved}
onRowDoubleClicked={handleRowDoubleClick} onRowDoubleClicked={handleRowDoubleClick}
/> />
</Box> </div>
<Stack <Stack
ref={cq.ref} ref={cq.ref}
mt="3rem" gap="lg"
spacing="lg" style={{ marginTop: '3rem' }}
> >
{cq.height || cq.width ? ( {cq.height || cq.width ? (
<> <>

View File

@ -1,9 +1,8 @@
import { Group, Stack } from '@mantine/core';
import { forwardRef, Fragment, Ref } from 'react'; import { forwardRef, Fragment, Ref } from 'react';
import { generatePath, useParams } from 'react-router'; import { generatePath, useParams } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { LibraryItem, ServerType } from '/@/renderer/api/types'; import { LibraryItem, ServerType } from '/@/renderer/api/types';
import { Rating, Text } from '/@/renderer/components'; import { Group, Rating, Stack, Text } from '/@/renderer/components';
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared'; import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
@ -66,8 +65,8 @@ export const AlbumDetailHeader = forwardRef(
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }} item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack spacing="sm"> <Stack gap="sm">
<Group spacing="sm"> <Group gap="sm">
{metadataItems.map((item, index) => ( {metadataItems.map((item, index) => (
<Fragment key={`item-${item.id}-${index}`}> <Fragment key={`item-${item.id}-${index}`}>
{index > 0 && <Text $noSelect></Text>} {index > 0 && <Text $noSelect></Text>}
@ -89,11 +88,11 @@ export const AlbumDetailHeader = forwardRef(
)} )}
</Group> </Group>
<Group <Group
mah="4rem" gap="md"
spacing="md" style={{
sx={{
WebkitBoxOrient: 'vertical', WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2, WebkitLineClamp: 2,
maxHeight: '4rem',
overflow: 'hidden', overflow: 'hidden',
}} }}
> >

View File

@ -1,6 +1,5 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react'; import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { openModal } from '@mantine/modals'; import { openModal } from '@mantine/modals';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -16,7 +15,18 @@ import {
} from 'react-icons/ri'; } from 'react-icons/ri';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types'; import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components'; import {
Button,
Divider,
DropdownMenu,
Flex,
Group,
MultiSelect,
Slider,
Stack,
Switch,
Text,
} from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
@ -296,7 +306,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
[pageKey, setDisplayType], [pageKey, setDisplayType],
); );
const handleTableColumns = (values: TableColumn[]) => { const handleTableColumns = (values: TableColumn[] | any[]) => {
const existingColumns = table.columns; const existingColumns = table.columns;
if (values.length === 0) { if (values.length === 0) {
@ -352,15 +362,14 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
ref={cq.ref} ref={cq.ref}
spacing="sm" gap="sm"
w="100%" style={{ width: '100%' }}
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw={600} fw={600}
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -390,10 +399,9 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw={600} fw={600}
size="md" size="compact-md"
sx={{ style={{
svg: { svg: {
fill: isFolderFilterApplied fill: isFolderFilterApplied
? 'var(--primary-color) !important' ? 'var(--primary-color) !important'
@ -422,9 +430,8 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
)} )}
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md" style={{
sx={{
svg: { svg: {
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined, fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
}, },
@ -439,8 +446,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
</Button> </Button>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'sentenceCase' }) }} tooltip={{ label: t('common.refresh', { postProcess: 'sentenceCase' }) }}
variant="subtle" variant="subtle"
onClick={handleRefresh} onClick={handleRefresh}
@ -451,8 +457,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size={15} /> <RiMoreFill size={15} />
@ -460,26 +465,26 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiPlayFill />} leftSection={<RiPlayFill />}
onClick={() => handlePlay?.({ playType: Play.NOW })} onClick={() => handlePlay?.({ playType: Play.NOW })}
> >
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddBoxFill />} leftSection={<RiAddBoxFill />}
onClick={() => handlePlay?.({ playType: Play.LAST })} onClick={() => handlePlay?.({ playType: Play.LAST })}
> >
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddCircleFill />} leftSection={<RiAddCircleFill />}
onClick={() => handlePlay?.({ playType: Play.NEXT })} onClick={() => handlePlay?.({ playType: Play.NEXT })}
> >
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiRefreshLine />} leftSection={<RiRefreshLine />}
onClick={handleRefresh} onClick={handleRefresh}
> >
{t('common.refresh', { postProcess: 'sentenceCase' })} {t('common.refresh', { postProcess: 'sentenceCase' })}
@ -488,8 +493,8 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
</DropdownMenu> </DropdownMenu>
</Group> </Group>
<Group <Group
noWrap gap="sm"
spacing="sm" wrap="nowrap"
> >
<DropdownMenu <DropdownMenu
position="bottom-end" position="bottom-end"
@ -497,8 +502,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
> >
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ tooltip={{
label: t('common.configure', { postProcess: 'sentenceCase' }), label: t('common.configure', { postProcess: 'sentenceCase' }),
}} }}
@ -573,7 +577,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
<DropdownMenu.Item <DropdownMenu.Item
closeMenuOnClick={false} closeMenuOnClick={false}
component="div" component="div"
sx={{ cursor: 'default' }} style={{ cursor: 'default' }}
> >
<Stack> <Stack>
<MultiSelect <MultiSelect
@ -585,7 +589,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
width={300} width={300}
onChange={handleTableColumns} onChange={handleTableColumns}
/> />
<Group position="apart"> <Group justify="space-between">
<Text>Auto Fit Columns</Text> <Text>Auto Fit Columns</Text>
<Switch <Switch
defaultChecked={table.autoFit} defaultChecked={table.autoFit}

View File

@ -1,10 +1,9 @@
import { useEffect, useRef, type ChangeEvent, type MutableRefObject } from 'react'; import { useEffect, useRef, type ChangeEvent, type MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components'; import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters'; import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared'; import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
@ -57,7 +56,7 @@ export const AlbumListHeader = ({
return ( return (
<Stack <Stack
ref={cq.ref} ref={cq.ref}
spacing={0} gap={0}
> >
<PageHeader backgroundColor="var(--titlebar-bg)"> <PageHeader backgroundColor="var(--titlebar-bg)">
<Flex <Flex

View File

@ -1,10 +1,18 @@
import { ChangeEvent, useMemo, useState } from 'react'; import { ChangeEvent, useMemo, useState } from 'react';
import { Divider, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListFilterByKey } from '../../../store/list.store'; import { useListFilterByKey } from '../../../store/list.store';
import { AlbumArtistListSort, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types'; import { AlbumArtistListSort, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
import { MultiSelect, NumberInput, SpinnerIcon, Switch, Text } from '/@/renderer/components'; import {
Divider,
Group,
MultiSelect,
NumberInput,
SpinnerIcon,
Stack,
Switch,
Text,
} from '/@/renderer/components';
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query'; import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
import { useGenreList } from '/@/renderer/features/genres'; import { useGenreList } from '/@/renderer/features/genres';
import { AlbumListFilter, useListStoreActions } from '/@/renderer/store'; import { AlbumListFilter, useListStoreActions } from '/@/renderer/store';
@ -176,11 +184,11 @@ export const JellyfinAlbumFilters = ({
}; };
return ( return (
<Stack p="0.8rem"> <Stack style={{ padding: '0.8rem' }}>
{toggleFilters.map((filter) => ( {toggleFilters.map((filter) => (
<Group <Group
key={`nd-filter-${filter.label}`} key={`nd-filter-${filter.label}`}
position="apart" justify="space-between"
> >
<Text>{filter.label}</Text> <Text>{filter.label}</Text>
<Switch <Switch
@ -190,7 +198,7 @@ export const JellyfinAlbumFilters = ({
/> />
</Group> </Group>
))} ))}
<Divider my="0.5rem" /> <Divider marginY="0.5rem" />
<Group grow> <Group grow>
<NumberInput <NumberInput
defaultValue={filter?._custom?.jellyfin?.minYear} defaultValue={filter?._custom?.jellyfin?.minYear}

View File

@ -1,6 +1,14 @@
import { ChangeEvent, useMemo, useState } from 'react'; import { ChangeEvent, useMemo, useState } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import {
import { NumberInput, Switch, Text, Select, SpinnerIcon } from '/@/renderer/components'; NumberInput,
Switch,
Text,
Select,
SpinnerIcon,
Divider,
Group,
Stack,
} from '/@/renderer/components';
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useGenreList } from '/@/renderer/features/genres'; import { useGenreList } from '/@/renderer/features/genres';
@ -210,11 +218,11 @@ export const NavidromeAlbumFilters = ({
}; };
return ( return (
<Stack p="0.8rem"> <Stack style={{ padding: '0.8rem' }}>
{toggleFilters.map((filter) => ( {toggleFilters.map((filter) => (
<Group <Group
key={`nd-filter-${filter.label}`} key={`nd-filter-${filter.label}`}
position="apart" justify="space-between"
> >
<Text>{filter.label}</Text> <Text>{filter.label}</Text>
<Switch <Switch
@ -223,7 +231,7 @@ export const NavidromeAlbumFilters = ({
/> />
</Group> </Group>
))} ))}
<Divider my="0.5rem" /> <Divider marginY="0.5rem" />
<Group grow> <Group grow>
<NumberInput <NumberInput
defaultValue={filter._custom?.navidrome?.year} defaultValue={filter._custom?.navidrome?.year}

View File

@ -1,4 +1,4 @@
import { Button, Spinner, Spoiler, Text } from '/@/renderer/components'; import { Button, Center, Group, Spinner, Spoiler, Stack, Text } from '/@/renderer/components';
import { import {
AnimatedPage, AnimatedPage,
LibraryHeader, LibraryHeader,
@ -13,7 +13,6 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types'; import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { Stack, Group, Box, Center } from '@mantine/core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { formatDurationString } from '/@/renderer/utils'; import { formatDurationString } from '/@/renderer/utils';
@ -139,8 +138,8 @@ const DummyAlbumDetailRoute = () => {
item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }} item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack spacing="sm"> <Stack gap="sm">
<Group spacing="sm"> <Group gap="sm">
{metadataItems.map((item, index) => ( {metadataItems.map((item, index) => (
<Fragment key={`item-${item.id}-${index}`}> <Fragment key={`item-${item.id}-${index}`}>
{index > 0 && <Text $noSelect></Text>} {index > 0 && <Text $noSelect></Text>}
@ -149,11 +148,11 @@ const DummyAlbumDetailRoute = () => {
))} ))}
</Group> </Group>
<Group <Group
mah="4rem" gap="md"
spacing="md" style={{
sx={{
WebkitBoxOrient: 'vertical', WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2, WebkitLineClamp: 2,
maxHeight: '4rem',
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
@ -177,19 +176,19 @@ const DummyAlbumDetailRoute = () => {
</LibraryHeader> </LibraryHeader>
</Stack> </Stack>
<DetailContainer> <DetailContainer>
<Box component="section"> <section>
<Group <Group
position="apart" gap="sm"
spacing="sm" justify="space-between"
> >
<Group> <Group>
<PlayButton onClick={() => handlePlay()} /> <PlayButton onClick={() => handlePlay()} />
<Button <Button
compact
loading={ loading={
createFavoriteMutation.isLoading || createFavoriteMutation.isLoading ||
deleteFavoriteMutation.isLoading deleteFavoriteMutation.isLoading
} }
size="compact-md"
variant="subtle" variant="subtle"
onClick={handleFavorite} onClick={handleFavorite}
> >
@ -203,7 +202,7 @@ const DummyAlbumDetailRoute = () => {
)} )}
</Button> </Button>
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
if (!detailQuery?.data) return; if (!detailQuery?.data) return;
@ -214,17 +213,16 @@ const DummyAlbumDetailRoute = () => {
</Button> </Button>
</Group> </Group>
</Group> </Group>
</Box> </section>
{showGenres && ( {showGenres && (
<Box component="section"> <section>
<Group spacing="sm"> <Group gap="sm">
{detailQuery?.data?.genres?.map((genre) => ( {detailQuery?.data?.genres?.map((genre) => (
<Button <Button
key={`genre-${genre.id}`} key={`genre-${genre.id}`}
compact
component={Link} component={Link}
radius={0} radius={0}
size="md" size="compact-md"
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, { to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
genreId: genre.id, genreId: genre.id,
})} })}
@ -234,16 +232,16 @@ const DummyAlbumDetailRoute = () => {
</Button> </Button>
))} ))}
</Group> </Group>
</Box> </section>
)} )}
{comment && ( {comment && (
<Box component="section"> <section>
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler> <Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
</Box> </section>
)} )}
<Box component="section"> <section>
<Center> <Center>
<Group mr={5}> <Group style={{ marginRight: '5px' }}>
<RiErrorWarningLine <RiErrorWarningLine
color="var(--danger-color)" color="var(--danger-color)"
size={30} size={30}
@ -251,7 +249,7 @@ const DummyAlbumDetailRoute = () => {
</Group> </Group>
<h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2> <h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2>
</Center> </Center>
</Box> </section>
</DetailContainer> </DetailContainer>
</AnimatedPage> </AnimatedPage>
); );

View File

@ -1,6 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import { Box, Group, Stack } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaLastfmSquare } from 'react-icons/fa'; import { FaLastfmSquare } from 'react-icons/fa';
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri'; import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
@ -17,7 +16,7 @@ import {
ServerType, ServerType,
SortOrder, SortOrder,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { Button, Spoiler, TextTitle } from '/@/renderer/components'; import { Button, Group, Spoiler, Stack, TextTitle } from '/@/renderer/components';
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel'; import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table'; import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query'; import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
@ -221,10 +220,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
})} })}
</TextTitle> </TextTitle>
<Button <Button
compact
uppercase
component={Link} component={Link}
size="compact-md"
to={artistDiscographyLink} to={artistDiscographyLink}
tt="uppercase"
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewDiscography')} {t('page.albumArtistDetail.viewDiscography')}
@ -354,14 +353,14 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
<ContentContainer ref={cq.ref}> <ContentContainer ref={cq.ref}>
<LibraryBackgroundOverlay $backgroundColor={background} /> <LibraryBackgroundOverlay $backgroundColor={background} />
<DetailContainer> <DetailContainer>
<Group spacing="md"> <Group gap="md">
<PlayButton onClick={() => handlePlay(playButtonBehavior)} /> <PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Group spacing="xs"> <Group gap="xs">
<Button <Button
compact
loading={ loading={
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
} }
size="compact-md"
variant="subtle" variant="subtle"
onClick={handleFavorite} onClick={handleFavorite}
> >
@ -375,7 +374,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
)} )}
</Button> </Button>
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
if (!detailQuery?.data) return; if (!detailQuery?.data) return;
@ -386,36 +385,35 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Button> </Button>
</Group> </Group>
</Group> </Group>
<Group spacing="md"> <Group gap="md">
<Button <Button
compact
uppercase
component={Link} component={Link}
size="compact-md"
to={artistDiscographyLink} to={artistDiscographyLink}
tt="uppercase"
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewDiscography')} {t('page.albumArtistDetail.viewDiscography')}
</Button> </Button>
<Button <Button
compact
uppercase
component={Link} component={Link}
size="compact-md"
to={artistSongsLink} to={artistSongsLink}
tt="uppercase"
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewAllTracks')} {t('page.albumArtistDetail.viewAllTracks')}
</Button> </Button>
</Group> </Group>
{showGenres ? ( {showGenres ? (
<Box component="section"> <section>
<Group spacing="sm"> <Group gap="sm">
{detailQuery?.data?.genres?.map((genre) => ( {detailQuery?.data?.genres?.map((genre) => (
<Button <Button
key={`genre-${genre.id}`} key={`genre-${genre.id}`}
compact
component={Link} component={Link}
radius="md" radius="md"
size="md" size="compact-md"
to={generatePath(genrePath, { to={generatePath(genrePath, {
genreId: genre.id, genreId: genre.id,
})} })}
@ -425,20 +423,19 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Button> </Button>
))} ))}
</Group> </Group>
</Box> </section>
) : null} ) : null}
{externalLinks ? ( {externalLinks ? (
<Box component="section"> <section>
<Group spacing="sm"> <Group gap="sm">
<Button <Button
compact
component="a" component="a"
href={`https://www.last.fm/music/${encodeURIComponent( href={`https://www.last.fm/music/${encodeURIComponent(
detailQuery?.data?.name || '', detailQuery?.data?.name || '',
)}`} )}`}
radius="md" radius="md"
rel="noopener noreferrer" rel="noopener noreferrer"
size="md" size="compact-md"
target="_blank" target="_blank"
tooltip={{ tooltip={{
label: t('action.openIn.lastfm'), label: t('action.openIn.lastfm'),
@ -449,12 +446,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Button> </Button>
{mbzId ? ( {mbzId ? (
<Button <Button
compact
component="a" component="a"
href={`https://musicbrainz.org/artist/${mbzId}`} href={`https://musicbrainz.org/artist/${mbzId}`}
radius="md" radius="md"
rel="noopener noreferrer" rel="noopener noreferrer"
size="md" size="compact-md"
target="_blank" target="_blank"
tooltip={{ tooltip={{
label: t('action.openIn.musicbrainz'), label: t('action.openIn.musicbrainz'),
@ -465,13 +461,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Button> </Button>
) : null} ) : null}
</Group> </Group>
</Box> </section>
) : null} ) : null}
{biography ? ( {biography ? (
<Box <section style={{ maxWidth: '1280px' }}>
component="section"
maw="1280px"
>
<TextTitle <TextTitle
order={2} order={2}
weight={700} weight={700}
@ -481,17 +474,17 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
})} })}
</TextTitle> </TextTitle>
<Spoiler dangerouslySetInnerHTML={{ __html: biography }} /> <Spoiler dangerouslySetInnerHTML={{ __html: biography }} />
</Box> </section>
) : null} ) : null}
{showTopSongs ? ( {showTopSongs ? (
<Box component="section"> <section>
<Group <Group
noWrap justify="space-between"
position="apart" wrap="nowrap"
> >
<Group <Group
noWrap
align="flex-end" align="flex-end"
wrap="nowrap"
> >
<TextTitle <TextTitle
order={2} order={2}
@ -502,15 +495,15 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
})} })}
</TextTitle> </TextTitle>
<Button <Button
compact
uppercase
component={Link} component={Link}
size="compact-md"
to={generatePath( to={generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS, AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
{ {
albumArtistId, albumArtistId,
}, },
)} )}
tt="uppercase"
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewAll', { {t('page.albumArtistDetail.viewAll', {
@ -537,10 +530,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
onCellContextMenu={handleContextMenu} onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick} onRowDoubleClicked={handleRowDoubleClick}
/> />
</Box> </section>
) : null} ) : null}
<Box component="section"> <section>
<Stack spacing="xl"> <Stack gap="xl">
{carousels {carousels
.filter((c) => !c.isHidden) .filter((c) => !c.isHidden)
.map((carousel) => ( .map((carousel) => (
@ -563,7 +556,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
/> />
))} ))}
</Stack> </Stack>
</Box> </section>
</DetailContainer> </DetailContainer>
</ContentContainer> </ContentContainer>
); );

View File

@ -1,8 +1,7 @@
import { forwardRef, Fragment, Ref } from 'react'; import { forwardRef, Fragment, Ref } from 'react';
import { Group, Rating, Stack } from '@mantine/core';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { LibraryItem, ServerType } from '/@/renderer/api/types'; import { LibraryItem, ServerType } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components'; import { Group, Rating, Stack, Text } from '/@/renderer/components';
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query'; import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared'; import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';

View File

@ -47,8 +47,8 @@ export const AlbumArtistDetailTopSongsListHeader = ({
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="compact-md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size={15} /> <RiMoreFill size={15} />
@ -56,19 +56,19 @@ export const AlbumArtistDetailTopSongsListHeader = ({
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiPlayFill />} leftSection={<RiPlayFill />}
onClick={() => handlePlay(Play.NOW)} onClick={() => handlePlay(Play.NOW)}
> >
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddBoxFill />} leftSection={<RiAddBoxFill />}
onClick={() => handlePlay(Play.LAST)} onClick={() => handlePlay(Play.LAST)}
> >
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddCircleFill />} leftSection={<RiAddCircleFill />}
onClick={() => handlePlay(Play.NEXT)} onClick={() => handlePlay(Play.NEXT)}
> >
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext', { postProcess: 'sentenceCase' })}

View File

@ -1,7 +1,6 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react'; import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { IDatasource } from '@ag-grid-community/core'; import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,7 +9,18 @@ import { useListContext } from '../../../context/list-context';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumArtistListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types'; import { AlbumArtistListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components'; import {
Button,
Divider,
DropdownMenu,
Flex,
Group,
MultiSelect,
Slider,
Stack,
Switch,
Text,
} from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared'; import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
@ -284,7 +294,7 @@ export const AlbumArtistListHeaderFilters = ({
[pageKey, setDisplayType], [pageKey, setDisplayType],
); );
const handleTableColumns = (values: TableColumn[]) => { const handleTableColumns = (values: TableColumn[] | any[]) => {
const existingColumns = table.columns; const existingColumns = table.columns;
if (values.length === 0) { if (values.length === 0) {
@ -329,15 +339,14 @@ export const AlbumArtistListHeaderFilters = ({
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
ref={cq.ref} ref={cq.ref}
spacing="sm" gap="sm"
w="100%" style={{ width: '100%' }}
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -367,9 +376,8 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{cq.isMd ? 'Folder' : <RiFolder2Line size={15} />} {cq.isMd ? 'Folder' : <RiFolder2Line size={15} />}
@ -392,8 +400,7 @@ export const AlbumArtistListHeaderFilters = ({
)} )}
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }} tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle" variant="subtle"
onClick={handleRefresh} onClick={handleRefresh}
@ -404,8 +411,7 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size={15} /> <RiMoreFill size={15} />
@ -413,7 +419,7 @@ export const AlbumArtistListHeaderFilters = ({
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiRefreshLine />} leftSection={<RiRefreshLine />}
onClick={handleRefresh} onClick={handleRefresh}
> >
Refresh Refresh
@ -428,8 +434,7 @@ export const AlbumArtistListHeaderFilters = ({
> >
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiSettings3Fill size="1.3rem" /> <RiSettings3Fill size="1.3rem" />
@ -520,7 +525,7 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu.Item <DropdownMenu.Item
closeMenuOnClick={false} closeMenuOnClick={false}
component="div" component="div"
sx={{ cursor: 'default' }} style={{ cursor: 'default' }}
> >
<Stack> <Stack>
<MultiSelect <MultiSelect
@ -532,7 +537,7 @@ export const AlbumArtistListHeaderFilters = ({
width={300} width={300}
onChange={handleTableColumns} onChange={handleTableColumns}
/> />
<Group position="apart"> <Group justify="space-between">
<Text> <Text>
{t('table.config.general.autoFitColumns', { {t('table.config.general.autoFitColumns', {
postProcess: 'sentenceCase', postProcess: 'sentenceCase',

View File

@ -1,11 +1,10 @@
import type { ChangeEvent, MutableRefObject } from 'react'; import type { ChangeEvent, MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FilterBar } from '../../shared/components/filter-bar'; import { FilterBar } from '../../shared/components/filter-bar';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components'; import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters'; import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
import { LibraryHeaderBar } from '/@/renderer/features/shared'; import { LibraryHeaderBar } from '/@/renderer/features/shared';
@ -43,12 +42,12 @@ export const AlbumArtistListHeader = ({
return ( return (
<Stack <Stack
ref={cq.ref} ref={cq.ref}
spacing={0} gap={0}
> >
<PageHeader backgroundColor="var(--titlebar-bg)"> <PageHeader backgroundColor="var(--titlebar-bg)">
<Flex <Flex
justify="space-between" justify="space-between"
w="100%" style={{ width: '100%' }}
> >
<LibraryHeaderBar> <LibraryHeaderBar>
<LibraryHeaderBar.Title> <LibraryHeaderBar.Title>

View File

@ -1,6 +1,5 @@
import { createContext, Fragment, ReactNode, useState, useMemo, useCallback } from 'react'; import { createContext, Fragment, ReactNode, useState, useMemo, useCallback } from 'react';
import { RowNode } from '@ag-grid-community/core'; import { RowNode } from '@ag-grid-community/core';
import { Divider, Group, Portal, Stack } from '@mantine/core';
import { import {
useClickOutside, useClickOutside,
useMergedRef, useMergedRef,
@ -35,8 +34,12 @@ import {
ConfirmModal, ConfirmModal,
ContextMenu, ContextMenu,
ContextMenuButton, ContextMenuButton,
Divider,
Group,
HoverCard, HoverCard,
Portal,
Rating, Rating,
Stack,
Text, Text,
toast, toast,
} from '/@/renderer/components'; } from '/@/renderer/components';
@ -861,9 +864,9 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
xPos={ctx.xPos} xPos={ctx.xPos}
yPos={ctx.yPos} yPos={ctx.yPos}
> >
<Stack spacing={0}> <Stack gap={0}>
<Stack <Stack
spacing={0} gap={0}
onClick={closeContextMenu} onClick={closeContextMenu}
> >
{ctx.menuItems?.map((item) => { {ctx.menuItems?.map((item) => {
@ -897,7 +900,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
</ContextMenuButton> </ContextMenuButton>
</HoverCard.Target> </HoverCard.Target>
<HoverCard.Dropdown> <HoverCard.Dropdown>
<Stack spacing={0}> <Stack gap={0}>
{contextMenuItems[ {contextMenuItems[
item.id item.id
].children?.map((child) => ( ].children?.map((child) => (
@ -937,7 +940,6 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
<Divider <Divider
key={`context-menu-divider-${item.id}`} key={`context-menu-divider-${item.id}`}
color="rgb(62, 62, 62)" color="rgb(62, 62, 62)"
size="sm"
/> />
)} )}
</Fragment> </Fragment>
@ -945,10 +947,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
); );
})} })}
</Stack> </Stack>
<Divider <Divider color="rgb(62, 62, 62)" />
color="rgb(62, 62, 62)"
size="sm"
/>
<ContextMenuButton disabled> <ContextMenuButton disabled>
{t('page.contextMenu.numberSelected', { {t('page.contextMenu.numberSelected', {
count: ctx.data?.length || 0, count: ctx.data?.length || 0,

View File

@ -1,5 +1,5 @@
import { GridOptions, RowNode } from '@ag-grid-community/core'; import { GridOptions, RowNode } from '@ag-grid-community/core';
import { createUseExternalEvents } from '@mantine/utils'; import { createUseExternalEvents } from '@mantine/core';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
export type OpenContextMenuProps = { export type OpenContextMenuProps = {

View File

@ -1,6 +1,5 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react'; import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -13,7 +12,18 @@ import {
} from 'react-icons/ri'; } from 'react-icons/ri';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { GenreListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types'; import { GenreListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components'; import {
Button,
Divider,
DropdownMenu,
Flex,
Group,
MultiSelect,
Slider,
Stack,
Switch,
Text,
} from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
@ -182,7 +192,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
[pageKey, setDisplayType], [pageKey, setDisplayType],
); );
const handleTableColumns = (values: TableColumn[]) => { const handleTableColumns = (values: TableColumn[] | any[]) => {
const existingColumns = table.columns; const existingColumns = table.columns;
if (values.length === 0) { if (values.length === 0) {
@ -229,15 +239,14 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
ref={cq.ref} ref={cq.ref}
spacing="sm" gap="sm"
w="100%" style={{ width: '100%' }}
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw={600} fw={600}
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -267,10 +276,9 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw={600} fw={600}
size="md" size="compact-md"
sx={{ style={{
svg: { svg: {
fill: isFolderFilterApplied fill: isFolderFilterApplied
? 'var(--primary-color) !important' ? 'var(--primary-color) !important'
@ -299,8 +307,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
)} )}
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }} tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle" variant="subtle"
onClick={handleRefresh} onClick={handleRefresh}
@ -311,8 +318,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size={15} /> <RiMoreFill size={15} />
@ -320,7 +326,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiRefreshLine />} leftSection={<RiRefreshLine />}
onClick={handleRefresh} onClick={handleRefresh}
> >
{t('common.refresh', { postProcess: 'titleCase' })} {t('common.refresh', { postProcess: 'titleCase' })}
@ -328,8 +334,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ tooltip={{
label: t( label: t(
genreTarget === GenreTarget.ALBUM genreTarget === GenreTarget.ALBUM
@ -346,8 +351,8 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
</DropdownMenu> </DropdownMenu>
</Group> </Group>
<Group <Group
noWrap gap="sm"
spacing="sm" wrap="nowrap"
> >
<DropdownMenu <DropdownMenu
position="bottom-end" position="bottom-end"
@ -355,8 +360,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
> >
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ tooltip={{
label: t('common.configure', { postProcess: 'titleCase' }), label: t('common.configure', { postProcess: 'titleCase' }),
}} }}
@ -426,7 +430,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
<DropdownMenu.Item <DropdownMenu.Item
closeMenuOnClick={false} closeMenuOnClick={false}
component="div" component="div"
sx={{ cursor: 'default' }} style={{ cursor: 'default' }}
> >
<Stack> <Stack>
<MultiSelect <MultiSelect
@ -438,7 +442,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
width={300} width={300}
onChange={handleTableColumns} onChange={handleTableColumns}
/> />
<Group position="apart"> <Group justify="space-between">
<Text> <Text>
{t('table.config.general.autoFitColumns', { {t('table.config.general.autoFitColumns', {
postProcess: 'titleCase', postProcess: 'titleCase',

View File

@ -1,9 +1,8 @@
import { ChangeEvent, MutableRefObject } from 'react'; import { ChangeEvent, MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components'; import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { GenreListHeaderFilters } from '/@/renderer/features/genres/components/genre-list-header-filters'; import { GenreListHeaderFilters } from '/@/renderer/features/genres/components/genre-list-header-filters';
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared'; import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
@ -37,12 +36,12 @@ export const GenreListHeader = ({ itemCount, gridRef, tableRef }: GenreListHeade
return ( return (
<Stack <Stack
ref={cq.ref} ref={cq.ref}
spacing={0} gap={0}
> >
<PageHeader backgroundColor="var(--titlebar-bg)"> <PageHeader backgroundColor="var(--titlebar-bg)">
<Flex <Flex
justify="space-between" justify="space-between"
w="100%" style={{ width: '100%' }}
> >
<LibraryHeaderBar> <LibraryHeaderBar>
<LibraryHeaderBar.Title> <LibraryHeaderBar.Title>

View File

@ -1,5 +1,4 @@
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { ActionIcon, Group, Stack } from '@mantine/core';
import { import {
AlbumListSort, AlbumListSort,
LibraryItem, LibraryItem,
@ -7,7 +6,15 @@ import {
SongListSort, SongListSort,
SortOrder, SortOrder,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { FeatureCarousel, NativeScrollArea, Spinner, TextTitle } from '/@/renderer/components'; import {
ActionIcon,
FeatureCarousel,
Group,
NativeScrollArea,
Spinner,
Stack,
TextTitle,
} from '/@/renderer/components';
import { useAlbumList } from '/@/renderer/features/albums'; import { useAlbumList } from '/@/renderer/features/albums';
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query'; import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
@ -244,10 +251,10 @@ const HomeRoute = () => {
}} }}
> >
<Stack <Stack
gap="lg"
mb="5rem" mb="5rem"
pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'} pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'}
px="2rem" px="2rem"
spacing="lg"
> >
<FeatureCarousel data={featureItemsWithImage} /> <FeatureCarousel data={featureItemsWithImage} />
{sortedCarousel.map((carousel) => ( {sortedCarousel.map((carousel) => (

View File

@ -304,7 +304,7 @@ export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
<Table <Table
highlightOnHover highlightOnHover
horizontalSpacing="sm" horizontalSpacing="sm"
sx={{ userSelect: 'text' }} style={{ userSelect: 'text' }}
verticalSpacing="sm" verticalSpacing="sm"
> >
<tbody>{body}</tbody> <tbody>{body}</tbody>

View File

@ -1,5 +1,4 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useDebouncedValue } from '@mantine/hooks'; import { useDebouncedValue } from '@mantine/hooks';
import { openModal } from '@mantine/modals'; import { openModal } from '@mantine/modals';
@ -12,7 +11,15 @@ import {
LyricsOverride, LyricsOverride,
} from '../../../api/types'; } from '../../../api/types';
import { useLyricSearch } from '../queries/lyric-search-query'; import { useLyricSearch } from '../queries/lyric-search-query';
import { ScrollArea, Spinner, Text, TextInput } from '/@/renderer/components'; import {
Divider,
Group,
ScrollArea,
Spinner,
Stack,
Text,
TextInput,
} from '/@/renderer/components';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
const SearchItem = styled.button` const SearchItem = styled.button`
@ -47,12 +54,12 @@ const SearchResult = ({ data, onClick }: SearchResultProps) => {
return ( return (
<SearchItem onClick={onClick}> <SearchItem onClick={onClick}>
<Group <Group
noWrap justify="space-between"
position="apart" wrap="nowrap"
> >
<Stack <Stack
gap={0}
maw="65%" maw="65%"
spacing={0}
> >
<Text <Text
size="md" size="md"
@ -62,8 +69,8 @@ const SearchResult = ({ data, onClick }: SearchResultProps) => {
</Text> </Text>
<Text $secondary>{artist}</Text> <Text $secondary>{artist}</Text>
<Group <Group
noWrap gap="sm"
spacing="sm" wrap="nowrap"
> >
<Text <Text
$secondary $secondary
@ -146,7 +153,7 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
type="auto" type="auto"
w="100%" w="100%"
> >
<Stack spacing="md"> <Stack gap="md">
{searchResults.map((result) => ( {searchResults.map((result) => (
<SearchResult <SearchResult
key={`${result.source}-${result.id}`} key={`${result.source}-${result.id}`}

View File

@ -1,6 +1,5 @@
import { ComponentPropsWithoutRef } from 'react'; import { ComponentPropsWithoutRef } from 'react';
import { TextTitle } from '/@/renderer/components/text-title'; import { TextTitle, TextTitleProps } from '/@/renderer/components/text-title';
import { TitleProps } from '@mantine/core';
import styled from 'styled-components'; import styled from 'styled-components';
interface LyricLineProps extends ComponentPropsWithoutRef<'div'> { interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
@ -9,7 +8,7 @@ interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
text: string; text: string;
} }
const StyledText = styled(TextTitle)<TitleProps & { $alignment: string; $fontSize: number }>` const StyledText = styled(TextTitle)<TextTitleProps & { $alignment: string; $fontSize: number }>`
padding: 0 1rem; padding: 0 1rem;
font-size: ${(props) => props.$fontSize}px; font-size: ${(props) => props.$fontSize}px;
font-weight: 600; font-weight: 600;

View File

@ -1,9 +1,9 @@
import { Box, Center, Group, Select, SelectItem } from '@mantine/core'; import { ComboboxItem } from '@mantine/core';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RiAddFill, RiSubtractFill } from 'react-icons/ri'; import { RiAddFill, RiSubtractFill } from 'react-icons/ri';
import { LyricsOverride } from '/@/renderer/api/types'; import { LyricsOverride } from '/@/renderer/api/types';
import { Button, NumberInput, Tooltip } from '/@/renderer/components'; import { Box, Button, Center, Group, NumberInput, Select, Tooltip } from '/@/renderer/components';
import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form'; import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form';
import { import {
useCurrentSong, useCurrentSong,
@ -14,7 +14,7 @@ import {
interface LyricsActionsProps { interface LyricsActionsProps {
index: number; index: number;
languages: SelectItem[]; languages: ComboboxItem[];
onRemoveLyric: () => void; onRemoveLyric: () => void;
onResetLyric: () => void; onResetLyric: () => void;
@ -61,11 +61,11 @@ export const LyricsActions = ({
</Center> </Center>
)} )}
<Group position="center"> <Group justify="center">
{isDesktop && sources.length ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase
disabled={isActionsDisabled} disabled={isActionsDisabled}
tt="uppercase"
variant="subtle" variant="subtle"
onClick={() => onClick={() =>
openLyricSearchModal({ openLyricSearchModal({
@ -94,7 +94,7 @@ export const LyricsActions = ({
styles={{ input: { textAlign: 'center' } }} styles={{ input: { textAlign: 'center' } }}
value={delayMs || 0} value={delayMs || 0}
width={55} width={55}
onChange={handleLyricOffset} onChange={(e) => handleLyricOffset(Number(e))}
/> />
</Tooltip> </Tooltip>
<Button <Button
@ -106,8 +106,8 @@ export const LyricsActions = ({
</Button> </Button>
{isDesktop && sources.length ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase
disabled={isActionsDisabled} disabled={isActionsDisabled}
tt="uppercase"
variant="subtle" variant="subtle"
onClick={onResetLyric} onClick={onResetLyric}
> >
@ -119,9 +119,9 @@ export const LyricsActions = ({
<Box style={{ position: 'absolute', right: 0, top: 0 }}> <Box style={{ position: 'absolute', right: 0, top: 0 }}>
{isDesktop && sources.length ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase
color="red" color="red"
disabled={isActionsDisabled} disabled={isActionsDisabled}
tt="uppercase"
variant="subtle" variant="subtle"
onClick={onRemoveLyric} onClick={onRemoveLyric}
> >

View File

@ -1,12 +1,11 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { Center, Group } from '@mantine/core';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import { RiInformationFill } from 'react-icons/ri'; import { RiInformationFill } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query'; import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query';
import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics'; import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics';
import { Spinner, TextTitle } from '/@/renderer/components'; import { Center, Group, Spinner, TextTitle } from '/@/renderer/components';
import { ErrorFallback } from '/@/renderer/features/action-required'; import { ErrorFallback } from '/@/renderer/features/action-required';
import { import {
UnsynchronizedLyrics, UnsynchronizedLyrics,

View File

@ -1,9 +1,9 @@
import { useRef } from 'react'; import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Box, Flex } from '@mantine/core';
import { PlayQueueListControls } from './play-queue-list-controls'; import { PlayQueueListControls } from './play-queue-list-controls';
import { Song } from '/@/renderer/api/types'; import { Song } from '/@/renderer/api/types';
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
import { Box, Flex } from '/@/renderer/components';
export const DrawerPlayQueue = () => { export const DrawerPlayQueue = () => {
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null); const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
@ -15,7 +15,7 @@ export const DrawerPlayQueue = () => {
> >
<Box <Box
bg="var(--main-bg)" bg="var(--main-bg)"
sx={{ borderRadius: '10px' }} style={{ borderRadius: '10px' }}
> >
<PlayQueueListControls <PlayQueueListControls
tableRef={queueRef} tableRef={queueRef}

View File

@ -1,7 +1,6 @@
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Group } from '@mantine/core'; import { Button, Group, Popover } from '/@/renderer/components';
import { Button, Popover } from '/@/renderer/components';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -107,16 +106,15 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
return ( return (
<Group <Group
position="apart" justify="space-between"
px="1rem" px="1rem"
py="1rem" py="1rem"
sx={{ alignItems: 'center' }} style={{ alignItems: 'center' }}
w="100%" w="100%"
> >
<Group spacing="sm"> <Group gap="sm">
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('player.shuffle', { postProcess: 'sentenceCase' }) }} tooltip={{ label: t('player.shuffle', { postProcess: 'sentenceCase' }) }}
variant="default" variant="default"
onClick={handleShuffleQueue} onClick={handleShuffleQueue}
@ -124,8 +122,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
<RiShuffleLine size="1.1rem" /> <RiShuffleLine size="1.1rem" />
</Button> </Button>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('action.moveToBottom', { postProcess: 'sentenceCase' }) }} tooltip={{ label: t('action.moveToBottom', { postProcess: 'sentenceCase' }) }}
variant="default" variant="default"
onClick={handleMoveToBottom} onClick={handleMoveToBottom}
@ -133,8 +130,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
<RiArrowDownLine size="1.1rem" /> <RiArrowDownLine size="1.1rem" />
</Button> </Button>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('action.moveToTop', { postProcess: 'sentenceCase' }) }} tooltip={{ label: t('action.moveToTop', { postProcess: 'sentenceCase' }) }}
variant="default" variant="default"
onClick={handleMoveToTop} onClick={handleMoveToTop}
@ -142,8 +138,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
<RiArrowUpLine size="1.1rem" /> <RiArrowUpLine size="1.1rem" />
</Button> </Button>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ tooltip={{
label: t('action.removeFromQueue', { postProcess: 'sentenceCase' }), label: t('action.removeFromQueue', { postProcess: 'sentenceCase' }),
}} }}
@ -153,8 +148,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
<RiEraserLine size="1.1rem" /> <RiEraserLine size="1.1rem" />
</Button> </Button>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }} tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }}
variant="default" variant="default"
onClick={handleClearQueue} onClick={handleClearQueue}
@ -169,8 +163,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
> >
<Popover.Target> <Popover.Target>
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ tooltip={{
label: t('common.configure', { postProcess: 'sentenceCase' }), label: t('common.configure', { postProcess: 'sentenceCase' }),
}} }}

View File

@ -15,7 +15,7 @@ const NowPlayingRoute = () => {
<AnimatedPage> <AnimatedPage>
<VirtualGridContainer> <VirtualGridContainer>
<NowPlayingHeader /> <NowPlayingHeader />
<Paper sx={{ borderTop: '1px solid var(--generic-border-color)' }}> <Paper style={{ borderTop: '1px solid var(--generic-border-color)' }}>
<PlayQueueListControls <PlayQueueListControls
tableRef={queueRef} tableRef={queueRef}
type="nowPlaying" type="nowPlaying"

View File

@ -1,4 +1,3 @@
import { Flex, Stack, Group, Center } from '@mantine/core';
import { useSetState } from '@mantine/hooks'; import { useSetState } from '@mantine/hooks';
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'framer-motion'; import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'framer-motion';
import { useEffect, useRef, useLayoutEffect, useState, useCallback, Fragment } from 'react'; import { useEffect, useRef, useLayoutEffect, useState, useCallback, Fragment } from 'react';
@ -7,7 +6,7 @@ import { generatePath } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { QueueSong } from '/@/renderer/api/types'; import { QueueSong } from '/@/renderer/api/types';
import { Badge, Text, TextTitle } from '/@/renderer/components'; import { Badge, Center, Flex, Group, Stack, Text, TextTitle } from '/@/renderer/components';
import { useFastAverageColor } from '/@/renderer/hooks'; import { useFastAverageColor } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { import {
@ -104,7 +103,7 @@ const ImageWithPlaceholder = ({
if (!props.src) { if (!props.src) {
return ( return (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: '100%', height: '100%',
@ -246,11 +245,10 @@ export const FullScreenPlayerImage = () => {
</ImageContainer> </ImageContainer>
<MetadataContainer <MetadataContainer
className="full-screen-player-image-metadata" className="full-screen-player-image-metadata"
gap="xs"
maw="100%" maw="100%"
spacing="xs"
> >
<TextTitle <TextTitle
align="center"
order={1} order={1}
overflow="hidden" overflow="hidden"
style={{ style={{
@ -263,7 +261,6 @@ export const FullScreenPlayerImage = () => {
</TextTitle> </TextTitle>
<TextTitle <TextTitle
$link $link
align="center"
component={Link} component={Link}
order={3} order={3}
overflow="hidden" overflow="hidden"
@ -280,7 +277,6 @@ export const FullScreenPlayerImage = () => {
</TextTitle> </TextTitle>
<TextTitle <TextTitle
key="fs-artists" key="fs-artists"
align="center"
order={3} order={3}
style={{ style={{
textShadow: 'var(--fullscreen-player-text-shadow)', textShadow: 'var(--fullscreen-player-text-shadow)',
@ -291,7 +287,7 @@ export const FullScreenPlayerImage = () => {
{index > 0 && ( {index > 0 && (
<Text <Text
$secondary $secondary
sx={{ style={{
display: 'inline-block', display: 'inline-block',
padding: '0 0.5rem', padding: '0 0.5rem',
}} }}
@ -317,8 +313,8 @@ export const FullScreenPlayerImage = () => {
))} ))}
</TextTitle> </TextTitle>
<Group <Group
justify="center"
mt="sm" mt="sm"
position="center"
> >
{currentSong?.container && ( {currentSong?.container && (
<Badge size="lg"> <Badge size="lg">

View File

@ -1,10 +1,9 @@
import { Group } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { HiOutlineQueueList } from 'react-icons/hi2'; import { HiOutlineQueueList } from 'react-icons/hi2';
import { RiFileMusicLine, RiFileTextLine } from 'react-icons/ri'; import { RiFileMusicLine, RiFileTextLine } from 'react-icons/ri';
import styled from 'styled-components'; import styled from 'styled-components';
import { Button } from '/@/renderer/components'; import { Button, Group } from '/@/renderer/components';
import { PlayQueue } from '/@/renderer/features/now-playing'; import { PlayQueue } from '/@/renderer/features/now-playing';
import { import {
useFullScreenPlayerStore, useFullScreenPlayerStore,
@ -91,23 +90,23 @@ export const FullScreenPlayerQueue = () => {
<Group <Group
grow grow
align="center" align="center"
position="center" justify="center"
> >
{headerItems.map((item) => ( {headerItems.map((item) => (
<HeaderItemWrapper key={`tab-${item.label}`}> <HeaderItemWrapper key={`tab-${item.label}`}>
<Button <Button
fullWidth fullWidth
uppercase
fw="600" fw="600"
pos="relative" pos="relative"
size="lg" size="lg"
sx={{ style={{
alignItems: 'center', alignItems: 'center',
color: item.active color: item.active
? 'var(--main-fg) !important' ? 'var(--main-fg) !important'
: 'var(--main-fg-secondary) !important', : 'var(--main-fg-secondary) !important',
letterSpacing: '1px', letterSpacing: '1px',
}} }}
tt="uppercase"
variant="subtle" variant="subtle"
onClick={item.onClick} onClick={item.onClick}
> >

View File

@ -1,5 +1,4 @@
import { useLayoutEffect, useRef } from 'react'; import { useLayoutEffect, useRef } from 'react';
import { Divider, Group } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import { Variants, motion } from 'framer-motion'; import { Variants, motion } from 'framer-motion';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -8,6 +7,8 @@ import { useLocation } from 'react-router';
import styled from 'styled-components'; import styled from 'styled-components';
import { import {
Button, Button,
Divider,
Group,
NumberInput, NumberInput,
Option, Option,
Popover, Popover,
@ -108,17 +109,16 @@ const Controls = () => {
return ( return (
<Group <Group
gap="sm"
p="1rem" p="1rem"
pos="absolute" pos="absolute"
spacing="sm" style={{
sx={{
left: 0, left: 0,
top: 10, top: 10,
}} }}
> >
<Button <Button
compact size="compact-sm"
size="sm"
tooltip={{ label: t('common.minimize', { postProcess: 'titleCase' }) }} tooltip={{ label: t('common.minimize', { postProcess: 'titleCase' }) }}
variant="subtle" variant="subtle"
onClick={handleToggleFullScreenPlayer} onClick={handleToggleFullScreenPlayer}
@ -128,8 +128,7 @@ const Controls = () => {
<Popover position="bottom-start"> <Popover position="bottom-start">
<Popover.Target> <Popover.Target>
<Button <Button
compact size="compact-sm"
size="sm"
tooltip={{ label: t('common.configure', { postProcess: 'titleCase' }) }} tooltip={{ label: t('common.configure', { postProcess: 'titleCase' }) }}
variant="subtle" variant="subtle"
> >
@ -229,7 +228,7 @@ const Controls = () => {
/> />
</Option.Control> </Option.Control>
</Option> </Option>
<Divider my="sm" /> <Divider marginY="sm" />
<Option> <Option>
<Option.Label> <Option.Label>
{t('page.fullscreenPlayer.config.followCurrentLyric', { {t('page.fullscreenPlayer.config.followCurrentLyric', {
@ -283,8 +282,8 @@ const Controls = () => {
</Option.Label> </Option.Label>
<Option.Control> <Option.Control>
<Group <Group
noWrap
w="100%" w="100%"
wrap="nowrap"
> >
<Slider <Slider
defaultValue={lyricConfig.fontSize} defaultValue={lyricConfig.fontSize}
@ -323,8 +322,8 @@ const Controls = () => {
</Option.Label> </Option.Label>
<Option.Control> <Option.Control>
<Group <Group
noWrap
w="100%" w="100%"
wrap="nowrap"
> >
<Slider <Slider
defaultValue={lyricConfig.gap} defaultValue={lyricConfig.gap}
@ -393,7 +392,7 @@ const Controls = () => {
/> />
</Option.Control> </Option.Control>
</Option> </Option>
<Divider my="sm" /> <Divider marginY="sm" />
<TableConfigDropdown type="fullScreen" /> <TableConfigDropdown type="fullScreen" />
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>

View File

@ -162,7 +162,7 @@ export const LeftControls = () => {
/> />
) : ( ) : (
<Center <Center
sx={{ style={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
height: '100%', height: '100%',
}} }}
@ -177,11 +177,10 @@ export const LeftControls = () => {
{!collapsed && ( {!collapsed && (
<Button <Button
compact
opacity={0.8} opacity={0.8}
radius={50} radius={50}
size="md" size="compact-md"
sx={{ style={{
cursor: 'default', cursor: 'default',
position: 'absolute', position: 'absolute',
right: 2, right: 2,
@ -209,9 +208,9 @@ export const LeftControls = () => {
<MetadataStack layout="position"> <MetadataStack layout="position">
<LineItem> <LineItem>
<Group <Group
noWrap
align="flex-start" align="flex-start"
spacing="xs" gap="xs"
wrap="nowrap"
> >
<Text <Text
$link $link
@ -225,7 +224,7 @@ export const LeftControls = () => {
</Text> </Text>
{isSongDefined && ( {isSongDefined && (
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
onClick={(e) => handleGeneralContextMenu(e, [currentSong!])} onClick={(e) => handleGeneralContextMenu(e, [currentSong!])}
> >

View File

@ -1,5 +1,4 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Flex, Group } from '@mantine/core';
import { useHotkeys, useMediaQuery } from '@mantine/hooks'; import { useHotkeys, useMediaQuery } from '@mantine/hooks';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -26,7 +25,7 @@ import { useRightControls } from '../hooks/use-right-controls';
import { PlayerButton } from './player-button'; import { PlayerButton } from './player-button';
import { LibraryItem, QueueSong, ServerType, Song } from '/@/renderer/api/types'; import { LibraryItem, QueueSong, ServerType, Song } from '/@/renderer/api/types';
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared'; import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
import { DropdownMenu, Rating } from '/@/renderer/components'; import { DropdownMenu, Flex, Group, Rating } from '/@/renderer/components';
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider'; import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
import { Slider } from '/@/renderer/components/slider'; import { Slider } from '/@/renderer/components/slider';
@ -213,9 +212,9 @@ export const RightControls = () => {
)} )}
</Group> </Group>
<Group <Group
noWrap
align="center" align="center"
spacing="xs" gap="xs"
wrap="nowrap"
> >
<DropdownMenu <DropdownMenu
withArrow withArrow
@ -272,7 +271,7 @@ export const RightControls = () => {
<RiHeartLine size="1.1rem" /> <RiHeartLine size="1.1rem" />
) )
} }
sx={{ style={{
svg: { svg: {
fill: !currentSong?.userFavorite fill: !currentSong?.userFavorite
? undefined ? undefined
@ -297,8 +296,8 @@ export const RightControls = () => {
/> />
) : null} ) : null}
<Group <Group
noWrap gap="xs"
spacing="xs" wrap="nowrap"
> >
<PlayerButton <PlayerButton
icon={ icon={

View File

@ -129,7 +129,7 @@ export const ShuffleAllModal = ({
}, [musicFolders]); }, [musicFolders]);
return ( return (
<Stack spacing="md"> <Stack gap="md">
<NumberInput <NumberInput
required required
label="How many tracks?" label="How many tracks?"
@ -188,7 +188,7 @@ export const ShuffleAllModal = ({
<Divider /> <Divider />
<Group grow> <Group grow>
<Button <Button
leftIcon={<RiAddBoxFill size="1rem" />} leftSection={<RiAddBoxFill size="1rem" />}
type="submit" type="submit"
variant="default" variant="default"
onClick={() => handlePlay(Play.LAST)} onClick={() => handlePlay(Play.LAST)}
@ -196,7 +196,7 @@ export const ShuffleAllModal = ({
Add Add
</Button> </Button>
<Button <Button
leftIcon={<RiAddCircleFill size="1rem" />} leftSection={<RiAddCircleFill size="1rem" />}
type="submit" type="submit"
variant="default" variant="default"
onClick={() => handlePlay(Play.NEXT)} onClick={() => handlePlay(Play.NEXT)}
@ -205,7 +205,7 @@ export const ShuffleAllModal = ({
</Button> </Button>
</Group> </Group>
<Button <Button
leftIcon={<RiPlayFill size="1rem" />} leftSection={<RiPlayFill size="1rem" />}
type="submit" type="submit"
variant="filled" variant="filled"
onClick={() => handlePlay(Play.NOW)} onClick={() => handlePlay(Play.NOW)}

View File

@ -229,7 +229,7 @@ export const AddToPlaylistContextModal = ({
})} })}
{...form.getInputProps('skipDuplicates', { type: 'checkbox' })} {...form.getInputProps('skipDuplicates', { type: 'checkbox' })}
/> />
<Group position="right"> <Group align="flex-end">
<Group> <Group>
<Button <Button
disabled={addToPlaylistMutation.isLoading} disabled={addToPlaylistMutation.isLoading}

View File

@ -144,7 +144,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
</Stack> </Stack>
)} )}
<Group position="right"> <Group align="flex-end">
<Button <Button
variant="subtle" variant="subtle"
onClick={onCancel} onClick={onCancel}

View File

@ -159,15 +159,15 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
return ( return (
<ContentContainer> <ContentContainer>
<Group <Group
justify="space-between"
p="1rem" p="1rem"
position="apart"
> >
<Group> <Group>
<PlayButton onClick={() => handlePlay()} /> <PlayButton onClick={() => handlePlay()} />
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size={20} /> <RiMoreFill size={20} />
@ -199,10 +199,10 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
<Button <Button
compact
uppercase
component={Link} component={Link}
size="compact-md"
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })} to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
tt="uppercase"
variant="subtle" variant="subtle"
> >
View full playlist View full playlist
@ -234,15 +234,15 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
/> />
</Box> </Box>
<MotionGroup <MotionGroup
justify="center"
p="2rem" p="2rem"
position="center"
onViewportEnter={handleLoadMore} onViewportEnter={handleLoadMore}
> >
<Button <Button
ref={loadMoreRef} ref={loadMoreRef}
compact
disabled={!playlistSongsQueryInfinite.hasNextPage} disabled={!playlistSongsQueryInfinite.hasNextPage}
loading={playlistSongsQueryInfinite.isFetchingNextPage} loading={playlistSongsQueryInfinite.isFetchingNextPage}
size="compact-md"
variant="subtle" variant="subtle"
onClick={handleLoadMore} onClick={handleLoadMore}
> >

View File

@ -51,7 +51,7 @@ export const PlaylistDetailHeader = forwardRef(
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack> <Stack>
<Group spacing="sm"> <Group gap="sm">
{metadataItems.map((item, index) => ( {metadataItems.map((item, index) => (
<Fragment key={`item-${item.id}-${index}`}> <Fragment key={`item-${item.id}-${index}`}>
{index > 0 && <Text $noSelect></Text>} {index > 0 && <Text $noSelect></Text>}

View File

@ -318,7 +318,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
[page, setPage], [page, setPage],
); );
const handleTableColumns = (values: TableColumn[]) => { const handleTableColumns = (values: TableColumn[] | any[]) => {
const existingColumns = page.table.columns; const existingColumns = page.table.columns;
if (values.length === 0) { if (values.length === 0) {
@ -394,15 +394,14 @@ export const PlaylistDetailSongListHeaderFilters = ({
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
ref={cq.ref} ref={cq.ref}
spacing="sm" gap="sm"
w="100%" w="100%"
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -430,9 +429,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size="1.3rem" /> <RiMoreFill size="1.3rem" />
@ -440,26 +438,26 @@ export const PlaylistDetailSongListHeaderFilters = ({
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiPlayFill />} leftSection={<RiPlayFill />}
onClick={() => handlePlay(Play.NOW)} onClick={() => handlePlay(Play.NOW)}
> >
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddBoxFill />} leftSection={<RiAddBoxFill />}
onClick={() => handlePlay(Play.LAST)} onClick={() => handlePlay(Play.LAST)}
> >
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiAddCircleFill />} leftSection={<RiAddCircleFill />}
onClick={() => handlePlay(Play.NEXT)} onClick={() => handlePlay(Play.NEXT)}
> >
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiEditFill />} leftSection={<RiEditFill />}
onClick={() => onClick={() =>
openUpdatePlaylistModal({ openUpdatePlaylistModal({
playlist: detailQuery.data!, playlist: detailQuery.data!,
@ -470,14 +468,14 @@ export const PlaylistDetailSongListHeaderFilters = ({
{t('action.editPlaylist', { postProcess: 'sentenceCase' })} {t('action.editPlaylist', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiDeleteBinFill />} leftSection={<RiDeleteBinFill />}
onClick={openDeletePlaylistModal} onClick={openDeletePlaylistModal}
> >
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })} {t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiRefreshLine />} leftSection={<RiRefreshLine />}
onClick={handleRefresh} onClick={handleRefresh}
> >
{t('action.refresh', { postProcess: 'sentenceCase' })} {t('action.refresh', { postProcess: 'sentenceCase' })}
@ -505,8 +503,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
> >
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiSettings3Fill size="1.3rem" /> <RiSettings3Fill size="1.3rem" />
@ -548,7 +545,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
<DropdownMenu.Item <DropdownMenu.Item
closeMenuOnClick={false} closeMenuOnClick={false}
component="div" component="div"
sx={{ cursor: 'default' }} style={{ cursor: 'default' }}
> >
<Stack> <Stack>
<MultiSelect <MultiSelect
@ -560,7 +557,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
width={300} width={300}
onChange={handleTableColumns} onChange={handleTableColumns}
/> />
<Group position="apart"> <Group justify="space-between">
<Text>Auto Fit Columns</Text> <Text>Auto Fit Columns</Text>
<Switch <Switch
defaultChecked={page.table.autoFit} defaultChecked={page.table.autoFit}

View File

@ -43,7 +43,7 @@ export const PlaylistDetailSongListHeader = ({
const isSmartPlaylist = detailQuery?.data?.rules; const isSmartPlaylist = detailQuery?.data?.rules;
return ( return (
<Stack spacing={0}> <Stack gap={0}>
<PageHeader backgroundColor="var(--titlebar-bg)"> <PageHeader backgroundColor="var(--titlebar-bg)">
<LibraryHeaderBar> <LibraryHeaderBar>
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} /> <LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />

View File

@ -235,7 +235,7 @@ export const PlaylistListHeaderFilters = ({
[pageKey, setDisplayType], [pageKey, setDisplayType],
); );
const handleTableColumns = (values: TableColumn[]) => { const handleTableColumns = (values: TableColumn[] | any[]) => {
const existingColumns = table.columns; const existingColumns = table.columns;
if (values.length === 0) { if (values.length === 0) {
@ -288,15 +288,14 @@ export const PlaylistListHeaderFilters = ({
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
ref={cq.ref} ref={cq.ref}
spacing="sm" gap="sm"
w="100%" w="100%"
> >
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -322,8 +321,7 @@ export const PlaylistListHeaderFilters = ({
/> />
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
compact size="compact-md"
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }} tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle" variant="subtle"
onClick={handleRefresh} onClick={handleRefresh}
@ -334,9 +332,8 @@ export const PlaylistListHeaderFilters = ({
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact
fw="600" fw="600"
size="md" size="compact-md"
variant="subtle" variant="subtle"
> >
<RiMoreFill size="1.3rem" /> <RiMoreFill size="1.3rem" />
@ -344,7 +341,7 @@ export const PlaylistListHeaderFilters = ({
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
icon={<RiRefreshLine />} leftSection={<RiRefreshLine />}
onClick={handleRefresh} onClick={handleRefresh}
> >
{t('common.refresh', { postProcess: 'titleCase' })} {t('common.refresh', { postProcess: 'titleCase' })}
@ -359,8 +356,7 @@ export const PlaylistListHeaderFilters = ({
> >
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact size="compact-md"
size="md"
variant="subtle" variant="subtle"
> >
<RiSettings3Fill size="1.3rem" /> <RiSettings3Fill size="1.3rem" />
@ -437,7 +433,7 @@ export const PlaylistListHeaderFilters = ({
<DropdownMenu.Item <DropdownMenu.Item
closeMenuOnClick={false} closeMenuOnClick={false}
component="div" component="div"
sx={{ cursor: 'default' }} style={{ cursor: 'default' }}
> >
<Stack> <Stack>
<MultiSelect <MultiSelect
@ -449,7 +445,7 @@ export const PlaylistListHeaderFilters = ({
width={300} width={300}
onChange={handleTableColumns} onChange={handleTableColumns}
/> />
<Group position="apart"> <Group justify="space-between">
<Text> <Text>
{t('table.config.general.autoFitColumns', { {t('table.config.general.autoFitColumns', {
postProcess: 'titleCase', postProcess: 'titleCase',

View File

@ -52,7 +52,7 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
return ( return (
<Stack <Stack
ref={cq.ref} ref={cq.ref}
spacing={0} gap={0}
> >
<PageHeader backgroundColor="var(--titlebar-bg)"> <PageHeader backgroundColor="var(--titlebar-bg)">
<Flex <Flex

View File

@ -411,9 +411,11 @@ export const PlaylistQueryBuilder = forwardRef(
return ( return (
<MotionFlex <MotionFlex
direction="column" style={{
h="calc(100% - 3.5rem)" flexDirection: 'column',
justify="space-between" height: 'calc(100% - 3.5rem)',
justifyContent: 'space-between',
}}
> >
<ScrollArea <ScrollArea
h="100%" h="100%"
@ -446,15 +448,15 @@ export const PlaylistQueryBuilder = forwardRef(
/> />
</ScrollArea> </ScrollArea>
<Group <Group
noWrap
align="flex-end" align="flex-end"
justify="space-between"
m="1rem" m="1rem"
position="apart" wrap="nowrap"
> >
<Group <Group
noWrap gap="sm"
spacing="sm"
w="100%" w="100%"
wrap="nowrap"
> >
<Select <Select
searchable searchable
@ -489,8 +491,8 @@ export const PlaylistQueryBuilder = forwardRef(
</Group> </Group>
{onSave && onSaveAs && ( {onSave && onSaveAs && (
<Group <Group
noWrap gap="sm"
spacing="sm" wrap="nowrap"
> >
<Button <Button
loading={isSaving} loading={isSaving}
@ -519,7 +521,7 @@ export const PlaylistQueryBuilder = forwardRef(
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item <DropdownMenu.Item
$danger $danger
icon={<RiSaveLine color="var(--danger-color)" />} leftSection={<RiSaveLine color="var(--danger-color)" />}
onClick={handleSave} onClick={handleSave}
> >
{t('common.saveAndReplace', { postProcess: 'titleCase' })} {t('common.saveAndReplace', { postProcess: 'titleCase' })}

View File

@ -89,7 +89,7 @@ export const SaveAsPlaylistForm = ({
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })} {...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
/> />
)} )}
<Group position="right"> <Group justify="flex-end">
<Button <Button
variant="subtle" variant="subtle"
onClick={onCancel} onClick={onCancel}

View File

@ -117,7 +117,7 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })} {...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
/> />
)} )}
<Group position="right"> <Group justify="flex-end">
<Button <Button
variant="subtle" variant="subtle"
onClick={onCancel} onClick={onCancel}

View File

@ -179,7 +179,7 @@ const PlaylistDetailSongListRoute = () => {
> >
<Group p="1rem"> <Group p="1rem">
<Button <Button
compact size="compact-md"
variant="default" variant="default"
onClick={handleToggleExpand} onClick={handleToggleExpand}
> >

View File

@ -93,15 +93,15 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
size="lg" size="lg"
> >
<Group <Group
gap="sm"
mb="1rem" mb="1rem"
spacing="sm"
> >
{pages.map((page, index) => ( {pages.map((page, index) => (
<Fragment key={page}> <Fragment key={page}>
{index > 0 && ' > '} {index > 0 && ' > '}
<Button <Button
compact
disabled disabled
size="compact-md"
variant="default" variant="default"
> >
{page?.toLocaleUpperCase()} {page?.toLocaleUpperCase()}
@ -122,7 +122,7 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
<TextInput <TextInput
ref={searchInputRef} ref={searchInputRef}
data-autofocus data-autofocus
icon={<RiSearchLine />} leftSection={<RiSearchLine />}
rightSection={ rightSection={
<ActionIcon <ActionIcon
onClick={() => { onClick={() => {
@ -261,11 +261,11 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
mt="0.5rem" mt="0.5rem"
p="0.5rem" p="0.5rem"
> >
<Group position="apart"> <Group justify="space-between">
<Command.Loading> <Command.Loading>
{isHome && isLoading && query !== '' && <Spinner />} {isHome && isLoading && query !== '' && <Spinner />}
</Command.Loading> </Command.Loading>
<Group spacing="sm"> <Group gap="sm">
<Kbd size="md">ESC</Kbd> <Kbd size="md">ESC</Kbd>
<Kbd size="md"></Kbd> <Kbd size="md"></Kbd>
<Kbd size="md"></Kbd> <Kbd size="md"></Kbd>

Some files were not shown because too many files have changed in this diff Show More