feat(roadmap): add expand/collapse functionality for phase features (#1796)
* feat(roadmap): add expand/collapse functionality for phase features Previously, only 5 features were displayed per phase with a non-clickable "+X more features" text. This commit adds: - useState hook to track expanded/collapsed state per phase - Clickable "Show X more features" / "Show less" toggle button - ChevronDown/ChevronUp icons for visual feedback - i18n translations for expand/collapse labels (EN/FR) * fix(roadmap): use Button component for expand/collapse toggle Replace raw <button> with Button component for styling consistency. Add aria-expanded attribute for keyboard and screen reader accessibility. * fix(i18n): add pluralization for showMoreFeatures key * fix(roadmap): improve accessibility with functional setState and button elements - Use functional setState for isExpanded toggle - Change feature item from div to button for keyboard accessibility - Add type='button' and w-full text-left classes for proper layout * fix(roadmap): avoid nested buttons for accessibility Use div with role='button', tabIndex, and onKeyDown instead of button to avoid invalid nested interactive elements with inner Button components. * fix(roadmap): restructure feature row to avoid nested interactive elements - Remove role/button attributes from outer container div - Make the title/label area a semantic button for feature selection - Keep action buttons (View Task, Build) as independent clickable elements * fix(roadmap): add focus-visible styles for keyboard accessibility --------- Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { CheckCircle2, Circle, ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { CheckCircle2, ChevronDown, ChevronUp, Circle, ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TaskOutcomeBadge } from './TaskOutcomeBadge';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
@@ -7,6 +9,8 @@ import { Progress } from '../ui/progress';
|
||||
import { ROADMAP_PRIORITY_COLORS } from '../../../shared/constants';
|
||||
import type { PhaseCardProps } from './types';
|
||||
|
||||
const INITIAL_VISIBLE_COUNT = 5;
|
||||
|
||||
export function PhaseCard({
|
||||
phase,
|
||||
features,
|
||||
@@ -15,8 +19,13 @@ export function PhaseCard({
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
}: PhaseCardProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const completedCount = features.filter((f) => f.status === 'done').length;
|
||||
const progress = features.length > 0 ? (completedCount / features.length) * 100 : 0;
|
||||
const visibleFeatures = isExpanded ? features : features.slice(0, INITIAL_VISIBLE_COUNT);
|
||||
const hiddenCount = features.length - INITIAL_VISIBLE_COUNT;
|
||||
const hasMoreFeatures = hiddenCount > 0;
|
||||
|
||||
return (
|
||||
<Card className="p-4">
|
||||
@@ -87,13 +96,16 @@ export function PhaseCard({
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Features ({features.length})</h4>
|
||||
<div className="grid gap-2">
|
||||
{features.slice(0, 5).map((feature) => (
|
||||
{visibleFeatures.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className="flex items-center justify-between p-2 rounded-md bg-muted/50 hover:bg-muted cursor-pointer transition-colors"
|
||||
onClick={() => onFeatureSelect(feature)}
|
||||
className="flex items-center justify-between p-2 rounded-md bg-muted/50 hover:bg-muted transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
onClick={() => onFeatureSelect(feature)}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${ROADMAP_PRIORITY_COLORS[feature.priority]}`}
|
||||
@@ -104,7 +116,7 @@ export function PhaseCard({
|
||||
{feature.competitorInsightIds && feature.competitorInsightIds.length > 0 && (
|
||||
<TrendingUp className="h-3 w-3 text-primary flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
{feature.taskOutcome ? (
|
||||
<span className="flex-shrink-0">
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="lg" showLabel={false} />
|
||||
@@ -140,10 +152,26 @@ export function PhaseCard({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{features.length > 5 && (
|
||||
<div className="text-sm text-muted-foreground text-center py-1">
|
||||
+{features.length - 5} more features
|
||||
</div>
|
||||
{hasMoreFeatures && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
aria-expanded={isExpanded}
|
||||
className="flex items-center justify-center gap-1 text-sm text-muted-foreground hover:text-foreground w-full"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
{t('roadmap.showLessFeatures')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
{t('roadmap.showMoreFeatures', { count: hiddenCount })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -599,7 +599,10 @@
|
||||
"roadmap": {
|
||||
"taskCompleted": "Completed",
|
||||
"taskDeleted": "Deleted",
|
||||
"taskArchived": "Archived"
|
||||
"taskArchived": "Archived",
|
||||
"showMoreFeatures": "Show {{count}} more feature",
|
||||
"showMoreFeatures_plural": "Show {{count}} more features",
|
||||
"showLessFeatures": "Show less"
|
||||
},
|
||||
"roadmapGeneration": {
|
||||
"progress": "Progress",
|
||||
|
||||
@@ -599,7 +599,10 @@
|
||||
"roadmap": {
|
||||
"taskCompleted": "Terminé",
|
||||
"taskDeleted": "Supprimé",
|
||||
"taskArchived": "Archivé"
|
||||
"taskArchived": "Archivé",
|
||||
"showMoreFeatures": "Afficher {{count}} fonctionnalité supplémentaire",
|
||||
"showMoreFeatures_plural": "Afficher {{count}} fonctionnalités supplémentaires",
|
||||
"showLessFeatures": "Afficher moins"
|
||||
},
|
||||
"roadmapGeneration": {
|
||||
"progress": "Progression",
|
||||
|
||||
Reference in New Issue
Block a user