Skip to content

Commit

Permalink
resizable accordion panels
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Jan 14, 2025
1 parent 61d2757 commit 3413cff
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 37 deletions.
2 changes: 1 addition & 1 deletion app/src/app/components/Toolbar/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const ColorPicker = <T extends boolean>({
const mapDocument = useMapStore(state => state.mapDocument);
const hotkeyRef = useRef<string | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const numDistricts = 20; //mapDocument?.num_districts || 4;
const numDistricts = mapDocument?.num_districts || 4;

const handleKeyPressSubmit = () => {
if (!hotkeyRef.current) return;
Expand Down
112 changes: 78 additions & 34 deletions app/src/app/components/sidebar/DataPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {Box} from '@radix-ui/themes';
import {Box, IconButton} from '@radix-ui/themes';
import Evaluation from '@components/sidebar/Evaluation';
import PopulationPanel from '@components/sidebar/PopulationPanel';
import React from 'react';
import classNames from "classnames";
import * as Accordion from "@radix-ui/react-accordion";
import { DoubleArrowDownIcon } from "@radix-ui/react-icons";
import {useMapStore} from '@/app/store/mapStore';
import classNames from 'classnames';
import * as Accordion from '@radix-ui/react-accordion';
import {DoubleArrowDownIcon, DragHandleHorizontalIcon} from '@radix-ui/react-icons';
import {MapStore, useMapStore} from '@/app/store/mapStore';
import Draggable from 'react-draggable';

interface DataPanelSpec {
title: string;
title: MapStore['sidebarPanels'][number];
label: string;
icon?: React.ReactNode;
content?: React.ReactNode;
Expand All @@ -30,48 +31,91 @@ const defaultPanels: DataPanelSpec[] = [
content: <Evaluation />,
},
];

const DataPanels: React.FC<DataPanelsProps> = ({
panels = defaultPanels,
}) => {
const ResizableAccordionPanel: React.FC<{panel: DataPanelSpec, open: boolean}> = ({panel, open}) => {
const [height, setHeight] = React.useState<null | number>(null);
const [hovered, setHovered] = React.useState(false);
const itemRef = React.useRef<HTMLDivElement>(null);
return (
<Accordion.Item
key={panel.title}
value={panel.title}
className="AccordionItem border-[1px] border-gray-300 rounded-lg my-1 bg-white relative"
defaultValue={open ? 'open' : undefined}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<AccordionTrigger className="AccordionTrigger">{panel.label}</AccordionTrigger>
<AccordionContent
className="AccordionContent"
ref={itemRef}
style={{
overflowY: 'auto',
height: height === null ? 'auto' : height,
minHeight: "100px"
}}
>
{panel.content}
</AccordionContent>
<div
className="absolute bottom-0 left-[50%]"
style={{
transform: 'translate(-50%, 50%)',
}}
>
<Draggable
grid={[10, 10]}
onDrag={(e: any) => {
const top = itemRef.current?.getBoundingClientRect().top;
if (top) {
setHeight(e.clientY - top);
}
}}
position={{x: 0, y: 0}}
axis="y"
>
<IconButton
variant="surface"
style={{
background: 'rgb(245,245,245)',
width: '40px',
height: '16px',
opacity: hovered && open ? 1 : 0,
transition: 'opacity 0.2s',
}}
>
<DragHandleHorizontalIcon />
</IconButton>
</Draggable>
</div>
</Accordion.Item>
);
};
const DataPanels: React.FC<DataPanelsProps> = ({panels = defaultPanels}) => {
const sidebarPanels = useMapStore(state => state.sidebarPanels);
const setSidebarPanels = useMapStore(state => state.setSidebarPanels);
return (
<Accordion.Root
type="multiple"
className="AccordionRoot"
<Accordion.Root
type="multiple"
className="AccordionRoot"
value={sidebarPanels}
onValueChange={setSidebarPanels}
>
{panels.map(panel => (
<Accordion.Item
key={panel.title}
value={panel.title}
className="AccordionItem border-[1px] border-gray-300 rounded-lg my-1 bg-white"
defaultValue={'open'}
>
<AccordionTrigger className="AccordionTrigger">
{panel.label}
</AccordionTrigger>
<AccordionContent className="AccordionContent">
{panel.content}
</AccordionContent>
</Accordion.Item>
>
{panels.map((panel, i) => (
<ResizableAccordionPanel key={i} panel={panel} open={sidebarPanels.includes(panel.title)}/>
))}
</Accordion.Root>
);
};


const AccordionTrigger = React.forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof Accordion.Trigger>
>(({ children, className, ...props }, forwardedRef) => (
>(({children, className, ...props}, forwardedRef) => (
<Accordion.Header className="flex">
<Accordion.Trigger
className={classNames(
`bg-white group flex h-[45px] flex-1 cursor-default items-center justify-between px-5 leading-none rounded-md data-[state=closed]:shadow-md outline-none hover:bg-blue-200`,
className,
className
)}
{...props}
ref={forwardedRef}
Expand All @@ -90,11 +134,11 @@ AccordionTrigger.displayName = 'AccordionTrigger';
const AccordionContent = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof Accordion.Content>
>(({ children, className, ...props }, forwardedRef) => (
>(({children, className, ...props}, forwardedRef) => (
<Accordion.Content
className={classNames(
"overflow-hidden text-[15px] data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown",
className,
'overflow-hidden text-[15px] data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown',
className
)}
{...props}
ref={forwardedRef}
Expand Down
11 changes: 9 additions & 2 deletions app/src/app/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ const HandleIconButton = () => {
export default function SidebarComponent() {
const document_id = useMapStore(store => store.mapDocument?.document_id);
const [width, setWidth] = React.useState(window.innerWidth * 0.25);
const [hovered, setHovered] = React.useState(false);
return (
<Box
p="3"
className="z-10 flex-none overflow-y-auto
className="z-10 flex-none
border-t lg:border-t-0
lg:h-screen
landscape:border-t-0
Expand All @@ -42,8 +43,11 @@ export default function SidebarComponent() {
border-gray-500
shadow-xl
relative
overflow-y-auto
"
style={{width: width, overflow: 'visible'}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div
style={{
Expand Down Expand Up @@ -74,12 +78,15 @@ export default function SidebarComponent() {
background: 'rgba(245, 245, 245)',
height: '40px',
cursor: 'ew-resize',
opacity: hovered ? 1 : 0,
transition: 'opacity 0.2s',
}}
>
<DragHandleHorizontalIcon />
</IconButton>
</Draggable>
</div>
<Box className="size-full overflow-y-auto">
<Flex direction="column" gap="3">
<Heading as="h3" size="3" className="hidden lg:block">
Districtr
Expand All @@ -96,7 +103,7 @@ export default function SidebarComponent() {
>
<DataPanels />
</Box>
</Flex>
</Flex></Box>
</Box>
);
}

0 comments on commit 3413cff

Please sign in to comment.