From 932f6aa03cde4007a6e65100869e8403de525552 Mon Sep 17 00:00:00 2001 From: Edd <77212889+SinanovicEdis@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:52:31 +0200 Subject: [PATCH] chore: improve dropdowns (#626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Urban Vidovič Co-authored-by: edis Co-authored-by: Urban --- .changeset/slow-cats-love.md | 5 + packages/dapp/components.json | 17 + packages/dapp/package.json | 10 +- packages/dapp/public/images/ethereum_logo.svg | 4 + .../dapp/public/images/polygon_matic_logo.svg | 3 + .../src/components/AddressPopover/index.tsx | 22 +- .../components/AppNavbar/NavConnection.tsx | 14 +- .../dapp/src/components/AppNavbar/index.tsx | 2 - .../MethodDropdownButton.tsx | 99 +- .../components/MethodDropdownMenu/index.tsx | 114 +- .../NetworkDropdownMenuItem.tsx | 109 + .../components/NetworkDropDownMenu/index.tsx | 193 + .../src/components/SettingsCard/index.tsx | 12 +- .../dapp/src/components/ToggleTheme/index.tsx | 2 +- .../dapp/src/components/ui/dropdown-menu.tsx | 203 + packages/dapp/src/components/ui/switch.tsx | 29 + packages/dapp/src/lib/utils.ts | 6 + packages/dapp/src/messages/en.json | 3 +- packages/dapp/src/utils/networks.ts | 37 +- packages/dapp/tailwind.config.js | 11 + pnpm-lock.yaml | 4209 ++++++++++++++--- 21 files changed, 4267 insertions(+), 837 deletions(-) create mode 100644 .changeset/slow-cats-love.md create mode 100644 packages/dapp/components.json create mode 100644 packages/dapp/public/images/ethereum_logo.svg create mode 100644 packages/dapp/public/images/polygon_matic_logo.svg create mode 100644 packages/dapp/src/components/NetworkDropDownMenu/NetworkDropdownMenuItem.tsx create mode 100644 packages/dapp/src/components/NetworkDropDownMenu/index.tsx create mode 100644 packages/dapp/src/components/ui/dropdown-menu.tsx create mode 100644 packages/dapp/src/components/ui/switch.tsx create mode 100644 packages/dapp/src/lib/utils.ts diff --git a/.changeset/slow-cats-love.md b/.changeset/slow-cats-love.md new file mode 100644 index 000000000..ae74da36f --- /dev/null +++ b/.changeset/slow-cats-love.md @@ -0,0 +1,5 @@ +--- +"@blockchain-lab-um/dapp": patch +--- + +Improves dropdowns. diff --git a/packages/dapp/components.json b/packages/dapp/components.json new file mode 100644 index 000000000..05f0531b6 --- /dev/null +++ b/packages/dapp/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/packages/dapp/package.json b/packages/dapp/package.json index 72f3c07d4..ad1f42f92 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -26,10 +26,14 @@ "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", "@nextui-org/react": "^2.2.10", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.5", "@react-oauth/google": "^0.12.1", "@supabase/supabase-js": "^2.39.7", - "@tanstack/react-query": "^5.28.4", + "@tanstack/react-query": "^5.32.0", "@tanstack/react-table": "^8.13.2", "@types/dompurify": "^3.0.5", "@types/js-cookie": "^3.0.6", @@ -46,6 +50,7 @@ "@veramo/utils": "6.0.0", "@vercel/analytics": "^1.2.2", "@vercel/og": "^0.6.2", + "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "date-fns": "^3.3.1", "did-jwt-vc": "^3.2.13", @@ -63,6 +68,7 @@ "js-cookie": "^3.0.5", "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.367.0", "luxon": "^3.4.4", "marked": "^12.0.1", "next": "14.1.3", @@ -77,7 +83,9 @@ "sharp": "^0.33.2", "siwe": "^2.1.4", "swr": "^2.2.5", + "tailwind-merge": "^2.2.2", "tailwind-scrollbar": "^3.1.0", + "tailwindcss-animate": "^1.0.7", "viem": "^2.9.23", "wagmi": "^2.5.20", "zustand": "^4.5.2" diff --git a/packages/dapp/public/images/ethereum_logo.svg b/packages/dapp/public/images/ethereum_logo.svg new file mode 100644 index 000000000..15053e920 --- /dev/null +++ b/packages/dapp/public/images/ethereum_logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/dapp/public/images/polygon_matic_logo.svg b/packages/dapp/public/images/polygon_matic_logo.svg new file mode 100644 index 000000000..074d2d617 --- /dev/null +++ b/packages/dapp/public/images/polygon_matic_logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/dapp/src/components/AddressPopover/index.tsx b/packages/dapp/src/components/AddressPopover/index.tsx index 25a46a165..5417cccba 100644 --- a/packages/dapp/src/components/AddressPopover/index.tsx +++ b/packages/dapp/src/components/AddressPopover/index.tsx @@ -9,6 +9,7 @@ import Image from 'next/image'; import { mainnet } from 'viem/chains'; import { normalize } from 'viem/ens'; import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'; +import ToggleTheme from '@/components/ToggleTheme'; import { copyToClipboard } from '@/utils/string'; import { TextSkeleton } from '../Skeletons/TextSkeleton'; @@ -119,14 +120,19 @@ const AddressPopover = ({ did, disconnect }: AddressPopoverProps) => { -
- +
+
+ +
+
+ +
diff --git a/packages/dapp/src/components/AppNavbar/NavConnection.tsx b/packages/dapp/src/components/AppNavbar/NavConnection.tsx index bbaf8126d..c48cb4449 100644 --- a/packages/dapp/src/components/AppNavbar/NavConnection.tsx +++ b/packages/dapp/src/components/AppNavbar/NavConnection.tsx @@ -7,7 +7,7 @@ import { useAccount, useChainId, useDisconnect, useSwitchChain } from 'wagmi'; import AddressPopover from '@/components/AddressPopover'; import ConnectButton from '@/components/ConnectButton'; -import DropdownMenu from '@/components/DropdownMenu'; + import MethodDropdownMenu from '@/components/MethodDropdownMenu'; import { useMascaStore } from '@/stores'; import { @@ -16,6 +16,8 @@ import { getAvailableNetworksList, } from '@/utils/networks'; +import NetworkDropDownMenu from '@/components/NetworkDropDownMenu'; + export const NavConnection = () => { const { switchChain } = useSwitchChain(); const t = useTranslations('NavConnection'); @@ -39,7 +41,7 @@ export const NavConnection = () => { (NETWORKS_BY_DID[currMethod].includes(stringified) || NETWORKS_BY_DID[currMethod].includes('*')) ) { - return network; + return network.name; } return t('unsupported-network'); }; @@ -57,7 +59,9 @@ export const NavConnection = () => { }, [chainId, currMethod]); const setNetwork = async (network: string) => { - const key = Object.keys(NETWORKS).find((val) => NETWORKS[val] === network); + const key = Object.keys(NETWORKS).find( + (val) => NETWORKS[val].name === network + ); if (key) { switchChain( { chainId: Number(key) }, @@ -73,14 +77,14 @@ export const NavConnection = () => { }; return isConnected ? ( -
+
{(currMethod === 'did:ethr' || currMethod === 'did:pkh' || currMethod === 'did:polygonid' || currMethod === 'did:ens' || currMethod === 'did:iden3') && (
-
-
diff --git a/packages/dapp/src/components/MethodDropdownMenu/MethodDropdownButton.tsx b/packages/dapp/src/components/MethodDropdownMenu/MethodDropdownButton.tsx index fe21a5687..22c0f0ef4 100644 --- a/packages/dapp/src/components/MethodDropdownMenu/MethodDropdownButton.tsx +++ b/packages/dapp/src/components/MethodDropdownMenu/MethodDropdownButton.tsx @@ -1,49 +1,86 @@ -import { Menu } from '@headlessui/react'; import { CheckIcon } from '@heroicons/react/24/solid'; +import { DropdownMenuItem } from '@radix-ui/react-dropdown-menu'; import { clsx } from 'clsx'; +import { useState } from 'react'; interface DropdownButtonProps { children: React.ReactNode; handleBtn: (text: string) => Promise; selected: boolean; + variant?: + | 'primary' + | 'secondary' + | 'primary-active' + | 'secondary-active' + | 'gray' + | 'method'; } -export const DropdownButton = ({ +const variants: Record = { + primary: + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark/95 animated-transition cursor-pointer bg-pink-50 text-pink-600', + secondary: 'bg-navy-blue-100 text-navy-blue-600 ', + 'primary-active': + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark/95 animated-transition cursor-pointer bg-pink-50 text-pink-600', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600', + gray: 'bg-gray-100 text-gray-800 ', + method: + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark animated-transition cursor-pointer bg-pink-50 text-pink-600', +}; + +const variantsSelected: Record = { + primary: + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', + secondary: 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + 'primary-active': + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + gray: 'bg-gray-100 font-semibold text-gray-900', + method: + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', +}; + +const variantsSelectedElse: Record = { + primary: 'dark:text-navy-blue-100 text-gray-600', + secondary: 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + 'primary-active': 'dark:text-navy-blue-100 text-gray-600 ', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + gray: 'bg-gray-100 font-semibold text-gray-900', + method: '', +}; + +export function DropdownButton({ children, handleBtn, selected, -}: DropdownButtonProps) => ( - - {({ active }) => ( - { - handleBtn(children as string) - .then(() => {}) - .catch(() => {}); - }} + variant = 'primary', +}: DropdownButtonProps) { + const [isActive, setIsActive] = useState(false); + const handleMouseEnter = () => setIsActive(true); + const handleMouseLeave = () => setIsActive(false); + + return ( + + + + ); +} diff --git a/packages/dapp/src/components/MethodDropdownMenu/index.tsx b/packages/dapp/src/components/MethodDropdownMenu/index.tsx index 3ce35b402..658da8885 100644 --- a/packages/dapp/src/components/MethodDropdownMenu/index.tsx +++ b/packages/dapp/src/components/MethodDropdownMenu/index.tsx @@ -5,11 +5,17 @@ import { isError, requiresNetwork, } from '@blockchain-lab-um/masca-connector'; -import { Menu, Transition } from '@headlessui/react'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; + import { ChevronDownIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import { useTranslations } from 'next-intl'; -import { Fragment } from 'react'; +import { useState } from 'react'; import { useChainId, useSwitchChain } from 'wagmi'; import { useMascaStore, useToastStore } from '@/stores'; @@ -95,67 +101,51 @@ export default function MethodDropdownMenu() { } }; - return ( - - {({ open }) => ( - -
- - {currMethod ? ( - currMethod === 'did:key:jwk_jcs-pub' ? ( - 'did:key (EBSI)' - ) : ( - currMethod - ) - ) : ( - - )} - {currMethod && ( - - )} - -
+ const [open, setOpen] = useState(false); - - {currMethod && ( - -
- {methods.map((method) => ( - - {method} - - ))} -
-
+ return ( + setOpen(!open)}> + + {currMethod ? ( + currMethod === 'did:key:jwk_jcs-pub' ? ( + 'did:key (EBSI)' + ) : ( + currMethod + ) + ) : ( + + )} + {currMethod && ( + -
- )} -
+ /> + )} + + + +
+ {methods.map((method) => ( + + {method} + + ))} +
+
+ ); } diff --git a/packages/dapp/src/components/NetworkDropDownMenu/NetworkDropdownMenuItem.tsx b/packages/dapp/src/components/NetworkDropDownMenu/NetworkDropdownMenuItem.tsx new file mode 100644 index 000000000..34de88e86 --- /dev/null +++ b/packages/dapp/src/components/NetworkDropDownMenu/NetworkDropdownMenuItem.tsx @@ -0,0 +1,109 @@ +import { CheckIcon } from '@heroicons/react/24/outline'; +import { clsx } from 'clsx'; + +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; +import type { Network } from '@/utils/networks'; + +import Image from 'next/image'; +import { useState } from 'react'; +import { useTheme } from 'next-themes'; + +interface DropdownMenuItemProps { + children: Network; + handleBtn: (text: string) => void; + selected: boolean; + variant?: + | 'primary' + | 'secondary' + | 'primary-active' + | 'secondary-active' + | 'gray' + | 'method'; +} + +const variants: Record = { + primary: + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark/95 animated-transition cursor-pointer bg-pink-50 text-pink-600', + secondary: 'bg-navy-blue-100 text-navy-blue-600 ', + 'primary-active': + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark/95 animated-transition cursor-pointer bg-pink-50 text-pink-600', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600', + gray: 'bg-gray-100 text-gray-800 ', + method: + 'dark:bg-navy-blue-500 dark:text-orange-accent-dark animated-transition cursor-pointer bg-pink-50 text-pink-600', +}; + +const variantsSelected: Record = { + primary: + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', + secondary: 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + 'primary-active': + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + gray: 'bg-gray-100 font-semibold text-gray-900', + method: + 'dark:text-orange-accent-dark dark:bg-navy-blue-600 bg-white text-pink-700', +}; + +const variantsSelectedElse: Record = { + primary: 'dark:text-navy-blue-100 text-gray-600', + secondary: 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + 'primary-active': 'dark:text-navy-blue-100 text-gray-600 ', + 'secondary-active': 'bg-navy-blue-100 text-navy-blue-600 font-semibold', + gray: 'bg-gray-100 font-semibold text-gray-900', + method: '', +}; + +export default function NetworkDropdownMenuItem({ + children, + handleBtn, + selected, + variant = 'primary', +}: DropdownMenuItemProps) { + const { resolvedTheme } = useTheme(); + const [isActive, setIsActive] = useState(false); + const handleMouseEnter = () => setIsActive(true); + const handleMouseLeave = () => setIsActive(false); + + const networkBackgroundColor = + resolvedTheme === 'dark' ? '#ffffffbf' : children.backgroundColor; + + return ( + + + + ); +} diff --git a/packages/dapp/src/components/NetworkDropDownMenu/index.tsx b/packages/dapp/src/components/NetworkDropDownMenu/index.tsx new file mode 100644 index 000000000..821b44370 --- /dev/null +++ b/packages/dapp/src/components/NetworkDropDownMenu/index.tsx @@ -0,0 +1,193 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu'; + +import { Switch } from '@/components/ui/switch'; + +import Image from 'next/image'; + +import clsx from 'clsx'; +import NetworkDropdownMenuItem from './NetworkDropdownMenuItem'; +import { ChevronDownIcon } from 'lucide-react'; + +import type { Network } from '@/utils/networks'; +import { useToastStore } from '@/stores'; +import { useTranslations } from 'next-intl'; +import { useTheme } from 'next-themes'; + +interface DropdownMenuProps { + items: Network[]; + multiple?: boolean; + selected: string; + variant?: + | 'primary' + | 'secondary' + | 'primary-active' + | 'secondary-active' + | 'gray' + | 'method'; + size?: 'xs' | 'sm' | 'md' | 'lg' | 'method'; + rounded?: 'full' | '2xl' | 'xl' | 'lg' | 'none'; + shadow?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'none' | 'inner' | ''; + setSelected: (selected: string) => void; +} + +const sizes: Record = { + xs: 'md:text-sm md:py-1 md:px-3 text-sm py-1 px-2 max-w-xs', + sm: 'md:text-md md:py-1.5 md:px-3.5 text-md font-medium py-1 px-3 max-w-xs', + md: 'lg:text-lg lg:py-2 lg:px-4 md:text-md font-medium md:py-1.5 md:px-3.5 text-sm py-1 px-3 max-w-xs', + lg: 'lg:text-2xl lg:py-2.5 lg:px-5 md:text-lg md:py-2 font-medium md:px-4 text-md py-1.5 px-3.5 max-w-xs font-semibold max-w-xs', + method: + 'text-h5 font-ubuntu animated-transition inline-flex w-full justify-center rounded-3xl px-4 py-2 font-thin focus:outline-none', +}; + +const variants: Record = { + primary: + 'bg-pink-500 dark:bg-orange-accent-dark hover:opacity-80 text-white dark:text-navy-blue-800 animated-transition ', + secondary: + 'text-pink-500 border-[0.135rem] border-pink-500 dark:text-orange-accent-dark dark:border-orange-accent-dark animated-transition ', + 'primary-active': + 'text-pink-500 border-[0.135rem] border-pink-500 dark:text-orange-accent-dark dark:border-orange-accent-dark animated-transition ', + 'secondary-active': + 'text-navy-blue-500 border border-1 border-navy-blue-300 animated-transition ', + gray: 'bg-gray-200 text-gray-800 btn hover:opacity-80 animated-transition ', + method: + 'dark:text-navy-blue-400 text-gray-600 dark:hover:bg-navy-blue-800 hover:bg-orange-100/50', +}; + +const variantsHover: Record = { + primary: 'bg-pink-500 dark:bg-orange-accent-dark opacity-80', + secondary: 'bg-navy-blue-500 text-white btn hover:opacity-80 ', + 'primary-active': ' ', + 'secondary-active': ' ', + gray: 'opacity-80', + method: 'dark:bg-navy-blue-800 bg-orange-100/50', +}; + +function isSelectedTestnet(selected: string, items: Network[]): boolean { + return items.find((item) => item.name === selected)?.isTestnet === true; +} + +export default function NetworkDropDownMenu({ + items, + selected, + setSelected, + variant = 'primary', + size = 'md', + rounded = 'full', + shadow = 'sm', +}: DropdownMenuProps) { + const t = useTranslations('NavConnection'); + const { resolvedTheme } = useTheme(); + const [open, setOpen] = useState(false); + + const [showTestNets, setShowTestNets] = useState(false); + + const filteredNetworks = items.filter( + (item) => showTestNets || !item.isTestnet + ); + + const networkBackgroundColor = + resolvedTheme === 'dark' + ? '#ffffffbf' + : items.find((item) => item.name === selected)?.backgroundColor; + + useEffect(() => { + const isTestnet = isSelectedTestnet(selected, items); + setShowTestNets(isTestnet); + }, [selected]); + + return ( + setOpen(!open)}> + +
+ {selected && ( + item.name === selected)?.logo ?? ''} + alt={selected} + style={{ + width: '100%', + height: 'auto', + backgroundColor: networkBackgroundColor, + borderRadius: '25%', + }} + width={16} + height={16} + /> + )} +
+ {selected && ( +
+
+
+ + +
+ {filteredNetworks.map((item) => ( + + ))} +
+ + + +
+ Show testnets +
+ { + if (isSelectedTestnet(selected, items)) { + setTimeout(() => { + useToastStore.setState({ + open: true, + title: t('disable-testnet-switch'), + type: 'error', + loading: false, + link: null, + }); + }); + } else { + setShowTestNets(!showTestNets); + } + }} + className={clsx( + showTestNets ? 'bg-red-500 dark:bg-orange-accent-dark' : '', + 'scale-80' + )} + /> +
+
+
+
+ ); +} diff --git a/packages/dapp/src/components/SettingsCard/index.tsx b/packages/dapp/src/components/SettingsCard/index.tsx index b4c550492..d19cf0052 100644 --- a/packages/dapp/src/components/SettingsCard/index.tsx +++ b/packages/dapp/src/components/SettingsCard/index.tsx @@ -1,10 +1,8 @@ 'use client'; import { isError } from '@blockchain-lab-um/masca-connector'; -import { ArrowLeftIcon } from '@heroicons/react/20/solid'; import { saveAs } from 'file-saver'; import { useTranslations } from 'next-intl'; -import Link from 'next/link'; import ToggleSwitch from '@/components/Switch'; import { useMascaStore, useToastStore } from '@/stores'; @@ -192,15 +190,7 @@ const SettingsCard = () => { return (
-
- - - +
{t('title')}
diff --git a/packages/dapp/src/components/ToggleTheme/index.tsx b/packages/dapp/src/components/ToggleTheme/index.tsx index bab04c231..f6df7c3d4 100644 --- a/packages/dapp/src/components/ToggleTheme/index.tsx +++ b/packages/dapp/src/components/ToggleTheme/index.tsx @@ -23,7 +23,7 @@ const ToggleTheme = () => {