Skip to content

Commit 983af25

Browse files
committed
fix(Select): updates Select for radix v1
Update Select for addition of Portal component Adds placeholder props to component. see #286
1 parent 282cb31 commit 983af25

File tree

8 files changed

+207
-90
lines changed

8 files changed

+207
-90
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@
5050
"size-limit": [
5151
{
5252
"path": "dist/index.cjs.js",
53-
"limit": "85 KB"
53+
"limit": "90 KB"
5454
},
5555
{
5656
"path": "dist/index.esm.js",
57-
"limit": "85 KB"
57+
"limit": "90 KB"
5858
}
5959
],
6060
"lint-staged": {
@@ -145,7 +145,7 @@
145145
"typescript": "^4.6.3"
146146
},
147147
"dependencies": {
148-
"@committed/hooks": "^0.5.0",
148+
"@committed/hooks": "^0.8.0",
149149
"@mdi/js": "^6.6.96",
150150
"@radix-ui/colors": "^0.1.8",
151151
"@radix-ui/react-accordion": "^1.0.0",
@@ -173,4 +173,4 @@
173173
"@radix-ui/react-visually-hidden": "^1.0.0",
174174
"@stitches/react": "^1.2.7"
175175
}
176-
}
176+
}

src/components/ComponentsProvider/ComponentsProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const ComponentsProvider: FC<
9090
)}
9191
>
9292
<>
93-
<Isolate css={css as any} isolated={isolated}>
93+
<Isolate css={css} isolated={isolated}>
9494
{children}
9595
</Isolate>
9696
{viewport && <ToastViewport {...viewport} />}

