Compare commits

..

2 Commits

Author SHA1 Message Date
83bfeda4ca [FEAT] Chakra V3 full operational 2025-01-25 01:34:11 +01:00
c489fabb77 [theme and reciepice 2025-01-24 21:57:04 +01:00
123 changed files with 15679 additions and 2691 deletions

View File

@ -29,7 +29,14 @@
"*.{ts,tsx,js,jsx,json}": "prettier --write" "*.{ts,tsx,js,jsx,json}": "prettier --write"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/cli": "3.3.1",
"@chakra-ui/react": "3.3.1",
"@emotion/react": "11.14.0",
"allotment": "1.20.2",
"css-mediaquery": "0.1.2",
"dayjs": "1.11.13",
"history": "5.3.0", "history": "5.3.0",
"next-themes": "^0.4.4",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-error-boundary": "5.0.0", "react-error-boundary": "5.0.0",
@ -41,6 +48,7 @@
"zustand": "5.0.3" "zustand": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@chakra-ui/styled-system": "^2.12.0",
"@playwright/test": "1.49.1", "@playwright/test": "1.49.1",
"@storybook/addon-actions": "8.4.7", "@storybook/addon-actions": "8.4.7",
"@storybook/addon-essentials": "8.4.7", "@storybook/addon-essentials": "8.4.7",
@ -72,6 +80,7 @@
"lint-staged": "15.3.0", "lint-staged": "15.3.0",
"npm-check-updates": "^17.1.13", "npm-check-updates": "^17.1.13",
"prettier": "3.4.2", "prettier": "3.4.2",
"puppeteer": "24.0.0",
"react-is": "19.0.0", "react-is": "19.0.0",
"storybook": "8.4.7", "storybook": "8.4.7",
"ts-node": "10.9.2", "ts-node": "10.9.2",

2068
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,109 +1,128 @@
import { useState } from 'react';
import {
Box,
Button,
ChakraProvider,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogRoot,
DialogTrigger,
SelectContent,
SelectItem,
SelectRoot,
SelectTrigger,
SelectValueText,
Stack,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { environment } from '@/environment';
import { App as SpaApp } from '@/scene/App'; import { App as SpaApp } from '@/scene/App';
import { FullPage } from './ui'; import { USERS, USERS_COLLECTION } from '@/service/session';
import { hashLocalData } from '@/utils/sso';
import { Toaster } from './components/ui/toaster';
import { systemTheme } from './theme/theme';
import { CloseButton } from './components/ui/close-button';
// const AppEnvHint = () => { const AppEnvHint = () => {
// const dialog = useDisclosure(); const dialog = useDisclosure();
// const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER'); const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
// //const setUser = useRightsStore((store) => store.setUser); //const setUser = useRightsStore((store) => store.setUser);
// const buildEnv = const buildEnv =
// process.env.NODE_ENV === 'development' process.env.NODE_ENV === 'development'
// ? 'Development' ? 'Development'
// : import.meta.env.VITE_DEV_ENV_NAME; : import.meta.env.VITE_DEV_ENV_NAME;
// const envName: Array<string> = []; const envName: Array<string> = [];
// !!buildEnv && envName.push(buildEnv); !!buildEnv && envName.push(buildEnv);
// if (!envName.length) { if (!envName.length) {
// return null; return null;
// } }
// const handleChange = (selectedOption) => { const handleChange = (selectedOption) => {
// console.log(`SELECT: [${selectedOption.target.value}]`); console.log(`SELECT: [${selectedOption.target.value}]`);
// setSelectUserTest(selectedOption.target.value); setSelectUserTest(selectedOption.target.value);
// }; };
// const onClose = () => { const onClose = () => {
// dialog.onClose(); dialog.onClose();
// if (selectUserTest == 'NO_USER') { if (selectUserTest == 'NO_USER') {
// window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`; window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
// } else { } else {
// window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`; window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
// } }
// }; };
// return ( return (
// <> <>
// <div style={{ <Box
// zIndex: "100000", as="button"
// position: "fixed", zIndex="100000"
// top: "0", position="fixed"
// height: "2px", top="0"
// background: "warning.400", insetStart="0"
// cursor: "pointer" insetEnd="0"
// }} h="2px"
// data-test-id="devtools" bg="warning.400"
// onClick={dialog.onOpen} cursor="pointer"
// > data-test-id="devtools"
// <div style={{ onClick={dialog.onOpen}
// position: "fixed", >
// top: "0", <Text
// background: "warning.400", position="fixed"
// color: "warning.900", top="0"
// fontSize: "0.6rem", insetStart="4"
// fontWeight: "bold", bg="warning.400"
// paddingLeft: "10px", color="warning.900"
// paddingRight: "10px", fontSize="0.6rem"
// marginLeft: "25%", fontWeight="bold"
// borderRadius: "10px 0 10px 0", px="10px"
// textTransform: "uppercase", marginLeft="25%"
// }} borderBottomStartRadius="sm"
// > borderBottomEndRadius="sm"
// {envName.join(' : ')} textTransform="uppercase"
// </div> >
// </div> {envName.join(' : ')}
// <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}> </Text>
// <Dialog.Trigger asChild> </Box >
// <Button> <DialogRoot open={dialog.open} onOpenChange={dialog.onClose}>
// {dialog.open ? "Close" : "Open"} Dialog <DialogContent>
// </Button> <DialogHeader>Outils développeurs</DialogHeader>
// </Dialog.Trigger> <DialogBody>
// <Portal> <Stack>
// <Dialog.Backdrop /> <Text>User</Text>
// <Dialog.Positioner> <SelectRoot onChange={handleChange} collection={USERS_COLLECTION}>
// <Dialog.Content> <SelectTrigger>
// <Dialog.Title>Outils développeurs</Dialog.Title> <SelectValueText placeholder="Select test user" />
// <Dialog.Description> </SelectTrigger>
// <HStack> <SelectContent>
// <Text>User</Text> {USERS_COLLECTION.items.map((value) => (
// <Select.Root onChange={handleChange} collection={USERS_COLLECTION}> <SelectItem item={value} key={value.value}>
// <Select.Trigger> {value.label}
// <SelectValueText placeholder="Select test user" /> </SelectItem>
// </Select.Trigger> ))}
// <Select.Content> </SelectContent>
// {USERS_COLLECTION.items.map((value) => ( </SelectRoot>
// <Select.Item item={value} key={value.value}> </Stack>
// {value.label} </DialogBody>
// </Select.Item> <DialogFooter>
// ))} <Button onClick={onClose}>Close</Button>
// </Select.Content> </DialogFooter>
// </Select.Root> </DialogContent>
// </HStack> </DialogRoot>
// </Dialog.Description> </>
// <Dialog.CloseTrigger> );
// <Button onClick={onClose}>Apply</Button> };
// </Dialog.CloseTrigger>
// </Dialog.Content>
// </Dialog.Positioner>
// </Portal>
// </Dialog.Root>
// </>
// );
// };
const App = () => { const App = () => {
return ( return (
<FullPage data-test-id="Full-root-page"> <ChakraProvider value={systemTheme}>
{/* <AppEnvHint /> */} <AppEnvHint />
<SpaApp data-test-id="app" /> <SpaApp />
{/* <Toaster /> */} <Toaster />
</FullPage> </ChakraProvider>
); );
}; };

View File

@ -1,6 +1,12 @@
import { SyntheticEvent, useEffect, useRef, useState } from 'react'; import { SyntheticEvent, useEffect, useRef, useState } from 'react';
import {
Box,
Flex,
IconButton,
SliderTrack,
Text,
} from '@chakra-ui/react';
import { import {
MdFastForward, MdFastForward,
MdFastRewind, MdFastRewind,
@ -22,10 +28,10 @@ import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track'; import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { Flex, Text } from '@/ui'; import { Slider } from './ui/slider';
export enum PlayMode { export enum PlayMode {
PLAY_ONE, PLAY_ONE,
@ -35,10 +41,10 @@ export enum PlayMode {
} }
const playModeIcon = { const playModeIcon = {
[PlayMode.PLAY_ONE]: <MdLooksOne size="30px" />, [PlayMode.PLAY_ONE]: <MdLooksOne style={{ width: "100%", height: "100%" }} />,
[PlayMode.PLAY_ALL]: <MdTrendingFlat size="30px" />, [PlayMode.PLAY_ALL]: <MdTrendingFlat style={{ width: "100%", height: "100%" }} />,
[PlayMode.PLAY_ONE_LOOP]: <MdRepeatOne size="30px" />, [PlayMode.PLAY_ONE_LOOP]: <MdRepeatOne style={{ width: "100%", height: "100%" }} />,
[PlayMode.PLAY_ALL_LOOP]: <MdRepeat size="30px" />, [PlayMode.PLAY_ALL_LOOP]: <MdRepeat style={{ width: "100%", height: "100%" }} />,
}; };
export type AudioPlayerProps = {}; export type AudioPlayerProps = {};
@ -77,12 +83,11 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
: '' : ''
); );
}, [dataTrack, setMediaSource]); }, [dataTrack, setMediaSource]);
const backColor = useColorThemeValue('back.100', 'back.800'); const backColor = useColorModeValue('back.100', 'back.800');
const configButton = { const configButton = {
borderRadius: 'full', borderRadius: 'full',
backgroundColor: '#00000000', backgroundColor: 'transparent',
_hover: { _hover: {
boxShadow: 'outline-over',
bgColor: 'brand.500', bgColor: 'brand.500',
}, },
width: "50px", width: "50px",
@ -200,133 +205,141 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
const onChangeStateToPause = () => { const onChangeStateToPause = () => {
setIsPlaying(false); setIsPlaying(false);
}; };
const marks = () => {
const minutes = Math.floor(duration / 60);
const result: number[] = [];
for (let i = 1; i <= minutes; i++) {
result.push(60 * i);
}
return result;
}
return ( return (
<> <>
{!isNullOrUndefined(trackOffset) && ( {!isNullOrUndefined(trackOffset) && (
<Flex <Flex
style={{ position="absolute"
position: "absolute", height="150px"
height: "150px", minHeight="150px"
minHeight: "150px", paddingY="5px"
padding: "5px 10px 5px 10px", paddingX="10px"
margin: "0 15px 0 15px", marginX="15px"
bottom: 0, bottom={0}
left: 0, left={0}
right: 0, right={0}
zIndex: 1000, zIndex={1000}
borderWidth: "1px", borderWidth="1px"
borderColor: "brand.900", borderColor="brand.900"
backgroundColor: backColor, bgColor={backColor}
borderRadius: "10px 10px 0 0", borderTopRadius="10px"
}}
direction="column" direction="column"
> >
<Text <Text
alignContent="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
style={{ userSelect="none"
alignContent: "left", marginRight="auto"
userSelect: "none", overflow="hidden"
marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1} // noOfLines={1}
> >
{dataTrack?.name ?? '???'} {dataTrack?.name ?? '???'}
</Text> </Text>
<Text <Text
alignContent="left"
fontSize="16px" fontSize="16px"
style={{ userSelect="none"
alignContent: "left", marginRight="auto"
userSelect: "none", overflow="hidden"
marginRight: "auto", // noOfLines={1}
overflow: "hidden",
//noOfLines:1
}}
> >
{dataArtists.map((data) => data.name).join(', ')} /{' '} {dataArtists.map((data) => data.name).join(', ')} /{' '}
{dataAlbum && dataAlbum?.name} {dataAlbum && dataAlbum?.name}
{dataGender && ` / ${dataGender.name}`} {dataGender && ` / ${dataGender.name}`}
</Text> </Text>
<Flex style={{ width: "full", padding: "0 15px 0 15px" }}> <Box width="full" paddingX="15px">
<>TODO ... </> <Slider
{/* <Slider.Root
defaultValue={[0]} defaultValue={[0]}
value={[timeProgress]} value={[timeProgress]}
min={0} min={0}
max={duration} max={duration}
step={0.1} step={0.1}
onChange={onSeek} onValueChange={(e) => onSeek(e.value)}
variant="outline" variant="outline"
// focusThumbOnChange={false} colorPalette="brand"
marks={marks()}
//focusCapture={false}
> >
<SliderTrack bg="gray.200" height="10px" borderRadius="full"> <SliderTrack bg="brand.200" height="10px" borderRadius="full">
{/ * <SliderFilledTrack bg="brand.600" /> * /}
</SliderTrack> </SliderTrack>
</Slider.Root> */} </Slider>
</Flex> </Box>
<Flex> <Flex>
<Text <Text
alignContent="left"
fontSize="16px" fontSize="16px"
style={{ userSelect="none"
alignContent: "left", marginRight="auto"
userSelect: "none", overflow="hidden"
marginRight: "auto", // noOfLines={1}
overflow: "hidden",
// noOfLines={1},
}}
> >
{formatTime(timeProgress)} {formatTime(timeProgress)}
</Text> </Text>
<Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}> <Text alignContent="left" fontSize="16px" userSelect="none">
{formatTime(duration)} {formatTime(duration)}
</Text> </Text>
</Flex> </Flex>
{/* <Flex gap="5px"> <Flex gap="5px">
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Play'} aria-label={'Play'}
onClick={onPlay} onClick={onPlay}
variant="ghost"
> >
{isPlaying ? ( {isPlaying ? (
<MdPause size="30px" /> <MdPause style={{ width: "100%", height: "100%" }} />
) : ( ) : (
<MdPlayArrow size="30px" /> <MdPlayArrow style={{ width: "100%", height: "100%" }} />
)} )}
</IconButton> </IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Stop'} aria-label={'Stop'}
onClick={onStop} onClick={onStop}
><MdStop size="30px" /></IconButton> variant="ghost"
><MdStop style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Previous track'} aria-label={'Previous track'}
onClick={onNavigatePrevious} onClick={onNavigatePrevious}
marginLeft="auto" marginLeft="auto"
><MdNavigateBefore size="30px" /> </IconButton> variant="ghost"
><MdNavigateBefore style={{ width: "100%", height: "100%" }} /> </IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'jump 15sec in past'} aria-label={'jump 15sec in past'}
onClick={onFastRewind} onClick={onFastRewind}
><MdFastRewind size="30px" /></IconButton> variant="ghost"
><MdFastRewind style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'jump 15sec in future'} aria-label={'jump 15sec in future'}
onClick={onFastForward} onClick={onFastForward}
variant="ghost"
><MdFastForward style={{ width: "100%", height: "100%" }} /></IconButton> ><MdFastForward style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Next track'} aria-label={'Next track'}
marginRight="auto" marginRight="auto"
onClick={onNavigateNext} onClick={onNavigateNext}
variant="ghost"
><MdNavigateNext style={{ width: "100%", height: "100%" }} /></IconButton> ><MdNavigateNext style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'continue to the end'} aria-label={'continue to the end'}
onClick={onTypePlay} onClick={onTypePlay}
variant="ghost"
>{playModeIcon[playingMode]}</IconButton> >{playModeIcon[playingMode]}</IconButton>
</Flex> */} </Flex>
</Flex> </Flex>
)} )}

View File

@ -1,13 +1,15 @@
import { CSSProperties, ReactElement, useEffect, useState } from 'react'; import { ReactElement, useEffect, useState } from 'react';
import { Box, BoxProps, Flex, FlexProps } from '@chakra-ui/react';
import { Image } from '@chakra-ui/react';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { ObjectId } from '@/back-api'; import { ObjectId } from '@/back-api';
import { DivProps, Flex } from '@/ui';
export type CoversProps = Omit<DivProps, "iconEmpty"> & { export type CoversProps = Omit<BoxProps, "iconEmpty"> & {
data?: ObjectId[]; data?: ObjectId[];
size?: CSSProperties["width"]; size?: BoxProps["width"];
iconEmpty?: ReactElement; iconEmpty?: ReactElement;
slideshow?: boolean; slideshow?: boolean;
}; };
@ -43,28 +45,26 @@ export const Covers = ({
return <Icon icon={iconEmpty} sizeIcon={size} />; return <Icon icon={iconEmpty} sizeIcon={size} />;
} else { } else {
return ( return (
<Flex <Box
style={{ width={size}
width: size, height={size}
height: size, minHeight={size}
minHeight: size, minWidth={size}
minWidth: size, borderColor="blue"
borderColor: "blue", borderWidth="1px"
borderWidth: "1px", margin="auto"
margin: "auto",
}}
{...rest} {...rest}
></Flex> ></Box>
); );
} }
} }
if (slideshow === false || data.length === 1) { if (slideshow === false || data.length === 1) {
const url = DataUrlAccess.getThumbnailUrl(data[0]); const url = DataUrlAccess.getThumbnailUrl(data[0]);
return <></>;//<image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />; return <Image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />;
} }
const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]); const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]);
const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]); const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]);
return <></>/*<Flex return <Flex
position="relative" position="relative"
// {...rest} // {...rest}
maxWidth={size} maxWidth={size}
@ -95,5 +95,5 @@ export const Covers = ({
opacity={topOpacity} opacity={topOpacity}
zIndex={2} zIndex={2}
/> />
</Flex>*/ </Flex>
}; };

View File

@ -1,15 +1,13 @@
import { Flex } from "@/ui"; import { Box } from '@chakra-ui/react';
export const EmptyEnd = () => { export const EmptyEnd = () => {
return ( return (
<Flex <Box
style={{ width="full"
width: "full", height="25%"
height: "25%", minHeight="250px"
minHeight: "250px",
}}
// borderWidth="1px" // borderWidth="1px"
// borderColor="red" // borderColor="red"
></Flex> ></Box>
); );
}; };

View File

