UI changes
* Remove side bar, add "about" drawer * Move sidebar elements to title bar * Make connection status a spinner with a tooltip * Move user list to popover * Change username to be inline editable element * Make dark mode toggle an icon in the titlebar * Make document title copy link on click * Add confirmation box for loading Rustpad code
This commit is contained in:
parent
124ff15d3d
commit
06344f9759
3 changed files with 415 additions and 232 deletions
421
src/App.tsx
421
src/App.tsx
|
@ -1,39 +1,42 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Flex,
|
||||
Button, Container, Drawer, DrawerBody,
|
||||
DrawerCloseButton, DrawerContent, DrawerHeader, DrawerOverlay, Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Icon,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Link,
|
||||
Select,
|
||||
Stack,
|
||||
Switch,
|
||||
Text,
|
||||
useToast,
|
||||
Icon, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, Link,
|
||||
Popover, PopoverCloseButton,
|
||||
PopoverContent, PopoverTrigger,
|
||||
Select, Spacer, Text, Tooltip, useDisclosure, useToast
|
||||
} from "@chakra-ui/react";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { HiUserGroup, HiUser } from "react-icons/hi";
|
||||
import { FaMoon, FaSun } from "react-icons/fa";
|
||||
import {
|
||||
VscChevronRight,
|
||||
VscFolderOpened,
|
||||
VscGist,
|
||||
VscRepoPull,
|
||||
VscLink, VscRepoPull
|
||||
} from "react-icons/vsc";
|
||||
import useStorage from "use-local-storage-state";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import rustpadRaw from "../rustpad-server/src/rustpad.rs?raw";
|
||||
import languages from "./languages.json";
|
||||
import animals from "./animals.json";
|
||||
import Rustpad, { UserInfo } from "./rustpad";
|
||||
import useHash from "./useHash";
|
||||
import ConnectionStatus from "./ConnectionStatus";
|
||||
import Footer from "./Footer";
|
||||
import User from "./User";
|
||||
import languages from "./languages.json";
|
||||
import Rustpad, { UserInfo } from "./rustpad";
|
||||
import useHash from "./useHash";
|
||||
import UserList from "./User";
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
||||
function getWsUri(id: string) {
|
||||
return (
|
||||
|
@ -125,6 +128,8 @@ function App() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function handleCopy() {
|
||||
await navigator.clipboard.writeText(`${window.location.origin}/#${id}`);
|
||||
toast({
|
||||
|
@ -136,7 +141,13 @@ function App() {
|
|||
});
|
||||
}
|
||||
|
||||
function handleLoadSample() {
|
||||
|
||||
function handleDarkMode() {
|
||||
setDarkMode(!darkMode);
|
||||
}
|
||||
|
||||
|
||||
const handleLoadSample = () => {
|
||||
if (editor?.getModel()) {
|
||||
const model = editor.getModel()!;
|
||||
model.pushEditOperations(
|
||||
|
@ -156,9 +167,9 @@ function App() {
|
|||
}
|
||||
}
|
||||
|
||||
function handleDarkMode() {
|
||||
setDarkMode(!darkMode);
|
||||
}
|
||||
|
||||
const aboutDrawer = useDisclosure()
|
||||
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
@ -168,59 +179,201 @@ function App() {
|
|||
bgColor={darkMode ? "#1e1e1e" : "white"}
|
||||
color={darkMode ? "#cbcaca" : "inherit"}
|
||||
>
|
||||
|
||||
<Box
|
||||
flexShrink={0}
|
||||
flexShrink={2}
|
||||
bgColor={darkMode ? "#333333" : "#e8e8e8"}
|
||||
color={darkMode ? "#cccccc" : "#383838"}
|
||||
textAlign="center"
|
||||
fontSize="sm"
|
||||
py={0.5}
|
||||
>
|
||||
Rustpad
|
||||
</Box>
|
||||
<Flex flex="1 0" minH={0}>
|
||||
<Container
|
||||
w="xs"
|
||||
bgColor={darkMode ? "#252526" : "#f3f3f3"}
|
||||
overflowY="auto"
|
||||
maxW="full"
|
||||
lineHeight={1.4}
|
||||
py={4}
|
||||
>
|
||||
<Flex spacing={0} px={2} alignItems="center">
|
||||
<ConnectionStatus darkMode={darkMode} connection={connection} />
|
||||
|
||||
<Flex justifyContent="space-between" mt={4} mb={1.5} w="full">
|
||||
<Heading size="sm">Dark Mode</Heading>
|
||||
<Switch isChecked={darkMode} onChange={handleDarkMode} />
|
||||
</Flex>
|
||||
|
||||
<Heading mt={4} mb={1.5} size="sm">
|
||||
Language
|
||||
</Heading>
|
||||
<Select
|
||||
size="sm"
|
||||
bgColor={darkMode ? "#3c3c3c" : "white"}
|
||||
borderColor={darkMode ? "#3c3c3c" : "white"}
|
||||
value={language}
|
||||
onChange={(event) => handleChangeLanguage(event.target.value)}
|
||||
<Button
|
||||
h="1.4rem"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
_hover={{ bg: darkMode ? "#575759" : "gray.200" }}
|
||||
bgColor={darkMode ? "#333333" : "gray.200"}
|
||||
px={2}
|
||||
onClick={aboutDrawer.onOpen}
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<option key={lang} value={lang} style={{ color: "black" }}>
|
||||
{lang}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
Rustpad
|
||||
</Button>
|
||||
|
||||
<Tooltip label="Syntax highlighting">
|
||||
<Select
|
||||
maxWidth="10em"
|
||||
size="xs"
|
||||
bgColor={darkMode ? "#3c3c3c" : "white"}
|
||||
_hover={{ bgColor: darkMode ? "#575757" : "dddddd" }}
|
||||
borderColor={darkMode ? "#3c3c3c" : "white"}
|
||||
value={language}
|
||||
onChange={(event) => handleChangeLanguage(event.target.value)}
|
||||
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<option key={lang} value={lang} style={{ color: "black" }}>
|
||||
{lang}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Tooltip>
|
||||
<Spacer />
|
||||
<Users
|
||||
users={users}
|
||||
darkMode={darkMode}
|
||||
me={{ name, hue }}
|
||||
setName={setName}
|
||||
setHue={setHue}
|
||||
id={id}
|
||||
handleCopy={handleCopy}
|
||||
/>
|
||||
<DarkModeToggle darkMode={darkMode} toggle={handleDarkMode} />
|
||||
</Flex>
|
||||
</Box >
|
||||
<Flex flex="1 0" minH={0}>
|
||||
|
||||
<Drawer
|
||||
isOpen={aboutDrawer.isOpen}
|
||||
onClose={aboutDrawer.onClose}
|
||||
placement="left"
|
||||
>
|
||||
<AboutDrawer loadSample={handleLoadSample} darkMode={darkMode} />
|
||||
</Drawer>
|
||||
<Flex flex={1} minW={0} h="100%" direction="column" overflow="hidden">
|
||||
<HStack
|
||||
h={6}
|
||||
spacing={1}
|
||||
color="#888888"
|
||||
fontWeight="medium"
|
||||
fontSize="13px"
|
||||
px={3.5}
|
||||
flexShrink={0}
|
||||
>
|
||||
<Icon as={VscFolderOpened} fontSize="md" color="blue.500" />
|
||||
<Text>rustpad</Text>
|
||||
<Icon as={VscChevronRight} fontSize="md" />
|
||||
<Link onClick={handleCopy} color="#bbbbbb" _hover={{ color: "#ffffff" }} >
|
||||
<HStack spacing={1} >
|
||||
<Icon as={VscGist} fontSize="md" color="purple.500" />
|
||||
<Text>{id}</Text>
|
||||
<Icon as={VscLink} fontSize="md" color="grey.500" />
|
||||
</HStack>
|
||||
</Link>
|
||||
</HStack>
|
||||
<Box flex={1} minH={0}>
|
||||
<Editor
|
||||
theme={darkMode ? "vs-dark" : "vs"}
|
||||
language={language}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
fontSize: 13,
|
||||
}}
|
||||
onMount={(editor) => setEditor(editor)}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex >
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
type DarkModeProps = {
|
||||
darkMode: boolean;
|
||||
toggle: () => unknown;
|
||||
}
|
||||
|
||||
function DarkModeToggle({ darkMode, toggle }: DarkModeProps) {
|
||||
return (
|
||||
<Tooltip label="Toggle dark mode">
|
||||
<IconButton
|
||||
aria-label="Toggle dark mode"
|
||||
size="xs"
|
||||
rounded="none"
|
||||
onClick={toggle}
|
||||
bgColor={darkMode ? "#333333" : "#e8e8e8"}
|
||||
_hover={{ bg: darkMode ? "#575757" : "gray.200" }}
|
||||
icon={darkMode ? <FaSun /> : <FaMoon />}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
type UsersProps = {
|
||||
users: Record<number, UserInfo>;
|
||||
darkMode: boolean;
|
||||
me: UserInfo;
|
||||
setName: (name: string) => unknown;
|
||||
setHue: (hue: number) => unknown;
|
||||
id: string;
|
||||
handleCopy: () => unknown;
|
||||
};
|
||||
|
||||
|
||||
function Users({ users, darkMode, me, setName, setHue, id, handleCopy }: UsersProps) {
|
||||
const [usersIsOpen, setUsersIsOpen] = useState(false)
|
||||
const open = () => setUsersIsOpen(!usersIsOpen)
|
||||
const close = () => setUsersIsOpen(false)
|
||||
const userCount = () => Object.entries(users).length
|
||||
|
||||
return (
|
||||
<Popover
|
||||
isOpen={usersIsOpen}
|
||||
onClose={close}
|
||||
placement='bottom'
|
||||
closeOnBlur={false}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button size="xs"
|
||||
onClick={open}
|
||||
rounded="none"
|
||||
bgColor={
|
||||
usersIsOpen ?
|
||||
(darkMode ? "#444444" : "#dddddd") :
|
||||
(darkMode ? "#333333" : "#e8e8e8")
|
||||
}
|
||||
_hover={{}} >
|
||||
<Icon as={userCount() > 0 ? HiUserGroup : HiUser} />
|
||||
<Text px={1}>{userCount() > 0 ?
|
||||
(userCount() + " other editor" + (userCount() > 1 ? "s" : "")) :
|
||||
"editing alone"}</Text>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
borderColor={darkMode ? "#222222" : "#999999"}
|
||||
bgColor={darkMode ? "#333333" : "#e8e8e8"}
|
||||
color={darkMode ? "#cbcaca" : "inherit"}
|
||||
paddingBottom={5}
|
||||
>
|
||||
<PopoverCloseButton />
|
||||
<Container>
|
||||
<Heading mt={4} mb={1.5} size="sm">
|
||||
Active Users
|
||||
</Heading>
|
||||
<UserList
|
||||
users={users}
|
||||
me={me}
|
||||
onChangeName={(name) => name.length > 0 && setName(name)}
|
||||
onChangeColor={() => setHue(generateHue())}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
|
||||
<Heading mt={4} mb={1.5} size="sm">
|
||||
Share Link
|
||||
</Heading>
|
||||
<InputGroup size="sm">
|
||||
<InputLeftElement>
|
||||
<Icon as={VscLink} color="grey.500" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
readOnly
|
||||
pr="3.5rem"
|
||||
variant="outline"
|
||||
bgColor={darkMode ? "#3c3c3c" : "white"}
|
||||
borderColor={darkMode ? "#3c3c3c" : "white"}
|
||||
variant="flushed"
|
||||
value={`${window.location.origin}/#${id}`}
|
||||
/>
|
||||
<InputRightElement width="3.5rem">
|
||||
|
@ -235,26 +388,31 @@ function App() {
|
|||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</Container>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
<Heading mt={4} mb={1.5} size="sm">
|
||||
Active Users
|
||||
</Heading>
|
||||
<Stack spacing={0} mb={1.5} fontSize="sm">
|
||||
<User
|
||||
info={{ name, hue }}
|
||||
isMe
|
||||
onChangeName={(name) => name.length > 0 && setName(name)}
|
||||
onChangeColor={() => setHue(generateHue())}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
{Object.entries(users).map(([id, info]) => (
|
||||
<User key={id} info={info} darkMode={darkMode} />
|
||||
))}
|
||||
</Stack>
|
||||
type AboutBoxProps = {
|
||||
loadSample: () => unknown;
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
|
||||
function AboutDrawer({ darkMode, loadSample }: AboutBoxProps) {
|
||||
return (
|
||||
<>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent
|
||||
bgColor={darkMode ? "#1e1e1e" : "white"}
|
||||
color={darkMode ? "#cbcaca" : "inherit"}
|
||||
>
|
||||
<DrawerCloseButton />
|
||||
<DrawerHeader>About <Link href="/">Rustpad</Link></DrawerHeader>
|
||||
|
||||
<DrawerBody>
|
||||
|
||||
<Heading mt={4} mb={1.5} size="sm">
|
||||
About
|
||||
</Heading>
|
||||
<Text fontSize="sm" mb={1.5}>
|
||||
<strong>Rustpad</strong> is an open-source collaborative text editor
|
||||
based on the <em>operational transformation</em> algorithm.
|
||||
|
@ -276,51 +434,64 @@ function App() {
|
|||
for details.
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme={darkMode ? "whiteAlpha" : "blackAlpha"}
|
||||
borderColor={darkMode ? "purple.400" : "purple.600"}
|
||||
color={darkMode ? "purple.400" : "purple.600"}
|
||||
variant="outline"
|
||||
leftIcon={<VscRepoPull />}
|
||||
mt={1}
|
||||
onClick={handleLoadSample}
|
||||
>
|
||||
Read the code
|
||||
</Button>
|
||||
</Container>
|
||||
<Flex flex={1} minW={0} h="100%" direction="column" overflow="hidden">
|
||||
<HStack
|
||||
h={6}
|
||||
spacing={1}
|
||||
color="#888888"
|
||||
fontWeight="medium"
|
||||
fontSize="13px"
|
||||
px={3.5}
|
||||
flexShrink={0}
|
||||
>
|
||||
<Icon as={VscFolderOpened} fontSize="md" color="blue.500" />
|
||||
<Text>documents</Text>
|
||||
<Icon as={VscChevronRight} fontSize="md" />
|
||||
<Icon as={VscGist} fontSize="md" color="purple.500" />
|
||||
<Text>{id}</Text>
|
||||
</HStack>
|
||||
<Box flex={1} minH={0}>
|
||||
<Editor
|
||||
theme={darkMode ? "vs-dark" : "vs"}
|
||||
language={language}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
fontSize: 13,
|
||||
}}
|
||||
onMount={(editor) => setEditor(editor)}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex>
|
||||
);
|
||||
<LoadSampleButton darkMode={darkMode} loadSample={loadSample} />
|
||||
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
function LoadSampleButton({ darkMode, loadSample }: AboutBoxProps) {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const cancelRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme={darkMode ? "whiteAlpha" : "blackAlpha"}
|
||||
borderColor={darkMode ? "purple.400" : "purple.600"}
|
||||
color={darkMode ? "purple.400" : "purple.600"}
|
||||
variant="outline"
|
||||
leftIcon={<VscRepoPull />}
|
||||
mt={1}
|
||||
onClick={onOpen}
|
||||
>
|
||||
Read the code
|
||||
</Button>
|
||||
|
||||
<AlertDialog
|
||||
isOpen={isOpen}
|
||||
leastDestructiveRef={cancelRef}
|
||||
onClose={onClose}
|
||||
>
|
||||
<AlertDialogOverlay>
|
||||
<AlertDialogContent
|
||||
|
||||
bgColor={darkMode ? "#333333" : "#e8e8e8"}
|
||||
color={darkMode ? "#cccccc" : "#383838"}
|
||||
>
|
||||
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
|
||||
Load Rustpad code
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>
|
||||
Are you sure? This will overwrite the current session.
|
||||
</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
colorScheme={darkMode ? "whiteAlpha" : "blackAlpha"} ref={cancelRef} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button colorScheme='red' onClick={() => { loadSample(); onClose() }} ml={3}>
|
||||
Yes
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { HStack, Icon, Text } from "@chakra-ui/react";
|
||||
import { VscCircleFilled } from "react-icons/vsc";
|
||||
import { Tooltip, Spinner } from "@chakra-ui/react";
|
||||
|
||||
type ConnectionStatusProps = {
|
||||
connection: "connected" | "disconnected" | "desynchronized";
|
||||
|
@ -8,9 +7,16 @@ type ConnectionStatusProps = {
|
|||
|
||||
function ConnectionStatus({ connection, darkMode }: ConnectionStatusProps) {
|
||||
return (
|
||||
<HStack spacing={1}>
|
||||
<Icon
|
||||
as={VscCircleFilled}
|
||||
<Tooltip label={
|
||||
{
|
||||
connected: "Connected",
|
||||
disconnected: "Connecting...",
|
||||
desynchronized: "Disconnected, please refresh",
|
||||
}[connection]
|
||||
}>
|
||||
<Spinner
|
||||
marginRight={2}
|
||||
size="xs"
|
||||
color={
|
||||
{
|
||||
connected: "green.500",
|
||||
|
@ -18,21 +24,17 @@ function ConnectionStatus({ connection, darkMode }: ConnectionStatusProps) {
|
|||
desynchronized: "red.500",
|
||||
}[connection]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontStyle="italic"
|
||||
color={darkMode ? "gray.300" : "gray.600"}
|
||||
>
|
||||
{
|
||||
bgColor={
|
||||
{
|
||||
connected: "You are connected!",
|
||||
disconnected: "Connecting to the server...",
|
||||
desynchronized: "Disconnected, please refresh.",
|
||||
connected: "green.500",
|
||||
disconnected: "",
|
||||
desynchronized: "",
|
||||
}[connection]
|
||||
}
|
||||
</Text>
|
||||
</HStack>
|
||||
emptyColor={connection == "disconnected" ? 'transparent' : ''}
|
||||
speed={connection == "disconnected" ? '0.5s' : '0s'}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
190
src/User.tsx
190
src/User.tsx
|
@ -1,112 +1,122 @@
|
|||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Editable,
|
||||
EditablePreview,
|
||||
EditableInput,
|
||||
HStack,
|
||||
Icon,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverCloseButton,
|
||||
PopoverContent,
|
||||
PopoverFooter,
|
||||
PopoverHeader,
|
||||
PopoverTrigger,
|
||||
Spacer,
|
||||
Stack,
|
||||
Text,
|
||||
useDisclosure,
|
||||
useEditableControls,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Box,
|
||||
} from "@chakra-ui/react";
|
||||
import { useRef } from "react";
|
||||
import { FaPalette } from "react-icons/fa";
|
||||
import { VscAccount } from "react-icons/vsc";
|
||||
import { VscAccount, VscClose, VscEdit } from "react-icons/vsc";
|
||||
import { UserInfo } from "./rustpad";
|
||||
import React from "react";
|
||||
|
||||
type UserProps = {
|
||||
info: UserInfo;
|
||||
isMe?: boolean;
|
||||
onChangeName?: (name: string) => unknown;
|
||||
onChangeColor?: () => unknown;
|
||||
darkMode: boolean;
|
||||
};
|
||||
|
||||
function makeColor(hue: number, darkMode: boolean): string {
|
||||
return `hsl(${hue}, 90%, ${darkMode ? "70%" : "25%"})`;
|
||||
}
|
||||
|
||||
function User({
|
||||
info,
|
||||
isMe = false,
|
||||
onChangeName,
|
||||
onChangeColor,
|
||||
darkMode,
|
||||
}: UserProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const nameColor = `hsl(${info.hue}, 90%, ${darkMode ? "70%" : "25%"})`;
|
||||
return (
|
||||
<Popover
|
||||
placement="right"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
initialFocusRef={inputRef}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<HStack
|
||||
p={2}
|
||||
rounded="md"
|
||||
_hover={{
|
||||
bgColor: darkMode ? "#464647" : "gray.200",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => isMe && onOpen()}
|
||||
>
|
||||
<Icon as={VscAccount} />
|
||||
<Text fontWeight="medium" color={nameColor}>
|
||||
{info.name}
|
||||
</Text>
|
||||
{isMe && <Text>(you)</Text>}
|
||||
</HStack>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
bgColor={darkMode ? "#333333" : "white"}
|
||||
borderColor={darkMode ? "#464647" : "gray.200"}
|
||||
>
|
||||
<PopoverHeader
|
||||
fontWeight="semibold"
|
||||
borderColor={darkMode ? "#464647" : "gray.200"}
|
||||
>
|
||||
Update Info
|
||||
</PopoverHeader>
|
||||
<PopoverArrow bgColor={darkMode ? "#333333" : "white"} />
|
||||
<PopoverCloseButton />
|
||||
<PopoverBody borderColor={darkMode ? "#464647" : "gray.200"}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
mb={2}
|
||||
value={info.name}
|
||||
maxLength={25}
|
||||
onChange={(event) => onChangeName?.(event.target.value)}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
w="100%"
|
||||
leftIcon={<FaPalette />}
|
||||
colorScheme={darkMode ? "whiteAlpha" : "gray"}
|
||||
onClick={onChangeColor}
|
||||
>
|
||||
Change Color
|
||||
</Button>
|
||||
</PopoverBody>
|
||||
<PopoverFooter
|
||||
d="flex"
|
||||
justifyContent="flex-end"
|
||||
borderColor={darkMode ? "#464647" : "gray.200"}
|
||||
>
|
||||
<ButtonGroup size="sm">
|
||||
<Button colorScheme="blue" onClick={onClose}>
|
||||
Done
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</PopoverFooter>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<HStack>
|
||||
<Icon as={VscAccount}></Icon>
|
||||
<Text fontWeight="medium" color={makeColor(info.hue, darkMode)}>
|
||||
{info.name}
|
||||
</Text>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default User;
|
||||
function EditableControls({ darkMode }: UserProps) {
|
||||
const {
|
||||
isEditing,
|
||||
getEditButtonProps,
|
||||
getCancelButtonProps,
|
||||
} = useEditableControls();
|
||||
|
||||
return isEditing ? (
|
||||
<IconButton aria-label="cancel" colorScheme={darkMode ? "white" : "gray"} size="xs" icon={<VscClose />} {...getCancelButtonProps()} />
|
||||
) : (
|
||||
<IconButton aria-label="edit" colorScheme={darkMode ? "white" : "gray"} size="xs" icon={<VscEdit />} {...getEditButtonProps()} />
|
||||
)
|
||||
}
|
||||
|
||||
type UserEditProps = {
|
||||
me: UserInfo;
|
||||
onChangeName: (name: string) => unknown;
|
||||
onChangeColor?: () => unknown;
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
function UserEdit({
|
||||
me,
|
||||
onChangeName,
|
||||
onChangeColor,
|
||||
darkMode,
|
||||
}: UserEditProps) {
|
||||
const colorScheme = darkMode ? "white" : "gray"
|
||||
|
||||
|
||||
return (
|
||||
<Editable placeholder={me.name} defaultValue={me.name} submitOnBlur={true} onSubmit={onChangeName}>
|
||||
<HStack>
|
||||
<Tooltip label="Change your color">
|
||||
<IconButton
|
||||
colorScheme={colorScheme}
|
||||
size="xxs"
|
||||
aria-label="change color"
|
||||
color={makeColor(me.hue, darkMode)}
|
||||
icon={<FaPalette />}
|
||||
onClick={onChangeColor}></IconButton>
|
||||
</Tooltip>
|
||||
<EditableInput fontWeight="medium" color={makeColor(me.hue, darkMode)} textAlign="left" maxLength={32} />
|
||||
<Tooltip label="Edit your display name">
|
||||
<EditablePreview fontWeight="medium" color={makeColor(me.hue, darkMode)} textAlign="left" />
|
||||
</Tooltip>
|
||||
<YouLabel />
|
||||
<Spacer />
|
||||
<EditableControls info={me} darkMode={darkMode} />
|
||||
</HStack >
|
||||
</Editable>
|
||||
);
|
||||
}
|
||||
type UserListProps = {
|
||||
users: Record<number, UserInfo>;
|
||||
} & UserEditProps;
|
||||
|
||||
function YouLabel() {
|
||||
const { isEditing, getEditButtonProps } = useEditableControls()
|
||||
return <Box {...getEditButtonProps()} textAlign="left"> {isEditing ? "" : "(you)"}</Box>
|
||||
}
|
||||
|
||||
function UserList({ users, me, onChangeName, onChangeColor, darkMode }: UserListProps) {
|
||||
return (
|
||||
<Stack spacing={0} mb={1.5} fontSize="sm">
|
||||
{Object.entries(users).map(([id, info]) => (
|
||||
<User key={id} info={info} darkMode={darkMode} />
|
||||
))}
|
||||
<UserEdit
|
||||
me={me}
|
||||
onChangeName={onChangeName}
|
||||
onChangeColor={onChangeColor}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserList;
|
Loading…
Add table
Reference in a new issue