fix(web): improve quadrant-matrix layout and text overflow handling

- Simplify axis label layout with horizontal/vertical arrangement
- Add proper text truncation with line-clamp and tooltips
- Fix overflow issues by adding min-w-0 on flex children
- Move scores inline with task name for compact display
- Add task count badge to quadrant headers
- Reduce maxDisplay to 3 for better density
This commit is contained in:
yyh
2026-01-16 16:58:06 +08:00
parent 13f2a43ccc
commit d62e16b9bb
3 changed files with 110 additions and 116 deletions

View File

@ -2,7 +2,6 @@
import type { FC } from 'react'
import type { QuadrantData } from './types'
import { useMemo } from 'react'
import { cn } from '@/utils/classnames'
import QuadrantCard from './quadrant-card'
import { isValidQuadrantData, QUADRANT_CONFIGS } from './types'
@ -28,9 +27,9 @@ const QuadrantMatrix: FC<QuadrantMatrixProps> = ({ content }) => {
if (!parsedData) {
return (
<div className="flex items-center justify-center rounded-lg bg-components-panel-bg-blur p-8">
<div className="flex items-center justify-center rounded-xl bg-components-panel-bg-blur p-8">
<div className="text-center text-text-secondary">
<div className="mb-2 text-lg">Invalid Quadrant Data</div>
<div className="system-md-semibold mb-2">Invalid Quadrant Data</div>
<div className="text-sm text-text-tertiary">
Expected JSON format with q1, q2, q3, q4 arrays
</div>
@ -46,7 +45,7 @@ const QuadrantMatrix: FC<QuadrantMatrixProps> = ({ content }) => {
+ parsedData.q4.length
return (
<div className="w-full rounded-lg bg-components-panel-bg-blur p-4">
<div className="w-full overflow-hidden rounded-xl bg-components-panel-bg-blur p-4">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<div>
@ -59,78 +58,77 @@ const QuadrantMatrix: FC<QuadrantMatrixProps> = ({ content }) => {
task
{totalTasks !== 1 ? 's' : ''}
{' '}
across 4 quadrants
prioritized
</div>
</div>
<div className="flex items-center gap-1 text-xs text-text-quaternary">
<span className="text-text-accent">I</span>
=Importance
<span className="ml-2 text-text-warning">U</span>
=Urgency
{/* Legend */}
<div className="flex items-center gap-3 text-[11px] text-text-quaternary">
<span>
<span className="font-medium text-text-accent">I</span>
{' '}
= Importance
</span>
<span>
<span className="font-medium text-text-warning">U</span>
{' '}
= Urgency
</span>
</div>
</div>
{/* Axis Labels */}
<div className="relative">
{/* Importance Label (Top Center) */}
<div className="mb-2 flex items-center justify-center">
<span className="rounded bg-state-accent-hover px-2 py-0.5 text-xs font-medium text-text-accent">
Important
{/* Axis Labels - Horizontal */}
<div className="mb-2 grid grid-cols-2 gap-3 pl-9">
<div className="text-center text-[11px] text-text-tertiary">
<span className="rounded bg-components-panel-on-panel-item-bg px-2 py-0.5">
Not Urgent
</span>
</div>
<div className="text-center text-[11px] text-text-warning">
<span className="rounded bg-state-warning-hover px-2 py-0.5">
Urgent
</span>
</div>
</div>
{/* Main Grid with Urgency Labels */}
<div className="flex gap-2">
{/* Left: Not Urgent Label */}
<div className="flex w-6 shrink-0 items-center justify-center">
<span
className={cn(
'-rotate-90 whitespace-nowrap rounded px-2 py-0.5 text-xs font-medium',
'bg-components-panel-on-panel-item-bg text-text-tertiary',
)}
>
Not Urgent
{/* Main Grid with Row Labels */}
<div className="flex gap-3">
{/* Row Labels - Vertical (rotated 90 degrees) */}
<div className="flex w-6 shrink-0 flex-col gap-3">
<div className="flex min-h-[200px] items-center justify-center">
<span className="-rotate-90 whitespace-nowrap rounded bg-state-accent-hover px-2 py-0.5 text-[11px] text-text-accent">
Important
</span>
</div>
{/* Center: 2x2 Grid */}
<div className="flex-1">
<div className="grid grid-cols-2 gap-3">
{/* Row 1: Important */}
<QuadrantCard
config={QUADRANT_CONFIGS.q2}
tasks={parsedData.q2}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q1}
tasks={parsedData.q1}
/>
{/* Row 2: Not Important */}
<QuadrantCard
config={QUADRANT_CONFIGS.q4}
tasks={parsedData.q4}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q3}
tasks={parsedData.q3}
/>
</div>
</div>
{/* Right: Urgent Label */}
<div className="flex w-6 shrink-0 items-center justify-center">
<span className="-rotate-90 whitespace-nowrap rounded bg-state-warning-hover px-2 py-0.5 text-xs font-medium text-text-warning">
Urgent
<div className="flex min-h-[200px] items-center justify-center">
<span className="-rotate-90 whitespace-nowrap rounded bg-components-panel-on-panel-item-bg px-2 py-0.5 text-[11px] text-text-tertiary">
Not Important
</span>
</div>
</div>
{/* Not Important Label (Bottom Center) */}
<div className="mt-2 flex items-center justify-center">
<span className="rounded bg-components-panel-on-panel-item-bg px-2 py-0.5 text-xs font-medium text-text-tertiary">
Not Important
</span>
{/* 2x2 Grid */}
<div className="min-w-0 flex-1">
<div className="grid grid-cols-2 gap-3">
{/* Row 1: Important */}
<QuadrantCard
config={QUADRANT_CONFIGS.q2}
tasks={parsedData.q2}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q1}
tasks={parsedData.q1}
/>
{/* Row 2: Not Important */}
<QuadrantCard
config={QUADRANT_CONFIGS.q4}
tasks={parsedData.q4}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q3}
tasks={parsedData.q3}
/>
</div>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@ type QuadrantCardProps = {
const QuadrantCard: FC<QuadrantCardProps> = ({
config,
tasks,
maxDisplay = 5,
maxDisplay = 3,
}) => {
const { title, subtitle, bgClass, borderClass, titleClass } = config
const displayTasks = tasks.slice(0, maxDisplay)
@ -22,19 +22,26 @@ const QuadrantCard: FC<QuadrantCardProps> = ({
return (
<div
className={cn(
'flex min-h-[180px] flex-col rounded-xl border p-3',
'flex min-h-[200px] min-w-0 flex-col rounded-xl border p-3',
bgClass,
borderClass,
)}
>
{/* Header */}
<div className="mb-2">
<div className={cn('system-sm-semibold', titleClass)}>{title}</div>
<div className="text-xs text-text-tertiary">{subtitle}</div>
<div className="mb-2 shrink-0">
<div className="flex items-center gap-2">
<span className={cn('system-sm-semibold', titleClass)}>{title}</span>
{tasks.length > 0 && (
<span className="bg-components-badge-bg-gray rounded-full px-1.5 py-0.5 text-[10px] font-medium text-text-tertiary">
{tasks.length}
</span>
)}
</div>
<div className="text-[11px] text-text-tertiary">{subtitle}</div>
</div>
{/* Task List */}
<div className="flex flex-1 flex-col gap-2">
{/* Task List - scrollable area */}
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
{displayTasks.length > 0
? (
displayTasks.map((task, index) => (
@ -50,7 +57,7 @@ const QuadrantCard: FC<QuadrantCardProps> = ({
{/* More indicator */}
{remainingCount > 0 && (
<div className="mt-2 text-center text-xs text-text-tertiary">
<div className="mt-2 shrink-0 text-center text-[11px] text-text-tertiary">
+
{remainingCount}
{' '}

View File

@ -3,22 +3,6 @@ import type { FC } from 'react'
import type { Task } from './types'
import { cn } from '@/utils/classnames'
type ScoreBadgeProps = {
label: string
score: number
colorClass: string
}
const ScoreBadge: FC<ScoreBadgeProps> = ({ label, score, colorClass }) => {
return (
<span className={cn('text-xs font-medium', colorClass)}>
{label}
:
{score}
</span>
)
}
type TaskItemProps = {
task: Task
showScores?: boolean
@ -28,47 +12,52 @@ const TaskItem: FC<TaskItemProps> = ({ task, showScores = true }) => {
const { name, description, deadline, importance_score, urgency_score, action_advice } = task
return (
<div className="group rounded-lg bg-components-panel-bg p-2.5 shadow-xs transition-all hover:shadow-sm">
{/* Task Name */}
<div className="system-sm-medium text-text-primary">{name}</div>
<div className="group min-w-0 rounded-lg bg-components-panel-bg p-2.5 shadow-xs transition-all hover:shadow-sm">
{/* Header: Task Name + Scores */}
<div className="flex items-start justify-between gap-2">
<div className="system-sm-medium min-w-0 flex-1 truncate text-text-primary" title={name}>
{name}
</div>
{showScores && (
<div className="flex shrink-0 items-center gap-1 text-[10px] font-medium">
<span className="text-text-accent">
I:
{importance_score}
</span>
<span className="text-text-warning">
U:
{urgency_score}
</span>
</div>
)}
</div>
{/* Description (if exists) */}
{/* Description */}
{description && (
<div className="mt-1 line-clamp-2 text-xs text-text-tertiary">
{description}
</div>
)}
{/* Metadata Row */}
<div className="mt-2 flex flex-wrap items-center gap-2">
{/* Deadline Badge */}
{deadline && (
<span className="bg-components-badge-bg-gray inline-flex items-center rounded px-1.5 py-0.5 text-xs text-text-tertiary">
{/* Deadline Badge */}
{deadline && (
<div className="mt-1.5">
<span className={cn(
'inline-flex items-center rounded px-1.5 py-0.5 text-[10px]',
'bg-components-badge-bg-gray text-text-tertiary',
)}
>
{deadline}
</span>
)}
</div>
)}
{/* Scores (optional) */}
{showScores && (
<div className="flex items-center gap-1.5">
<ScoreBadge
label="I"
score={importance_score}
colorClass="text-text-accent"
/>
<ScoreBadge
label="U"
score={urgency_score}
colorClass="text-text-warning"
/>
</div>
)}
</div>
{/* Action Advice (if exists) */}
{/* Action Advice */}
{action_advice && (
<div className="mt-2 border-t border-divider-subtle pt-2 text-xs italic text-text-quaternary">
{action_advice}
<div className="mt-2 overflow-hidden border-t border-divider-subtle pt-2">
<p className="line-clamp-2 text-xs italic text-text-quaternary" title={action_advice}>
{action_advice}
</p>
</div>
)}
</div>