@ -1,5 +1,8 @@
import {
import { Flex, FlexProps } from '@/ui'; Box,
Flex,
FlexProps,
} from '@chakra-ui/react';
import { forwardRef, ReactNode } from 'react'; import { forwardRef, ReactNode } from 'react';
export type IconProps = FlexProps & { export type IconProps = FlexProps & {
@ -9,31 +12,27 @@ export type IconProps = FlexProps & {
}; };
export const Icon = forwardRef<HTMLDivElement, IconProps>( export const Icon = forwardRef<HTMLDivElement, IconProps>(
({ icon: IconEl, color, sizeIcon = '1em', style, ...rest }, ref) => { ({ icon: IconEl, color, sizeIcon = '1em', ...rest }, ref) => {
return ( return (
<Flex <Flex flex="none"
minWidth={sizeIcon}
minHeight={sizeIcon}
maxWidth={sizeIcon}
maxHeight={sizeIcon}
align="center" align="center"
style={{ padding="1px"
flex: "none", ref={ref}
minWidth: sizeIcon,
minHeight: sizeIcon,
maxWidth: sizeIcon,
maxHeight: sizeIcon,
padding: "1px",
...style
}}
{...rest}> {...rest}>
<Flex <Box
style={{ marginX="auto"
margin: "0 auto 0 auto", width="100%"
width: "100%", minWidth="100%"
minWidth: "100%", height="100%"
height: "100%", color={color}
color: color, asChild
}}
> >
{IconEl} {IconEl}
</Flex> </Box>
</Flex> </Flex>
); );
} }

View File

@ -1,10 +1,10 @@
import React, { ReactNode, useEffect } from 'react'; import React, { ReactNode, useEffect } from 'react';
import { Flex, Image } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import background from '@/assets/images/ikon.svg'; import background from '@/assets/images/ikon.svg';
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar'; import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
import { Flex } from '@/ui';
export type LayoutProps = React.PropsWithChildren<unknown> & { export type LayoutProps = React.PropsWithChildren<unknown> & {
topBar?: ReactNode; topBar?: ReactNode;
@ -20,33 +20,30 @@ export const PageLayout = ({ children }: LayoutProps) => {
return ( return (
<> <>
<Flex <Flex
style={{ minH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
position: "absolute", maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, position="absolute"
maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, top={TOP_BAR_HEIGHT}
top: TOP_BAR_HEIGHT, bottom={0}
bottom: 0, left={0}
left: 0, right={0}
right: 0, minWidth="300px"
minWidth: "300px", zIndex={-1}
zIndex: -1,
}}
> >
{/* <Image src={background} boxSize="90%" margin="auto" opacity="30%" /> */} <Image src={background} boxSize="90%" margin="auto" opacity="30%" />
</Flex> </Flex>
<Flex <Flex
direction="column" direction="column"
style={{ overflowX="auto"
overflow: "auto", overflowY="auto"
minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, minH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
position: "absolute", position="absolute"
top: TOP_BAR_HEIGHT, top={TOP_BAR_HEIGHT}
bottom: 0, bottom={0}
left: 0, left={0}
right: 0, right={0}
minWidth: "300px", minWidth="300px"
}}
> >
{children} {children}
</Flex> </Flex>

View File

@ -1,20 +1,19 @@
import { CSSProperties, useEffect } from 'react'; import React, { ReactNode, useEffect } from 'react';
import { Flex, FlexProps } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { colors } from '@/theme/colors'; import { colors } from '@/theme/colors';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Flex, FlexProps } from '@/ui';
export type LayoutProps = FlexProps & { export type LayoutProps = FlexProps & {
width?: CSSProperties['width']; children: ReactNode;
}; };
export const PageLayoutInfoCenter = ({ export const PageLayoutInfoCenter = ({
children, children,
width = "75%", width = '25%',
style,
...rest ...rest
}: LayoutProps) => { }: LayoutProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
@ -27,17 +26,14 @@ export const PageLayoutInfoCenter = ({
<PageLayout> <PageLayout>
<Flex <Flex
direction="column" direction="column"
style={{ margin="auto"
margin: "auto", minWidth={width}
width, border="back.900"
border: "back.900", borderWidth="1px"
borderWidth: "1px", borderRadius="8px"
borderRadius: "8px", padding="10px"
padding: "10px", boxShadow={'0px 0px 16px ' + colors.back[900]}
boxShadow: '0px 0px 16px ' + colors.back[900], backgroundColor={useColorModeValue('#FFFFFF', '#000000')}
backgroundColor: useColorThemeValue('#FFFFFF', '#000000'),
...style
}}
{...rest} {...rest}
> >
{children} {children}

View File

@ -1,5 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import {
Group,
Input,
} from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md'; import { MdSearch } from 'react-icons/md';
export type SearchInputProps = { export type SearchInputProps = {
@ -38,15 +42,15 @@ export const SearchInput = ({
onSubmitValue(inputData); onSubmitValue(inputData);
} }
} }
return (<></> return (
//<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}> <Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}>
// <MdSearch color="gray.300" /> <MdSearch color="gray.300" />
// <Input <Input
// onFocus={onFocusKeep} onFocus={onFocusKeep}
// onBlur={() => setTimeout(() => onFocusLost(), 200)} onBlur={() => setTimeout(() => onFocusLost(), 200)}
// onChange={onChange} onChange={onChange}
// onSubmit={onSubmit} onSubmit={onSubmit}
// /> />
//</Group> </Group>
); );
}; };

View File

@ -1,9 +1,20 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import {
Box,
Flex,
HStack,
IconButton,
Text,
useDisclosure,
Button,
ConditionalValue,
Span,
} from '@chakra-ui/react';
import { import {
LuAlignJustify, LuAlignJustify,
LuArrowBigLeft, LuArrowBigLeft,
LuKeySquare,
LuLogIn, LuLogIn,
LuLogOut, LuLogOut,
LuMoon, LuMoon,
@ -15,17 +26,25 @@ import { useNavigate } from 'react-router-dom';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState'; import { SessionState } from '@/service/SessionState';
import { colors } from '@/theme/colors'; import { colors } from '@/theme/colors';
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso'; import { requestOpenSite, requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import { useSessionService } from '@/service/session'; import { useSessionService } from '@/service/session';
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md'; import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
import { useColorThemeValue, useTheme } from '@/theme/ThemeContext'; import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '@/components/ui/menu';
import { useDisclosure } from '@/utils/disclosure'; import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
import { Button, Flex, Text } from '@/ui'; import {
DrawerBody,
DrawerContent,
DrawerHeader,
DrawerRoot,
} from '@/components/ui/drawer';
export const TOP_BAR_HEIGHT = '50px'; export const TOP_BAR_HEIGHT = '50px';
export const BUTTON_TOP_BAR_PROPERTY = { export const BUTTON_TOP_BAR_PROPERTY = {
theme: '@menu', variant: "ghost" as ConditionalValue<"ghost" | "outline" | "solid" | "subtle" | "surface" | "plain" | undefined>,
//colorPalette: "brand",
fontSize: '20px',
textTransform: 'uppercase',
height: TOP_BAR_HEIGHT, height: TOP_BAR_HEIGHT,
}; };
@ -35,11 +54,11 @@ export type TopBarProps = {
}; };
export const TopBar = ({ title, children }: TopBarProps) => { export const TopBar = ({ title, children }: TopBarProps) => {
const { theme, toggleTheme } = useTheme(); const { colorMode, toggleColorMode } = useColorMode();
const { clearToken } = useSessionService(); const { clearToken } = useSessionService();
const { session } = useServiceContext(); const { session } = useServiceContext();
const backColor = useColorThemeValue('back.100', 'back.800'); const backColor = useColorModeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure(); const drawerDisclose = useDisclosure();
const onChangeTheme = () => { const onChangeTheme = () => {
drawerDisclose.onOpen(); drawerDisclose.onOpen();
@ -57,6 +76,9 @@ export const TopBar = ({ title, children }: TopBarProps) => {
clearToken(); clearToken();
requestSignOut(); requestSignOut();
}; };
const onKarso = (): void => {
requestOpenSite();
};
const onSelectAdd = () => { const onSelectAdd = () => {
navigate('/add'); navigate('/add');
}; };
@ -74,104 +96,106 @@ export const TopBar = ({ title, children }: TopBarProps) => {
}; };
return ( return (
<Flex <Flex
position="absolute"
top={0}
left={0}
right={0}
height={TOP_BAR_HEIGHT}
alignItems="center"
justifyContent="space-between"
backgroundColor={backColor}
gap="2" gap="2"
align="center" px="2"
justify="space-between" boxShadow={'0px 2px 4px ' + colors.back[900]}
style={{ zIndex={200}
position: "absolute",
top: 0,
left: 0,
right: 0,
height: TOP_BAR_HEIGHT,
backgroundColor: backColor,
padding: "0 2 0 2",
boxShadow: `0px 2px 4px ${colors.back[900]}`,
zIndex: 200,
}}
> >
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}> <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<LuAlignJustify /> <HStack>
<Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> <LuAlignJustify />
Karusic <Text paddingLeft="3px" fontWeight="bold">
</Text> Karusic
</Text>
</HStack>
</Button> </Button>
{title && ( {title && (
<Text <Text
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
style={{ textTransform="uppercase"
textTransform: "uppercase", marginRight="auto"
marginRight: "auto", userSelect="none"
userSelect: "none", color="brand.500"
}}
> >
{title} {title}
</Text> </Text>
)} )}
{children} {children}
<Flex style={{ right: 0 }}> <Flex right="0">
{session?.state !== SessionState.CONNECTED && ( {session?.state !== SessionState.CONNECTED && (
<> <>
<Button {...BUTTON_TOP_BAR_PROPERTY} /*{...THEME.Button.primary}*/ onClick={onSignIn}> <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onSignIn}>
<LuLogIn /> <LuLogIn />
<Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold"> <Text paddingLeft="3px" fontWeight="bold">
Sign-in Sign-in
</Text> </Text>
</Button> </Button>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
onClick={onSignUp} onClick={onSignUp}
// disabled={true} disabled={true}
> >
<MdMore /> <MdMore />
<Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> <Text paddingLeft="3px" fontWeight="bold">
Sign-up Sign-up
</Text> </Text>
</Button> </Button>
</> </>
)} )}
{/* {session?.state === SessionState.CONNECTED && ( {session?.state === SessionState.CONNECTED && (
<Menu.Root> <MenuRoot>
<Menu.Trigger asChild> <MenuTrigger asChild>
<IconButton <IconButton
as={IconButton} asChild
aria-label="Options" aria-label="Options"
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT} width={TOP_BAR_HEIGHT}
><MdSupervisedUserCircle /></IconButton> ><MdSupervisedUserCircle /></IconButton>
</Menu.Trigger> </MenuTrigger>
<Menu.Content> <MenuContent>
<Menu.Item value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}> <MenuItem value="user" valueText="user" color={useColorModeValue('brand.800', 'brand.200')}>
<MdSupervisedUserCircle /> <MdSupervisedUserCircle />
<Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box> <Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box>
</Menu.Item> </MenuItem>
<Menu.Item value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</Menu.Item> <MenuItem value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</MenuItem>
<Menu.Item value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</Menu.Item> <MenuItem value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</MenuItem>
<Menu.Item value="Sign-out" valueText="Sign-out" onClick={onSignOut}> <MenuItem value="Sign-out" valueText="Sign-out" onClick={onSignOut}>
<LuLogOut /> Sign-out <LuLogOut /> Sign-out
</Menu.Item> </MenuItem>
<MenuItem value="karso" valueText="Karso" onClick={onKarso}>
<LuKeySquare /> Karso (SSO)
</MenuItem>
{colorMode === 'light' ? ( {colorMode === 'light' ? (
<Menu.Item value="set-dark" valueText="set-dark" onClick={toggleColorMode}> <MenuItem value="set-dark" valueText="set-dark" onClick={toggleColorMode}>
<LuMoon /> Set dark mode <LuMoon /> Set dark mode
</Menu.Item> </MenuItem>
) : ( ) : (
<Menu.Item value="set-light" valueText="set-light" onClick={toggleColorMode}> <MenuItem value="set-light" valueText="set-light" onClick={toggleColorMode}>
<LuSun /> Set light mode <LuSun /> Set light mode
</Menu.Item> </MenuItem>
)} )}
</Menu.Content> </MenuContent>
</Menu.Root> </MenuRoot>
)} */} )}
</Flex> </Flex>
{/* <Drawer.Root <DrawerRoot
placement="start" placement="start"
onOpenChange={drawerDisclose.onClose} onOpenChange={drawerDisclose.onClose}
open={drawerDisclose.open} open={drawerDisclose.open}
data-testid="top-bar_drawer-root" data-testid="top-bar_drawer-root"
> >
<Drawer.Content <DrawerContent
data-test-id="top-bar_drawer-content"> data-testid="top-bar_drawer-content">
<Drawer.Header <DrawerHeader
paddingY="auto" paddingY="auto"
as="button" as="button"
onClick={drawerDisclose.onClose} onClick={drawerDisclose.onClose}
@ -180,52 +204,57 @@ export const TopBar = ({ title, children }: TopBarProps) => {
color={useColorModeValue('brand.900', 'brand.50')} color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase" textTransform="uppercase"
> >
<HStack height={TOP_BAR_HEIGHT}> <HStack
{...BUTTON_TOP_BAR_PROPERTY} cursor="pointer">
<LuArrowBigLeft /> <LuArrowBigLeft />
<Text as="span" paddingLeft="3px"> <Span paddingLeft="3px">
Karusic Karusic
</Text> </Span>
</HStack> </HStack>
</Drawer.Header> </DrawerHeader>
<Drawer.Body paddingX="0px"> <DrawerBody paddingX="0px">
<Box marginY="3" />
<Button <Button
background="#00000000" background="#00000000"
borderRadius="0px" borderRadius="0px"
onClick={onSelectHome} onClick={onSelectHome}
width="full" width="full"
{...BUTTON_TOP_BAR_PROPERTY}
> >
<MdHome /> <MdHome style={{ width: "45px", height: "45px" }} />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> <Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Home Home
</Text> </Text>
</Button> </Button>
<hr /> <Box marginY="5" marginX="10" height="2px" background="brand.600" />
<Button <Button
background="#00000000" background="#00000000"
borderRadius="0px" borderRadius="0px"
onClick={onSelectOnAir} onClick={onSelectOnAir}
width="full" width="full"
{...BUTTON_TOP_BAR_PROPERTY}
> >
<MdOutlinePlaylistPlay /> <MdOutlinePlaylistPlay style={{ width: "45px", height: "45px" }} />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> <Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
On air On air
</Text> </Text>
</Button> </Button>
<hr /> <Box marginY="5" marginX="10" height="2px" background="brand.600" />
<Button <Button
background="#00000000" background="#00000000"
borderRadius="0px" borderRadius="0px"
onClick={onSelectAdd} onClick={onSelectAdd}
width="full" width="full"
{...BUTTON_TOP_BAR_PROPERTY}
> >
<MdOutlineUploadFile /> <MdOutlineUploadFile style={{ width: "45px", height: "45px" }} />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> <Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Add Media Add Media
</Text> </Text>
</Button> </Button>
</Drawer.Body> </DrawerBody>
</Drawer.Content> </DrawerContent>
</Drawer.Root> */} </DrawerRoot>
</Flex> </Flex>
); );
}; };

View File

@ -1,12 +1,10 @@
import { Flex, Span, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu'; import { LuDisc3 } from 'react-icons/lu';
import { Album } from '@/back-api'; import { Album } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { useCountTracksWithAlbumId } from '@/service/Track'; import { useCountTracksWithAlbumId } from '@/service/Track';
import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing'; import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
export type DisplayAlbumProps = { export type DisplayAlbumProps = {
dataAlbum?: Album; dataAlbum?: Album;
@ -27,7 +25,7 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
data={dataAlbum?.covers} data={dataAlbum?.covers}
size={BASE_WRAP_ICON_SIZE} size={BASE_WRAP_ICON_SIZE}
flex={1} flex={1}
// TODO: iconEmpty={LuDisc3} iconEmpty={<LuDisc3 />}
/> />
<Flex <Flex
direction="column" direction="column"
@ -35,33 +33,26 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
//maxWidth="150px" //maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
style={{ overflowX="hidden"
marginRight: "auto", flex={1}
overflow: "hidden",
flex: 1,
}}
> >
<Span <Span
// align="left" textAlign="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
style={{ marginRight="auto"
marginRight: "auto", overflow="hidden"
overflow: "hidden",
}}
// noOfLines={[1, 2]} // noOfLines={[1, 2]}
> >
{dataAlbum?.name} {dataAlbum?.name}
</Span> </Span>
<Span <Span
// align="left" textAlign="left"
fontSize="15px" fontSize="15px"
userSelect="none" userSelect="none"
style={{ marginRight="auto"
marginRight: "auto", overflow="hidden"
overflow: "hidden",
}}
// noOfLines={1} // noOfLines={1}
> >
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'} {countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}

View File

@ -1,8 +1,12 @@
import { ReactNode, useState } from 'react';
import { LuMenu } from 'react-icons/lu'; import { LuMenu } from 'react-icons/lu';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu';
import { Button } from '../ui/button';
export type MenuElement = { export type MenuElement = {
icon?: ReactNode;
name: string; name: string;
onClick: () => void; onClick: () => void;
}; };
@ -12,28 +16,31 @@ export type ContextMenuProps = {
}; };
export const ContextMenu = ({ elements }: ContextMenuProps) => { export const ContextMenu = ({ elements }: ContextMenuProps) => {
// if (!elements) { if (!elements) {
return <></>; return <></>;
// } }
// return ( return (
// <Menu.Root <MenuRoot
// data-testid="context-menu"> data-testid="context-menu">
// <Menu.Trigger asChild <MenuTrigger asChild
// data-testid="context-menu_trigger"> marginY="auto"
// {/* This is very stupid, we need to set as span to prevent a button in button... WTF */} marginRight="4px"
// <Button {...THEME.Button.primary} > data-testid="context-menu_trigger">
// <LuMenu /> {/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
// </Button> <Button variant="ghost" color="brand.500">
// </Menu.Trigger> <LuMenu />
// <Menu.Content </Button>
// data-testid="context-menu_content"> </MenuTrigger>
// {elements?.map((data) => ( <MenuContent
// <Menu.Item key={data.name} value={data.name} onClick={data.onClick} data-testid="context-menu_content">
// data-testid="context-menu_item"> {elements?.map((data) => (
// {data.name} <MenuItem key={data.name} value={data.name} onClick={data.onClick} height="65px" fontSize="25px"
// </Menu.Item> data-test-id="context-menu_item" >
// ))} {data.icon}
// </Menu.Content> {data.name}
// </Menu.Root > </MenuItem>
//); ))}
</MenuContent>
</MenuRoot >
);
}; };

View File

@ -3,7 +3,14 @@ import {
RefObject, RefObject,
} from 'react'; } from 'react';
import {
Box,
BoxProps,
Center,
Flex,
HStack,
Image,
} from '@chakra-ui/react';
import { import {
MdHighlightOff, MdHighlightOff,
MdUploadFile, MdUploadFile,
@ -12,7 +19,6 @@ import {
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { Flex, FlexProps } from '@/ui';
export type DragNdropProps = { export type DragNdropProps = {
onFilesSelected?: (file: File[]) => void; onFilesSelected?: (file: File[]) => void;
@ -52,42 +58,40 @@ export const DragNdrop = ({
onUriSelected(listUri); onUriSelected(listUri);
} }
}; };
return <></>;
/* return (
return ( <Box
<Box width={width}
width={width} height={height}
height={height} border="2px"
border="2px" borderRadius="5px"
borderRadius="5px" borderStyle="dashed"
borderStyle="dashed" onDrop={handleDrop}
onDrop={handleDrop} onDragOver={(event) => event.preventDefault()}
onDragOver={(event) => event.preventDefault()} >
> <label htmlFor="browse">
<label htmlFor="browse"> <Box paddingY="15%" height="100%" cursor="pointer">
<Box paddingY="15%" height="100%" cursor="pointer"> <Center>
<Center> <MdUploadFile size="50%" />
<MdUploadFile size="50%" /> </Center>
</Center> <Center>
<Center> <input
<input type="file"
type="file" hidden
hidden id="browse"
id="browse" onChange={handleFileChange}
onChange={handleFileChange} //accept=".pdf,.docx,.pptx,.txt,.xlsx"
//accept=".pdf,.docx,.pptx,.txt,.xlsx" multiple
multiple />
/> Browse files
Browse files </Center>
</Center> </Box>
</Box> </label>
</label> </Box>
</Box> );
);
*/
}; };
export type CenterIconProps = FlexProps & { export type CenterIconProps = BoxProps & {
icon: any; icon: any;
sizeIcon?: string; sizeIcon?: string;
}; };
@ -95,22 +99,19 @@ export type CenterIconProps = FlexProps & {
export const CenterIcon = ({ export const CenterIcon = ({
icon: IconEl, icon: IconEl,
sizeIcon = '15px', sizeIcon = '15px',
style,
...rest ...rest
}: CenterIconProps) => { }: CenterIconProps) => {
return ( return (
<Flex style={{ position: "relative", width: sizeIcon, height: sizeIcon, flex: "none", ...style }} <Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}>
{...rest}> <Box
{/*<Flex
as={IconEl}
w={sizeIcon} w={sizeIcon}
h={sizeIcon} h={sizeIcon}
position="absolute" position="absolute"
top="50%" top="50%"
left="50%" left="50%"
transform="translate(-50%, -50%)" transform="translate(-50%, -50%)"
/>*/} >{IconEl}</Box>
</Flex> </Box>
); );
}; };
@ -141,21 +142,20 @@ export const FormCovers = ({
isModify={form.isModify[variableName]} isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })} onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest} {...rest}
> <></> >
{/* <HStack wrap="wrap" width="full"> <HStack wrap="wrap" width="full">
{urls.map((data, index) => ( {urls.map((data, index) => (
<Flex align="flex-start" key={data}> <Flex align="flex-start" key={data}>
<Box width="125px" height="125px" position="relative"> <Box width="125px" height="125px" position="relative">
<Box width="125px" height="125px" position="absolute"> <Box width="125px" height="125px" position="absolute">
<CenterIcon <CenterIcon
icon={MdHighlightOff}
width="125px" width="125px"
sizeIcon="100%" sizeIcon="100%"
zIndex="+1" zIndex="+1"
color="#00000020" color="#00000020"
_hover={{ color: 'red' }} _hover={{ color: 'red' }}
onClick={() => onRemove && onRemove(index)} onClick={() => onRemove && onRemove(index)}
/> ><MdHighlightOff /></CenterIcon>
</Box> </Box>
<Image loading="lazy" src={data} boxSize="full" /> <Image loading="lazy" src={data} boxSize="full" />
</Box> </Box>
@ -169,7 +169,7 @@ export const FormCovers = ({
onUriSelected={onUriSelected} onUriSelected={onUriSelected}
/> />
</Flex> </Flex>
</HStack> */} </HStack>
</FormGroup> </FormGroup>
); );
}; };

View File

@ -1,7 +1,6 @@
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md'; import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md';
export type FormGroupProps = { export type FormGroupProps = {
@ -24,22 +23,20 @@ export const FormGroup = ({
onRestore, onRestore,
}: FormGroupProps) => ( }: FormGroupProps) => (
<Flex <Flex
style={{ borderLeftWidth="3px"
borderLeftWidth: "3px", borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'}
borderLeftColor: error ? 'red' : isModify ? 'blue' : '#00000000',
}}
paddingLeft="7px" paddingLeft="7px"
padding="0 4px" paddingY="4px"
direction="column" direction="column"
> >
<Flex direction="row" width="full" gap="52px"> <Flex direction="row" width="full" gap="52px">
{!!label && ( {!!label && (
<Text style={{ marginRight: "auto" }} fontWeight="bold"> <Text marginRight="auto" fontWeight="bold">
{label}{' '} {label}{' '}
{isRequired && ( {isRequired && (
<Span color="red.600"> <Text as="span" color="red.600">
* *
</Span> </Text>
)} )}
</Text> </Text>
)} )}

View File

@ -1,8 +1,9 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Input } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { Input } from '@/ui';
export type FormInputProps = { export type FormInputProps = {
form: UseFormidableReturn; form: UseFormidableReturn;

View File

@ -1,11 +1,11 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { NumberInputField, NumberInputProps, NumberInputRoot } from '../ui/number-input';
export type FormNumberProps = Pick< export type FormNumberProps = Pick<
NumberInput.RootProps, NumberInputProps,
'step' | 'defaultValue' | 'min' | 'max' 'step' | 'defaultValue' | 'min' | 'max'
> & { > & {
form: UseFormidableReturn; form: UseFormidableReturn;
@ -27,24 +27,26 @@ export const FormNumber = ({
defaultValue, defaultValue,
...rest ...rest
}: FormNumberProps) => { }: FormNumberProps) => {
const onEvent = (value) => {
form.setValues({ [variableName]: value.value });
}
return ( return (
<FormGroup <FormGroup
isModify={form.isModify[variableName]} isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })} onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest} {...rest}
> >
<></> <NumberInputRoot
{/* <NumberInput.Root
ref={ref} ref={ref}
value={form.values[variableName]} value={form.values[variableName]}
onValueChange={(value) => form.setValues({ [variableName]: value })} onValueChange={onEvent}
step={step} step={step}
defaultValue={defaultValue} defaultValue={defaultValue}
min={min} min={min}
max={max} max={max}
> >
<NumberInput.Input /> <NumberInputField />
</NumberInput.Root> */} </NumberInputRoot>
</FormGroup> </FormGroup>
); );
}; };

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Box } from '@chakra-ui/react';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { Flex } from '@/ui';
export default { export default {
title: 'Components/FormSelect', title: 'Components/FormSelect',
@ -94,7 +94,7 @@ export const DarkBackground = {
render: () => { render: () => {
const form = useFormidable<BasicFormData>({}); const form = useFormidable<BasicFormData>({});
return ( return (
<Flex style={{ padding: "4", color: "white", background: "gray.800" }}> <Box p="4" color="white" bg="gray.800">
<FormSelect <FormSelect
label="Simple Title for (DarkBackground)" label="Simple Title for (DarkBackground)"
form={form} form={form}
@ -105,7 +105,7 @@ export const DarkBackground = {
{ id: 333, name: 'third item' }, { id: 333, name: 'third item' },
]} ]}
/> />
</Flex> </Box>
); );
}, },

