Compare commits

...

3 Commits

112 changed files with 2456 additions and 4413 deletions

View File

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

2104
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,10 @@
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 { USERS, USERS_COLLECTION } from '@/service/session';
import { hashLocalData } from '@/utils/sso';
import { Toaster } from './components/ui/toaster';
import { systemTheme } from './theme/theme';
import { Div, FullPage } from './ui';
import { useDisclosure } from './utils/disclosure';
import { useState } from 'react';
import { environment } from './environment';
import { hashLocalData } from './utils/sso';
import { USERS } from './service/session';
const AppEnvHint = () => {
const dialog = useDisclosure();
@ -55,66 +34,70 @@ const AppEnvHint = () => {
return (
<>
<Box
zIndex="100000"
position="fixed"
top="0"
insetStart="0"
insetEnd="0"
h="2px"
bg="warning.400"
as="button"
cursor="pointer"
<Div style={{
zIndex: "100000",
position: "fixed",
top: "0",
height: "2px",
width: "100%",
background: "warning.400",
cursor: "pointer"
}}
data-test-id="devtools"
onClick={dialog.onOpen}
>
<Text
position="fixed"
top="0"
insetStart="4"
bg="warning.400"
color="warning.900"
fontSize="0.6rem"
fontWeight="bold"
px="10px"
marginLeft="25%"
borderBottomStartRadius="sm"
borderBottomEndRadius="sm"
textTransform="uppercase"
<Div style={{
position: "fixed",
top: "0",
background: "warning.400",
color: "warning.900",
fontSize: "0.6rem",
fontWeight: "bold",
paddingLeft: "10px",
paddingRight: "10px",
marginLeft: "25%",
borderRadius: "0 0 10px 10px",
textTransform: "uppercase",
}}
>
{envName.join(' : ')}
</Text>
</Box>
<DialogRoot open={dialog.open} onOpenChange={dialog.onClose}>
<DialogContent>
<DialogHeader>Outils développeurs</DialogHeader>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
{dialog.open ? "Close" : "Open"} Dialog
</Button>
</DialogTrigger>
<DialogBody>
<Stack>
<Text>User</Text>
<SelectRoot onChange={handleChange} collection={USERS_COLLECTION}>
<SelectTrigger>
<SelectValueText placeholder="Select test user" />
</SelectTrigger>
<SelectContent>
{USERS_COLLECTION.items.map((value) => (
<SelectItem item={value} key={value.value}>
{value.label}
</SelectItem>
))}
</SelectContent>
</SelectRoot>
</Stack>
</DialogBody>
<DialogFooter>
<Button onClick={onClose}>Apply</Button>
</DialogFooter>
</DialogContent>
</DialogRoot>
</Div>
</Div>
{/* <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}>
<Dialog.Trigger asChild>
<Button>
{dialog.open ? "Close" : "Open"} Dialog
</Button>
</Dialog.Trigger>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>Outils développeurs</Dialog.Title>
<Dialog.Description>
<HStack>
<Text>User</Text>
<Select.Root onChange={handleChange} collection={USERS_COLLECTION}>
<Select.Trigger>
<SelectValueText placeholder="Select test user" />
</Select.Trigger>
<Select.Content>
{USERS_COLLECTION.items.map((value) => (
<Select.Item item={value} key={value.value}>
{value.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</HStack>
</Dialog.Description>
<Dialog.CloseTrigger>
<Button onClick={onClose}>Apply</Button>
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root> */}
</>
);
};
@ -122,11 +105,11 @@ const AppEnvHint = () => {
const App = () => {
return (
<ChakraProvider value={systemTheme}>
<FullPage data-test-id="Full-root-page">
<AppEnvHint />
<SpaApp />
<Toaster />
</ChakraProvider>
<SpaApp data-test-id="app" />
{/* <Toaster /> */}
</FullPage>
);
};

View File

@ -1,16 +1,6 @@
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
import {
Box,
Button,
Flex,
IconButton,
Slider,
SliderRoot,
SliderThumb,
SliderTrack,
Text,
} from '@chakra-ui/react';
import {
MdFastForward,
MdFastRewind,
@ -32,9 +22,10 @@ import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access';
import { useColorModeValue } from '@/components/ui/color-mode';
import { useColorThemeValue } from '@/theme/ThemeContext';
import { isNullOrUndefined } from '@/utils/validator';
import { Icon } from './Icon';
import { Flex, Text } from '@/ui';
export enum PlayMode {
PLAY_ONE,
@ -86,10 +77,10 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
: ''
);
}, [dataTrack, setMediaSource]);
const backColor = useColorModeValue('back.100', 'back.800');
const backColor = useColorThemeValue('back.100', 'back.800');
const configButton = {
borderRadius: 'full',
backgroundColor: '#00000000',
background: '#00000000',
_hover: {
boxShadow: 'outline-over',
bgColor: 'brand.500',
@ -213,47 +204,53 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
<>
{!isNullOrUndefined(trackOffset) && (
<Flex
position="absolute"
height="150px"
minHeight="150px"
paddingY="5px"
paddingX="10px"
marginX="15px"
bottom={0}
left={0}
right={0}
zIndex={1000}
borderWidth="1px"
borderColor="brand.900"
bgColor={backColor}
borderTopRadius="10px"
style={{
position: "absolute",
height: "150px",
minHeight: "150px",
padding: "5px 10px 5px 10px",
margin: "0 15px 0 15px",
bottom: 0,
left: 0,
right: 0,
zIndex: 1000,
borderWidth: "1px",
borderColor: "brand.900",
background: backColor,
borderRadius: "10px 10px 0 0",
}}
direction="column"
>
<Text
alignContent="left"
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1}
>
{dataTrack?.name ?? '???'}
</Text>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
//noOfLines:1
}}
>
{dataArtists.map((data) => data.name).join(', ')} /{' '}
{dataAlbum && dataAlbum?.name}
{dataGender && ` / ${dataGender.name}`}
</Text>
<Box width="full" paddingX="15px">
<Slider.Root
<Flex style={{ width: "100%", padding: "0 15px 0 15px" }}>
<>TODO ... </>
{/* <Slider.Root
defaultValue={[0]}
value={[timeProgress]}
min={0}
@ -264,26 +261,28 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
// focusThumbOnChange={false}
>
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
{/* <SliderFilledTrack bg="brand.600" /> */}
{/ * <SliderFilledTrack bg="brand.600" /> * /}
</SliderTrack>
</Slider.Root>
</Box>
</Slider.Root> */}
</Flex>
<Flex>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// noOfLines={1},
}}
>
{formatTime(timeProgress)}
</Text>
<Text alignContent="left" fontSize="16px" userSelect="none">
<Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}>
{formatTime(duration)}
</Text>
</Flex>
<Flex gap="5px">
{/* <Flex gap="5px">
<IconButton
{...configButton}
aria-label={'Play'}
@ -327,7 +326,7 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
aria-label={'continue to the end'}
onClick={onTypePlay}
>{playModeIcon[playingMode]}</IconButton>
</Flex>
</Flex> */}
</Flex>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,6 @@
import { ReactNode } from 'react';
import {
Box,
Flex,
HStack,
IconButton,
Text,
useDisclosure,
} from '@chakra-ui/react';
import {
LuAlignJustify,
LuArrowBigLeft,
@ -21,19 +14,13 @@ import { useNavigate } from 'react-router-dom';
import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState';
import { colors } from '@/theme/foundations/colors';
import { basicColor } from '@/theme/colors';
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import { useSessionService } from '@/service/session';
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '@/components/ui/menu';
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
import {
DrawerBody,
DrawerContent,
DrawerHeader,
DrawerRoot,
} from '@/components/ui/drawer';
import { Button } from '../ui/themed';
import { useColorThemeValue, useTheme } from '@/theme/ThemeContext';
import { useDisclosure } from '@/utils/disclosure';
import { Button, Flex, Text } from '@/ui';
export const TOP_BAR_HEIGHT = '50px';
@ -48,11 +35,11 @@ export type TopBarProps = {
};
export const TopBar = ({ title, children }: TopBarProps) => {
const { colorMode, toggleColorMode } = useColorMode();
const { theme, toggleTheme } = useTheme();
const { clearToken } = useSessionService();
const { session } = useServiceContext();
const backColor = useColorModeValue('back.100', 'back.800');
const backColor = useColorThemeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure();
const onChangeTheme = () => {
drawerDisclose.onOpen();
@ -87,22 +74,24 @@ export const TopBar = ({ title, children }: TopBarProps) => {
};
return (
<Flex
position="absolute"
top={0}
left={0}
right={0}
height={TOP_BAR_HEIGHT}
alignItems="center"
justifyContent="space-between"
backgroundColor={backColor}
gap="2"
px="2"
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
align="center"
justify="space-between"
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: TOP_BAR_HEIGHT,
background: backColor,
padding: "0 2 0 2",
boxShadow: `0px 2px 4px ${basicColor.back[900]}`,
zIndex: 200,
}}
>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold">
<Text style={{ padding: "0 0 0 3px" }} fontWeight="bold">
Karusic
</Text>
</Button>
@ -110,82 +99,84 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<Text
fontSize="20px"
fontWeight="bold"
textTransform="uppercase"
marginRight="auto"
userSelect="none"
style={{
textTransform: "uppercase",
marginRight: "auto",
userSelect: "none",
}}
>
{title}
</Text>
)}
{children}
<Flex right="0">
<Flex style={{ right: 0 }}>
{session?.state !== SessionState.CONNECTED && (
<>
<Button {...BUTTON_TOP_BAR_PROPERTY} theme="@primary" onClick={onSignIn}>
<Button {...BUTTON_TOP_BAR_PROPERTY} /*{...THEME.Button.primary}*/ onClick={onSignIn}>
<LuLogIn />
<Text paddingLeft="3px" fontWeight="bold">
<Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold">
Sign-in
</Text>
</Button>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={onSignUp}
disabled={true}
// disabled={true}
>
<MdMore />
<Text paddingLeft="3px" fontWeight="bold">
<Text style={{ padding: "0 0 0 3px" }} fontWeight="bold">
Sign-up
</Text>
</Button>
</>
)}
{session?.state === SessionState.CONNECTED && (
<MenuRoot>
<MenuTrigger asChild>
{/* {session?.state === SessionState.CONNECTED && (
<Menu.Root>
<Menu.Trigger asChild>
<IconButton
as={IconButton}
aria-label="Options"
{...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT}
><MdSupervisedUserCircle /></IconButton>
</MenuTrigger>
<MenuContent>
<MenuItem value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}>
</Menu.Trigger>
<Menu.Content>
<Menu.Item value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}>
<MdSupervisedUserCircle />
<Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box>
</MenuItem>
<MenuItem value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</MenuItem>
<MenuItem value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</MenuItem>
<MenuItem value="Sign-out" valueText="Sign-out" onClick={onSignOut}>
</Menu.Item>
<Menu.Item value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</Menu.Item>
<Menu.Item value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</Menu.Item>
<Menu.Item value="Sign-out" valueText="Sign-out" onClick={onSignOut}>
<LuLogOut /> Sign-out
</MenuItem>
</Menu.Item>
{colorMode === 'light' ? (
<MenuItem value="set-dark" valueText="set-dark" onClick={toggleColorMode}>
<Menu.Item value="set-dark" valueText="set-dark" onClick={toggleColorMode}>
<LuMoon /> Set dark mode
</MenuItem>
</Menu.Item>
) : (
<MenuItem value="set-light" valueText="set-light" onClick={toggleColorMode}>
<Menu.Item value="set-light" valueText="set-light" onClick={toggleColorMode}>
<LuSun /> Set light mode
</MenuItem>
</Menu.Item>
)}
</MenuContent>
</MenuRoot>
)}
</Menu.Content>
</Menu.Root>
)} */}
</Flex>
<DrawerRoot
{/* <Drawer.Root
placement="start"
onOpenChange={drawerDisclose.onClose}
open={drawerDisclose.open}
data-testid="top-bar_drawer-root"
>
<DrawerContent
data-testid="top-bar_drawer-content">
<DrawerHeader
<Drawer.Content
data-test-id="top-bar_drawer-content">
<Drawer.Header
paddingY="auto"
as="button"
onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor}
background={backColor}
color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase"
>
@ -195,13 +186,13 @@ export const TopBar = ({ title, children }: TopBarProps) => {
Karusic
</Text>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
</Drawer.Header>
<Drawer.Body paddingX="0px">
<Button
background="#00000000"
borderRadius="0px"
onClick={onSelectHome}
width="full"
width="100%"
>
<MdHome />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
@ -213,7 +204,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
background="#00000000"
borderRadius="0px"
onClick={onSelectOnAir}
width="full"
width="100%"
>
<MdOutlinePlaylistPlay />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
@ -225,16 +216,16 @@ export const TopBar = ({ title, children }: TopBarProps) => {
background="#00000000"
borderRadius="0px"
onClick={onSelectAdd}
width="full"
width="100%"
>
<MdOutlineUploadFile />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Add Media
</Text>
</Button>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
</Drawer.Body>
</Drawer.Content>
</Drawer.Root> */}
</Flex>
);
};

