返回 oh-my-ppt
StyleCaseFilter.tsx
根目录 / src / renderer / src / components / style / StyleCaseFilter.tsx
1 import * as React from 'react'
2 import { useMemo } from 'react'
3 import { buildStyleCaseOptions, type StyleCaseItem } from '@renderer/lib/style-case'
4 import { cn } from '@renderer/lib/utils'
5
6 export type StyleCaseFilterProps = {
7 /** 任意带 styleCase 字段的条目数组(风格列表 / 下拉选项均适用) */
8 items: StyleCaseItem[]
9 /** 可选候选集:用于禁用当前搜索/收藏条件下不会产生结果的 chip,不影响展示计数 */
10 availableItems?: StyleCaseItem[]
11 /** 当前选中的用途标签,空串表示"全部" */
12 selected: string
13 onSelect: (label: string) => void
14 /** "全部" chip 的文案 */
15 allLabel: string
16 /** 可选标题(如"按适用场景筛选"),不传则不渲染 */
17 title?: string
18 /** 外层容器样式,由调用方决定是卡片还是窄条 */
19 className?: string
20 }
21
22 const chipClassName = (active: boolean): string =>
23 cn(
24 'rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-45',
25 active
26 ? 'border-[#97aa7c] bg-[#dbe7ca] text-[#2f3b28]'
27 : 'border-[#d6c08d]/80 bg-white/70 text-[#7c6a4c] hover:bg-[#fff3d8]'
28 )
29
30 /**
31 * 用途(styleCase)筛选 chip 栏。风格库页与风格下拉共用,保证两处分类一致。
32 * 规则与 styles.tsx 原实现一致:只展示命中数 > 1 的用途,并保证当前选中项始终可见。
33 */
34 export function StyleCaseFilter({
35 items,
36 availableItems,
37 selected,
38 onSelect,
39 allLabel,
40 title,
41 className
42 }: StyleCaseFilterProps): React.JSX.Element | null {
43 const options = useMemo(() => buildStyleCaseOptions(items), [items])
44 const availableLabels = useMemo(() => {
45 if (!availableItems) return null
46 return new Set(buildStyleCaseOptions(availableItems).map((option) => option.label))
47 }, [availableItems])
48 const visible = useMemo(() => {
49 const popular = options.filter((option) => option.count > 1)
50 const matched = options.find((option) => option.label === selected)
51 return matched && !popular.some((option) => option.label === matched.label)
52 ? [...popular, matched]
53 : popular
54 }, [options, selected])
55
56 if (options.length === 0) return null
57
58 return (
59 <div className={className}>
60 {title ? <p className="mb-2 text-xs font-medium text-[#3e4a32]">{title}</p> : null}
61 <div className="flex flex-wrap gap-1.5">
62 <button type="button" className={chipClassName(selected === '')} onClick={() => onSelect('')}>
63 {`${allLabel} · ${items.length}`}
64 </button>
65 {visible.map((option) => (
66 <button
67 key={option.label}
68 type="button"
69 className={chipClassName(selected === option.label)}
70 disabled={selected !== option.label && availableLabels ? !availableLabels.has(option.label) : false}
71 onClick={() => onSelect(option.label)}
72 >
73 {`${option.label} · ${option.count}`}
74 </button>
75 ))}
76 </div>
77 </div>
78 )
79 }
80
80 lines Plain Text