Skip to content

Commit 53053cb

Browse files
[Select] Add placeholder support (#1384)
* [Select] Add placeholder support Closes #1235 * Update chromatic story * PR feedback
1 parent a670727 commit 53053cb

File tree

4 files changed

+91
-30
lines changed

4 files changed

+91
-30
lines changed

.yarn/versions/c0df45fc.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
releases:
2+
"@radix-ui/react-select": patch
3+
4+
declined:
5+
- primitives
6+
- ssr-testing

packages/react/select/src/Select.stories.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export const NoDefaultValue = () => (
156156
Choose a number:
157157
<Select.Root>
158158
<Select.Trigger className={triggerClass()}>
159-
<Select.Value />
159+
<Select.Value placeholder="Pick an option" />
160160
<Select.Icon />
161161
</Select.Trigger>
162162
<Select.Content className={contentClass()}>
@@ -512,6 +512,7 @@ export const ChromaticNoDefaultValue = () => (
512512
display: 'grid',
513513
height: '100vh',
514514
placeItems: 'center',
515+
gridTemplateColumns: 'repeat(2, 1fr)',
515516
}}
516517
>
517518
<Select.Root open>
@@ -534,6 +535,27 @@ export const ChromaticNoDefaultValue = () => (
534535
<Select.ScrollDownButton className={scrollDownButtonClass()}></Select.ScrollDownButton>
535536
</Select.Content>
536537
</Select.Root>
538+
539+
<Select.Root open>
540+
<Select.Trigger className={triggerClass()}>
541+
<Select.Value placeholder="Pick an option" />
542+
<Select.Icon />
543+
</Select.Trigger>
544+
<Select.Content className={contentClass()} style={{ opacity: 0.7 }}>
545+
<Select.ScrollUpButton className={scrollUpButtonClass()}></Select.ScrollUpButton>
546+
<Select.Viewport className={viewportClass()}>
547+
{Array.from({ length: 10 }, (_, i) => (
548+
<Select.Item key={i} className={itemClass()} value={String(i)} disabled={i < 5}>
549+
<Select.ItemText>{String(i)}</Select.ItemText>
550+
<Select.ItemIndicator className={indicatorClass()}>
551+
<TickIcon />
552+
</Select.ItemIndicator>
553+
</Select.Item>
554+
))}
555+
</Select.Viewport>
556+
<Select.ScrollDownButton className={scrollDownButtonClass()}></Select.ScrollDownButton>
557+
</Select.Content>
558+
</Select.Root>
537559
</div>
538560
);
539561
ChromaticNoDefaultValue.parameters = { chromatic: { disable: false } };

packages/react/select/src/Select.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@ const SelectTrigger = React.forwardRef<SelectTriggerElement, SelectTriggerProps>
197197
aria-autocomplete="none"
198198
aria-labelledby={labelledBy}
199199
dir={context.dir}
200+
data-state={context.open ? 'open' : 'closed'}
200201
disabled={disabled}
201202
data-disabled={disabled ? '' : undefined}
203+
data-placeholder={context.value === undefined ? '' : undefined}
202204
{...triggerProps}
203205
ref={composedRefs}
204206
onPointerDown={composeEventHandlers(triggerProps.onPointerDown, (event) => {
@@ -243,18 +245,20 @@ const VALUE_NAME = 'SelectValue';
243245

244246
type SelectValueElement = React.ElementRef<typeof Primitive.span>;
245247
type PrimitiveSpanProps = Radix.ComponentPropsWithoutRef<typeof Primitive.span>;
246-
interface SelectValueProps extends PrimitiveSpanProps {}
248+
interface SelectValueProps extends Omit<PrimitiveSpanProps, 'placeholder'> {
249+
placeholder?: React.ReactNode;
250+
}
247251

248252
const SelectValue = React.forwardRef<SelectValueElement, SelectValueProps>(
249253
(props: ScopedProps<SelectValueProps>, forwardedRef) => {
250254
// We ignore `className` and `style` as this part shouldn't be styled.
251-
const { __scopeSelect, className, style, ...valueProps } = props;
255+
const { __scopeSelect, className, style, children, placeholder, ...valueProps } = props;
252256
const context = useSelectContext(VALUE_NAME, __scopeSelect);
253257
const { onValueNodeHasChildrenChange } = context;
254-
const hasChildren = props.children !== undefined;
258+
const hasChildren = children !== undefined;
255259
const composedRefs = useComposedRefs(forwardedRef, context.onValueNodeChange);
256260

257-
React.useEffect(() => {
261+
useLayoutEffect(() => {
258262
onValueNodeHasChildrenChange(hasChildren);
259263
}, [onValueNodeHasChildrenChange, hasChildren]);
260264

@@ -265,7 +269,9 @@ const SelectValue = React.forwardRef<SelectValueElement, SelectValueProps>(
265269
// we don't want events from the portalled `SelectValue` children to bubble
266270
// through the item they came from
267271
style={{ pointerEvents: 'none' }}
268-
/>
272+
>
273+
{context.value === undefined && placeholder !== undefined ? placeholder : children}
274+
</Primitive.span>
269275
);
270276
}
271277
);

ssr-testing/pages/select.tsx

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,56 @@ import * as Select from '@radix-ui/react-select';
33

44
export default function SelectPage() {
55
return (
6-
<Select.Root defaultValue="1">
7-
<Select.Trigger>
8-
<Select.Value />
9-
<Select.Icon></Select.Icon>
10-
</Select.Trigger>
11-
<Select.Content>
12-
<Select.ScrollUpButton></Select.ScrollUpButton>
13-
<Select.Viewport>
14-
<Select.Item value="1">
15-
<Select.ItemText>Item 1</Select.ItemText>
16-
<Select.ItemIndicator></Select.ItemIndicator>
17-
</Select.Item>
18-
<Select.Item value="2">
19-
<Select.ItemText>Item 2</Select.ItemText>
20-
<Select.ItemIndicator></Select.ItemIndicator>
21-
</Select.Item>
22-
<Select.Item value="3">
23-
<Select.ItemText>Item 3</Select.ItemText>
24-
<Select.ItemIndicator></Select.ItemIndicator>
25-
</Select.Item>
26-
</Select.Viewport>
27-
<Select.ScrollDownButton></Select.ScrollDownButton>
28-
</Select.Content>
29-
</Select.Root>
6+
<>
7+
<Select.Root defaultValue="1">
8+
<Select.Trigger>
9+
<Select.Value />
10+
<Select.Icon></Select.Icon>
11+
</Select.Trigger>
12+
<Select.Content>
13+
<Select.ScrollUpButton></Select.ScrollUpButton>
14+
<Select.Viewport>
15+
<Select.Item value="1">
16+
<Select.ItemText>Item 1</Select.ItemText>
17+
<Select.ItemIndicator></Select.ItemIndicator>
18+
</Select.Item>
19+
<Select.Item value="2">
20+
<Select.ItemText>Item 2</Select.ItemText>
21+
<Select.ItemIndicator></Select.ItemIndicator>
22+
</Select.Item>
23+
<Select.Item value="3">
24+
<Select.ItemText>Item 3</Select.ItemText>
25+
<Select.ItemIndicator></Select.ItemIndicator>
26+
</Select.Item>
27+
</Select.Viewport>
28+
<Select.ScrollDownButton></Select.ScrollDownButton>
29+
</Select.Content>
30+
</Select.Root>
31+
32+
<Select.Root>
33+
<Select.Trigger>
34+
<Select.Value placeholder="Pick an option" />
35+
<Select.Icon></Select.Icon>
36+
</Select.Trigger>
37+
<Select.Content>
38+
<Select.ScrollUpButton></Select.ScrollUpButton>
39+
<Select.Viewport>
40+
<Select.Item value="1">
41+
<Select.ItemText>Item 1</Select.ItemText>
42+
<Select.ItemIndicator></Select.ItemIndicator>
43+
</Select.Item>
44+
<Select.Item value="2">
45+
<Select.ItemText>Item 2</Select.ItemText>
46+
<Select.ItemIndicator></Select.ItemIndicator>
47+
</Select.Item>
48+
<Select.Item value="3">
49+
<Select.ItemText>Item 3</Select.ItemText>
50+
<Select.ItemIndicator></Select.ItemIndicator>
51+
</Select.Item>
52+
</Select.Viewport>
53+
<Select.ScrollDownButton></Select.ScrollDownButton>
54+
</Select.Content>
55+
</Select.Root>
56+
</>
3057
);
3158
}

0 commit comments

Comments
 (0)