Skip to content

Commit 1b6781b

Browse files
committed
feat(ui): implements compact view density mode
1 parent 9c84fa1 commit 1b6781b

18 files changed

+353
-156
lines changed

src/app/components/dashboard/ActionsTab.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ export default function ActionsTab(props: ActionsTabProps) {
221221
return (
222222
<div class="divide-y divide-base-300">
223223
{/* Toolbar */}
224-
<div class="flex items-start gap-3 px-4 py-2 border-b border-base-300 bg-base-100">
225-
<div class="flex flex-wrap items-center gap-3 min-w-0 flex-1">
224+
<div class={`flex items-start px-4 border-b border-base-300 bg-base-100 ${config.viewDensity === "compact" ? "py-0.5 gap-2" : "py-2 gap-3"}`}>
225+
<div class={`flex flex-wrap items-center min-w-0 flex-1 ${config.viewDensity === "compact" ? "gap-2" : "gap-3"}`}>
226226
<label class="flex items-center gap-1.5 text-sm text-base-content/70 cursor-pointer select-none">
227227
<input
228228
type="checkbox"
@@ -299,7 +299,7 @@ export default function ActionsTab(props: ActionsTabProps) {
299299
<button
300300
onClick={() => toggleExpandedRepo("actions", repoGroup.repoFullName)}
301301
aria-expanded={isExpanded()}
302-
class="flex-1 flex items-center gap-2 px-4 py-2.5 text-left text-sm font-semibold text-base-content"
302+
class={`flex-1 flex items-center gap-2 px-4 text-left text-sm font-semibold text-base-content ${config.viewDensity === "compact" ? "py-1.5" : "py-2.5"}`}
303303
>
304304
<ChevronIcon size="md" rotated={!isExpanded()} />
305305
{repoGroup.repoFullName}

src/app/components/dashboard/IgnoreBadge.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createSignal, For, Show } from "solid-js";
22
import type { IgnoredItem } from "../../stores/view";
3+
import { config } from "../../stores/config";
34
import { Tooltip } from "../shared/Tooltip";
45

56
interface IgnoreBadgeProps {
@@ -47,16 +48,16 @@ export default function IgnoreBadge(props: IgnoreBadgeProps) {
4748
<Tooltip content={`${props.items.length} ignored item${props.items.length === 1 ? "" : "s"}`}>
4849
<button
4950
onClick={() => setOpen((v) => !v)}
50-
class="btn btn-ghost btn-sm relative"
51+
class={`btn btn-ghost relative ${config.viewDensity === "compact" ? "btn-xs" : "btn-sm"}`}
5152
aria-haspopup="true"
5253
aria-expanded={open()}
5354
aria-label={`${props.items.length} ignored items`}
5455
>
55-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
56+
<svg xmlns="http://www.w3.org/2000/svg" class={config.viewDensity === "compact" ? "h-3.5 w-3.5" : "h-4 w-4"} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
5657
<path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd" />
5758
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
5859
</svg>
59-
<span class="badge badge-neutral badge-xs absolute -top-1 -right-1">{props.items.length}</span>
60+
<span class={`badge badge-neutral absolute ${config.viewDensity === "compact" ? "badge-xs text-[8px] -top-0.5 -right-0.5 px-0.5 min-w-3 h-3" : "badge-xs -top-1 -right-1"}`}>{props.items.length}</span>
6061
</button>
6162
</Tooltip>
6263

src/app/components/dashboard/IssuesTab.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ export default function IssuesTab(props: IssuesTabProps) {
240240
return (
241241
<div class="flex flex-col h-full">
242242
{/* Filter chips + ignore badge toolbar */}
243-
<div class="flex items-start gap-3 px-4 py-2 border-b border-base-300 bg-base-100">
244-
<div class="flex flex-wrap items-center gap-3 min-w-0 flex-1">
243+
<div class={`flex items-start px-4 border-b border-base-300 bg-base-100 ${config.viewDensity === "compact" ? "py-0.5 gap-2" : "py-2 gap-3"}`}>
244+
<div class={`flex flex-wrap items-center min-w-0 flex-1 ${config.viewDensity === "compact" ? "gap-2" : "gap-3"}`}>
245245
<FilterToolbar
246246
groups={filterGroups()}
247247
values={viewState.tabFilters.issues}
@@ -339,7 +339,7 @@ export default function IssuesTab(props: IssuesTabProps) {
339339
<button
340340
onClick={() => toggleExpandedRepo("issues", repoGroup.repoFullName)}
341341
aria-expanded={isExpanded()}
342-
class="flex-1 flex items-center gap-2 px-4 py-2.5 text-left text-sm font-semibold text-base-content"
342+
class={`flex-1 flex items-center gap-2 px-4 text-left text-sm font-semibold text-base-content ${config.viewDensity === "compact" ? "py-1.5" : "py-2.5"}`}
343343
>
344344
<ChevronIcon size="md" rotated={!isExpanded()} />
345345
{repoGroup.repoFullName}

src/app/components/dashboard/ItemRow.tsx

Lines changed: 148 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,25 @@ export default function ItemRow(props: ItemRowProps) {
5656
return created !== "" && updated !== "" && created !== updated;
5757
});
5858

59+
const compactLabelTooltip = createMemo(() => {
60+
const parts: string[] = [];
61+
if (props.labels.length > 0) {
62+
parts.push(`Labels: ${props.labels.map((l) => expandEmoji(l.name)).join(", ")}`);
63+
}
64+
if ((props.commentCount ?? 0) > 0) {
65+
parts.push(`${props.commentCount} comment${props.commentCount === 1 ? "" : "s"}`);
66+
}
67+
return parts.join(" | ");
68+
});
69+
const hasCompactTooltip = createMemo(() => isCompact() && compactLabelTooltip() !== "");
70+
const hasLabels = createMemo(() => props.labels.length > 0);
71+
5972
return (
6073
<div
61-
class={`group relative flex items-start gap-3
74+
class={`group relative flex items-center gap-2
6275
hover:bg-base-200
6376
transition-colors
64-
${isCompact() ? "px-4 py-2" : "px-4 py-3"}
77+
${isCompact() ? "px-3 py-1 items-center gap-2" : "px-4 py-3 items-start gap-3"}
6578
${props.isFlashing ? "animate-flash" : props.isPolling ? "animate-shimmer" : ""}`}
6679
>
6780
{/* Overlay link — covers entire row; interactive children use relative z-10 */}
@@ -90,89 +103,151 @@ export default function ItemRow(props: ItemRowProps) {
90103
</Tooltip>
91104
</Show>
92105

93-
{/* Main content */}
94-
<div class="flex-1 min-w-0">
95-
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm">
96-
<span class="text-base-content/60 shrink-0">
97-
#{props.number}
98-
</span>
99-
<span class="font-medium text-base-content truncate">
100-
{props.title}
101-
</span>
102-
</div>
106+
{/* ── COMPACT LAYOUT: everything on one line ── */}
107+
<Show when={isCompact()}>
108+
{/* Number */}
109+
<span class="text-xs text-base-content/50 shrink-0">#{props.number}</span>
103110

104-
{/* Labels row */}
105-
<Show when={props.labels.length > 0}>
106-
<div class={`flex flex-wrap gap-1 ${isCompact() ? "mt-0.5" : "mt-1"}`}>
107-
<For each={props.labels}>
108-
{(label) => (
109-
<span
110-
class={`inline-flex items-center rounded-full text-xs px-2 py-0.5 font-medium ${labelColorClass(label.color)}`}
111-
>
112-
{expandEmoji(label.name)}
113-
</span>
114-
)}
115-
</For>
116-
</div>
117-
</Show>
111+
{/* Title — truncated, fills available space */}
112+
<span class="font-medium text-sm truncate flex-1 min-w-0">
113+
{props.title}
114+
</span>
118115

119-
{/* Additional children slot — z-10 to sit above stretched link */}
116+
{/* Children (badges) inline */}
120117
<Show when={props.children !== undefined}>
121-
<div class={`relative z-10 ${isCompact() ? "mt-0.5" : "mt-1"}`}>{props.children}</div>
118+
<div class="relative z-10 shrink-0 flex items-center gap-1">{props.children}</div>
119+
</Show>
120+
121+
{/* Label + comment count indicator via tooltip */}
122+
<Show when={hasCompactTooltip()}>
123+
<Tooltip content={compactLabelTooltip()} placement="top" focusable>
124+
<span class="relative z-10 inline-flex items-center gap-0.5 text-xs text-base-content/40 cursor-default select-none">
125+
<Show when={hasLabels()}>
126+
<span class="inline-flex items-center gap-0.5">
127+
<svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
128+
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
129+
</svg>
130+
{props.labels.length}
131+
</span>
132+
</Show>
133+
<Show when={(props.commentCount ?? 0) > 0}>
134+
<span class="inline-flex items-center gap-0.5">
135+
<svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
136+
<path fill-rule="evenodd" d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7z" clip-rule="evenodd" />
137+
</svg>
138+
{formatCount(props.commentCount!)}
139+
</span>
140+
</Show>
141+
</span>
142+
</Tooltip>
122143
</Show>
123-
</div>
124144

125-
{/* Author + time + comment count */}
126-
<div class={`shrink-0 flex flex-col items-end gap-0.5 text-xs text-base-content/60 ${isCompact() ? "" : "pt-0.5"}`}>
127-
<span>{props.author}</span>
145+
{/* surfacedByBadge */}
128146
<Show when={props.surfacedByBadge !== undefined}>
129-
<div class="relative z-10">{props.surfacedByBadge}</div>
147+
<div class="relative z-10 shrink-0">{props.surfacedByBadge}</div>
130148
</Show>
131-
<span class="inline-flex items-center gap-1 whitespace-nowrap">
132-
<Tooltip content={staticDateInfo().createdTitle} class="relative z-10">
133-
<time
134-
datetime={props.createdAt}
135-
aria-label={dateDisplay().createdLabel}
136-
>
137-
{dateDisplay().created}
138-
</time>
139-
</Tooltip>
140-
<Show when={shouldShowUpdated()}>
141-
<span aria-hidden="true">{"\u00B7"}</span>
142-
<Tooltip content={staticDateInfo().updatedTitle} class="relative z-10">
149+
150+
{/* Author + time — compact, inline */}
151+
<span class="shrink-0 text-xs text-base-content/50 whitespace-nowrap">
152+
{props.author}
153+
{" · "}
154+
<time datetime={props.updatedAt} title={staticDateInfo().updatedTitle} aria-label={dateDisplay().updatedLabel}>
155+
{dateDisplay().updated || dateDisplay().created}
156+
</time>
157+
</span>
158+
159+
{/* Poll spinner */}
160+
<Show when={props.isPolling}>
161+
<span class="loading loading-spinner loading-xs text-base-content/40 shrink-0" />
162+
</Show>
163+
</Show>
164+
165+
{/* ── COMFORTABLE LAYOUT: multi-line original ── */}
166+
<Show when={!isCompact()}>
167+
{/* Main content */}
168+
<div class="flex-1 min-w-0">
169+
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm">
170+
<span class="text-base-content/60 shrink-0">
171+
#{props.number}
172+
</span>
173+
<span class="font-medium text-base-content truncate">
174+
{props.title}
175+
</span>
176+
</div>
177+
178+
{/* Labels row */}
179+
<Show when={props.labels.length > 0}>
180+
<div class="flex flex-wrap gap-1 mt-1">
181+
<For each={props.labels}>
182+
{(label) => (
183+
<span
184+
class={`inline-flex items-center rounded-full text-xs px-2 py-0.5 font-medium ${labelColorClass(label.color)}`}
185+
>
186+
{expandEmoji(label.name)}
187+
</span>
188+
)}
189+
</For>
190+
</div>
191+
</Show>
192+
193+
{/* Additional children slot — z-10 to sit above stretched link */}
194+
<Show when={props.children !== undefined}>
195+
<div class="relative z-10 mt-1">{props.children}</div>
196+
</Show>
197+
</div>
198+
199+
{/* Author + time + comment count */}
200+
<div class="shrink-0 flex flex-col items-end gap-0.5 text-xs text-base-content/60 pt-0.5">
201+
<span>{props.author}</span>
202+
<Show when={props.surfacedByBadge !== undefined}>
203+
<div class="relative z-10">{props.surfacedByBadge}</div>
204+
</Show>
205+
<span class="inline-flex items-center gap-1 whitespace-nowrap">
206+
<Tooltip content={staticDateInfo().createdTitle} class="relative z-10">
143207
<time
144-
datetime={props.updatedAt}
145-
aria-label={dateDisplay().updatedLabel}
208+
datetime={props.createdAt}
209+
aria-label={dateDisplay().createdLabel}
146210
>
147-
{dateDisplay().updated}
211+
{dateDisplay().created}
148212
</time>
149213
</Tooltip>
214+
<Show when={shouldShowUpdated()}>
215+
<span aria-hidden="true">{"\u00B7"}</span>
216+
<Tooltip content={staticDateInfo().updatedTitle} class="relative z-10">
217+
<time
218+
datetime={props.updatedAt}
219+
aria-label={dateDisplay().updatedLabel}
220+
>
221+
{dateDisplay().updated}
222+
</time>
223+
</Tooltip>
224+
</Show>
225+
</span>
226+
<Show when={props.isPolling}>
227+
<span class="loading loading-spinner loading-xs text-base-content/40" />
228+
</Show>
229+
<Show when={(props.commentCount ?? 0) > 0}>
230+
<Tooltip content={`${props.commentCount} total ${props.commentCount === 1 ? "comment" : "comments"}`} focusable class="relative z-10">
231+
<span class="flex items-center gap-0.5">
232+
<svg
233+
xmlns="http://www.w3.org/2000/svg"
234+
class="h-3 w-3"
235+
viewBox="0 0 20 20"
236+
fill="currentColor"
237+
aria-hidden="true"
238+
>
239+
<path
240+
fill-rule="evenodd"
241+
d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z"
242+
clip-rule="evenodd"
243+
/>
244+
</svg>
245+
{formatCount(props.commentCount!)}
246+
</span>
247+
</Tooltip>
150248
</Show>
151-
</span>
152-
<Show when={props.isPolling}>
153-
<span class="loading loading-spinner loading-xs text-base-content/40" />
154-
</Show>
155-
<Show when={(props.commentCount ?? 0) > 0}>
156-
<Tooltip content={`${props.commentCount} total ${props.commentCount === 1 ? "comment" : "comments"}`} focusable class="relative z-10">
157-
<span class="flex items-center gap-0.5">
158-
<svg
159-
xmlns="http://www.w3.org/2000/svg"
160-
class="h-3 w-3"
161-
viewBox="0 0 20 20"
162-
fill="currentColor"
163-
aria-hidden="true"
164-
>
165-
<path
166-
fill-rule="evenodd"
167-
d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z"
168-
clip-rule="evenodd"
169-
/>
170-
</svg>
171-
{formatCount(props.commentCount!)}
172-
</span>
173-
</Tooltip>
174-
</Show>
175-
</div>
249+
</div>
250+
</Show>
176251

177252
{/* Ignore button — visible on hover */}
178253
<Tooltip content="Ignore" class="shrink-0 self-center relative z-10">

0 commit comments

Comments
 (0)