View File

@ -1,5 +1,6 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Text } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Box } from '@chakra-ui/react';
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple'; import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { Flex } from '@/ui';
export default { export default {
title: 'Components/FormSelectMultipleMultiple', title: 'Components/FormSelectMultipleMultiple',
@ -94,7 +94,7 @@ export const DarkBackground = {
render: () => { render: () => {
const form = useFormidable<BasicFormData>({}); const form = useFormidable<BasicFormData>({});
return ( return (
<Flex style={{ padding: "4", color: "white", background: "gray.800" }}> <Box p="4" color="white" bg="gray.800">
<FormSelectMultiple <FormSelectMultiple
label="Simple Title for (DarkBackground)" label="Simple Title for (DarkBackground)"
form={form} form={form}
@ -105,7 +105,7 @@ export const DarkBackground = {
{ id: 333, name: 'third item' }, { id: 333, name: 'third item' },
]} ]}
/> />
</Flex> </Box>
); );
}, },

View File

@ -1,5 +1,6 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Textarea } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';

View File

@ -1,10 +1,9 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu'; import { LuDisc3 } from 'react-icons/lu';
import { Gender } from '@/back-api'; import { Gender } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { useCountTracksOfAGender } from '@/service/Track'; import { useCountTracksOfAGender } from '@/service/Track';
import { Flex, Span, Text } from '@/ui';
export type DisplayGenderProps = { export type DisplayGenderProps = {
dataGender?: Gender; dataGender?: Gender;
@ -24,7 +23,7 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
data={dataGender?.covers} data={dataGender?.covers}
size="100" size="100"
height="full" height="full"
//TODO: iconEmpty={LuDisc3} iconEmpty={<LuDisc3 />}
/> />
<Flex <Flex
direction="column" direction="column"
@ -32,36 +31,31 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
maxWidth="150px" maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
style={{ overflowX="hidden"
overflowX: "hidden"
}}
> >
<Span <Text
as="span"
alignContent="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
style={{ userSelect="none"
userSelect: "none", marginRight="auto"
marginRight: "auto", overflow="hidden"
overflow: "hidden",
alignContent: "left",
}}
//TODO: noOfLines={[1, 2]} //TODO: noOfLines={[1, 2]}
> >
{dataGender?.name} {dataGender?.name}
</Span> </Text>
<Span <Text
as="span"
alignContent="left"
fontSize="15px" fontSize="15px"
style={{ userSelect="none"
userSelect: "none", marginRight="auto"
marginRight: "auto", overflow="hidden"
overflow: "hidden",
alignContent: "left",
}}
//TODO: noOfLines={1} //TODO: noOfLines={1}
> >
{countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'} {countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'}
</Span> </Text>
</Flex> </Flex>
</Flex> </Flex>
); );

View File

@ -1,5 +1,10 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Flex,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
@ -19,7 +24,9 @@ import { useAlbumService, useSpecificAlbum } from '@/service/Album';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksWithAlbumId } from '@/service/Track'; import { useCountTracksWithAlbumId } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure'; import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { Button } from '../ui/button';
export type AlbumEditPopUpProps = {}; export type AlbumEditPopUpProps = {};
@ -128,110 +135,109 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
}) })
); );
}; };
return <></>; return (
// return ( <DialogRoot
// <Dialog.Root //initialFocusRef={initialRef}
// //initialFocusRef={initialRef} //finalFocusRef={finalRef}
// //finalFocusRef={finalRef} //closeOnOverlayClick={false}
// //closeOnOverlayClick={false} //onOpenChange={onClose}
// onOpenChange={onClose} open={true}
// open={true} data-testid="album-edit-pop-up"
// data-testid="album-edit-pop-up" >
// > {/* <DialogOverlay /> */}
// {/* <DialogOverlay /> */} {/* <DialogCloseTrigger /> */}
// {/* <DialogCloseTrigger /> */} <DialogContent>
// <Dialog.Content> <DialogHeader>Edit Album</DialogHeader>
// <Dialog.Header>Edit Album</Dialog.Header> {/* <DialogCloseButton ref={finalRef} /> */}
// {/* <DialogCloseButton ref={finalRef} /> */}
// <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> <DialogBody pb={6} gap="0px" paddingLeft="18px">
// {admin && ( {admin && (
// <> <>
// <FormGroup isRequired label="Id"> <FormGroup isRequired label="Id">
// <Text>{dataAlbum?.id}</Text> <Text>{dataAlbum?.id}</Text>
// </FormGroup> </FormGroup>
// {countTracksOfAnAlbum !== 0 && ( {countTracksOfAnAlbum !== 0 && (
// <Flex paddingLeft="14px"> <Flex paddingLeft="14px">
// <MdWarning color="red.600" /> <MdWarning color="red.600" />
// <Text paddingLeft="6px" color="red.600" fontWeight="bold"> <Text paddingLeft="6px" color="red.600" fontWeight="bold">
// Can not remove album {countTracksOfAnAlbum} track(s) depend Can not remove album {countTracksOfAnAlbum} track(s) depend
// on it. on it.
// </Text> </Text>
// </Flex> </Flex>
// )} )}
// <FormGroup label="Action(s):"> <FormGroup label="Action(s):">
// <Button <Button
// onClick={disclosure.onOpen} onClick={disclosure.onOpen}
// marginRight="auto" marginRight="auto"
// theme="@danger" colorPalette="@danger"
// disabled={countTracksOfAnAlbum !== 0} disabled={countTracksOfAnAlbum !== 0}
// > >
// <MdDeleteForever /> Remove Media <MdDeleteForever /> Remove Media
// </Button> </Button>
// </FormGroup> </FormGroup>
// <ConfirmPopUp <ConfirmPopUp
// disclosure={disclosure} disclosure={disclosure}
// title="Remove album" title="Remove album"
// body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`}
// confirmTitle="Remove" confirmTitle="Remove"
// onConfirm={onRemove} onConfirm={onRemove}
// /> />
// </> </>
// )} )}
// {!admin && ( {!admin && (
// <> <>
// <FormInput <FormInput
// form={form} form={form}
// variableName="name" variableName="name"
// isRequired isRequired
// label="Title" label="Title"
// ref={initialRef} ref={initialRef}
// /> />
// <FormTextarea <FormTextarea
// form={form} form={form}
// variableName="description" variableName="description"
// label="Description" label="Description"
// /> />
// <FormInput <FormInput
// form={form} form={form}
// variableName="publication" variableName="publication"
// label="Publication" label="Publication"
// /> />
// <FormCovers <FormCovers
// form={form} form={form}
// variableName="covers" variableName="covers"
// onFilesSelected={onFilesSelected} onFilesSelected={onFilesSelected}
// onUriSelected={onUriSelected} onUriSelected={onUriSelected}
// onRemove={onRemoveCover} onRemove={onRemoveCover}
// /> />
// </> </>
// )} )}
// </Dialog.Body> </DialogBody>
// <Dialog.Footer> <DialogFooter>
// <Button <Button
// onClick={() => setAdmin((value) => !value)} onClick={() => setAdmin((value) => !value)}
// marginRight="auto" marginRight="auto"
// > >
// {admin ? ( {admin ? (
// <> <>
// <MdEdit /> <MdEdit />
// Edit Edit
// </> </>
// ) : ( ) : (
// <> <>
// <MdAdminPanelSettings /> <MdAdminPanelSettings />
// Admin Admin
// </> </>
// )} )}
// </Button> </Button>
// {!admin && form.isFormModified && ( {!admin && form.isFormModified && (
// <Button colorScheme="blue" mr={3} onClick={onSave}> <Button colorScheme="blue" mr={3} onClick={onSave}>
// Save Save
// </Button> </Button>
// )} )}
// <Button onClick={onClose}>Cancel</Button> <Button onClick={onClose}>Cancel</Button>
// </Dialog.Footer> </DialogFooter>
// </Dialog.Content> </DialogContent>
// </Dialog.Root> </DialogRoot>
// ); );
}; };

View File

@ -1,6 +1,12 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Flex,
Text,
useDisclosure,
Button,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
@ -20,7 +26,6 @@ import { useArtistService, useSpecificArtist } from '@/service/Artist';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAnArtist } from '@/service/Track'; import { useCountTracksOfAnArtist } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type ArtistEditPopUpProps = {}; export type ArtistEditPopUpProps = {};
@ -129,110 +134,111 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
); );
}; };
return ( return (
// <Dialog.Root <DialogRoot
// //initialFocusRef={initialRef} //initialFocusRef={initialRef}
// //finalFocusRef={finalRef} //finalFocusRef={finalRef}
// //closeOnOverlayClick={false} //closeOnOverlayClick={false}
// onOpenChange={onClose} onOpenChange={onClose}
// open={true} open={true}
// data-testid="artist-edit-pop-up" data-testid="artist-edit-pop-up"
// > >
// {/* <DialogOverlay /> */} {/* <DialogOverlay /> */}
// <Dialog.Content> <DialogContent>
// <Dialog.Header>Edit Artist</Dialog.Header> <DialogHeader>Edit Artist</DialogHeader>
// {/* <DialogCloseButton ref={finalRef} /> */} {/* <DialogCloseButton ref={finalRef} /> */}
// <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> <DialogBody pb={6} gap="0px" paddingLeft="18px">
// {admin && ( {admin && (
// <> <>
// <FormGroup isRequired label="Id"> <FormGroup isRequired label="Id">
// <Text>{dataArtist?.id}</Text> <Text>{dataArtist?.id}</Text>
// </FormGroup> </FormGroup>
// {countTracksOnAnArtist !== 0 && ( {countTracksOnAnArtist !== 0 && (
// <Flex paddingLeft="14px"> <Flex paddingLeft="14px">
// <MdWarning color="red.600" /> <MdWarning color="red.600" />
// <Text paddingLeft="6px" color="red.600" fontWeight="bold"> <Text paddingLeft="6px" color="red.600" fontWeight="bold">
// Can not remove artist {countTracksOnAnArtist} track(s) Can not remove artist {countTracksOnAnArtist} track(s)
// depend on it. depend on it.
// </Text> </Text>
// </Flex> </Flex>
// )} )}
// <FormGroup label="Action(s):"> <FormGroup label="Action(s):">
// <Button <Button
// onClick={disclosure.onOpen} onClick={disclosure.onOpen}
// marginRight="auto" marginRight="auto"
// theme="@danger" colorPalette="@danger"
// disabled={countTracksOnAnArtist !== 0} disabled={countTracksOnAnArtist !== 0}
// > >
// <MdDeleteForever /> Remove Media <MdDeleteForever /> Remove Media
// </Button> </Button>
// </FormGroup> </FormGroup>
// <ConfirmPopUp <ConfirmPopUp
// disclosure={disclosure} disclosure={disclosure}
// title="Remove artist" title="Remove artist"
// body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`}
// confirmTitle="Remove" confirmTitle="Remove"
// onConfirm={onRemove} onConfirm={onRemove}
// /> />
// </> </>
// )} )}
// {!admin && ( {!admin && (
// <> <>
// <FormInput <FormInput
// form={form} form={form}
// variableName="name" variableName="name"
// isRequired isRequired
// label="Artist name" label="Artist name"
// ref={initialRef} ref={initialRef}
// /> />
// <FormTextarea <FormTextarea
// form={form} form={form}
// variableName="description" variableName="description"
// label="Description" label="Description"
// /> />
// <FormInput <FormInput
// form={form} form={form}
// variableName="firstName" variableName="firstName"
// label="First Name" label="First Name"
// /> />
// <FormInput form={form} variableName="surname" label="SurName" /> <FormInput form={form} variableName="surname" label="SurName" />
// <FormInput form={form} variableName="birth" label="Birth date" /> <FormInput form={form} variableName="birth" label="Birth date" />
// <FormInput form={form} variableName="death" label="Death date" /> <FormInput form={form} variableName="death" label="Death date" />
// <FormCovers <FormCovers
// form={form} form={form}
// variableName="covers" variableName="covers"
// onFilesSelected={onFilesSelected} onFilesSelected={onFilesSelected}
// onUriSelected={onUriSelected} onUriSelected={onUriSelected}
// onRemove={onRemoveCover} onRemove={onRemoveCover}
// /> />
// </> </>
// )} )}
// </Dialog.Body> </DialogBody>
// <Dialog.Footer> <DialogFooter>
// <Button <Button
// onClick={() => setAdmin((value) => !value)} onClick={() => setAdmin((value) => !value)}
// marginRight="auto" marginRight="auto"
// > colorPalette={admin ? undefined : "@danger"}
// {admin ? ( >
// <> {admin ? (
// <MdEdit /> <>
// Edit <MdEdit />
// </> Edit
// ) : ( </>
// <> ) : (
// <MdAdminPanelSettings /> <>
// Admin <MdAdminPanelSettings />
// </> Admin
// )} </>
// </Button> )}
// {!admin && form.isFormModified && ( </Button>
// <Button colorScheme="blue" mr={3} onClick={onSave}> {!admin && form.isFormModified && (
// Save <Button colorScheme="blue" mr={3} onClick={onSave}>
// </Button> Save
// )} </Button>
// <Button onClick={onClose}>Cancel</Button> )}
// </Dialog.Footer> <Button onClick={onClose}>Cancel</Button>
// </Dialog.Content> </DialogFooter>
// </Dialog.Root> </DialogContent>
<></>); </DialogRoot>
);
}; };

View File

@ -1,8 +1,11 @@
import { UseDisclosureReturn } from '@/utils/disclosure';
import { useRef } from 'react'; import { useRef } from 'react';
import {
Button,
UseDisclosureReturn,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
export type ConfirmPopUpProps = { export type ConfirmPopUpProps = {
title: string; title: string;
body: string; body: string;
@ -23,28 +26,29 @@ export const ConfirmPopUp = ({
disclosure.onClose(); disclosure.onClose();
}; };
const cancelRef = useRef<HTMLButtonElement>(null); const cancelRef = useRef<HTMLButtonElement>(null);
return <></>; return (
// return ( <DialogRoot role="alertdialog"
// <Dialog.Root role="alertdialog" open={disclosure.open}
// open={disclosure.open} //leastDestructiveRef={cancelRef}
// //leastDestructiveRef={cancelRef} onOpenChange={disclosure.onClose}
// onOpenChange={disclosure.onClose} data-testid="confirm-pop-up"
// data-testid="confirm-pop-up" >
// > <DialogContent>
// <Dialog.Content> <DialogHeader fontSize="lg" fontWeight="bold">
// <Dialog.Header fontSize="lg" fontWeight="bold"> {title}
// {title} </DialogHeader>
// </Dialog.Header>
// <Dialog.Body>{body}</Dialog.Body> <DialogBody>{body}</DialogBody>
// <Dialog.Footer>
// <Button onClick={disclosure.onClose} ref={cancelRef}> <DialogFooter>
// Cancel <Button onClick={disclosure.onClose} ref={cancelRef}>
// </Button> Cancel
// <Button colorScheme="red" onClick={onClickConfirm} ml={3}> </Button>
// {confirmTitle} <Button colorScheme="red" onClick={onClickConfirm} ml={3}>
// </Button> {confirmTitle}
// </Dialog.Footer> </Button>
// </Dialog.Content> </DialogFooter>
// </Dialog.Root> </DialogContent>
// ); </DialogRoot>
);
}; };

View File

@ -1,6 +1,11 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Flex,
Text,
useDisclosure,
Button
} from '@chakra-ui/react';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
@ -8,6 +13,7 @@ import {
MdWarning, MdWarning,
} from 'react-icons/md'; } from 'react-icons/md';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Gender, GenderResource } from '@/back-api'; import { Gender, GenderResource } from '@/back-api';
@ -21,7 +27,6 @@ import { useGenderService, useSpecificGender } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAGender } from '@/service/Track'; import { useCountTracksOfAGender } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type GenderEditPopUpProps = {}; export type GenderEditPopUpProps = {};
@ -128,104 +133,103 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
}) })
); );
}; };
return <></>; return (
// return ( <DialogRoot
// <Dialog.Root //initialFocusRef={initialRef}
// //initialFocusRef={initialRef} //finalFocusRef={finalRef}
// //finalFocusRef={finalRef} //closeOnOverlayClick={false}
// //closeOnOverlayClick={false} onOpenChange={onClose}
// onOpenChange={onClose} open={true}
// open={true} data-testid="gender-edit-pop-up"
// data-testid="gender-edit-pop-up" >
// > {/* <DialogOverlay /> */}
// {/* <DialogOverlay /> */} <DialogContent>
// <Dialog.Content> <DialogHeader>Edit Gender</DialogHeader>
// <Dialog.Header>Edit Gender</Dialog.Header> {/* <DialogCloseButton ref={finalRef} /> */}
// {/* <DialogCloseButton ref={finalRef} /> */}
// <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> <DialogBody pb={6} gap="0px" paddingLeft="18px">
// {admin && ( {admin && (
// <> <>
// <FormGroup isRequired label="Id"> <FormGroup isRequired label="Id">
// <Text>{dataGender?.id}</Text> <Text>{dataGender?.id}</Text>
// </FormGroup> </FormGroup>
// {countTracksOnAGender !== 0 && ( {countTracksOnAGender !== 0 && (
// <Flex paddingLeft="14px"> <Flex paddingLeft="14px">
// <MdWarning color="red.600" /> <MdWarning color="red.600" />
// <Text paddingLeft="6px" color="red.600" fontWeight="bold"> <Text paddingLeft="6px" color="red.600" fontWeight="bold">
// Can not remove gender {countTracksOnAGender} track(s) depend Can not remove gender {countTracksOnAGender} track(s) depend
// on it. on it.
// </Text> </Text>
// </Flex> </Flex>
// )} )}
// <FormGroup label="Action(s):"> <FormGroup label="Action(s):">
// <Button <Button
// onClick={disclosure.onOpen} onClick={disclosure.onOpen}
// marginRight="auto" marginRight="auto"
// theme="@danger" colorPalette="@danger"
// disabled={countTracksOnAGender !== 0} disabled={countTracksOnAGender !== 0}
// > >
// <MdDeleteForever /> Remove gender <MdDeleteForever /> Remove gender
// </Button> </Button>
// </FormGroup> </FormGroup>
// <ConfirmPopUp <ConfirmPopUp
// disclosure={disclosure} disclosure={disclosure}
// title="Remove gender" title="Remove gender"
// body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`}
// confirmTitle="Remove" confirmTitle="Remove"
// onConfirm={onRemove} onConfirm={onRemove}
// /> />
// </> </>
// )} )}
// {!admin && ( {!admin && (
// <> <>
// <FormInput <FormInput
// form={form} form={form}
// variableName="name" variableName="name"
// isRequired isRequired
// label="Gender name" label="Gender name"
// ref={initialRef} ref={initialRef}
// /> />
// <FormTextarea <FormTextarea
// form={form} form={form}
// variableName="description" variableName="description"
// label="Description" label="Description"
// /> />
// <FormCovers <FormCovers
// form={form} form={form}
// variableName="covers" variableName="covers"
// onFilesSelected={onFilesSelected} onFilesSelected={onFilesSelected}
// onUriSelected={onUriSelected} onUriSelected={onUriSelected}
// onRemove={onRemoveCover} onRemove={onRemoveCover}
// /> />
// </> </>
// )} )}
// </Dialog.Body> </DialogBody>
// <Dialog.Footer> <DialogFooter>
// <Button <Button
// onClick={() => setAdmin((value) => !value)} onClick={() => setAdmin((value) => !value)}
// marginRight="auto" marginRight="auto"
// > >
// {admin ? ( {admin ? (
// <> <>
// <MdEdit /> <MdEdit />
// Edit Edit
// </> </>
// ) : ( ) : (
// <> <>
// <MdAdminPanelSettings /> <MdAdminPanelSettings />
// Admin Admin
// </> </>
// )} )}
// </Button> </Button>
// {!admin && form.isFormModified && ( {!admin && form.isFormModified && (
// <Button colorScheme="blue" mr={3} onClick={onSave}> <Button colorScheme="blue" mr={3} onClick={onSave}>
// Save Save
// </Button> </Button>
// )} )}
// <Button onClick={onClose}>Cancel</Button> <Button onClick={onClose}>Cancel</Button>
// </Dialog.Footer> </DialogFooter>
// </Dialog.Content> </DialogContent>
// </Dialog.Root> </DialogRoot>
// ); );
}; };

