@motor-hero/ui-kit 0.24.0 → 0.25.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 +7 -1
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/index.cjs +40 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/combobox.tsx +57 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
2
2
|
import { Command as CommandPrimitive } from "cmdk"
|
|
3
|
-
import { Check, ChevronsUpDown, Loader2, Search } from "lucide-react"
|
|
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"
|
|
6
6
|
import { useInfiniteScrollSentinel } from "../hooks/use-infinite-scroll-sentinel"
|
|
@@ -40,6 +40,12 @@ export interface ComboboxProps {
|
|
|
40
40
|
/** Modo servidor: rótulo do valor já selecionado que pode não estar na
|
|
41
41
|
* página carregada (ex.: formulário de edição). */
|
|
42
42
|
selectedOption?: ComboboxOption
|
|
43
|
+
/** Permite confirmar o texto digitado como valor, mesmo fora da lista
|
|
44
|
+
* (ex.: unidade de medida com texto livre). Mostra uma linha "criar" no
|
|
45
|
+
* topo e exibe o valor personalizado no gatilho. */
|
|
46
|
+
creatable?: boolean
|
|
47
|
+
/** Rótulo da linha de criação. Padrão: `Usar "<texto>"`. */
|
|
48
|
+
formatCreateLabel?: (search: string) => string
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/**
|
|
@@ -73,14 +79,41 @@ export function Combobox({
|
|
|
73
79
|
loading,
|
|
74
80
|
hasMore,
|
|
75
81
|
selectedOption,
|
|
82
|
+
creatable,
|
|
83
|
+
formatCreateLabel,
|
|
76
84
|
}: ComboboxProps) {
|
|
77
85
|
const [open, setOpen] = React.useState(false)
|
|
86
|
+
const [search, setSearch] = React.useState("")
|
|
78
87
|
const isDesktop = useIsDesktop()
|
|
79
88
|
const isServer = onSearchChange !== undefined
|
|
80
89
|
const selected =
|
|
81
90
|
selectedOption?.value === value
|
|
82
91
|
? selectedOption
|
|
83
|
-
: options.find((option) => option.value === value)
|
|
92
|
+
: (options.find((option) => option.value === value) ??
|
|
93
|
+
(creatable && value ? { value, label: value } : undefined))
|
|
94
|
+
|
|
95
|
+
const handleOpenChange = (next: boolean) => {
|
|
96
|
+
setOpen(next)
|
|
97
|
+
if (!next) setSearch("")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleSelect = (next: string) => {
|
|
101
|
+
onChange(next)
|
|
102
|
+
handleOpenChange(false)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleSearchChange = (next: string) => {
|
|
106
|
+
setSearch(next)
|
|
107
|
+
onSearchChange?.(next)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const trimmedSearch = search.trim()
|
|
111
|
+
const showCreate =
|
|
112
|
+
!!creatable &&
|
|
113
|
+
trimmedSearch.length > 0 &&
|
|
114
|
+
!options.some(
|
|
115
|
+
(option) => option.value.toLowerCase() === trimmedSearch.toLowerCase(),
|
|
116
|
+
)
|
|
84
117
|
const { listRef, sentinelRef } = useInfiniteScrollSentinel({
|
|
85
118
|
enabled: isServer && !!hasMore,
|
|
86
119
|
loading,
|
|
@@ -117,7 +150,9 @@ export function Combobox({
|
|
|
117
150
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
118
151
|
<CommandPrimitive.Input
|
|
119
152
|
placeholder={searchPlaceholder}
|
|
120
|
-
onValueChange={
|
|
153
|
+
onValueChange={
|
|
154
|
+
isServer || creatable ? handleSearchChange : undefined
|
|
155
|
+
}
|
|
121
156
|
className="flex h-11 w-full bg-transparent py-2 text-base outline-none placeholder:text-muted-foreground sm:h-9 sm:text-sm"
|
|
122
157
|
/>
|
|
123
158
|
</div>
|
|
@@ -125,11 +160,26 @@ export function Combobox({
|
|
|
125
160
|
ref={listRef}
|
|
126
161
|
className="max-h-[50vh] overflow-y-auto overflow-x-hidden p-1 sm:max-h-60"
|
|
127
162
|
>
|
|
128
|
-
{!isServer && (
|
|
163
|
+
{!isServer && !showCreate && (
|
|
129
164
|
<CommandPrimitive.Empty className="py-6 text-center text-sm text-muted-foreground">
|
|
130
165
|
{emptyMessage}
|
|
131
166
|
</CommandPrimitive.Empty>
|
|
132
167
|
)}
|
|
168
|
+
{showCreate && (
|
|
169
|
+
<CommandPrimitive.Item
|
|
170
|
+
// cmdk pontua pelo `value`; igualar à busca mantém a linha sempre no topo.
|
|
171
|
+
value={trimmedSearch}
|
|
172
|
+
onSelect={() => handleSelect(trimmedSearch)}
|
|
173
|
+
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"
|
|
174
|
+
>
|
|
175
|
+
<Plus className="h-4 w-4 shrink-0" />
|
|
176
|
+
<span className="truncate">
|
|
177
|
+
{(formatCreateLabel ?? ((text) => `Usar "${text}"`))(
|
|
178
|
+
trimmedSearch,
|
|
179
|
+
)}
|
|
180
|
+
</span>
|
|
181
|
+
</CommandPrimitive.Item>
|
|
182
|
+
)}
|
|
133
183
|
{options.map((option) => (
|
|
134
184
|
<CommandPrimitive.Item
|
|
135
185
|
key={option.value}
|
|
@@ -137,10 +187,7 @@ export function Combobox({
|
|
|
137
187
|
// o servidor já filtrou, então usamos o id (único, evita colisão de labels).
|
|
138
188
|
value={isServer ? option.value : option.label}
|
|
139
189
|
disabled={option.disabled}
|
|
140
|
-
onSelect={() =>
|
|
141
|
-
onChange(option.value)
|
|
142
|
-
setOpen(false)
|
|
143
|
-
}}
|
|
190
|
+
onSelect={() => handleSelect(option.value)}
|
|
144
191
|
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"
|
|
145
192
|
>
|
|
146
193
|
<Check
|
|
@@ -171,7 +218,7 @@ export function Combobox({
|
|
|
171
218
|
|
|
172
219
|
if (isDesktop) {
|
|
173
220
|
return (
|
|
174
|
-
<PopoverPrimitive.Root open={open} onOpenChange={
|
|
221
|
+
<PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange}>
|
|
175
222
|
<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
|
|
176
223
|
<PopoverPrimitive.Portal>
|
|
177
224
|
<PopoverPrimitive.Content
|
|
@@ -188,7 +235,7 @@ export function Combobox({
|
|
|
188
235
|
}
|
|
189
236
|
|
|
190
237
|
return (
|
|
191
|
-
<DrawerPrimitive.Root open={open} onOpenChange={
|
|
238
|
+
<DrawerPrimitive.Root open={open} onOpenChange={handleOpenChange}>
|
|
192
239
|
<DrawerPrimitive.Trigger asChild>{trigger}</DrawerPrimitive.Trigger>
|
|
193
240
|
<DrawerPrimitive.Portal>
|
|
194
241
|
<DrawerPrimitive.Overlay className="fixed inset-0 z-50 bg-black/80" />
|