Change overall background color and menu

This commit is contained in:
David Corbitt
2023-08-12 02:31:52 -07:00
parent 2a80cbf74a
commit ce6936f753
3 changed files with 131 additions and 141 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import { import {
Heading, Heading,
VStack, VStack,
@@ -9,6 +9,7 @@ import {
Box, Box,
Link as ChakraLink, Link as ChakraLink,
Flex, Flex,
useBreakpointValue,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
@@ -16,7 +17,6 @@ import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs";
import { IoStatsChartOutline } from "react-icons/io5"; import { IoStatsChartOutline } from "react-icons/io5";
import { RiHome3Line, RiDatabase2Line, RiFlaskLine } from "react-icons/ri"; import { RiHome3Line, RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
import { signIn, useSession } from "next-auth/react"; import { signIn, useSession } from "next-auth/react";
import UserMenu from "./UserMenu";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
import ProjectMenu from "./ProjectMenu"; import ProjectMenu from "./ProjectMenu";
import NavSidebarOption from "./NavSidebarOption"; import NavSidebarOption from "./NavSidebarOption";
@@ -27,10 +27,16 @@ const Divider = () => <Box h="1px" bgColor="gray.300" w="full" />;
const NavSidebar = () => { const NavSidebar = () => {
const user = useSession().data; const user = useSession().data;
// Hack to get around initial flash, see https://github.com/chakra-ui/chakra-ui/issues/6452
const isMobile = useBreakpointValue({ base: true, md: false, ssr: false });
const renderCount = useRef(0);
renderCount.current++;
const displayLogo = isMobile && renderCount.current > 1;
return ( return (
<VStack <VStack
align="stretch" align="stretch"
bgColor="gray.50"
py={2} py={2}
px={2} px={2}
pb={0} pb={0}
@@ -40,6 +46,8 @@ const NavSidebar = () => {
borderRightWidth={1} borderRightWidth={1}
borderColor="gray.300" borderColor="gray.300"
> >
{displayLogo && (
<>
<HStack <HStack
as={Link} as={Link}
href="/" href="/"
@@ -54,6 +62,9 @@ const NavSidebar = () => {
</Heading> </Heading>
</HStack> </HStack>
<Divider /> <Divider />
</>
)}
<VStack align="flex-start" overflowY="auto" overflowX="hidden" flex={1}> <VStack align="flex-start" overflowY="auto" overflowX="hidden" flex={1}>
{user != null && ( {user != null && (
<> <>
@@ -75,6 +86,19 @@ const NavSidebar = () => {
{env.NEXT_PUBLIC_SHOW_DATA && ( {env.NEXT_PUBLIC_SHOW_DATA && (
<IconLink icon={RiDatabase2Line} label="Data" href="/data" /> <IconLink icon={RiDatabase2Line} label="Data" href="/data" />
)} )}
<VStack w="full" alignItems="flex-start" spacing={0} pt={8}>
<Text
pl={2}
pb={2}
fontSize="xs"
fontWeight="bold"
color="gray.500"
display={{ base: "none", md: "flex" }}
>
CONFIGURATION
</Text>
<IconLink icon={BsGearFill} label="Project Settings" href="/project/settings" />
</VStack>
</> </>
)} )}
{user === null && ( {user === null && (
@@ -96,20 +120,7 @@ const NavSidebar = () => {
</NavSidebarOption> </NavSidebarOption>
)} )}
</VStack> </VStack>
<VStack w="full" alignItems="flex-start" spacing={0}>
<Text
pl={2}
pb={2}
fontSize="xs"
fontWeight="bold"
color="gray.500"
display={{ base: "none", md: "flex" }}
>
CONFIGURATION
</Text>
<IconLink icon={BsGearFill} label="Project Settings" href="/project/settings" />
</VStack>
{user && <UserMenu user={user} borderColor={"gray.200"} />}
<Divider /> <Divider />
<VStack spacing={0} align="center"> <VStack spacing={0} align="center">
<ChakraLink <ChakraLink
@@ -169,7 +180,7 @@ export default function AppShell({
<title>{title ? `${title} | OpenPipe` : "OpenPipe"}</title> <title>{title ? `${title} | OpenPipe` : "OpenPipe"}</title>
</Head> </Head>
<NavSidebar /> <NavSidebar />
<Box h="100%" flex={1} overflowY="auto"> <Box h="100%" flex={1} overflowY="auto" bgColor="gray.50">
{children} {children}
</Box> </Box>
</Flex> </Flex>

View File

@@ -6,16 +6,19 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
Flex, Flex,
IconButton,
Icon, Icon,
Divider, Divider,
Button, Button,
useDisclosure, useDisclosure,
Spinner, Spinner,
Link as ChakraLink,
IconButton,
Image,
Box,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { BsChevronRight, BsGear, BsPlus } from "react-icons/bs"; import { BsGear, BsPlus, BsPersonCircle } from "react-icons/bs";
import { type Project } from "@prisma/client"; import { type Project } from "@prisma/client";
import { useAppStore } from "~/state/store"; import { useAppStore } from "~/state/store";
@@ -23,6 +26,7 @@ import { api } from "~/utils/api";
import NavSidebarOption from "./NavSidebarOption"; import NavSidebarOption from "./NavSidebarOption";
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks"; import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useSession, signOut } from "next-auth/react";
export default function ProjectMenu() { export default function ProjectMenu() {
const router = useRouter(); const router = useRouter();
@@ -49,28 +53,32 @@ export default function ProjectMenu() {
const createMutation = api.projects.create.useMutation(); const createMutation = api.projects.create.useMutation();
const [createProject, isLoading] = useHandledAsyncCallback(async () => { const [createProject, isLoading] = useHandledAsyncCallback(async () => {
const newProj = await createMutation.mutateAsync({ name: "New Project" }); const newProj = await createMutation.mutateAsync({ name: "Untitled Project" });
await utils.projects.list.invalidate(); await utils.projects.list.invalidate();
setselectedProjectId(newProj.id); setselectedProjectId(newProj.id);
await router.push({ pathname: "/project/settings" }); await router.push({ pathname: "/project/settings" });
}, [createMutation, router]); }, [createMutation, router]);
const user = useSession().data;
const profileImage = user?.user.image ? (
<Image src={user.user.image} alt="profile picture" boxSize={6} borderRadius="50%" />
) : (
<Icon as={BsPersonCircle} boxSize={6} />
);
return ( return (
<VStack w="full" alignItems="flex-start" spacing={0}> <VStack w="full" alignItems="flex-start" spacing={0} py={1}>
<Text <Popover
pl={2} placement="bottom"
pb={2} isOpen={popover.isOpen}
fontSize="xs" onOpen={popover.onOpen}
fontWeight="bold" onClose={popover.onClose}
color="gray.500" closeOnBlur
display={{ base: "none", md: "flex" }}
> >
PROJECT
</Text>
<Popover placement="right-end" isOpen={popover.isOpen} onClose={popover.onClose} closeOnBlur>
<PopoverTrigger> <PopoverTrigger>
<NavSidebarOption> <NavSidebarOption>
<HStack w="full" onClick={popover.onToggle}> <HStack w="full">
<Flex <Flex
p={1} p={1}
borderRadius={4} borderRadius={4}
@@ -83,20 +91,35 @@ export default function ProjectMenu() {
> >
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text> <Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
</Flex> </Flex>
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}> <Text
fontSize="sm"
display={{ base: "none", md: "block" }}
py={1}
flex={1}
fontWeight="bold"
>
{selectedProject?.name} {selectedProject?.name}
</Text> </Text>
<Icon as={BsChevronRight} boxSize={4} color="gray.500" /> <Box mr={2}>{profileImage}</Box>
</HStack> </HStack>
</NavSidebarOption> </NavSidebarOption>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent _focusVisible={{ outline: "unset" }} ml={-1} w="auto" minW={100} maxW={280}> <PopoverContent
<VStack alignItems="flex-start" spacing={2} py={4} px={2}> _focusVisible={{ outline: "unset" }}
<Text color="gray.500" fontSize="xs" fontWeight="bold" pb={1}> ml={-1}
PROJECTS w={224}
boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);"
fontSize="sm"
>
<VStack alignItems="flex-start" spacing={1} py={1}>
<Text px={3} py={2}>
{user?.user.email}
</Text> </Text>
<Divider /> <Divider />
<VStack spacing={0} w="full"> <Text alignSelf="flex-start" fontWeight="bold" px={3} pt={2}>
Your Projects
</Text>
<VStack spacing={0} w="full" px={1}>
{projects?.map((proj) => ( {projects?.map((proj) => (
<ProjectOption <ProjectOption
key={proj.id} key={proj.id}
@@ -105,20 +128,39 @@ export default function ProjectMenu() {
onClose={popover.onClose} onClose={popover.onClose}
/> />
))} ))}
</VStack>
<HStack <HStack
as={Button} as={Button}
variant="ghost" variant="ghost"
colorScheme="blue" colorScheme="blue"
color="blue.400" color="blue.400"
pr={8} fontSize="sm"
w="full" justifyContent="flex-start"
onClick={createProject} onClick={createProject}
w="full"
borderRadius={4}
spacing={0}
> >
<Icon as={isLoading ? Spinner : BsPlus} boxSize={6} /> <Text>Add project</Text>
<Text>New project</Text> <Icon as={isLoading ? Spinner : BsPlus} boxSize={4} strokeWidth={0.5} />
</HStack> </HStack>
</VStack> </VStack>
<Divider />
<VStack w="full" px={1}>
<ChakraLink
onClick={() => {
signOut().catch(console.error);
}}
_hover={{ bgColor: "gray.200", textDecoration: "none" }}
w="full"
py={2}
px={2}
borderRadius={4}
>
<Text>Sign out</Text>
</ChakraLink>
</VStack>
</VStack>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</VStack> </VStack>
@@ -136,6 +178,7 @@ const ProjectOption = ({
}) => { }) => {
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId); const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
const [gearHovered, setGearHovered] = useState(false); const [gearHovered, setGearHovered] = useState(false);
return ( return (
<HStack <HStack
as={Link} as={Link}
@@ -146,9 +189,10 @@ const ProjectOption = ({
}} }}
w="full" w="full"
justifyContent="space-between" justifyContent="space-between"
bgColor={isActive ? "gray.100" : "transparent"}
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }} _hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
p={2} color={isActive ? "blue.400" : undefined}
py={2}
px={4}
borderRadius={4} borderRadius={4}
spacing={4} spacing={4}
> >
@@ -157,7 +201,14 @@ const ProjectOption = ({
as={Link} as={Link}
href="/project/settings" href="/project/settings"
aria-label={`Open ${proj.name} settings`} aria-label={`Open ${proj.name} settings`}
icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />} icon={
<Icon
as={BsGear}
boxSize={5}
strokeWidth={0.5}
color={isActive ? "blue.400" : "gray.500"}
/>
}
variant="ghost" variant="ghost"
size="xs" size="xs"
p={0} p={0}

View File

@@ -1,72 +0,0 @@
import {
HStack,
Icon,
Image,
VStack,
Text,
Popover,
PopoverTrigger,
PopoverContent,
Link,
type StackProps,
} from "@chakra-ui/react";
import { type Session } from "next-auth";
import { signOut } from "next-auth/react";
import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs";
import NavSidebarOption from "./NavSidebarOption";
export default function UserMenu({ user, ...rest }: { user: Session } & StackProps) {
const profileImage = user.user.image ? (
<Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" />
) : (
<Icon as={BsPersonCircle} boxSize={6} />
);
return (
<>
<Popover placement="right">
<PopoverTrigger>
<NavSidebarOption>
<HStack
// Weird values to make mobile look right; can clean up when we make the sidebar disappear on mobile
py={2}
px={1}
spacing={3}
{...rest}
>
{profileImage}
<VStack spacing={0} align="start" flex={1} flexShrink={1}>
<Text fontWeight="bold" fontSize="sm">
{user.user.name}
</Text>
<Text color="gray.500" fontSize="xs">
{/* {user.user.email} */}
</Text>
</VStack>
<Icon as={BsChevronRight} boxSize={4} color="gray.500" />
</HStack>
</NavSidebarOption>
</PopoverTrigger>
<PopoverContent _focusVisible={{ outline: "unset" }} ml={-1} minW={48} w="full">
<VStack align="stretch" spacing={0}>
{/* sign out */}
<HStack
as={Link}
onClick={() => {
signOut().catch(console.error);
}}
px={4}
py={2}
spacing={4}
color="gray.500"
fontSize="sm"
>
<Icon as={BsBoxArrowRight} boxSize={6} />
<Text>Sign out</Text>
</HStack>
</VStack>
</PopoverContent>
</Popover>
</>
);
}