View File

@ -1,5 +1,14 @@
import { useRef } from 'react'; import { useRef } from 'react';
import {
Flex,
Progress,
Text,
Button,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
export type PopUpUploadProgressProps = { export type PopUpUploadProgressProps = {
title: string; title: string;
@ -32,66 +41,64 @@ export const PopUpUploadProgress = ({
}: PopUpUploadProgressProps) => { }: PopUpUploadProgressProps) => {
const initialRef = useRef<HTMLButtonElement>(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null); const finalRef = useRef<HTMLButtonElement>(null);
return <></>; return (
<DialogRoot
//initialFocusRef={initialRef}
//finalFocusRef={finalRef}
//closeOnOverlayClick={false}
onOpenChange={onClose}
open={true}
data-testid="upload-progress-edit-pop-up"
>
{/* <DialogOverlay /> */}
<DialogContent>
<DialogHeader>{title}</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}
// return ( <DialogBody pb={6} paddingLeft="18px">
// <Dialog.Root <Flex direction="column" gap="10px">
// //initialFocusRef={initialRef} {isFinished ? (
// //finalFocusRef={finalRef} <Text fontSize="20px" fontWeight="bold">
// //closeOnOverlayClick={false} All {elements.length} element have been sent
// onOpenChange={onClose} </Text>
// open={true} ) : (
// data-testid="upload-progress-edit-pop-up" <Text fontSize="20px" fontWeight="bold">
// > [{index + 1}/{elements.length}] {elements[index]}
// {/* <DialogOverlay /> */} </Text>
// <Dialog.Content> )}
// <Dialog.Header>{title}</Dialog.Header> <Progress.Root
// {/* <DialogCloseButton ref={finalRef} /> */} colorScheme="green"
striped
// <Dialog.Body pb={6} paddingLeft="18px"> value={currentSize}
// <Flex direction="column" gap="10px"> animated
// {isFinished ? ( max={totalSize}
// <Text fontSize="20px" fontWeight="bold"> height="24px"
// All {elements.length} element have been sent />
// </Text> <Flex>
// ) : ( <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text>
// <Text fontSize="20px" fontWeight="bold"> <Text marginLeft="auto">
// [{index + 1}/{elements.length}] {elements[index]} {totalSize.toLocaleString('fr-FR')} Bytes
// </Text> </Text>
// )} </Flex>
// <Progress.Root {error && (
// colorScheme="green" <Text fontWeight="bold" color="darkred">
// striped {error}
// value={currentSize} </Text>
// animated )}
// max={totalSize} </Flex>
// height="24px" </DialogBody>
// /> <DialogFooter>
// <Flex> {isFinished ? (
// <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> <Button onClick={onClose} colorPalette="green">
// <Text marginLeft="auto"> Ok
// {totalSize.toLocaleString('fr-FR')} Bytes </Button>
// </Text> ) : (
// </Flex> <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
// {error && ( Abort
// <Text fontWeight="bold" color="darkred"> </Button>
// {error} )}
// </Text> </DialogFooter>
// )} </DialogContent>
// </Flex> </DialogRoot>
// </Dialog.Body> );
// <Dialog.Footer>
// {isFinished ? (
// <Button onClick={onClose} theme="@success">
// Ok
// </Button>
// ) : (
// <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
// Abort
// </Button>
// )}
// </Dialog.Footer>
// </Dialog.Content>
// </Dialog.Root>
// );
}; };

View File

@ -1,5 +1,12 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Text,
useDisclosure,
Button,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md'; import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@ -19,7 +26,6 @@ import { useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useSpecificTrack, useTrackService } from '@/service/Track'; import { useSpecificTrack, useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type TrackEditPopUpProps = {}; export type TrackEditPopUpProps = {};
@ -80,117 +86,116 @@ export const TrackEditPopUp = ({ }: TrackEditPopUpProps) => {
}) })
); );
}; };
return <></>; return (
// return ( <DialogRoot
// <Dialog.Root //initialFocusRef={initialRef}
// //initialFocusRef={initialRef} //finalFocusRef={finalRef}
// //finalFocusRef={finalRef} //closeOnOverlayClick={false}
// //closeOnOverlayClick={false} onOpenChange={onClose}
// onOpenChange={onClose} open={true}
// open={true} data-testid="track-edit-pop-up"
// data-testid="track-edit-pop-up" >
// > {/* <DialogOverlay /> */}
// {/* <DialogOverlay /> */} <DialogContent>
// <Dialog.Content> <DialogHeader>Edit Track</DialogHeader>
// <Dialog.Header>Edit Track</Dialog.Header> {/* <DialogCloseButton ref={finalRef} /> */}
// {/* <DialogCloseButton ref={finalRef} /> */}
// <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> <DialogBody pb={6} gap="0px" paddingLeft="18px">
// {admin && ( {admin && (
// <> <>
// <FormGroup isRequired label="Id"> <FormGroup isRequired label="Id">
// <Text>{dataTrack?.id}</Text> <Text>{dataTrack?.id}</Text>
// </FormGroup> </FormGroup>
// <FormGroup label="Data Id"> <FormGroup label="Data Id">
// <Text>{dataTrack?.dataId}</Text> <Text>{dataTrack?.dataId}</Text>
// </FormGroup> </FormGroup>
// <FormGroup label="Action(s):"> <FormGroup label="Action(s):">
// <Button <Button
// onClick={disclosure.onOpen} onClick={disclosure.onOpen}
// marginRight="auto" marginRight="auto"
// theme="@danger" colorPalette="@danger"
// > >
// <MdDeleteForever /> Remove Media <MdDeleteForever /> Remove Media
// </Button> </Button>
// </FormGroup> </FormGroup>
// <ConfirmPopUp <ConfirmPopUp
// disclosure={disclosure} disclosure={disclosure}
// title="Remove track" title="Remove track"
// body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
// confirmTitle="Remove" confirmTitle="Remove"
// onConfirm={onRemove} onConfirm={onRemove}
// /> />
// </> </>
// )} )}
// {!admin && ( {!admin && (
// <> <>
// <FormInput <FormInput
// form={form} form={form}
// variableName="name" variableName="name"
// isRequired isRequired
// label="Title" label="Title"
// ref={initialRef} ref={initialRef}
// /> />
// <FormTextarea <FormTextarea
// form={form} form={form}
// variableName="description" variableName="description"
// label="Description" label="Description"
// /> />
// <FormSelect <FormSelect
// form={form} form={form}
// variableName="genderId" variableName="genderId"
// options={dataGenders} options={dataGenders}
// label="Gender" label="Gender"
// /> />
// <FormSelectMultiple <FormSelectMultiple
// form={form} form={form}
// variableName="artists" variableName="artists"
// options={dataArtist} options={dataArtist}
// label="Artist(s)" label="Artist(s)"
// /> />
// <FormSelect <FormSelect
// form={form} form={form}
// variableName="albumId" variableName="albumId"
// options={dataAlbums} options={dataAlbums}
// label="Album" label="Album"
// /> />
// <FormNumber <FormNumber
// form={form} form={form}
// variableName="track" variableName="track"
// label="Track n°" label="Track n°"
// step={1} step={1}
// //defaultValue={0} //defaultValue={0}
// min={0} min={0}
// max={1000} max={1000}
// /> />
// </> </>
// )} )}
// </Dialog.Body> </DialogBody>
// <Dialog.Footer> <DialogFooter>
// <Button <Button
// onClick={() => setAdmin((value) => !value)} onClick={() => setAdmin((value) => !value)}
// marginRight="auto" marginRight="auto"
// > >
// {admin ? ( {admin ? (
// <> <>
// <MdEdit /> <MdEdit />
// Edit Edit
// </> </>
// ) : ( ) : (
// <> <>
// <MdAdminPanelSettings /> <MdAdminPanelSettings />
// Admin Admin
// </> </>
// )} )}
// </Button> </Button>
// {!admin && form.isFormModified && ( {!admin && form.isFormModified && (
// <Button colorScheme="blue" mr={3} onClick={onSave}> <Button colorScheme="blue" mr={3} onClick={onSave}>
// Save Save
// </Button> </Button>
// )} )}
// <Button onClick={onClose}>Cancel</Button> <Button onClick={onClose}>Cancel</Button>
// </Dialog.Footer> </DialogFooter>
// </Dialog.Content> </DialogContent>
// </Dialog.Root> </DialogRoot>
// ); );
}; };

View File

@ -1,9 +1,9 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { MdAdd } from 'react-icons/md'; import { MdAdd } from 'react-icons/md';
import { isNullOrUndefined, isNumber } from '@/utils/validator'; import { isNullOrUndefined, isNumber } from '@/utils/validator';
import { Button, Flex, Text } from '@/ui';
export type SelectListModel = { export type SelectListModel = {
id: any; id: any;
@ -65,65 +65,56 @@ export const SelectList = ({
} }
}, []); }, []);
return ( return (
<Flex style={{ position: "relative" }}> <Box position="relative">
<Flex <Flex
direction="column" direction="column"
style={{ width="full"
width: "full", position="absolute"
position: "absolute", border="1px"
border: "1px", borderColor="black"
borderColor: "black", backgroundColor="gray.700"
backgroundColor: "gray.700", overflowY="auto"
overflowY: "auto", overflowX="hidden"
overflowX: "hidden", maxHeight="300px"
maxHeight: "300px", zIndex={300}
zIndex: 300, transform="translateY(1px)"
transform: "translateY(1px)",
}}
> >
{displayedValue.length === 0 && ( {displayedValue.length === 0 && (
<Text fontWeight="bold" color="red.500" style={{ margin: "0 auto" }}> <Text marginX="auto" color="red.500" fontWeight="bold" marginY="10px">
... No element found... ... No element found...
</Text> </Text>
)} )}
{displayedValue.map((data) => ( {displayedValue.map((data) => (
<Button <Button
key={data.id} key={data.id}
style={{ marginY="1px"
margin: "1px 0", borderRadius="0px"
borderRadius: "0px", autoFocus={false}
//autoFocus: false, backgroundColor={data.isSelected ? 'green.800' : '0x00000000'}
backgroundColor: data.isSelected ? 'green.800' : '0x00000000', _hover={{ backgroundColor: 'gray.400' }}
//_hover={ backgroundColor: 'gray.400' },
}}
onClick={() => onSelectValue(data)} onClick={() => onSelectValue(data)}
//ref={data.isSelected ? scrollToRef : undefined} ref={data.isSelected ? scrollToRef : undefined}
> >
<Text style={{ margin: "0 auto", }} /*autoFocus={false}*/> <Text marginRight="auto" autoFocus={false}>
{data.name} {data.name}
</Text> </Text>
</Button> </Button>
))} ))}
{onCreate && search && search.length > 0 && ( {onCreate && search && search.length > 0 && (
<Button <Button
style={{ marginY="1px"
margin: "1px 0", borderRadius="0px"
borderRadius: "0px", autoFocus={false}
//autoFocus:false, _hover={{ backgroundColor: 'gray.400' }}
//_hover={ backgroundColor: 'gray.400' }
}}
onClick={() => onCreate(search)} onClick={() => onCreate(search)}
> >
<Flex <Flex marginRight="auto">
style={{
margin: "0 auto",
}}>
<MdAdd /> <MdAdd />
<Text /*autoFocus={false}*/>Create '{search}'</Text> <Text autoFocus={false}>Create '{search}'</Text>
</Flex> </Flex>
</Button> </Button>
)} )}
</Flex> </Flex>
</Flex> </Box>
); );
}; };

View File

