Rotate arrow indicator in React-select v2 - reactjs

I'm using React Select v2 in my project with Styled Components and I need to be able to turn the arrow indicator upside down when the menu is open, which was supported in v1.
I kinda managed to do it by doing this:
css`
&.react-select__control--is-focused {
& .react-select__indicators {
& .react-select__dropdown-indicator {
transform: rotate(180deg);
}
}
}
`;
Problem is that, if I press the arrow to open the menu and click on it again to close it, the arrow stays upside down because the select is still focused, which feels a bit weird in terms of UIX.
Is there a proper way to rotate it based on the state of the menu? I looked for something in the documentation but I couldn't find it.
Maybe I missed it, if someone could point me in the right direction, that'd be awesome!
Thanks!

Technically you can use the style-in-JS props of the v2. Like the following example:
dropdownIndicator: (base, state) => ({
...base,
transition: 'all .2s ease',
transform: state.isFocused ? 'rotate(180deg)' : null
})
It seems that the isFocused state isn't bind with the isMenuOpen state but with the real focus state of the container.
A solution is to set closeMenuOnSelect={false} so the user would have to click outside the select and your arrow will flip back.
Or you could change the className props using onMenuOpen and onMenuClose by adding a specific suffix to target your animation.
UPDATE
You can directly access the menuOpen props via the state so no need to manually add class like the following:
dropdownIndicator: (base, state) => ({
...base,
transition: 'all .2s ease',
transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : null
})
PLEASE NOTE THAT
In react-select v2.3 a control--menu-is-open has been added directly in the code.

This worked for me
<select styles={{
dropdownIndicator: (provided, state) => ({
...provided,
transform: state.selectProps.menuIsOpen && "rotate(180deg)"
})
}}
/>

So, based on Laura's response, my solution was to use the onMenuClose and onMenuOpen to set the state of a property in my styled component.
const indicatorStyle = (props: StyledSelectProps & DropdownProps<{}>) => css`
& .react-select__indicators {
& .react-select__dropdown-indicator {
transition: all .2s ease;
transform: ${props.isOpen && "rotate(180deg)"};
}
}
`;
This function is called inside of my styled component's css.
And then in the component I call my styled component, I control the state:
export class Dropdown<TValue> extends React.Component<DropdownProps<TValue>> {
public state = { isOpen: false };
private onMenuOpen = () => this.setState({ isOpen: true });
private onMenuClose = () => this.setState({ isOpen: false });
public render() {
const { ...props } = this.props;
const { isOpen } = this.state;
return (
<StyledSelect {...props} isOpen={isOpen} onMenuOpen={this.onMenuOpen} onMenuClose={this.onMenuClose} />
);
}
}
A bit convoluted but it works for now.

This worked for me.
dropdownIndicator: (base, state) => ({
...base,
transform: state.selectProps.menuIsOpen ? 'rotate(-90deg)' : 'rotate(0)',
transition: '250ms',
}),

i solved this issue like this.
mostly you have to play arround with these in your css to do stuff on certain conditions
--is-focused
--menu-is-open
--is-disabled
--is-selected
styled component css
.paginatorPageSizeCustomSelectPreffix__indicator {
svg {
color: var(--black);
height: 18px;
width: 18px;
transform: rotate(180deg);
transition: 0.2s;
}
}
.paginatorPageSizeCustomSelectPreffix__control--menu-is-open {
.paginatorPageSizeCustomSelectPreffix__indicator {
svg {
color: var(--black);
height: 18px;
width: 18px;
transform: rotate(360deg);
transition: 0.2s;
}
}
}
}

Related

How to avoid long conditional rendering with styled components?

I'm working on a project using a lot of different variables. Depending on props, they will render a certain color (for example). The problem is that with the amount of variables my code ends up really long. Is there a solution to clean up this code?
This is an example:
Variables:
const Variables = {
Colors: {
Primary50: "rgb(244, 246, 247)",
Primary100: "rgb(209, 218, 225)",
...rest of colors
},
...rest of variables
}
And the styled component:
const Component = styled.div<{ $color: number }>`
background-color: ${({ $color }) =>
$color === 50
? Variables.Colors.Primary50
: $color === 100
? Variables.Colors.Primary100
: ...rest};
`
I tried something like this, but it is not working:
const Component = styled.div<{ $number: number }>`
background-color: ${Variables.Colors.Primary[$number]};
`
Thanks for your answers!
Better use switch instead of ternary operator for your instance. The second aproach Primary[$number] is wrong, it look like Variables.Colors.Primary.100 you need to enclose Primary50 entirely in square brackets.
const Variables = {
Colors: {
Primary50: 'rgb(244, 246, 247)',
Primary100: 'rgb(209, 218, 225)',
},
};
const Component = styled.div<{ $color: number }>`
background-color: ${props => {
switch (props.$color) {
case 50:
return Variables.Colors.Primary50;
case 100:
return Variables.Colors.Primary100;
default:
return 'white';
}
}};
`;
const Component2 = styled.div<{ $number: 50 | 100 }>`
background-color: ${props => Variables.Colors[`Primary${props.$number}`]};
`;
function App() {
return (
<div className="App">
<Component $color={100}>First example</Component>
<Component2 $number={50}>Second example</Component2>
</div>
);
}
export default App;

How to useStyles based on props inside a breakpoint with material-ui

