Commit 0f94a75f authored by tom's avatar tom

side bar tabs and collapse button

parent 06b2d672
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { File } from './types'; import type { File } from './types';
import CodeEditorFileTree from './CodeEditorFileTree'; import CodeEditorFileTree from './CodeEditorFileTree';
import CoderEditorCollapseButton from './CoderEditorCollapseButton';
import composeFileTree from './utils/composeFileTree'; import composeFileTree from './utils/composeFileTree';
interface Props { interface Props {
...@@ -12,6 +13,7 @@ interface Props { ...@@ -12,6 +13,7 @@ interface Props {
} }
const CodeEditorFileExplorer = ({ data, onFileSelect }: Props) => { const CodeEditorFileExplorer = ({ data, onFileSelect }: Props) => {
const [ key, setKey ] = React.useState(0);
const tree = React.useMemo(() => { const tree = React.useMemo(() => {
return composeFileTree(data); return composeFileTree(data);
}, [ data ]); }, [ data ]);
...@@ -25,9 +27,14 @@ const CodeEditorFileExplorer = ({ data, onFileSelect }: Props) => { ...@@ -25,9 +27,14 @@ const CodeEditorFileExplorer = ({ data, onFileSelect }: Props) => {
} }
}, [ data, onFileSelect ]); }, [ data, onFileSelect ]);
const handleCollapseButtonClick = React.useCallback(() => {
setKey((prev) => prev + 1);
}, []);
return ( return (
<Box> <Box>
<CodeEditorFileTree tree={ tree } onItemClick={ handleFileClick }/> <CoderEditorCollapseButton onClick={ handleCollapseButtonClick } label="Collapse folders"/>
<CodeEditorFileTree key={ key } tree={ tree } onItemClick={ handleFileClick } isCollapsed={ key > 0 }/>
</Box> </Box>
); );
}; };
......
...@@ -7,10 +7,11 @@ import type { FileTree } from './types'; ...@@ -7,10 +7,11 @@ import type { FileTree } from './types';
interface Props { interface Props {
tree: FileTree; tree: FileTree;
level?: number; level?: number;
isCollapsed?: boolean;
onItemClick: (event: React.MouseEvent) => void; onItemClick: (event: React.MouseEvent) => void;
} }
const CodeEditorFileTree = ({ tree, level = 0, onItemClick }: Props) => { const CodeEditorFileTree = ({ tree, level = 0, onItemClick, isCollapsed }: Props) => {
const itemProps: ChakraProps = { const itemProps: ChakraProps = {
ml: level ? 4 : 0, ml: level ? 4 : 0,
borderWidth: '0px', borderWidth: '0px',
...@@ -21,7 +22,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick }: Props) => { ...@@ -21,7 +22,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick }: Props) => {
}; };
return ( return (
<Accordion allowMultiple defaultIndex={ tree.map((item, index) => index) } reduceMotion> <Accordion allowMultiple defaultIndex={ isCollapsed ? undefined : tree.map((item, index) => index) } reduceMotion>
{ {
tree.map((leaf, index) => { tree.map((leaf, index) => {
if ('children' in leaf) { if ('children' in leaf) {
...@@ -32,7 +33,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick }: Props) => { ...@@ -32,7 +33,7 @@ const CodeEditorFileTree = ({ tree, level = 0, onItemClick }: Props) => {
<span>{ leaf.name }</span> <span>{ leaf.name }</span>
</AccordionButton> </AccordionButton>
<AccordionPanel p={ 0 }> <AccordionPanel p={ 0 }>
<CodeEditorFileTree tree={ leaf.children } level={ level + 1 } onItemClick={ onItemClick }/> <CodeEditorFileTree tree={ leaf.children } level={ level + 1 } onItemClick={ onItemClick } isCollapsed={ isCollapsed }/>
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
); );
......
...@@ -6,6 +6,7 @@ import type { File, Monaco, SearchResult } from './types'; ...@@ -6,6 +6,7 @@ import type { File, Monaco, SearchResult } from './types';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import CodeEditorSearchSection from './CodeEditorSearchSection'; import CodeEditorSearchSection from './CodeEditorSearchSection';
import CoderEditorCollapseButton from './CoderEditorCollapseButton';
interface Props { interface Props {
data: Array<File>; data: Array<File>;
...@@ -16,6 +17,7 @@ interface Props { ...@@ -16,6 +17,7 @@ interface Props {
const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => { const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => {
const [ searchTerm, changeSearchTerm ] = React.useState(''); const [ searchTerm, changeSearchTerm ] = React.useState('');
const [ searchResults, setSearchResults ] = React.useState<Array<SearchResult>>([]); const [ searchResults, setSearchResults ] = React.useState<Array<SearchResult>>([]);
const [ expandedSections, setExpandedSections ] = React.useState<Array<number>>([]);
const debouncedSearchTerm = useDebounce(searchTerm, 300); const debouncedSearchTerm = useDebounce(searchTerm, 300);
...@@ -41,6 +43,10 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => { ...@@ -41,6 +43,10 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => {
setSearchResults(result.length > 0 ? result : []); setSearchResults(result.length > 0 ? result : []);
}, [ debouncedSearchTerm, monaco ]); }, [ debouncedSearchTerm, monaco ]);
React.useEffect(() => {
setExpandedSections(searchResults.map((item, index) => index));
}, [ searchResults ]);
const handleSearchTermChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const handleSearchTermChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
changeSearchTerm(event.target.value); changeSearchTerm(event.target.value);
}, []); }, []);
...@@ -52,13 +58,31 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => { ...@@ -52,13 +58,31 @@ const CodeEditorSearch = ({ monaco, data, onFileSelect }: Props) => {
} }
}, [ data, onFileSelect ]); }, [ data, onFileSelect ]);
const handleAccordionStateChange = React.useCallback((newValue: Array<number>) => {
setExpandedSections(newValue);
}, []);
const handleToggleCollapseClick = React.useCallback(() => {
if (expandedSections.length === 0) {
setExpandedSections(searchResults.map((item, index) => index));
} else {
setExpandedSections([]);
}
}, [ expandedSections.length, searchResults ]);
return ( return (
<Box> <Box>
<CoderEditorCollapseButton
onClick={ handleToggleCollapseClick }
label={ expandedSections.length === 0 ? 'Expand all' : 'Collapse all' }
isDisabled={ searchResults.length === 0 }
/>
<Input size="xs" onChange={ handleSearchTermChange } value={ searchTerm } placeholder="Search"/> <Input size="xs" onChange={ handleSearchTermChange } value={ searchTerm } placeholder="Search"/>
<Accordion <Accordion
key={ debouncedSearchTerm } key={ debouncedSearchTerm }
allowMultiple allowMultiple
defaultIndex={ searchResults.map((item, index) => index) } index={ expandedSections }
onChange={ handleAccordionStateChange }
reduceMotion reduceMotion
mt={ 3 } mt={ 3 }
> >
......
import { Box, Flex, Tooltip } from '@chakra-ui/react'; import type { HTMLChakraProps } from '@chakra-ui/react';
import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { File, Monaco } from './types'; import type { File, Monaco } from './types';
...@@ -13,40 +14,37 @@ interface Props { ...@@ -13,40 +14,37 @@ interface Props {
} }
const CodeEditorSideBar = ({ onFileSelect, data, monaco }: Props) => { const CodeEditorSideBar = ({ onFileSelect, data, monaco }: Props) => {
const [ activePanelIndex, setActivePanelIndex ] = React.useState(0);
const PANELS = React.useMemo(() => ([ const tabProps: HTMLChakraProps<'button'> = {
{ id: 'explorer', label: 'Explorer', text: 'E', component: <CodeEditorFileExplorer data={ data } onFileSelect={ onFileSelect }/> }, fontFamily: 'heading',
{ id: 'search', label: 'Search', text: 'S', component: <CodeEditorSearch data={ data } onFileSelect={ onFileSelect } monaco={ monaco }/> }, textTransform: 'uppercase',
]), [ data, monaco, onFileSelect ]); fontSize: 'xs',
fontWeight: 500,
const activePanel = PANELS[activePanelIndex]; color: 'gray.600',
_selected: {
const handleTabClick = React.useCallback((event: React.MouseEvent) => { color: 'black',
const id = (event.currentTarget as HTMLDivElement).getAttribute('data-id'); },
const index = PANELS.findIndex((item) => item.id === id); px: 0,
if (index > -1) { letterSpacing: 0.3,
setActivePanelIndex(index); };
}
}, [ PANELS ]);
return ( return (
<Box w="250px" flexShrink={ 0 } bgColor="lightpink" fontSize="sm" overflowY="scroll" px={ 3 }> <Box w="250px" flexShrink={ 0 } bgColor="lightpink" fontSize="sm" overflowY="scroll" px={ 3 } position="relative">
<Flex> <Tabs isLazy lazyBehavior="keepMounted" variant="unstyled" size="sm">
<Box fontFamily="heading" letterSpacing={ 0.5 } fontWeight={ 600 } textTransform="uppercase" lineHeight={ 6 }> <TabList columnGap={ 3 }>
{ activePanel.label } <Tab { ...tabProps }>Explorer</Tab>
</Box> <Tab { ...tabProps }>Search</Tab>
<Flex ml="auto" columnGap={ 2 }> </TabList>
{ PANELS.map(({ id, text, label }) => (
<Tooltip key={ id } label={ label }> <TabPanels>
<Box data-id={ id } boxSize={ 6 } cursor="pointer" textAlign="center" bgColor="lightblue" onClick={ handleTabClick }> <TabPanel p={ 0 }>
{ text } <CodeEditorFileExplorer data={ data } onFileSelect={ onFileSelect }/>
</Box> </TabPanel>
</Tooltip> <TabPanel p={ 0 }>
)) } <CodeEditorSearch data={ data } onFileSelect={ onFileSelect } monaco={ monaco }/>
</Flex> </TabPanel>
</Flex> </TabPanels>
{ activePanel.component } </Tabs>
</Box> </Box>
); );
}; };
......
import { Flex, IconButton, Tooltip } from '@chakra-ui/react';
import React from 'react';
import iconCopy from 'icons/copy.svg';
interface Props {
onClick: () => void;
label: string;
isDisabled?: boolean;
}
const CoderEditorCollapseButton = ({ onClick, label, isDisabled }: Props) => {
return (
<Tooltip label={ label } isDisabled={ isDisabled }>
<Flex position="absolute" right={ 3 } top={ 2 }>
<IconButton
as={ iconCopy }
boxSize={ 4 }
variant="unstyled"
cursor="pointer"
aria-label="collapse"
onClick={ onClick }
isDisabled={ isDisabled }
/>
</Flex>
</Tooltip>
);
};
export default React.memo(CoderEditorCollapseButton);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment