[FEAT] add many basic element, ux bad but build is OK

This commit is contained in:
Edouard DUPIN 2025-01-22 00:30:16 +01:00
parent f9019ec508
commit 12223347d3
77 changed files with 1514 additions and 1163 deletions

View File

@ -29,6 +29,7 @@
"*.{ts,tsx,js,jsx,json}": "prettier --write" "*.{ts,tsx,js,jsx,json}": "prettier --write"
}, },
"dependencies": { "dependencies": {
"history": "5.3.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-error-boundary": "5.0.0", "react-error-boundary": "5.0.0",

10
front/pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
history:
specifier: 5.3.0
version: 5.3.0
react: react:
specifier: 18.3.1 specifier: 18.3.1
version: 18.3.1 version: 18.3.1
@ -2372,6 +2375,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
@ -6820,6 +6826,10 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
history@5.3.0:
dependencies:
'@babel/runtime': 7.24.7
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
dependencies: dependencies:
react-is: 16.13.1 react-is: 16.13.1

View File

@ -22,7 +22,7 @@ import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track'; import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { Flex, Text } from '@/ui'; import { Flex, Text } from '@/ui';
@ -77,7 +77,7 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
: '' : ''
); );
}, [dataTrack, setMediaSource]); }, [dataTrack, setMediaSource]);
const backColor = useColorModeValue('back.100', 'back.800'); const backColor = useColorThemeValue('back.100', 'back.800');
const configButton = { const configButton = {
borderRadius: 'full', borderRadius: 'full',
backgroundColor: '#00000000', backgroundColor: '#00000000',

View File

@ -1,13 +1,13 @@
import { ReactElement, useEffect, useState } from 'react'; import { CSSProperties, ReactElement, useEffect, useState } from 'react';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { ObjectId } from '@/back-api'; import { ObjectId } from '@/back-api';
import { Flex } from '@/ui'; import { DivProps, Flex } from '@/ui';
export type CoversProps = Omit<BoxProps, "iconEmpty"> & { export type CoversProps = Omit<DivProps, "iconEmpty"> & {
data?: ObjectId[]; data?: ObjectId[];
size?: BoxProps["width"]; size?: CSSProperties["width"];
iconEmpty?: ReactElement; iconEmpty?: ReactElement;
slideshow?: boolean; slideshow?: boolean;
}; };

View File

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

View File

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

View File

@ -38,15 +38,15 @@ export const SearchInput = ({
onSubmitValue(inputData); onSubmitValue(inputData);
} }
} }
return ( return (<></>
<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}> //<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}>
<MdSearch color="gray.300" /> // <MdSearch color="gray.300" />
<Input // <Input
onFocus={onFocusKeep} // onFocus={onFocusKeep}
onBlur={() => setTimeout(() => onFocusLost(), 200)} // onBlur={() => setTimeout(() => onFocusLost(), 200)}
onChange={onChange} // onChange={onChange}
onSubmit={onSubmit} // onSubmit={onSubmit}
/> // />
</Group> //</Group>
); );
}; };

View File

