@motor-hero/ui-kit 0.50.0 → 0.51.1
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/dist/components/combobox.d.ts +9 -0
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/components/multi-combobox.d.ts +1 -1
- package/dist/components/multi-combobox.d.ts.map +1 -1
- package/dist/index.cjs +40 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/combobox.tsx +44 -7
- package/src/components/multi-combobox.tsx +21 -4
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
|
-
import { Command as CommandPrimitive } from "cmdk";
|
|
2
|
+
import { Command as CommandPrimitive, defaultFilter } from "cmdk";
|
|
3
3
|
import { Check, ChevronsUpDown, Loader2, Plus, Search } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { Drawer as DrawerPrimitive } from "vaul";
|
|
@@ -14,6 +14,10 @@ export interface ComboboxOption {
|
|
|
14
14
|
/** Optional leading decoration rendered before the label (icon, colored
|
|
15
15
|
* dot) — purely visual, doesn't affect filtering/matching. */
|
|
16
16
|
icon?: React.ReactNode;
|
|
17
|
+
/** Linha secundária abaixo do rótulo na lista (ex.: e-mail de uma pessoa,
|
|
18
|
+
* para separar homônimos). No gatilho vira o `title`. Entra na busca do
|
|
19
|
+
* modo cliente. */
|
|
20
|
+
description?: string;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export interface ComboboxProps {
|
|
@@ -159,7 +163,10 @@ export function Combobox({
|
|
|
159
163
|
className,
|
|
160
164
|
)}
|
|
161
165
|
>
|
|
162
|
-
<span
|
|
166
|
+
<span
|
|
167
|
+
title={selected?.description}
|
|
168
|
+
className={cn("truncate", !selected && "text-muted-foreground")}
|
|
169
|
+
>
|
|
163
170
|
{selected ? selected.label : placeholder}
|
|
164
171
|
</span>
|
|
165
172
|
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
|
@@ -169,6 +176,7 @@ export function Combobox({
|
|
|
169
176
|
const command = (
|
|
170
177
|
<CommandPrimitive
|
|
171
178
|
shouldFilter={!isServer}
|
|
179
|
+
filter={filterByKeywords}
|
|
172
180
|
className="flex h-full w-full flex-col overflow-hidden"
|
|
173
181
|
>
|
|
174
182
|
<div className="flex items-center border-b px-3">
|
|
@@ -190,8 +198,10 @@ export function Combobox({
|
|
|
190
198
|
)}
|
|
191
199
|
{showCreate && (
|
|
192
200
|
<CommandPrimitive.Item
|
|
193
|
-
//
|
|
201
|
+
// A busca como `value`, não uma chave fixa: o cmdk só relê as
|
|
202
|
+
// keywords quando o `value` muda. Keyword igual à busca = sempre no topo.
|
|
194
203
|
value={trimmedSearch}
|
|
204
|
+
keywords={[trimmedSearch]}
|
|
195
205
|
onSelect={() => handleSelect(trimmedSearch)}
|
|
196
206
|
className="relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground sm:py-1.5"
|
|
197
207
|
>
|
|
@@ -206,9 +216,8 @@ export function Combobox({
|
|
|
206
216
|
{options.map((option) => (
|
|
207
217
|
<CommandPrimitive.Item
|
|
208
218
|
key={option.value}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
value={isServer ? option.value : option.label}
|
|
219
|
+
value={option.value}
|
|
220
|
+
keywords={optionKeywords(option)}
|
|
212
221
|
disabled={option.disabled}
|
|
213
222
|
onSelect={() => handleSelect(option.value)}
|
|
214
223
|
className="relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5"
|
|
@@ -220,7 +229,7 @@ export function Combobox({
|
|
|
220
229
|
)}
|
|
221
230
|
/>
|
|
222
231
|
{option.icon}
|
|
223
|
-
<
|
|
232
|
+
<OptionText option={option} />
|
|
224
233
|
</CommandPrimitive.Item>
|
|
225
234
|
))}
|
|
226
235
|
{isServer && !loading && options.length === 0 && (
|
|
@@ -274,3 +283,31 @@ export function Combobox({
|
|
|
274
283
|
</DrawerPrimitive.Root>
|
|
275
284
|
);
|
|
276
285
|
}
|
|
286
|
+
|
|
287
|
+
// O `value` do item é o id, único mesmo entre homônimos — rótulo igual como
|
|
288
|
+
// `value` fazia o cmdk tratar duas opções como uma (hover, teclado, seleção).
|
|
289
|
+
// A busca pontua só pelas keywords (rótulo + descrição), nunca pelo id.
|
|
290
|
+
export const filterByKeywords = (
|
|
291
|
+
_value: string,
|
|
292
|
+
search: string,
|
|
293
|
+
keywords: string[] = [],
|
|
294
|
+
) => defaultFilter(keywords.join(" "), search);
|
|
295
|
+
|
|
296
|
+
export const optionKeywords = (option: ComboboxOption) =>
|
|
297
|
+
option.description ? [option.label, option.description] : [option.label];
|
|
298
|
+
|
|
299
|
+
export function OptionText({ option }: { option: ComboboxOption }) {
|
|
300
|
+
if (!option.description)
|
|
301
|
+
return <span className="truncate">{option.label}</span>;
|
|
302
|
+
return (
|
|
303
|
+
<span className="flex min-w-0 flex-col">
|
|
304
|
+
<span className="truncate">{option.label}</span>
|
|
305
|
+
<span
|
|
306
|
+
title={option.description}
|
|
307
|
+
className="truncate text-xs text-muted-foreground"
|
|
308
|
+
>
|
|
309
|
+
{option.description}
|
|
310
|
+
</span>
|
|
311
|
+
</span>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
@@ -6,7 +6,12 @@ import { Drawer as DrawerPrimitive } from "vaul"
|
|
|
6
6
|
import { useInfiniteScrollSentinel } from "../hooks/use-infinite-scroll-sentinel"
|
|
7
7
|
import { useIsDesktop } from "../hooks/use-is-desktop"
|
|
8
8
|
import { cn } from "../lib/utils"
|
|
9
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
type ComboboxOption,
|
|
11
|
+
filterByKeywords,
|
|
12
|
+
OptionText,
|
|
13
|
+
optionKeywords,
|
|
14
|
+
} from "./combobox"
|
|
10
15
|
|
|
11
16
|
export interface MultiComboboxProps {
|
|
12
17
|
options: ComboboxOption[]
|
|
@@ -101,6 +106,15 @@ export function MultiCombobox({
|
|
|
101
106
|
.map((v) => byValue.get(v) ?? labelCache.current.get(v))
|
|
102
107
|
.filter((option): option is ComboboxOption => option !== undefined)
|
|
103
108
|
|
|
109
|
+
// Chips homônimos não se distinguem só pelo rótulo: esses ganham a descrição.
|
|
110
|
+
const labelCounts = new Map<string, number>()
|
|
111
|
+
for (const option of selected)
|
|
112
|
+
labelCounts.set(option.label, (labelCounts.get(option.label) ?? 0) + 1)
|
|
113
|
+
const chipLabel = (option: ComboboxOption) =>
|
|
114
|
+
option.description && (labelCounts.get(option.label) ?? 0) > 1
|
|
115
|
+
? `${option.label} · ${option.description}`
|
|
116
|
+
: option.label
|
|
117
|
+
|
|
104
118
|
const { listRef, sentinelRef } = useInfiniteScrollSentinel({
|
|
105
119
|
enabled: isServer && !!hasMore,
|
|
106
120
|
loading,
|
|
@@ -146,9 +160,10 @@ export function MultiCombobox({
|
|
|
146
160
|
selected.map((option) => (
|
|
147
161
|
<span
|
|
148
162
|
key={option.value}
|
|
163
|
+
title={option.description}
|
|
149
164
|
className="inline-flex items-center gap-1 rounded bg-secondary px-1.5 py-0.5 text-xs text-secondary-foreground"
|
|
150
165
|
>
|
|
151
|
-
{option
|
|
166
|
+
{chipLabel(option)}
|
|
152
167
|
<button
|
|
153
168
|
type="button"
|
|
154
169
|
tabIndex={-1}
|
|
@@ -172,6 +187,7 @@ export function MultiCombobox({
|
|
|
172
187
|
const command = (
|
|
173
188
|
<CommandPrimitive
|
|
174
189
|
shouldFilter={!isServer}
|
|
190
|
+
filter={filterByKeywords}
|
|
175
191
|
className="flex h-full w-full flex-col overflow-hidden"
|
|
176
192
|
>
|
|
177
193
|
<div className="flex items-center border-b px-3">
|
|
@@ -194,7 +210,8 @@ export function MultiCombobox({
|
|
|
194
210
|
{options.map((option) => (
|
|
195
211
|
<CommandPrimitive.Item
|
|
196
212
|
key={option.value}
|
|
197
|
-
value={
|
|
213
|
+
value={option.value}
|
|
214
|
+
keywords={optionKeywords(option)}
|
|
198
215
|
disabled={option.disabled}
|
|
199
216
|
onSelect={() => toggleValue(option.value)}
|
|
200
217
|
className="relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 sm:py-1.5"
|
|
@@ -205,7 +222,7 @@ export function MultiCombobox({
|
|
|
205
222
|
value.includes(option.value) ? "opacity-100" : "opacity-0",
|
|
206
223
|
)}
|
|
207
224
|
/>
|
|
208
|
-
<
|
|
225
|
+
<OptionText option={option} />
|
|
209
226
|
</CommandPrimitive.Item>
|
|
210
227
|
))}
|
|
211
228
|
{isServer && !loading && options.length === 0 && (
|