feat: create shared dropdown component
This commit is contained in:
parent
37f84cfba5
commit
381c3d6df4
84
app/components/Dropdown.tsx
Normal file
84
app/components/Dropdown.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Menu, type MenuButtonProps, Transition } from '@headlessui/react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { Fragment, type HTMLProps, type ReactNode } from 'react'
|
||||||
|
|
||||||
|
type Properties = {
|
||||||
|
readonly children: ReactNode;
|
||||||
|
readonly button: ReactNode;
|
||||||
|
// eslint-disable-next-line unicorn/no-keyword-prefix
|
||||||
|
readonly className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dropdown(properties: Properties) {
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<Menu>
|
||||||
|
<Button className='flex flex-col items-center'>
|
||||||
|
{properties.button}
|
||||||
|
</Button>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter='transition ease-out duration-100'
|
||||||
|
enterFrom='transform opacity-0 scale-95'
|
||||||
|
enterTo='transform opacity-100 scale-100'
|
||||||
|
leave='transition ease-in duration-75'
|
||||||
|
leaveFrom='transform opacity-100 scale-100'
|
||||||
|
leaveTo='transform opacity-0 scale-95'
|
||||||
|
>
|
||||||
|
<Menu.Items className={clsx(
|
||||||
|
'absolute right-0 w-36 mt-2 rounded-md',
|
||||||
|
'text-gray-700 dark:text-gray-300',
|
||||||
|
'bg-white dark:bg-zinc-800 text-right',
|
||||||
|
'overflow-hidden',
|
||||||
|
'border border-gray-200 dark:border-zinc-700',
|
||||||
|
'divide-y divide-gray-200 dark:divide-zinc-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{properties.children}
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button(properties: MenuButtonProps<'button'>) {
|
||||||
|
return (
|
||||||
|
<Menu.Button
|
||||||
|
{...properties}
|
||||||
|
className={clsx(
|
||||||
|
properties.className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{properties.children}
|
||||||
|
</Menu.Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemProperties = HTMLProps<HTMLDivElement> & {
|
||||||
|
variant?: 'static' | 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item(properties: ItemProperties) {
|
||||||
|
return (
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => (
|
||||||
|
<div
|
||||||
|
{...properties}
|
||||||
|
className={clsx(
|
||||||
|
'px-4 py-2 w-full text-right',
|
||||||
|
'focus:outline-none focus:ring',
|
||||||
|
'focus:ring-gray-300 dark:focus:ring-zinc-700',
|
||||||
|
properties.className,
|
||||||
|
properties.variant !== 'static' && active
|
||||||
|
? 'bg-gray-100 dark:bg-zinc-500' : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{properties.children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Object.assign(Dropdown, { Item })
|
||||||
@ -1,10 +1,8 @@
|
|||||||
import { Menu, Transition } from '@headlessui/react'
|
|
||||||
import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIcon, UserCircleIcon, UsersIcon } from '@heroicons/react/24/outline'
|
import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIcon, UserCircleIcon, UsersIcon } from '@heroicons/react/24/outline'
|
||||||
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
||||||
import { Form, Outlet, useLoaderData, useRouteError } from '@remix-run/react'
|
import { Form, Outlet, useLoaderData, useRouteError } from '@remix-run/react'
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Fragment } from 'react/jsx-runtime'
|
|
||||||
|
|
||||||
|
import Dropdown from '~/components/Dropdown'
|
||||||
import { ErrorPopup } from '~/components/Error'
|
import { ErrorPopup } from '~/components/Error'
|
||||||
import TabLink from '~/components/TabLink'
|
import TabLink from '~/components/TabLink'
|
||||||
import { getContext } from '~/utils/config'
|
import { getContext } from '~/utils/config'
|
||||||
@ -64,56 +62,21 @@ export default function Layout() {
|
|||||||
<a href='https://github.com/juanfont/headscale' target='_blank' rel='noreferrer' className='text-gray-300 hover:text-white'>
|
<a href='https://github.com/juanfont/headscale' target='_blank' rel='noreferrer' className='text-gray-300 hover:text-white'>
|
||||||
Headscale
|
Headscale
|
||||||
</a>
|
</a>
|
||||||
<div className='relative'>
|
<Dropdown
|
||||||
<Menu>
|
button={<UserCircleIcon className='w-8 h-8'/>}
|
||||||
<Menu.Button>
|
>
|
||||||
<UserCircleIcon className='w-8 h-8'/>
|
<Dropdown.Item variant='static'>
|
||||||
</Menu.Button>
|
<p className='font-bold'>{data.user?.name}</p>
|
||||||
<Transition
|
<p>{data.user?.email}</p>
|
||||||
as={Fragment}
|
</Dropdown.Item>
|
||||||
enter='transition ease-out duration-100'
|
<Dropdown.Item className='text-red-700'>
|
||||||
enterFrom='transform opacity-0 scale-95'
|
<Form method='POST' action='/logout'>
|
||||||
enterTo='transform opacity-100 scale-100'
|
<button type='submit'>
|
||||||
leave='transition ease-in duration-75'
|
Logout
|
||||||
leaveFrom='transform opacity-100 scale-100'
|
</button>
|
||||||
leaveTo='transform opacity-0 scale-95'
|
</Form>
|
||||||
>
|
</Dropdown.Item>
|
||||||
<Menu.Items className={clsx(
|
</Dropdown>
|
||||||
'absolute right-0 w-36 mt-2 rounded-md',
|
|
||||||
'text-gray-700 dark:text-gray-300',
|
|
||||||
'bg-white dark:bg-zinc-800 text-right',
|
|
||||||
'overflow-hidden',
|
|
||||||
'border border-gray-200 dark:border-zinc-500',
|
|
||||||
'divide-y divide-gray-200 dark:divide-zinc-500'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Menu.Item>
|
|
||||||
{() => (
|
|
||||||
<div className='px-4 py-2'>
|
|
||||||
<p>{data.user?.name}</p>
|
|
||||||
<p>{data.user?.email}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => (
|
|
||||||
<Form method='POST' action='/logout'>
|
|
||||||
<button
|
|
||||||
type='submit'
|
|
||||||
className={clsx(
|
|
||||||
'px-4 py-2 w-full text-right',
|
|
||||||
active ? 'bg-gray-200 dark:bg-zinc-500' : ''
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-x-4'>
|
<div className='flex items-center gap-x-4'>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user