diff options
author | Marcin Zelent <marcin@zelent.net> | 2022-11-16 15:16:38 +0100 |
---|---|---|
committer | Marcin Zelent <marcin@zelent.net> | 2022-11-16 15:16:38 +0100 |
commit | f2ecc1803f3ea294a0c6b7915b61348ed0395b26 (patch) | |
tree | e8c6fb1350ae4f659b3f9ef8d17157158b974b16 /src/components/Sidebar | |
parent | efb64f24d6200a39870c0e8966ab4f87e07c93a9 (diff) |
Remade and extended the app using React
Diffstat (limited to 'src/components/Sidebar')
-rw-r--r-- | src/components/Sidebar/Sidebar.module.css | 158 | ||||
-rw-r--r-- | src/components/Sidebar/Sidebar.tsx | 152 |
2 files changed, 310 insertions, 0 deletions
diff --git a/src/components/Sidebar/Sidebar.module.css b/src/components/Sidebar/Sidebar.module.css new file mode 100644 index 0000000..8081492 --- /dev/null +++ b/src/components/Sidebar/Sidebar.module.css @@ -0,0 +1,158 @@ +.aside {
+ min-width: 272px;
+ width: 20%;
+ height: 100%;
+ padding: 5px;
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid #e5e5e5;
+ box-shadow: -2px 0 10px #000;
+ z-index: 9999;
+ background-color: #fff;
+}
+
+.headline {
+ padding-left: 16px;
+ position: relative;
+}
+
+.headline h2 {
+ display: inline-block;
+ line-height: 40px;
+ margin-bottom: 0;
+}
+
+.headline button {
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ margin-left: 2px;
+ display: inline-block;
+ border: none;
+ border-radius: 100%;
+ background: none;
+ cursor: pointer;
+}
+
+.headline button:hover {
+ background: #e9f3ff;
+}
+
+.headline button div {
+ width: 8px;
+ height: 8px;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-top: 8px solid #000;
+ display: inline-block;
+}
+
+.tripList {
+ min-width: 80%;
+ padding: 5px 0;
+ position: absolute;
+ top: 100%;
+ left: 16px;
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: 5px;
+ box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+}
+
+.tripList ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.tripList li {
+ padding: 5px;
+ user-select: none;
+}
+
+.tripList li a {
+ padding: 10px 20px 10px 10px;
+ display: block;
+ border-radius: 5px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.tripList li a:hover {
+ background-color: #f5f5f5;
+}
+
+.list {
+ margin: 24px 0 0 0;
+ padding: 0;
+ list-style: none;
+ overflow-y: auto;
+}
+
+.listItem {
+ min-height: 56px;
+ margin-bottom: 5px;
+ padding: 10px 16px;
+ display: flex;
+ border-radius: 10px;
+ cursor: pointer;
+ user-select: none;
+ outline: currentcolor none medium;
+}
+
+.listItem:hover {
+ background-color: #f5f5f5;
+}
+
+.listItem:hover > .listItemButton{
+ display: flex;
+}
+
+.listItemActive {
+ background-color: #e9f3ff !important;
+}
+
+.listItemContent {
+ max-width: calc(100% - 36px);
+ margin: auto 0;
+ flex-grow: 1;
+}
+
+.listItemButton {
+ width: 36px;
+ height: 36px;
+ padding: 9px;
+ margin: auto;
+ display: none;
+ border: none;
+ border-radius: 36px;
+ box-shadow: 0 0 0 1px rgb(0 0 0 / 10%);
+ background: #fff;
+ cursor: pointer;
+}
+
+.listItemButton:hover {
+ background: #fafafa;
+}
+
+.listItemButton svg {
+ margin: auto;
+}
+
+.preformatted {
+ white-space: pre;
+}
+
+@media only screen and (max-width: 500px) {
+ .aside {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -100vw;
+ transition: left 0.3s ease-in;
+ }
+
+ .asideOpen {
+ left: 0;
+ }
+}
diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..b4ddaa9 --- /dev/null +++ b/src/components/Sidebar/Sidebar.tsx @@ -0,0 +1,152 @@ +import React, { useEffect, useRef, useState } from 'react';
+
+import { Group, Trip } from '../../models';
+
+import styles from './Sidebar.module.css';
+
+interface Props {
+ trips: Trip[];
+ currentTrip: Trip;
+ groups: Group[];
+ currentGroup: Group | undefined;
+ headline?: string;
+ asideOpen: boolean;
+ handleClose: () => void;
+ handleGroupChange: (groupIndex: number, openGallery?: boolean) => void;
+ handleTripChange: (tripIndex: number) => void;
+}
+
+export default function Sidebar({
+ trips,
+ currentTrip,
+ groups,
+ currentGroup,
+ asideOpen,
+ handleClose,
+ handleGroupChange,
+ handleTripChange,
+}: Props): JSX.Element {
+ const [tripListOpen, setTripListOpen] = useState(false);
+ const wrapperRef = useRef<null | HTMLElement>(null);
+
+ useEffect(() => {
+ function handleClickOutside(e: Event): void {
+ if (
+ wrapperRef.current !== null &&
+ e.target !== null &&
+ !wrapperRef.current.contains(e.target as Node)
+ ) {
+ handleClose();
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [wrapperRef]);
+
+ function groupChangeHandler(groupIndex: number, openGallery?: boolean): void {
+ handleGroupChange(groupIndex, openGallery);
+ handleClose();
+ }
+
+ function tripChangeHandler(tripIndex: number): void {
+ handleTripChange(tripIndex);
+ setTripListOpen(false);
+ }
+
+ function buildGroupDescription(
+ template: string | undefined,
+ tokens: { [key: string]: string | number } | undefined,
+ ): string {
+ if (template === undefined) {
+ return '';
+ }
+
+ let description = template;
+
+ if (tokens === undefined) {
+ return description;
+ }
+
+ Object.keys(tokens).forEach((key) => {
+ description = description.replaceAll(`{${key}}`, `${tokens[key]}`);
+ });
+
+ return description;
+ }
+
+ const asideStyle = asideOpen ? `${styles.aside} ${styles.asideOpen}` : styles.aside;
+
+ return (
+ <aside ref={wrapperRef} className={asideStyle}>
+ {currentTrip.name !== undefined && (
+ <div className={styles.headline}>
+ <h2>{currentTrip.name}</h2>
+ {trips.length > 1 && (
+ <button
+ type="button"
+ title="Show list of trips"
+ onClick={() => setTripListOpen(!tripListOpen)}
+ >
+ <div></div>
+ </button>
+ )}
+ {tripListOpen && (
+ <div className={styles.tripList}>
+ <ul>
+ {trips.map((t, i) => (
+ <li key={i}>
+ <a onClick={() => tripChangeHandler(i)}>{t.name}</a>
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+ </div>
+ )}
+ <ul className={styles.list}>
+ {groups.map((group, index) => {
+ const listItemStyle =
+ group.id === currentGroup?.id
+ ? `${styles.listItem} ${styles.listItemActive}`
+ : styles.listItem;
+
+ return (
+ <li key={index}>
+ <a
+ onClick={() => groupChangeHandler(index)}
+ onKeyPress={() => groupChangeHandler(index)}
+ tabIndex={0}
+ role="menuitem"
+ className={listItemStyle}
+ >
+ <div className={styles.listItemContent}>
+ <b>{group.name}</b>
+ <br />
+ <div className={styles.preformatted}>
+ {buildGroupDescription(group.description, group.metadata)}
+ </div>
+ </div>
+ <button
+ type="button"
+ title="Show gallery"
+ className={styles.listItemButton}
+ onClick={(e) => {
+ e.stopPropagation();
+ groupChangeHandler(index, true);
+ }}
+ >
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
+ <path d="M512 32H160c-35.35 0-64 28.65-64 64v224c0 35.35 28.65 64 64 64H512c35.35 0 64-28.65 64-64V96C576 60.65 547.3 32 512 32zM528 320c0 8.822-7.178 16-16 16h-16l-109.3-160.9C383.7 170.7 378.7 168 373.3 168c-5.352 0-10.35 2.672-13.31 7.125l-62.74 94.11L274.9 238.6C271.9 234.4 267.1 232 262 232c-5.109 0-9.914 2.441-12.93 6.574L176 336H160c-8.822 0-16-7.178-16-16V96c0-8.822 7.178-16 16-16H512c8.822 0 16 7.178 16 16V320zM224 112c-17.67 0-32 14.33-32 32s14.33 32 32 32c17.68 0 32-14.33 32-32S241.7 112 224 112zM456 480H120C53.83 480 0 426.2 0 360v-240C0 106.8 10.75 96 24 96S48 106.8 48 120v240c0 39.7 32.3 72 72 72h336c13.25 0 24 10.75 24 24S469.3 480 456 480z" />
+ </svg>
+ </button>
+ </a>
+ </li>
+ );
+ })}
+ </ul>
+ </aside>
+ );
+}
|