141 lines
4.6 KiB
TypeScript
141 lines
4.6 KiB
TypeScript
|
|
import { Badge } from '@nice/ui/components/badge';
|
|||
|
|
import { Button } from '@nice/ui/components/button';
|
|||
|
|
import { Input } from '@nice/ui/components/input';
|
|||
|
|
import { Label } from '@nice/ui/components/label';
|
|||
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nice/ui/components/select';
|
|||
|
|
import { TabsList, TabsTrigger } from '@nice/ui/components/tabs';
|
|||
|
|
import { IconSearch } from '@tabler/icons-react';
|
|||
|
|
import { SORT_OPTIONS, BATCH_ACTIONS } from '@/lib/articles/constants';
|
|||
|
|
import { TermSelect } from '../selector/term-select';
|
|||
|
|
import { useArticlesContext } from './context';
|
|||
|
|
|
|||
|
|
export function ArticleToolbar() {
|
|||
|
|
const {
|
|||
|
|
filters,
|
|||
|
|
selectedArticles,
|
|||
|
|
batchAction,
|
|||
|
|
stats,
|
|||
|
|
updateFilters,
|
|||
|
|
setBatchAction,
|
|||
|
|
handleBatchAction,
|
|||
|
|
resetSelection,
|
|||
|
|
} = useArticlesContext();
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="bg-background px-6 py-4 ">
|
|||
|
|
<div className="flex items-center justify-between gap-4">
|
|||
|
|
{/* 左侧:状态标签页 */}
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<TabsList className="grid w-auto grid-cols-5 h-9">
|
|||
|
|
<TabsTrigger value="all" className="px-3 text-sm">
|
|||
|
|
全部{' '}
|
|||
|
|
<Badge variant="secondary" className="ml-1 text-xs">
|
|||
|
|
{stats.all}
|
|||
|
|
</Badge>
|
|||
|
|
</TabsTrigger>
|
|||
|
|
<TabsTrigger value="published" className="px-3 text-sm">
|
|||
|
|
已发布{' '}
|
|||
|
|
<Badge variant="secondary" className="ml-1 text-xs">
|
|||
|
|
{stats.published}
|
|||
|
|
</Badge>
|
|||
|
|
</TabsTrigger>
|
|||
|
|
<TabsTrigger value="draft" className="px-3 text-sm">
|
|||
|
|
草稿{' '}
|
|||
|
|
<Badge variant="secondary" className="ml-1 text-xs">
|
|||
|
|
{stats.draft}
|
|||
|
|
</Badge>
|
|||
|
|
</TabsTrigger>
|
|||
|
|
<TabsTrigger value="archived" className="px-3 text-sm">
|
|||
|
|
已归档{' '}
|
|||
|
|
<Badge variant="secondary" className="ml-1 text-xs">
|
|||
|
|
{stats.archived}
|
|||
|
|
</Badge>
|
|||
|
|
</TabsTrigger>
|
|||
|
|
<TabsTrigger value="trash" className="px-3 text-sm">
|
|||
|
|
已删除{' '}
|
|||
|
|
<Badge variant="secondary" className="ml-1 text-xs">
|
|||
|
|
{stats.deleted}
|
|||
|
|
</Badge>
|
|||
|
|
</TabsTrigger>
|
|||
|
|
</TabsList>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 中间:批量操作 */}
|
|||
|
|
<div className="flex items-center justify-center">
|
|||
|
|
{selectedArticles.length > 0 && (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<Badge variant="secondary" className="text-sm font-medium">
|
|||
|
|
{selectedArticles.length} 篇已选
|
|||
|
|
</Badge>
|
|||
|
|
<Select value={batchAction} onValueChange={setBatchAction}>
|
|||
|
|
<SelectTrigger className="w-32 h-9">
|
|||
|
|
<SelectValue placeholder="批量操作" />
|
|||
|
|
</SelectTrigger>
|
|||
|
|
<SelectContent>
|
|||
|
|
{BATCH_ACTIONS.map((action) => (
|
|||
|
|
<SelectItem key={action.value} value={action.value}>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<action.icon className="h-3 w-3" />
|
|||
|
|
{action.label}
|
|||
|
|
</div>
|
|||
|
|
</SelectItem>
|
|||
|
|
))}
|
|||
|
|
</SelectContent>
|
|||
|
|
</Select>
|
|||
|
|
<Button size="sm" onClick={handleBatchAction} disabled={!batchAction} className="h-9">
|
|||
|
|
执行
|
|||
|
|
</Button>
|
|||
|
|
<Button size="sm" variant="outline" onClick={resetSelection} className="h-9">
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 右侧:搜索、分类和排序 */}
|
|||
|
|
<div className="flex items-center gap-3">
|
|||
|
|
{/* 搜索和分类 */}
|
|||
|
|
<div className="flex items-center gap-3">
|
|||
|
|
<div className="relative w-64">
|
|||
|
|
<IconSearch className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|||
|
|
<Input
|
|||
|
|
placeholder="搜索文章..."
|
|||
|
|
value={filters.searchTerm}
|
|||
|
|
onChange={(e) => updateFilters({ searchTerm: e.target.value })}
|
|||
|
|
className="pl-10 h-9"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<TermSelect
|
|||
|
|
taxonomySlug="category"
|
|||
|
|
value={filters.categoryFilter}
|
|||
|
|
onValueChange={(value) => updateFilters({ categoryFilter: value as string })}
|
|||
|
|
placeholder="全部分类"
|
|||
|
|
className="h-9 w-32"
|
|||
|
|
allowClear
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 排序 */}
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<Label htmlFor="sort-select" className="text-sm font-medium text-muted-foreground whitespace-nowrap">
|
|||
|
|
排序:
|
|||
|
|
</Label>
|
|||
|
|
<Select value={filters.sortBy} onValueChange={(value) => updateFilters({ sortBy: value })}>
|
|||
|
|
<SelectTrigger id="sort-select" className="w-48 h-9">
|
|||
|
|
<SelectValue />
|
|||
|
|
</SelectTrigger>
|
|||
|
|
<SelectContent>
|
|||
|
|
{SORT_OPTIONS.map((option) => (
|
|||
|
|
<SelectItem key={option.value} value={option.value}>
|
|||
|
|
{option.label}
|
|||
|
|
</SelectItem>
|
|||
|
|
))}
|
|||
|
|
</SelectContent>
|
|||
|
|
</Select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|