feat(10-11): teleport DocumentCard folder picker to body with getBoundingClientRect positioning
- Add pickerTriggerEl ref and pickerStyle computed from getBoundingClientRect - Wrap folder picker dropdown in Teleport to body with data-test attribute - Add updatePickerPosition() called on toggle, scroll, and resize - Add window scroll/resize listeners with cleanup in onUnmounted - 2 DocumentCard dropdown tests now pass
This commit is contained in:
@@ -62,6 +62,7 @@
|
|||||||
<!-- Move to folder -->
|
<!-- Move to folder -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
|
ref="pickerTriggerEl"
|
||||||
@click.stop="toggleFolderPicker"
|
@click.stop="toggleFolderPicker"
|
||||||
aria-label="Move to folder"
|
aria-label="Move to folder"
|
||||||
class="min-h-[44px] min-w-[44px] flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 transition-colors"
|
class="min-h-[44px] min-w-[44px] flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 transition-colors"
|
||||||
@@ -71,24 +72,6 @@
|
|||||||
d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" />
|
d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Folder picker dropdown -->
|
|
||||||
<div
|
|
||||||
v-if="showFolderPicker"
|
|
||||||
class="absolute right-0 top-full mt-1 w-48 bg-white border border-gray-200 rounded-xl shadow-lg z-20 py-1"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="w-full text-left px-3 py-2 text-sm text-gray-600 hover:bg-gray-50"
|
|
||||||
@click.stop="moveToFolder(null)"
|
|
||||||
>Root (no folder)</button>
|
|
||||||
<button
|
|
||||||
v-for="folder in allFolders"
|
|
||||||
:key="folder.id"
|
|
||||||
class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 truncate"
|
|
||||||
@click.stop="moveToFolder(folder.id)"
|
|
||||||
>{{ folder.name }}</button>
|
|
||||||
<p v-if="!allFolders.length" class="px-3 py-2 text-xs text-gray-400">No folders yet</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Share -->
|
<!-- Share -->
|
||||||
<button
|
<button
|
||||||
@@ -112,6 +95,30 @@
|
|||||||
@unshared="doc.is_shared = false"
|
@unshared="doc.is_shared = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Teleported folder picker — avoids overflow:hidden clipping -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="showFolderPicker"
|
||||||
|
:style="pickerStyle"
|
||||||
|
data-test="folder-picker"
|
||||||
|
class="fixed z-[9999] bg-white border border-gray-200 rounded-xl shadow-lg overflow-y-auto py-1"
|
||||||
|
style="max-height: 240px;"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full text-left px-3 py-2 text-sm text-gray-600 hover:bg-gray-50"
|
||||||
|
@click.stop="moveToFolder(null)"
|
||||||
|
>Root (no folder)</button>
|
||||||
|
<button
|
||||||
|
v-for="folder in allFolders"
|
||||||
|
:key="folder.id"
|
||||||
|
class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 truncate"
|
||||||
|
@click.stop="moveToFolder(folder.id)"
|
||||||
|
>{{ folder.name }}</button>
|
||||||
|
<p v-if="!allFolders.length" class="px-3 py-2 text-xs text-gray-400">No folders yet</p>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -134,6 +141,8 @@ const foldersStore = useFoldersStore()
|
|||||||
const showShareModal = ref(false)
|
const showShareModal = ref(false)
|
||||||
const showFolderPicker = ref(false)
|
const showFolderPicker = ref(false)
|
||||||
const reanalyzing = ref(false)
|
const reanalyzing = ref(false)
|
||||||
|
const pickerTriggerEl = ref(null)
|
||||||
|
const pickerStyle = ref({})
|
||||||
|
|
||||||
const allFolders = computed(() => foldersStore.rootFolders)
|
const allFolders = computed(() => foldersStore.rootFolders)
|
||||||
|
|
||||||
@@ -141,16 +150,58 @@ function openShareModal() {
|
|||||||
showShareModal.value = true
|
showShareModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updatePickerPosition() {
|
||||||
|
if (!pickerTriggerEl.value) return
|
||||||
|
const rect = pickerTriggerEl.value.getBoundingClientRect()
|
||||||
|
const spaceBelow = window.innerHeight - rect.bottom
|
||||||
|
const dropH = 240
|
||||||
|
if (spaceBelow >= dropH || spaceBelow > 120) {
|
||||||
|
pickerStyle.value = {
|
||||||
|
top: `${rect.bottom + 4}px`,
|
||||||
|
left: `${rect.left}px`,
|
||||||
|
width: '192px',
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pickerStyle.value = {
|
||||||
|
bottom: `${window.innerHeight - rect.top + 4}px`,
|
||||||
|
left: `${rect.left}px`,
|
||||||
|
width: '192px',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleFolderPicker() {
|
function toggleFolderPicker() {
|
||||||
showFolderPicker.value = !showFolderPicker.value
|
if (showFolderPicker.value) {
|
||||||
|
showFolderPicker.value = false
|
||||||
|
} else {
|
||||||
|
updatePickerPosition()
|
||||||
|
showFolderPicker.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
if (showFolderPicker.value) updatePickerPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeFolderPicker(e) {
|
function closeFolderPicker(e) {
|
||||||
showFolderPicker.value = false
|
if (
|
||||||
|
!e.target.closest('[data-test="folder-picker"]') &&
|
||||||
|
!e.target.closest('.relative')
|
||||||
|
) {
|
||||||
|
showFolderPicker.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => document.addEventListener('click', closeFolderPicker))
|
onMounted(() => {
|
||||||
onUnmounted(() => document.removeEventListener('click', closeFolderPicker))
|
document.addEventListener('click', closeFolderPicker)
|
||||||
|
window.addEventListener('scroll', onScroll, true)
|
||||||
|
window.addEventListener('resize', onScroll)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', closeFolderPicker)
|
||||||
|
window.removeEventListener('scroll', onScroll, true)
|
||||||
|
window.removeEventListener('resize', onScroll)
|
||||||
|
})
|
||||||
|
|
||||||
async function moveToFolder(folderId) {
|
async function moveToFolder(folderId) {
|
||||||
showFolderPicker.value = false
|
showFolderPicker.value = false
|
||||||
|
|||||||
Reference in New Issue
Block a user