diff --git a/assets/creator-studio/scripts/preview.cjs b/assets/creator-studio/scripts/preview.cjs index 41e36103..97a334df 100644 --- a/assets/creator-studio/scripts/preview.cjs +++ b/assets/creator-studio/scripts/preview.cjs @@ -32,7 +32,7 @@ const mockBootstrap = { ], selected: [1], }, - tags: { items: ['写作', '产品'], enabled: true }, + tags: { items: ['写作', '产品'], enabled: true, suggestions: ['写作', '产品', '前端', 'WordPress', '设计'] }, custom: {}, customDefs: [], featured: { type: '', data: {}, canImage: true, canSlide: false, canVideo: false }, diff --git a/assets/creator-studio/src/components/category-select.tsx b/assets/creator-studio/src/components/category-select.tsx new file mode 100644 index 00000000..47690b5c --- /dev/null +++ b/assets/creator-studio/src/components/category-select.tsx @@ -0,0 +1,159 @@ +/** + * 分类多选下拉。 + * + * 触发器本身就是「已选令牌 + 占位文字」的输入框外观,点开后是带搜索的复选列表: + * 作者不用在一屏几十个分类里横向找,输入关键词就能定位。面板 portal 到 body, + * 避免被发布设置卡片或页面容器裁剪。 + * + * @package Jio + */ + +import { createPortal, useEffect, useMemo, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +import { useAnchoredPanel } from '../hooks/use-anchored-panel'; + +export type CategoryOption = { id: number; name: string }; + +type Props = { + options: CategoryOption[]; + value: number[]; + onChange: (ids: number[]) => void; +}; + +export function CategorySelect({ options, value, onChange }: Props) { + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(''); + const { triggerRef, panelRef, position } = useAnchoredPanel({ + open, + width: 320, + align: 'start', + followScroll: true, + onDismiss: () => setOpen(false), + }); + + const selected = useMemo(() => new Set(value), [value]); + const chosen = useMemo( + () => options.filter((option) => selected.has(option.id)), + [options, selected] + ); + const filtered = useMemo(() => { + const keyword = query.trim().toLowerCase(); + if (keyword === '') { + return options; + } + return options.filter((option) => option.name.toLowerCase().includes(keyword)); + }, [options, query]); + + // 每次打开都从干净的搜索词开始,避免上次的过滤条件把列表藏起来。 + useEffect(() => { + if (!open) { + setQuery(''); + } + }, [open]); + + const toggle = (id: number) => { + onChange( + selected.has(id) ? value.filter((item) => item !== id) : [...value, id] + ); + }; + + const panel = open + ? createPortal( +
+
+