src/components/Select/Select.stories.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ export const Scrollable: Story = () => (
126126
export const WithLabel = Template.bind({})
127127
WithLabel.args = { label: 'Label' }
128128

129+
/**
130+
* The Select component support a placeholder.
131+
*
132+
* A html select would normally have a `value`, if controlled, or `defaultValue` in an uncontrolled state.
133+
* However, if a default value is not supplied a placeholder should be used.
134+
*/
135+
export const Placeholder: Story = () => {
136+
return (
137+
<Select label="Demo" name="demo" placeholder="Select an item...">
138+
<SelectItem value="1">Item 1</SelectItem>
139+
<SelectItem value="2">Item 2</SelectItem>
140+
<SelectItem value="3">Item 3</SelectItem>
141+
</Select>
142+
)
143+
}
144+
129145
/** Using `<SelectRoot>` and `<SelectRootItem>` gives further access to the underlying Radix implementation, allowing for more customization */
130146
export const Customization: Story = () => (
131147
<SelectRoot defaultValue="19">

src/components/Select/Select.tsx

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Item,
66
ItemIndicator,
77
ItemText,
8+
Portal,
89
Root,
910
ScrollDownButton,
1011
ScrollUpButton,
@@ -29,6 +30,7 @@ const StyledTrigger = styled(Trigger, defaultStyles, inputStyles, {
2930
justifyContent: 'space-between',
3031
cursor: 'pointer',
3132
alignContent: 'center',
33+
'&[data-placeholder] span:first-child': { color: '$grey8' },
3234
})
3335

3436
const StyledValue = styled(Value, {
@@ -38,6 +40,9 @@ const StyledValue = styled(Value, {
3840
const StyledContent = styled(Content, paperStyles, {
3941
overflow: 'hidden',
4042
boxShadow: '$2',
43+
'&::before': {
44+
boxShadow: 'none',
45+
},
4146
})
4247

4348
const StyledViewport = styled(Viewport, {
@@ -109,27 +114,75 @@ const StyledScrollUpButton = styled(ScrollUpButton, scrollButtonStyles)
109114

110115
const StyledScrollDownButton = styled(ScrollDownButton, scrollButtonStyles)
111116

117+
type SelectContentProps = ComponentProps<typeof StyledContent> &
118+
CSSProps & {
119+
/** By default, portals your content parts into the body, set false to add at dom location. */
120+
portalled?: boolean
121+
/** Specify a container element to portal the content into. */
122+
container?: ComponentProps<typeof Portal>['container']
123+
}
124+
125+
export const SelectContent = forwardRef<
126+
ElementRef<typeof StyledContent>,
127+
SelectContentProps
128+
>(({ container, portalled = true, children, ...props }, forwardedRef) => (
129+
<ConditionalWrapper
130+
condition={portalled}
131+
wrapper={(child) => <Portal container={container}>{child}</Portal>}
132+
>
133+
<StyledContent {...props} ref={forwardedRef}>
134+
{children}
135+
</StyledContent>
136+
</ConditionalWrapper>
137+
))
138+
SelectContent.toString = () => `.${StyledContent.className}`
139+
112140
type SelectProps = ComponentProps<typeof Root> &
113141
UseFormControlProps & {
114142
/** Add a label to the select */
115143
label?: string
144+
/** By default, portals your content parts into the body, set false to add at dom location. */
145+
portalled?: boolean
146+
/** Specify a placeholder if no value of defaultValue supplied. */
147+
placeholder?: ComponentProps<typeof Value>['placeholder']
148+
/** Specify a container element to portal the content into. */
149+
container?: ComponentProps<typeof Portal>['container']
116150
} & CSSProps
117151

152+
/**
153+
* Select component
154+
*
155+
* Displays a dropdown list of options to the user - usually triggered by clicking a button.
156+
*
157+
*
158+
* Based on [Radix Select](https://www.radix-ui.com/docs/primitives/components/select).
159+
*/
118160
export const Select = forwardRef<ElementRef<typeof Root>, SelectProps>(
119-
({ label, css, children, ...props }, forwardedRef) => {
161+
(
162+
{
163+
label,
164+
css,
165+
container,
166+
portalled = true,
167+
placeholder,
168+
children,
169+
...props
170+
},
171+
forwardedRef
172+
) => {
120173
const [id, { state, disabled, required }, remainingProps] = useFormControl(
121174
props
122175
)
123176
return (
124177
<ConditionalWrapper
125178
condition={label}
126-
wrapper={(children) => (
179+
wrapper={(child) => (
127180
<Label variant="wrapping">
128181
<span>
129182
{label}
130183
{required === false && <LabelOptional />}
131184
</span>
132-
{children}
185+
{child}
133186
</Label>
134187
)}
135188
>
@@ -141,18 +194,20 @@ export const Select = forwardRef<ElementRef<typeof Root>, SelectProps>(
141194
css={css}
142195
ref={forwardedRef}
143196
>
144-
<StyledValue />
145-
<ChevronDown />
197+
<StyledValue placeholder={placeholder} />
198+
<Icon>
199+
<ChevronDown />
200+
</Icon>
146201
</StyledTrigger>
147-
<StyledContent>
202+
<SelectContent portalled={portalled} container={container}>
148203
<StyledScrollUpButton>
149204
<ChevronUp />
150205
</StyledScrollUpButton>
151206
<StyledViewport>{children}</StyledViewport>
152207
<StyledScrollDownButton>
153208
<ChevronDown />
154209
</StyledScrollDownButton>
155-
</StyledContent>
210+
</SelectContent>
156211
</Root>
157212
</ConditionalWrapper>
158213
)
@@ -175,18 +230,9 @@ export const SelectItem = forwardRef<
175230
)
176231
})
177232

178-
/**
179-
* Select component
180-
*
181-
* Displays a dropdown list of options to the user - usually triggered by clicking a button.
182-
*
183-
*
184-
* Based on [Radix Select](https://www.radix-ui.com/docs/primitives/components/select).
185-
*/
186233
export const SelectTrigger = StyledTrigger
187234
export const SelectValue = StyledValue
188235
export const SelectIcon = Icon
189-
export const SelectContent = StyledContent
190236
export const SelectViewport = StyledViewport
191237
export const SelectGroup = Group
192238
export const SelectItemText = ItemText

src/components/Slider/Slider.tsx

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useHover } from '@committed/hooks'
1+
import { useHover, useMergedRefs } from '@committed/hooks'
2+
import { Arrow, Content, Portal } from '@radix-ui/react-popover'
23
import { Range, Root, Thumb, Track } from '@radix-ui/react-slider'
34
import { useCallbackRef } from '@radix-ui/react-use-callback-ref'
45
import { useControllableState } from '@radix-ui/react-use-controllable-state'
@@ -12,7 +13,9 @@ import React, {
1213
} from 'react'
1314
import type { CSSProps, VariantProps } from '../../stitches.config'
1415
import { styled } from '../../stitches.config'
15-
import { Tooltip } from '../Tooltip'
16+
import { ConditionalWrapper } from '../../utils'
17+
import { Popover, PopoverAnchor } from '../Popover'
18+
import { Tooltip, tooltipArrowStyles, tooltipContentStyles } from '../Tooltip'
1619

1720
type LabelStyle = 'always' | 'hover' | 'none'
1821
type LabelSide = React.ComponentProps<typeof Tooltip>['side']
@@ -130,41 +133,54 @@ export const StyledSlider = styled(Root, {
130133
})
131134

132135
type SliderThumbProps = ComponentProps<typeof Thumb> & {
133-
labelStyle: LabelStyle
136+
showLabel?: boolean
134137
value: number | string
135138
labelSide: LabelSide
136139
portalled: boolean
137140
}
138141

142+
const StyledPopoverContent = styled(Content, tooltipContentStyles)
143+
const StyledPopoverArrow = styled(Arrow, tooltipArrowStyles)
144+
145+
type ThumbPopoverContentProps = ComponentProps<typeof Content> & {
146+
/** By default, portals your content parts into the body, set false to add at dom location. */
147+
portalled?: boolean
148+
/** Specify a container element to portal the content into. */
149+
container?: ComponentProps<typeof Portal>['container']
150+
}
151+
152+
const ThumbPopoverContent = forwardRef<
153+
ElementRef<typeof StyledPopoverContent>,
154+
ThumbPopoverContentProps
155+
>(({ portalled = true, container, children, ...props }, forwardedRef) => (
156+
<ConditionalWrapper
157+
condition={portalled}
158+
wrapper={(child) => <Portal container={container}>{child}</Portal>}
159+
>
160+
<StyledPopoverContent {...props} ref={forwardedRef}>
161+
<StyledPopoverArrow offset={-1} />
162+
{children}
163+
</StyledPopoverContent>
164+
</ConditionalWrapper>
165+
))
166+
ThumbPopoverContent.toString = () => `.${StyledPopoverContent.className}`
167+
139168
export const SliderThumb: FC<SliderThumbProps> = ({
140169
value,
141-
labelStyle,
170+
showLabel,
142171
labelSide,
143-
portalled,
172+
portalled = true,
144173
...props
145174
}) => {
146-
const trackRef = useRef<HTMLSpanElement>(null)
147-
const [isHovered] = useHover(trackRef)
148-
149-
const isOpen = useMemo(() => {
150-
if (labelStyle == 'always') {
151-
return true
152-
}
153-
if (labelStyle == 'none') {
154-
return false
155-
}
156-
return isHovered
157-
}, [isHovered, labelStyle])
158-
159175
return (
160-
<Tooltip
161-
open={isOpen}
162-
side={labelSide}
163-
content={value}
164-
portalled={portalled}
165-
>
166-
<StyledThumb {...props} ref={trackRef} />
167-
</Tooltip>
176+
<Popover open={showLabel}>
177+
<PopoverAnchor>
178+
<StyledThumb {...props} />
179+
</PopoverAnchor>
180+
<ThumbPopoverContent side={labelSide} portalled={portalled}>
181+
{value}
182+
</ThumbPopoverContent>
183+
</Popover>
168184
)
169185
}
170186

@@ -181,13 +197,14 @@ type SliderProps = ComponentProps<typeof Root> &
181197
/** Set `false` to turn off the use of portals for labels */
182198
portalled?: boolean
183199
}
200+
type SliderRef = ElementRef<typeof StyledSlider>
184201

185202
/**
186203
* Sliders can be used for selection from a (numeric) range of values.
187204
*
188205
* Based on [Radix Slider](https://radix-ui.com/primitives/docs/components/slider).
189206
*/
190-
export const Slider = forwardRef<ElementRef<typeof StyledSlider>, SliderProps>(
207+
export const Slider = forwardRef<SliderRef, SliderProps>(
191208
(
192209
{
193210
min = 0,
@@ -219,13 +236,27 @@ export const Slider = forwardRef<ElementRef<typeof StyledSlider>, SliderProps>(
219236
labelFunction(val)
220237
)
221238

239+
const internalRef = useRef<SliderRef>(null)
240+
const [isHovered] = useHover(internalRef)
241+
const mergedRef = useMergedRefs<SliderRef>(internalRef, forwardedRef)
242+
243+
const showLabels = useMemo(() => {
244+
if (labelStyle == 'always') {
245+
return true
246+
}
247+
if (labelStyle == 'none') {
248+
return false
249+
}
250+
return isHovered //hover.reduce((acc, cur) => acc || cur, false)
251+
}, [isHovered, labelStyle])
252+
222253
return (
223254
<StyledSlider
224255
{...props}
225256
min={min}
226257
value={values}
227258
onValueChange={setValues}
228-
ref={forwardedRef}
259+
ref={mergedRef}
229260
>
230261
<SliderTrack>
231262
<SliderRange />
@@ -234,7 +265,7 @@ export const Slider = forwardRef<ElementRef<typeof StyledSlider>, SliderProps>(
234265
<SliderThumb
235266
key={i}
236267
value={handleLabelFunction(val)}
237-
labelStyle={labelStyle}
268+
showLabel={showLabels}
238269
labelSide={labelSide}
239270
portalled={portalled}
240271
/>

0 commit comments

Comments
 (0)