[theme and reciepice

This commit is contained in:
Edouard DUPIN 2025-01-24 21:57:04 +01:00
parent d52052de90
commit c489fabb77
37 changed files with 11601 additions and 147 deletions

View File

@ -29,11 +29,9 @@
"*.{ts,tsx,js,jsx,json}": "prettier --write"
},
"dependencies": {
"@chakra-ui/anatomy": "2.3.4",
"@chakra-ui/cli": "3.3.1",
"@chakra-ui/react": "3.3.1",
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.0",
"allotment": "1.20.2",
"css-mediaquery": "0.1.2",
"dayjs": "1.11.13",

36
front/pnpm-lock.yaml generated
View File

@ -8,9 +8,6 @@ importers:
.:
dependencies:
'@chakra-ui/anatomy':
specifier: 2.3.4
version: 2.3.4
'@chakra-ui/cli':
specifier: 3.3.1
version: 3.3.1(@chakra-ui/react@3.3.1(@emotion/react@11.14.0(@types/react@18.3.8)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@ -20,9 +17,6 @@ importers:
'@emotion/react':
specifier: 11.14.0
version: 11.14.0(@types/react@18.3.8)(react@18.3.1)
'@emotion/styled':
specifier: 11.14.0
version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.8)(react@18.3.1))(@types/react@18.3.8)(react@18.3.1)
allotment:
specifier: 1.20.2
version: 1.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -378,9 +372,6 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
'@chakra-ui/anatomy@2.3.4':
resolution: {integrity: sha512-fFIYN7L276gw0Q7/ikMMlZxP7mvnjRaWJ7f3Jsf9VtDOi6eAYIBRrhQe6+SZ0PGmoOkRaBc7gSE5oeIbgFFyrw==}
'@chakra-ui/cli@3.3.1':
resolution: {integrity: sha512-TTpGVT4RuajxzYjMP95Ba3HU052cmdrYgru77ZGD+IDb/HLATjpXNViFAn1R+ITMCxa4v0zqYEWLY9Ex2L090A==}
hasBin: true
@ -444,16 +435,6 @@ packages:
'@emotion/sheet@1.4.0':
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/styled@11.14.0':
resolution: {integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==}
peerDependencies:
'@emotion/react': ^11.0.0-rc.0
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
@ -5469,8 +5450,6 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
'@chakra-ui/anatomy@2.3.4': {}
'@chakra-ui/cli@3.3.1(@chakra-ui/react@3.3.1(@emotion/react@11.14.0(@types/react@18.3.8)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))':
dependencies:
'@chakra-ui/react': 3.3.1(@emotion/react@11.14.0(@types/react@18.3.8)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -5595,21 +5574,6 @@ snapshots:
'@emotion/sheet@1.4.0': {}
'@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.8)(react@18.3.1))(@types/react@18.3.8)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.7
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.1
'@emotion/react': 11.14.0(@types/react@18.3.8)(react@18.3.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
'@emotion/utils': 1.4.2
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.8
transitivePeerDependencies:
- supports-color
'@emotion/unitless@0.10.0': {}
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':

View File

@ -56,6 +56,7 @@ const AppEnvHint = () => {
return (
<>
<Box
as="button"
zIndex="100000"
position="fixed"
top="0"
@ -63,7 +64,6 @@ const AppEnvHint = () => {
insetEnd="0"
h="2px"
bg="warning.400"
as="button"
cursor="pointer"
data-test-id="devtools"
onClick={dialog.onOpen}
@ -84,7 +84,7 @@ const AppEnvHint = () => {
>
{envName.join(' : ')}
</Text>
</Box>
</Box >
<DialogRoot open={dialog.open} onOpenChange={dialog.onClose}>
<DialogContent>
<DialogHeader>Outils développeurs</DialogHeader>

View File

@ -38,7 +38,14 @@ import { Button } from '../ui/themed';
export const TOP_BAR_HEIGHT = '50px';
export const BUTTON_TOP_BAR_PROPERTY = {
theme: '@menu',
bg: 'back.100',
color: 'brand.900',
borderRadius: 0,
border: 0,
_hover: { background: 'back.300' },
_focus: { border: 'none' },
fontSize: '20px',
textTransform: 'uppercase',
height: TOP_BAR_HEIGHT,
};
@ -143,7 +150,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<MenuRoot>
<MenuTrigger asChild>
<IconButton
as={IconButton}
asChild
aria-label="Options"
{...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT}

View File

@ -1,4 +1,4 @@
import { Flex, Text } from '@chakra-ui/react';
import { Flex, Span, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { Album } from '@/back-api';
@ -25,7 +25,7 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
data={dataAlbum?.covers}
size={BASE_WRAP_ICON_SIZE}
flex={1}
// TODO: iconEmpty={LuDisc3}
iconEmpty={<LuDisc3 />}
/>
<Flex
direction="column"
@ -36,8 +36,7 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
overflowX="hidden"
flex={1}
>
<Text
as="span"
<Span
// align="left"
fontSize="20px"
fontWeight="bold"
@ -47,9 +46,8 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
// noOfLines={[1, 2]}
>
{dataAlbum?.name}
</Text>
<Text
as="span"
</Span>
<Span
// align="left"
fontSize="15px"
userSelect="none"
@ -58,7 +56,7 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
// noOfLines={1}
>
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
</Text>
</Span>
</Flex>
</Flex>
);

View File

@ -1,12 +1,9 @@
import { useState } from 'react';
import {
IconButton,
} from '@chakra-ui/react';
import { LuMenu } from 'react-icons/lu';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu';
import { Button } from '../ui/themed';
import { customVariant } from '@/theme/recipes/button';
import { Button } from '../ui/button';
export type MenuElement = {
name: string;
@ -17,21 +14,6 @@ export type ContextMenuProps = {
elements?: MenuElement[];
};
const theme = {
plop: {
bg: { _light: 'red', _dark: 'brand.300', _pink: "orange" },
_hover: { bg: { _light: 'green', _dark: 'brand.400', _pink: "black" } },
// bg: { _light: 'brand.600', _dark: 'brand.300' },
// _hover: { bg: { _light: 'brand.700', _dark: 'brand.400' } },
/*
bgActive: { _light: 'brand.600', _dark: 'brand.300' },
color: { _light: 'white', _dark: 'brand.900' },
colorHover: { _light: 'brand.800', _dark: 'brand.100' },
boxShadowHover: 'outline-over'
*/
}
};
export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) {
return <></>;
@ -40,9 +22,11 @@ export const ContextMenu = ({ elements }: ContextMenuProps) => {
<MenuRoot
data-testid="context-menu">
<MenuTrigger asChild
marginY="auto"
marginRight="4px"
data-testid="context-menu_trigger">
{/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
<Button {...theme.plop} /*theme="@primary"*/>
<Button variant="ghost" color="brand.500">
<LuMenu />
</Button>
</MenuTrigger>

View File

@ -104,14 +104,13 @@ export const CenterIcon = ({
return (
<Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}>
<Box
as={IconEl}
w={sizeIcon}
h={sizeIcon}
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
/>
>{IconEl}</Box>
</Box>
);
};
@ -150,14 +149,13 @@ export const FormCovers = ({
<Box width="125px" height="125px" position="relative">
<Box width="125px" height="125px" position="absolute">
<CenterIcon
icon={MdHighlightOff}
width="125px"
sizeIcon="100%"
zIndex="+1"
color="#00000020"
_hover={{ color: 'red' }}
onClick={() => onRemove && onRemove(index)}
/>
><MdHighlightOff /></CenterIcon>
</Box>
<Image loading="lazy" src={data} boxSize="full" />
</Box>

View File

@ -25,7 +25,8 @@ import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksWithAlbumId } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import { Button } from '../ui/themed';
import { Button } from '../ui/button';
export type AlbumEditPopUpProps = {};
@ -168,7 +169,9 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
<Button
onClick={disclosure.onOpen}
marginRight="auto"
theme="@danger"
//theme="@danger"
variant="outline"
colorScheme=""
disabled={countTracksOfAnAlbum !== 0}
>
<MdDeleteForever /> Remove Media

View File

@ -4,6 +4,7 @@ import {
Flex,
Text,
useDisclosure,
Button,
} from '@chakra-ui/react';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog';
import {
@ -25,7 +26,7 @@ import { useArtistService, useSpecificArtist } from '@/service/Artist';
import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAnArtist } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
import { Button } from '../ui/themed';
//import { Button } from '../ui/themed';
export type ArtistEditPopUpProps = {};
@ -217,6 +218,7 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
<Button
onClick={() => setAdmin((value) => !value)}
marginRight="auto"
variant="@danger"
>
{admin ? (
<>

View File

@ -109,7 +109,7 @@ export const SelectMultiple = ({
return (
<Flex direction="column" width="full" gap="0px">
{selectedOptions && (
<HStack wrap="wrap" /*spacing="5px"*/ justify="left" width="full" marginBottom="2px">
<HStack wrap="wrap" gap="5px" justify="left" width="full" marginBottom="2px">
{selectedOptions.map((data) => (
<Flex align="flex-start" key={data[keyKey]}>
<Tag.Root

View File

@ -20,7 +20,7 @@ export const DisplayTrackSkeleton = () => {
{/* <SkeletonText
skeletonHeight="20px"
noOfLines={1}
spacing={0}
gap={0}
width="50%"
marginY="auto"
/> */}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@ import {
Box,
Collapsible,
useDisclosure,
Text,
HStack,
} from '@chakra-ui/react';
import {
FallbackProps,
@ -30,7 +32,10 @@ const ErrorFallback = ({ error }: FallbackProps) => {
//size="sm"
onClick={onToggle}
>
Show details {open ? <LuChevronUp /> : <LuChevronDown />}
<HStack>
<Text>Show details</Text>{' '}
{open ? <LuChevronUp /> : <LuChevronDown />}
</HStack>
</Button>
<Collapsible.Root open={open}>
<Collapsible.Content>

View File

@ -36,7 +36,7 @@ export const AlbumsPage = () => {
<SearchInput onChange={setFilterTitle} />
</TopBar>
<PageLayout>
<HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center">
<HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{dataAlbums.map((data) => (
<Flex align="flex-start"
width={BASE_WRAP_WIDTH}

View File

@ -126,7 +126,6 @@ export const ArtistAlbumDetailPage = () => {
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
key={data.id}
padding="5px"
as="button"
_hover={{
boxShadow: 'outline-over',
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),

View File

@ -88,7 +88,7 @@ export const ArtistDetailPage = () => {
</Flex>
</Flex>
<HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center">
<HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{albumIdsOfAnArtist?.map((data) => (
<Flex align="flex-start"
width={BASE_WRAP_WIDTH}

View File

@ -64,7 +64,7 @@ export const ArtistsPage = () => {
</Tooltip.Root>
</TopBar>
<PageLayout>
<HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center">
<HStack wrap="wrap" gap={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center">
{dataArtist?.map((data) => (
<Flex align="flex-start"
width={BASE_WRAP_WIDTH}

View File

@ -35,7 +35,7 @@ export const GendersPage = () => {
<SearchInput onChange={setFilterTitle} />
</TopBar>
<PageLayout>
<HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center">
<HStack wrap="wrap" gap="20px" marginX="auto" padding="20px" justify="center">
{dataGenders.map((data) => (
<Flex align="flex-start"
width="270px"

View File

@ -58,7 +58,7 @@ export const HomePage = () => {
<>
<TopBar title="Home" />
<PageLayout>
<HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center">
<HStack wrap="wrap" gap="20px" marginX="auto" padding="20px" justify="center">
{homeList.map((data) => (
<Flex align="flex-start"
width="200px"

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,21 @@
type PandaColorModel = {
value: string;
}
type ThemeModel = {
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
50: PandaColorModel;
100: PandaColorModel;
200: PandaColorModel;
300: PandaColorModel;
400: PandaColorModel;
500: PandaColorModel;
600: PandaColorModel;
700: PandaColorModel;
800: PandaColorModel;
900: PandaColorModel;
};
const back = {
const back: ThemeModel = {
50: { value: '#ebf4fa' },
100: { value: '#d1dbe0' },
200: { value: '#b6c2c9' },
@ -24,7 +28,7 @@ const back = {
900: { value: '#020f12' },
};
const brand = {
const brand: ThemeModel = {
50: { value: '#e3edff' },
100: { value: '#b6c9fd' },
200: { value: '#88a5f7' },
@ -36,7 +40,7 @@ const brand = {
800: { value: '#02164a' },
900: { value: '#00071e' },
};
const normalText = {
const normalText: ThemeModel = {
50: { value: '#f2f2f2' },
100: { value: '#d9d9d9' },
200: { value: '#bfbfbf' },
@ -49,7 +53,7 @@ const normalText = {
900: { value: '#0d0d0d' },
};
const green = {
const green: ThemeModel = {
50: { value: '#f0fdf4' },
100: { value: '#dcfce7' },
200: { value: '#bbf7d0' },
@ -61,7 +65,7 @@ const green = {
800: { value: '#166534' },
900: { value: '#14532d' },
};
const blue = {
const blue: ThemeModel = {
50: { value: '#eff6ff' },
100: { value: '#dbeafe' },
200: { value: '#bfdbfe' },
@ -74,7 +78,7 @@ const blue = {
900: { value: '#1e3a8a' },
};
const orange = {
const orange: ThemeModel = {
50: { value: '#fff7ed' },
100: { value: '#ffedd5' },
200: { value: '#fed7aa' },
@ -86,7 +90,7 @@ const orange = {
800: { value: '#9a3412' },
900: { value: '#7c2d12' },
};
const red = {
const red: ThemeModel = {
50: { value: '#fef2f2' },
100: { value: '#fee2e2' },
200: { value: '#fecaca' },

View File

@ -1,21 +1,23 @@
import { SystemConfig } from '@chakra-ui/react';
import { colors } from './colors';
const createOutline = (colorScheme = 'gray') =>
`0 0 0 3px ${colorScheme}.500/3`;
const createOutline = (colorScheme = 'gray') => {
return { value: `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)',
// '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)',
//'smooth-light': 'inset 0px 0px 16px rgba(255, 255, 255, 0.1)',
};

View File

@ -33,8 +33,12 @@ export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxSha
};
const buttonRecipe = defineRecipe({
base: {
borderRadius: 0,
background: "green",
},
variants: {
theme: {
variant: {
"@primary":
customVariant({
bg: { _light: 'brand.600', _dark: 'brand.300' },
@ -106,4 +110,8 @@ const buttonRecipe = defineRecipe({
},
});
console.log(`buttonRecipe: ${JSON.stringify(buttonRecipe, null, 2)}`);
export default buttonRecipe;

View File

@ -1,5 +1,5 @@
export { default as Badge } from './badge';
export { default as Button } from './button';
export { default as button } from './button';
export { default as Checkbox } from './checkbox';
export { default as Input } from './input';
//export { default as NumberInput } from './numberInput.ts_';

View File

@ -19,7 +19,7 @@ const Color = ({ children, ...rest }: FlexProps) => (
const Colors = ({ colorScheme = 'gray', ...rest }) => (
<HStack
// spacing="0"
gap="0"
overflow="hidden"
boxShadow="lg"
color={`${colorScheme}.700`}

View File

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

View File

@ -1,6 +1,8 @@
import * as recipes from './recipes';
import { createSystem, defaultConfig, mergeConfigs, SystemConfig } from "@chakra-ui/react"
import { colors } from "./foundations/colors"
import { shadows } from './foundations/shadows';
import buttonRecipe from './recipes/button';
const baseTheme: SystemConfig = {
globalCss: {
@ -17,28 +19,127 @@ const baseTheme: SystemConfig = {
}
},
theme: {
...recipes,
//recipes: {...recipes},
recipes: {
button: buttonRecipe,
buttonqsdqsd: {
base: {
"display": "inline-flex",
"appearance": "none",
"alignItems": "center",
"justifyContent": "center",
"userSelect": "none",
"position": "relative",
"borderRadius": "l2",
"whiteSpace": "nowrap",
"verticalAlign": "middle",
"borderWidth": "1px",
"borderColor": "transparent",
"cursor": "button",
"flexShrink": "0",
"outline": "0",
"lineHeight": "1.2",
"isolation": "isolate",
"fontWeight": "medium",
"transitionProperty": "common",
"transitionDuration": "moderate",
"focusVisibleRing": "outside",
"_disabled": {
"layerStyle": "disabled"
},
"_icon": {
"flexShrink": "0"
}
},
"variants": {
"variant": {
"solid": {
//"bg": "colorPalette.solid",
"bg": "brand.500",
"color": "colorPalette.contrast",
"_hover": {
"bg": "colorPalette.solid/90"
},
"_expanded": {
"bg": "colorPalette.solid/90"
}
},
"QSDQDS": {
},
"@primary": {
bg: {
_light: "brand.600",
_dark: "brand.300"
},
color: {
_light: "white",
_dark: "brand.900"
},
border: "1px solid transparent",
_focus: {
border: "1px solid",
borderColor: "black"
},
"_hover": {
"bg": {
"_light": "brand.700",
"_dark": "brand.400"
},
"color": {
"_light": "brand.800",
"_dark": "brand.100"
},
"boxShadow": "outline-over",
"_disabled": {
"bg": {
"_light": "brand.600",
"_dark": "brand.300"
},
"boxShadow": "none"
}
},
"_active": {
"bg": {
"_light": "brand.600",
"_dark": "brand.300"
}
}
},
}
},
"defaultVariants": {
//"size": "md",
//"variant": "solid"
}
},
},
tokens: {
fonts: {
heading: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
body: { value: `Roboto, Helvetica, Arial, "sans-serif"` },
},
colors,
// spacing: {
// vGutter: { value: '6.25rem' },
// },
shadows
},
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}" },
},
},
},
// 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);
//console.log("defaultConfig: " + JSON.stringify(defaultConfig, null, 2));
export const systemTheme = createSystem(config);