@ -14,12 +14,11 @@ import { useNavigate } from 'react-router-dom';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState'; import { SessionState } from '@/service/SessionState';
import { colors } from '@/theme/foundations/colors'; import { colors } from '@/theme/colors';
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso'; import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import { useSessionService } from '@/service/session'; import { useSessionService } from '@/service/session';
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md'; import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode'; import { useColorThemeValue, useTheme } from '@/theme/ThemeContext';
import { THEME } from '@/theme/theme';
import { useDisclosure } from '@/utils/disclosure'; import { useDisclosure } from '@/utils/disclosure';
import { Button, Flex, Text } from '@/ui'; import { Button, Flex, Text } from '@/ui';
@ -36,11 +35,11 @@ export type TopBarProps = {
}; };
export const TopBar = ({ title, children }: TopBarProps) => { export const TopBar = ({ title, children }: TopBarProps) => {
const { colorMode, toggleColorMode } = useColorMode(); const { theme, toggleTheme } = useTheme();
const { clearToken } = useSessionService(); const { clearToken } = useSessionService();
const { session } = useServiceContext(); const { session } = useServiceContext();
const backColor = useColorModeValue('back.100', 'back.800'); const backColor = useColorThemeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure(); const drawerDisclose = useDisclosure();
const onChangeTheme = () => { const onChangeTheme = () => {
drawerDisclose.onOpen(); drawerDisclose.onOpen();
@ -113,7 +112,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<Flex style={{ right: 0 }}> <Flex style={{ right: 0 }}>
{session?.state !== SessionState.CONNECTED && ( {session?.state !== SessionState.CONNECTED && (
<> <>
<Button {...BUTTON_TOP_BAR_PROPERTY} {...THEME.Button.primary} onClick={onSignIn}> <Button {...BUTTON_TOP_BAR_PROPERTY} /*{...THEME.Button.primary}*/ onClick={onSignIn}>
<LuLogIn /> <LuLogIn />
<Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold"> <Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold">
Sign-in Sign-in

View File

@ -5,6 +5,8 @@ import { Album } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { useCountTracksWithAlbumId } from '@/service/Track'; import { useCountTracksWithAlbumId } from '@/service/Track';
import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing'; import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
export type DisplayAlbumProps = { export type DisplayAlbumProps = {
dataAlbum?: Album; dataAlbum?: Album;
@ -33,32 +35,37 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
//maxWidth="150px" //maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{
flex={1} marginRight: "auto",
overflow: "hidden",
flex: 1,
}}
> >
<Text <Span
as="span"
// align="left" // align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto" style={{
overflow="hidden" marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={[1, 2]} // noOfLines={[1, 2]}
> >
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Span>
<Text <Span
as="span"
// align="left" // align="left"
fontSize="15px" fontSize="15px"
userSelect="none" userSelect="none"
marginRight="auto" style={{
overflow="hidden" marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1} // noOfLines={1}
> >
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'} {countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
</Text> </Span>
</Flex> </Flex>
</Flex> </Flex>
); );

View File

@ -1,6 +1,6 @@
import { LuMenu } from 'react-icons/lu'; import { LuMenu } from 'react-icons/lu';
import { THEME } from '@/theme/theme';
export type MenuElement = { export type MenuElement = {
name: string; name: string;
@ -12,28 +12,28 @@ export type ContextMenuProps = {
}; };
export const ContextMenu = ({ elements }: ContextMenuProps) => { export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) { // if (!elements) {
return <></>; return <></>;
} // }
return ( // return (
<Menu.Root // <Menu.Root
data-testid="context-menu"> // data-testid="context-menu">
<Menu.Trigger asChild // <Menu.Trigger asChild
data-testid="context-menu_trigger"> // data-testid="context-menu_trigger">
{/* This is very stupid, we need to set as span to prevent a button in button... WTF */} // {/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
<Button {...THEME.Button.primary} > // <Button {...THEME.Button.primary} >
<LuMenu /> // <LuMenu />
</Button> // </Button>
</Menu.Trigger> // </Menu.Trigger>
<Menu.Content // <Menu.Content
data-testid="context-menu_content"> // data-testid="context-menu_content">
{elements?.map((data) => ( // {elements?.map((data) => (
<Menu.Item key={data.name} value={data.name} onClick={data.onClick} // <Menu.Item key={data.name} value={data.name} onClick={data.onClick}
data-testid="context-menu_item"> // data-testid="context-menu_item">
{data.name} // {data.name}
</Menu.Item> // </Menu.Item>
))} // ))}
</Menu.Content> // </Menu.Content>
</Menu.Root > // </Menu.Root >
); //);
}; };

View File

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

View File

@ -2,6 +2,7 @@ import { RefObject } from 'react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { Input } from '@/ui';
export type FormInputProps = { export type FormInputProps = {
form: UseFormidableReturn; form: UseFormidableReturn;

View File

@ -33,7 +33,8 @@ export const FormNumber = ({
onRestore={() => form.restoreValue({ [variableName]: true })} onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest} {...rest}
> >
<NumberInput.Root <></>
{/* <NumberInput.Root
ref={ref} ref={ref}
value={form.values[variableName]} value={form.values[variableName]}
onValueChange={(value) => form.setValues({ [variableName]: value })} onValueChange={(value) => form.setValues({ [variableName]: value })}
@ -43,7 +44,7 @@ export const FormNumber = ({
max={max} max={max}
> >
<NumberInput.Input /> <NumberInput.Input />
</NumberInput.Root> </NumberInput.Root> */}
</FormGroup> </FormGroup>
); );
}; };

View File

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

View File

@ -128,109 +128,110 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Dialog.Root // return (
//initialFocusRef={initialRef} // <Dialog.Root
//finalFocusRef={finalRef} // //initialFocusRef={initialRef}
//closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onOpenChange={onClose} // //closeOnOverlayClick={false}
open={true} // onOpenChange={onClose}
data-testid="album-edit-pop-up" // open={true}
> // data-testid="album-edit-pop-up"
{/* <DialogOverlay /> */} // >
{/* <DialogCloseTrigger /> */} // {/* <DialogOverlay /> */}
<Dialog.Content> // {/* <DialogCloseTrigger /> */}
<Dialog.Header>Edit Album</Dialog.Header> // <Dialog.Content>
{/* <DialogCloseButton ref={finalRef} /> */} // <Dialog.Header>Edit Album</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<Dialog.Body pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataAlbum?.id}</Text> // <Text>{dataAlbum?.id}</Text>
</FormGroup> // </FormGroup>
{countTracksOfAnAlbum !== 0 && ( // {countTracksOfAnAlbum !== 0 && (
<Flex paddingLeft="14px"> // <Flex paddingLeft="14px">
<MdWarning color="red.600" /> // <MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold"> // <Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove album {countTracksOfAnAlbum} track(s) depend // Can not remove album {countTracksOfAnAlbum} track(s) depend
on it. // on it.
</Text> // </Text>
</Flex> // </Flex>
)} // )}
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
theme="@danger" // theme="@danger"
disabled={countTracksOfAnAlbum !== 0} // disabled={countTracksOfAnAlbum !== 0}
> // >
<MdDeleteForever /> Remove Media // <MdDeleteForever /> Remove Media
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove album" // title="Remove album"
body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} // body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Title" // label="Title"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormInput // <FormInput
form={form} // form={form}
variableName="publication" // variableName="publication"
label="Publication" // label="Publication"
/> // />
<FormCovers // <FormCovers
form={form} // form={form}
variableName="covers" // variableName="covers"
onFilesSelected={onFilesSelected} // onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} // onUriSelected={onUriSelected}
onRemove={onRemoveCover} // onRemove={onRemoveCover}
/> // />
</> // </>
)} // )}
</Dialog.Body> // </Dialog.Body>
<Dialog.Footer> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</Dialog.Footer> // </Dialog.Footer>
</Dialog.Content> // </Dialog.Content>
</Dialog.Root> // </Dialog.Root>
); // );
}; };

