@@ -2,10 +2,11 @@ import * as stylex from '@stylexjs/stylex';
22import React from 'react' ;
33
44import type {
5+ SelectMultipleProps as PrimitiveSelectMultipleProps ,
56 SelectOptionProps as PrimitiveSelectOptionProps ,
67 SelectPopupProps as PrimitiveSelectPopupProps ,
78 SelectPortalProps ,
8- SelectProps as PrimitiveSelectProps ,
9+ SelectSingleProps as PrimitiveSelectSingleProps ,
910} from '../../primitives/select' ;
1011import { Select as Primitive } from '../../primitives/select' ;
1112import type { MosaicComponentProps , MosaicStyleProps } from '../../props' ;
@@ -32,7 +33,7 @@ export interface SelectItem {
3233 disabled ?: boolean ;
3334}
3435
35- export interface SelectProps extends Omit < PrimitiveSelectProps , 'items' > {
36+ interface SelectItemsProps {
3637 /**
3738 * Every choice. Required, unlike the primitive's: the options only mount while the listbox is
3839 * open, so this is what lets the closed trigger show the selected label. `Select.Popup`
@@ -41,6 +42,10 @@ export interface SelectProps extends Omit<PrimitiveSelectProps, 'items'> {
4142 items : SelectItem [ ] ;
4243}
4344
45+ export type SelectProps =
46+ | ( Omit < PrimitiveSelectSingleProps , 'items' > & SelectItemsProps )
47+ | ( Omit < PrimitiveSelectMultipleProps , 'items' > & SelectItemsProps ) ;
48+
4449const ItemsContext = React . createContext < SelectItem [ ] > ( [ ] ) ;
4550
4651/** Holds the selection and the items; renders no element of its own. */
@@ -51,7 +56,7 @@ export function SelectRoot({ items, alignItemWithTrigger, children, ...rest }: S
5156 < ItemsContext . Provider value = { items } >
5257 < Primitive . Root
5358 items = { items }
54- alignItemWithTrigger = { alignItemWithTrigger ?? ! described }
59+ alignItemWithTrigger = { alignItemWithTrigger ?? ! ( described || rest . multiple ) }
5560 { ...rest }
5661 >
5762 { children }
@@ -62,7 +67,7 @@ export function SelectRoot({ items, alignItemWithTrigger, children, ...rest }: S
6267
6368export type SelectTriggerVariant = 'outline' | 'ghost' ;
6469
65- export type SelectTriggerProps = MosaicComponentProps < 'button' > & {
70+ export type SelectTriggerProps = Omit < MosaicComponentProps < 'button' > , 'children '> & {
6671 /**
6772 * `outline` is the bordered form control; `ghost` drops the border for triggers that sit in
6873 * dense surfaces such as table rows.
@@ -72,6 +77,11 @@ export type SelectTriggerProps = MosaicComponentProps<'button'> & {
7277 variant ?: SelectTriggerVariant ;
7378 /** Shown in place of the selected label while nothing is selected. */
7479 placeholder ?: React . ReactNode ;
80+ /**
81+ * Content that replaces the selected label. A function renders inside the value slot and
82+ * receives the selected values; anything else replaces the value slot entirely.
83+ */
84+ children ?: React . ReactNode | ( ( value : string [ ] ) => React . ReactNode ) ;
7585} ;
7686
7787/**
@@ -108,6 +118,8 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
108118 labelElementType : 'span' ,
109119 } ) ;
110120 const id = fieldProps ?. id ?? idProp ?? generatedId ;
121+ const renderValue = typeof children === 'function' ? children : undefined ;
122+ const content = typeof children === 'function' ? undefined : children ;
111123 // A combobox takes no name from its content, so the value is named explicitly, after any label.
112124 // An `aria-label` is kept by naming the trigger itself.
113125 const labelledBy = fieldProps ?. [ 'aria-labelledby' ] ?? ariaLabelledBy ?? ( ariaLabel ? id : undefined ) ;
@@ -129,21 +141,23 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
129141 id = { id }
130142 disabled = { fieldProps ?. disabled ?? disabledProp }
131143 aria-label = { ariaLabel }
132- aria-labelledby = { mergeIds ( labelledBy , children === undefined ? valueId : undefined ) }
144+ aria-labelledby = { mergeIds ( labelledBy , content === undefined ? valueId : undefined ) }
133145 aria-describedby = { fieldProps ?. [ 'aria-describedby' ] ?? ariaDescribedBy }
134146 aria-invalid = { fieldProps ?. [ 'aria-invalid' ] ?? ariaInvalid }
135147 aria-required = { ariaRequired ?? ( fieldProps ?. required ? true : undefined ) }
136148 { ...mergeStyleProps ( themeProps ( 'select-trigger' , { variant } ) , stylex . props ( xstyle ) , rest ) }
137149 >
138- { children ?? (
150+ { content ?? (
139151 < Primitive . Value
140152 id = { valueId }
141153 placeholder = { placeholder }
142154 { ...mergeStyleProps (
143155 themeProps ( 'select-value' ) ,
144156 stylex . props ( reset . base , truncationStyles . singleLine , slots . value . base ) ,
145157 ) }
146- />
158+ >
159+ { renderValue }
160+ </ Primitive . Value >
147161 ) }
148162 < Icon
149163 name = 'chevron-down'
0 commit comments