From 29f0dfb3381293331a804aab0a4e296ced0f31eb Mon Sep 17 00:00:00 2001 From: nikec <43032218+niikeec@users.noreply.github.com> Date: Tue, 21 Mar 2023 06:01:48 +0100 Subject: [PATCH] [Desktop, UI] Fix library dropdown overflow & navigation (#619) * add radix dropdown menu * [desktop] Improve Libraries Dropdown * rename alignToParent prop and fix sizing * Update pnpm-lock.yaml * Revert "Update pnpm-lock.yaml" This reverts commit 6113361c51e816788691b44aca24ac23cc723d5b. * fix pnpm lock --- .../Layout/Sidebar/LibrariesDropdown.tsx | 72 ++++---- packages/ui/package.json | 1 + packages/ui/src/ContextMenu.tsx | 82 +++++---- packages/ui/src/Dropdown.tsx | 28 +-- packages/ui/src/DropdownMenu.tsx | 162 ++++++++++++++++++ packages/ui/src/index.ts | 3 +- packages/ui/style/tailwind.js | 1 + pnpm-lock.yaml | Bin 832937 -> 833331 bytes 8 files changed, 270 insertions(+), 79 deletions(-) create mode 100644 packages/ui/src/DropdownMenu.tsx diff --git a/interface/app/$libraryId/Layout/Sidebar/LibrariesDropdown.tsx b/interface/app/$libraryId/Layout/Sidebar/LibrariesDropdown.tsx index c8e061b04..855f8e61b 100644 --- a/interface/app/$libraryId/Layout/Sidebar/LibrariesDropdown.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/LibrariesDropdown.tsx @@ -1,22 +1,19 @@ import clsx from 'clsx'; import { Gear, Lock, Plus } from 'phosphor-react'; import { useClientContext } from '@sd/client'; -import { Dropdown, dialogManager } from '@sd/ui'; +import { Dropdown, DropdownMenu, dialogManager } from '@sd/ui'; import CreateDialog from '../../settings/node/libraries/CreateDialog'; export default () => { const { library, libraries, currentLibraryId } = useClientContext(); return ( - { } + // we override the sidebar dropdown item's hover styles + // because the dark style clashes with the sidebar + className="dark:bg-sidebar-box dark:border-sidebar-line dark:divide-menu-selected/30 mt-1 shadow-none" + alignToTrigger + animate > - - {libraries.data?.map((lib) => ( - - {lib.config.name} - - ))} - - - dialogManager.create((dp) => )} + {libraries.data?.map((lib) => ( + - New Library - - - Manage Library - - alert('TODO: Not implemented yet!')}> - Lock - - - + {lib.config.name} + + ))} + + dialogManager.create((dp) => )} + className="font-medium" + /> + + alert('TODO: Not implemented yet!')} + className="font-medium" + /> + ); }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 1a6d769c7..d225f02c4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -72,6 +72,7 @@ "storybook-tailwind-dark-mode": "^1.0.15", "style-loader": "^3.3.1", "tailwindcss": "^3.1.8", + "tailwindcss-animate": "^1.0.5", "typescript": "^4.8.4" } } diff --git a/packages/ui/src/ContextMenu.tsx b/packages/ui/src/ContextMenu.tsx index 59dd17ce3..cf50db5f4 100644 --- a/packages/ui/src/ContextMenu.tsx +++ b/packages/ui/src/ContextMenu.tsx @@ -1,28 +1,28 @@ import * as RadixCM from '@radix-ui/react-context-menu'; import { VariantProps, cva } from 'class-variance-authority'; import clsx from 'clsx'; -import { CaretRight, Icon } from 'phosphor-react'; +import { CaretRight, Icon, IconProps } from 'phosphor-react'; import { PropsWithChildren, Suspense } from 'react'; -interface Props extends RadixCM.MenuContentProps { +interface ContextMenuProps extends RadixCM.MenuContentProps { trigger: React.ReactNode; } -const MENU_CLASSES = ` - flex flex-col z-50 - min-w-[8rem] px-1 py-0.5 my-2 - text-left text-sm text-menu-ink - bg-menu cool-shadow - border border-menu-line - select-none cursor-default rounded-md -`; +export const contextMenuClasses = clsx( + 'z-50 flex flex-col', + 'my-2 min-w-[8rem] px-1 py-0.5', + 'text-menu-ink text-left text-sm', + 'bg-menu cool-shadow', + 'border-menu-line border', + 'cursor-default select-none rounded-md' +); -export const Root = ({ trigger, children, className, ...props }: PropsWithChildren) => { +const Root = ({ trigger, children, className, ...props }: PropsWithChildren) => { return ( {trigger} - + {children} @@ -30,33 +30,39 @@ export const Root = ({ trigger, children, className, ...props }: PropsWithChildr ); }; -export const Separator = () => ( - +export const contextMenuSeparatorClassNames = + 'border-b-menu-line pointer-events-none mx-2 my-1 border-0 border-b'; + +const Separator = (props: { className?: string }) => ( + ); -export const SubMenu = ({ +export const contextSubMenuTriggerClassNames = + "[&[data-state='open']_div]:bg-accent text-menu-ink py-[3px] focus:outline-none [&[data-state='open']_div]:text-white"; + +const SubMenu = ({ label, icon, className, ...props -}: RadixCM.MenuSubContentProps & ItemProps) => { +}: RadixCM.MenuSubContentProps & ContextMenuItemProps) => { return ( - + - + ); }; -const itemStyles = cva( +export const contextMenuItemStyles = cva( [ - 'flex flex-1 flex-row items-center justify-start', + 'flex flex-1 flex-row items-center justify-start overflow-hidden', 'space-x-2 px-2 py-[3px]', 'cursor-default rounded', 'focus:outline-none' @@ -78,46 +84,49 @@ const itemStyles = cva( } ); -interface ItemProps extends VariantProps { +export interface ContextMenuItemProps extends VariantProps { icon?: Icon; + iconProps?: IconProps; rightArrow?: boolean; label?: string; keybind?: string; } -export const Item = ({ +const Item = ({ icon, label, rightArrow, children, keybind, variant, - ...props -}: ItemProps & RadixCM.MenuItemProps) => { +}: ContextMenuItemProps & RadixCM.MenuItemProps) => { return ( - -
+ +
{children ? children : }
); }; -const DivItem = ({ variant, ...props }: ItemProps) => ( -
+const DivItem = ({ variant, ...props }: ContextMenuItemProps) => ( +
); -const ItemInternals = ({ icon, label, rightArrow, keybind }: ItemProps) => { +export const ItemInternals = ({ + icon, + label, + rightArrow, + keybind, + iconProps +}: ContextMenuItemProps) => { const ItemIcon = icon; return ( <> - {ItemIcon && } + {ItemIcon && } {label &&

{label}

} {keybind && ( @@ -134,3 +143,10 @@ const ItemInternals = ({ icon, label, rightArrow, keybind }: ItemProps) => { ); }; + +export const ContextMenu = { + Root, + Item, + Separator, + SubMenu +}; diff --git a/packages/ui/src/Dropdown.tsx b/packages/ui/src/Dropdown.tsx index 41383429f..62eb61f25 100644 --- a/packages/ui/src/Dropdown.tsx +++ b/packages/ui/src/Dropdown.tsx @@ -2,7 +2,7 @@ import { ReactComponent as CaretDown } from '@sd/assets/svgs/caret.svg'; import { Menu, Transition } from '@headlessui/react'; import { VariantProps, cva } from 'class-variance-authority'; import clsx from 'clsx'; -import { Fragment, PropsWithChildren } from 'react'; +import { Fragment, PropsWithChildren, forwardRef } from 'react'; import { Link } from 'react-router-dom'; import * as UI from '.'; import { tw } from './utils'; @@ -60,18 +60,20 @@ export const Item = ({ to, className, icon: Icon, children, ...props }: Dropdown ); }; -export const Button = ({ children, className, ...props }: UI.ButtonProps) => { - return ( - - {children} - - - ); -}; +export const Button = forwardRef( + ({ children, className, ...props }, ref) => { + return ( + + {children} + + + ); + } +); export interface DropdownRootProps { button: React.ReactNode; diff --git a/packages/ui/src/DropdownMenu.tsx b/packages/ui/src/DropdownMenu.tsx new file mode 100644 index 000000000..9340f51f8 --- /dev/null +++ b/packages/ui/src/DropdownMenu.tsx @@ -0,0 +1,162 @@ +import * as RadixDM from '@radix-ui/react-dropdown-menu'; +import clsx from 'clsx'; +import React, { PropsWithChildren, Suspense, useCallback, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { + ContextMenuItemProps, + ItemInternals, + contextMenuClasses, + contextMenuItemStyles, + contextMenuSeparatorClassNames, + contextSubMenuTriggerClassNames +} from './ContextMenu'; + +interface DropdownMenuProps + extends RadixDM.MenuContentProps, + Pick { + trigger: React.ReactNode; + triggerClassName?: string; + alignToTrigger?: boolean; + animate?: boolean; +} + +const Root = ({ + trigger, + children, + className, + asChild = true, + triggerClassName, + alignToTrigger, + onOpenChange, + animate, + ...props +}: PropsWithChildren) => { + const [width, setWidth] = useState(); + + const measureRef = useCallback((ref: HTMLButtonElement | null) => { + alignToTrigger && ref && setWidth(ref.getBoundingClientRect().width); + }, []); + + return ( + + + {trigger} + + +
+
+ + {children} + +
+
+
+ ); +}; + +const Separator = (props: { className?: string }) => ( + +); + +const SubMenu = ({ + label, + icon, + className, + ...props +}: RadixDM.MenuSubContentProps & ContextMenuItemProps) => { + return ( + + +
+ +
+
+ + + + + +
+ ); +}; + +interface DropdownItemProps extends ContextMenuItemProps, RadixDM.MenuItemProps { + to?: string; + selected?: boolean; +} + +const Item = ({ + icon, + iconProps, + label, + rightArrow, + children, + keybind, + variant, + className, + selected, + to, + ...props +}: DropdownItemProps) => { + const ref = useRef(null); + + return ( + + {to ? ( + ref.current?.click()} + > + {children ? ( + {children} + ) : ( + + )} + + ) : ( +
+ {children || } +
+ )} +
+ ); +}; + +export const DropdownMenu = { + Root, + Item, + Separator, + SubMenu +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 953bde36f..04103c51e 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,7 +1,8 @@ export { cva, cx } from 'class-variance-authority'; export * from './Button'; export * from './CheckBox'; -export * as ContextMenu from './ContextMenu'; +export { ContextMenu } from './ContextMenu'; +export { DropdownMenu } from './DropdownMenu'; export * from './Dialog'; export * as Dropdown from './Dropdown'; export * from './Input'; diff --git a/packages/ui/style/tailwind.js b/packages/ui/style/tailwind.js index 9d8b5b7d0..6bf664bde 100644 --- a/packages/ui/style/tailwind.js +++ b/packages/ui/style/tailwind.js @@ -162,6 +162,7 @@ module.exports = function (app, options) { // addVariant('open', '&[data-state="open"]'); // addVariant('closed', '&[data-state="closed"]'); // }), + require('tailwindcss-animate'), require('@headlessui/tailwindcss'), require('tailwindcss-radix')() ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8b3c4c5e7fcaefa39ae75e82e20ad1036d8e261..76e0ef5d3df32e65061e751ada1fa13734cc0ff3 100644 GIT binary patch delta 253 zcmZ4a+Gz7TqYa1s`4jUpa}!HatrX%6^$he(H*fHd=aNj!%qh>zOGz#+)`iQ1r6*6c zmYkevDAcSF)vgf52*gZ4%nZaVK+FonY(UHo#2i4(3B+966{5KRY4bwO(FYqd{lG~U z^U%;xF z+|nRdH^-=y+%R`jpMX?TS4(Hd;t~rJvtS=*lPptDRpqYa1sH$V0F;hKC>SFpJ>s=YLd5r~<9m>Gy!fS47C*?^cGh&h0m6NtIC Umqu~_({5kU&b@s_J5RSh00pBTKL7v#