View File

@ -23,29 +23,28 @@ export const ConfirmPopUp = ({
disclosure.onClose(); disclosure.onClose();
}; };
const cancelRef = useRef<HTMLButtonElement>(null); const cancelRef = useRef<HTMLButtonElement>(null);
return ( return <></>;
<Dialog.Root role="alertdialog" // return (
open={disclosure.open} // <Dialog.Root role="alertdialog"
//leastDestructiveRef={cancelRef} // open={disclosure.open}
onOpenChange={disclosure.onClose} // //leastDestructiveRef={cancelRef}
data-testid="confirm-pop-up" // onOpenChange={disclosure.onClose}
> // data-testid="confirm-pop-up"
<Dialog.Content> // >
<Dialog.Header fontSize="lg" fontWeight="bold"> // <Dialog.Content>
{title} // <Dialog.Header fontSize="lg" fontWeight="bold">
</Dialog.Header> // {title}
// </Dialog.Header>
<Dialog.Body>{body}</Dialog.Body> // <Dialog.Body>{body}</Dialog.Body>
// <Dialog.Footer>
<Dialog.Footer> // <Button onClick={disclosure.onClose} ref={cancelRef}>
<Button onClick={disclosure.onClose} ref={cancelRef}> // Cancel
Cancel // </Button>
</Button> // <Button colorScheme="red" onClick={onClickConfirm} ml={3}>
<Button colorScheme="red" onClick={onClickConfirm} ml={3}> // {confirmTitle}
{confirmTitle} // </Button>
</Button> // </Dialog.Footer>
</Dialog.Footer> // </Dialog.Content>
</Dialog.Content> // </Dialog.Root>
</Dialog.Root> // );
);
}; };

