fix(ui): resolve breadcrumb visibility and AI combobox layout issues
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m31s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m31s
This commit is contained in:
@@ -677,7 +677,7 @@ export function ContextualAIChat({
|
|||||||
|
|
||||||
{/* ── Chat Footer: Context + Tone + Input ── */}
|
{/* ── Chat Footer: Context + Tone + Input ── */}
|
||||||
<div className="px-6 py-8 border-t border-border shrink-0 space-y-6">
|
<div className="px-6 py-8 border-t border-border shrink-0 space-y-6">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-foreground/40 px-1">CONTEXTE</label>
|
<label className="text-[10px] uppercase tracking-[0.25em] font-bold text-foreground/40 px-1">CONTEXTE</label>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -703,6 +703,7 @@ export function ContextualAIChat({
|
|||||||
onSelect={(id) => setChatScope(id)}
|
onSelect={(id) => setChatScope(id)}
|
||||||
placeholder="Inclure un carnet..."
|
placeholder="Inclure un carnet..."
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
size="sm"
|
||||||
dropUp
|
dropUp
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState, useMemo, useRef, useCallback, useLayoutEffect } from 'react'
|
import React, { useState, useMemo, useRef, useCallback } from 'react'
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -11,16 +11,16 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { Notebook } from '@/lib/types'
|
import { Notebook } from '@/lib/types'
|
||||||
import { motion, AnimatePresence } from 'motion/react'
|
import { motion, AnimatePresence } from 'motion/react'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
interface HierarchicalNotebookSelectorProps {
|
interface HierarchicalNotebookSelectorProps {
|
||||||
notebooks: Notebook[]
|
notebooks: { id: string; name: string; icon?: string | null; parentId?: string | null; trashedAt?: Date | string | null }[]
|
||||||
selectedId: string | null
|
selectedId: string | null
|
||||||
onSelect: (id: string) => void
|
onSelect: (id: string) => void
|
||||||
className?: string
|
className?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
dropUp?: boolean
|
dropUp?: boolean
|
||||||
|
size?: 'default' | 'sm'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HierarchicalNotebookSelector({
|
export function HierarchicalNotebookSelector({
|
||||||
@@ -30,19 +30,19 @@ export function HierarchicalNotebookSelector({
|
|||||||
className = '',
|
className = '',
|
||||||
placeholder = 'Select a notebook...',
|
placeholder = 'Select a notebook...',
|
||||||
dropUp = false,
|
dropUp = false,
|
||||||
|
size = 'default',
|
||||||
}: HierarchicalNotebookSelectorProps) {
|
}: HierarchicalNotebookSelectorProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
|
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
|
||||||
const triggerRef = useRef<HTMLDivElement>(null)
|
const triggerRef = useRef<HTMLDivElement>(null)
|
||||||
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties | null>(null)
|
|
||||||
|
|
||||||
const computeStyle = useCallback(() => {
|
const getDropdownStyle = useCallback((): React.CSSProperties => {
|
||||||
if (!triggerRef.current) return null
|
if (!triggerRef.current) return { position: 'fixed', top: 0, left: 0, width: 280, zIndex: 9999 }
|
||||||
const rect = triggerRef.current.getBoundingClientRect()
|
const rect = triggerRef.current.getBoundingClientRect()
|
||||||
if (dropUp) {
|
if (dropUp) {
|
||||||
return {
|
return {
|
||||||
position: 'fixed' as const,
|
position: 'fixed',
|
||||||
bottom: window.innerHeight - rect.top + 8,
|
bottom: window.innerHeight - rect.top + 8,
|
||||||
left: rect.left,
|
left: rect.left,
|
||||||
width: Math.max(rect.width, 280),
|
width: Math.max(rect.width, 280),
|
||||||
@@ -50,7 +50,7 @@ export function HierarchicalNotebookSelector({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
position: 'fixed' as const,
|
position: 'fixed',
|
||||||
top: rect.bottom + 8,
|
top: rect.bottom + 8,
|
||||||
left: rect.left,
|
left: rect.left,
|
||||||
width: Math.max(rect.width, 280),
|
width: Math.max(rect.width, 280),
|
||||||
@@ -58,23 +58,12 @@ export function HierarchicalNotebookSelector({
|
|||||||
}
|
}
|
||||||
}, [dropUp])
|
}, [dropUp])
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
setDropdownStyle(computeStyle())
|
|
||||||
const handleResize = () => setDropdownStyle(computeStyle())
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
return () => window.removeEventListener('resize', handleResize)
|
|
||||||
} else {
|
|
||||||
setDropdownStyle(null)
|
|
||||||
}
|
|
||||||
}, [isOpen, computeStyle])
|
|
||||||
|
|
||||||
const selectedNotebook = notebooks.find(nb => nb.id === selectedId)
|
const selectedNotebook = notebooks.find(nb => nb.id === selectedId)
|
||||||
|
|
||||||
const path = useMemo(() => {
|
const path = useMemo(() => {
|
||||||
if (!selectedNotebook) return []
|
if (!selectedNotebook) return []
|
||||||
const trail: Notebook[] = []
|
const trail: typeof notebooks = []
|
||||||
let current: Notebook | undefined = selectedNotebook
|
let current: typeof notebooks[0] | undefined = selectedNotebook
|
||||||
while (current) {
|
while (current) {
|
||||||
trail.unshift(current)
|
trail.unshift(current)
|
||||||
if (!current.parentId) break
|
if (!current.parentId) break
|
||||||
@@ -93,7 +82,7 @@ export function HierarchicalNotebookSelector({
|
|||||||
setExpandedIds(next)
|
setExpandedIds(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderTree = (parentId: string | null | undefined, level = 0) => {
|
const renderTree = (parentId: string | null | undefined, level = 0): React.ReactNode => {
|
||||||
const children = notebooks.filter(
|
const children = notebooks.filter(
|
||||||
nb => (nb.parentId ?? null) === (parentId ?? null)
|
nb => (nb.parentId ?? null) === (parentId ?? null)
|
||||||
)
|
)
|
||||||
@@ -167,10 +156,10 @@ export function HierarchicalNotebookSelector({
|
|||||||
<div className={`relative ${className}`}>
|
<div className={`relative ${className}`}>
|
||||||
<div
|
<div
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(prev => !prev)}
|
||||||
className="w-full bg-slate-50 dark:bg-white/5 border border-border/80 rounded-xl px-4 py-4 text-sm outline-none focus:ring-4 ring-blueprint/5 focus:border-blueprint/40 transition-all cursor-pointer text-ink flex items-center gap-3"
|
className={`w-full bg-slate-50 dark:bg-white/5 border border-border/80 rounded-xl outline-none focus:ring-4 ring-blueprint/5 focus:border-blueprint/40 transition-all cursor-pointer text-ink flex items-center gap-3 ${size === 'sm' ? 'px-3 py-2 text-xs' : 'px-4 py-3 text-sm'}`}
|
||||||
>
|
>
|
||||||
<Folder size={16} className="text-blueprint/60 shrink-0" />
|
<Folder size={size === 'sm' ? 14 : 16} className="text-blueprint/60 shrink-0" />
|
||||||
<div className="flex-1 flex items-center gap-1 min-w-0">
|
<div className="flex-1 flex items-center gap-1 min-w-0">
|
||||||
{path.length > 0 ? (
|
{path.length > 0 ? (
|
||||||
<div className="flex items-center gap-1.5 truncate">
|
<div className="flex items-center gap-1.5 truncate">
|
||||||
@@ -190,15 +179,17 @@ export function HierarchicalNotebookSelector({
|
|||||||
<ChevronDown size={14} className={`transition-transform duration-300 text-concrete shrink-0 ${isOpen ? 'rotate-180' : ''}`} />
|
<ChevronDown size={14} className={`transition-transform duration-300 text-concrete shrink-0 ${isOpen ? 'rotate-180' : ''}`} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence>
|
{isOpen && typeof window !== 'undefined' && createPortal(
|
||||||
{isOpen && dropdownStyle && typeof window !== 'undefined' && createPortal(
|
<>
|
||||||
<>
|
<div className="fixed inset-0 z-[9998]" onClick={() => setIsOpen(false)} />
|
||||||
<div className="fixed inset-0 z-[9998]" onClick={() => setIsOpen(false)} />
|
<AnimatePresence>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: dropUp ? -10 : 10, scale: 0.98 }}
|
key="notebook-dropdown"
|
||||||
|
initial={{ opacity: 0, y: dropUp ? -8 : 8, scale: 0.98 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
exit={{ opacity: 0, y: dropUp ? -10 : 10, scale: 0.98 }}
|
exit={{ opacity: 0, y: dropUp ? -8 : 8, scale: 0.98 }}
|
||||||
style={dropdownStyle}
|
transition={{ duration: 0.15 }}
|
||||||
|
style={getDropdownStyle()}
|
||||||
className="bg-card border border-border shadow-2xl rounded-2xl overflow-hidden flex flex-col"
|
className="bg-card border border-border shadow-2xl rounded-2xl overflow-hidden flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="p-3 border-b border-border/40 bg-slate-50/50 dark:bg-white/5">
|
<div className="p-3 border-b border-border/40 bg-slate-50/50 dark:bg-white/5">
|
||||||
@@ -231,10 +222,10 @@ export function HierarchicalNotebookSelector({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>,
|
</AnimatePresence>
|
||||||
document.body
|
</>,
|
||||||
)}
|
document.body
|
||||||
</AnimatePresence>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,11 +420,14 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
{currentNotebook && notebookPath.length > 0 && (
|
{currentNotebook && notebookPath.length > 0 && (
|
||||||
<div className="flex items-center gap-2 text-base font-semibold mb-1">
|
<div
|
||||||
|
className="flex items-center gap-2 text-[12px] uppercase tracking-[.2em] font-bold mb-2"
|
||||||
|
style={{ color: 'var(--color-ink)', opacity: 1 }}
|
||||||
|
>
|
||||||
{notebookPath.map((nb: any, i: number) => (
|
{notebookPath.map((nb: any, i: number) => (
|
||||||
<React.Fragment key={nb.id}>
|
<React.Fragment key={nb.id}>
|
||||||
{i > 0 && <ChevronRight size={16} className="text-foreground/40" />}
|
{i > 0 && <ChevronRight size={10} className="shrink-0" style={{ color: 'var(--color-concrete)' }} />}
|
||||||
<span className={i === notebookPath.length - 1 ? 'text-foreground' : 'text-foreground/60'}>
|
<span style={{ color: i === notebookPath.length - 1 ? 'var(--color-ink)' : 'var(--color-concrete)' }}>
|
||||||
{nb.name}
|
{nb.name}
|
||||||
</span>
|
</span>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ReminderDialog } from '@/components/reminder-dialog'
|
|||||||
import { ContextualAIChat } from '@/components/contextual-ai-chat'
|
import { ContextualAIChat } from '@/components/contextual-ai-chat'
|
||||||
import { NoteDocumentInfoPanel } from '@/components/note-document-info-panel'
|
import { NoteDocumentInfoPanel } from '@/components/note-document-info-panel'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
|
import { ChevronRight } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { Note } from '@/lib/types'
|
import { Note } from '@/lib/types'
|
||||||
|
|
||||||
@@ -40,10 +41,10 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
|||||||
{/* Breadcrumb + Title block */}
|
{/* Breadcrumb + Title block */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Breadcrumb: Notebook › Date */}
|
{/* Breadcrumb: Notebook › Date */}
|
||||||
<div className="flex items-center gap-3 text-[12px] text-foreground/50 uppercase tracking-[.25em] font-bold">
|
<div className="flex items-center gap-3 text-[12px] uppercase tracking-[.25em] font-bold">
|
||||||
{notebookName && <span>{notebookName}</span>}
|
{notebookName && <span style={{ color: 'var(--color-ink)' }}>{notebookName}</span>}
|
||||||
{notebookName && <span>›</span>}
|
{notebookName && <ChevronRight size={10} style={{ color: 'var(--color-concrete)' }} />}
|
||||||
<span suppressHydrationWarning>
|
<span suppressHydrationWarning style={{ color: 'var(--color-concrete)' }}>
|
||||||
{format(new Date(note.contentUpdatedAt), 'MMM d, yyyy')}
|
{format(new Date(note.contentUpdatedAt), 'MMM d, yyyy')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user