@motor-hero/ui-kit 0.51.0 → 0.52.0
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 +2 -0
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/components/multi-combobox.d.ts.map +1 -1
- package/dist/components/rating.d.ts.map +1 -1
- package/dist/index.cjs +47 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +166 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/combobox.tsx +23 -7
- package/src/components/multi-combobox.tsx +9 -3
- package/src/components/rating.tsx +30 -13
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";
|
|
@@ -163,7 +163,10 @@ export function Combobox({
|
|
|
163
163
|
className,
|
|
164
164
|
)}
|
|
165
165
|
>
|
|
166
|
-
<span
|
|
166
|
+
<span
|
|
167
|
+
title={selected?.description}
|
|
168
|
+
className={cn("truncate", !selected && "text-muted-foreground")}
|
|
169
|
+
>
|
|
167
170
|
{selected ? selected.label : placeholder}
|
|
168
171
|
</span>
|
|
169
172
|
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
|
@@ -173,6 +176,7 @@ export function Combobox({
|
|
|
173
176
|
const command = (
|
|
174
177
|
<CommandPrimitive
|
|
175
178
|
shouldFilter={!isServer}
|
|
179
|
+
filter={filterByKeywords}
|
|
176
180
|
className="flex h-full w-full flex-col overflow-hidden"
|
|
177
181
|
>
|
|
178
182
|
<div className="flex items-center border-b px-3">
|
|
@@ -194,8 +198,10 @@ export function Combobox({
|
|
|
194
198
|
)}
|
|
195
199
|
{showCreate && (
|
|
196
200
|
<CommandPrimitive.Item
|
|
197
|
-
//
|
|
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.
|
|
198
203
|
value={trimmedSearch}
|
|
204
|
+
keywords={[trimmedSearch]}
|
|
199
205
|
onSelect={() => handleSelect(trimmedSearch)}
|
|
200
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"
|
|
201
207
|
>
|
|
@@ -210,10 +216,8 @@ export function Combobox({
|
|
|
210
216
|
{options.map((option) => (
|
|
211
217
|
<CommandPrimitive.Item
|
|
212
218
|
key={option.value}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
value={isServer ? option.value : option.label}
|
|
216
|
-
keywords={option.description ? [option.description] : undefined}
|
|
219
|
+
value={option.value}
|
|
220
|
+
keywords={optionKeywords(option)}
|
|
217
221
|
disabled={option.disabled}
|
|
218
222
|
onSelect={() => handleSelect(option.value)}
|
|
219
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"
|
|
@@ -280,6 +284,18 @@ export function Combobox({
|
|
|
280
284
|
);
|
|
281
285
|
}
|
|
282
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
|
+
|
|
283
299
|
export function OptionText({ option }: { option: ComboboxOption }) {
|
|
284
300
|
if (!option.description)
|
|
285
301
|
return <span className="truncate">{option.label}</span>;
|
|
@@ -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[]
|
|
@@ -182,6 +187,7 @@ export function MultiCombobox({
|
|
|
182
187
|
const command = (
|
|
183
188
|
<CommandPrimitive
|
|
184
189
|
shouldFilter={!isServer}
|
|
190
|
+
filter={filterByKeywords}
|
|
185
191
|
className="flex h-full w-full flex-col overflow-hidden"
|
|
186
192
|
>
|
|
187
193
|
<div className="flex items-center border-b px-3">
|
|
@@ -204,8 +210,8 @@ export function MultiCombobox({
|
|
|
204
210
|
{options.map((option) => (
|
|
205
211
|
<CommandPrimitive.Item
|
|
206
212
|
key={option.value}
|
|
207
|
-
value={
|
|
208
|
-
keywords={option
|
|
213
|
+
value={option.value}
|
|
214
|
+
keywords={optionKeywords(option)}
|
|
209
215
|
disabled={option.disabled}
|
|
210
216
|
onSelect={() => toggleValue(option.value)}
|
|
211
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"
|
|
@@ -19,9 +19,12 @@ export interface RatingProps {
|
|
|
19
19
|
"aria-label"?: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const formatNote = (value: number) =>
|
|
23
|
+
value.toLocaleString("pt-BR", { maximumFractionDigits: 1 })
|
|
24
|
+
|
|
22
25
|
function defaultLabel(value: number) {
|
|
23
26
|
return value > 0
|
|
24
|
-
? `
|
|
27
|
+
? `Nota ${formatNote(value)} de ${MAX_STARS}`
|
|
25
28
|
: "Avaliação: sem nota"
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -43,18 +46,32 @@ export function Rating({
|
|
|
43
46
|
aria-label={label}
|
|
44
47
|
className={cn("inline-flex items-center gap-0.5", className)}
|
|
45
48
|
>
|
|
46
|
-
{Array.from({ length: MAX_STARS }, (_, index) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
className=
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
{Array.from({ length: MAX_STARS }, (_, index) => {
|
|
50
|
+
// Média 4,3 pinta 30% da quinta estrela: a fração é a nota, não um arredondamento.
|
|
51
|
+
const fill = Math.min(1, Math.max(0, clamped - index))
|
|
52
|
+
return (
|
|
53
|
+
<span key={index} className="relative inline-flex">
|
|
54
|
+
<Star
|
|
55
|
+
aria-hidden
|
|
56
|
+
className={cn(sizeClasses[size], "text-muted-foreground")}
|
|
57
|
+
/>
|
|
58
|
+
{fill > 0 && (
|
|
59
|
+
<span
|
|
60
|
+
className="absolute inset-y-0 left-0 overflow-hidden"
|
|
61
|
+
style={{ width: `${fill * 100}%` }}
|
|
62
|
+
>
|
|
63
|
+
<Star
|
|
64
|
+
aria-hidden
|
|
65
|
+
className={cn(
|
|
66
|
+
sizeClasses[size],
|
|
67
|
+
"max-w-none fill-amber-400 text-amber-400",
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</span>
|
|
73
|
+
)
|
|
74
|
+
})}
|
|
58
75
|
</div>
|
|
59
76
|
)
|
|
60
77
|
}
|