View File

@ -128,103 +128,104 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Dialog.Root // return (
//initialFocusRef={initialRef} // <Dialog.Root
//finalFocusRef={finalRef} // //initialFocusRef={initialRef}
//closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onOpenChange={onClose} // //closeOnOverlayClick={false}
open={true} // onOpenChange={onClose}
data-testid="gender-edit-pop-up" // open={true}
> // data-testid="gender-edit-pop-up"
{/* <DialogOverlay /> */} // >
<Dialog.Content> // {/* <DialogOverlay /> */}
<Dialog.Header>Edit Gender</Dialog.Header> // <Dialog.Content>
{/* <DialogCloseButton ref={finalRef} /> */} // <Dialog.Header>Edit Gender</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<Dialog.Body pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataGender?.id}</Text> // <Text>{dataGender?.id}</Text>
</FormGroup> // </FormGroup>
{countTracksOnAGender !== 0 && ( // {countTracksOnAGender !== 0 && (
<Flex paddingLeft="14px"> // <Flex paddingLeft="14px">
<MdWarning color="red.600" /> // <MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold"> // <Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove gender {countTracksOnAGender} track(s) depend // Can not remove gender {countTracksOnAGender} track(s) depend
on it. // on it.
</Text> // </Text>
</Flex> // </Flex>
)} // )}
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
theme="@danger" // theme="@danger"
disabled={countTracksOnAGender !== 0} // disabled={countTracksOnAGender !== 0}
> // >
<MdDeleteForever /> Remove gender // <MdDeleteForever /> Remove gender
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove gender" // title="Remove gender"
body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} // body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Gender name" // label="Gender name"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormCovers // <FormCovers
form={form} // form={form}
variableName="covers" // variableName="covers"
onFilesSelected={onFilesSelected} // onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} // onUriSelected={onUriSelected}
onRemove={onRemoveCover} // onRemove={onRemoveCover}
/> // />
</> // </>
)} // )}
</Dialog.Body> // </Dialog.Body>
<Dialog.Footer> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</Dialog.Footer> // </Dialog.Footer>
</Dialog.Content> // </Dialog.Content>
</Dialog.Root> // </Dialog.Root>
); // );
}; };

View File

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

