feat: create shared button component

This commit is contained in:
Aarnav Tale
2024-03-29 15:58:22 -04:00
parent b5658750a9
commit 4a1cdaa432
8 changed files with 67 additions and 65 deletions
-25
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
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>
)
}
+6 -6
View File
@@ -2,7 +2,7 @@ import clsx from 'clsx'
import { type DetailedHTMLProps, type InputHTMLAttributes } from 'react' import { type DetailedHTMLProps, type InputHTMLAttributes } from 'react'
type Properties = { type Properties = {
readonly isEmbedded?: boolean; readonly variant?: 'embedded' | 'normal';
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> } & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
export default function Input(properties: Properties) { export default function Input(properties: Properties) {
@@ -14,11 +14,11 @@ export default function Input(properties: Properties) {
'border-gray-300 dark:border-zinc-700', 'border-gray-300 dark:border-zinc-700',
'focus:outline-none focus:ring', 'focus:outline-none focus:ring',
'focus:ring-blue-500 dark:focus:ring-blue-300', 'focus:ring-blue-500 dark:focus:ring-blue-300',
properties.isEmbedded ? 'bg-transparent' : 'dark:bg-zinc-800', properties.variant === 'embedded' ? 'bg-transparent' : 'dark:bg-zinc-800',
properties.isEmbedded ? 'p-0' : 'px-2.5 py-1.5', properties.variant === 'embedded' ? 'p-0' : 'px-2.5 py-1.5',
properties.isEmbedded ? 'border-none' : 'border', properties.variant === 'embedded' ? 'border-none' : 'border',
properties.isEmbedded ? 'focus:ring-0' : 'focus:ring-1', properties.variant === 'embedded' ? 'focus:ring-0' : 'focus:ring-1',
properties.isEmbedded ? 'rounded-none' : 'rounded-lg', properties.variant === 'embedded' ? 'rounded-none' : 'rounded-lg',
properties.className properties.className
)} )}
/> />
+8 -8
View File
@@ -20,7 +20,7 @@ import { useFetcher } from '@remix-run/react'
import clsx from 'clsx' import clsx from 'clsx'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import Action from '~/components/Action' import Button from '~/components/Button'
import Input from '~/components/Input' import Input from '~/components/Input'
import TableList from '~/components/TableList' import TableList from '~/components/TableList'
@@ -102,7 +102,7 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
</SortableContext> </SortableContext>
<TableList.Item key='add-sd'> <TableList.Item key='add-sd'>
<Input <Input
isEmbedded variant='embedded'
type='text' type='text'
className='font-mono text-sm' className='font-mono text-sm'
placeholder='Search Domain' placeholder='Search Domain'
@@ -111,9 +111,9 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
setNewDomain(event.target.value) setNewDomain(event.target.value)
}} }}
/> />
<Action <Button
className='text-sm' className='text-sm'
isDisabled={newDomain.length === 0} disabled={newDomain.length === 0}
onClick={() => { onClick={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -127,7 +127,7 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
}} }}
> >
Add Add
</Action> </Button>
</TableList.Item> </TableList.Item>
</TableList> </TableList>
</DndContext> </DndContext>
@@ -177,8 +177,8 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
{domain} {domain}
</p> </p>
{isDrag ? undefined : ( {isDrag ? undefined : (
<Action <Button
isDestructive variant='destructive'
className='text-sm' className='text-sm'
onClick={() => { onClick={() => {
fetcher.submit({ fetcher.submit({
@@ -191,7 +191,7 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
}} }}
> >
Remove Remove
</Action> </Button>
)} )}
</div> </div>
) )
+12 -10
View File
@@ -3,26 +3,27 @@ import { useFetcher } from '@remix-run/react'
import clsx from 'clsx' import clsx from 'clsx'
import { useState } from 'react' import { useState } from 'react'
import Button from '~/components/Button'
type Properties = { type Properties = {
readonly isEnabled: boolean; 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 [isOpen, setIsOpen] = useState(false)
const fetcher = useFetcher() const fetcher = useFetcher()
return ( return (
<> <>
<button <Button
type='button' variant='emphasized'
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm' className='w-fit text-sm'
onClick={() => { onClick={() => {
setIsOpen(true) setIsOpen(true)
}} }}
> >
{isEnabled ? 'Disable' : 'Enable'} Magic DNS {isEnabled ? 'Disable' : 'Enable'} Magic DNS
</button> </Button>
<Dialog <Dialog
className='relative z-50' className='relative z-50'
open={isOpen} onClose={() => { open={isOpen} onClose={() => {
@@ -39,11 +40,12 @@ export default function Modal({ isEnabled, baseDomain }: Properties) {
Devices will no longer be accessible via your tailnet domain. Devices will no longer be accessible via your tailnet domain.
The search domain will also be disabled. The search domain will also be disabled.
</Dialog.Description> </Dialog.Description>
<button <Button
variant='emphasized'
type='submit' type='submit'
className={clsx( className={clsx(
'rounded-lg py-2 bg-gray-800 text-white w-full mt-12', 'w-full mt-12',
isEnabled ? 'bg-red-800' : 'bg-gray-800' isEnabled ? 'bg-red-800 dark:bg-red-500' : ''
)} )}
onClick={() => { onClick={() => {
fetcher.submit({ fetcher.submit({
@@ -58,7 +60,7 @@ export default function Modal({ isEnabled, baseDomain }: Properties) {
}} }}
> >
{isEnabled ? 'Disable' : 'Enable'} {isEnabled ? 'Disable' : 'Enable'}
</button> </Button>
</Dialog.Panel> </Dialog.Panel>
</div> </div>
</Dialog> </Dialog>
+5 -6
View File
@@ -4,6 +4,7 @@ import { Dialog } from '@headlessui/react'
import { useFetcher } from '@remix-run/react' import { useFetcher } from '@remix-run/react'
import { useState } from 'react' import { useState } from 'react'
import Button from '~/components/Button'
import Code from '~/components/Code' import Code from '~/components/Code'
import Input from '~/components/Input' import Input from '~/components/Input'
@@ -32,23 +33,21 @@ export default function Modal({ name }: Properties) {
<Input <Input
readOnly readOnly
className='font-mono text-sm my-4' 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' type='text'
value={name} value={name}
onFocus={event => { onFocus={event => {
event.target.select() event.target.select()
}} }}
/> />
<button <Button
type='button' variant='emphasized'
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm' className='text-sm w-fit'
onClick={() => { onClick={() => {
setIsOpen(true) setIsOpen(true)
}} }}
> >
Rename Tailnet... Rename Tailnet...
</button> </Button>
<Dialog <Dialog
className='relative z-50' className='relative z-50'
open={isOpen} onClose={() => { open={isOpen} onClose={() => {
+9 -9
View File
@@ -4,7 +4,7 @@ import { json, useFetcher, useLoaderData } from '@remix-run/react'
import clsx from 'clsx' import clsx from 'clsx'
import { useState } from 'react' import { useState } from 'react'
import Action from '~/components/Action' import Button from '~/components/Button'
import Code from '~/components/Code' import Code from '~/components/Code'
import Input from '~/components/Input' import Input from '~/components/Input'
import TableList from '~/components/TableList' import TableList from '~/components/TableList'
@@ -94,8 +94,8 @@ export default function Page() {
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
<TableList.Item key={index}> <TableList.Item key={index}>
<p className='font-mono text-sm'>{ns}</p> <p className='font-mono text-sm'>{ns}</p>
<Action <Button
isDestructive variant='destructive'
className='text-sm' className='text-sm'
onClick={() => { onClick={() => {
fetcher.submit({ fetcher.submit({
@@ -108,12 +108,12 @@ export default function Page() {
}} }}
> >
Remove Remove
</Action> </Button>
</TableList.Item> </TableList.Item>
))} ))}
<TableList.Item> <TableList.Item>
<Input <Input
isEmbedded variant='embedded'
type='text' type='text'
className='font-mono text-sm' className='font-mono text-sm'
placeholder='Nameserver' placeholder='Nameserver'
@@ -122,9 +122,9 @@ export default function Page() {
setNs(event.target.value) setNs(event.target.value)
}} }}
/> />
<Action <Button
className='text-sm' className='text-sm'
isDisabled={ns.length === 0} disabled={ns.length === 0}
onClick={() => { onClick={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -138,7 +138,7 @@ export default function Page() {
}} }}
> >
Add Add
</Action> </Button>
</TableList.Item> </TableList.Item>
</TableList> </TableList>
{/* TODO: Split DNS and Custom A Records */} {/* TODO: Split DNS and Custom A Records */}
@@ -162,7 +162,7 @@ export default function Page() {
{' '} {' '}
when Magic DNS is enabled. when Magic DNS is enabled.
</p> </p>
<MagicModal isEnabled={data.magicDns} baseDomain={data.baseDomain}/> <MagicModal isEnabled={data.magicDns}/>
</div> </div>
</div> </div>
) )
+1 -1
View File
@@ -38,7 +38,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Layout() { export default function Layout() {
return ( 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'> <nav className='container mx-auto'>
<div className='flex items-center gap-x-2 mb-8 pt-4'> <div className='flex items-center gap-x-2 mb-8 pt-4'>
<CpuChipIcon className='w-8 h-8'/> <CpuChipIcon className='w-8 h-8'/>