Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add classNames API and unstyled prop #5457

Merged
merged 12 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we version docs? Should this be minor too?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No one is consuming the docs as a package, so I probably wouldn't even worry about versioning it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed it's at version 3.1.2 though, and no idea how it got there 🤷

---

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a quick and dirty fix for some links being styled differently in the docs.
Fixing it properly would be more work and not sure how long for this world the existing docs site is.

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 },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason behind changing this (other than simplifying things?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous style would result in:

// 0
<h1/>

// 0
<h2/>

// 0
<h3/>

// 30
<h2/>

// 30
<h3/>

but i think the intention was to just do:

// 0
<h1/>

<h2/>

<h3/>

<h2/>

<h3/>

};

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