@xyhp915/slack-base-ui 0.0.6 → 0.0.8

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.
@@ -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)">