@ -1,10 +1,18 @@
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import {
Button,
Flex,
HStack,
Input,
Spinner,
Tag,
TagLabel,
} from '@chakra-ui/react';
import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md';
import { SelectList, SelectListModel } from '@/components/select/SelectList'; import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, HStack, Input } from '@/ui';
export type SelectMultipleProps = { export type SelectMultipleProps = {
options?: object[]; options?: object[];
@ -75,7 +83,7 @@ export const SelectMultiple = ({
} }
}; };
if (!options) { if (!options) {
return <></>;// TODO: <Spinner />; return <Spinner />;
} }
const onChangeInput = (value: string): void => { const onChangeInput = (value: string): void => {
setHasSuggestion(false); setHasSuggestion(false);
@ -101,19 +109,18 @@ export const SelectMultiple = ({
return ( return (
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="full" gap="0px">
{selectedOptions && ( {selectedOptions && (
<HStack style={{ flexWrap: "wrap", /*spacing="5px"*/ justifyContent: "left", width: "full", marginBottom: "2px" }}> <HStack wrap="wrap" gap="5px" justify="left" width="full" marginBottom="2px">
{selectedOptions.map((data) => ( {selectedOptions.map((data) => (
<Flex align="flex-start" key={data[keyKey]}> <Flex align="flex-start" key={data[keyKey]}>
{/* <Tag.Root <Tag.Root
size="md" size="xl"
key="md"
borderRadius="5px" borderRadius="5px"
variant="solid" variant="surface"
backgroundColor="green.500" backgroundColor="green.800"
> >
<Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label> <Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label>
<Tag.CloseTrigger onClick={() => selectValue(data)} /> <Tag.CloseTrigger boxSize="5" onClick={() => selectValue(data)} />
</Tag.Root> */} </Tag.Root>
</Flex> </Flex>
))} ))}
</HStack> </HStack>
@ -122,24 +129,19 @@ export const SelectMultiple = ({
<Flex> <Flex>
<Input <Input
ref={refFocus} ref={refFocus}
style={{ width="full"
width: "full",
borderRadius: "5px 0 0 5px",
}}
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit} //onSubmit={onSubmit}
// TODO: onFocus={() => setShowList(true)} onFocus={() => setShowList(true)}
// TODO: onBlur={() => setTimeout(() => setShowList(false), 200)} onBlur={() => setTimeout(() => setShowList(false), 200)}
value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''} value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''}
borderRadius="5px 0 0 5px"
/> />
<Button <Button
onClick={onOpenClose} onClick={onOpenClose}
//variant="outline" variant="outline"
style={{ borderRadius="0 5px 5px 0"
borderRadius: "0 5px 5px 0", borderWidth="1px 1px 1px 0"
borderWidth: "1px 1px 1px 0",
}}
> >
{showList ? ( {showList ? (
<MdKeyboardArrowUp color="gray.300" /> <MdKeyboardArrowUp color="gray.300" />

View File

@ -1,5 +1,6 @@
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { Button, Flex, Input, Spinner } from '@chakra-ui/react';
import { import {
MdClose, MdClose,
MdEdit, MdEdit,
@ -9,7 +10,6 @@ import {
import { SelectList, SelectListModel } from '@/components/select/SelectList'; import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, Input } from '@/ui';
export type SelectSingleProps = { export type SelectSingleProps = {
options?: object[]; options?: object[];
@ -70,7 +70,7 @@ export const SelectSingle = ({
} }
}; };
if (!transformedOption) { if (!transformedOption) {
return <></>; // TODO: <Spinner />; return <Spinner />;
} }
function onChangeInput(value: string): void { function onChangeInput(value: string): void {
setHasSuggestion(false); setHasSuggestion(false);
@ -105,49 +105,44 @@ export const SelectSingle = ({
<Flex> <Flex>
<Input <Input
ref={refFocus} ref={refFocus}
style={{ width="full"
width: "full",
backgroundColor: showList || !selectedOptions ? undefined : 'green.500',
borderRadius: "5px 0 0 5px",
}}
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
//onFocus={() => setShowList(true)} onFocus={() => setShowList(true)}
//onBlur={() => setTimeout(() => setShowList(false), 200)} onBlur={() => setTimeout(() => setShowList(false), 200)}
value={ value={
showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : '')) showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : ''))
} }
backgroundColor={
showList || !selectedOptions ? undefined : 'green.800'
}
borderRadius="5px 0 0 5px"
/> />
<Button <Button
onClick={onRemoveItem} onClick={onRemoveItem}
// TODO: variant="outline" variant="outline"
style={{ borderRadius="0 5px 5px 0"
borderRadius: "0 5px 5px 0", borderWidth="1px 1px 1px 0"
borderWidth: "1px 1px 1px 0",
}}
> >
{ {selectedOptions ? (
selectedOptions ? ( <MdClose color="gray.300" />
<MdClose color="gray.300" /> ) : showList ? (
) : showList ? ( <MdKeyboardArrowUp color="gray.300" />
<MdKeyboardArrowUp color="gray.300" /> ) : hasSuggestion ? (
) : hasSuggestion ? ( <MdEdit color="gray.300" />
<MdEdit color="gray.300" /> ) : (
) : ( <MdKeyboardArrowDown color="gray.300" />
<MdKeyboardArrowDown color="gray.300" /> )}
)}
</Button> </Button>
</Flex> </Flex>
{ {showList && (
showList && ( <SelectList
<SelectList options={transformedOption}
options={transformedOption} selected={selectedOptions ? [selectedOptions] : []}
selected={selectedOptions ? [selectedOptions] : []} search={currentSearch}
search={currentSearch} onSelectValue={selectValue}
onSelectValue={selectValue} onCreate={createNewItem}
onCreate={createNewItem} />
/> )}
) </Flex>
}
</Flex >
); );
}; };

View File

@ -1,10 +1,10 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { Track } from '@/back-api'; import { Track } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu'; import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = { export type DisplayTrackProps = {
track: Track; track: Track;
@ -23,9 +23,9 @@ export const DisplayTrack = ({
data={track?.covers} data={track?.covers}
size="50" size="50"
height="full" height="full"
/* TODO: iconEmpty={ iconEmpty={
trackActive?.id === track.id ? LuPlay : LuMusic2 trackActive?.id === track.id ? <LuPlay /> : <LuMusic2 />
} */ }
onClick={onClick} onClick={onClick}
/> />
<Flex <Flex
@ -33,26 +33,23 @@ export const DisplayTrack = ({
width="full" width="full"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
style={{ overflowX="hidden"
overflowX: "hidden",
}}
onClick={onClick} onClick={onClick}
> >
<Span <Text
as="span"
alignContent="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// TODO: noOfLines={[1, 2]}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
[{track.track}] {track.name} [{track.track}] {track.name}
</Span> </Text>
</Flex> </Flex>
<ContextMenu elements={contextMenu} /> <ContextMenu elements={contextMenu} />
</Flex> </Flex>

View File

@ -1,4 +1,7 @@
import { Suspense } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { Track } from '@/back-api'; import { Track } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
@ -7,7 +10,6 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtists } from '@/service/Artist'; import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = { export type DisplayTrackProps = {
track: Track; track: Track;
@ -28,11 +30,11 @@ export const DisplayTrackFull = ({
data-testid="display-track-full"> data-testid="display-track-full">
<Covers <Covers
data={track?.covers} data={track?.covers}
size="50" size="60px"
marginY="auto" marginY="auto"
/* TODO: iconEmpty={ iconEmpty={
trackActive?.id === track.id ? LuPlay : LuMusic2 trackActive?.id === track.id ? <LuPlay /> : <LuMusic2 />
} */ }
onClick={onClick} onClick={onClick}
/> />
<Flex <Flex
@ -40,78 +42,69 @@ export const DisplayTrackFull = ({
width="full" width="full"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden"
onClick={onClick} onClick={onClick}
style={{
overflowX: "hidden",
}}
> >
<Span <Text
as="span"
alignContent="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// TODO: noOfLines={1}
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={1}
margin: "auto 0",
}}
> >
{track.name} {track.track && ` [${track.track}]`} {track.name} {track.track && ` [${track.track}]`}
</Span> </Text>
{dataAlbum && ( {dataAlbum && (
<Span <Text
fontSize="15px" as="span"
fontWeight="bold" alignContent="left"
//noOfLines={1}
color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
>
<Span fontWeight="normal">Album:</Span> {dataAlbum.name}
</Span>
)}
{dataArtists && (
<Span
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')} <Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name}
</Span> </Text>
)} )}
{dataGender && ( {dataArtists && (
<Span <Text
as="span"
alignContent="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Span fontWeight="normal">Gender:</Span> {dataGender.name} <Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')}
</Span> </Text>
)}
{dataGender && (
<Text
as="span"
alignContent="left"
fontSize="15px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined}
>
<Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name}
</Text>
)} )}
</Flex> </Flex>
<ContextMenu elements={contextMenu} <ContextMenu elements={contextMenu}

View File

@ -1,4 +1,5 @@
import { Track } from '@/back-api'; import { Track } from '@/back-api';
import { Covers } from '@/components/Cover';
import { MenuElement } from '@/components/contextMenu/ContextMenu'; import { MenuElement } from '@/components/contextMenu/ContextMenu';
import { useSpecificTrack } from '@/service/Track'; import { useSpecificTrack } from '@/service/Track';
import { DisplayTrackFull } from './DisplayTrackFull'; import { DisplayTrackFull } from './DisplayTrackFull';

View File

@ -1,29 +1,26 @@
import { Flex } from "@/ui"; import { Flex, Skeleton } from '@chakra-ui/react';
export const DisplayTrackSkeleton = () => { export const DisplayTrackSkeleton = () => {
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="full" height="full">
{/* <Skeleton <Skeleton
borderRadius="0px" borderRadius="0px"
height="50" height="50"
width="50" width="50"
minWidth="50" minWidth="50"
minHeight="50" minHeight="50"
/> */} />
<Flex <Flex
direction="column" direction="column"
width="full" width="full"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
style={{ overflowX="hidden"
overflowX: "hidden"
}}
> >
{/* <SkeletonText {/* <SkeletonText
skeletonHeight="20px" skeletonHeight="20px"
noOfLines={1} noOfLines={1}
spacing={0} gap={0}
width="50%" width="50%"
marginY="auto" marginY="auto"
/> */} /> */}

View File

@ -0,0 +1,74 @@
"use client"
import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
import * as React from "react"
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>
export interface AvatarProps extends ChakraAvatar.RootProps {
name?: string
src?: string
srcSet?: string
loading?: ImageProps["loading"]
icon?: React.ReactElement
fallback?: React.ReactNode
}
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
function Avatar(props, ref) {
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
props
return (
<ChakraAvatar.Root ref={ref} {...rest}>
<AvatarFallback name={name} icon={icon}>
{fallback}
</AvatarFallback>
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
{children}
</ChakraAvatar.Root>
)
},
)
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
name?: string
icon?: React.ReactElement
}
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
function AvatarFallback(props, ref) {
const { name, icon, children, ...rest } = props
return (
<ChakraAvatar.Fallback ref={ref} {...rest}>
{children}
{name != null && children == null && <>{getInitials(name)}</>}
{name == null && children == null && (
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
)}
</ChakraAvatar.Fallback>
)
},
)
function getInitials(name: string) {
const names = name.trim().split(" ")
const firstName = names[0] != null ? names[0] : ""
const lastName = names.length > 1 ? names[names.length - 1] : ""
return firstName && lastName
? `${firstName.charAt(0)}${lastName.charAt(0)}`
: firstName.charAt(0)
}
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
function AvatarGroup(props, ref) {
const { size, variant, borderless, ...rest } = props
return (
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
</ChakraAvatar.PropsProvider>
)
},
)

View File

@ -0,0 +1,40 @@
import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"
import {
AbsoluteCenter,
Button as ChakraButton,
Span,
Spinner,
} from "@chakra-ui/react"
import * as React from "react"
interface ButtonLoadingProps {
loading?: boolean
loadingText?: React.ReactNode
}
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
function Button(props, ref) {
const { loading, disabled, loadingText, children, ...rest } = props
return (
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
{loading && !loadingText ? (
<>
<AbsoluteCenter display="inline-flex">
<Spinner size="inherit" color="inherit" />
</AbsoluteCenter>
<Span opacity={0}>{children}</Span>
</>
) : loading && loadingText ? (
<>
<Spinner size="inherit" color="inherit" />
{loadingText}
</>
) : (
children
)}
</ChakraButton>
)
},
)

View File

@ -0,0 +1,25 @@
import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
import * as React from "react"
export interface CheckboxProps extends ChakraCheckbox.RootProps {
icon?: React.ReactNode
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
rootRef?: React.Ref<HTMLLabelElement>
}
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
function Checkbox(props, ref) {
const { icon, children, inputProps, rootRef, ...rest } = props
return (
<ChakraCheckbox.Root ref={rootRef} {...rest}>
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
<ChakraCheckbox.Control>
{icon || <ChakraCheckbox.Indicator />}
</ChakraCheckbox.Control>
{children != null && (
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
)}
</ChakraCheckbox.Root>
)
},
)

View File

@ -0,0 +1,17 @@
import type { ButtonProps } from "@chakra-ui/react"
import { IconButton as ChakraIconButton } from "@chakra-ui/react"
import * as React from "react"
import { LuX } from "react-icons/lu"
export type CloseButtonProps = ButtonProps
export const CloseButton = React.forwardRef<
HTMLButtonElement,
CloseButtonProps
>(function CloseButton(props, ref) {
return (
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
{props.children ?? <LuX />}
</ChakraIconButton>
)
})

View File

@ -0,0 +1,75 @@
"use client"
import type { IconButtonProps } from "@chakra-ui/react"
import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
import { ThemeProvider, useTheme } from "next-themes"
import type { ThemeProviderProps } from "next-themes"
import * as React from "react"
import { LuMoon, LuSun } from "react-icons/lu"
export interface ColorModeProviderProps extends ThemeProviderProps {}
export function ColorModeProvider(props: ColorModeProviderProps) {
return (
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
)
}
export type ColorMode = "light" | "dark"
export interface UseColorModeReturn {
colorMode: ColorMode
setColorMode: (colorMode: ColorMode) => void
toggleColorMode: () => void
}
export function useColorMode(): UseColorModeReturn {
const { resolvedTheme, setTheme } = useTheme()
const toggleColorMode = () => {
setTheme(resolvedTheme === "light" ? "dark" : "light")
}
return {
colorMode: resolvedTheme as ColorMode,
setColorMode: setTheme,
toggleColorMode,
}
}
export function useColorModeValue<T>(light: T, dark: T) {
const { colorMode } = useColorMode()
return colorMode === "dark" ? dark : light
}
export function ColorModeIcon() {
const { colorMode } = useColorMode()
return colorMode === "dark" ? <LuMoon /> : <LuSun />
}
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
export const ColorModeButton = React.forwardRef<
HTMLButtonElement,
ColorModeButtonProps
>(function ColorModeButton(props, ref) {
const { toggleColorMode } = useColorMode()
return (
<ClientOnly fallback={<Skeleton boxSize="8" />}>
<IconButton
onClick={toggleColorMode}
variant="ghost"
aria-label="Toggle color mode"
size="sm"
ref={ref}
{...props}
css={{
_icon: {
width: "5",
height: "5",
},
}}
>
<ColorModeIcon />
</IconButton>
</ClientOnly>
)
})

View File

@ -0,0 +1,62 @@
import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface DialogContentProps extends ChakraDialog.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
backdrop?: boolean
}
export const DialogContent = React.forwardRef<
HTMLDivElement,
DialogContentProps
>(function DialogContent(props, ref) {
const {
children,
portalled = true,
portalRef,
backdrop = true,
...rest
} = props
return (
<Portal disabled={!portalled} container={portalRef}>
{backdrop && <ChakraDialog.Backdrop />}
<ChakraDialog.Positioner>
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
{children}
</ChakraDialog.Content>
</ChakraDialog.Positioner>
</Portal>
)
})
export const DialogCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraDialog.CloseTriggerProps
>(function DialogCloseTrigger(props, ref) {
return (
<ChakraDialog.CloseTrigger
position="absolute"
top="2"
insetEnd="2"
{...props}
asChild
>
<CloseButton size="sm" ref={ref}>
{props.children}
</CloseButton>
</ChakraDialog.CloseTrigger>
)
})
export const DialogRoot = ChakraDialog.Root
export const DialogFooter = ChakraDialog.Footer
export const DialogHeader = ChakraDialog.Header
export const DialogBody = ChakraDialog.Body
export const DialogBackdrop = ChakraDialog.Backdrop
export const DialogTitle = ChakraDialog.Title
export const DialogDescription = ChakraDialog.Description
export const DialogTrigger = ChakraDialog.Trigger
export const DialogActionTrigger = ChakraDialog.ActionTrigger

View File

@ -0,0 +1,52 @@
import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface DrawerContentProps extends ChakraDrawer.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
offset?: ChakraDrawer.ContentProps["padding"]
}
export const DrawerContent = React.forwardRef<
HTMLDivElement,
DrawerContentProps
>(function DrawerContent(props, ref) {
const { children, portalled = true, portalRef, offset, ...rest } = props
return (
<Portal disabled={!portalled} container={portalRef}>
<ChakraDrawer.Positioner padding={offset}>
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
{children}
</ChakraDrawer.Content>
</ChakraDrawer.Positioner>
</Portal>
)
})
export const DrawerCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraDrawer.CloseTriggerProps
>(function DrawerCloseTrigger(props, ref) {
return (
<ChakraDrawer.CloseTrigger
position="absolute"
top="2"
insetEnd="2"
{...props}
asChild
>
<CloseButton size="sm" ref={ref} />
</ChakraDrawer.CloseTrigger>
)
})
export const DrawerTrigger = ChakraDrawer.Trigger
export const DrawerRoot = ChakraDrawer.Root
export const DrawerFooter = ChakraDrawer.Footer
export const DrawerHeader = ChakraDrawer.Header
export const DrawerBody = ChakraDrawer.Body
export const DrawerBackdrop = ChakraDrawer.Backdrop
export const DrawerDescription = ChakraDrawer.Description
export const DrawerTitle = ChakraDrawer.Title
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger

View File

@ -0,0 +1,33 @@
import { Field as ChakraField } from "@chakra-ui/react"
import * as React from "react"
export interface FieldProps extends Omit<ChakraField.RootProps, "label"> {
label?: React.ReactNode
helperText?: React.ReactNode
errorText?: React.ReactNode
optionalText?: React.ReactNode
}
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
function Field(props, ref) {
const { label, children, helperText, errorText, optionalText, ...rest } =
props
return (
<ChakraField.Root ref={ref} {...rest}>
{label && (
<ChakraField.Label>
{label}
<ChakraField.RequiredIndicator fallback={optionalText} />
</ChakraField.Label>
)}
{children}
{helperText && (
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
)}
{errorText && (
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
)}
</ChakraField.Root>
)
},
)

View File

@ -0,0 +1,53 @@
import type { BoxProps, InputElementProps } from "@chakra-ui/react"
import { Group, InputElement } from "@chakra-ui/react"
import * as React from "react"
export interface InputGroupProps extends BoxProps {
startElementProps?: InputElementProps
endElementProps?: InputElementProps
startElement?: React.ReactNode
endElement?: React.ReactNode
children: React.ReactElement<InputElementProps>
startOffset?: InputElementProps["paddingStart"]
endOffset?: InputElementProps["paddingEnd"]
}
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
function InputGroup(props, ref) {
const {
startElement,
startElementProps,
endElement,
endElementProps,
children,
startOffset = "6px",
endOffset = "6px",
...rest
} = props
const child =
React.Children.only<React.ReactElement<InputElementProps>>(children)
return (
<Group ref={ref} {...rest}>
{startElement && (
<InputElement pointerEvents="none" {...startElementProps}>
{startElement}
</InputElement>
)}
{React.cloneElement(child, {
...(startElement && {
ps: `calc(var(--input-height) - ${startOffset})`,
}),
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
...children.props,
})}
{endElement && (
<InputElement placement="end" {...endElementProps}>
{endElement}
</InputElement>
)}
</Group>
)
},
)

View File

@ -0,0 +1,110 @@
"use client"
import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react"
import * as React from "react"
import { LuCheck, LuChevronRight } from "react-icons/lu"
interface MenuContentProps extends ChakraMenu.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
}
export const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>(
function MenuContent(props, ref) {
const { portalled = true, portalRef, ...rest } = props
return (
<Portal disabled={!portalled} container={portalRef}>
<ChakraMenu.Positioner>
<ChakraMenu.Content ref={ref} {...rest} />
</ChakraMenu.Positioner>
</Portal>
)
},
)
export const MenuArrow = React.forwardRef<
HTMLDivElement,
ChakraMenu.ArrowProps
>(function MenuArrow(props, ref) {
return (
<ChakraMenu.Arrow ref={ref} {...props}>
<ChakraMenu.ArrowTip />
</ChakraMenu.Arrow>
)
})
export const MenuCheckboxItem = React.forwardRef<
HTMLDivElement,
ChakraMenu.CheckboxItemProps
>(function MenuCheckboxItem(props, ref) {
return (
<ChakraMenu.CheckboxItem ref={ref} {...props}>
<ChakraMenu.ItemIndicator hidden={false}>
<LuCheck />
</ChakraMenu.ItemIndicator>
{props.children}
</ChakraMenu.CheckboxItem>
)
})
export const MenuRadioItem = React.forwardRef<
HTMLDivElement,
ChakraMenu.RadioItemProps
>(function MenuRadioItem(props, ref) {
const { children, ...rest } = props
return (
<ChakraMenu.RadioItem ps="8" ref={ref} {...rest}>
<AbsoluteCenter axis="horizontal" left="4" asChild>
<ChakraMenu.ItemIndicator>
<LuCheck />
</ChakraMenu.ItemIndicator>
</AbsoluteCenter>
<ChakraMenu.ItemText>{children}</ChakraMenu.ItemText>
</ChakraMenu.RadioItem>
)
})
export const MenuItemGroup = React.forwardRef<
HTMLDivElement,
ChakraMenu.ItemGroupProps
>(function MenuItemGroup(props, ref) {
const { title, children, ...rest } = props
return (
<ChakraMenu.ItemGroup ref={ref} {...rest}>
{title && (
<ChakraMenu.ItemGroupLabel userSelect="none">
{title}
</ChakraMenu.ItemGroupLabel>
)}
{children}
</ChakraMenu.ItemGroup>
)
})
export interface MenuTriggerItemProps extends ChakraMenu.ItemProps {
startIcon?: React.ReactNode
}
export const MenuTriggerItem = React.forwardRef<
HTMLDivElement,
MenuTriggerItemProps
>(function MenuTriggerItem(props, ref) {
const { startIcon, children, ...rest } = props
return (
<ChakraMenu.TriggerItem ref={ref} {...rest}>
{startIcon}
{children}
<LuChevronRight />
</ChakraMenu.TriggerItem>
)
})
export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup
export const MenuContextTrigger = ChakraMenu.ContextTrigger
export const MenuRoot = ChakraMenu.Root
export const MenuSeparator = ChakraMenu.Separator
export const MenuItem = ChakraMenu.Item
export const MenuItemText = ChakraMenu.ItemText
export const MenuItemCommand = ChakraMenu.ItemCommand
export const MenuTrigger = ChakraMenu.Trigger

View File

@ -0,0 +1,24 @@
import { NumberInput as ChakraNumberInput } from "@chakra-ui/react"
import * as React from "react"
export interface NumberInputProps extends ChakraNumberInput.RootProps {}
export const NumberInputRoot = React.forwardRef<
HTMLDivElement,
NumberInputProps
>(function NumberInput(props, ref) {
const { children, ...rest } = props
return (
<ChakraNumberInput.Root ref={ref} variant="outline" {...rest}>
{children}
<ChakraNumberInput.Control>
<ChakraNumberInput.IncrementTrigger />
<ChakraNumberInput.DecrementTrigger />
</ChakraNumberInput.Control>
</ChakraNumberInput.Root>
)
})
export const NumberInputField = ChakraNumberInput.Input
export const NumberInputScrubber = ChakraNumberInput.Scrubber
export const NumberInputLabel = ChakraNumberInput.Label

View File

@ -0,0 +1,59 @@
import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface PopoverContentProps extends ChakraPopover.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
}
export const PopoverContent = React.forwardRef<
HTMLDivElement,
PopoverContentProps
>(function PopoverContent(props, ref) {
const { portalled = true, portalRef, ...rest } = props
return (
<Portal disabled={!portalled} container={portalRef}>
<ChakraPopover.Positioner>
<ChakraPopover.Content ref={ref} {...rest} />
</ChakraPopover.Positioner>
</Portal>
)
})
export const PopoverArrow = React.forwardRef<
HTMLDivElement,
ChakraPopover.ArrowProps
>(function PopoverArrow(props, ref) {
return (
<ChakraPopover.Arrow {...props} ref={ref}>
<ChakraPopover.ArrowTip />
</ChakraPopover.Arrow>
)
})
export const PopoverCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraPopover.CloseTriggerProps
>(function PopoverCloseTrigger(props, ref) {
return (
<ChakraPopover.CloseTrigger
position="absolute"
top="1"
insetEnd="1"
{...props}
asChild
ref={ref}
>
<CloseButton size="sm" />
</ChakraPopover.CloseTrigger>
)
})
export const PopoverTitle = ChakraPopover.Title
export const PopoverDescription = ChakraPopover.Description
export const PopoverFooter = ChakraPopover.Footer
export const PopoverHeader = ChakraPopover.Header
export const PopoverRoot = ChakraPopover.Root
export const PopoverBody = ChakraPopover.Body
export const PopoverTrigger = ChakraPopover.Trigger

View File

@ -0,0 +1,15 @@
"use client"
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
import {
ColorModeProvider,
type ColorModeProviderProps,
} from "./color-mode"
export function Provider(props: ColorModeProviderProps) {
return (
<ChakraProvider value={defaultSystem}>
<ColorModeProvider {...props} />
</ChakraProvider>
)
}

View File

@ -0,0 +1,24 @@
import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
import * as React from "react"
export interface RadioProps extends ChakraRadioGroup.ItemProps {
rootRef?: React.Ref<HTMLDivElement>
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
}
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
function Radio(props, ref) {
const { children, inputProps, rootRef, ...rest } = props
return (
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
<ChakraRadioGroup.ItemIndicator />
{children && (
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
)}
</ChakraRadioGroup.Item>
)
},
)
export const RadioGroup = ChakraRadioGroup.Root

View File

@ -0,0 +1,82 @@
import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
import * as React from "react"
export interface SliderProps extends ChakraSlider.RootProps {
marks?: Array<number | { value: number; label: React.ReactNode }>
label?: React.ReactNode
showValue?: boolean
}
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
function Slider(props, ref) {
const { marks: marksProp, label, showValue, ...rest } = props
const value = props.defaultValue ?? props.value
const marks = marksProp?.map((mark) => {
if (typeof mark === "number") return { value: mark, label: undefined }
return mark
})
const hasMarkLabel = !!marks?.some((mark) => mark.label)
return (
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
{label && !showValue && (
<ChakraSlider.Label>{label}</ChakraSlider.Label>
)}
{label && showValue && (
<HStack justify="space-between">
<ChakraSlider.Label>{label}</ChakraSlider.Label>
<ChakraSlider.ValueText />
</HStack>
)}
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
<ChakraSlider.Track>
<ChakraSlider.Range />
</ChakraSlider.Track>
<SliderThumbs value={value} />
<SliderMarks marks={marks} />
</ChakraSlider.Control>
</ChakraSlider.Root>
)
},
)
function SliderThumbs(props: { value?: number[] }) {
const { value } = props
return (
<For each={value}>
{(_, index) => (
<ChakraSlider.Thumb key={index} index={index}>
<ChakraSlider.HiddenInput />
</ChakraSlider.Thumb>
)}
</For>
)
}
interface SliderMarksProps {
marks?: Array<number | { value: number; label: React.ReactNode }>
}
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
function SliderMarks(props, ref) {
const { marks } = props
if (!marks?.length) return null
return (
<ChakraSlider.MarkerGroup ref={ref}>
{marks.map((mark, index) => {
const value = typeof mark === "number" ? mark : mark.value
const label = typeof mark === "number" ? undefined : mark.label
return (
<ChakraSlider.Marker key={index} value={value}>
<ChakraSlider.MarkerIndicator />
{label}
</ChakraSlider.Marker>
)
})}
</ChakraSlider.MarkerGroup>
)
},
)

View File

@ -1,39 +1,51 @@
"use client" "use client"
import { RestErrorResponse } from "@/back-api"; import { RestErrorResponse } from "@/back-api";
import { HStack } from "@/ui"; import {
Toaster as ChakraToaster,
Portal,
Spinner,
Stack,
Toast,
createToaster,
} from "@chakra-ui/react"
// export const toaster = createToaster({ export const toaster = createToaster({
// placement: "bottom-end", placement: "bottom-end",
// pauseOnPageIdle: true, pauseOnPageIdle: true,
// }) })
// export const toasterAPIError = (error: RestErrorResponse) => { export const toasterAPIError = (error: RestErrorResponse) => {
// toaster.create({ toaster.create({
// title: `[${error.status}] ${error.statusMessage}`, title: `[${error.status}] ${error.statusMessage}`,
// description: error.message, description: error.message,
// }); });
// }; };
// export const Toaster = () => { export const Toaster = () => {
// return ( return (
// <Portal> <Portal>
// <ArkToaster toaster={toaster}> <ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}>
// {(toast) => ( {(toast) => (
// <Toast.Root> <Toast.Root width={{ md: "sm" }}>
// <HStack spacing="1" style={{ width: "100%" }}> {toast.type === "loading" ? (
// {toast.title && <Toast.Title>{toast.title}</Toast.Title>} <Spinner size="sm" color="blue.solid" />
// {toast.description && ( ) : (
// <Toast.Description>{toast.description}</Toast.Description> <Toast.Indicator />
// )} )}
// </HStack> <Stack gap="1" flex="1" maxWidth="100%">
// {toast.action && ( {toast.title && <Toast.Title>{toast.title}</Toast.Title>}
// <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger> {toast.description && (
// )} <Toast.Description>{toast.description}</Toast.Description>
// {toast.meta?.closable && <Toast.CloseTrigger />} )}
// </Toast.Root> </Stack>
// )} {toast.action && (
// </ArkToaster> <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
// </Portal> )}
// ) {toast.meta?.closable && <Toast.CloseTrigger />}
// } </Toast.Root>
)}
</ChakraToaster>
</Portal>
)
}

