fix: dragging works correctly

This commit is contained in:
Aarnav Tale 2024-03-28 19:40:06 -04:00
parent 358629a93b
commit 5df9be1b8e
No known key found for this signature in database
3 changed files with 113 additions and 105 deletions

View File

@ -1,13 +1,13 @@
/* eslint-disable unicorn/no-keyword-prefix */ /* eslint-disable unicorn/no-keyword-prefix */
import { import {
closestCenter, closestCorners,
DndContext, DndContext,
DragOverlay, DragOverlay
PointerSensor,
TouchSensor,
useSensor,
useSensors
} from '@dnd-kit/core' } from '@dnd-kit/core'
import {
restrictToParentElement,
restrictToVerticalAxis
} from '@dnd-kit/modifiers'
import { import {
arrayMove, arrayMove,
SortableContext, SortableContext,
@ -16,9 +16,9 @@ import {
} from '@dnd-kit/sortable' } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
import { Bars3Icon } from '@heroicons/react/24/outline' import { Bars3Icon } from '@heroicons/react/24/outline'
import { useFetcher, useRevalidator } from '@remix-run/react' import { useFetcher } from '@remix-run/react'
import clsx from 'clsx' import clsx from 'clsx'
import { useState } from 'react' import { useEffect, useState } from 'react'
type Properties = { type Properties = {
readonly baseDomain?: string; readonly baseDomain?: string;
@ -31,12 +31,10 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
const [localDomains, setLocalDomains] = useState(searchDomains) const [localDomains, setLocalDomains] = useState(searchDomains)
const [newDomain, setNewDomain] = useState('') const [newDomain, setNewDomain] = useState('')
const fetcher = useFetcher({ key: 'search-domains' }) const fetcher = useFetcher({ key: 'search-domains' })
const revalidator = useRevalidator()
const sensors = useSensors( useEffect(() => {
useSensor(PointerSensor), setLocalDomains(searchDomains)
useSensor(TouchSensor) }, [searchDomains])
)
return ( return (
<div className='flex flex-col w-2/3'> <div className='flex flex-col w-2/3'>
@ -45,47 +43,47 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
Set custom DNS search domains for your Tailnet. Set custom DNS search domains for your Tailnet.
When using Magic DNS, your tailnet domain is used as the first search domain. When using Magic DNS, your tailnet domain is used as the first search domain.
</p> </p>
<div className='border border-gray-200 rounded-lg bg-gray-50 overflow-clip'> <DndContext
{baseDomain ? ( modifiers={[restrictToVerticalAxis, restrictToParentElement]}
<div collisionDetection={closestCorners}
key='magic-dns-sd' onDragStart={event => {
className={clsx( setActiveId(event.active.id)
'flex items-center justify-between px-3 py-2', }}
'border-b border-gray-200 last:border-b-0' onDragEnd={event => {
)} // eslint-disable-next-line unicorn/no-null
> setActiveId(null)
<p className='font-mono text-sm'>{baseDomain}</p> const { active, over } = event
</div> if (!over) {
) : undefined} return
<DndContext }
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={event => {
setActiveId(event.active.id)
}}
onDragEnd={event => {
// eslint-disable-next-line unicorn/no-null
setActiveId(null)
const { active, over } = event
if (!over) {
return
}
const activeItem = localDomains[active.id as number - 1] const activeItem = localDomains[active.id as number - 1]
const overItem = localDomains[over.id as number - 1] const overItem = localDomains[over.id as number - 1]
if (!activeItem || !overItem) { if (!activeItem || !overItem) {
return return
} }
const oldIndex = localDomains.indexOf(activeItem) const oldIndex = localDomains.indexOf(activeItem)
const newIndex = localDomains.indexOf(overItem) const newIndex = localDomains.indexOf(overItem)
if (oldIndex !== newIndex) { if (oldIndex !== newIndex) {
setLocalDomains(arrayMove(localDomains, oldIndex, newIndex)) setLocalDomains(arrayMove(localDomains, oldIndex, newIndex))
} }
}} }}
> >
<div className='border border-gray-200 rounded-lg bg-gray-50 overflow-clip'>
{baseDomain ? (
<div
key='magic-dns-sd'
className={clsx(
'flex items-center justify-between px-3 py-2',
'border-b border-gray-200 last:border-b-0'
)}
>
<p className='font-mono text-sm'>{baseDomain}</p>
</div>
) : undefined}
<SortableContext <SortableContext
items={localDomains} items={localDomains}
strategy={verticalListSortingStrategy} strategy={verticalListSortingStrategy}
@ -94,55 +92,52 @@ export default function Domains({ baseDomain, searchDomains }: Properties) {
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
<Domain key={index} domain={sd} id={index + 1} localDomains={localDomains}/> <Domain key={index} domain={sd} id={index + 1} localDomains={localDomains}/>
))} ))}
<DragOverlay adjustScale>
{activeId ? <Domain
isDrag
domain={localDomains[activeId as number - 1]}
localDomains={localDomains}
id={activeId as number - 1}
/> : undefined}
</DragOverlay>
</SortableContext> </SortableContext>
<DragOverlay adjustScale> <div
{activeId ? <Domain key='add-sd'
isDrag className={clsx(
domain={localDomains[activeId as number - 1]} 'flex items-center justify-between px-3 py-2',
localDomains={localDomains} 'border-b border-gray-200 last:border-b-0',
id={activeId as number - 1} 'bg-white dark:bg-gray-800'
/> : undefined} )}
</DragOverlay>
</DndContext>
<div
key='add-sd'
className={clsx(
'flex items-center justify-between px-3 py-2',
'border-b border-gray-200 last:border-b-0',
'bg-white dark:bg-gray-800'
)}
>
<input
type='text'
className='w-full focus:ring-none focus:outline-none font-mono text-sm'
placeholder='Search Domain'
value={newDomain}
onChange={event => {
setNewDomain(event.target.value)
}}
/>
<button
type='button'
className='text-sm text-blue-700'
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.domains': [...localDomains, newDomain]
}, {
method: 'PATCH',
encType: 'application/json'
})
setNewDomain('')
if (revalidator.state === 'idle') {
revalidator.revalidate()
}
}}
> >
Add <input
</button> type='text'
className='w-full focus:ring-none focus:outline-none font-mono text-sm'
placeholder='Search Domain'
value={newDomain}
onChange={event => {
setNewDomain(event.target.value)
}}
/>
<button
type='button'
className='text-sm text-blue-700'
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.domains': [...localDomains, newDomain]
}, {
method: 'PATCH',
encType: 'application/json'
})
setNewDomain('')
}}
>
Add
</button>
</div>
</div> </div>
</div> </DndContext>
</div> </div>
) )
} }
@ -156,7 +151,6 @@ type DomainProperties = {
function Domain({ domain, id, localDomains, isDrag }: DomainProperties) { function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
const fetcher = useFetcher({ key: 'individual-domain' }) const fetcher = useFetcher({ key: 'individual-domain' })
const revalidator = useRevalidator()
const { const {
attributes, attributes,
@ -174,17 +168,19 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
'flex items-center justify-between px-3 py-2', 'flex items-center justify-between px-3 py-2',
'border-b border-gray-200 last:border-b-0', 'border-b border-gray-200 last:border-b-0',
isDragging ? 'text-gray-400' : 'bg-gray-50', isDragging ? 'text-gray-400' : 'bg-gray-50',
isDrag ? 'outline outline-1 outline-gray-500 rounded-md' : undefined isDrag ? 'outline outline-1 outline-gray-500' : undefined
)} )}
style={{ style={{
transform: CSS.Transform.toString(transform), transform: CSS.Transform.toString(transform),
transition transition
}} }}
{...attributes}
{...listeners}
> >
<p className='font-mono text-sm flex items-center gap-4'> <p className='font-mono text-sm flex items-center gap-4'>
<Bars3Icon className='h-4 w-4 text-gray-400'/> <Bars3Icon
className='h-4 w-4 text-gray-400 focus:outline-none'
{...attributes}
{...listeners}
/>
{domain} {domain}
</p> </p>
{isDrag ? undefined : ( {isDrag ? undefined : (
@ -199,10 +195,6 @@ function Domain({ domain, id, localDomains, isDrag }: DomainProperties) {
method: 'PATCH', method: 'PATCH',
encType: 'application/json' encType: 'application/json'
}) })
if (revalidator.state === 'idle') {
revalidator.revalidate()
}
}} }}
> >
Remove Remove

View File

@ -12,6 +12,7 @@
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@headlessui/react": "^1.7.18", "@headlessui/react": "^1.7.18",

15
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ dependencies:
'@dnd-kit/core': '@dnd-kit/core':
specifier: ^6.1.0 specifier: ^6.1.0
version: 6.1.0(react-dom@18.2.0)(react@18.2.0) version: 6.1.0(react-dom@18.2.0)(react@18.2.0)
'@dnd-kit/modifiers':
specifier: ^7.0.0
version: 7.0.0(@dnd-kit/core@6.1.0)(react@18.2.0)
'@dnd-kit/sortable': '@dnd-kit/sortable':
specifier: ^8.0.0 specifier: ^8.0.0
version: 8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0) version: 8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0)
@ -462,6 +465,18 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@dnd-kit/modifiers@7.0.0(@dnd-kit/core@6.1.0)(react@18.2.0):
resolution: {integrity: sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg==}
peerDependencies:
'@dnd-kit/core': ^6.1.0
react: '>=16.8.0'
dependencies:
'@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
react: 18.2.0
tslib: 2.6.2
dev: false
/@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0): /@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0):
resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==} resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==}
peerDependencies: peerDependencies: