diff --git a/src/content/App.tsx b/src/content/App.tsx index 1412bea..b341204 100644 --- a/src/content/App.tsx +++ b/src/content/App.tsx @@ -18,6 +18,7 @@ import FilterPanel from './components/FilterPanel'; import { useCourseData } from '@/hooks/useCourseData'; import { filterVods, filterAssigns, filterQuizes } from '@/lib/filterData'; import PendingDialogWithBeforeUnload from './components/PendingDialog'; +import StickyPopoverTrigger from './StickyPopoverTrigger'; // 리팩토링: 필터 옵션 추출 const attendanceOptions = ['출석', '결석']; // string[] @@ -165,34 +166,37 @@ export default function App() { <> {}} /> - - {isOpen ? ( - { - setIsOpen(!isOpen); - e.preventDefault(); - }} - draggable={false} - className="rounded-2xl w-32 h-32 shadow-xl cursor-pointer" - alt="Close" - /> - ) : ( - { - setIsOpen(!isOpen); - e.preventDefault(); - }} - draggable={false} - className="rounded-2xl w-32 h-32 shadow-xl cursor-pointer" - alt="Open" - /> - )} - + + + {isOpen ? ( + { + setIsOpen(!isOpen); + e.preventDefault(); + }} + draggable={false} + className="rounded-2xl w-32 h-32 shadow-2xl shadow-zinc-900 cursor-pointer" + alt="Close" + /> + ) : ( + { + setIsOpen(!isOpen); + e.preventDefault(); + }} + draggable={false} + className="rounded-2xl w-32 h-32 shadow-2xl shadow-zinc-900 cursor-pointer" + alt="Open" + /> + )} + +
diff --git a/src/content/StickyPopoverTrigger.tsx b/src/content/StickyPopoverTrigger.tsx new file mode 100644 index 0000000..9c71ac4 --- /dev/null +++ b/src/content/StickyPopoverTrigger.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react'; + +const StickyPopoverTrigger: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [triggerStyle, setTriggerStyle] = useState({ + position: 'fixed', + bottom: '20px', + right: '20px', + zIndex: 1000, + transition: 'bottom 0.2s ease-out', + }); + + useEffect(() => { + const placeholder = document.getElementById('footer-placeholder'); + if (!placeholder) return; + + const fixedBottom = 20; + const collapsedBottom = 8; + const transitionRange = 20; + + const handleScroll = () => { + const rect = placeholder.getBoundingClientRect(); + const viewportBottom = window.innerHeight; + + if (rect.top > viewportBottom - fixedBottom) { + setTriggerStyle({ + position: 'fixed', + bottom: `${fixedBottom}px`, + right: '20px', + zIndex: 1000, + transition: 'bottom 0.2s ease-out', + }); + } else { + const overlap = viewportBottom - fixedBottom - rect.top; + if (overlap < transitionRange) { + const newBottom = fixedBottom - overlap; + setTriggerStyle({ + position: 'fixed', + bottom: `${newBottom}px`, + right: '20px', + zIndex: 1000, + transition: 'bottom 0.2s ease-out', + }); + } else { + setTriggerStyle({ + position: 'absolute', + right: '8px', + zIndex: 1000, + transition: 'none', + }); + } + } + }; + + window.addEventListener('scroll', handleScroll); + handleScroll(); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return
{children}
; +}; + +export default StickyPopoverTrigger; diff --git a/src/content/index.tsx b/src/content/index.tsx index 6d0c231..724378f 100644 --- a/src/content/index.tsx +++ b/src/content/index.tsx @@ -13,6 +13,17 @@ if (footer && url === 'https://learn.hansung.ac.kr/') { if (backtop) backtop.remove(); footer.style.paddingBottom = '24px'; + + const placeholder = document.createElement('div'); + placeholder.id = 'footer-placeholder'; + placeholder.style.display = 'block'; + placeholder.style.position = 'relative'; + placeholder.style.bottom = '8px'; + placeholder.style.zIndex = '10'; + placeholder.style.height = '80px'; + placeholder.style.backgroundColor = 'transparent'; + footer.prepend(placeholder); + const host = document.createElement('div'); host.id = 'extension-content-root'; host.style.display = 'block'; @@ -20,7 +31,7 @@ if (footer && url === 'https://learn.hansung.ac.kr/') { host.style.bottom = '8px'; host.style.zIndex = '100'; host.style.backgroundColor = 'transparent'; - footer.prepend(host); + placeholder.append(host); const shadowRoot = createShadowRoot(host, [styles]);