feat(tracker-web): SegmentedControl view toggle + board Tooltips (UX-12.1)

Replace the bespoke roadmap board/list toggle buttons (hardcoded blue-600) with
the shared SegmentedControl, and wrap truncated board-card titles in Tooltip.
Update the e2e toggle selector from button to radio for SegmentedControl.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-28 21:01:58 -07:00
parent 328e307212
commit ddf25cf501
4 changed files with 104 additions and 91 deletions

View File

@ -215,8 +215,11 @@ pnpm build # final gate
## UX-12 — Detail & board richness (Tabs · Tooltip · Drawer · Timeline · rich-text) ## UX-12 — Detail & board richness (Tabs · Tooltip · Drawer · Timeline · rich-text)
- [ ] **12.1** `/dashboard` board↔list: use `SegmentedControl` (or `Tabs`) for the view toggle - [x] **12.1** `/dashboard` board↔list: use `SegmentedControl` (or `Tabs`) for the view toggle
instead of bespoke buttons; `Tooltip` on truncated titles / status dots. instead of bespoke buttons; `Tooltip` on truncated titles / status dots.
(SegmentedControl now drives the roadmap board/list toggle — the app's only board↔list
toggle — replacing the bespoke `blue-600` buttons; `Tooltip` wraps the truncated board
card titles. e2e toggle selector updated button→radio. UX-12.1 verified: tc/lint/test 162 ✓/build/e2e 18 ✓)
- [ ] **12.2** Item detail: move row/item actions into an `ActionMenu`, and render the item's - [ ] **12.2** Item detail: move row/item actions into an `ActionMenu`, and render the item's
activity/comment history with `Timeline`. activity/comment history with `Timeline`.
- [ ] **12.3** _(stretch — needs HTML-capable description/comment storage)_ Swap the plain - [ ] **12.3** _(stretch — needs HTML-capable description/comment storage)_ Swap the plain

View File

@ -309,7 +309,8 @@ test.describe('Tracker — Public Roadmap', () => {
test('can toggle between board and list view', async ({ page }) => { test('can toggle between board and list view', async ({ page }) => {
await page.goto('/roadmap'); await page.goto('/roadmap');
await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible();
await page.getByRole('button', { name: 'List', exact: true }).click(); // The view toggle is a shared SegmentedControl (role=radio), UX-12.1.
await page.getByRole('radio', { name: 'List', exact: true }).click();
// List view still shows the items (rendered as rows, not a <table>) // List view still shows the items (rendered as rows, not a <table>)
await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Export to CSV' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Export to CSV' })).toBeVisible();

View File

@ -3,6 +3,12 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { PageHeader } from '@bytelyst/dashboard-components'; import { PageHeader } from '@bytelyst/dashboard-components';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/Primitives';
import { useAuth } from '@/lib/auth-context'; import { useAuth } from '@/lib/auth-context';
import { listItems, updateItemStatus, type TrackerItem } from '@/lib/tracker-client'; import { listItems, updateItemStatus, type TrackerItem } from '@/lib/tracker-client';
@ -56,6 +62,7 @@ export default function BoardPage() {
}; };
return ( return (
<TooltipProvider>
<div className="space-y-6"> <div className="space-y-6">
<PageHeader <PageHeader
title="Board" title="Board"
@ -102,12 +109,17 @@ export default function BoardPage() {
</span> </span>
</div> </div>
<Tooltip>
<TooltipTrigger asChild>
<Link <Link
href={`/dashboard/items/${item.id}`} href={`/dashboard/items/${item.id}`}
className="text-sm font-medium leading-tight hover:text-primary hover:underline" className="block truncate text-sm font-medium leading-tight hover:text-primary hover:underline"
> >
{item.title} {item.title}
</Link> </Link>
</TooltipTrigger>
<TooltipContent>{item.title}</TooltipContent>
</Tooltip>
<div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground"> <div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground">
{item.voteCount > 0 && <span>{item.voteCount} votes</span>} {item.voteCount > 0 && <span>{item.voteCount} votes</span>}
@ -139,5 +151,6 @@ export default function BoardPage() {
})} })}
</div> </div>
</div> </div>
</TooltipProvider>
); );
} }

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { SegmentedControl } from '@/components/ui/Primitives';
import { import {
getRoadmapItems, getRoadmapItems,
getRoadmapStats, getRoadmapStats,
@ -264,20 +265,15 @@ export default function RoadmapPage() {
<option value="bug">Bugs</option> <option value="bug">Bugs</option>
<option value="task">Tasks</option> <option value="task">Tasks</option>
</select> </select>
<div className="flex border border-slate-300 dark:border-slate-600 rounded-lg overflow-hidden"> <SegmentedControl
<button aria-label="Roadmap view"
onClick={() => setView('board')} value={view}
className={`px-3 py-2 text-sm ${view === 'board' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300'}`} onValueChange={v => setView(v as 'board' | 'list')}
> options={[
Board { value: 'board', label: 'Board' },
</button> { value: 'list', label: 'List' },
<button ]}
onClick={() => setView('list')} />
className={`px-3 py-2 text-sm ${view === 'list' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300'}`}
>
List
</button>
</div>
</div> </div>
{loading ? ( {loading ? (