11import { Combobox as ComboboxBase } from "@base-ui/react/combobox" ;
22import { CaretDownIcon , CheckIcon , XIcon } from "@phosphor-icons/react" ;
3- import { Fragment , type PropsWithChildren , type ReactNode } from "react" ;
4- import { inputVariants } from "../input/input" ;
3+ import {
4+ Fragment ,
5+ createContext ,
6+ useContext ,
7+ type PropsWithChildren ,
8+ type ReactNode ,
9+ } from "react" ;
10+ import {
11+ inputVariants ,
12+ KUMO_INPUT_VARIANTS ,
13+ type KumoInputSize ,
14+ } from "../input/input" ;
515import { cn } from "../../utils/cn" ;
616import { Field , type FieldErrorMatch } from "../field/field" ;
717
8- /** Combobox input position variant definitions. */
18+ /** Combobox variant definitions. */
919export const KUMO_COMBOBOX_VARIANTS = {
20+ size : KUMO_INPUT_VARIANTS . size ,
1021 inputSide : {
1122 right : {
1223 classes : "" ,
@@ -20,14 +31,28 @@ export const KUMO_COMBOBOX_VARIANTS = {
2031} as const ;
2132
2233export const KUMO_COMBOBOX_DEFAULT_VARIANTS = {
34+ size : "base" ,
2335 inputSide : "right" ,
2436} as const ;
2537
38+ // Context to pass size down to sub-components
39+ const ComboboxSizeContext = createContext < KumoInputSize > ( "base" ) ;
40+
2641// Derived types from KUMO_COMBOBOX_VARIANTS
42+ export type KumoComboboxSize = keyof typeof KUMO_COMBOBOX_VARIANTS . size ;
2743export type KumoComboboxInputSide =
2844 keyof typeof KUMO_COMBOBOX_VARIANTS . inputSide ;
2945
3046export interface KumoComboboxVariantsProps {
47+ /**
48+ * Size of the combobox trigger. Matches Input component sizes.
49+ * - `"xs"` — Extra small for compact UIs (h-5 / 20px)
50+ * - `"sm"` — Small for secondary fields (h-6.5 / 26px)
51+ * - `"base"` — Default size (h-9 / 36px)
52+ * - `"lg"` — Large for prominent fields (h-10 / 40px)
53+ * @default "base"
54+ */
55+ size ?: KumoComboboxSize ;
3156 /**
3257 * Position of the text input relative to chips in multi-select mode.
3358 * - `"right"` — Input inline to the right of chips
@@ -45,6 +70,7 @@ export function comboboxVariants({
4570
4671// Legacy type alias for backwards compatibility
4772export type ComboboxInputSide = KumoComboboxInputSide ;
73+ export type ComboboxSize = KumoComboboxSize ;
4874
4975export type ComboboxRootProps <
5076 Value = unknown ,
@@ -116,16 +142,20 @@ function Root<Value, Multiple extends boolean | undefined = false>({
116142 description,
117143 error,
118144 children,
145+ size = "base" ,
119146 ...props
120147} : ComboboxBase . Root . Props < Value , Multiple > & {
121148 label ?: ReactNode ;
122149 required ?: boolean ;
123150 labelTooltip ?: ReactNode ;
124151 description ?: ReactNode ;
125152 error ?: string | { message : ReactNode ; match : FieldErrorMatch } ;
153+ size ?: KumoComboboxSize ;
126154} ) {
127155 const comboboxControl = (
128- < ComboboxBase . Root { ...props } > { children } </ ComboboxBase . Root >
156+ < ComboboxSizeContext . Provider value = { size } >
157+ < ComboboxBase . Root { ...props } > { children } </ ComboboxBase . Root >
158+ </ ComboboxSizeContext . Provider >
129159 ) ;
130160
131161 // Render with Field wrapper if label, description, or error are provided
@@ -192,43 +222,110 @@ function Content({
192222 ) ;
193223}
194224
225+ // Size-dependent styles for TriggerValue icon
226+ const triggerValueIconStyles : Record <
227+ KumoComboboxSize ,
228+ { padding : string ; iconSize : number ; iconRight : string }
229+ > = {
230+ xs : { padding : "pr-5" , iconSize : 12 , iconRight : "right-1" } ,
231+ sm : { padding : "pr-6" , iconSize : 14 , iconRight : "right-1.5" } ,
232+ base : { padding : "pr-8" , iconSize : 16 , iconRight : "right-2" } ,
233+ lg : { padding : "pr-10" , iconSize : 18 , iconRight : "right-3" } ,
234+ } ;
235+
195236function TriggerValue ( {
196237 className,
197238 ...props
198239} : ComboboxBase . Value . Props & { className ?: string } ) {
240+ const size = useContext ( ComboboxSizeContext ) ;
241+ const iconStyles = triggerValueIconStyles [ size ] ;
242+
199243 return (
200244 < ComboboxBase . Trigger
201245 className = { cn (
202- inputVariants ( ) ,
203- "relative flex items-center pr-8" ,
246+ inputVariants ( { size } ) ,
247+ "relative flex items-center" ,
248+ iconStyles . padding ,
204249 className ,
205250 ) }
206251 >
207252 < ComboboxBase . Value > { props . children } </ ComboboxBase . Value >
208- < ComboboxBase . Icon className = "absolute top-1/2 right-2 -translate-y-1/2" >
209- < CaretDownIcon className = "fill-kumo-ring" />
253+ < ComboboxBase . Icon
254+ className = { cn (
255+ "absolute top-1/2 -translate-y-1/2" ,
256+ iconStyles . iconRight ,
257+ ) }
258+ >
259+ < CaretDownIcon size = { iconStyles . iconSize } className = "fill-kumo-ring" />
210260 </ ComboboxBase . Icon >
211261 </ ComboboxBase . Trigger >
212262 ) ;
213263}
214264
265+ // Size-dependent styles for TriggerInput icons
266+ const triggerInputIconStyles : Record <
267+ KumoComboboxSize ,
268+ { padding : string ; iconSize : number ; clearRight : string ; caretRight : string }
269+ > = {
270+ xs : {
271+ padding : "pr-7" ,
272+ iconSize : 12 ,
273+ clearRight : "right-5" ,
274+ caretRight : "right-1" ,
275+ } ,
276+ sm : {
277+ padding : "pr-9" ,
278+ iconSize : 14 ,
279+ clearRight : "right-6" ,
280+ caretRight : "right-1.5" ,
281+ } ,
282+ base : {
283+ padding : "pr-12" ,
284+ iconSize : 16 ,
285+ clearRight : "right-8" ,
286+ caretRight : "right-2" ,
287+ } ,
288+ lg : {
289+ padding : "pr-14" ,
290+ iconSize : 18 ,
291+ clearRight : "right-9" ,
292+ caretRight : "right-3" ,
293+ } ,
294+ } ;
295+
215296function TriggerInput ( props : ComboboxBase . Input . Props ) {
297+ const size = useContext ( ComboboxSizeContext ) ;
298+ const iconStyles = triggerInputIconStyles [ size ] ;
299+
216300 return (
217301 < div
218302 className = { cn ( "relative inline-block w-full max-w-xs" , props . className ) }
219303 >
220304 < ComboboxBase . Input
221305 { ...props }
222- className = { cn ( inputVariants ( ) , "w-full pr-12" ) }
306+ className = { cn ( inputVariants ( { size } ) , "w-full" , iconStyles . padding ) }
223307 />
224308
225- < ComboboxBase . Clear className = "absolute top-1/2 right-8 flex -translate-y-1/2 cursor-pointer bg-transparent p-0" >
226- < XIcon />
309+ < ComboboxBase . Clear
310+ className = { cn (
311+ "absolute top-1/2 flex -translate-y-1/2 cursor-pointer bg-transparent p-0" ,
312+ iconStyles . clearRight ,
313+ ) }
314+ >
315+ < XIcon size = { iconStyles . iconSize } />
227316 </ ComboboxBase . Clear >
228317
229318 < ComboboxBase . Trigger className = "p-0" >
230- < ComboboxBase . Icon className = "absolute top-1/2 right-2 flex -translate-y-1/2 cursor-pointer" >
231- < CaretDownIcon className = "fill-kumo-ring" />
319+ < ComboboxBase . Icon
320+ className = { cn (
321+ "absolute top-1/2 flex -translate-y-1/2 cursor-pointer" ,
322+ iconStyles . caretRight ,
323+ ) }
324+ >
325+ < CaretDownIcon
326+ size = { iconStyles . iconSize }
327+ className = "fill-kumo-ring"
328+ />
232329 </ ComboboxBase . Icon >
233330 </ ComboboxBase . Trigger >
234331 </ div >
@@ -324,6 +421,14 @@ function Chip(props: ComboboxBase.Chip.Props) {
324421 ) ;
325422}
326423
424+ // Map size to min-height class for TriggerMultipleWithInput
425+ const sizeToMinHeight : Record < KumoComboboxSize , string > = {
426+ xs : "min-h-5" ,
427+ sm : "min-h-6.5" ,
428+ base : "min-h-9" ,
429+ lg : "min-h-10" ,
430+ } ;
431+
327432function TriggerMultipleWithInput < ValueType > ( {
328433 placeholder,
329434 renderItem,
@@ -338,14 +443,15 @@ function TriggerMultipleWithInput<ValueType>({
338443 /** Optional controlled value for rendering chips (use when pre-selecting values) */
339444 value ?: ValueType [ ] ;
340445} ) {
446+ const size = useContext ( ComboboxSizeContext ) ;
341447 // Determine which value to use for rendering chips
342448 const chipsToRender = controlledValue ;
343449
344450 return (
345451 < ComboboxBase . Chips
346452 className = { cn (
347- inputVariants ( ) ,
348- cn ( "flex flex-col" , "gap-1 p-1" , "min-h-9" , "h-auto" ) ,
453+ inputVariants ( { size } ) ,
454+ cn ( "flex flex-col" , "gap-1 p-1" , sizeToMinHeight [ size ] , "h-auto" ) ,
349455 className ,
350456 ) }
351457 >
0 commit comments