@xyhp915/slack-base-ui 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/libs/Combobox.d.ts +77 -0
- package/libs/Combobox.d.ts.map +1 -0
- package/libs/Dropdown.d.ts +177 -0
- package/libs/Dropdown.d.ts.map +1 -0
- package/libs/index.d.ts +4 -0
- package/libs/index.d.ts.map +1 -1
- package/libs/index.js +9363 -7095
- package/package.json +1 -1
- package/src/components/Combobox.tsx +441 -0
- package/src/components/Dropdown.tsx +520 -0
- package/src/components/index.ts +38 -0
- package/src/main.tsx +4 -1
- package/src/pages/ComponentShowcase.tsx +324 -0
|
@@ -97,6 +97,21 @@ import { useToast } from '../components/Toast'
|
|
|
97
97
|
import type { ToastPosition } from '../components/Toast'
|
|
98
98
|
import { Loading } from '../components/Loading'
|
|
99
99
|
import { AutoComplete } from '../components/AutoComplete'
|
|
100
|
+
import { Combobox, ComboboxMultiple } from '../components/Combobox'
|
|
101
|
+
import type { ComboboxOption } from '../components/Combobox'
|
|
102
|
+
import {
|
|
103
|
+
Dropdown,
|
|
104
|
+
DropdownTrigger,
|
|
105
|
+
DropdownContent,
|
|
106
|
+
DropdownItem,
|
|
107
|
+
DropdownSeparator,
|
|
108
|
+
DropdownGroup,
|
|
109
|
+
DropdownSub,
|
|
110
|
+
DropdownSubTrigger,
|
|
111
|
+
DropdownSubContent,
|
|
112
|
+
useDropdown,
|
|
113
|
+
} from '../components/Dropdown'
|
|
114
|
+
import type { DropdownOption } from '../components/Dropdown'
|
|
100
115
|
|
|
101
116
|
export const ComponentShowcase = () => {
|
|
102
117
|
const [simpleDialogOpen, setSimpleDialogOpen] = useState(false)
|
|
@@ -134,6 +149,10 @@ export const ComponentShowcase = () => {
|
|
|
134
149
|
const [acValue, setAcValue] = useState('')
|
|
135
150
|
const [acAsync, setAcAsync] = useState('')
|
|
136
151
|
|
|
152
|
+
// Combobox states
|
|
153
|
+
const [cbValue, setCbValue] = useState<ComboboxOption | null>(null)
|
|
154
|
+
const [cbMultiValue, setCbMultiValue] = useState<ComboboxOption[]>([])
|
|
155
|
+
|
|
137
156
|
// Imperative Dialog
|
|
138
157
|
const { show: showDialog, confirm: confirmDialog, alert: alertDialog } = useDialog()
|
|
139
158
|
const [dialogResult, setDialogResult] = useState<string | null>(null)
|
|
@@ -141,6 +160,10 @@ export const ComponentShowcase = () => {
|
|
|
141
160
|
// Imperative Popover
|
|
142
161
|
const imperativePopover = useImperativePopover()
|
|
143
162
|
|
|
163
|
+
// Dropdown
|
|
164
|
+
const dropdown = useDropdown()
|
|
165
|
+
const [dropdownResult, setDropdownResult] = useState<string | null>(null)
|
|
166
|
+
|
|
144
167
|
return (
|
|
145
168
|
<div className="min-h-screen bg-(--bg-muted) pb-20 font-sans transition-colors duration-300">
|
|
146
169
|
{/* Header */}
|
|
@@ -1866,6 +1889,307 @@ export const ComponentShowcase = () => {
|
|
|
1866
1889
|
</div>
|
|
1867
1890
|
</section>
|
|
1868
1891
|
|
|
1892
|
+
{/* Section: Combobox */}
|
|
1893
|
+
<section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
|
|
1894
|
+
<div className="pb-2 border-b border-(--border-light)">
|
|
1895
|
+
<h2 className="text-2xl font-bold text-(--text-primary)">Combobox</h2>
|
|
1896
|
+
<p className="text-(--text-secondary) mt-1">基于 Base UI Combobox 构建的组合输入框,支持过滤、分组、多选、Chip 等功能。</p>
|
|
1897
|
+
</div>
|
|
1898
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
|
1899
|
+
{/* Basic single */}
|
|
1900
|
+
<div className="space-y-4">
|
|
1901
|
+
<h3 className="font-semibold text-(--text-secondary)">基础单选</h3>
|
|
1902
|
+
<Combobox
|
|
1903
|
+
label="选择水果"
|
|
1904
|
+
placeholder="搜索水果…"
|
|
1905
|
+
options={[
|
|
1906
|
+
{ value: 'apple', label: 'Apple' },
|
|
1907
|
+
{ value: 'banana', label: 'Banana' },
|
|
1908
|
+
{ value: 'cherry', label: 'Cherry' },
|
|
1909
|
+
{ value: 'grape', label: 'Grape' },
|
|
1910
|
+
{ value: 'mango', label: 'Mango' },
|
|
1911
|
+
{ value: 'orange', label: 'Orange' },
|
|
1912
|
+
{ value: 'peach', label: 'Peach' },
|
|
1913
|
+
{ value: 'pear', label: 'Pear' },
|
|
1914
|
+
{ value: 'strawberry', label: 'Strawberry' },
|
|
1915
|
+
{ value: 'watermelon', label: 'Watermelon' },
|
|
1916
|
+
]}
|
|
1917
|
+
value={cbValue}
|
|
1918
|
+
onValueChange={setCbValue}
|
|
1919
|
+
fullWidth
|
|
1920
|
+
/>
|
|
1921
|
+
{cbValue && (
|
|
1922
|
+
<p className="text-[13px] text-(--text-secondary)">
|
|
1923
|
+
已选:<span className="font-semibold text-(--text-primary)">{cbValue.label}</span>
|
|
1924
|
+
</p>
|
|
1925
|
+
)}
|
|
1926
|
+
</div>
|
|
1927
|
+
|
|
1928
|
+
{/* Grouped */}
|
|
1929
|
+
<div className="space-y-4">
|
|
1930
|
+
<h3 className="font-semibold text-(--text-secondary)">分组选项</h3>
|
|
1931
|
+
<Combobox
|
|
1932
|
+
label="选择食物"
|
|
1933
|
+
placeholder="搜索…"
|
|
1934
|
+
groups={[
|
|
1935
|
+
{
|
|
1936
|
+
label: '水果',
|
|
1937
|
+
options: [
|
|
1938
|
+
{ value: 'apple', label: 'Apple' },
|
|
1939
|
+
{ value: 'banana', label: 'Banana' },
|
|
1940
|
+
{ value: 'cherry', label: 'Cherry' },
|
|
1941
|
+
],
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
label: '蔬菜',
|
|
1945
|
+
options: [
|
|
1946
|
+
{ value: 'carrot', label: 'Carrot' },
|
|
1947
|
+
{ value: 'lettuce', label: 'Lettuce' },
|
|
1948
|
+
{ value: 'spinach', label: 'Spinach' },
|
|
1949
|
+
],
|
|
1950
|
+
},
|
|
1951
|
+
]}
|
|
1952
|
+
fullWidth
|
|
1953
|
+
/>
|
|
1954
|
+
</div>
|
|
1955
|
+
|
|
1956
|
+
{/* Error state */}
|
|
1957
|
+
<div className="space-y-4">
|
|
1958
|
+
<h3 className="font-semibold text-(--text-secondary)">Required & Error</h3>
|
|
1959
|
+
<Combobox
|
|
1960
|
+
label="所在城市"
|
|
1961
|
+
placeholder="请选择城市…"
|
|
1962
|
+
required
|
|
1963
|
+
error="请选择一个城市"
|
|
1964
|
+
options={[
|
|
1965
|
+
{ value: 'beijing', label: '北京' },
|
|
1966
|
+
{ value: 'shanghai', label: '上海' },
|
|
1967
|
+
{ value: 'guangzhou', label: '广州' },
|
|
1968
|
+
{ value: 'shenzhen', label: '深圳' },
|
|
1969
|
+
{ value: 'hangzhou', label: '杭州' },
|
|
1970
|
+
{ value: 'chengdu', label: '成都' },
|
|
1971
|
+
]}
|
|
1972
|
+
fullWidth
|
|
1973
|
+
/>
|
|
1974
|
+
</div>
|
|
1975
|
+
|
|
1976
|
+
{/* Disabled */}
|
|
1977
|
+
<div className="space-y-4">
|
|
1978
|
+
<h3 className="font-semibold text-(--text-secondary)">Disabled</h3>
|
|
1979
|
+
<Combobox
|
|
1980
|
+
label="项目"
|
|
1981
|
+
placeholder="已禁用"
|
|
1982
|
+
disabled
|
|
1983
|
+
options={[
|
|
1984
|
+
{ value: 'alpha', label: 'Project Alpha' },
|
|
1985
|
+
{ value: 'beta', label: 'Project Beta' },
|
|
1986
|
+
]}
|
|
1987
|
+
defaultValue={{ value: 'alpha', label: 'Project Alpha' }}
|
|
1988
|
+
fullWidth
|
|
1989
|
+
/>
|
|
1990
|
+
</div>
|
|
1991
|
+
|
|
1992
|
+
{/* Multiple select */}
|
|
1993
|
+
<div className="space-y-4 md:col-span-2">
|
|
1994
|
+
<h3 className="font-semibold text-(--text-secondary)">多选(带 Chip)</h3>
|
|
1995
|
+
<ComboboxMultiple
|
|
1996
|
+
label="选择编程语言"
|
|
1997
|
+
placeholder="搜索语言…"
|
|
1998
|
+
options={[
|
|
1999
|
+
{ value: 'ts', label: 'TypeScript' },
|
|
2000
|
+
{ value: 'js', label: 'JavaScript' },
|
|
2001
|
+
{ value: 'py', label: 'Python' },
|
|
2002
|
+
{ value: 'rs', label: 'Rust' },
|
|
2003
|
+
{ value: 'go', label: 'Go' },
|
|
2004
|
+
{ value: 'java', label: 'Java' },
|
|
2005
|
+
{ value: 'cpp', label: 'C++' },
|
|
2006
|
+
{ value: 'swift', label: 'Swift' },
|
|
2007
|
+
{ value: 'kotlin', label: 'Kotlin' },
|
|
2008
|
+
{ value: 'ruby', label: 'Ruby' },
|
|
2009
|
+
]}
|
|
2010
|
+
value={cbMultiValue}
|
|
2011
|
+
onValueChange={setCbMultiValue}
|
|
2012
|
+
fullWidth
|
|
2013
|
+
/>
|
|
2014
|
+
{cbMultiValue.length > 0 && (
|
|
2015
|
+
<p className="text-[13px] text-(--text-secondary)">
|
|
2016
|
+
已选:<span className="font-semibold text-(--text-primary)">{cbMultiValue.map(v => v.label).join(', ')}</span>
|
|
2017
|
+
</p>
|
|
2018
|
+
)}
|
|
2019
|
+
</div>
|
|
2020
|
+
</div>
|
|
2021
|
+
</section>
|
|
2022
|
+
|
|
2023
|
+
{/* Section: Dropdown */}
|
|
2024
|
+
<section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
|
|
2025
|
+
<div className="pb-2 border-b border-(--border-light)">
|
|
2026
|
+
<h2 className="text-2xl font-bold text-(--text-primary)">Dropdown</h2>
|
|
2027
|
+
<p className="text-(--text-secondary) mt-1">
|
|
2028
|
+
选值导向的下拉菜单,支持图标、快捷键、分隔线、子菜单及危险项。提供声明式复合组件和{' '}
|
|
2029
|
+
<code className="text-sm bg-(--bg-secondary) px-1.5 py-0.5 rounded border border-(--border-light) font-mono">useDropdown()</code>{' '}
|
|
2030
|
+
命令式锚定 API。
|
|
2031
|
+
</p>
|
|
2032
|
+
</div>
|
|
2033
|
+
|
|
2034
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
|
2035
|
+
{/* 声明式 - 基础用法 */}
|
|
2036
|
+
<div className="space-y-4">
|
|
2037
|
+
<h3 className="font-semibold text-(--text-secondary)">声明式 Declarative</h3>
|
|
2038
|
+
<div className="flex flex-wrap gap-3">
|
|
2039
|
+
{/* 基础 */}
|
|
2040
|
+
<Dropdown onSelect={(v) => setDropdownResult(v)}>
|
|
2041
|
+
<DropdownTrigger>
|
|
2042
|
+
<Button size="sm" variant="secondary">基础菜单</Button>
|
|
2043
|
+
</DropdownTrigger>
|
|
2044
|
+
<DropdownContent>
|
|
2045
|
+
<DropdownItem value="copy" icon={<Copy size={14} />} shortcut="⌘C">复制</DropdownItem>
|
|
2046
|
+
<DropdownItem value="paste" icon={<ClipboardPaste size={14} />} shortcut="⌘V">粘贴</DropdownItem>
|
|
2047
|
+
<DropdownItem value="cut" icon={<Scissors size={14} />} shortcut="⌘X">剪切</DropdownItem>
|
|
2048
|
+
<DropdownSeparator />
|
|
2049
|
+
<DropdownItem value="delete" icon={<Trash2 size={14} />} destructive shortcut="⌘⌫">删除</DropdownItem>
|
|
2050
|
+
</DropdownContent>
|
|
2051
|
+
</Dropdown>
|
|
2052
|
+
|
|
2053
|
+
{/* 带分组 */}
|
|
2054
|
+
<Dropdown onSelect={(v) => setDropdownResult(v)}>
|
|
2055
|
+
<DropdownTrigger>
|
|
2056
|
+
<Button size="sm" variant="secondary">分组菜单</Button>
|
|
2057
|
+
</DropdownTrigger>
|
|
2058
|
+
<DropdownContent>
|
|
2059
|
+
<DropdownGroup label="文件">
|
|
2060
|
+
<DropdownItem value="new" icon={<FileText size={14} />}>新建</DropdownItem>
|
|
2061
|
+
<DropdownItem value="open" icon={<FolderOpen size={14} />}>打开</DropdownItem>
|
|
2062
|
+
<DropdownItem value="save" icon={<Save size={14} />} shortcut="⌘S">保存</DropdownItem>
|
|
2063
|
+
</DropdownGroup>
|
|
2064
|
+
<DropdownSeparator />
|
|
2065
|
+
<DropdownGroup label="导出">
|
|
2066
|
+
<DropdownItem value="export-pdf" icon={<Download size={14} />}>导出 PDF</DropdownItem>
|
|
2067
|
+
<DropdownItem value="share" icon={<Share2 size={14} />}>分享链接</DropdownItem>
|
|
2068
|
+
</DropdownGroup>
|
|
2069
|
+
</DropdownContent>
|
|
2070
|
+
</Dropdown>
|
|
2071
|
+
|
|
2072
|
+
{/* 子菜单 */}
|
|
2073
|
+
<Dropdown onSelect={(v) => setDropdownResult(v)}>
|
|
2074
|
+
<DropdownTrigger>
|
|
2075
|
+
<Button size="sm" variant="secondary">子菜单</Button>
|
|
2076
|
+
</DropdownTrigger>
|
|
2077
|
+
<DropdownContent>
|
|
2078
|
+
<DropdownItem value="refresh" icon={<RefreshCw size={14} />}>刷新</DropdownItem>
|
|
2079
|
+
<DropdownSeparator />
|
|
2080
|
+
<DropdownSub>
|
|
2081
|
+
<DropdownSubTrigger>
|
|
2082
|
+
<Star size={14} className="text-(--text-muted)" />
|
|
2083
|
+
<span>标记为</span>
|
|
2084
|
+
</DropdownSubTrigger>
|
|
2085
|
+
<DropdownSubContent>
|
|
2086
|
+
<DropdownItem value="star" icon={<Star size={14} />}>加星标</DropdownItem>
|
|
2087
|
+
<DropdownItem value="unstar" icon={<StarOff size={14} />}>取消星标</DropdownItem>
|
|
2088
|
+
<DropdownItem value="pin">置顶</DropdownItem>
|
|
2089
|
+
</DropdownSubContent>
|
|
2090
|
+
</DropdownSub>
|
|
2091
|
+
<DropdownSeparator />
|
|
2092
|
+
<DropdownItem value="delete" destructive icon={<Trash2 size={14} />}>删除</DropdownItem>
|
|
2093
|
+
</DropdownContent>
|
|
2094
|
+
</Dropdown>
|
|
2095
|
+
</div>
|
|
2096
|
+
{dropdownResult && (
|
|
2097
|
+
<p className="text-sm text-(--text-muted)">
|
|
2098
|
+
选中:<strong className="text-(--text-primary)">{dropdownResult}</strong>
|
|
2099
|
+
</p>
|
|
2100
|
+
)}
|
|
2101
|
+
</div>
|
|
2102
|
+
|
|
2103
|
+
{/* 命令式 */}
|
|
2104
|
+
<div className="space-y-4">
|
|
2105
|
+
<h3 className="font-semibold text-(--text-secondary)">命令式 Imperative</h3>
|
|
2106
|
+
<div className="flex flex-wrap gap-3">
|
|
2107
|
+
{/* show */}
|
|
2108
|
+
<Button
|
|
2109
|
+
size="sm"
|
|
2110
|
+
variant="secondary"
|
|
2111
|
+
onClick={(e) => {
|
|
2112
|
+
const opts: DropdownOption[] = [
|
|
2113
|
+
{ value: 'edit', label: '编辑', icon: <Pencil size={14} /> },
|
|
2114
|
+
{ value: 'duplicate', label: '复制副本', icon: <Copy size={14} /> },
|
|
2115
|
+
{ value: 'rename', label: '重命名', icon: <Pencil size={14} /> },
|
|
2116
|
+
{ value: 'delete', label: '删除', icon: <Trash2 size={14} />, destructive: true },
|
|
2117
|
+
]
|
|
2118
|
+
dropdown.show(e.currentTarget, {
|
|
2119
|
+
options: opts,
|
|
2120
|
+
onSelect: (v) => setDropdownResult(v),
|
|
2121
|
+
align: 'start',
|
|
2122
|
+
})
|
|
2123
|
+
}}
|
|
2124
|
+
>
|
|
2125
|
+
show()
|
|
2126
|
+
</Button>
|
|
2127
|
+
|
|
2128
|
+
{/* toggle */}
|
|
2129
|
+
<Button
|
|
2130
|
+
size="sm"
|
|
2131
|
+
variant={dropdown.isOpen ? 'primary' : 'secondary'}
|
|
2132
|
+
onClick={(e) => {
|
|
2133
|
+
dropdown.toggle(e.currentTarget, {
|
|
2134
|
+
options: [
|
|
2135
|
+
{ value: 'profile', label: '个人资料', icon: <UserCircle size={14} /> },
|
|
2136
|
+
{ value: 'settings', label: '设置', icon: <Settings size={14} />, shortcut: '⌘,' },
|
|
2137
|
+
{ value: 'shortcuts', label: '快捷键', icon: <Command size={14} />, shortcut: '⌘/' },
|
|
2138
|
+
],
|
|
2139
|
+
onSelect: (v) => setDropdownResult(v),
|
|
2140
|
+
side: 'bottom',
|
|
2141
|
+
align: 'start',
|
|
2142
|
+
})
|
|
2143
|
+
}}
|
|
2144
|
+
>
|
|
2145
|
+
toggle()
|
|
2146
|
+
</Button>
|
|
2147
|
+
|
|
2148
|
+
{/* 带分组的命令式 */}
|
|
2149
|
+
<Button
|
|
2150
|
+
size="sm"
|
|
2151
|
+
variant="secondary"
|
|
2152
|
+
onClick={(e) => {
|
|
2153
|
+
dropdown.show(e.currentTarget, {
|
|
2154
|
+
groups: [
|
|
2155
|
+
{
|
|
2156
|
+
label: '操作',
|
|
2157
|
+
options: [
|
|
2158
|
+
{ value: 'open', label: '打开', icon: <ExternalLink size={14} /> },
|
|
2159
|
+
{ value: 'download', label: '下载', icon: <Download size={14} /> },
|
|
2160
|
+
],
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
label: '危险区',
|
|
2164
|
+
options: [
|
|
2165
|
+
{ value: 'delete', label: '删除', icon: <Trash2 size={14} />, destructive: true },
|
|
2166
|
+
],
|
|
2167
|
+
},
|
|
2168
|
+
],
|
|
2169
|
+
onSelect: (v) => setDropdownResult(v),
|
|
2170
|
+
align: 'start',
|
|
2171
|
+
})
|
|
2172
|
+
}}
|
|
2173
|
+
>
|
|
2174
|
+
分组 groups
|
|
2175
|
+
</Button>
|
|
2176
|
+
</div>
|
|
2177
|
+
{dropdownResult && (
|
|
2178
|
+
<p className="text-sm text-(--text-muted)">
|
|
2179
|
+
选中:<strong className="text-(--text-primary)">{dropdownResult}</strong>
|
|
2180
|
+
</p>
|
|
2181
|
+
)}
|
|
2182
|
+
<div className="rounded-md bg-(--bg-secondary) border border-(--border-light) p-3 text-[13px] text-(--text-secondary) font-mono leading-relaxed space-y-1">
|
|
2183
|
+
<div><span className="text-(--text-muted)">// 任意组件内</span></div>
|
|
2184
|
+
<div><span className="text-blue-500 dark:text-blue-400">const</span> dropdown = <span className="text-green-600 dark:text-green-400">useDropdown</span>()</div>
|
|
2185
|
+
<div className="pt-1">dropdown.<span className="text-green-600 dark:text-green-400">show</span>(anchor, {'{'} options, onSelect {'}'})</div>
|
|
2186
|
+
<div>dropdown.<span className="text-green-600 dark:text-green-400">toggle</span>(anchor, {'{'} options, onSelect {'}'})</div>
|
|
2187
|
+
<div>dropdown.<span className="text-green-600 dark:text-green-400">hide</span>()</div>
|
|
2188
|
+
</div>
|
|
2189
|
+
</div>
|
|
2190
|
+
</div>
|
|
2191
|
+
</section>
|
|
2192
|
+
|
|
1869
2193
|
{/* Section: Toast */}
|
|
1870
2194
|
<section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
|
|
1871
2195
|
<div className="pb-2 border-b border-(--border-light)">
|