feat: create shared button component

This commit is contained in:
Aarnav Tale 2024-03-29 15:58:22 -04:00
parent b5658750a9
commit 4a1cdaa432
No known key found for this signature in database
8 changed files with 67 additions and 65 deletions

View File

@ -1,25 +0,0 @@
import clsx from 'clsx'
import { type HTMLProps } from 'react'
type Properties = HTMLProps<HTMLButtonElement> & {
readonly isDestructive?: boolean;
readonly isDisabled?: boolean;
}
export default function Action(properties: Properties) {
return (
<button
{...properties}
type='button'
className={clsx(
properties.className,
properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.isDestructive
? 'text-red-700 dark:text-red-500'
: 'text-blue-700 dark:text-blue-400'
)}
>
{properties.children}
</button>
)
}

26
app/components/Button.tsx Normal file
View File

@ -0,0 +1,26 @@
import clsx from 'clsx'
import { type ButtonHTMLAttributes, type DetailedHTMLProps } from 'react'
type Properties = {
readonly variant?: 'emphasized' | 'normal' | 'destructive';
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
export default function Action(properties: Properties) {
return (
<button
type='button'
{...properties}
className={clsx(
'focus:outline-none focus:ring focus:ring-1',
'focus:ring-blue-500 dark:focus:ring-blue-300',
properties.className,
properties.disabled && 'opacity-50 cursor-not-allowed',
properties.variant === 'destructive' ? 'text-red-700 dark:text-red-500' : '',
properties.variant === 'emphasized' ? 'rounded-lg px-4 py-2 bg-gray-800 dark:bg-gray-700 text-white' : '',
!properties.variant || properties.variant === 'normal' ? 'text-blue-700 dark:text-blue-400' : ''
)}
>
{properties.children}
</button>
)
}

View File

@ -2,7 +2,7 @@ import clsx from 'clsx'
import { type DetailedHTMLProps, type InputHTMLAttributes } from 'react'
type Properties = {
readonly isEmbedded?: boolean;
readonly variant?: 'embedded' | 'normal';
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
export default function Input(properties: Properties) {
@ -14,11 +14,11 @@ export default function Input(properties: Properties) {
'border-gray-300 dark:border-zinc-700',
'focus:outline-none focus:ring',
'focus:ring-blue-500 dark:focus:ring-blue-300',
properties.isEmbedded ? 'bg-transparent' : 'dark:bg-zinc-800',
properties.isEmbedded ? 'p-0' : 'px-2.5 py-1.5',
properties.isEmbedded ? 'border-none' : 'border',
properties.isEmbedded ? 'focus:ring-0' : 'focus:ring-1',
properties.isEmbedded ? 'rounded-none' : 'rounded-lg',
properties.variant === 'embedded' ? 'bg-transparent' : 'dark:bg-zinc-800',
properties.variant === 'embedded' ? 'p-0' : 'px-2.5 py-1.5',
properties.variant === 'embedded' ? 'border-none' : 'border',
properties.variant === 'embedded' ? 'focus:ring-0' : 'focus:ring-1',
properties.variant === 'embedded' ? 'rounded-none' : 'rounded-lg',
properties.className
)}
/>

View File

@ -20,7 +20,7 @@ import { useFetcher } from '@remix-run/react'
import clsx from 'clsx'
import { useEffect, useState } from 'react'
import Action from '~/components/Action'
import Button from '~/components/Button'
import Input from '~/components/Input'
import TableList from '~/components/TableList'
@ -102,7 +102,7 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
</SortableContext>
<TableList.Item key='add-sd'>
<Input
isEmbedded
variant='embedded'
type='text'
className='font-mono text-sm'
placeholder='Search Domain'
@ -111,9 +111,9 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
setNewDomain(event.target.value)
}}
/>
<Action
<Button
className='text-sm'
isDisabled={newDomain.length === 0}
disabled={newDomain.length === 0}
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
@ -127,7 +127,7 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
}}
>
Add
</Action>
</Button>
</TableList.Item>
</TableList>
</DndContext>
@ -177,8 +177,8 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
{domain}
</p>
{isDrag ? undefined : (
<Action
isDestructive
<Button
variant='destructive'
className='text-sm'
onClick={() => {
fetcher.submit({
@ -191,7 +191,7 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
}}
>
Remove
</Action>
</Button>
)}
</div>
)

View File

@ -3,26 +3,27 @@ import { useFetcher } from '@remix-run/react'
import clsx from 'clsx'
import { useState } from 'react'
import Button from '~/components/Button'
type Properties = {
readonly isEnabled: boolean;
readonly baseDomain: string;
}
export default function Modal({ isEnabled, baseDomain }: Properties) {
export default function Modal({ isEnabled }: Properties) {
const [isOpen, setIsOpen] = useState(false)
const fetcher = useFetcher()
return (
<>
<button
type='button'
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm'
<Button
variant='emphasized'
className='w-fit text-sm'
onClick={() => {
setIsOpen(true)
}}
>
{isEnabled ? 'Disable' : 'Enable'} Magic DNS
</button>
</Button>
<Dialog
className='relative z-50'
open={isOpen} onClose={() => {
@ -39,11 +40,12 @@ export default function Modal({ isEnabled, baseDomain }: Properties) {
Devices will no longer be accessible via your tailnet domain.
The search domain will also be disabled.
</Dialog.Description>
<button
<Button
variant='emphasized'
type='submit'
className={clsx(
'rounded-lg py-2 bg-gray-800 text-white w-full mt-12',
isEnabled ? 'bg-red-800' : 'bg-gray-800'
'w-full mt-12',
isEnabled ? 'bg-red-800 dark:bg-red-500' : ''
)}
onClick={() => {
fetcher.submit({
@ -58,7 +60,7 @@ export default function Modal({ isEnabled, baseDomain }: Properties) {
}}
>
{isEnabled ? 'Disable' : 'Enable'}
</button>
</Button>
</Dialog.Panel>
</div>
</Dialog>

View File

@ -4,6 +4,7 @@ import { Dialog } from '@headlessui/react'
import { useFetcher } from '@remix-run/react'
import { useState } from 'react'
import Button from '~/components/Button'
import Code from '~/components/Code'
import Input from '~/components/Input'
@ -32,23 +33,21 @@ export default function Modal({ name }: Properties) {
<Input
readOnly
className='font-mono text-sm my-4'
// 'my-4 px-3 py-2 border rounded-lg focus:ring-none w-2/3 font-mono text-sm',
// 'dark:bg-zinc-800 dark:text-white dark:border-zinc-700'
type='text'
value={name}
onFocus={event => {
event.target.select()
}}
/>
<button
type='button'
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm'
<Button
variant='emphasized'
className='text-sm w-fit'
onClick={() => {
setIsOpen(true)
}}
>
Rename Tailnet...
</button>
</Button>
<Dialog
className='relative z-50'
open={isOpen} onClose={() => {

View File

@ -4,7 +4,7 @@ import { json, useFetcher, useLoaderData } from '@remix-run/react'
import clsx from 'clsx'
import { useState } from 'react'
import Action from '~/components/Action'
import Button from '~/components/Button'
import Code from '~/components/Code'
import Input from '~/components/Input'
import TableList from '~/components/TableList'
@ -94,8 +94,8 @@ export default function Page() {
// eslint-disable-next-line react/no-array-index-key
<TableList.Item key={index}>
<p className='font-mono text-sm'>{ns}</p>
<Action
isDestructive
<Button
variant='destructive'
className='text-sm'
onClick={() => {
fetcher.submit({
@ -108,12 +108,12 @@ export default function Page() {
}}
>
Remove
</Action>
</Button>
</TableList.Item>
))}
<TableList.Item>
<Input
isEmbedded
variant='embedded'
type='text'
className='font-mono text-sm'
placeholder='Nameserver'
@ -122,9 +122,9 @@ export default function Page() {
setNs(event.target.value)
}}
/>
<Action
<Button
className='text-sm'
isDisabled={ns.length === 0}
disabled={ns.length === 0}
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
@ -138,7 +138,7 @@ export default function Page() {
}}
>
Add
</Action>
</Button>
</TableList.Item>
</TableList>
{/* TODO: Split DNS and Custom A Records */}
@ -162,7 +162,7 @@ export default function Page() {
{' '}
when Magic DNS is enabled.
</p>
<MagicModal isEnabled={data.magicDns} baseDomain={data.baseDomain}/>
<MagicModal isEnabled={data.magicDns}/>
</div>
</div>
)

View File

@ -38,7 +38,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Layout() {
return (
<>
<header className='bg-gray-800 text-white mb-16'>
<header className='mb-16 bg-gray-800 text-white dark:bg-gray-700'>
<nav className='container mx-auto'>
<div className='flex items-center gap-x-2 mb-8 pt-4'>
<CpuChipIcon className='w-8 h-8'/>