Skip to content

Commit

Permalink
Merge pull request #5457 from JedWatson/alex/with-tailwind
Browse files Browse the repository at this point in the history
Add `classNames` API and `unstyled` prop
  • Loading branch information
nderkim committed Nov 16, 2022
2 parents 5f52869 + 56a56c4 commit 9289057
Show file tree
Hide file tree
Showing 29 changed files with 952 additions and 688 deletions.
6 changes: 6 additions & 0 deletions .changeset/weak-roses-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'react-select': minor
'@react-select/docs': patch
---

Add classNames API and unstyled prop
2 changes: 2 additions & 0 deletions docs/examples/Experimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const Group = (props: GroupProps<DateOption, false>) => {
const {
Heading,
getStyles,
getClassNames,
children,
label,
headingProps,
Expand All @@ -136,6 +137,7 @@ const Group = (props: GroupProps<DateOption, false>) => {
selectProps={selectProps}
theme={theme}
getStyles={getStyles}
getClassNames={getClassNames}
cx={cx}
{...headingProps}
>
Expand Down
5 changes: 4 additions & 1 deletion docs/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ body {
}
p > a,
p > a:hover,
p > a:visited {
p > a:visited,
li > a,
li > a:hover,
li > a:visited {
color: #2684ff;
}
code {
Expand Down
3 changes: 1 addition & 2 deletions docs/markdown/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ const Heading = (props: HeadingProps) => {
store.add(nodeKey, { key: nodeKey, label, level, path: `#${slug}` });
}
const css = {
marginTop: 0,
'&:not(:first-of-type)': { marginTop: 30 },
'&:first-child': { marginTop: 0 },
};

return linkify ? (
Expand Down
288 changes: 132 additions & 156 deletions docs/pages/styles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,146 +26,128 @@ export default function Styles() {
{md`
# Styles
React-Select offers a flexible, light-weight styling framework which is
a thin abstraction over simple javascript objects using
[emotion](https://emotion.sh/).
React Select offers 3 main APIs for styling:
- [The styles prop](#the-styles-prop)
- [The classNames prop](#the-classnames-prop)
- [The classNamePrefix prop](#the-classnameprefix-prop)
## The styles prop
The recommended way to provide custom styles to \`react-select\` is to use the \`styles\` prop.
\`styles\` takes an object with keys to represent the various [inner components](#inner-components) that \`react-select\` is made up of.
Each inner component takes a callback function with the following signature:
~~~jsx
/**
* @param {Object} provided -- the component's default styles
* @param {Object} state -- the component's current state e.g. \`isFocused\`
* @returns {Object}
*/
function styleFn(provided, state) {
return { ...provided, color: state.isFocused ? 'blue' : 'red' };
}
<Select
styles={{
control: (baseStyles, state) => ({
...baseStyles,
borderColor: state.isFocused ? 'grey' : 'red',
}),
}}
/>
~~~
## Style Object
Each component is keyed, and ships with default styles. The component's
default style object is passed as the first argument to the function
when it's resolved.
The second argument is the current state of the select, features like
\`isFocused\`, \`isSelected\` etc. allowing you to
implement dynamic styles for each of the components.
###### Style Keys
- \`clearIndicator\`
- \`container\`
- \`control\`
- \`dropdownIndicator\`
- \`group\`
- \`groupHeading\`
- \`indicatorsContainer\`
- \`indicatorSeparator\`
- \`input\`
- \`loadingIndicator\`
- \`loadingMessage\`
- \`menu\`
- \`menuList\`
- \`menuPortal\`
- \`multiValue\`
- \`multiValueLabel\`
- \`multiValueRemove\`
- \`noOptionsMessage\`
- \`option\`
- \`placeholder\`
- \`singleValue\`
- \`valueContainer\`
## Provided Styles and State
Spreading the provided styles into your returned object lets you extend it
however you like while maintaining existing styles. Alternatively, you
can omit the provided styles and completely take control of the component's styles.
The first argument is an object with the base styles. Spreading the base styles into your returned object lets you extend it however you like while maintaining existing styles. Alternatively, you can omit the provided styles and completely take control of the component's styles.
The second argument is the current state (features like \`isFocused\`, \`isSelected\` etc). This allows you to implement dynamic styles for each of the components.
## The classNames prop
As of version \`5.7.0\` of \`react-select\` you can now use the \`classNames\` prop for styling. Note: this is not to be confused with the \`className\` prop, which will add a class to the component.
\`classNames\` takes an object with keys to represent the various [inner components](#inner-components) that \`react-select\` is made up of.
Each inner component takes a callback function with the following signature:
~~~jsx
const customStyles = {
option: (provided, state) => ({
...provided,
borderBottom: '1px dotted pink',
color: state.isSelected ? 'red' : 'blue',
padding: 20,
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition };
}
}
const App = () => (
<Select
styles={customStyles}
options={...}
/>
);
~~~
<Select
classNames={{
control: (state) =>
state.isFocused ? 'border-red-600' : 'border-grey-300',
}}
/>
~~~
### Note on CSS specificity
If you are using the \`classNames\` API and you are trying to override some base styles with the same level of specificity, you must ensure that your provided styles are declared later than the styles from React Select (e.g. the \`link\` or \`style\` tag in the head of your HTML document) in order for them to take precedence.
For an example on how you might want to do this, see the [Storybook example here](https://github.com/JedWatson/react-select/blob/master/storybook/stories/ClassNamesWithTailwind.stories.tsx).
## The unstyled prop
If you are trying to style everything from scratch you can use the \`unstyled\` prop. This removes all the presentational styles from React Select (leaving some important functional styles, like those for menu positioning and input width in multi select).
## Select Props
In the second argument \`state\`, you have access to \`selectProps\` which will allow you to gain access to
your own arguments passed into the \`Select\` body.
This will make it easier to completely specify your own \`styles\` _or_ \`classNames\` to control the look of React Select, without having to specifically override the default theme we apply.
## Inner components
<details>
<summary>See list of keys for all of React Select's inner components</summary>
<ul>
<li>clearIndicator</li>
<li>container</li>
<li>control</li>
<li>dropdownIndicator</li>
<li>group</li>
<li>groupHeading</li>
<li>indicatorsContainer</li>
<li>indicatorSeparator</li>
<li>input</li>
<li>loadingIndicator</li>
<li>loadingMessage</li>
<li>menu</li>
<li>menuList</li>
<li>menuPortal</li>
<li>multiValue</li>
<li>multiValueLabel</li>
<li>multiValueRemove</li>
<li>noOptionsMessage</li>
<li>option</li>
<li>placeholder</li>
<li>singleValue</li>
<li>valueContainer</li>
</ul>
</details>
## The classNamePrefix prop
If you provide the \`classNamePrefix\` prop to React Select, all inner elements will be given a className with the provided prefix.
Given the following JSX:
~~~jsx
const customStyles = {
menu: (provided, state) => ({
...provided,
width: state.selectProps.width,
borderBottom: '1px dotted pink',
color: state.selectProps.menuColor,
padding: 20,
}),
control: (_, { selectProps: { width }}) => ({
width: width
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition };
}
}
const App = () => (
<Select
styles={customStyles}
width='200px'
menuColor='red'
options={...}
/>
);
~~~
<Select
{...props}
className="react-select-container"
classNamePrefix="react-select"
/>
~~~
...the DOM structure is similar to this:
~~~html
<div class="react-select-container">
<div class="react-select__control">
<div class="react-select__value-container">...</div>
<div class="react-select__indicators">...</div>
</div>
<div class="react-select__menu">
<div class="react-select__menu-list">
<div class="react-select__option">...</div>
</div>
</div>
</div>
~~~
## Select props
In the \`state\` argument for both the \`styles\` and \`classNames\` API, you have access to \`selectProps\` which will allow you to gain access to your own arguments passed into the Select body.
${(
<ExampleWrapper
label="Customised Styles for Single Select"
urlPath="docs/examples/StyledSingle.tsx"
raw={require('!!raw-loader!../../examples/StyledSingle.tsx')}
>
<StyledSingle />
</ExampleWrapper>
)}
${(
<ExampleWrapper
label="Customised styles for Multi Select"
urlPath="docs/examples/StyledMulti.tsx"
raw={require('!!raw-loader!../../examples/StyledMulti.tsx')}
>
<StyledMulti />
</ExampleWrapper>
)}
## cx and custom Components
Expand Down Expand Up @@ -234,34 +216,6 @@ export default function Styles() {
</ExampleWrapper>
)}
## Using classNames
If you provide the \`className\` prop to react-select, the SelectContainer will be given a className based on the provided value.
If you provide the \`classNamePrefix\` prop to react-select, all inner elements will be given a className
with the provided prefix.
For example, given \`className='react-select-container'\` and \`classNamePrefix="react-select"\`,
the DOM structure is similar to this:
~~~html
<div class="react-select-container">
<div class="react-select__control">
<div class="react-select__value-container">...</div>
<div class="react-select__indicators">...</div>
</div>
<div class="react-select__menu">
<div class="react-select__menu-list">
<div class="react-select__option">...</div>
</div>
</div>
</div>
~~~
While we encourage you to use the new Styles API, you still have the option of styling via CSS classes.
This ensures compatibility with [styled components](https://www.styled-components.com/),
[CSS modules](https://github.com/css-modules/css-modules) and other libraries.
## Overriding the theme
The default styles are derived from a theme object, which you can mutate like \`styles\`.
Expand Down Expand Up @@ -292,6 +246,28 @@ export default function Styles() {
</div>
)}
## Examples
${(
<ExampleWrapper
label="Customised Styles for Single Select"
urlPath="docs/examples/StyledSingle.tsx"
raw={require('!!raw-loader!../../examples/StyledSingle.tsx')}
>
<StyledSingle />
</ExampleWrapper>
)}
${(
<ExampleWrapper
label="Customised styles for Multi Select"
urlPath="docs/examples/StyledMulti.tsx"
raw={require('!!raw-loader!../../examples/StyledMulti.tsx')}
>
<StyledMulti />
</ExampleWrapper>
)}
`}
</Fragment>
);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"start": "cd docs && yarn start",
"start:test": "cd docs && yarn start:test",
"build:docs": "cd docs && yarn build:docs",
"build:docs": "yarn --cwd=docs build:docs && yarn --cwd=storybook build && cp -r storybook/storybook-static docs/dist/storybook",
"fresh": "rm -rf node_modules && yarn install",
"test": "npm run test:jest && npm run test:cypress",
"test:jest": "jest --coverage",
Expand Down
Loading

0 comments on commit 9289057

Please sign in to comment.