View File

@ -1,10 +1,12 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { Album } from '@/back-api';
import { Covers } from '@/components/Cover';
import { useCountTracksWithAlbumId } from '@/service/Track';
import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
export type DisplayAlbumProps = {
dataAlbum?: Album;
@ -13,13 +15,13 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(dataAlbum?.id);
if (!dataAlbum) {
return (
<Flex direction="row" width="full" height="full">
<Flex direction="row" width="100%" height="full">
Fail to retrieve Album Data.
</Flex>
);
}
return (
<Flex direction="row" width="full" height="full"
<Flex direction="row" width="100%" height="full"
data-testid="display-album_flex">
<Covers
data={dataAlbum?.covers}
@ -33,32 +35,37 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
//maxWidth="150px"
height="full"
paddingLeft="5px"
overflowX="hidden"
flex={1}
style={{
marginRight: "auto",
overflow: "hidden",
flex: 1,
}}
>
<Text
as="span"
<Span
// align="left"
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
style={{
marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={[1, 2]}
>
{dataAlbum?.name}
</Text>
<Text
as="span"
</Span>
<Span
// align="left"
fontSize="15px"
userSelect="none"
marginRight="auto"
overflow="hidden"
style={{
marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1}
>
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
</Text>
</Span>
</Flex>
</Flex>
);

View File

@ -1,12 +1,6 @@
import { useState } from 'react';
import {
IconButton,
} from '@chakra-ui/react';
import { LuMenu } from 'react-icons/lu';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu';
import { Button } from '../ui/themed';
import { customVariant } from '@/theme/recipes/button';
export type MenuElement = {
name: string;
@ -17,44 +11,29 @@ export type ContextMenuProps = {
elements?: MenuElement[];
};
const theme = {
plop: {
bg: { _light: 'red', _dark: 'brand.300', _pink: "orange" },
_hover: { bg: { _light: 'green', _dark: 'brand.400', _pink: "black" } },
// bg: { _light: 'brand.600', _dark: 'brand.300' },
// _hover: { bg: { _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'
*/
}
};
export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) {
return <></>;
}
return (
<MenuRoot
data-testid="context-menu">
<MenuTrigger asChild
data-testid="context-menu_trigger">
{/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
<Button {...theme.plop} /*theme="@primary"*/>
<LuMenu />
</Button>
</MenuTrigger>
<MenuContent
data-testid="context-menu_content">
{elements?.map((data) => (
<MenuItem key={data.name} value={data.name} onClick={data.onClick}
data-testid="context-menu_item">
{data.name}
</MenuItem>
))}
</MenuContent>
</MenuRoot >
);
// if (!elements) {
return <></>;
// }
// return (
// <Menu.Root
// data-testid="context-menu">
// <Menu.Trigger asChild
// data-testid="context-menu_trigger">
// {/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
// <Button {...THEME.Button.primary} >
// <LuMenu />
// </Button>
// </Menu.Trigger>
// <Menu.Content
// data-testid="context-menu_content">
// {elements?.map((data) => (
// <Menu.Item key={data.name} value={data.name} onClick={data.onClick}
// data-testid="context-menu_item">
// {data.name}
// </Menu.Item>
// ))}
// </Menu.Content>
// </Menu.Root >
//);
};

View File

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

View File

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

View File

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

View File

@ -1,8 +1,5 @@
import { RefObject } from 'react';
import {
NumberInput,
} from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
@ -36,7 +33,8 @@ export const FormNumber = ({
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<NumberInput.Root
<></>
{/* <NumberInput.Root
ref={ref}
value={form.values[variableName]}
onValueChange={(value) => form.setValues({ [variableName]: value })}
@ -46,7 +44,7 @@ export const FormNumber = ({
max={max}
>
<NumberInput.Input />
</NumberInput.Root>
</NumberInput.Root> */}
</FormGroup>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,5 @@
import { useRef } from 'react';
import {
Flex,
Progress,
Text,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { Button } from '../ui/themed';
export type PopUpUploadProgressProps = {
title: string;
@ -40,64 +32,66 @@ export const PopUpUploadProgress = ({
}: PopUpUploadProgressProps) => {
const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null);
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">
<Flex direction="column" gap="10px">
{isFinished ? (
<Text fontSize="20px" fontWeight="bold">
All {elements.length} element have been sent
</Text>
) : (
<Text fontSize="20px" fontWeight="bold">
[{index + 1}/{elements.length}] {elements[index]}
</Text>
)}
<Progress.Root
colorScheme="green"
striped
value={currentSize}
animated
max={totalSize}
height="24px"
/>
<Flex>
<Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text>
<Text marginLeft="auto">
{totalSize.toLocaleString('fr-FR')} Bytes
</Text>
</Flex>
{error && (
<Text fontWeight="bold" color="darkred">
{error}
</Text>
)}
</Flex>
</DialogBody>
<DialogFooter>
{isFinished ? (
<Button onClick={onClose} theme="@success">
Ok
</Button>
) : (
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
Abort
</Button>
)}
</DialogFooter>
</DialogContent>
</DialogRoot>
);
// return (
// <Dialog.Root
// //initialFocusRef={initialRef}
// //finalFocusRef={finalRef}
// //closeOnOverlayClick={false}
// onOpenChange={onClose}
// open={true}
// data-testid="upload-progress-edit-pop-up"
// >
// {/* <DialogOverlay /> */}
// <Dialog.Content>
// <Dialog.Header>{title}</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
// <Dialog.Body pb={6} paddingLeft="18px">
// <Flex direction="column" gap="10px">
// {isFinished ? (
// <Text fontSize="20px" fontWeight="bold">
// All {elements.length} element have been sent
// </Text>
// ) : (
// <Text fontSize="20px" fontWeight="bold">
// [{index + 1}/{elements.length}] {elements[index]}
// </Text>
// )}
// <Progress.Root
// colorScheme="green"
// striped
// value={currentSize}
// animated
// max={totalSize}
// height="24px"
// />
// <Flex>
// <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text>
// <Text marginLeft="auto">
// {totalSize.toLocaleString('fr-FR')} Bytes
// </Text>
// </Flex>
// {error && (
// <Text fontWeight="bold" color="darkred">
// {error}
// </Text>
// )}
// </Flex>
// </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,11 +1,5 @@
import { useRef, useState } from 'react';
import {
Text,
useDisclosure,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
@ -25,7 +19,7 @@ import { useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext';
import { useSpecificTrack, useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
import { Button } from '../ui/themed';
import { useDisclosure } from '@/utils/disclosure';
export type TrackEditPopUpProps = {};
@ -86,116 +80,117 @@ export const TrackEditPopUp = ({ }: TrackEditPopUpProps) => {
})
);
};
return (
<DialogRoot
//initialFocusRef={initialRef}
//finalFocusRef={finalRef}
//closeOnOverlayClick={false}
onOpenChange={onClose}
open={true}
data-testid="track-edit-pop-up"
>
{/* <DialogOverlay /> */}
<DialogContent>
<DialogHeader>Edit Track</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}
return <></>;
// return (
// <Dialog.Root
// //initialFocusRef={initialRef}
// //finalFocusRef={finalRef}
// //closeOnOverlayClick={false}
// onOpenChange={onClose}
// open={true}
// data-testid="track-edit-pop-up"
// >
// {/* <DialogOverlay /> */}
// <Dialog.Content>
// <Dialog.Header>Edit Track</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<DialogBody pb={6} gap="0px" paddingLeft="18px">
{admin && (
<>
<FormGroup isRequired label="Id">
<Text>{dataTrack?.id}</Text>
</FormGroup>
<FormGroup label="Data Id">
<Text>{dataTrack?.dataId}</Text>
</FormGroup>
<FormGroup label="Action(s):">
<Button
onClick={disclosure.onOpen}
marginRight="auto"
theme="@danger"
>
<MdDeleteForever /> Remove Media
</Button>
</FormGroup>
<ConfirmPopUp
disclosure={disclosure}
title="Remove track"
body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
confirmTitle="Remove"
onConfirm={onRemove}
/>
</>
)}
{!admin && (
<>
<FormInput
form={form}
variableName="name"
isRequired
label="Title"
ref={initialRef}
/>
<FormTextarea
form={form}
variableName="description"
label="Description"
/>
<FormSelect
form={form}
variableName="genderId"
options={dataGenders}
label="Gender"
/>
<FormSelectMultiple
form={form}
variableName="artists"
options={dataArtist}
label="Artist(s)"
/>
<FormSelect
form={form}
variableName="albumId"
options={dataAlbums}
label="Album"
/>
<FormNumber
form={form}
variableName="track"
label="Track n°"
step={1}
//defaultValue={0}
min={0}
max={1000}
/>
</>
)}
</DialogBody>
<DialogFooter>
<Button
onClick={() => setAdmin((value) => !value)}
marginRight="auto"
>
{admin ? (
<>
<MdEdit />
Edit
</>
) : (
<>
<MdAdminPanelSettings />
Admin
</>
)}
</Button>
{!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}>
Save
</Button>
)}
<Button onClick={onClose}>Cancel</Button>
</DialogFooter>
</DialogContent>
</DialogRoot>
);
// <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
// {admin && (
// <>
// <FormGroup isRequired label="Id">
// <Text>{dataTrack?.id}</Text>
// </FormGroup>
// <FormGroup label="Data Id">
// <Text>{dataTrack?.dataId}</Text>
// </FormGroup>
// <FormGroup label="Action(s):">
// <Button
// onClick={disclosure.onOpen}
// marginRight="auto"
// theme="@danger"
// >
// <MdDeleteForever /> Remove Media
// </Button>
// </FormGroup>
// <ConfirmPopUp
// disclosure={disclosure}
// title="Remove track"
// body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
// confirmTitle="Remove"
// onConfirm={onRemove}
// />
// </>
// )}
// {!admin && (
// <>
// <FormInput
// form={form}
// variableName="name"
// isRequired
// label="Title"
// ref={initialRef}
// />
// <FormTextarea
// form={form}
// variableName="description"
// label="Description"
// />
// <FormSelect
// form={form}
// variableName="genderId"
// options={dataGenders}
// label="Gender"
// />
// <FormSelectMultiple
// form={form}
// variableName="artists"
// options={dataArtist}
// label="Artist(s)"
// />
// <FormSelect
// form={form}
// variableName="albumId"
// options={dataAlbums}
// label="Album"
// />
// <FormNumber
// form={form}
// variableName="track"
// label="Track n°"
// step={1}
// //defaultValue={0}
// min={0}
// max={1000}
// />
// </>
// )}
// </Dialog.Body>
// <Dialog.Footer>
// <Button
// onClick={() => setAdmin((value) => !value)}
// marginRight="auto"
// >
// {admin ? (
// <>
// <MdEdit />
// Edit
// </>
// ) : (
// <>
// <MdAdminPanelSettings />
// Admin
// </>
// )}
// </Button>
// {!admin && form.isFormModified && (
// <Button colorScheme="blue" mr={3} onClick={onSave}>
// Save
// </Button>
// )}
// <Button onClick={onClose}>Cancel</Button>
// </Dialog.Footer>
// </Dialog.Content>
// </Dialog.Root>
// );
};

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { Button, Flex, Input, Spinner } from '@chakra-ui/react';
import {
MdClose,
MdEdit,
@ -10,6 +9,7 @@ import {
import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, Input } from '@/ui';
export type SelectSingleProps = {
options?: object[];
@ -70,7 +70,7 @@ export const SelectSingle = ({
}
};
if (!transformedOption) {
return <Spinner />;
return <></>; // TODO: <Spinner />;
}
function onChangeInput(value: string): void {
setHasSuggestion(false);
@ -101,48 +101,53 @@ export const SelectSingle = ({
};
return (
<Flex direction="column" width="full" gap="0px">
<Flex direction="column" width="100%" gap="0px">
<Flex>
<Input
ref={refFocus}
width="full"
style={{
width: "100%",
background: showList || !selectedOptions ? undefined : 'green.500',
borderRadius: "5px 0 0 5px",
}}
onChange={(e) => onChangeInput(e.target.value)}
onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)}
//onFocus={() => setShowList(true)}
//onBlur={() => setTimeout(() => setShowList(false), 200)}
value={
showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : ''))
}
backgroundColor={
showList || !selectedOptions ? undefined : 'green.500'
}
borderRadius="5px 0 0 5px"
/>
<Button
onClick={onRemoveItem}
variant="outline"
borderRadius="0 5px 5px 0"
borderWidth="1px 1px 1px 0"
// TODO: variant="outline"
style={{
borderRadius: "0 5px 5px 0",
borderWidth: "1px 1px 1px 0",
}}
>
{selectedOptions ? (
<MdClose color="gray.300" />
) : showList ? (
<MdKeyboardArrowUp color="gray.300" />
) : hasSuggestion ? (
<MdEdit color="gray.300" />
) : (
<MdKeyboardArrowDown color="gray.300" />
)}
{
selectedOptions ? (
<MdClose color="gray.300" />
) : showList ? (
<MdKeyboardArrowUp color="gray.300" />
) : hasSuggestion ? (
<MdEdit color="gray.300" />
) : (
<MdKeyboardArrowDown color="gray.300" />
)}
</Button>
</Flex>
{showList && (
<SelectList
options={transformedOption}
selected={selectedOptions ? [selectedOptions] : []}
search={currentSearch}
onSelectValue={selectValue}
onCreate={createNewItem}
/>
)}
</Flex>
{
showList && (
<SelectList
options={transformedOption}
selected={selectedOptions ? [selectedOptions] : []}
search={currentSearch}
onSelectValue={selectValue}
onCreate={createNewItem}
/>
)
}
</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 { Covers } from '@/components/Cover';
import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = {
track: Track;
@ -18,7 +18,7 @@ export const DisplayTrack = ({
}: DisplayTrackProps) => {
const { trackActive } = useActivePlaylistService();
return (
<Flex direction="row" width="full" height="full">
<Flex direction="row" width="100%" height="full">
<Covers
data={track?.covers}
size="50"
@ -30,26 +30,29 @@ export const DisplayTrack = ({
/>
<Flex
direction="column"
width="full"
width="100%"
height="full"
paddingLeft="5px"
overflowX="hidden"
style={{
overflowX: "hidden",
}}
onClick={onClick}
>
<Text
as="span"
alignContent="left"
<Span
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// TODO: noOfLines={[1, 2]}
marginY="auto"
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}
</Text>
</Span>
</Flex>
<ContextMenu elements={contextMenu} />
</Flex>

View File

@ -1,7 +1,4 @@
import { Suspense } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { Track } from '@/back-api';
import { Covers } from '@/components/Cover';
@ -10,6 +7,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = {
track: Track;
@ -26,7 +24,7 @@ export const DisplayTrackFull = ({
const { dataGender } = useSpecificGender(track?.genderId);
const { dataArtists } = useSpecificArtists(track?.artists);
return (
<Flex direction="row" width="full" height="full"
<Flex direction="row" width="100%" height="full"
data-testid="display-track-full">
<Covers
data={track?.covers}
@ -39,72 +37,81 @@ export const DisplayTrackFull = ({
/>
<Flex
direction="column"
width="full"
width="100%"
height="full"
paddingLeft="5px"
overflowX="hidden"
onClick={onClick}
style={{
overflowX: "hidden",
}}
>
<Text
as="span"
alignContent="left"
<Span
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// TODO: noOfLines={1}
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}]`}
</Text>
</Span>
{dataAlbum && (
<Text
as="span"
alignContent="left"
<Span
fontSize="15px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
>
<Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name}
</Text>
<Span fontWeight="normal">Album:</Span> {dataAlbum.name}
</Span>
)}
{dataArtists && (
<Text
as="span"
alignContent="left"
<Span
fontSize="15px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
>
<Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')}
</Text>
<Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')}
</Span>
)}
{dataGender && (
<Text
as="span"
alignContent="left"
<Span
fontSize="15px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
>
<Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name}
</Text>
<Span fontWeight="normal">Gender:</Span> {dataGender.name}
</Span>
)}
</Flex>
<ContextMenu elements={contextMenu}

View File

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

View File

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

View File

@ -1,17 +0,0 @@
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

@ -1,29 +0,0 @@
"use client"
import { ThemeProvider, useTheme, ThemeProviderProps } from "next-themes"
export interface ColorModeProviderProps extends ThemeProviderProps { }
export function ColorModeProvider(props: ColorModeProviderProps) {
return (
<ThemeProvider attribute="class" themes={['pink', 'dark', 'light']} disableTransitionOnChange {...props} />
)
}
export function useColorMode() {
const { resolvedTheme, setTheme } = useTheme()
const toggleColorMode = () => {
console.log(`plop: ${resolvedTheme}`);
setTheme(resolvedTheme === "light" ? "pink" : resolvedTheme === "pink" ? "dark" : "light")
}
return {
colorMode: resolvedTheme,
setColorMode: setTheme,
toggleColorMode,
}
}
export function useColorModeValue<T>(light: T, dark: T) {
const { colorMode } = useColorMode()
return colorMode === "light" ? light : dark
}

View File

@ -1,62 +0,0 @@
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

@ -1,52 +0,0 @@
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

@ -1,110 +0,0 @@
"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

@ -1,7 +0,0 @@
import buttonRecipe from '@/theme/recipes/button';
import { chakra } from '@chakra-ui/react';
// we export the element with the application recipe theme.
// cf doc: https://www.chakra-ui.com/docs/theming/recipes
export const Button = chakra("button", buttonRecipe)

View File

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

View File

@ -1,28 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,27 @@
import { Button, Flex, Text } from '@/ui';
import { useDisclosure } from '@/utils/disclosure';
import React, { FC } from 'react';
import {
AlertDescription,
AlertRoot,
AlertTitle,
Box,
Collapsible,
useDisclosure,
} from '@chakra-ui/react';
import {
FallbackProps,
ErrorBoundary as ReactErrorBoundary,
} from 'react-error-boundary';
import { Button } from '@/components/ui/themed';
import { LuChevronUp, LuChevronDown } from 'react-icons/lu';
const ErrorFallback = ({ error }: FallbackProps) => {
const { open, onToggle } = useDisclosure();
return (
<Box p="4" m="auto">
<Flex direction="column" style={{ padding: 4, margin: "auto", background: "red.500" }}>
<Text color='red'>An unexpected error has occurred.</Text>
<Text>Message: {error.message}</Text>
{/*
<AlertRoot status="error" borderRadius="md">
{/* <AlertIcon /> */}
<Box flex="1">
<Flex style={{ flex: "1" }}>
<AlertTitle>An unexpected error has occurred.</AlertTitle>
<AlertDescription display="block" lineHeight="1.4">
<Button
theme="@secondary"
//theme="@secondary"
color="red.800"
//size="sm"
onClick={onToggle}
@ -34,15 +30,15 @@ const ErrorFallback = ({ error }: FallbackProps) => {
</Button>
<Collapsible.Root open={open}>
<Collapsible.Content>
<Box mt={4} fontFamily="monospace">
<Flex mt={4} fontFamily="monospace">
{error.message}
</Box>
</Flex>
</Collapsible.Content>
</Collapsible.Root>
</AlertDescription>
</Box>
</AlertRoot>
</Box>
</Flex>
</AlertRoot> */}
</Flex >
);
};

View File

@ -1,14 +1,15 @@
import { createIcon } from '@chakra-ui/react';
export const DoubleArrowIcon = createIcon({
displayName: 'DoubleArrowIcon',
viewBox: '0 0 24 24',
path: (
<path
fillRule="evenodd"
clipRule="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"
fill="currentColor"
/>
),
});
export const DoubleArrowIcon = <></>;
// createIcon({
// displayName: 'DoubleArrowIcon',
// viewBox: '0 0 24 24',
// path: (
// <path
// fillRule="evenodd"
// clipRule="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"
// fill="currentColor"
// />
// ),
// });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd';
@ -8,9 +7,10 @@ import { TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useColorModeValue } from '@/components/ui/color-mode';
import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId';
import { Flex } from '@/ui';
export const OnAirPage = () => {
const { playInList } = useActivePlaylistService();
@ -73,24 +73,27 @@ export const OnAirPage = () => {
<Flex
direction="column"
gap={BASE_WRAP_SPACING}
marginX="auto"
padding="20px"
width="80%"
style={{
margin: "0 auto",
padding: "20px",
width: "80%",
}}
>
{!playTrackList && <>No playing</>}
{playTrackList && playTrackList?.map((data) => (
<Box
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
<Flex
key={data}
padding="5px"
as="button"
_hover={{
boxShadow: 'outline-over',
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
style={{
minWidth: "100%",
//height:"60px",
border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}}
>
<DisplayTrackFullId
@ -111,7 +114,7 @@ export const OnAirPage = () => {
},
]}
/>
</Box>
</Flex>
))}
<EmptyEnd />
</Flex>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,202 @@
import { createContext, useContext, useState, useEffect, CSSProperties, ReactNode, useCallback } from "react";
import { basicColor } from "./colors";
export type ApplyThemeProperty = {
componentType?: String;
// define the format of the shape
shape?: String;
// Define the color model used
palette?: String;
}
export type ThemeContextProps = {
theme: string;
themeList: string[];
setTheme: (string) => void;
toggleTheme: () => void;
convertStyle: (style: CSSProperties) => CSSProperties,
applyTheme: (style: CSSProperties, theme: ApplyThemeProperty) => CSSProperties,
}
const ThemeContext = createContext<ThemeContextProps>({
theme: "error",
themeList: ["error"],
setTheme: (_: string) => {
console.error("Request setTheme without context");
},
toggleTheme: () => {
console.error("Request toggleTheme without context");
},
convertStyle: (style: CSSProperties): CSSProperties => {
console.error("Request convertStyle without context");
return style;
},
applyTheme: (style: CSSProperties, _theme: ApplyThemeProperty) => {
console.error("Request applyTheme without context");
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",
},
},
base: {
Button: {
border: '1px solid red',
},
Flex: {
},
},
shape: {
outline: {
border: '1px solid transparent',
},
},
palette: {
primary: {
borderColor: "green"
},
secondary: {
},
danger: {
},
success: {
}
}
};
export function ThemeProvider({ children, themeList = ["light", "dark"] }: { children: ReactNode, themeList?: string[] }) {
const [theme, setTheme] = useState<string>(themeList[1]);
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(() => {
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;
}
// console.log(`request convert value: ${value}`);
if (basicsColor[value]) {
// console.log(`convert value: ${value} in ${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, "backgroundColor");
convertElementStyle(out, "borderColor");
convertElementStyle(out, "color");
return out;
}, [convertElementStyle]);
const applyTheme = useCallback((style: CSSProperties, selectedTheme?: ApplyThemeProperty): CSSProperties => {
let out = style;
console.log(`apply style ... ${JSON.stringify(selectedTheme, null, 2)}`);
// Apply the basic theme of the component:
if (selectedTheme?.componentType) {
console.log(`detect component type theme ... ${selectedTheme?.componentType}`);
const base = themes?.base[selectedTheme.componentType];
console.log(` base = ${JSON.stringify(base, null, 2)}`);
if (base) {
out = { ...base, ...out };
}
}
// Apply the variant selected:
if (selectedTheme?.shape) {
const shape = themes?.shape[selectedTheme.shape];
if (shape) {
out = { ...shape, ...out };
}
}
// Apply the palette selected:
if (selectedTheme?.palette) {
const palette = themes?.shape[selectedTheme.palette];
if (palette) {
const palette2 = palette[theme];
if (palette2) {
out = { ...palette2, ...out };
}
}
}
return convertStyle(out);
}, [convertStyle]);
return (
<ThemeContext.Provider value={{ theme, themeList, setTheme, toggleTheme, convertStyle, applyTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
export function useColorThemeValue<T>(firstTheme: T, secondTheme: T) {
const { theme, themeList } = useTheme()
//console.log(`use theme =${theme} ==> ${theme === themeList[0] ? firstTheme : secondTheme}`);
return theme === themeList[0] ? firstTheme : secondTheme
}

126
front/src/theme/colors.ts Normal file
View File

@ -0,0 +1,126 @@
// 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 black = {
50: '#f2f2f2',
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',
}
export const basicColor = {
brand,
green,
red,
orange,
black,
blue,
yellow,
purple,
cyan,
back: black,
warning: orange,
}

View File

@ -1,110 +0,0 @@
type ThemeModel = {
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
const back = {
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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
brand: brand,
back: back,
text: normalText,
success: green,
error: red,
warning: orange,
} as const;

View File

@ -1,9 +0,0 @@
import { colors } from './colors';
import { shadows } from './shadows';
const foundations = {
colors,
shadows,
};
export default foundations;

View File

@ -1,21 +0,0 @@
import { colors } from './colors';
const createOutline = (colorScheme = 'gray') =>
`0 0 0 3px ${colorScheme}.500/3`;
export const shadows = {
outline: createOutline('brand'),
'outline-brand': '0 0 0 1px brand.900',
'outline-gray': createOutline('gray'),
'outline-over': `4px 4px 5px #00000088`,
'outline-darkgray': `0 0 0 3px gray.500/8`,
'outline-success': createOutline('success'),
'outline-warning': createOutline('warning'),
'outline-error': createOutline('error'),
'outline-doing': createOutline('doing'),
'outline-paused': createOutline('paused'),
layout: '0 0 24px 1px rgba(0, 0, 0, 0.05)',
smooth: 'inset 0px 0px 16px rgba(0, 0, 0, 0.05)',
// smooth-light is used for dark backgrounds
'smooth-light': 'inset 0px 0px 16px rgba(255, 255, 255, 0.1)',
};

View File

@ -1,4 +1,3 @@
import { defineRecipe, defineStyle, RecipeVariantRecord, SystemStyleObject } from '@chakra-ui/react';
// https://medium.com/@a.heydari.dev/simplifying-chakra-ui-v3-recipes-vs-chakra-factory-a-developers-perspective-4020b62f1b4d
@ -9,7 +8,7 @@ import { defineRecipe, defineStyle, RecipeVariantRecord, SystemStyleObject } fro
// `;
export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxShadowHover }) => {
return defineStyle({
return {
bg,
color,
border: '1px solid transparent',
@ -29,81 +28,76 @@ export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxSha
_active: {
bg: bgActive,
},
});
};
};
const buttonRecipe = defineRecipe({
variants: {
theme: {
"@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":
defineStyle({
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": defineStyle({
bg: 'back.100',
color: 'brand.900',
borderRadius: 0,
border: 0,
_hover: { background: 'back.300' },
_focus: { border: 'none' },
fontSize: '20px',
textTransform: 'uppercase',
}),
},
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,
*/
},
});
export default buttonRecipe;
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,6 +1,6 @@
import { defineRecipe } from '@chakra-ui/react';
const drawerRecipe = defineRecipe({
const drawerRecipe = {
base: {
bg: { _light: 'white', _dark: 'gray.800' },
color: { _light: 'gray.900', _dark: 'whiteAlpha.900' },
@ -24,6 +24,6 @@ const drawerRecipe = defineRecipe({
defaultVariants: {
variant: 'solid',
},
});
};
export default drawerRecipe;

View File

@ -1,7 +1,7 @@
import { defineRecipe } from '@chakra-ui/react';
const flexTheme = defineRecipe({
const flexTheme = {
variants: {
variant: {
'@menu': {
@ -15,6 +15,6 @@ const flexTheme = defineRecipe({
},
},
},
});
};
export default flexTheme;

View File

@ -1,6 +1,5 @@
import { defineRecipe } from '@chakra-ui/react';
const inputTheme = defineRecipe({
const inputTheme = {
variants: {
variant: {
outline: {
@ -16,6 +15,6 @@ const inputTheme = defineRecipe({
},
},
},
});
};
export default inputTheme;

View File

@ -1,5 +1,3 @@
import { modalAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);

View File

@ -1,27 +0,0 @@
import { numberInputAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { getColor, mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(numberInputAnatomy.keys);
const baseStyle = definePartsStyle((props) => {
return {
field: {
border: 0,
_focusVisible: {
borderColor: {_light:'brand.500', _dark:'brand.300'},
boxShadow: `0 0 0 1px 'brand.500'`,
ring: '1px',
ringColor: {_light:'brand.500', _dark:'brand.300'},
ringOffset: '1px',
ringOffsetColor: {_light:'brand.500', _dark:'brand.300'},
},
},
};
});
export default defineMultiStyleConfig({
baseStyle,
});

View File

@ -1,6 +1,5 @@
import { defineRecipe } from '@chakra-ui/react';
const selectTheme = defineRecipe({
const selectTheme = {
variants: {
variant: {
outline: {
@ -15,6 +14,6 @@ const selectTheme = defineRecipe({
},
},
},
});
};
export default selectTheme;

View File

@ -1,7 +1,5 @@
import { defineRecipe } from '@chakra-ui/react';
const textAreaTheme = defineRecipe({
const textAreaTheme = {
variants: {
variant: {
outline: {
@ -14,5 +12,5 @@ const textAreaTheme = defineRecipe({
},
}
},
});
};
export default textAreaTheme;

View File

@ -1,67 +0,0 @@
import { Group, VStack } from '@chakra-ui/react';
import { HiMinus, HiPlus } from 'react-icons/hi';
const meta = {
title: 'StyleGuide/Buttons',
};
export default meta;
export const Default = {
render: () => (
<Group>
<Button>Default Button</Button>
<IconButton aria-label="Add" ><HiPlus /></IconButton>
</Group>
),
};
export const Primary = {
render: () => (
<Group>
<Button theme="@primary">Primary Button</Button>
<IconButton theme="@primary" aria-label="Add" ><HiPlus /></IconButton>
</Group>
),
};
export const Secondary = {
render: () => (
<Group>
<Button theme="@secondary">Secondary Button</Button>
<IconButton theme="@secondary" aria-label="Add" ><HiPlus /></IconButton>
</Group>
),
};
export const Danger = {
render: () => (
<Group>
<Button theme="@danger">Danger Button</Button>
<IconButton theme="@danger" aria-label="Remove"><HiMinus /></IconButton>
</Group>
),
};
export const Progress = {
render: () => (
<Group>
<VStack>
<Button variant="solid" colorScheme="brand">
Progress Button with Brand ColorScheme
</Button>
<Button variant="solid" colorScheme="error">
Progress Button with Error ColorScheme
</Button>
<Button variant="solid">
Button
</Button>
<IconButton
variant="solid"
theme="@danger"
aria-label="Remove"
><HiMinus /></IconButton>
</VStack>
</Group>
),
};

View File

@ -1,66 +0,0 @@
import { Box, Flex, FlexProps, HStack } from '@chakra-ui/react';
const Color = ({ children, ...rest }: FlexProps) => (
<Flex flex="1" h="16" p="2" {...rest}>
<Box
bg="white"
display="inline-block"
px="2"
py="1"
m="auto"
fontSize="xs"
fontWeight="bold"
borderRadius="md"
>
{children}
</Box>
</Flex>
);
const Colors = ({ colorScheme = 'gray', ...rest }) => (
<HStack
// spacing="0"
overflow="hidden"
boxShadow="lg"
color={`${colorScheme}.700`}
borderRadius="md"
{...rest}
>
<Color bg={`${colorScheme}.50`}>50</Color>
<Color bg={`${colorScheme}.100`}>100</Color>
<Color bg={`${colorScheme}.200`}>200</Color>
<Color bg={`${colorScheme}.300`}>300</Color>
<Color bg={`${colorScheme}.400`}>400</Color>
<Color bg={`${colorScheme}.500`}>500</Color>
<Color bg={`${colorScheme}.600`}>600</Color>
<Color bg={`${colorScheme}.700`}>700</Color>
<Color bg={`${colorScheme}.800`}>800</Color>
<Color bg={`${colorScheme}.900`}>900</Color>
</HStack>
);
const meta = {
title: 'StyleGuide/Colors',
};
export default meta;
export const Brand = {
render: () => <Colors colorScheme="brand" />,
};
export const Gray = {
render: () => <Colors colorScheme="gray" />,
};
export const Error = {
render: () => <Colors colorScheme="error" />,
};
export const Warning = {
render: () => <Colors colorScheme="warning" />,
};
export const Success = {
render: () => <Colors colorScheme="success" />,
};

View File

@ -1,4 +0,0 @@
import { Styles } from '@chakra-ui/theme-tools';
export const styles: Styles = {
};

View File

@ -1,44 +0,0 @@
import * as recipes from './recipes';
import { createSystem, defaultConfig, mergeConfigs, SystemConfig } from "@chakra-ui/react"
import { colors } from "./foundations/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: {
...recipes,
tokens: {
fonts: {
heading: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
body: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
},
colors,
},
semanticTokens: {
colors: {
brand: {
solid: { value: "{colors.brand.500}" },
contrast: { value: "{colors.brand.100}" },
fg: { value: "{colors.brand.700}" },
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);
export const systemTheme = createSystem(config);

View File

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

33
front/src/ui/Button.tsx Normal file
View File

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

33
front/src/ui/Div.tsx Normal file
View File

@ -0,0 +1,33 @@
import { ApplyThemeProperty, useTheme } from '@/theme/ThemeContext';
import { CSSProperties, ReactNode, useState } from 'react';
export type DivProps = {
children?: ReactNode;
onClick?: () => void;
style?: CSSProperties;
_hover?: CSSProperties;
componentType?: ApplyThemeProperty['componentType'];
shape?: ApplyThemeProperty['shape'];
palette?: ApplyThemeProperty['palette'];
};
export const Div = ({ children, onClick, style, _hover, ...themeToApply }: DivProps) => {
const { applyTheme } = useTheme();
const [hover, setHover] = useState(false);
const hoverTheme = hover ? _hover : {};
const themedStyle = style ? applyTheme({ ...style, ...hoverTheme }, themeToApply) : undefined;
return (
<div
onMouseEnter={() => {
setHover(true);
}}
onMouseLeave={() => {
setHover(false);
}}
onClick={onClick}
style={themedStyle}
>
{children}
</div>
);
};

View File

@ -0,0 +1,9 @@
import { RestErrorResponse } from "@/back-api";
export const registerAnError = (error) => {
console.error(`Detect error: ${JSON.stringify(error, null, 2)}`);
}
export const registerAnApiError = (error: RestErrorResponse) => {
console.error(`Detect error: ${JSON.stringify(error, null, 2)}`);
}

76
front/src/ui/Flex.tsx Normal file
View File

@ -0,0 +1,76 @@
import React, { CSSProperties, ReactNode } from 'react';
import { Div } from './Div';
import { ApplyThemeProperty } from '@/theme/ThemeContext';
export type FlexProps = {
children?: ReactNode;
componentType?: ApplyThemeProperty['componentType'];
shape?: ApplyThemeProperty['shape'];
palette?: ApplyThemeProperty['palette'];
onClick?: () => void;
direction?: 'row' | 'column';
gap?: string | number;
justify?: CSSProperties['justifyContent'];
align?: CSSProperties['alignItems'];
width?: CSSProperties['width'];
height?: CSSProperties['height'];
padding?: CSSProperties['padding'];
margin?: CSSProperties['margin'];
maxWidth?: CSSProperties['maxWidth'];
minWidth?: CSSProperties['minWidth'];
maxHeight?: CSSProperties['maxHeight'];
minHeight?: CSSProperties['maxHeight'];
paddingLeft?: CSSProperties['paddingLeft'];
paddingRight?: CSSProperties['paddingRight'];
paddingTop?: CSSProperties['paddingTop'];
paddingBottom?: CSSProperties['paddingBottom'];
marginLeft?: CSSProperties['marginLeft'];
marginRight?: CSSProperties['marginRight'];
marginTop?: CSSProperties['marginTop'];
marginBottom?: CSSProperties['marginBottom'];
style?: Omit<CSSProperties, "flexDirection" | "display" | "justifyContent" | "alignItems" | "alignItems" | "direction">
_hover?: CSSProperties;
};
export const Flex = ({
children,
onClick,
direction = 'row',
justify = 'flex-start',
gap,
align = 'stretch',
width,
height,
padding,
margin,
maxWidth, minWidth, maxHeight, minHeight,
paddingLeft, paddingRight, paddingTop, paddingBottom,
marginLeft, marginRight, marginTop, marginBottom,
componentType = "Flex", shape, palette,
style, _hover }: FlexProps) => {
return (
<Div
onClick={onClick}
style={{
display: 'flex',
flexDirection: direction,
justifyContent: justify,
gap: gap,
alignItems: align,
width,
height,
padding,
margin,
maxWidth, minWidth, maxHeight, minHeight,
paddingLeft, paddingRight, paddingTop, paddingBottom,
marginLeft, marginRight, marginTop, marginBottom,
...style,
}}
_hover={_hover}
componentType={componentType} shape={shape} palette={palette}
>
{children}
</Div >
);
};

23
front/src/ui/FullPage.tsx Normal file
View File

@ -0,0 +1,23 @@
import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type FullPageProps = {
children: ReactNode;
style?: CSSProperties;
};
export const FullPage = ({ children, style }: FullPageProps) => {
return (
<Div
style={{
top: '0',
bottom: '0',
right: '0',
left: '0',
...style,
}}
>
{children}
</Div>
);
};

25
front/src/ui/HStack.tsx Normal file
View File

@ -0,0 +1,25 @@
import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type HStackProps = {
children: ReactNode;
spacing?: CSSProperties['gap'];
align?: CSSProperties['alignItems'];
style?: Omit<CSSProperties, "gap" | "alignItems">;
};
export const HStack = ({ children, spacing = '8px', align = 'flex-start', style }: HStackProps) => {
return (
<Div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: align,
gap: spacing,
...style,
}}
>
{children}
</Div>
);
};

30
front/src/ui/Image.tsx Normal file
View File

@ -0,0 +1,30 @@
import { CSSProperties, RefObject } from "react";
import { useTheme } from "@/theme/ThemeContext";
export type ImageProps = {
ref?: RefObject<any>;
src?: string;
boxSize?: string;
onChange?: (e) => void;
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
};
export const Image = ({
ref,
src,
boxSize,
onChange,
style,
}: ImageProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle({ width: boxSize, height: boxSize, ...style }) : undefined;
return (
<img
ref={ref}
src={src}
onChange={onChange}
style={themedStyle}
>
</img>
);
};

29
front/src/ui/Input.tsx Normal file
View File

@ -0,0 +1,29 @@
import { ReactNode, CSSProperties, RefObject } from "react";
import { Div } from "./Div";
import { useTheme } from "@/theme/ThemeContext";
export type InputProps = {
ref?: RefObject<any>;
value?: string;
onChange?: (e) => void;
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
};
export const Input = ({
ref,
value,
onChange,
style,
}: InputProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return (
<input
ref={ref}
value={value}
onChange={onChange}
style={themedStyle}
>
</input>
);
};

16
front/src/ui/Link.tsx Normal file
View File

@ -0,0 +1,16 @@
import { CSSProperties, ReactNode } from 'react'
import { useNavigate } from 'react-router-dom';
import { Div } from './Div';
export type LinkProps = {
href?: string;
style?: CSSProperties;
children?: ReactNode;
}
export const Link = ({ href = "/", style, children }: LinkProps) => {
const navigate = useNavigate();
return (<Div onClick={() => navigate(href)}
style={style}
> {children}</Div >);
}

36
front/src/ui/Span.tsx Normal file
View File

@ -0,0 +1,36 @@
import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
import { useTheme } from "@/theme/ThemeContext";
export type SpanProps = {
children: ReactNode;
color?: string;
fontSize?: CSSProperties["fontSize"];
fontWeight?: CSSProperties["fontWeight"];
userSelect?: CSSProperties["userSelect"];
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
};
export const Span = ({ children,
fontSize = '16px',
fontWeight = "normal",
color = "#000000",
userSelect,
style,
}: SpanProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle({
fontSize,
color,
fontWeight,
userSelect,
...style,
}) : undefined;
return (
<span
style={themedStyle}
>
{children}
</span>
);
};

View File

@ -0,0 +1,12 @@
import { useTheme } from "@/theme/ThemeContext";
import { CSSProperties, ReactNode } from "react"
export type BodyProps = {
children?: ReactNode;
style?: CSSProperties;
};
export const Body = ({ children, style }: BodyProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return <tbody style={themedStyle}>{children}</tbody>
}

View File

@ -0,0 +1,12 @@
import { useTheme } from "@/theme/ThemeContext";
import { CSSProperties, ReactNode } from "react"
export type CellProps = {
children?: ReactNode;
style?: CSSProperties;
};
export const Cell = ({ children, style }: CellProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return <td style={themedStyle}>{children}</td>
}

View File

@ -0,0 +1,12 @@
import { useTheme } from "@/theme/ThemeContext";
import { CSSProperties, ReactNode } from "react"
export type ColumnHeaderProps = {
children?: ReactNode;
style?: CSSProperties;
};
export const ColumnHeader = ({ children, style }: ColumnHeaderProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return <th style={themedStyle}>{children}</th>
}

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