With amazing details screen on the sidebar now!
This commit is contained in:
parent
ff371aa855
commit
35da4f83ce
2003
package-lock.json
generated
2003
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -14,5 +14,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/input": "^2.1.2",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"framer-motion": "^12.9.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,14 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<script>
|
||||
// Insert this script in your index.html right after the <body> tag.
|
||||
// This will help to prevent a flash if dark mode is the default.
|
||||
(function() {
|
||||
document.documentElement.style.backgroundColor = '#171923';
|
||||
document.body.style.backgroundColor = '#171923';
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -10,8 +10,12 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/checkbox": "^2.3.2",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/input": "^2.1.2",
|
||||
"@chakra-ui/menu": "^2.2.1",
|
||||
"@chakra-ui/modal": "^2.3.1",
|
||||
"@chakra-ui/react": "^3.12.0",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@chakra-ui/transition": "^2.1.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
|
||||
@ -1,15 +1,69 @@
|
||||
import { Box, Button, Flex, Heading, Input, Spinner, Text } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { Box, Button, Flex, Heading, Input, Spinner, Text, useStyleConfig } from "@chakra-ui/react";
|
||||
import { useState, useRef } from "react";
|
||||
import { SongList } from "./components/SongList";
|
||||
import { PlaylistManager } from "./components/PlaylistManager";
|
||||
import { SongDetails } from "./components/SongDetails";
|
||||
import { useXmlParser } from "./hooks/useXmlParser";
|
||||
import { exportToXml } from "./services/xmlService";
|
||||
import { api } from "./services/api";
|
||||
import { Song } from "./types/interfaces";
|
||||
import "./App.css";
|
||||
|
||||
const StyledFileInput = () => {
|
||||
const { handleFileUpload } = useXmlParser();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleClick = () => {
|
||||
inputRef.current?.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
position="relative"
|
||||
width="auto"
|
||||
maxW="300px"
|
||||
>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".xml"
|
||||
onChange={handleFileUpload}
|
||||
height="40px"
|
||||
padding="8px 12px"
|
||||
opacity="0"
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
cursor="pointer"
|
||||
ref={inputRef}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
<Button
|
||||
width="100%"
|
||||
height="40px"
|
||||
bg="gray.700"
|
||||
color="gray.300"
|
||||
border="2px dashed"
|
||||
borderColor="gray.600"
|
||||
_hover={{
|
||||
bg: "gray.600",
|
||||
borderColor: "gray.500"
|
||||
}}
|
||||
_active={{
|
||||
bg: "gray.500"
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Choose XML File
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default function RekordboxReader() {
|
||||
const { songs, playlists, setPlaylists, handleFileUpload, loading } = useXmlParser();
|
||||
const { songs, playlists, setPlaylists, loading } = useXmlParser();
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>("All Songs");
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
|
||||
const handleCreatePlaylist = async (name: string) => {
|
||||
const newPlaylist = { name, tracks: [] };
|
||||
@ -18,10 +72,10 @@ export default function RekordboxReader() {
|
||||
setPlaylists(savedPlaylists);
|
||||
};
|
||||
|
||||
const handleAddSongToPlaylist = async (songId: string, playlistName: string) => {
|
||||
const handleAddSongsToPlaylist = async (songIds: string[], playlistName: string) => {
|
||||
const updatedPlaylists = playlists.map((playlist) =>
|
||||
playlist.name === playlistName
|
||||
? { ...playlist, tracks: [...playlist.tracks, songId] }
|
||||
? { ...playlist, tracks: [...new Set([...playlist.tracks, ...songIds])] }
|
||||
: playlist
|
||||
);
|
||||
const savedPlaylists = await api.savePlaylists(updatedPlaylists);
|
||||
@ -40,6 +94,10 @@ export default function RekordboxReader() {
|
||||
document.body.removeChild(a);
|
||||
};
|
||||
|
||||
const handleSongSelect = (song: Song) => {
|
||||
setSelectedSong(song);
|
||||
};
|
||||
|
||||
const displayedSongs = selectedItem === "All Songs"
|
||||
? songs
|
||||
: songs.filter((song) =>
|
||||
@ -57,20 +115,16 @@ export default function RekordboxReader() {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex direction="column" gap={2} mb={4}>
|
||||
<Heading size="md">Rekordbox Reader</Heading>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".xml"
|
||||
onChange={handleFileUpload}
|
||||
size="sm"
|
||||
width="auto"
|
||||
/>
|
||||
{songs.length > 0 && (
|
||||
<Button onClick={handleExport} size="sm" colorScheme="blue" width="auto">
|
||||
Export XML
|
||||
</Button>
|
||||
)}
|
||||
<Flex direction="column" gap={4} mb={6}>
|
||||
<Heading size="md" mb={2}>Rekordbox Reader</Heading>
|
||||
<Flex gap={4} align="center">
|
||||
<StyledFileInput />
|
||||
{songs.length > 0 && (
|
||||
<Button onClick={handleExport} size="sm" width="auto">
|
||||
Export XML
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={4} alignItems="start">
|
||||
<Box w="200px">
|
||||
@ -84,10 +138,13 @@ export default function RekordboxReader() {
|
||||
<Box flex={1}>
|
||||
<SongList
|
||||
songs={displayedSongs}
|
||||
onAddToPlaylist={handleAddSongToPlaylist}
|
||||
onAddToPlaylist={handleAddSongsToPlaylist}
|
||||
playlists={playlists}
|
||||
onSongSelect={handleSongSelect}
|
||||
selectedSongId={selectedSong?.id || null}
|
||||
/>
|
||||
</Box>
|
||||
<SongDetails song={selectedSong} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
|
||||
119
packages/frontend/src/components/SongDetails.tsx
Normal file
119
packages/frontend/src/components/SongDetails.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import { Box, VStack, Text, Divider } from "@chakra-ui/react";
|
||||
import { Song } from "../types/interfaces";
|
||||
|
||||
interface SongDetailsProps {
|
||||
song: Song | null;
|
||||
}
|
||||
|
||||
export const SongDetails: React.FC<SongDetailsProps> = ({ song }) => {
|
||||
if (!song) {
|
||||
return (
|
||||
<Box
|
||||
w="300px"
|
||||
position="sticky"
|
||||
top={4}
|
||||
h="calc(100vh - 2rem)"
|
||||
bg="gray.800"
|
||||
p={4}
|
||||
borderRadius="md"
|
||||
borderLeft="1px"
|
||||
borderColor="gray.700"
|
||||
>
|
||||
<Text color="gray.500">Select a song to view details</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const details = [
|
||||
{ label: "Title", value: song.title },
|
||||
{ label: "Artist", value: song.artist },
|
||||
{ label: "Album", value: song.album },
|
||||
{ label: "Genre", value: song.genre },
|
||||
{ label: "BPM", value: song.averageBpm },
|
||||
{ label: "Key", value: song.tonality },
|
||||
{ label: "Year", value: song.year },
|
||||
{ label: "Label", value: song.label },
|
||||
{ label: "Mix", value: song.mix },
|
||||
{ label: "Rating", value: song.rating },
|
||||
{ label: "Comments", value: song.comments },
|
||||
].filter(detail => detail.value); // Only show fields that have values
|
||||
|
||||
return (
|
||||
<Box
|
||||
w="300px"
|
||||
position="sticky"
|
||||
top={4}
|
||||
h="calc(100vh - 2rem)"
|
||||
bg="gray.800"
|
||||
borderRadius="md"
|
||||
borderLeft="1px"
|
||||
borderColor="gray.700"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Box p={4} flex="1" overflowY="auto" css={{
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: 'transparent',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: 'var(--chakra-colors-gray-600)',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: 'var(--chakra-colors-gray-500)',
|
||||
},
|
||||
}}>
|
||||
<VStack align="stretch" spacing={4}>
|
||||
<Box>
|
||||
<Text fontSize="lg" fontWeight="bold" color="white">
|
||||
{song.title}
|
||||
</Text>
|
||||
<Text fontSize="md" color="gray.400">
|
||||
{song.artist}
|
||||
</Text>
|
||||
</Box>
|
||||
<Divider borderColor="gray.700" />
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{details.map(({ label, value }) => (
|
||||
<Box key={label}>
|
||||
<Text fontSize="xs" color="gray.500" mb={1}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.300">
|
||||
{value}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
{song.tempo && (
|
||||
<>
|
||||
<Divider borderColor="gray.700" />
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.500" mb={2}>
|
||||
Tempo Details
|
||||
</Text>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.500">BPM</Text>
|
||||
<Text fontSize="sm" color="gray.300">{song.tempo.bpm}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.500">Beat</Text>
|
||||
<Text fontSize="sm" color="gray.300">{song.tempo.battito}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fontSize="xs" color="gray.500">Time Signature</Text>
|
||||
<Text fontSize="sm" color="gray.300">{song.tempo.metro}</Text>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,43 +1,204 @@
|
||||
import { Box, Flex, Text } from "@chakra-ui/react";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Text,
|
||||
Button,
|
||||
IconButton,
|
||||
HStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { Menu, MenuButton, MenuList, MenuItem } from "@chakra-ui/menu";
|
||||
import { Checkbox } from "@chakra-ui/checkbox";
|
||||
import { Input, InputGroup, InputLeftElement } from "@chakra-ui/input";
|
||||
import { Search2Icon } from "@chakra-ui/icons";
|
||||
import { Song } from "../types/interfaces";
|
||||
import { ChangeEvent } from "react";
|
||||
import { useState, useCallback, useMemo, forwardRef } from "react";
|
||||
import { ChangeEvent, MouseEvent } from "react";
|
||||
|
||||
interface SongListProps {
|
||||
songs: Song[];
|
||||
onAddToPlaylist: (songId: string, playlistName: string) => void;
|
||||
onAddToPlaylist: (songIds: string[], playlistName: string) => void;
|
||||
playlists: { name: string }[];
|
||||
onSongSelect: (song: Song) => void;
|
||||
selectedSongId: string | null;
|
||||
}
|
||||
|
||||
export const SongList: React.FC<SongListProps> = ({ songs, onAddToPlaylist, playlists }) => {
|
||||
export const SongList: React.FC<SongListProps> = ({
|
||||
songs,
|
||||
onAddToPlaylist,
|
||||
playlists,
|
||||
onSongSelect,
|
||||
selectedSongId
|
||||
}) => {
|
||||
const [selectedSongs, setSelectedSongs] = useState<Set<string>>(new Set());
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const filteredSongs = useMemo(() => {
|
||||
if (!searchQuery) return songs;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return songs.filter(
|
||||
song =>
|
||||
song.title.toLowerCase().includes(query) ||
|
||||
song.artist.toLowerCase().includes(query)
|
||||
);
|
||||
}, [songs, searchQuery]);
|
||||
|
||||
const toggleSelection = useCallback((songId: string) => {
|
||||
setSelectedSongs(prev => {
|
||||
const newSelection = new Set(prev);
|
||||
if (newSelection.has(songId)) {
|
||||
newSelection.delete(songId);
|
||||
} else {
|
||||
newSelection.add(songId);
|
||||
}
|
||||
return newSelection;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleSelectAll = useCallback(() => {
|
||||
setSelectedSongs(prev =>
|
||||
prev.size === songs.length ? new Set() : new Set(songs.map(s => s.id))
|
||||
);
|
||||
}, [songs]);
|
||||
|
||||
const handleBulkAddToPlaylist = (playlistName: string) => {
|
||||
if (selectedSongs.size > 0) {
|
||||
onAddToPlaylist(Array.from(selectedSongs), playlistName);
|
||||
setSelectedSongs(new Set()); // Clear selection after action
|
||||
}
|
||||
};
|
||||
|
||||
const handleSongClick = (e: MouseEvent, song: Song) => {
|
||||
e.stopPropagation();
|
||||
onSongSelect(song);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={2}>
|
||||
{songs.map((song) => (
|
||||
<Box key={song.id} borderWidth="1px" borderRadius="md" p={2} shadow="sm">
|
||||
<Flex justify="space-between" align="center">
|
||||
<Box>
|
||||
<Text fontWeight="bold" fontSize="sm">{song.title}</Text>
|
||||
<Text color="gray.500" fontSize="xs">{song.artist}</Text>
|
||||
</Box>
|
||||
<select
|
||||
onChange={(e: ChangeEvent<HTMLSelectElement>) => onAddToPlaylist(song.id, e.target.value)}
|
||||
value=""
|
||||
style={{
|
||||
padding: '0.25rem',
|
||||
fontSize: '0.875rem',
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid #E2E8F0'
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>Add to playlist</option>
|
||||
<Box>
|
||||
{/* Search Bar */}
|
||||
<InputGroup mb={4}>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<Search2Icon color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder="Search songs by title or artist..."
|
||||
value={searchQuery}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
|
||||
bg="gray.800"
|
||||
borderColor="gray.600"
|
||||
_hover={{ borderColor: "gray.500" }}
|
||||
_focus={{ borderColor: "blue.300", boxShadow: "0 0 0 1px var(--chakra-colors-blue-300)" }}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* Bulk Actions Toolbar */}
|
||||
<Flex justify="space-between" align="center" mb={4} p={2} bg="gray.800" borderRadius="md">
|
||||
<HStack>
|
||||
<Checkbox
|
||||
isChecked={selectedSongs.size === filteredSongs.length}
|
||||
isIndeterminate={selectedSongs.size > 0 && selectedSongs.size < filteredSongs.length}
|
||||
onChange={toggleSelectAll}
|
||||
colorScheme="blue"
|
||||
sx={{
|
||||
'& > span:first-of-type': {
|
||||
opacity: 1,
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.500'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedSongs.size === 0
|
||||
? "Select All"
|
||||
: `Selected ${selectedSongs.size} song${selectedSongs.size === 1 ? '' : 's'}`}
|
||||
</Checkbox>
|
||||
</HStack>
|
||||
|
||||
{selectedSongs.size > 0 && (
|
||||
<Menu>
|
||||
<MenuButton as={Button} colorScheme="blue" size="sm">
|
||||
Add to Playlist
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{playlists.map((playlist) => (
|
||||
<option key={playlist.name} value={playlist.name}>
|
||||
<MenuItem
|
||||
key={playlist.name}
|
||||
value={playlist.name}
|
||||
onClick={() => handleBulkAddToPlaylist(playlist.name)}
|
||||
>
|
||||
{playlist.name}
|
||||
</option>
|
||||
</MenuItem>
|
||||
))}
|
||||
</select>
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{/* Song List */}
|
||||
<Flex direction="column" gap={2}>
|
||||
{filteredSongs.map((song) => (
|
||||
<Box
|
||||
key={song.id}
|
||||
borderWidth="1px"
|
||||
borderRadius="md"
|
||||
p={2}
|
||||
shadow="sm"
|
||||
bg={selectedSongId === song.id ? "blue.800" : selectedSongs.has(song.id) ? "gray.700" : "transparent"}
|
||||
_hover={{ bg: selectedSongId === song.id ? "blue.700" : selectedSongs.has(song.id) ? "gray.700" : "gray.900" }}
|
||||
transition="background-color 0.2s"
|
||||
cursor="pointer"
|
||||
onClick={(e: MouseEvent) => handleSongClick(e, song)}
|
||||
>
|
||||
<Flex justify="space-between" align="center">
|
||||
<HStack>
|
||||
<Checkbox
|
||||
isChecked={selectedSongs.has(song.id)}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
toggleSelection(song.id);
|
||||
}}
|
||||
colorScheme="blue"
|
||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||
sx={{
|
||||
'& > span:first-of-type': {
|
||||
opacity: 1,
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.500'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fontWeight="bold" fontSize="sm">{song.title}</Text>
|
||||
<Text color="gray.500" fontSize="xs">{song.artist}</Text>
|
||||
</Box>
|
||||
</HStack>
|
||||
|
||||
{!selectedSongs.has(song.id) && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Add to playlist"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
•••
|
||||
</MenuButton>
|
||||
<MenuList onClick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
{playlists.map((playlist) => (
|
||||
<MenuItem
|
||||
key={playlist.name}
|
||||
value={playlist.name}
|
||||
onClick={() => onAddToPlaylist([song.id], playlist.name)}
|
||||
>
|
||||
{playlist.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,12 +1,108 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
|
||||
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
|
||||
import './index.css';
|
||||
import App from './App.tsx';
|
||||
|
||||
const theme = extendTheme({
|
||||
config: {
|
||||
initialColorMode: 'dark',
|
||||
useSystemColorMode: false,
|
||||
},
|
||||
styles: {
|
||||
global: {
|
||||
body: {
|
||||
bg: 'gray.900',
|
||||
color: 'white'
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
gray: {
|
||||
700: '#2D3748',
|
||||
800: '#1A202C',
|
||||
900: '#171923',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
defaultProps: {
|
||||
colorScheme: 'gray',
|
||||
variant: 'outline',
|
||||
},
|
||||
variants: {
|
||||
solid: {
|
||||
bg: 'gray.700',
|
||||
color: 'white',
|
||||
_hover: {
|
||||
bg: 'gray.600',
|
||||
},
|
||||
_active: {
|
||||
bg: 'gray.500',
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
borderColor: 'gray.600',
|
||||
color: 'gray.300',
|
||||
_hover: {
|
||||
bg: 'gray.700',
|
||||
},
|
||||
_active: {
|
||||
bg: 'gray.600',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
IconButton: {
|
||||
defaultProps: {
|
||||
colorScheme: 'gray',
|
||||
variant: 'outline',
|
||||
},
|
||||
variants: {
|
||||
ghost: {
|
||||
color: 'gray.400',
|
||||
_hover: {
|
||||
bg: 'gray.700',
|
||||
},
|
||||
_active: {
|
||||
bg: 'gray.600',
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
borderColor: 'gray.600',
|
||||
color: 'gray.300',
|
||||
_hover: {
|
||||
bg: 'gray.700',
|
||||
},
|
||||
_active: {
|
||||
bg: 'gray.600',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Menu: {
|
||||
baseStyle: {
|
||||
list: {
|
||||
bg: 'gray.800',
|
||||
borderColor: 'gray.600',
|
||||
},
|
||||
item: {
|
||||
bg: 'gray.800',
|
||||
_hover: {
|
||||
bg: 'gray.700',
|
||||
},
|
||||
_focus: {
|
||||
bg: 'gray.700',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ChakraProvider value={defaultSystem}>
|
||||
<ChakraProvider theme={theme}>
|
||||
<App />
|
||||
</ChakraProvider>
|
||||
</StrictMode>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user