Let's say I have a very simple props interface that specifics a boolean property. Now, in my useStyles, I want to change how that style is rendered based on both the conditional property AND a breakpoint. Here's a very simple example:
interface Props {
isError: boolean;
}
const useStyles = makeStyles<Theme, Props>(theme => ({
box: ({ isError}) => ({
backgroundColor: isError? 'red' : 'green',
[theme.breakpoints.up('md')]: {
backgroundColor: isError ? 'maroon' : 'teal',
}
}),
}));
When I'm under the md breakpoint, this works as expected; but as soon as I go over the md breakpoint, the color doesn't change. How come?
Here's a demo on StackBlitz that demonstrates the problem.
In working up the example for this question, I figured out the problem. The property value for creating styles based on a breakpoint, also needs to be a function:
const useStyles = makeStyles<Theme, Props>(theme => ({
box: (outerProps) => ({
backgroundColor: outerProps.isError ? 'red' : 'green',
[theme.breakpoints.up('md')]: (innerProps) => ({
backgroundColor: innerProps.isError ? 'maroon' : 'aqua',
}),
})
}));
Here's an updated StackBlitz showing the working example.

Animation for react-select menu open/close

Is it possible to animate opening and closing the menu? The menu node is removed or attached to DOM tree on opening/closing so this interferes with css animation. Is there a work around for this?
For opening animation, it's relatively easy, you only have to add the animation to the <Menu/> component using css.
The following example use fade-in/out animation
#keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(2rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.menu {
animation: fadeIn 0.2s ease-in-out;
}
In your render method
<Select
{...}
components={{
Menu: (props) => <components.Menu {...props} className="menu" />
}}
/>
The closing animation is a bit trickier because when the close event fires, the menu element is removed immediately, leaving no time for the closing animation to run.
We need to change the close event behavior a little bit. The strategy is to just let the menu close abruptly as normal. But before that, we make another clone of the menu, and that clone will run the closing animation and remove itself when the animation finished.
// generate unique ID for every Select components
const [uniqueId] = React.useState(
() => 'select_' + Math.random().toFixed(5).slice(2),
);
return (
<Select
id={uniqueId}
onMenuClose={() => {
const menuEl = document.querySelector(`#${uniqueId} .menu`);
const containerEl = menuEl?.parentElement;
const clonedMenuEl = menuEl?.cloneNode(true);
if (!clonedMenuEl) return; // safeguard
clonedMenuEl.classList.add("menu--close");
clonedMenuEl.addEventListener("animationend", () => {
containerEl?.removeChild(clonedMenuEl);
});
containerEl?.appendChild(clonedMenuEl!);
}}
{...}
/>
);
Don't forget to attach the closing animation to the right css class. In this case menu--close
#keyframes fadeOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(2rem);
}
}
.menu--close {
animation: fadeOut 0.2s ease-in-out;
}
In the end, you will have something like this
Live Demo
Another way is to use menuIsOpen props instead of cloning.
const [isMenuOpen, setIsMenuOpen] = useState(false)
const openMenuHandler = async () => {
await setIsMenuOpen(true)
const menu = document.querySelector(`#select .menu`)
menu.style.opacity = '1'
}
const closeMenuHandler = () => {
const menu = document.querySelector(`#select .menu`)
menu.style.opacity = '0'
setTimeout(() => {
setIsMenuOpen(false)
}, 400)
}
<Select
menuIsOpen={isMenuOpen}
onMenuOpen={() => {openMenuHandler()}}
onMenuClose={() => {closeMenuHandler()}}
/>

Remove (or at least hide) card on react-admin List

I want to get rid of the Card on the background react-admin's List (v3.4.2). I get the desired effect if I define a string on the component property:
<List component={"some string"}/>
But this spams the console with an error:
And I don't want to have that error. On top of that, I think I shouldn't be changing the component property (I can't find it on the official docs).
The code should be the following: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/list/List.js
How should I do this? Is it possible to pass a style? Is there any component that works out of the box? Or should I just go custom?
You can hide the background using styling:
import { makeStyles } from '#material-ui/core/styles'
const useListStyles = makeStyles(theme => ({
content: {
boxShadow: 'none',
backgroundColor: 'inherit',
},
main: {
// backgroundColor: 'red',
},
root: {
// backgroundColor: 'red',
},
}))
const MyList = (props) => {
const classes = useListStyles()
return (
<List classes={classes} {...props} >
...
</List>
)
}

react-spring Transition does not animate enter state

I'm making a Collapse component using react-spring which receives children and a boolean collapsed prop.
It's rather basic, but for some reason the animation when children are mounted never runs and at the same time leave animation works good.
Here's what the component looks like
const baseStyles = {
overflow: "hidden"
};
const openStyles = {
height: "auto"
};
const collapsedStyles = {
height: 0
};
const animationConfig = {
duration: 1000
};
const Collapse = ({ collapsed, children, ...props }) => {
return (
<Transition
items={collapsed}
native
config={animationConfig}
from={baseStyles}
enter={openStyles}
leave={collapsedStyles}
// onFrame={console.log}
{...props}
>
{collapsed => !collapsed
? style => <animated.div style={style} children={children} />
: null
}
</Transition>
);
};
And here's working code https://codesandbox.io/s/459p84ky4
Am I doing something wrong or is it a bug in react spring?
You need to understand from and enter you are not applying anything in both props, means opacity is always 1 and thus animation is not working
from means what it should be at the initial stage and enter means what it should be at rendering.
So, you need to set opacity 0 in from and set it to 1 inside enter
const baseStyles = {
background: "rgba(255,0,0,.2)",
overflow: "hidden",
opacity:0
};
const openStyles = {
height: "auto",
opacity: 1
};
Edit:
If you want height form zero to auto then you need to first set height to 0 in from
const baseStyles = {
background: "rgba(255,0,0,.2)",
overflow: "hidden",
height: 0
};
const openStyles = {
height: "auto",
opacity: 1
};
Demo

Resources