View File

@ -0,0 +1,46 @@
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
import * as React from "react"
export interface TooltipProps extends ChakraTooltip.RootProps {
showArrow?: boolean
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
content: React.ReactNode
contentProps?: ChakraTooltip.ContentProps
disabled?: boolean
}
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
function Tooltip(props, ref) {
const {
showArrow,
children,
disabled,
portalled = true,
content,
contentProps,
portalRef,
...rest
} = props
if (disabled) return children
return (
<ChakraTooltip.Root {...rest}>
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
<Portal disabled={!portalled} container={portalRef}>
<ChakraTooltip.Positioner>
<ChakraTooltip.Content ref={ref} {...contentProps}>
{showArrow && (
<ChakraTooltip.Arrow>
<ChakraTooltip.ArrowTip />
</ChakraTooltip.Arrow>
)}
{content}
</ChakraTooltip.Content>
</ChakraTooltip.Positioner>
</Portal>
</ChakraTooltip.Root>
)
},
)

28
front/src/config/dayjs.ts Normal file
View File

@ -0,0 +1,28 @@
import dayjs from 'dayjs';
import 'dayjs/locale/fr';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import dayOfYear from 'dayjs/plugin/dayOfYear';
import duration from 'dayjs/plugin/duration';
import isBetween from 'dayjs/plugin/isBetween';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isToday from 'dayjs/plugin/isToday';
import isTomorrow from 'dayjs/plugin/isTomorrow';
import isYesterday from 'dayjs/plugin/isYesterday';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
import relativeTime from 'dayjs/plugin/relativeTime';
import weekOfYear from 'dayjs/plugin/weekOfYear';
dayjs.locale('fr');
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
dayjs.extend(weekOfYear);
dayjs.extend(isSameOrAfter);
dayjs.extend(isToday);
dayjs.extend(isTomorrow);
dayjs.extend(isYesterday);
dayjs.extend(dayOfYear);
dayjs.extend(isBetween);
dayjs.extend(advancedFormat);
dayjs.extend(quarterOfYear);
dayjs.extend(duration);

View File

@ -0,0 +1,2 @@
import './axios';
import './dayjs';

View File

@ -1,4 +1,4 @@
export const BASE_WRAP_SPACING = "5px"; // { base: "5px", md: "10px", lg: "20px" }; export const BASE_WRAP_SPACING = { base: "5px", md: "10px", lg: "20px" };
export const BASE_WRAP_WIDTH = "90%"; // { base: "90%", md: "45%", lg: "270px" }; export const BASE_WRAP_WIDTH = { base: "90%", md: "45%", lg: "270px" };
export const BASE_WRAP_HEIGHT = "75px"; // { base: "75px", lg: "120px" }; export const BASE_WRAP_HEIGHT = { base: "75px", lg: "120px" };
export const BASE_WRAP_ICON_SIZE = "50px";// { base: "50px", lg: "100px" }; export const BASE_WRAP_ICON_SIZE = { base: "50px", lg: "100px" };

View File