@ -80,116 +80,117 @@ export const TrackEditPopUp = ({ }: TrackEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Dialog.Root // return (
//initialFocusRef={initialRef} // <Dialog.Root
//finalFocusRef={finalRef} // //initialFocusRef={initialRef}
//closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onOpenChange={onClose} // //closeOnOverlayClick={false}
open={true} // onOpenChange={onClose}
data-testid="track-edit-pop-up" // open={true}
> // data-testid="track-edit-pop-up"
{/* <DialogOverlay /> */} // >
<Dialog.Content> // {/* <DialogOverlay /> */}
<Dialog.Header>Edit Track</Dialog.Header> // <Dialog.Content>
{/* <DialogCloseButton ref={finalRef} /> */} // <Dialog.Header>Edit Track</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<Dialog.Body pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataTrack?.id}</Text> // <Text>{dataTrack?.id}</Text>
</FormGroup> // </FormGroup>
<FormGroup label="Data Id"> // <FormGroup label="Data Id">
<Text>{dataTrack?.dataId}</Text> // <Text>{dataTrack?.dataId}</Text>
</FormGroup> // </FormGroup>
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
theme="@danger" // theme="@danger"
> // >
<MdDeleteForever /> Remove Media // <MdDeleteForever /> Remove Media
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove track" // title="Remove track"
body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} // body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Title" // label="Title"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormSelect // <FormSelect
form={form} // form={form}
variableName="genderId" // variableName="genderId"
options={dataGenders} // options={dataGenders}
label="Gender" // label="Gender"
/> // />
<FormSelectMultiple // <FormSelectMultiple
form={form} // form={form}
variableName="artists" // variableName="artists"
options={dataArtist} // options={dataArtist}
label="Artist(s)" // label="Artist(s)"
/> // />
<FormSelect // <FormSelect
form={form} // form={form}
variableName="albumId" // variableName="albumId"
options={dataAlbums} // options={dataAlbums}
label="Album" // label="Album"
/> // />
<FormNumber // <FormNumber
form={form} // form={form}
variableName="track" // variableName="track"
label="Track n°" // label="Track n°"
step={1} // step={1}
//defaultValue={0} // //defaultValue={0}
min={0} // min={0}
max={1000} // max={1000}
/> // />
</> // </>
)} // )}
</Dialog.Body> // </Dialog.Body>
<Dialog.Footer> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</Dialog.Footer> // </Dialog.Footer>
</Dialog.Content> // </Dialog.Content>
</Dialog.Root> // </Dialog.Root>
); // );
}; };

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtists } from '@/service/Artist'; import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = { export type DisplayTrackProps = {
track: Track; track: Track;
@ -39,69 +40,78 @@ export const DisplayTrackFull = ({
width="full" width="full"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden"
onClick={onClick} onClick={onClick}
style={{
overflowX: "hidden",
}}
> >
<Text <Span
as="span"
alignContent="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// TODO: noOfLines={1}
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={1}
margin: "auto 0",
}}
> >
{track.name} {track.track && ` [${track.track}]`} {track.name} {track.track && ` [${track.track}]`}
</Text> </Span>
{dataAlbum && ( {dataAlbum && (
<Text <Span
as="span"
alignContent="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1} //noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name} <Span fontWeight="normal">Album:</Span> {dataAlbum.name}
</Text> </Span>
)} )}
{dataArtists && ( {dataArtists && (
<Text <Span
as="span"
alignContent="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')} <Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')}
</Text> </Span>
)} )}
{dataGender && ( {dataGender && (
<Text <Span
as="span"
alignContent="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
//noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name} <Span fontWeight="normal">Gender:</Span> {dataGender.name}
</Text> </Span>
)} )}
</Flex> </Flex>
<ContextMenu elements={contextMenu} <ContextMenu elements={contextMenu}

View File

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

View File

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

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,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_SPACING = "5px"; // { base: "5px", md: "10px", lg: "20px" };
export const BASE_WRAP_WIDTH = { base: "90%", md: "45%", lg: "270px" }; export const BASE_WRAP_WIDTH = "90%"; // { base: "90%", md: "45%", lg: "270px" };
export const BASE_WRAP_HEIGHT = { base: "75px", lg: "120px" }; export const BASE_WRAP_HEIGHT = "75px"; // { base: "75px", lg: "120px" };
export const BASE_WRAP_ICON_SIZE = { base: "50px", lg: "100px" }; export const BASE_WRAP_ICON_SIZE = "50px";// { base: "50px", lg: "100px" };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { import {
unstable_HistoryRouter as HistoryRouter, unstable_HistoryRouter as HistoryRouter,
Route, Route,
@ -21,31 +21,31 @@ import { OnAirPage } from './onAir/OnAirPage';
export const AppRoutes = () => { export const AppRoutes = () => {
const { isReadable } = useHasRight('user'); const { isReadable } = useHasRight('user');
return ( return (
// <HistoryRouter <HistoryRouter
// // @ts-expect-error // @ts-expect-error
// history={createBrowserHistory({ window })} history={createBrowserHistory({ window })}
// basename="/karusic" basename="/karusic"
// > >
<Routes> <Routes>
{/* Need to keep it in all case, it is the only way to log-in */} {/* Need to keep it in all case, it is the only way to log-in */}
<Route path="sso/*" element={<SSORoutes />} /> <Route path="sso/*" element={<SSORoutes />} />
{isReadable ? ( {isReadable ? (
<> <>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="help" element={<HelpPage />} /> <Route path="help" element={<HelpPage />} />
<Route path="settings" element={<SettingsPage />} /> <Route path="settings" element={<SettingsPage />} />
<Route path="add" element={<AddPage />} /> <Route path="add" element={<AddPage />} />
<Route path="on-air/*" element={<OnAirPage />} /> <Route path="on-air/*" element={<OnAirPage />} />
<Route path="artist/*" element={<ArtistRoutes />} /> <Route path="artist/*" element={<ArtistRoutes />} />
<Route path="album/*" element={<AlbumRoutes />} /> <Route path="album/*" element={<AlbumRoutes />} />
<Route path="gender/*" element={<GenderRoutes />} /> <Route path="gender/*" element={<GenderRoutes />} />
<Route path="track/*" element={<TrackRoutes />} /> <Route path="track/*" element={<TrackRoutes />} />
<Route path="*" element={<Error404 />} /> <Route path="*" element={<Error404 />} />
</> </>
) : ( ) : (
<Route path="*" element={<Error401 />} /> <Route path="*" element={<Error401 />} />
)} )}
</Routes> </Routes>
// </HistoryRouter> </HistoryRouter>
); );
}; };

View File

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

View File

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

View File

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

View File

@ -11,8 +11,9 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp'; import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp';
import { useSpecificArtist } from '@/service/Artist'; import { useSpecificArtist } from '@/service/Artist';
import { useAlbumIdsOfAnArtist } from '@/service/Track'; import { useAlbumIdsOfAnArtist } from '@/service/Track';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing';
import { Button, Flex, HStack, Text } from '@/ui';
export const ArtistDetailPage = () => { export const ArtistDetailPage = () => {
const { artistId } = useParams(); const { artistId } = useParams();
@ -40,7 +41,7 @@ export const ArtistDetailPage = () => {
<> <>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto" style={{ marginRight: "auto" }}
onClick={() => navigate(`/artist/all`)} onClick={() => navigate(`/artist/all`)}
> >
<MdGroup height="full" /> <MdGroup height="full" />
@ -63,7 +64,7 @@ export const ArtistDetailPage = () => {
<Flex <Flex
direction="row" direction="row"
width="80%" width="80%"
marginX="auto" margin="0 auto"
padding="10px" padding="10px"
gap="10px" gap="10px"
> >
@ -87,25 +88,27 @@ export const ArtistDetailPage = () => {
</Flex> </Flex>
</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) => ( {albumIdsOfAnArtist?.map((data) => (
<Flex align="flex-start" <Button
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
padding="5px" style={{
as="button" alignContent: "flex-start",
_hover={{ width: BASE_WRAP_WIDTH,
boxShadow: 'outline-over', height: BASE_WRAP_HEIGHT,
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), border: "1px",
borderColor: "brand.900",
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover={
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<DisplayAlbumId id={data} key={data} /> <DisplayAlbumId id={data} key={data} />
</Flex> </Button>
))} ))}
</HStack> </HStack>
<EmptyEnd /> <EmptyEnd />

View File

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

View File

@ -14,7 +14,8 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useTracksOfAGender } from '@/service/Track'; import { useTracksOfAGender } from '@/service/Track';
//import { useTracksOfAGender } from '@/service/Track'; //import { useTracksOfAGender } from '@/service/Track';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Button, Flex, Text } from '@/ui';
export const GenderDetailPage = () => { export const GenderDetailPage = () => {
const { genderId } = useParams(); const { genderId } = useParams();
@ -66,17 +67,19 @@ export const GenderDetailPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="row" direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto",
padding: "10px",
}}
> >
<Covers <Covers
data={dataGender?.covers} data={dataGender?.covers}
// TODO: iconEmpty={LuDisc3} // TODO: iconEmpty={LuDisc3}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataGender?.name} {dataGender?.name}
</Text> </Text>
@ -89,23 +92,26 @@ export const GenderDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
marginX="auto" style={{
padding="20px" margin: "0 auto",
width="80%" padding: "20px",
width: "80%",
}}
> >
{tracksOnAGender?.map((data) => ( {tracksOnAGender?.map((data) => (
<Box <Flex
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" minWidth: "100%",
_hover={{ //height="60px",
boxShadow: 'outline-over', border: "1px",
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
backgroundColor: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
@ -121,7 +127,7 @@ export const GenderDetailPage = () => {
{ name: 'Add Playlist', onClick: () => { } }, { name: 'Add Playlist', onClick: () => { } },
]} ]}
/> />
</Box> </Flex>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@ -9,7 +9,7 @@ import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayGender } from '@/components/gender/DisplayGender'; import { DisplayGender } from '@/components/gender/DisplayGender';
import { useOrderedGenders } from '@/service/Gender'; import { useOrderedGenders } from '@/service/Gender';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Flex, HStack } from '@/ui'; import { Flex, HStack } from '@/ui';
export const GendersPage = () => { export const GendersPage = () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -0,0 +1,125 @@
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
const brand = {
50: '#e3edff',
100: '#b6c9fd',
200: '#88a5f7',
300: '#5a81f2',
400: '#2c5ded',
500: '#1543d4',
600: '#0d34a5',
700: '#062577',
800: '#02164a',
900: '#00071e',
};
const back = {
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 = {
green, red, orange, back, blue, yellow, purple, cyan
}
export const colors = {
brand: brand,
back: back,
success: green,
error: red,
warning: orange,
} as const;

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,48 +0,0 @@
import * as recipes from './recipes';
import { colors } from "./foundations/colors"
const baseTheme = {
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: {
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);
*/
export const THEME = {
...recipes
};

View File

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

View File

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

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

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

View File

@ -1,4 +1,5 @@
import React, { CSSProperties, ReactNode } from 'react'; import React, { CSSProperties, ReactNode } from 'react';
import { Div } from './Div';
export type FlexProps = { export type FlexProps = {
children?: ReactNode; children?: ReactNode;
@ -7,12 +8,44 @@ export type FlexProps = {
gap?: string | number; gap?: string | number;
justify?: CSSProperties['justifyContent']; justify?: CSSProperties['justifyContent'];
align?: CSSProperties['alignItems']; 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"> style?: Omit<CSSProperties, "flexDirection" | "display" | "justifyContent" | "alignItems" | "alignItems" | "direction">
}; };
export const Flex = ({ children, onClick, direction = 'row', justify = 'flex-start', gap, align = 'stretch', style }: FlexProps) => { 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,
style }: FlexProps) => {
return ( return (
<div <Div
onClick={onClick} onClick={onClick}
style={{ style={{
display: 'flex', display: 'flex',
@ -20,10 +53,18 @@ export const Flex = ({ children, onClick, direction = 'row', justify = 'flex-sta
justifyContent: justify, justifyContent: justify,
gap: gap, gap: gap,
alignItems: align, alignItems: align,
width,
height,
padding,
margin,
maxWidth,
minWidth,
maxHeight,
minHeight, paddingLeft, paddingRight, paddingTop, paddingBottom, marginLeft, marginRight, marginTop, marginBottom,
...style, ...style,
}} }}
> >
{children} {children}
</div> </Div>
); );
}; };

View File

@ -1,4 +1,5 @@
import { ReactNode, CSSProperties } from "react"; import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type FullPageProps = { export type FullPageProps = {
children: ReactNode; children: ReactNode;
@ -7,7 +8,7 @@ export type FullPageProps = {
export const FullPage = ({ children, style }: FullPageProps) => { export const FullPage = ({ children, style }: FullPageProps) => {
return ( return (
<div <Div
style={{ style={{
top: '0', top: '0',
bottom: '0', bottom: '0',
@ -17,6 +18,6 @@ export const FullPage = ({ children, style }: FullPageProps) => {
}} }}
> >
{children} {children}
</div> </Div>
); );
}; };

View File

@ -1,4 +1,5 @@
import { ReactNode, CSSProperties } from "react"; import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type HStackProps = { export type HStackProps = {
children: ReactNode; children: ReactNode;
@ -9,7 +10,7 @@ export type HStackProps = {
export const HStack = ({ children, spacing = '8px', align = 'flex-start', style }: HStackProps) => { export const HStack = ({ children, spacing = '8px', align = 'flex-start', style }: HStackProps) => {
return ( return (
<div <Div
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@ -19,6 +20,6 @@ export const HStack = ({ children, spacing = '8px', align = 'flex-start', style
}} }}
> >
{children} {children}
</div> </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>;
href?: string;
boxSize?: string;
onChange?: (e) => void;
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
};
export const Image = ({
ref,
href,
boxSize,
onChange,
style,
}: ImageProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle({ width: boxSize, height: boxSize, ...style }) : undefined;
return (
<image
ref={ref}
href={href}
onChange={onChange}
style={themedStyle}
>
</image>
);
};

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

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { useTheme } from "@/theme/ThemeContext";
import { CSSProperties, ReactNode } from "react" import { CSSProperties, ReactNode } from "react"
export type HeaderProps = { export type HeaderProps = {
@ -5,5 +6,7 @@ export type HeaderProps = {
style?: CSSProperties; style?: CSSProperties;
}; };
export const Header = ({ children, style }: HeaderProps) => { export const Header = ({ children, style }: HeaderProps) => {
return <thead style={style}>{children}</thead> const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return <thead style={themedStyle}>{children}</thead>
} }

View File

@ -1,3 +1,4 @@
import { useTheme } from '@/theme/ThemeContext';
import { CSSProperties, ReactNode } from 'react' import { CSSProperties, ReactNode } from 'react'
export type RootProps = { export type RootProps = {
@ -7,9 +8,11 @@ export type RootProps = {
} }
export const Root = ({ background, children, style }: RootProps) => { export const Root = ({ background, children, style }: RootProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle({
backgroundColor: background,
...style
}) : undefined;
return (<table return (<table
style={{ style={themedStyle}>{children}</table>);
backgroundColor: background,
...style
}}>{children}</table>);
} }

View File

@ -1,3 +1,4 @@
import { useTheme } from "@/theme/ThemeContext";
import { CSSProperties, ReactNode } from "react" import { CSSProperties, ReactNode } from "react"
export type RowProps = { export type RowProps = {
@ -6,5 +7,7 @@ export type RowProps = {
}; };
export const Row = ({ children, style }: RowProps) => { export const Row = ({ children, style }: RowProps) => {
return <tr style={style}>{children}</tr> const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return <tr style={themedStyle}>{children}</tr>
} }

View File

@ -1,10 +1,12 @@
import { ReactNode, CSSProperties } from "react"; import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type TextProps = { export type TextProps = {
children: ReactNode; children: ReactNode;
color?: string; color?: string;
fontSize?: CSSProperties["fontSize"]; fontSize?: CSSProperties["fontSize"];
fontWeight?: CSSProperties["fontWeight"]; fontWeight?: CSSProperties["fontWeight"];
userSelect?: CSSProperties["userSelect"];
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">; style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
}; };
@ -12,18 +14,20 @@ export const Text = ({ children,
fontSize = '16px', fontSize = '16px',
fontWeight = "normal", fontWeight = "normal",
color = "#000000", color = "#000000",
userSelect,
style, style,
}: TextProps) => { }: TextProps) => {
return ( return (
<span <Div
style={{ style={{
fontSize, fontSize,
color, color,
fontWeight, fontWeight,
userSelect,
...style, ...style,
}} }}
> >
{children} {children}
</span> </Div>
); );
}; };

29
front/src/ui/Textarea.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 TextareaProps = {
ref?: RefObject<any>;
value?: string;
onChange?: (e) => void;
style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">;
};
export const Textarea = ({
ref,
value,
onChange,
style,
}: TextareaProps) => {
const { convertStyle } = useTheme();
const themedStyle = style ? convertStyle(style) : undefined;
return (
<textarea
ref={ref}
value={value}
onChange={onChange}
style={themedStyle}
>
</textarea>
);
};

View File

@ -1,4 +1,5 @@
import { ReactNode, CSSProperties } from "react"; import { ReactNode, CSSProperties } from "react";
import { Div } from "./Div";
export type VStackProps = { export type VStackProps = {
children: ReactNode; children: ReactNode;
@ -9,7 +10,7 @@ export type VStackProps = {
export const VStack = ({ children, spacing = '8px', align = 'flex-start', style }: VStackProps) => { export const VStack = ({ children, spacing = '8px', align = 'flex-start', style }: VStackProps) => {
return ( return (
<div <Div
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
@ -19,6 +20,6 @@ export const VStack = ({ children, spacing = '8px', align = 'flex-start', style
}} }}
> >
{children} {children}
</div> </Div>
); );
}; };

View File

@ -4,4 +4,10 @@ export * from "./Flex.tsx"
export * from "./HStack.tsx" export * from "./HStack.tsx"
export * from "./VStack.tsx" export * from "./VStack.tsx"
export * from "./FullPage.tsx" export * from "./FullPage.tsx"
export * from "./Link.tsx"
export * from "./Div.tsx"
export * from "./Span.tsx"
export * from "./Input.tsx"
export * from "./Textarea.tsx"
export * from "./Image.tsx"
export * as Table from "./Table" export * as Table from "./Table"

File diff suppressed because one or more lines are too long