ui(sidebar): wrap edit form in collapsible, drop Header X button
Mirror the Metadata collapsible: an 'Edit' wrapper around filename, title, notes, rating, color, and flag so the editable form is hidden with one click. Default expanded. Drop the clear-selection X from the panel Header — Esc still clears selection and grid clicks do too. The X felt out of place once the panel restructured around two equal collapsible groups (Edit / Metadata) below a plain title bar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ import { formatApiError } from '../../lib/apiError'
|
|||||||
* rating / color / discard / pick across the entire selection.
|
* rating / color / discard / pick across the entire selection.
|
||||||
*/
|
*/
|
||||||
export function RightSidebar() {
|
export function RightSidebar() {
|
||||||
const { selectedPhotos, activePhotoId, clearSelection } = usePhotoStore()
|
const { selectedPhotos, activePhotoId } = usePhotoStore()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -182,9 +182,10 @@ export function RightSidebar() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Unified header rendered in every branch so the collapse button is
|
// Unified header rendered in every branch so the panel always has
|
||||||
// always reachable regardless of selection state. Title and the
|
// a title strip regardless of selection state. The title adapts to
|
||||||
// clear-selection X adapt to what's selected.
|
// what's selected; selection is cleared via Esc or by clicking
|
||||||
|
// empty space in the grid.
|
||||||
const headerTitle =
|
const headerTitle =
|
||||||
selectedPhotos.length === 0
|
selectedPhotos.length === 0
|
||||||
? 'Metadata'
|
? 'Metadata'
|
||||||
@@ -193,24 +194,10 @@ export function RightSidebar() {
|
|||||||
: `${selectedPhotos.length} Photos Selected`
|
: `${selectedPhotos.length} Photos Selected`
|
||||||
|
|
||||||
const Header = () => (
|
const Header = () => (
|
||||||
<div className="flex h-9 flex-shrink-0 items-center justify-between border-b border-border px-3">
|
<div className="flex h-9 flex-shrink-0 items-center border-b border-border px-3">
|
||||||
<h2 className="text-[11px] font-semibold uppercase tracking-[0.14em] text-text-muted">
|
<h2 className="text-[11px] font-semibold uppercase tracking-[0.14em] text-text-muted">
|
||||||
{headerTitle}
|
{headerTitle}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center gap-0.5">
|
|
||||||
{selectedPhotos.length > 0 && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-6 w-6 text-text-muted"
|
|
||||||
onClick={clearSelection}
|
|
||||||
title="Clear selection (Esc)"
|
|
||||||
aria-label="Clear selection"
|
|
||||||
>
|
|
||||||
<X className="h-3.5 w-3.5" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(
|
const [expandedSections, setExpandedSections] = useState<Set<string>>(
|
||||||
new Set(['metadata', 'basic', 'camera', 'location', 'tags'])
|
new Set(['edit', 'metadata', 'basic', 'camera', 'location', 'tags'])
|
||||||
)
|
)
|
||||||
const toggleSection = (section: string) => {
|
const toggleSection = (section: string) => {
|
||||||
const next = new Set(expandedSections)
|
const next = new Set(expandedSections)
|
||||||
@@ -390,8 +390,24 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
|||||||
isPlaceholderData && 'opacity-70'
|
isPlaceholderData && 'opacity-70'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Edit fields */}
|
{/* Edit fields — collapsible group so the user can hide the
|
||||||
<div className="space-y-2.5 border-b border-border p-3">
|
* editable form (filename, title, notes, rating, color, flag)
|
||||||
|
* the same way they can hide the readonly metadata block below. */}
|
||||||
|
<Collapsible
|
||||||
|
open={expandedSections.has('edit')}
|
||||||
|
onOpenChange={() => toggleSection('edit')}
|
||||||
|
className="border-b border-border"
|
||||||
|
>
|
||||||
|
<CollapsibleTrigger className="flex w-full items-center justify-between border-b border-border bg-surface-2/40 px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-text-muted hover:bg-surface-2 hover:text-text">
|
||||||
|
<span>Edit</span>
|
||||||
|
{expandedSections.has('edit') ? (
|
||||||
|
<ChevronDown className="h-3 w-3" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<div className="space-y-2.5 p-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs text-text-muted">Filename</label>
|
<label className="mb-1 block text-xs text-text-muted">Filename</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -544,6 +560,8 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
{/* Read-only metadata — collapsed/expanded as one block so the user
|
{/* Read-only metadata — collapsed/expanded as one block so the user
|
||||||
* can hide everything below the editable form with a single click.
|
* can hide everything below the editable form with a single click.
|
||||||
|
|||||||
Reference in New Issue
Block a user