chore(fc2a): polish pass — toasts, loading skeletons, focus guards, mobile breakpoints
AppSnackbar mounts once at the app root and exposes a window.__fcToast function the stores call from error paths. This avoids prop-drilling a toast emitter through every component while keeping the snackbar component itself testable in isolation. GalleryGrid renders shimmer-skeleton placeholders on initial load so the first paint isn't empty. Skeleton uses the same auto-fill grid columns as the real items so layout doesn't shift when content arrives. Modal arrow nav now ignores text-entry elements so typing in the tag autocomplete doesn't navigate prev/next. Small-screen breakpoint (<600px) tightens gallery thumbnails to 120px min. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
<div
|
||||
class="fc-viewer" role="dialog" aria-modal="true"
|
||||
@keydown.esc="$emit('close')"
|
||||
@keydown.left.prevent="modal.goPrev()"
|
||||
@keydown.right.prevent="modal.goNext()"
|
||||
@keydown.left="onArrowLeft"
|
||||
@keydown.right="onArrowRight"
|
||||
tabindex="-1" ref="rootEl"
|
||||
>
|
||||
<button class="fc-viewer__close" @click="$emit('close')" aria-label="Close">
|
||||
@@ -83,6 +83,22 @@ watch(() => modal.currentImageId, async () => {
|
||||
function nextFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve))
|
||||
}
|
||||
|
||||
function onArrowLeft(ev) {
|
||||
if (isTextEntry(ev.target)) return
|
||||
ev.preventDefault()
|
||||
modal.goPrev()
|
||||
}
|
||||
function onArrowRight(ev) {
|
||||
if (isTextEntry(ev.target)) return
|
||||
ev.preventDefault()
|
||||
modal.goNext()
|
||||
}
|
||||
function isTextEntry(el) {
|
||||
if (!el) return false
|
||||
const tag = el.tagName
|
||||
return tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user