Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
Add labelPropsCallbackRef prop to Select
Browse files Browse the repository at this point in the history
  • Loading branch information
justinanastos committed Nov 24, 2020
1 parent 7c53a87 commit 1760773
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 3 deletions.
27 changes: 26 additions & 1 deletion src/Select/Select.story.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ export const Select = (props) => {
);
};

export function SelectWithLabel({ label, select }) {
const [labelProps, setLabelProps] = React.useState();
const labelPropsCallback = React.useCallback((incomingLabelProps) => {
setLabelProps(incomingLabelProps);
}, []);
return (
<React.Fragment>
{React.cloneElement(label, labelProps)}
{React.cloneElement(select, { labelPropsRef: labelPropsCallback })}
</React.Fragment>
);
}

# Select

**Select** is emulates a native `select` element, but rendered with Space Kit appearances.
Expand All @@ -30,12 +43,24 @@ You must use the same `<option>` and `<optgroup>` elements you would use for a n

<Canvas>
<Story name="basic options">
<Select label={<>select an item</>} initialValue="">
<Select aria-labelledby="basic-options-label" initialValue="">
<option value="">Select an option</option>
<option value="value a">a</option>
<option value="value b">b</option>
</Select>
</Story>
<Story name="basic options with label">
<SelectWithLabel
label={<label>label</label>}
select={
<Select initialValue="">
<option value="">Select an option</option>
<option value="value a">a</option>
<option value="value b">b</option>
</Select>
}
/>
</Story>
<Story name="basic optgroup">
<Select label={<>select an item</>}>
<optgroup label="Group of options">
Expand Down
53 changes: 51 additions & 2 deletions src/Select/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ChangeEvent } from "react";
import React, { ChangeEvent, RefCallback } from "react";
import { Button } from "../Button";
import { colors } from "../colors";
import { IconArrowDown } from "../icons/IconArrowDown";
Expand All @@ -16,6 +16,7 @@ import {
} from "./select/reactNodeToDownshiftItems";
import { ListConfigProvider, useListConfig } from "../ListConfig";
import { As, createElementFromAs } from "../shared/createElementFromAs";
import useDeepCompareEffect from "use-deep-compare-effect";

export type OptionProps = Omit<
React.DetailedHTMLProps<
Expand Down Expand Up @@ -76,7 +77,10 @@ interface Props
| "popperOptions"
| "matchTriggerWidth"
>,
Pick<React.ComponentProps<typeof Button>, "feel" | "style">,
Pick<
React.ComponentProps<typeof Button>,
"feel" | "style" | "aria-labelledby"
>,
Pick<
React.DetailedHTMLProps<
React.SelectHTMLAttributes<HTMLSelectElement>,
Expand All @@ -86,6 +90,43 @@ interface Props
> {
disabled?: boolean;

/**
* `RefCallback` for props that should be spread onto a `label` component
* associated with this `Select`.
*
* The value will be calculated internally by `downshift`; so get the value
* and call this callback. This callback will only be called when the values
* change by a deep comparission (via
* [`use-deep-compare-effect`](https://github.com/kentcdodds/use-deep-compare-effect));
* not by `Object.is`. Therefore it's safe to save this entire value in state
* and spread it onto a label without fear of more than one re-render.
*
* Example:
*
* ```
* import * as React from 'react';
*
* export const SelectWithLabel: React.FC = () => {
* const [labelProps, setLabelProps] = React.useState();
*
* return (
* <React.Fragment>
* <label {...labelProps}>select label</label>
* <Select
* labelPropsCallbackRef={setLabelProps}
* ...
* >
* ...
* </Select>
* </React.Fragment>
* );
* }
* ```
*/
labelPropsCallbackRef?: RefCallback<
ReturnType<UseSelectPropGetters<OptionProps>["getLabelProps"]>
>;

/**
* Callback called when the selected item changes
*
Expand Down Expand Up @@ -118,6 +159,7 @@ export const Select: React.FC<Props> = ({
initialValue,
disabled = false,
feel,
labelPropsCallbackRef,
matchTriggerWidth,
onChange,
placement = "bottom-start",
Expand Down Expand Up @@ -163,6 +205,7 @@ export const Select: React.FC<Props> = ({
const items = reactNodeToDownshiftItems(children);

const {
getLabelProps,
getItemProps,
getMenuProps,
getToggleButtonProps,
Expand Down Expand Up @@ -216,6 +259,12 @@ export const Select: React.FC<Props> = ({
},
});

// Get the label's props and call the callback ref when they change.
const labelProps = getLabelProps();
useDeepCompareEffect(() => {
labelPropsCallbackRef?.(labelProps);
}, [labelProps, labelPropsCallbackRef]);

return (
<ListConfigProvider {...listConfig} hoverColor={null}>
<ClassNames>
Expand Down

0 comments on commit 1760773

Please sign in to comment.