mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: add Accordion component for UI and Zustand store for coordinator management
- Implemented Accordion component using Radix UI for collapsible sections. - Created Zustand store to manage coordinator execution state, command chains, logs, and interactive questions. - Added validation tests for CLI settings type definitions, ensuring type safety and correct behavior of helper functions.
This commit is contained in:
143
ccw/frontend/src/components/shared/NavGroup.tsx
Normal file
143
ccw/frontend/src/components/shared/NavGroup.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
// ========================================
|
||||
// NavGroup Component
|
||||
// ========================================
|
||||
// Collapsible navigation group using Radix Accordion
|
||||
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '@/components/ui/Accordion';
|
||||
|
||||
export interface NavItem {
|
||||
path: string;
|
||||
label: string;
|
||||
icon: React.ElementType;
|
||||
badge?: number | string;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'info';
|
||||
}
|
||||
|
||||
export interface NavGroupProps {
|
||||
/** Unique identifier for the group */
|
||||
groupId: string;
|
||||
/** Title i18n key */
|
||||
titleKey: string;
|
||||
/** Optional icon for group header */
|
||||
icon?: React.ElementType;
|
||||
/** Navigation items in this group */
|
||||
items: NavItem[];
|
||||
/** Whether sidebar is collapsed */
|
||||
collapsed?: boolean;
|
||||
/** Callback when nav item is clicked */
|
||||
onNavClick?: () => void;
|
||||
}
|
||||
|
||||
export function NavGroup({
|
||||
groupId,
|
||||
titleKey,
|
||||
icon: Icon,
|
||||
items,
|
||||
collapsed = false,
|
||||
onNavClick,
|
||||
}: NavGroupProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const location = useLocation();
|
||||
const title = formatMessage({ id: titleKey });
|
||||
|
||||
// If collapsed, render items without accordion
|
||||
if (collapsed) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{items.map((item) => {
|
||||
const ItemIcon = item.icon;
|
||||
const [basePath] = item.path.split('?');
|
||||
const isActive =
|
||||
location.pathname === basePath ||
|
||||
(basePath !== '/' && location.pathname.startsWith(basePath));
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={onNavClick}
|
||||
className={cn(
|
||||
'flex items-center justify-center gap-3 px-2 py-2.5 rounded-md text-sm transition-colors',
|
||||
'hover:bg-hover hover:text-foreground',
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
title={item.label}
|
||||
>
|
||||
<ItemIcon className="w-5 h-5 flex-shrink-0" />
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AccordionItem value={groupId} className="border-none">
|
||||
<AccordionTrigger className="px-3 py-2 hover:no-underline hover:bg-hover/50 rounded-md text-muted-foreground hover:text-foreground">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
{Icon && <Icon className="w-4 h-4" />}
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-1">
|
||||
<ul className="space-y-1">
|
||||
{items.map((item) => {
|
||||
const ItemIcon = item.icon;
|
||||
const [basePath, searchParams] = item.path.split('?');
|
||||
const isActive =
|
||||
location.pathname === basePath ||
|
||||
(basePath !== '/' && location.pathname.startsWith(basePath));
|
||||
const isQueryParamActive =
|
||||
searchParams && location.search.includes(searchParams);
|
||||
|
||||
return (
|
||||
<li key={item.path}>
|
||||
<NavLink
|
||||
to={item.path}
|
||||
onClick={onNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors pl-6',
|
||||
'hover:bg-hover hover:text-foreground',
|
||||
(isActive && !searchParams) || isQueryParamActive
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<ItemIcon className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="flex-1">{item.label}</span>
|
||||
{item.badge !== undefined && (
|
||||
<span
|
||||
className={cn(
|
||||
'px-2 py-0.5 text-xs font-semibold rounded-full',
|
||||
item.badgeVariant === 'success' &&
|
||||
'bg-success-light text-success',
|
||||
item.badgeVariant === 'warning' &&
|
||||
'bg-warning-light text-warning',
|
||||
item.badgeVariant === 'info' && 'bg-info-light text-info',
|
||||
(!item.badgeVariant || item.badgeVariant === 'default') &&
|
||||
'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavGroup;
|
||||
Reference in New Issue
Block a user