Polish UI: SearchBar sizing, SessionList layout, Tooltip offset, and progress CSS
SearchBar: - Switch from fixed w-80/sm:w-96 to fluid min-w-80 max-w-md w-full - Adjust left padding for better visual alignment with search icon SessionList: - Memoize project grouping computation with useMemo - Move horizontal padding from per-item margin (mx-2 + width calc hack) to container padding (px-2) for cleaner layout and full-width buttons - Remove inline width override that was compensating for the old margins Tooltip: - Increase offset from 8px to 12px for better visual separation CSS: - Add prose-message-progress variant with compact 11px mono typography for progress event content (code blocks, tables, links, blockquotes) - Reduce search minimap marker height from 4px to 3px - Normalize prose-message line-height: paragraphs 1.625→1.6, list items 1.5→1.6 for consistent rhythm - Switch custom checkbox checkmark sizing from fixed px to percentages for better scaling across different zoom levels Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,7 @@ export function SearchBar({
|
|||||||
const showControls = !!localQuery || !!query;
|
const showControls = !!localQuery || !!query;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-80 sm:w-96">
|
<div className="min-w-80 max-w-md w-full">
|
||||||
{/* Unified search container */}
|
{/* Unified search container */}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
@@ -137,7 +137,7 @@ export function SearchBar({
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
placeholder="Search messages..."
|
placeholder="Search messages..."
|
||||||
className="flex-1 min-w-0 bg-transparent px-2.5 py-2 text-body text-foreground
|
className="flex-1 min-w-0 bg-transparent pl-3 pr-2.5 py-2 text-body text-foreground
|
||||||
placeholder:text-foreground-muted
|
placeholder:text-foreground-muted
|
||||||
focus:outline-none"
|
focus:outline-none"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
import type { SessionEntry } from "../lib/types";
|
import type { SessionEntry } from "../lib/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -11,13 +11,16 @@ interface Props {
|
|||||||
export function SessionList({ sessions, loading, selectedId, onSelect }: Props) {
|
export function SessionList({ sessions, loading, selectedId, onSelect }: Props) {
|
||||||
const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||||
|
|
||||||
// Group by project
|
// Group by project (memoized to avoid recomputing on unrelated rerenders)
|
||||||
const grouped = new Map<string, SessionEntry[]>();
|
const grouped = useMemo(() => {
|
||||||
for (const session of sessions) {
|
const map = new Map<string, SessionEntry[]>();
|
||||||
const group = grouped.get(session.project) || [];
|
for (const session of sessions) {
|
||||||
group.push(session);
|
const group = map.get(session.project) || [];
|
||||||
grouped.set(session.project, group);
|
group.push(session);
|
||||||
}
|
map.set(session.project, group);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [sessions]);
|
||||||
|
|
||||||
// Auto-select project when selectedId changes
|
// Auto-select project when selectedId changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -73,7 +76,7 @@ export function SessionList({ sessions, loading, selectedId, onSelect }: Props)
|
|||||||
<div className="px-4 py-2 text-caption font-semibold text-foreground-muted uppercase tracking-wider border-b border-border-muted" style={{ background: "var(--color-surface-inset)" }}>
|
<div className="px-4 py-2 text-caption font-semibold text-foreground-muted uppercase tracking-wider border-b border-border-muted" style={{ background: "var(--color-surface-inset)" }}>
|
||||||
{formatProjectName(selectedProject)}
|
{formatProjectName(selectedProject)}
|
||||||
</div>
|
</div>
|
||||||
<div className="py-1">
|
<div className="py-1 px-2">
|
||||||
{projectSessions.map((session, idx) => {
|
{projectSessions.map((session, idx) => {
|
||||||
const isSelected = selectedId === session.id;
|
const isSelected = selectedId === session.id;
|
||||||
return (
|
return (
|
||||||
@@ -81,13 +84,13 @@ export function SessionList({ sessions, loading, selectedId, onSelect }: Props)
|
|||||||
key={session.id}
|
key={session.id}
|
||||||
onClick={() => onSelect(session.id)}
|
onClick={() => onSelect(session.id)}
|
||||||
className={`
|
className={`
|
||||||
w-full text-left mx-2 my-0.5 px-3 py-2.5 rounded-lg transition-all duration-200
|
w-full text-left my-0.5 px-3 py-2.5 rounded-lg transition-all duration-200
|
||||||
${isSelected
|
${isSelected
|
||||||
? "bg-accent-light shadow-glow-accent ring-1 ring-accent/25"
|
? "bg-accent-light shadow-glow-accent ring-1 ring-accent/25"
|
||||||
: "hover:bg-surface-overlay"
|
: "hover:bg-surface-overlay"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
style={{ width: "calc(100% - 1rem)", animationDelay: `${idx * 30}ms` }}
|
style={{ animationDelay: `${idx * 30}ms` }}
|
||||||
>
|
>
|
||||||
<div className={`text-body font-medium truncate ${isSelected ? "text-accent-dark" : "text-foreground"}`}>
|
<div className={`text-body font-medium truncate ${isSelected ? "text-accent-dark" : "text-foreground"}`}>
|
||||||
{session.summary || session.firstPrompt || "Untitled Session"}
|
{session.summary || session.firstPrompt || "Untitled Session"}
|
||||||
@@ -113,7 +116,7 @@ export function SessionList({ sessions, loading, selectedId, onSelect }: Props)
|
|||||||
|
|
||||||
// Project list
|
// Project list
|
||||||
return (
|
return (
|
||||||
<div className="py-1 animate-fade-in">
|
<div className="py-1 px-2 animate-fade-in">
|
||||||
{[...grouped.entries()].map(([project, projectSessions]) => {
|
{[...grouped.entries()].map(([project, projectSessions]) => {
|
||||||
const latest = projectSessions.reduce((a, b) =>
|
const latest = projectSessions.reduce((a, b) =>
|
||||||
(a.modified || a.created) > (b.modified || b.created) ? a : b
|
(a.modified || a.created) > (b.modified || b.created) ? a : b
|
||||||
@@ -123,8 +126,7 @@ export function SessionList({ sessions, loading, selectedId, onSelect }: Props)
|
|||||||
<button
|
<button
|
||||||
key={project}
|
key={project}
|
||||||
onClick={() => setSelectedProject(project)}
|
onClick={() => setSelectedProject(project)}
|
||||||
className="w-full text-left mx-2 my-0.5 px-3 py-2.5 rounded-lg hover:bg-surface-overlay transition-all duration-200 group"
|
className="w-full text-left my-0.5 px-3 py-2.5 rounded-lg hover:bg-surface-overlay transition-all duration-200 group"
|
||||||
style={{ width: "calc(100% - 1rem)" }}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-body font-medium text-foreground truncate">
|
<div className="text-body font-medium text-foreground truncate">
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function Tooltip({ content, children, delayMs = 150, side = "top" }: Prop
|
|||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
left: position.x,
|
left: position.x,
|
||||||
top: side === "top" ? position.y - 8 : position.y + 8,
|
top: side === "top" ? position.y - 12 : position.y + 12,
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
}}
|
}}
|
||||||
data-side={side}
|
data-side={side}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ mark.search-highlight {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 4px;
|
height: 3px;
|
||||||
background: rgba(254, 240, 138, 0.7);
|
background: rgba(254, 240, 138, 0.7);
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -362,7 +362,7 @@ mark.search-highlight {
|
|||||||
.prose-message p {
|
.prose-message p {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
line-height: 1.625;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-message p:first-child {
|
.prose-message p:first-child {
|
||||||
@@ -383,7 +383,7 @@ mark.search-highlight {
|
|||||||
.prose-message li {
|
.prose-message li {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-message code:not(pre code) {
|
.prose-message code:not(pre code) {
|
||||||
@@ -445,6 +445,116 @@ mark.search-highlight {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
Progress markdown (compact variant)
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
.prose-message-progress {
|
||||||
|
font-family: "JetBrains Mono", "Fira Code", "SF Mono", ui-monospace, monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress h1,
|
||||||
|
.prose-message-progress h2,
|
||||||
|
.prose-message-progress h3,
|
||||||
|
.prose-message-progress h4,
|
||||||
|
.prose-message-progress h5,
|
||||||
|
.prose-message-progress h6 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress p {
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
margin-bottom: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress ul,
|
||||||
|
.prose-message-progress ol {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress li {
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
margin-bottom: 0.125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress code:not(pre code) {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 0.0625rem 0.25rem;
|
||||||
|
border-radius: 0.1875rem;
|
||||||
|
background: var(--color-surface-inset);
|
||||||
|
border: 1px solid var(--color-border-muted);
|
||||||
|
color: #c4a1ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress pre {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress blockquote {
|
||||||
|
border-left: 2px solid var(--color-border);
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress a {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
text-decoration-color: rgba(91, 156, 245, 0.3);
|
||||||
|
transition: text-decoration-color 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress a:hover {
|
||||||
|
color: var(--color-accent-dark);
|
||||||
|
text-decoration-color: rgba(125, 180, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
margin-bottom: 0.375rem;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress th,
|
||||||
|
.prose-message-progress td {
|
||||||
|
border: 1px solid var(--color-border-muted);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-message-progress th {
|
||||||
|
background: var(--color-surface-inset);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════
|
||||||
Focus ring system
|
Focus ring system
|
||||||
═══════════════════════════════════════════════ */
|
═══════════════════════════════════════════════ */
|
||||||
@@ -481,10 +591,10 @@ mark.search-highlight {
|
|||||||
.custom-checkbox:checked::after {
|
.custom-checkbox:checked::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 6.25%;
|
||||||
left: 4px;
|
left: 25%;
|
||||||
width: 5px;
|
width: 31.25%;
|
||||||
height: 9px;
|
height: 56.25%;
|
||||||
border: solid #0c1017;
|
border: solid #0c1017;
|
||||||
border-width: 0 2px 2px 0;
|
border-width: 0 2px 2px 0;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
|
|||||||
Reference in New Issue
Block a user