feat: convert all slide panels to tabbed modals with shared TabbedModal component
Deploy / deploy (push) Successful in 11s

Extract reusable TabbedModal component (portal, backdrop, tab bar with icons/badges/underline, scrollable body, footer) and convert all 9 detail panels from SlidePanel+CollapsibleSection to tabbed modal layout:
- PostDetailPanel (5 tabs), TaskDetailPanel (3), ProjectEditPanel (2)
- TrackDetailPanel (2), CampaignDetailPanel (3), TeamMemberPanel (3)
- TeamPanel (2), IssueDetailPanel (4), ArtefactDetailPanel (4)
Also adds post versioning system (server routes + frontend).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-09 17:12:32 +03:00
parent 539c204bde
commit 44e706f777
14 changed files with 2839 additions and 1921 deletions
+10 -1
View File
@@ -3,6 +3,7 @@ import { Check, ChevronDown, X } from 'lucide-react'
export default function ApproverMultiSelect({ users = [], selected = [], onChange }) {
const [open, setOpen] = useState(false)
const [dropUp, setDropUp] = useState(false)
const wrapperRef = useRef(null)
// Close dropdown when clicking outside
@@ -17,6 +18,14 @@ export default function ApproverMultiSelect({ users = [], selected = [], onChang
return () => document.removeEventListener('mousedown', handleClick)
}, [open])
// Detect if dropdown should open upward
useEffect(() => {
if (!open || !wrapperRef.current) return
const rect = wrapperRef.current.getBoundingClientRect()
const spaceBelow = window.innerHeight - rect.bottom
setDropUp(spaceBelow < 220)
}, [open])
const toggle = (userId) => {
const id = String(userId)
const next = selected.includes(id) ? selected.filter(s => s !== id) : [...selected, id]
@@ -58,7 +67,7 @@ export default function ApproverMultiSelect({ users = [], selected = [], onChang
<ChevronDown className={`w-4 h-4 text-text-tertiary ml-auto shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} />
</div>
{open && (
<div className="absolute z-50 mt-1 w-full bg-surface border border-border rounded-lg shadow-lg max-h-48 overflow-y-auto">
<div className={`absolute z-50 w-full bg-surface border border-border rounded-lg shadow-lg max-h-48 overflow-y-auto ${dropUp ? 'bottom-full mb-1' : 'top-full mt-1'}`}>
{users.map(u => {
const uid = String(u._id || u.id || u.Id)
const isSelected = selected.includes(uid)