fix: useRef type for debounce timer (pre-existing build error on main) (#132)
* design: relationship-x-cosmetic wireframes * fix: relationship X button appears inline inside pill on hover X button now renders as a flex child inside the pill (next to the type icon) instead of being absolutely positioned outside the pill boundary. - Pill uses inset ring border on hover when bgColor is set - X appears inline with type icon via flex layout - Type icon rendered at 50% opacity per design spec - Updated test selector: .group/link is now the button element itself * fix: useRef type for debounce timer (pre-existing build error on main) --------- Co-authored-by: Test <test@test.com>
This commit is contained in:
165
design/relationship-x-cosmetic.pen
Normal file
165
design/relationship-x-cosmetic.pen
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "rel_x_default",
|
||||
"name": "Relationship Pill — Default (X hidden)",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 280,
|
||||
"height": "fit_content(40)",
|
||||
"fill": "$--background",
|
||||
"layout": "vertical",
|
||||
"gap": 8,
|
||||
"padding": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"theme": {
|
||||
"Mode": "Light"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_default_label",
|
||||
"content": "Relationship Pill — Default State",
|
||||
"fill": "$--muted-foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 11,
|
||||
"fontWeight": "500"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "rel_x_pill_default",
|
||||
"layout": "horizontal",
|
||||
"gap": 4,
|
||||
"padding": [
|
||||
4,
|
||||
8
|
||||
],
|
||||
"fill": "$--accent",
|
||||
"borderRadius": 6,
|
||||
"width": "fit_content",
|
||||
"height": "fit_content",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_pill_label",
|
||||
"content": "Note Title",
|
||||
"fill": "$--foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "500"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "rel_x_type_icon",
|
||||
"width": 14,
|
||||
"height": 14,
|
||||
"fill": "$--muted-foreground",
|
||||
"opacity": 0.5,
|
||||
"borderRadius": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_note_default",
|
||||
"content": "X button not visible in default state",
|
||||
"fill": "$--muted-foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "rel_x_hover",
|
||||
"name": "Relationship Pill — Hover (X visible)",
|
||||
"x": 320,
|
||||
"y": 0,
|
||||
"width": 280,
|
||||
"height": "fit_content(40)",
|
||||
"fill": "$--background",
|
||||
"layout": "vertical",
|
||||
"gap": 8,
|
||||
"padding": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"theme": {
|
||||
"Mode": "Light"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_hover_label",
|
||||
"content": "Relationship Pill — Hover State",
|
||||
"fill": "$--muted-foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 11,
|
||||
"fontWeight": "500"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "rel_x_pill_hover",
|
||||
"layout": "horizontal",
|
||||
"gap": 6,
|
||||
"padding": [
|
||||
4,
|
||||
8
|
||||
],
|
||||
"fill": "$--accent",
|
||||
"borderRadius": 6,
|
||||
"width": "fit_content",
|
||||
"height": "fit_content",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_pill_label_hover",
|
||||
"content": "Note Title",
|
||||
"fill": "$--foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "500"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "rel_x_trailing",
|
||||
"layout": "horizontal",
|
||||
"gap": 6,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_icon",
|
||||
"content": "×",
|
||||
"fill": "$--foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "400"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "rel_x_type_icon_hover",
|
||||
"width": 14,
|
||||
"height": 14,
|
||||
"fill": "$--muted-foreground",
|
||||
"opacity": 0.5,
|
||||
"borderRadius": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "rel_x_note_hover",
|
||||
"content": "X appears inline as flex child, before type icon. Ring border on hover.",
|
||||
"fill": "$--muted-foreground",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -77,7 +77,7 @@ describe('DynamicRelationshipsPanel', () => {
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)
|
||||
const chip = container.querySelector('.group\\/link button')
|
||||
const chip = container.querySelector('.group\\/link')
|
||||
expect(chip).toBeTruthy()
|
||||
expect(chip!.style.color).toBe(expectedColor)
|
||||
})
|
||||
|
||||
@@ -48,35 +48,36 @@ function LinkButton({ label, typeColor, bgColor, isArchived, isTrashed, onClick,
|
||||
const isDimmed = isArchived || isTrashed
|
||||
const color = isDimmed ? 'var(--muted-foreground)' : typeColor
|
||||
return (
|
||||
<div className="group/link relative flex items-center">
|
||||
<button
|
||||
className="flex w-full items-center justify-between gap-2 border-none text-left cursor-pointer hover:opacity-80 min-w-0"
|
||||
style={{
|
||||
background: isDimmed ? 'var(--muted)' : (bgColor ?? 'transparent'),
|
||||
color, borderRadius: 6, padding: bgColor ? '6px 10px' : '4px 0',
|
||||
fontSize: 12, fontWeight: 500, opacity: isDimmed ? 0.7 : 1,
|
||||
}}
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
>
|
||||
<span className="flex items-center gap-1 flex-1 truncate">
|
||||
{isTrashed && <Trash size={12} className="shrink-0" />}
|
||||
{label}
|
||||
<StatusSuffix isArchived={isArchived} isTrashed={isTrashed} />
|
||||
</span>
|
||||
<TypeIcon width={14} height={14} className="shrink-0" style={{ color }} />
|
||||
</button>
|
||||
{onRemove && (
|
||||
<button
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 border-none bg-transparent p-0.5 text-muted-foreground opacity-0 transition-opacity hover:text-destructive group-hover/link:opacity-100"
|
||||
onClick={onRemove}
|
||||
title="Remove from relation"
|
||||
data-testid="remove-relation-ref"
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className={`group/link flex w-full items-center justify-between gap-2 border-none text-left cursor-pointer min-w-0${bgColor ? ' ring-inset hover:ring-1 hover:ring-current' : ' hover:opacity-80'}`}
|
||||
style={{
|
||||
background: isDimmed ? 'var(--muted)' : (bgColor ?? 'transparent'),
|
||||
color, borderRadius: 6, padding: bgColor ? '6px 10px' : '4px 0',
|
||||
fontSize: 12, fontWeight: 500, opacity: isDimmed ? 0.7 : 1,
|
||||
}}
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
>
|
||||
<span className="flex items-center gap-1 flex-1 truncate">
|
||||
{isTrashed && <Trash size={12} className="shrink-0" />}
|
||||
{label}
|
||||
<StatusSuffix isArchived={isArchived} isTrashed={isTrashed} />
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5 shrink-0">
|
||||
{onRemove && (
|
||||
<span
|
||||
className="flex items-center opacity-0 transition-opacity group-hover/link:opacity-100"
|
||||
onClick={(e) => { e.stopPropagation(); onRemove() }}
|
||||
role="button"
|
||||
title="Remove from relation"
|
||||
data-testid="remove-relation-ref"
|
||||
>
|
||||
<X size={14} />
|
||||
</span>
|
||||
)}
|
||||
<TypeIcon width={14} height={14} className="shrink-0" style={{ color, opacity: 0.5 }} />
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export function useHeadingTitleSync({
|
||||
onTitleSync,
|
||||
}: HeadingTitleSyncConfig) {
|
||||
const syncActiveRef = useRef(true)
|
||||
const debounceTimerRef = useRef<ReturnType<typeof setTimeout>>()
|
||||
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
||||
const activeTabPathRef = useRef(activeTabPath)
|
||||
// eslint-disable-next-line react-hooks/refs
|
||||
activeTabPathRef.current = activeTabPath
|
||||
|
||||
Reference in New Issue
Block a user