From cbcfe5adba607d188a99a2cfb46ac78e5e0525b3 Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Thu, 13 Apr 2023 12:33:13 +0300 Subject: [PATCH] [ENG-450] TopBar revamp and restructure (#701) * TopBar revamp and restructure * file tweaks * Update package.json * Typecheck missed * TopBar revamp and restructure file tweaks Update package.json Typecheck missed * Removed search icon and layout change * move framer to interface/package.json * Increase spacing * fixes & tweaks * Update TopBar.tsx * typecheck tweak --- .../app/$libraryId/Explorer/SearchBar.tsx | 134 ++++---- interface/app/$libraryId/Explorer/TopBar.tsx | 291 +++++------------- interface/app/$libraryId/Explorer/index.tsx | 4 +- interface/app/$libraryId/PageLayout.tsx | 8 +- interface/app/$libraryId/overview.tsx | 13 +- interface/app/$libraryId/spacedrop.tsx | 20 +- interface/hooks/useToolBarOptions.tsx | 149 +++++++++ interface/package.json | 1 + packages/ui/src/Typography.tsx | 2 +- pnpm-lock.yaml | Bin 801575 -> 802183 bytes 10 files changed, 326 insertions(+), 296 deletions(-) create mode 100644 interface/hooks/useToolBarOptions.tsx diff --git a/interface/app/$libraryId/Explorer/SearchBar.tsx b/interface/app/$libraryId/Explorer/SearchBar.tsx index 2138a8532..a4f2b9965 100644 --- a/interface/app/$libraryId/Explorer/SearchBar.tsx +++ b/interface/app/$libraryId/Explorer/SearchBar.tsx @@ -4,72 +4,78 @@ import { useForm } from 'react-hook-form'; import { Input, Shortcut } from '@sd/ui'; import { useOperatingSystem } from '~/hooks/useOperatingSystem'; -export default forwardRef>( - (props, forwardedRef) => { - const { - register, - handleSubmit, - reset, - formState: { dirtyFields } - } = useForm(); +interface Props extends ComponentPropsWithRef<'input'> { + formClassName?: string; +} - const { ref, ...searchField } = register('searchField', { - onBlur: () => { - // if there's no text in the search bar, don't mark it as dirty so the key hint shows - if (!dirtyFields.searchField) reset(); - } - }); +export default forwardRef((props, forwardedRef) => { + const { + register, + handleSubmit, + reset, + formState: { dirtyFields } + } = useForm(); - const platform = useOperatingSystem(false); - const os = useOperatingSystem(true); + const { ref, ...searchField } = register('searchField', { + onBlur: () => { + // if there's no text in the search bar, don't mark it as dirty so the key hint shows + if (!dirtyFields.searchField) reset(); + } + }); - useEffect(() => { - const keyboardSearchFocus = (event: KeyboardEvent) => { - if (typeof forwardedRef !== 'function') { - if ((event.key === 'f' && event.metaKey) || event.ctrlKey) { - event.preventDefault(); - forwardedRef?.current?.focus(); - } else if (forwardedRef?.current === document.activeElement && event.key === 'Escape') { - forwardedRef.current?.blur(); - } + const platform = useOperatingSystem(false); + const os = useOperatingSystem(true); + + useEffect(() => { + const keyboardSearchFocus = (event: KeyboardEvent) => { + if (typeof forwardedRef !== 'function') { + if ((event.key === 'f' && event.metaKey) || event.ctrlKey) { + event.preventDefault(); + forwardedRef?.current?.focus(); + } else if (forwardedRef?.current === document.activeElement && event.key === 'Escape') { + forwardedRef.current?.blur(); + reset(); } - }; - document.addEventListener('keydown', keyboardSearchFocus); - return () => { - document.removeEventListener('keydown', keyboardSearchFocus); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + } + }; + document.addEventListener('keydown', keyboardSearchFocus); + return () => { + document.removeEventListener('keydown', keyboardSearchFocus); + }; + }, [forwardedRef, reset]); - return ( -
null)} className="relative flex h-7"> - { - ref(el); - if (typeof forwardedRef === 'function') forwardedRef(el); - else if (forwardedRef) forwardedRef.current = el; - }} - placeholder="Search" - className={clsx('w-32 transition-all focus-within:w-52', props.className)} - size="sm" - {...searchField} - right={ -
- {platform === 'browser' ? ( - - ) : os === 'macOS' ? ( - - ) : ( - - )} -
- } - /> -
- ); - } -); + return ( +
null)} + className={`relative flex h-7 ${props.formClassName}`} + > + { + ref(el); + if (typeof forwardedRef === 'function') forwardedRef(el); + else if (forwardedRef) forwardedRef.current = el; + }} + placeholder="Search" + className={clsx('w-52 transition-all duration-200 focus-within:w-60', props.className)} + size="sm" + {...searchField} + right={ +
+ {platform === 'browser' ? ( + + ) : os === 'macOS' ? ( + + ) : ( + + )} +
+ } + /> +
+ ); +}); diff --git a/interface/app/$libraryId/Explorer/TopBar.tsx b/interface/app/$libraryId/Explorer/TopBar.tsx index dc500d137..65ed8832b 100644 --- a/interface/app/$libraryId/Explorer/TopBar.tsx +++ b/interface/app/$libraryId/Explorer/TopBar.tsx @@ -1,229 +1,102 @@ import clsx from 'clsx'; -import { - ArrowsClockwise, - CaretLeft, - CaretRight, - Columns, - Key, - MonitorPlay, - Rows, - SidebarSimple, - SlidersHorizontal, - SquaresFour, - Tag -} from 'phosphor-react'; -import { useEffect, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { CaretLeft, CaretRight } from 'phosphor-react'; +import { useRef } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Popover, Tooltip } from '@sd/ui'; -import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore'; -import { useOperatingSystem } from '~/hooks/useOperatingSystem'; -import { KeybindEvent } from '~/util/keybind'; -import { KeyManager } from '../KeyManager'; -import OptionsPanel from './OptionsPanel'; +import { RoutePaths, groupKeys, useToolBarRouteOptions } from '~/hooks/useToolBarOptions'; import SearchBar from './SearchBar'; import TopBarButton from './TopBarButton'; export const TOP_BAR_HEIGHT = 46; -export type TopBarProps = { - showSeparator?: boolean; -}; - -export default (props: TopBarProps) => { +export default () => { const TOP_BAR_ICON_STYLE = 'm-0.5 w-5 h-5 text-ink-dull'; - const platform = useOperatingSystem(false); - const os = useOperatingSystem(true); - const store = useExplorerStore(); const navigate = useNavigate(); - //create function to focus on search box when cmd+k is pressed const searchRef = useRef(null); - - const focusSearchBar = (bar: HTMLInputElement, e?: Event): boolean => { - bar.focus(); - - e?.preventDefault(); - return false; - }; - - useEffect(() => { - const searchBar = searchRef.current; - - if (searchBar === null || !searchBar) return; - - const handleKeybindAction = (e: KeybindEvent) => { - if (e.detail.action === 'open_search') { - return focusSearchBar(searchBar, e); - } - }; - - const handleDOMKeydown = (e: KeyboardEvent) => { - if (e.target === searchBar && e.key === 'Escape') { - (e.target as HTMLInputElement).blur(); - e.preventDefault(); - return; - } - - const isBrowser = platform === 'browser'; - // use cmd on macOS and ctrl on Windows - const hasModifier = os === 'macOS' ? e.metaKey : e.ctrlKey; - - if ( - // allow slash on all platforms - (e.key === '/' && - !(document.activeElement instanceof HTMLInputElement) && - !(document.activeElement instanceof HTMLTextAreaElement)) || - // only do the cmd-f keybind check on browser to allow for native keybind functionality - // this is particularly useful for power-user niche use cases, - // like how macOS lets you redefine keybinds for apps - (isBrowser && hasModifier && e.key === 'f') - ) { - document.dispatchEvent(new KeybindEvent('open_search')); - e.preventDefault(); - return; - } - }; - - document.addEventListener('keydown', handleDOMKeydown); - document.addEventListener('keybindexec', handleKeybindAction); - - return () => { - document.removeEventListener('keydown', handleDOMKeydown); - document.removeEventListener('keybindexec', handleKeybindAction); - }; - }, [os, platform]); + const { pathname } = useLocation(); + const getPageName = pathname.split('/')[2] as RoutePaths; + const { toolBarRouteOptions } = useToolBarRouteOptions(); return ( - <> -
-
- - navigate(-1)}> - - - - - navigate(1)}> - - - -
+
+
+ + navigate(-1)}> + + + + + navigate(1)}> + + + +
-
-
- - (getExplorerStore().layoutMode = 'grid')} - > - - - - - (getExplorerStore().layoutMode = 'rows')} - > - - - - - (getExplorerStore().layoutMode = 'columns')} - > - - - - - (getExplorerStore().layoutMode = 'media')} - > - - - -
+ - - -
- - - - +
+
+ {toolBarRouteOptions[getPageName].options.map((group) => { + return (Object.keys(group) as groupKeys[]).map((groupKey) => { + return group[groupKey]?.map( + ({ icon, onClick, popOverComponent, toolTipLabel, topBarActive }, index) => { + const groupCount = Object.keys(group).length; + const groupIndex = Object.keys(group).indexOf(groupKey); + const roundingCondition = + index === 0 + ? 'left' + : index === (group[groupKey]?.length as number) - 1 + ? 'right' + : 'none'; + return ( +
+ + {popOverComponent ? ( + + {icon} + + } + > +
{popOverComponent}
+
+ ) : ( + + {icon} + + )} +
+ {index === (group[groupKey]?.length as number) - 1 && + groupCount !== groupIndex + 1 && ( +
+ )} +
+ ); } - > -
- -
- - - - (getExplorerStore().tagAssignMode = !store.tagAssignMode)} - active={store.tagAssignMode} - > - - - - - - - - -
-
-
- - - - - } - > -
- -
-
-
- - - (getExplorerStore().showInspector = !store.showInspector)} - className="my-2" - > - - - + ); + }); + })}
- +
); }; diff --git a/interface/app/$libraryId/Explorer/index.tsx b/interface/app/$libraryId/Explorer/index.tsx index a4d1991c8..4c04b1150 100644 --- a/interface/app/$libraryId/Explorer/index.tsx +++ b/interface/app/$libraryId/Explorer/index.tsx @@ -33,8 +33,8 @@ export default function Explorer(props: Props) { }, [locationId]); return ( -
- +
+
diff --git a/interface/app/$libraryId/PageLayout.tsx b/interface/app/$libraryId/PageLayout.tsx index e116a08d2..b43a4a565 100644 --- a/interface/app/$libraryId/PageLayout.tsx +++ b/interface/app/$libraryId/PageLayout.tsx @@ -3,6 +3,7 @@ import { PropsWithChildren, RefObject, createContext, useContext, useRef } from import { createPortal } from 'react-dom'; import { Outlet } from 'react-router'; import DragRegion from '~/components/DragRegion'; +import TopBar from './Explorer/TopBar'; const PageLayoutContext = createContext<{ ref: RefObject } | null>(null); @@ -11,11 +12,14 @@ export const Component = () => { return ( +
-
+
diff --git a/interface/app/$libraryId/overview.tsx b/interface/app/$libraryId/overview.tsx index 932f8ccb3..87c9a3953 100644 --- a/interface/app/$libraryId/overview.tsx +++ b/interface/app/$libraryId/overview.tsx @@ -14,7 +14,7 @@ import { import Skeleton from 'react-loading-skeleton'; import 'react-loading-skeleton/dist/skeleton.css'; import { Statistics, useLibraryContext, useLibraryQuery } from '@sd/client'; -import { Card } from '@sd/ui'; +import { Card, ScreenHeading } from '@sd/ui'; import useCounter from '~/hooks/useCounter'; import { usePlatform } from '~/util/Platform'; @@ -96,11 +96,12 @@ export const Component = () => { overviewMounted = true; return ( -
+
+ Overview {/* STAT HEADER */}
{/* STAT CONTAINER */} -
+
{Object.entries(stats?.data || []).map(([key, value]) => { if (!displayableStatItems.includes(key)) return null; return ( @@ -115,7 +116,7 @@ export const Component = () => {
-
+
@@ -131,7 +132,7 @@ export const Component = () => { Note:   This is a pre-alpha build of Spacedrive, many features are yet to be functional. -
+
); }; @@ -144,7 +145,7 @@ interface CategoryButtonProps { function CategoryButton({ category, icon: Icon }: CategoryButtonProps) { return ( - +

{category}

23,324 items

diff --git a/interface/app/$libraryId/spacedrop.tsx b/interface/app/$libraryId/spacedrop.tsx index 6c141c7cd..c64c4fd66 100644 --- a/interface/app/$libraryId/spacedrop.tsx +++ b/interface/app/$libraryId/spacedrop.tsx @@ -2,7 +2,7 @@ import { GoogleDrive, Mega, iCloud } from '@sd/assets/images'; import clsx from 'clsx'; import { DeviceMobile, HardDrives, Icon, Laptop, User } from 'phosphor-react'; import { useRef, useState } from 'react'; -import { Button, Select, SelectOption, forms, tw } from '@sd/ui'; +import { Button, ScreenHeading, Select, SelectOption, forms, tw } from '@sd/ui'; import { PeerMetadata, useBridgeMutation, useBridgeSubscription } from '~/../packages/client/src'; import { SubtleButton, SubtleButtonContainer } from '~/components/SubtleButton'; import { OperatingSystem } from '~/util/Platform'; @@ -41,7 +41,7 @@ function DropItem(props: DropItemProps) { } if (brandIconSrc) { icon = ( -
+
{props.name}
); @@ -56,11 +56,11 @@ function DropItem(props: DropItemProps) {
-
+
{/* */} -
{icon}
+
{icon}
@@ -117,12 +117,12 @@ function TemporarySpacedropDemo() { // TODO: Input select return (
-

Spacedrop Demo

+ Spacedrop Demo

Note: Right now the file must be less than 255 bytes long and only contain UTF-8 chars. Create a txt file in Vscode to test (note macOS TextEdit cause that is rtf by default)

-
+
- @@ -145,7 +145,7 @@ function TemporarySpacedropDemo() {