@ -1,27 +1,26 @@
import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react';
import { MdControlCamera } from 'react-icons/md'; import { MdControlCamera } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error401 = () => { export const Error401 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px" width="75%"> <PageLayoutInfoCenter padding="25px">
<Flex align="center"> <Center>
<MdControlCamera size="250px" color="red.600" /> <MdControlCamera style={{ width: "250px", height: "250px", color: "orange" }} />
</Flex> </Center>
<Flex style={{ textAlign: "center" }}> <Box textAlign="center">
<Text fontSize="px">Erreur 401</Text> <Heading>Erreur 401</Heading>
<Text color="red.600"> <Text color="red.600">
Vous n'êtes pas autorisé a accéder a ce contenu. Vous n'êtes pas autorisé a accéder a ce contenu.
</Text> </Text>
<Link href="/"> <Link as="a" href="/">
Retour à l'accueil Retour à l'accueil
</Link> </Link>
</Flex> </Box>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@ -1,26 +1,24 @@
import { MdControlCamera } from 'react-icons/md'; import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react';
import { MdDangerous } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error403 = () => { export const Error403 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px" width="75%"> <PageLayoutInfoCenter padding="25px">
<Flex align="center"> <Center>
<MdControlCamera size="250px" color="red.600" /> <MdDangerous style={{ width: "250px", height: "250px", color: "red" }} />
</Flex> </Center>
<Flex style={{ textAlign: "center" }}> <Box textAlign="center">
<Text fontSize="px">Erreur 403</Text> <Heading>Erreur 403</Heading>
<Text color="red.600"> <Text color="orange.600">Cette page vous est interdite</Text>
Cette page vous est interdite.
</Text>
<Link href="/"> <Link href="/">
Retour à l'accueil Retour à l'accueil
</Link> </Link>
</Flex> </Box>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@ -1,26 +1,26 @@
import { MdControlCamera } from 'react-icons/md'; import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react';
import { MdSignpost } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error404 = () => { export const Error404 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px" width="75%"> <PageLayoutInfoCenter padding="25px">
<Flex align="center"> <Center>
<MdControlCamera size="250px" color="red.600" /> <MdSignpost style={{ width: "250px", height: "250px", color: "aqua" }} />
</Flex> </Center>
<Flex style={{ textAlign: "center" }}> <Box textAlign="center">
<Text fontSize="px">Erreur 404</Text> <Heading>Erreur 404</Heading>
<Text color="red.600"> <Text color="gray.600">
Cette page n'existe plus ou l'URL a changé. Cette page n'existe plus ou l'URL a changé
</Text> </Text>
<Link href="/"> <Link href="/">
Retour à l'accueil Retour à l'accueil
</Link> </Link>
</Flex> </Box>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@ -1,44 +1,30 @@
import { Button, Flex, Text } from '@/ui';
import { useDisclosure } from '@/utils/disclosure';
import React, { FC } from 'react'; import React, { FC } from 'react';
import {
AlertDescription,
AlertTitle,
Box,
Alert,
} from '@chakra-ui/react';
import { import {
FallbackProps, FallbackProps,
ErrorBoundary as ReactErrorBoundary, ErrorBoundary as ReactErrorBoundary,
} from 'react-error-boundary'; } from 'react-error-boundary';
import { LuChevronUp, LuChevronDown } from 'react-icons/lu';
const ErrorFallback = ({ error }: FallbackProps) => { const ErrorFallback = ({ error }: FallbackProps) => {
const { open, onToggle } = useDisclosure();
return ( return (
<Flex direction="column" style={{ padding: 4, margin: "auto", backgroundColor: "red.500" }}> <Box padding="8" margin="auto">
<Text color='red'>An unexpected error has occurred.</Text> <Alert.Root status="error" borderRadius="md">
<Text>Message: {error.message}</Text> <Alert.Indicator height="75px" width="75px" />
{/* <Box flex="1">
<AlertRoot status="error" borderRadius="md"> <AlertTitle fontWeight="bold" fontSize="35px">An unexpected error has occurred.</AlertTitle>
<Flex style={{ flex: "1" }}> <AlertDescription padding="5" marginTop="3" fontSize="20px" lineHeight="1.4">
<AlertTitle>An unexpected error has occurred.</AlertTitle> <br />
<AlertDescription display="block" lineHeight="1.4"> {error.message}
<Button
//theme="@secondary"
color="red.800"
//size="sm"
onClick={onToggle}
>
Show details {open ? <LuChevronUp /> : <LuChevronDown />}
</Button>
<Collapsible.Root open={open}>
<Collapsible.Content>
<Flex mt={4} fontFamily="monospace">
{error.message}
</Flex>
</Collapsible.Content>
</Collapsible.Root>
</AlertDescription> </AlertDescription>
</Flex> </Box>
</AlertRoot> */} </Alert.Root>
</Flex > </Box>
); );
}; };

View File

@ -1,15 +1,14 @@
import { createIcon } from '@chakra-ui/react';
export const DoubleArrowIcon = createIcon({
export const DoubleArrowIcon = <></>; displayName: 'DoubleArrowIcon',
// createIcon({ viewBox: '0 0 24 24',
// displayName: 'DoubleArrowIcon', path: (
// viewBox: '0 0 24 24', <path
// path: ( fillRule="evenodd"
// <path clipRule="evenodd"
// fillRule="evenodd" d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z"
// clipRule="evenodd" fill="currentColor"
// d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z" />
// fill="currentColor" ),
// /> });
// ),
// });

View File

@ -1,15 +1,19 @@
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from '@/App'; import App from '@/App';
import { ThemeProvider } from './theme/ThemeContext'; import { ColorModeProvider } from './components/ui/color-mode';
// Render the app // Render the app
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
if (rootElement && !rootElement.innerHTML) { if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement); const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
// <StrictMode> <StrictMode>
<ThemeProvider> <ColorModeProvider>
<App /> <App />
</ThemeProvider> </ColorModeProvider>
// </StrictMode> </StrictMode>
); );
} }

View File

@ -1,7 +1,6 @@
import { AudioPlayer } from '@/components/AudioPlayer'; import { AudioPlayer } from '@/components/AudioPlayer';
import { ErrorBoundary } from '@/errors/ErrorBoundary'; import { ErrorBoundary } from '@/errors/ErrorBoundary';
import { AppRoutes } from '@/scene/AppRoutes'; import { AppRoutes } from '@/scene/AppRoutes';
import { Text } from '@/ui';
import { ServiceContextProvider } from '@/service/ServiceContext'; import { ServiceContextProvider } from '@/service/ServiceContext';
export const App = () => { export const App = () => {

View File

@ -1,5 +1,6 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { MdEdit } from 'react-icons/md'; import { LuDisc3 } from 'react-icons/lu';
import { Md1kPlus, MdAdd, MdEdit, MdPlusOne } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
@ -13,9 +14,8 @@ import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useTracksOfAnAlbum } from '@/service/Track'; import { useTracksOfAnAlbum } from '@/service/Track';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { Button, Flex, Text } from '@/ui';
export const AlbumDetailPage = () => { export const AlbumDetailPage = () => {
const { albumId } = useParams(); const { albumId } = useParams();
@ -46,7 +46,7 @@ export const AlbumDetailPage = () => {
return ( return (
<> <>
<TopBar title="Album detail" /> <TopBar title="Album detail" />
<PageLayoutInfoCenter width="75%"> <PageLayoutInfoCenter>
Fail to load artist id: {albumId} Fail to load artist id: {albumId}
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
@ -68,19 +68,17 @@ export const AlbumDetailPage = () => {
data-testid="Album-detail-page_layout"> data-testid="Album-detail-page_layout">
<Flex <Flex
direction="row" direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto",
padding: "10px",
}}
> >
<Covers <Covers
data={dataAlbum?.covers} data={dataAlbum?.covers}
// TODO: iconEmpty={LuDisc3} iconEmpty={<LuDisc3 />}
slideshow slideshow
/> />
<Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> <Flex direction="column" width="80%" marginRight="auto">
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Text>
@ -95,27 +93,25 @@ export const AlbumDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap={BASE_WRAP_SPACING} style={{ gap={BASE_WRAP_SPACING}
margin: "0 auto", marginX="auto"
padding: "20px", padding="20px"
width: "80%", width="80%"
}} data-testid="Album-detail-page_flex-list"
data-test-id="Album-detail-page_flex-list"
> >
{tracksOnAnAlbum?.map((data) => ( {tracksOnAnAlbum?.map((data) => (
<Flex <Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
minWidth: "100%", as="button"
//height="60px" _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
@ -124,15 +120,20 @@ export const AlbumDetailPage = () => {
contextMenu={[ contextMenu={[
{ {
name: 'Edit', name: 'Edit',
icon: <MdEdit />,
onClick: () => { onClick: () => {
navigate(`/album/${albumId}/edit-track/${data.id}`); navigate(`/album/${albumId}/edit-track/${data.id}`);
}, },
}, },
{ name: 'Add Playlist', onClick: () => { } }, {
icon: <MdAdd />,
name: 'Add Playlist',
onClick: () => { }
},
]} ]}
data-testid="Album-detail-page_display-detail" data-testid="Album-detail-page_display-detail"
/> />
</Flex> </Box>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@ -9,9 +9,9 @@ import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayAlbum } from '@/components/album/DisplayAlbum'; import { DisplayAlbum } from '@/components/album/DisplayAlbum';
import { useOrderedAlbums } from '@/service/Album'; import { useOrderedAlbums } from '@/service/Album';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING, BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing';
import { Button, Flex, HStack } from '@/ui'; import { Flex, HStack } from '@chakra-ui/react';
export const AlbumsPage = () => { export const AlbumsPage = () => {
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
@ -25,7 +25,7 @@ export const AlbumsPage = () => {
return ( return (
<> <>
<TopBar title="All Albums" /> <TopBar title="All Albums" />
<PageLayoutInfoCenter width="75%">No Album available</PageLayoutInfoCenter> <PageLayoutInfoCenter>No Album available</PageLayoutInfoCenter>
</> </>
); );
} }
@ -36,31 +36,29 @@ export const AlbumsPage = () => {
<SearchInput onChange={setFilterTitle} /> <SearchInput onChange={setFilterTitle} />
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> <HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{dataAlbums.map((data) => ( {dataAlbums.map((data) => (
<Button <Flex align="flex-start"
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
width: BASE_WRAP_WIDTH, as="button"
height: BASE_WRAP_HEIGHT, _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover={
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
alignContent: "flex-start",
}} }}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
> >
<DisplayAlbum dataAlbum={data} /> <DisplayAlbum dataAlbum={data} />
</Button> </Flex>
))} ))}
</HStack> </HStack>
<EmptyEnd /> <EmptyEnd />
</PageLayout > </PageLayout>
</> </>
); );
}; };

View File

@ -1,5 +1,6 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { MdEdit } from 'react-icons/md'; import { LuDisc3, LuUser } from 'react-icons/lu';
import { MdAdd, MdEdit, MdPerson } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
@ -14,8 +15,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtist } from '@/service/Artist'; import { useSpecificArtist } from '@/service/Artist';
import { useTracksOfAnAlbum } from '@/service/Track'; import { useTracksOfAnAlbum } from '@/service/Track';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Button, Flex, Text } from '@/ui';
export const ArtistAlbumDetailPage = () => { export const ArtistAlbumDetailPage = () => {
const { artistId, albumId } = useParams(); const { artistId, albumId } = useParams();
@ -60,19 +60,14 @@ export const ArtistAlbumDetailPage = () => {
<> <>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
style={{
marginRight: "auto"
}}
onClick={() => navigate(`/artist/${dataArtist.id}`)} onClick={() => navigate(`/artist/${dataArtist.id}`)}
> >
<Covers <Covers
data={dataArtist?.covers} data={dataArtist?.covers}
size="35px" size="35px"
style={{ borderRadius="full"
borderRadius: "full", iconEmpty={<MdPerson />}
}}
// TODO: iconEmpty={MdPerson}
/> />
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataArtist?.name} {dataArtist?.name}
@ -92,20 +87,17 @@ export const ArtistAlbumDetailPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="row" direction="row"
style={{ width="80%"
width: "80%", marginX="auto"
margin: "0 auto", padding="10px"
padding: "10px",
}}
gap="10px" gap="10px"
> >
<Covers <Covers
data={dataAlbum?.covers} data={dataAlbum?.covers}
// TODO: iconEmpty={LuDisc3} iconEmpty={<LuDisc3 />}
slideshow slideshow
/> />
<Flex direction="column" <Flex direction="column" width="80%" marginRight="auto">
style={{ width: "80%", marginRight: "auto" }}>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Text>
@ -121,28 +113,22 @@ export const ArtistAlbumDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
marginX="auto"
style={{ padding="20px"
margin: "0 auto", width="80%"
padding: "20px",
width: "80%",
}}
> >
{tracksOnAnAlbum?.map((data) => ( {tracksOnAnAlbum?.map((data) => (
<Flex <Box
minWidth="100%"
height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px"
style={{ _hover={{
minWidth: "100%", boxShadow: 'outline-over',
height: "60px", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
border: "1px",
borderColor: "brand.900",
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrack <DisplayTrack
@ -151,16 +137,21 @@ export const ArtistAlbumDetailPage = () => {
contextMenu={[ contextMenu={[
{ {
name: 'Edit', name: 'Edit',
icon: <MdEdit />,
onClick: () => { onClick: () => {
navigate( navigate(
`/artist/${artistIdInt}/album/${albumId}/edit-track/${data.id}` `/artist/${artistIdInt}/album/${albumId}/edit-track/${data.id}`
); );
}, },
}, },
{ name: 'Add Playlist', onClick: () => { } }, {
icon: <MdAdd />,
name: 'Add Playlist',
onClick: () => { }
},
]} ]}
/> />
</Flex> </Box>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@ -1,4 +1,5 @@
import { Button, Flex, Text, HStack } from '@chakra-ui/react';
import { LuUser } from 'react-icons/lu';
import { MdEdit, MdGroup } from 'react-icons/md'; import { MdEdit, MdGroup } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@ -11,9 +12,8 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp'; import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp';
import { useSpecificArtist } from '@/service/Artist'; import { useSpecificArtist } from '@/service/Artist';
import { useAlbumIdsOfAnArtist } from '@/service/Track'; import { useAlbumIdsOfAnArtist } from '@/service/Track';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; import { BASE_WRAP_HEIGHT, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing';
import { Button, Flex, HStack, Text } from '@/ui';
export const ArtistDetailPage = () => { export const ArtistDetailPage = () => {
const { artistId } = useParams(); const { artistId } = useParams();
@ -41,7 +41,7 @@ export const ArtistDetailPage = () => {
<> <>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
style={{ marginRight: "auto" }} marginRight="auto"
onClick={() => navigate(`/artist/all`)} onClick={() => navigate(`/artist/all`)}
> >
<MdGroup height="full" /> <MdGroup height="full" />
@ -64,13 +64,13 @@ export const ArtistDetailPage = () => {
<Flex <Flex
direction="row" direction="row"
width="80%" width="80%"
margin="0 auto" marginX="auto"
padding="10px" padding="10px"
gap="10px" gap="10px"
> >
<Covers <Covers
data={dataArtist?.covers} data={dataArtist?.covers}
// TODO: iconEmpty={LuUser} iconEmpty={<LuUser />}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column" width="80%" marginRight="auto">
@ -88,27 +88,25 @@ export const ArtistDetailPage = () => {
</Flex> </Flex>
</Flex> </Flex>
<HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> <HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{albumIdsOfAnArtist?.map((data) => ( {albumIdsOfAnArtist?.map((data) => (
<Button <Flex align="flex-start"
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
style={{ padding="5px"
alignContent: "flex-start", as="button"
width: BASE_WRAP_WIDTH, _hover={{
height: BASE_WRAP_HEIGHT, boxShadow: 'outline-over',
border: "1px", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
borderColor: "brand.900",
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover={
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<DisplayAlbumId id={data} key={data} /> <DisplayAlbumId id={data} key={data} />
</Button> </Flex>
))} ))}
</HStack> </HStack>
<EmptyEnd /> <EmptyEnd />

View File

@ -1,5 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { Button, Flex, Text, Tooltip, HStack, Span } from '@chakra-ui/react';
import { LuUser } from 'react-icons/lu';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Artist, Track } from '@/back-api'; import { Artist, Track } from '@/back-api';
@ -9,14 +11,13 @@ import { PageLayout } from '@/components/Layout/PageLayout';
import { SearchInput } from '@/components/SearchInput'; import { SearchInput } from '@/components/SearchInput';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { useOrderedArtists } from '@/service/Artist'; import { useOrderedArtists } from '@/service/Artist';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing';
import { MdOutlineForkRight } from 'react-icons/md'; import { MdOutlineForkRight } from 'react-icons/md';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { shuffleArray } from '@/utils/arrayTools'; import { shuffleArray } from '@/utils/arrayTools';
import { useTrackService } from '@/service/Track'; import { useTrackService } from '@/service/Track';
import { DataTools, TypeCheck } from '@/utils/data-tools'; import { DataTools, TypeCheck } from '@/utils/data-tools';
import { Button, Flex, HStack, Span, Text } from '@/ui';
const LIMIT_RANDOM_VALUES = 25; const LIMIT_RANDOM_VALUES = 25;
export const ArtistsPage = () => { export const ArtistsPage = () => {
@ -56,27 +57,27 @@ export const ArtistsPage = () => {
<> <>
<TopBar title="All artists"> <TopBar title="All artists">
<SearchInput onChange={setFilterName} /> <SearchInput onChange={setFilterName} />
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}> <Tooltip.Root aria-label="Random play">
<MdOutlineForkRight /> <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}>
</Button> <MdOutlineForkRight style={{ width: "100%", height: "100%" }} />
</Button>
</Tooltip.Root>
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", alignContent: "center" }}> <HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{dataArtist?.map((data) => ( {dataArtist?.map((data) => (
<Button <Flex align="flex-start"
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
width: BASE_WRAP_WIDTH, as="button"
height: BASE_WRAP_HEIGHT, _hover={{
alignContent: "flex-start", boxShadow: 'outline-over',
border: "1px", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
borderColor: "brand.900",
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover:{
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
@ -84,8 +85,8 @@ export const ArtistsPage = () => {
<Covers <Covers
data={data.covers} data={data.covers}
size={BASE_WRAP_ICON_SIZE} size={BASE_WRAP_ICON_SIZE}
style={{ height: "full" }} height="full"
// iconEmpty={LuUser} iconEmpty={<LuUser />}
/> />
<Flex <Flex
direction="column" direction="column"
@ -93,20 +94,18 @@ export const ArtistsPage = () => {
maxWidth="150px" maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
style={{ overflowX: "hidden" }} overflowX="hidden"
> >
<Text <Text
/*align="left"*/ textAlign="left"
/*noOfLines={[1, 2]}*/ /*noOfLines={[1, 2]}*/
> >
<Span <Span
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
style={{ marginRight="auto"
marginRight: "auto", overflow="hidden"
overflow: "hidden",
}}
> >
{data.name} {data.name}
</Span> </Span>
@ -114,11 +113,11 @@ export const ArtistsPage = () => {
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
</Button> </Flex>
))} ))}
</HStack> </HStack>
<EmptyEnd /> <EmptyEnd />
</PageLayout> </PageLayout >
</> </>
); );
}; };

View File

@ -1,4 +1,5 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { MdEdit } from 'react-icons/md'; import { MdEdit } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@ -9,13 +10,13 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp'; import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useTracksOfAGender } from '@/service/Track'; import { useTracksOfAGender } from '@/service/Track';
//import { useTracksOfAGender } from '@/service/Track'; //import { useTracksOfAGender } from '@/service/Track';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Button, Flex, Text } from '@/ui';
export const GenderDetailPage = () => { export const GenderDetailPage = () => {
const { genderId } = useParams(); const { genderId } = useParams();
@ -67,19 +68,17 @@ export const GenderDetailPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="row" direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto",
padding: "10px",
}}
> >
<Covers <Covers
data={dataGender?.covers} data={dataGender?.covers}
// TODO: iconEmpty={LuDisc3} iconEmpty={<LuDisc3 />}
slideshow slideshow
/> />
<Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> <Flex direction="column" width="80%" marginRight="auto">
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataGender?.name} {dataGender?.name}
</Text> </Text>
@ -92,26 +91,23 @@ export const GenderDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
style={{ marginX="auto"
margin: "0 auto", padding="20px"
padding: "20px", width="80%"
width: "80%",
}}
> >
{tracksOnAGender?.map((data) => ( {tracksOnAGender?.map((data) => (
<Flex <Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
minWidth: "100%", as="button"
//height="60px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
@ -127,7 +123,7 @@ export const GenderDetailPage = () => {
{ name: 'Add Playlist', onClick: () => { } }, { name: 'Add Playlist', onClick: () => { } },
]} ]}
/> />
</Flex> </Box>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>
@ -135,7 +131,7 @@ export const GenderDetailPage = () => {
<Route path="edit-track/:trackId" element={<TrackEditPopUp />} /> <Route path="edit-track/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-gender/:genderId" element={<GenderEditPopUp />} /> <Route path="edit-gender/:genderId" element={<GenderEditPopUp />} />
</Routes> </Routes>
</PageLayout> </PageLayout >
</> </>
); );
}; };

View File

@ -9,8 +9,8 @@ import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayGender } from '@/components/gender/DisplayGender'; import { DisplayGender } from '@/components/gender/DisplayGender';
import { useOrderedGenders } from '@/service/Gender'; import { useOrderedGenders } from '@/service/Gender';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Flex, HStack } from '@/ui'; import { Flex, HStack } from '@chakra-ui/react';
export const GendersPage = () => { export const GendersPage = () => {
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
@ -35,24 +35,20 @@ export const GendersPage = () => {
<SearchInput onChange={setFilterTitle} /> <SearchInput onChange={setFilterTitle} />
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<HStack style={{ <HStack wrap="wrap" gap="20px" marginX="auto" padding="20px" justify="center">
// wrap: "wrap",
/*spacing="20px"*/ margin: "0 auto 0 auto", padding: "20px", justifyContent: "center"
}}>
{dataGenders.map((data) => ( {dataGenders.map((data) => (
<Flex align="flex-start" <Flex align="flex-start"
width="270px"
height="120px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
width: "270px", as="button"
height: "120px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
//backgroundColor:{useColorModeValue('#FFFFFF88', '#00000088')},
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
> >

View File

@ -1,5 +1,12 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import {
Flex,
Input,
Table,
Text,
Button,
} from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu'; import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload } from 'react-icons/md'; import { MdCloudUpload } from 'react-icons/md';
@ -25,7 +32,6 @@ import { useGenderService, useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useTrackService } from '@/service/Track'; import { useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, Table, Text } from '@/ui';
export class ElementList { export class ElementList {
constructor( constructor(
@ -115,7 +121,7 @@ export const AddPage = () => {
updateNeedSend(); updateNeedSend();
}; };
const removeElementFromList = (data: FileParsedElement): void => { const removeElementFromList = (data: FileParsedElement, value: any): void => {
const parsedElementTmp = [...parsedElement]; const parsedElementTmp = [...parsedElement];
for (let iii = 0; iii < parsedElementTmp.length; iii++) { for (let iii = 0; iii < parsedElementTmp.length; iii++) {
if (parsedElementTmp[iii] === data) { if (parsedElementTmp[iii] === data) {
@ -414,17 +420,15 @@ export const AddPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="column" direction="column"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto 0 auto",
padding: "10px",
}}
> >
<Flex direction="column" style={{ width: "full" }}> <Flex direction="column" width="full">
<Flex> <Flex>
<Text>format:</Text> <Text flex={1}>format:</Text>
<Text> <Text flex={4}>
The format of the media permit to automatic find meta-data: The format of the media permit to automatic find meta-data:
<br /> <br />
Artist~album#idTrack-my name of my media.webm Artist~album#idTrack-my name of my media.webm
@ -433,15 +437,15 @@ export const AddPage = () => {
</Text> </Text>
</Flex> </Flex>
<Flex> <Flex>
<Text>Media:</Text> <Text flex={1}>Media:</Text>
{/* <Input <Input
flex={4} flex={4}
type="file" type="file"
placeholder="Select a media file" placeholder="Select a media file"
accept=".webm" accept=".webm"
multiple multiple
onChange={onChangeFile} onChange={onChangeFile}
/> */} />
</Flex> </Flex>
</Flex> </Flex>
{parsedElement && parsedElement.length !== 0 && ( {parsedElement && parsedElement.length !== 0 && (
@ -474,12 +478,14 @@ export const AddPage = () => {
suggestion={suggestedAlbum} suggestion={suggestedAlbum}
/> />
<Table.Root <Table.Root
colorPalette="striped"
colorScheme="teal"
background="gray.700" background="gray.700"
> >
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColumnHeader>track ID</Table.ColumnHeader> <Table.ColumnHeader>track ID</Table.ColumnHeader>
<Table.ColumnHeader style={{ width: "full" }}>Title</Table.ColumnHeader> <Table.ColumnHeader width="full">Title</Table.ColumnHeader>
<Table.ColumnHeader>actions</Table.ColumnHeader> <Table.ColumnHeader>actions</Table.ColumnHeader>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
@ -487,7 +493,7 @@ export const AddPage = () => {
{parsedElement.map((data) => ( {parsedElement.map((data) => (
<Table.Row key={data.uniqueId}> <Table.Row key={data.uniqueId}>
<Table.Cell> <Table.Cell>
{/* <Input <Input
type="number" type="number"
pattern="[0-9]{0-4}" pattern="[0-9]{0-4}"
placeholder="e?" placeholder="e?"
@ -498,10 +504,10 @@ export const AddPage = () => {
? 'darkred' ? 'darkred'
: undefined : undefined
} }
/> */} />
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell>
{/* <Input <Input
type="text" type="text"
placeholder="Name of the Media" placeholder="Name of the Media"
value={data.title} value={data.title}
@ -509,11 +515,11 @@ export const AddPage = () => {
backgroundColor={ backgroundColor={
data.title === '' ? 'darkred' : undefined data.title === '' ? 'darkred' : undefined
} }
/> */} />
{data.nameDetected === true && ( {data.nameDetected === true && (
<> <>
<br /> <br />
<Text style={{ backgroundColor: "red" }}> <Text as="span" color="@danger">
^^^This title already exist !!! ^^^This title already exist !!!
</Text> </Text>
</> </>
@ -521,8 +527,8 @@ export const AddPage = () => {
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell>
<Button <Button
onClick={() => onClick={(e) =>
removeElementFromList(data) removeElementFromList(data, e.target)
} }
> >
<LuTrash /> Remove <LuTrash /> Remove
@ -532,14 +538,13 @@ export const AddPage = () => {
))} ))}
</Table.Body> </Table.Body>
</Table.Root> </Table.Root>
<Flex style={{ margin: "15px 0 15px 0" }}> <Flex marginY="15px">
<Button <Button
//theme="@primary" colorPalette="brand"
onClick={sendFile} onClick={sendFile}
style={{ disabled={!needSend}
//disabled:!needSend, marginLeft="auto"
margin: "0 auto 0 30px" marginRight="30px"
}}
> >
<MdCloudUpload /> Upload <MdCloudUpload /> Upload
</Button> </Button>
@ -549,16 +554,14 @@ export const AddPage = () => {
{listFileInBdd && ( {listFileInBdd && (
<Table.Root <Table.Root
//fontPalette="striped" fontPalette="striped"
//colorScheme="teal" colorScheme="teal"
style={{ background="gray.700"
background: "gray.700"
}}
> >
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColumnHeader>track ID</Table.ColumnHeader> <Table.ColumnHeader>track ID</Table.ColumnHeader>
<Table.ColumnHeader style={{ width: "full" }}>Title</Table.ColumnHeader> <Table.ColumnHeader width="full">Title</Table.ColumnHeader>
<Table.ColumnHeader>actions</Table.ColumnHeader> <Table.ColumnHeader>actions</Table.ColumnHeader>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
@ -594,13 +597,13 @@ export const AddPage = () => {
<> <>
<Text fontSize="30px">Rejected:</Text> <Text fontSize="30px">Rejected:</Text>
<Table.Root <Table.Root
//colorPalette="striped" colorPalette="striped"
//colorScheme="teal" colorScheme="teal"
style={{ background: "gray.700" }} background="gray.700"
> >
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColumnHeader style={{ maxWidth: "80%" }}>file</Table.ColumnHeader> <Table.ColumnHeader maxWidth="80%">file</Table.ColumnHeader>
<Table.ColumnHeader>Reason</Table.ColumnHeader> <Table.ColumnHeader>Reason</Table.ColumnHeader>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>

View File

@ -1,5 +1,6 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { Center, Flex, HStack, Text } from '@chakra-ui/react';
import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu'; import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu';
import { MdGroup } from 'react-icons/md'; import { MdGroup } from 'react-icons/md';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -7,8 +8,7 @@ import { useNavigate } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Div, Flex, HStack, Text } from '@/ui';
type HomeListType = { type HomeListType = {
id: number; id: number;
@ -58,43 +58,40 @@ export const HomePage = () => {
<> <>
<TopBar title="Home" /> <TopBar title="Home" />
<PageLayout> <PageLayout>
<HStack style={{ flexWrap: "wrap", /*spacing="20px"*/ margin: "0 auto", padding: "20px", justify: "center" }}> <HStack wrap="wrap" gap="20px" marginX="auto" padding="20px" justify="center">
{homeList.map((data) => ( {homeList.map((data) => (
<Flex align="flex-start" <Flex align="flex-start"
width="200px"
height="190px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
width: "200px", as="button"
height: "190px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover:{
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<Flex direction="column" style={{ width: "full", height: "full" }}> <Flex direction="column" width="full" height="full">
<Div style={{ alignContent: "center", height: "full" }}>{data.icon}</Div> <Center height="full">{data.icon}</Center>
<Div style={{ alignContent: "center", height: "full" }}> <Center>
<Text <Text
fontSize="25px" fontSize="25px"
fontWeight="bold" fontWeight="bold"
style={{ textTransform="uppercase"
textTransform: "uppercase", userSelect="none"
userSelect: "none",
}}
> >
{data.name} {data.name}
</Text> </Text>
</Div> </Center>
</Flex> </Flex>
</Flex> </Flex>
))} ))}
</HStack> </HStack>
</PageLayout > </PageLayout>
</> </>
); );
}; };

View File

@ -1,16 +1,19 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { Route, Routes, useNavigate } from 'react-router-dom'; import { Route, Routes, useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId'; import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId';
import { Flex } from '@/ui'; import { MdCleanHands, MdClear, MdDryCleaning, MdEdit, MdKeyboardDoubleArrowDown, MdKeyboardDoubleArrowUp, MdLeakRemove, MdRemove, MdRemoveFromQueue, MdRemoveShoppingCart } from 'react-icons/md';
import { Button } from '@/components/ui/button';
import { LuTrash2 } from 'react-icons/lu';
export const OnAirPage = () => { export const OnAirPage = () => {
const { playInList } = useActivePlaylistService(); const { playInList } = useActivePlaylistService();
@ -53,9 +56,39 @@ export const OnAirPage = () => {
setNewPlaylist(newList); setNewPlaylist(newList);
} }
}; };
const removePreviousTrack = (index: number) => {
if (!playTrackList) {
console.log('Fail to remove element of on air...');
return;
}
const newList = playTrackList.slice(index);
let newPlayed = trackOffset;
if (newPlayed != undefined) {
if (index < newPlayed) {
newPlayed = newPlayed - index;
} else {
newPlayed = 0;
}
playInList(newPlayed, newList);
} else {
setNewPlaylist(newList);
}
}
const removeNextTrack = (index: number) => {
if (!playTrackList) {
console.log('Fail to remove element of on air...');
return;
}
const newList = playTrackList.slice(0, index + 1);
playInList(trackOffset, newList);
}
const clean = () => {
setNewPlaylist([]);
}
console.log(`playTrackList = ${JSON.stringify(playTrackList, null, 2)}`); console.log(`playTrackList = ${JSON.stringify(playTrackList, null, 2)}`);
if (!playTrackList) { if (!playTrackList || playTrackList.length == 0) {
return ( return (
<> <>
<TopBar title="Album detail" /> <TopBar title="Album detail" />
@ -68,32 +101,35 @@ export const OnAirPage = () => {
return ( return (
<> <>
<TopBar title="On Air ..."> <TopBar title="On Air ...">
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={clean}
>
<LuTrash2 />
</Button>
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<Flex <Flex
direction="column" direction="column"
gap={BASE_WRAP_SPACING} gap={BASE_WRAP_SPACING}
style={{ marginX="auto"
margin: "0 auto", padding="20px"
padding: "20px", width="80%"
width: "80%",
}}
> >
{!playTrackList && <>No playing</>} {!playTrackList && <>No playing</>}
{playTrackList && playTrackList?.map((data) => ( {playTrackList && playTrackList?.map((data, index) => (
<Flex <Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
style={{ padding="5px"
minWidth: "100%", as="button"
//height:"60px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFullId <DisplayTrackFullId
@ -102,19 +138,35 @@ export const OnAirPage = () => {
contextMenu={[ contextMenu={[
{ {
name: 'Edit', name: 'Edit',
icon: <MdEdit />,
onClick: () => { onClick: () => {
navigate(`edit-track/${data}`); navigate(`edit-track/${data}`);
}, },
}, },
{ {
name: 'Remove from playlist', name: 'Remove previous',
icon: <MdKeyboardDoubleArrowUp />,
onClick: () => {
removePreviousTrack(index);
},
},
{
name: 'Remove',
icon: <MdRemove />,
onClick: () => { onClick: () => {
removeTrack(data); removeTrack(data);
}, },
}, },
{
name: 'Remove After',
icon: <MdKeyboardDoubleArrowDown />,
onClick: () => {
removeNextTrack(index);
},
},
]} ]}
/> />
</Flex> </Box>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@ -1,5 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Center, Heading, Image, Text } from '@chakra-ui/react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import avatar_generic from '@/assets/images/avatar_generic.svg'; import avatar_generic from '@/assets/images/avatar_generic.svg';
@ -10,8 +11,6 @@ import { SessionState } from '@/service/SessionState';
import { useSessionService } from '@/service/session'; import { useSessionService } from '@/service/session';
import { b64_to_utf8 } from '@/utils/sso'; import { b64_to_utf8 } from '@/utils/sso';
import { Flex, Text, Image } from '@/ui';
export const SSOPage = () => { export const SSOPage = () => {
const { data, keepConnected, token } = useParams(); const { data, keepConnected, token } = useParams();
console.log(`- data: ${data}`); console.log(`- data: ${data}`);
@ -48,11 +47,13 @@ export const SSOPage = () => {
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter width="35%" gap="15px"> <PageLayoutInfoCenter width="35%" gap="15px">
<Text>LOGIN (after SSO)</Text> <Center w="full">
<Heading size="xl">LOGIN (after SSO) </Heading>
</Center>
<Flex width="full"> <Center w="full">
<Image href={avatar_generic} boxSize="150px" style={{ borderRadius: "full" }} /> <Image src={avatar_generic} boxSize="150px" borderRadius="full" />
</Flex> </Center>
{token === '__CANCEL__' && ( {token === '__CANCEL__' && (
<Text> <Text>
<b>ERROR: </b> Request cancel of connection ! <b>ERROR: </b> Request cancel of connection !

View File

@ -1,8 +1,12 @@
import { ReactElement } from 'react';
import { Flex, Text, HStack } from '@chakra-ui/react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { DataTools, TypeCheck } from '@/utils/data-tools';
import { useColorModeValue } from '@/components/ui/color-mode';
export const alphabet = [ export const alphabet = [
'a', 'a',
@ -50,13 +54,13 @@ export const TrackSelectionPage = () => {
height="75px" height="75px"
border="1px" border="1px"
borderColor="brand.900" borderColor="brand.900"
backgroundColor={useColorThemeValue('#FFFFFF88', '#00000088')} backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
padding="5px" padding="5px"
as="button" as="button"
_hover={{ _hover={{
boxShadow: 'outline-over', boxShadow: 'outline-over',
bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >

View File

@ -1,3 +1,6 @@
import { ReactElement } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
@ -8,8 +11,7 @@ import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
import { alphabet } from '@/scene/track/TrackSelectionPage'; import { alphabet } from '@/scene/track/TrackSelectionPage';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useTracksWithStartName } from '@/service/Track'; import { useTracksWithStartName } from '@/service/Track';
import { useColorThemeValue } from '@/theme/ThemeContext'; import { useColorModeValue } from '@/components/ui/color-mode';
import { Flex } from '@/ui';
export const TracksStartLetterDetailPage = () => { export const TracksStartLetterDetailPage = () => {
const { startLetter } = useParams(); const { startLetter } = useParams();
@ -48,55 +50,50 @@ export const TracksStartLetterDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
style={{ marginX="auto"
margin: "0 auto", padding="20px"
padding: "20px", width="80%"
width: "80%",
}}
> >
{isLoading && {isLoading &&
[1, 2, 3, 4]?.map((data) => ( [1, 2, 3, 4]?.map((data) => (
<Flex <Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
style={{ padding="5px"
minWidth: "100%", as="button"
//height:"60px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackSkeleton /> <DisplayTrackSkeleton />
</Flex> </Box>
))} ))}
{!isLoading && {!isLoading &&
tracksStartsWith?.map((data) => ( tracksStartsWith?.map((data) => (
<Flex <Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
style={{ padding="5px"
minWidth: "100%", as="button"
//height:"60px", _hover={{
border: "1px", boxShadow: 'outline-over',
borderColor: "brand.900", bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
track={data} track={data}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
/> />
</Flex> </Box>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@ -14,7 +14,7 @@ export type ActivePlaylistServiceProps = {
trackActive?: Track; trackActive?: Track;
setNewPlaylist: (listIds: number[]) => void; setNewPlaylist: (listIds: number[]) => void;
setNewPlaylistShuffle: (listIds: number[]) => void; setNewPlaylistShuffle: (listIds: number[]) => void;
playInList: (id: number, listIds: number[]) => void; playInList: (id: number | undefined, listIds: number[]) => void;
play: (id: number) => void; play: (id: number) => void;
previous: () => void; previous: () => void;
next: () => void; next: () => void;
@ -65,7 +65,7 @@ export const useActivePlaylistServiceWrapped = (
[setPlayTrackList, setTrackOffset] [setPlayTrackList, setTrackOffset]
); );
const playInList = useCallback( const playInList = useCallback(
(id: number, listIds: number[]) => { (id: number | undefined, listIds: number[]) => {
console.log(`Request paly in list: ${id} in ${listIds}`); console.log(`Request paly in list: ${id} in ${listIds}`);
setPlayTrackList(listIds); setPlayTrackList(listIds);
setTrackOffset(id); setTrackOffset(id);

View File

@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Album, AlbumResource } from '@/back-api'; import { Album, AlbumResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';

View File

@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { Track, TrackResource } from '@/back-api'; import { Track, TrackResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';

View File

@ -6,13 +6,16 @@ import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState'; import { SessionState } from '@/service/SessionState';
import { isBrowser } from '@/utils/layout'; import { isBrowser } from '@/utils/layout';
import { parseToken } from '@/utils/sso'; import { parseToken } from '@/utils/sso';
import { createListCollection } from '@chakra-ui/react';
const TOKEN_KEY = 'karusic-token-key-storage'; const TOKEN_KEY = 'karusic-token-key-storage';
export const USERS_COLLECTION = [ export const USERS_COLLECTION = createListCollection({
{ label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" }, items: [
{ label: "NO_USER", value: "svelte" }, { label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" },
]; { label: "NO_USER", value: "svelte" },
],
})
export const USERS = { export const USERS = {
admin: admin:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',

View File

@ -1,5 +1,6 @@
import { ReactElement, ReactNode } from 'react'; import { ReactElement, ReactNode } from 'react';
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
import { RenderOptions, render } from '@testing-library/react'; import { RenderOptions, render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';

View File

@ -1,115 +0,0 @@
import { createContext, useContext, useState, useEffect, CSSProperties, ReactNode, useCallback } from "react";
import { basicColor } from "./colors";
type themeContextProps = {
theme: string;
themeList: string[];
setTheme: (string) => void;
toggleTheme: () => void;
convertStyle: (style: CSSProperties) => CSSProperties,
}
const ThemeContext = createContext<themeContextProps>({
theme: "error",
themeList: ["error"],
setTheme: (_: string) => { },
toggleTheme: () => { },
convertStyle: (style: CSSProperties): CSSProperties => { return style; },
});
const themes = {
baseColors: {
...basicColor
},
themeColors: {
light: {
primary: "#007bff",
secondary: "#6c757d",
background: "#ffffff",
text: "#000000",
},
dark: {
primary: "#375a7f",
secondary: "#444444",
background: "#181818",
text: "#ffffff",
},
}
};
export function ThemeProvider({ children, themeList = ["light", "dark"] }: { children: ReactNode, themeList?: string[] }) {
const [theme, setTheme] = useState<string>(themeList[0]);
const [basicsColor, setBasicsColor] = useState<{ [key: string]: string }>({});
// update the global CSS wen theme is updated:
useEffect(() => {
// generates the colors:
const baseColor = themes.baseColors;
const newBasicsColor: { [key: string]: string } = {}
Object.entries(baseColor).forEach(([tableColorName, colorValues]) => {
Object.entries(colorValues).forEach(([key, value]) => {
const colorName = `${tableColorName}.${key}`;
newBasicsColor[colorName] = value;
});
});
setBasicsColor(newBasicsColor);
const root = document.documentElement;
//const currentColorTheme = themes.baseColors[theme];
Object.entries(newBasicsColor).forEach(([key, value]) => {
const cssKeyValue = `--${key.replace(".", "-")}`;
console.log(` generate CSS color: ${cssKeyValue}:${value}`);
root.style.setProperty(cssKeyValue, value);
});
}, [theme, setBasicsColor]);
const toggleTheme = useCallback(() => {
console.log(`plop: ${theme}`);
setTheme((previous) => {
if (themeList.length <= 1) {
return previous;
}
for (let iii = 0; iii < themeList.length - 1; iii++) {
if (themeList[iii] == theme) {
return themeList[iii + 1]
}
}
return themeList[0];
})
}, [setTheme]);
const convertElementStyle = useCallback((style: CSSProperties, key: string) => {
const value = style[key];
if (typeof value !== "string") {
return;
}
if (basicsColor[value]) {
style[key] = basicsColor[value];
}
}, [basicsColor]);
const convertStyle = useCallback((style: CSSProperties): CSSProperties => {
console.log(`plop: ${theme}`);
if (!style) {
return style;
}
const out = { ...style }
convertElementStyle(out, "background");
convertElementStyle(out, "borderColor");
convertElementStyle(out, "color");
return out;
}, [convertElementStyle]);
return (
<ThemeContext.Provider value={{ theme, themeList, setTheme, toggleTheme, convertStyle }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
export function useColorThemeValue<T>(firstTheme: T, secondTheme: T) {
const { theme, themeList } = useTheme()
return theme === themeList[0] ? firstTheme : secondTheme
}

View File

@ -1,125 +1,115 @@
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
const brand = {
50: '#e3edff',
100: '#b6c9fd',
200: '#88a5f7',
300: '#5a81f2',
400: '#2c5ded',
500: '#1543d4',
600: '#0d34a5',
700: '#062577',
800: '#02164a',
900: '#00071e',
};
const back = { type PandaColorModel = {
50: '#f2f2f2', value: string;
100: '#d9d9d9',
200: '#bfbfbf',
300: '#a6a6a6',
400: '#8c8c8c',
500: '#737373',
600: '#595959',
700: '#404040',
800: '#262626',
900: '#0d0d0d',
};
const green = {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
};
const blue = {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
};
const orange = {
50: '#fff7ed',
100: '#ffedd5',
200: '#fed7aa',
300: '#fdba74',
400: '#fb923c',
500: '#f97316',
600: '#ea580c',
700: '#c2410c',
800: '#9a3412',
900: '#7c2d12',
};
const red = {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
};
const yellow = {
50: '#ffffda',
100: '#ffffad',
200: '#ffff7d',
300: '#ffff4b',
400: '#ffff1a',
500: '#e5e600',
600: '#b2b300',
700: '#7f8000',
800: '#4c4d00',
900: '#191b00',
};
const purple = {
50: '#ffe3ff',
100: '#ffb2ff',
200: '#ff80ff',
300: '#fe4efe',
400: '#fe20fe',
500: '#e50ce4',
600: '#b204b1',
700: '#80007f',
800: '#4e004d',
900: '#1d001c',
};
const cyan = {
50: '#d6ffff',
100: '#aaffff',
200: '#7affff',
300: '#47ffff',
400: '#1affff',
500: '#00e5e6',
600: '#00b2b3',
700: '#008081',
800: '#004d4e',
900: '#001b1d',
} }
type ThemeModel = {
50: PandaColorModel;
100: PandaColorModel;
200: PandaColorModel;
300: PandaColorModel;
400: PandaColorModel;
500: PandaColorModel;
600: PandaColorModel;
700: PandaColorModel;
800: PandaColorModel;
900: PandaColorModel;
};
export const basicColor = { const back: ThemeModel = {
green, red, orange, back, blue, yellow, purple, cyan 50: { value: '#ebf4fa' },
} 100: { value: '#d1dbe0' },
200: { value: '#b6c2c9' },
300: { value: '#99aab4' },
400: { value: '#7c939e' },
500: { value: '#637985' },
600: { value: '#4d5e67' },
700: { value: '#37444a' },
800: { value: '#1f292e' },
900: { value: '#020f12' },
};
const brand: ThemeModel = {
50: { value: '#e3edff' },
100: { value: '#b6c9fd' },
200: { value: '#88a5f7' },
300: { value: '#5a81f2' },
400: { value: '#2c5ded' },
500: { value: '#1543d4' },
600: { value: '#0d34a5' },
700: { value: '#062577' },
800: { value: '#02164a' },
900: { value: '#00071e' },
};
const normalText: ThemeModel = {
50: { value: '#f2f2f2' },
100: { value: '#d9d9d9' },
200: { value: '#bfbfbf' },
300: { value: '#a6a6a6' },
400: { value: '#8c8c8c' },
500: { value: '#737373' },
600: { value: '#595959' },
700: { value: '#404040' },
800: { value: '#262626' },
900: { value: '#0d0d0d' },
};
const green: ThemeModel = {
50: { value: '#f0fdf4' },
100: { value: '#dcfce7' },
200: { value: '#bbf7d0' },
300: { value: '#86efac' },
400: { value: '#4ade80' },
500: { value: '#22c55e' },
600: { value: '#16a34a' },
700: { value: '#15803d' },
800: { value: '#166534' },
900: { value: '#14532d' },
};
const blue: ThemeModel = {
50: { value: '#eff6ff' },
100: { value: '#dbeafe' },
200: { value: '#bfdbfe' },
300: { value: '#93c5fd' },
400: { value: '#60a5fa' },
500: { value: '#3b82f6' },
600: { value: '#2563eb' },
700: { value: '#1d4ed8' },
800: { value: '#1e40af' },
900: { value: '#1e3a8a' },
};
const orange: ThemeModel = {
50: { value: '#fff7ed' },
100: { value: '#ffedd5' },
200: { value: '#fed7aa' },
300: { value: '#fdba74' },
400: { value: '#fb923c' },
500: { value: '#f97316' },
600: { value: '#ea580c' },
700: { value: '#c2410c' },
800: { value: '#9a3412' },
900: { value: '#7c2d12' },
};
const red: ThemeModel = {
50: { value: '#fef2f2' },
100: { value: '#fee2e2' },
200: { value: '#fecaca' },
300: { value: '#fca5a5' },
400: { value: '#f87171' },
500: { value: '#ef4444' },
600: { value: '#dc2626' },
700: { value: '#b91c1c' },
800: { value: '#991b1b' },
900: { value: '#7f1d1d' },
};
export const colors = { export const colors = {
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
brand: brand, brand: brand,
back: back, back: back,
text: normalText,
success: green, success: green,
error: red, error: red,
warning: orange, warning: orange,
danger: red,
} as const; } as const;

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
export default {
sizes: {
'2xs': {
fontSize: '0.5em',
},
xs: {
fontSize: '0.6em',
},
sm: {
fontSize: '0.7em',
},
md: {
fontSize: '0.8em',
textTransform: 'none',
},
lg: {
fontSize: '0.9em',
textTransform: 'none',
},
},
defaultProps: {
size: 'md',
},
};

View File

@ -1,103 +0,0 @@
// https://medium.com/@a.heydari.dev/simplifying-chakra-ui-v3-recipes-vs-chakra-factory-a-developers-perspective-4020b62f1b4d
// const shimmer = keyframes`
// 100% {
// transform: translateX(100%);
// }
// `;
export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxShadowHover }) => {
return {
bg,
color,
border: '1px solid transparent',
_focus: {
border: '1px solid',
borderColor: 'black',
},
_hover: {
bg: bgHover,
color: colorHover,
boxShadow: boxShadowHover,
_disabled: {
bg,
boxShadow: 'none',
},
},
_active: {
bg: bgActive,
},
};
};
const buttonTheme = {
primary:
customVariant({
bg: { _light: 'brand.600', _dark: 'brand.300' },
bgHover: { _light: 'brand.700', _dark: 'brand.400' },
bgActive: { _light: 'brand.600', _dark: 'brand.300' },
color: { _light: 'white', _dark: 'brand.900' },
colorHover: { _light: 'brand.800', _dark: 'brand.100' },
boxShadowHover: 'outline-over'
}),
secondary:
customVariant({
bg: { _light: 'brand.100', _dark: 'brand.900' },
bgHover: { _light: 'brand.200', _dark: 'brand.800' },
bgActive: { _light: 'brand.300', _dark: 'brand.700' },
color: { _light: 'brand.700', _dark: 'brand.50' },
colorHover: { _light: 'brand.800', _dark: 'brand.100' },
boxShadowHover: 'outline-over',
}),
danger:
customVariant({
bg: { _light: 'error.600', _dark: 'error.600' },
bgHover: { _light: 'error.700', _dark: 'error.500' },
bgActive: { _light: 'error.600', _dark: 'error.500' },
color: { _light: 'white', _dark: 'error.900' },
colorHover: { _light: 'error.700', _dark: 'error.900' },
boxShadowHover: 'outline-over',
}),
success:
customVariant({
bg: { _light: 'green.300', _dark: 'green.300' },
bgHover: { _light: 'green.400', _dark: 'green.400' },
bgActive: { _light: 'green.500', _dark: 'green.400' },
color: { _light: 'white', _dark: 'green.900' },
colorHover: { _light: 'green.500', _dark: 'green.900' },
boxShadowHover: 'outline-over',
}),
progress:
{
bg: { _light: `brand.500`, _dark: `brand.300` },
overflow: 'hidden',
/*
_after: !props.isLoading
? {
content: '""',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
transform: 'translateX(-100%)',
bgGradient: `linear(90deg, brand.100/0 0%, brand.100/2 20%, brand.100/5 60%, v.100/0`,
}
: undefined,
*/
},
menu: {
bg: 'back.100',
color: 'brand.900',
borderRadius: 0,
border: 0,
_hover: { background: 'back.300' },
_focus: { border: 'none' },
fontSize: '20px',
textTransform: 'uppercase',
},
};
export default buttonTheme;

View File

@ -1,5 +0,0 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -1,29 +0,0 @@
const drawerRecipe = {
base: {
bg: { _light: 'white', _dark: 'gray.800' },
color: { _light: 'gray.900', _dark: 'whiteAlpha.900' },
boxShadow: { _light: 'lg', _dark: 'dark-lg' },
padding: 4,
borderRadius: 'md',
},
variants: {
variant: {
solid: {
bg: { _light: 'blue.500', _dark: 'blue.300' },
color: 'white',
},
outline: {
border: '1px solid',
borderColor: { _light: 'gray.300', _dark: 'whiteAlpha.300' },
bg: { _light: 'white', _dark: 'gray.900' },
},
}
},
defaultVariants: {
variant: 'solid',
},
};
export default drawerRecipe;

View File

@ -1,20 +0,0 @@
const flexTheme = {
variants: {
variant: {
'@menu': {
bg: { _light: 'back.100', _dark: 'back.800' },
color: { _light: 'brand.900', _dark: 'brand.100' },
borderRadius: 0,
border: 0,
_hover: { background: { _light: 'back.300', _dark: 'back.600' } },
_focus: { border: 'none' },
fontSize: '20px',
},
},
},
};
export default flexTheme;

View File

@ -1,13 +0,0 @@
export { default as Badge } from './badge';
export { default as Button } from './button';
export { default as Checkbox } from './checkbox';
export { default as Input } from './input';
//export { default as NumberInput } from './numberInput.ts_';
export { default as Popover } from './popover';
export { default as Radio } from './radio';
export { default as Select } from './select';
export { default as Switch } from './switch';
export { default as Textarea } from './textarea';
//export { default as Modal } from './modal';
export { default as Flex } from './flex';
export { default as Drawer } from './drawer';

View File

@ -1,20 +0,0 @@
const inputTheme = {
variants: {
variant: {
outline: {
field: {
bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' },
borderColor: { _light: 'gray.200', _dark: 'whiteAlpha.100' },
color: { _light: 'gray.800', _dark: 'gray.50' },
_focus: {
borderColor: { _light: 'gray.800', _dark: 'gray.50' },
boxShadow: `0 0 0 1px gray.800`,
},
},
},
},
},
};
export default inputTheme;

View File

@ -1,15 +0,0 @@
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const baseStyle = definePartsStyle({
header: {
textAlign: 'center',
},
});
const modalTheme = defineMultiStyleConfig({
baseStyle,
});
export default modalTheme;

View File

@ -1,77 +0,0 @@
export default {
sizes: {
'3xs': {
content: {
width: '3xs',
},
},
'2xs': {
content: {
width: '2xs',
},
},
xs: {
content: {
width: 'xs',
},
},
sm: {
content: {
width: 'sm',
},
},
md: {
content: {
width: 'md',
},
},
lg: {
content: {
width: 'lg',
},
},
xl: {
content: {
width: 'xl',
},
},
'2xl': {
content: {
width: '2xl',
},
},
'3xl': {
content: {
width: '3xl',
},
},
'4xl': {
content: {
width: '4xl',
},
},
'5xl': {
content: {
width: '5xl',
},
},
'6xl': {
content: {
width: '6xl',
},
},
'7xl': {
content: {
width: '7xl',
},
},
'8xl': {
content: {
width: '8xl',
},
},
},
defaultProps: {
size: 'xs',
},
};

View File

@ -1,5 +0,0 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -1,19 +0,0 @@
const selectTheme = {
variants: {
variant: {
outline: {
field: {
bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' },
borderColor: { _light: 'blackAlpha.100', _dark: 'whiteAlpha.100' },
_focus: {
borderColor: { _light: 'brand.500', _dark: 'brand.300' },
boxShadow: `0 0 0 1px black`,
},
},
},
},
},
};
export default selectTheme;

View File

@ -1,5 +0,0 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -1,16 +0,0 @@
const textAreaTheme = {
variants: {
variant: {
outline: {
bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' },
borderColor: { _light: 'blackAlpha.100', _dark: 'whiteAlpha.100' },
_focus: {
borderColor: { _light: 'brand.500', _dark: 'brand.300' },
boxShadow: `0 0 0 1px brand.500`,
},
},
}
},
};
export default textAreaTheme;

76
front/src/theme/theme.ts Normal file
View File

@ -0,0 +1,76 @@
import { createSystem, defaultConfig, mergeConfigs, SystemConfig } from "@chakra-ui/react"
import { colors } from "./colors"
const baseTheme: SystemConfig = {
globalCss: {
body: {
overflowY: 'none',
bg: { _light: 'back.50', _dark: 'back.700' },
color: { _light: 'text.900', _dark: 'text.50' },
fontFamily: 'Roboto, Helvetica, Arial, "sans-serif"',
},
svg: {
width: "32px",
height: "32px",
aspectRatio: "square",
}
},
theme: {
slotRecipes: {
dialog: {
slots: [
"header"
],
base: {
header: {
fontWeight: "bold",
fontSize: "2xl",
color: { _dark: "brand.400", _light: "brand.500" }
},
},
},
},
recipes: {
button: {
base: {
borderRadius: 0,
_hover: {
//boxShadow: "3px 5px 8px gray"
}
},
},
},
tokens: {
fonts: {
heading: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
body: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
},
colors,
},
semanticTokens: {
colors: {
"@danger": {
solid: { value: "{colors.danger.500}" },
contrast: { value: "{colors.danger.100}" },
fg: { value: "{colors.danger.900}" },
muted: { value: "{colors.danger.100}" },
subtle: { value: "{colors.danger.200}" },
emphasized: { value: "{colors.danger.300}" },
focusRing: { value: "{colors.danger.500}" },
},
"brand": {
solid: { value: "{colors.brand.500}" },
contrast: { value: "{colors.brand.100}" },
fg: { value: "{colors.brand.900}" },
muted: { value: "{colors.brand.100}" },
subtle: { value: "{colors.brand.200}" },
emphasized: { value: "{colors.brand.300}" },
focusRing: { value: "{colors.brand.500}" },
},
},
},
},
};
const config = mergeConfigs(defaultConfig, baseTheme);
//console.log("defaultConfig: " + JSON.stringify(defaultConfig, null, 2));
export const systemTheme = createSystem(config);

5
front/src/types/theme.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { ThemeTypings } from '@chakra-ui/react';
import { colors } from '@/theme/colors';
export type ColorSchemes = ThemeTypings['colorSchemes'] | keyof typeof colors;

View File

@ -1,30 +0,0 @@
import { useTheme } from "@/theme/ThemeContext";
import { ReactNode, CSSProperties } from "react";
export type ButtonProps = {
children: ReactNode;
onClick?: () => void;
style?: CSSProperties;
};
export const Button = ({ children, onClick, style }: ButtonProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle({
padding: '10px 20px',
backgroundColor: '#3182CE',
color: '#FFF',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '16px',
...style,
}) : undefined;
return (
<button
onClick={onClick}
style={themedStyle}
>
{children}
</button>
);
};

View File

@ -1,21 +0,0 @@
import { useTheme } from '@/theme/ThemeContext';
import { CSSProperties, ReactNode } from 'react';
export type DivProps = {
children?: ReactNode;
onClick?: () => void;
style?: CSSProperties
};
export const Div = ({ children, onClick, style }: DivProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return (
<div
onClick={onClick}
style={themedStyle}
>
{children}
</div>
);
};

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