Styling ReactSelect with CSS-in-JS - css

Here is a link to another good stackoverflow post that I am basing my initial answer on. I am attempting to style my ReactSelect component so that it looks something like this:
It's not obvious from the screenshot above, but the select box has a much smaller height (29 pixels total) than the default ReactSelect, and that's my first objective with styling (reduce the height). Here is my code right now attempting to reduce the height, derived mainly from the stackoverflow post i've linked to:
const customStyles = {
control: base => ({
...base,
height: 22,
minHeight: 22
})
};
const customControlStyles = base => ({
...base,
height: 22,
minHeight: 22
});
const selectOptions = [
{ value: 'pct', label: 'FG Percentage' },
{ value: 'attFreq', label: 'FG Frequency' },
{ value: 'made', label: 'Total Makes' },
{ value: 'att', label: 'Total Attempts' }];
const shotdistSelect =
(<Select
// arrowRenderer={null}
maxMenuHeight={30}
placeholder={'Knickerbockers'}
isClearable={false}
isDisabled={this.props.loading}
backspaceRemovesValue={false}
isSearchable={true}
value={this.state.shotdistType}
onChange={this.handleShotdistChange}
options={selectOptions}
styles={{ control: customControlStyles }}
// styles={{ customStyles }}
/>);
And here's the result of the example above:
... not exactly what I was going for. Further, when I use customStyles rather than customControlStyles in the example above, the styling no longer works, and I am not sure what I've done wrong in creating customStyles that is causing it to not work. I figure I will need to do something similar to customStyles, as it seems I'll need to style more than just the control part of ReactSelect.
And 2nd, I'd like to remove both the vertical bar and down caret in the ReactSelect, similar to the initial screenshot.
Any help with this styling would be greatly appreciated!! I've been working on this for quite some time with no success yet. Thanks!

Option 1
Make sure you're using the most up-to-date version of react-select (v2.3.0). I was able to accomplish what you want by using a bit of CSS with the style keys offered by react-select.
Working example: https://codesandbox.io/s/7y6901y950
containers/Form/Form.js
import React, { Component } from "react";
import CustomSelect from "../../components/CustomSelect/CustomSelect";
const fgOptions = [
{ value: "pct", label: "FG Percentage" },
{ value: "attFreq", label: "FG Frequency" },
{ value: "made", label: "Total Makes" },
{ value: "att", label: "Total Attempts" }
];
const saveOptions = [
{ value: "pct", label: "Save Percentage" },
{ value: "sFreq", label: "Save Frequency" },
{ value: "tSaves", label: "Total Saves" }
];
const assistOptions = [
{ value: "pct", label: "Assist Percentage" },
{ value: "aFreq", label: "Assist Frequency" },
{ value: "tAssist", label: "Total Assists" }
];
export default class Form extends Component {
handleChange = (name, value) => {
this.setState({ [name]: value });
};
handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(this.state, null, 4));
};
render = () => (
<form onSubmit={this.handleSubmit} className="app-container">
<h1>React Select Styling</h1>
<CustomSelect
name="fg"
label="FG:"
placeholder="Field Goals"
handleChange={this.handleChange}
selectOptions={fgOptions}
/>
<CustomSelect
name="assists"
label="AS:"
placeholder="Assists"
handleChange={this.handleChange}
selectOptions={assistOptions}
/>
<CustomSelect
name="saves"
label="SV:"
placeholder="Saves"
handleChange={this.handleChange}
selectOptions={saveOptions}
/>
<button type="submit" className="submit">
Submit
</button>
</form>
);
}
components/CustomSelect/CustomSelect.js
import React from "react";
import PropTypes from "prop-types";
import Select from "react-select";
import { labelStyles, selectStyles } from "./styles/styles";
const CustomSelect = ({
handleChange,
label,
name,
placeholder,
selectOptions,
value
}) => (
<div className="select-container">
<label htmlFor={name} style={labelStyles}>
{label}
</label>
<Select
name={name}
placeholder={placeholder}
isClearable={false}
backspaceRemovesValue={false}
isSearchable={true}
value={value}
onChange={value => handleChange(name, value)}
options={selectOptions}
styles={selectStyles}
/>
</div>
);
CustomSelect.propTypes = {
handleChange: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
selectOptions: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
})
),
value: PropTypes.objectOf({
value: PropTypes.string,
label: PropTypes.string
})
};
export default CustomSelect;
components/CustomSelect/styles/styles.js (see documentation for style keys -- if you add longer labels, then you must adjust labelStyles width property; otherwise, the label to select ratio will vary)
export const selectStyles = {
option: (provided, state) => ({
...provided,
borderBottom: "1px dotted pink",
color: state.isSelected ? "blue" : "",
fontSize: 16,
backgroundColor: state.isSelected ? "#eee" : "",
textAlign: "left",
cursor: "pointer"
}),
container: base => ({
...base,
width: "100%"
}),
control: base => ({
...base,
height: 32,
minHeight: 32,
fontSize: 16,
borderRadius: 0,
width: "100%",
textAlign: "left",
cursor: "pointer"
}),
dropdownIndicator: base => ({
...base,
display: "none"
}),
indicatorSeparator: base => ({
...base,
display: "none"
}),
valueContainer: base => ({
...base,
padding: 0,
paddingLeft: 2
})
};
export const labelStyles = {
fontSize: 16,
paddingTop: 8,
marginRight: 5,
width: 50,
textAlign: "right"
};
styles.css
.app-container {
padding: 0px 20px;
text-align: center;
font-family: sans-serif;
}
.select-container {
display: flex;
margin: 0 auto;
width: 100%;
max-width: 500px;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-flex: 1;
margin-bottom: 10px;
}
.submit {
cursor: pointer;
background-color: #1e87f0;
color: #fff;
border: 1px solid transparent;
box-sizing: border-box;
padding: 0 30px;
vertical-align: middle;
font-size: 14px;
line-height: 38px;
text-transform: uppercase;
transition: 0.1s ease-in-out;
transition-property: color, background-color, border-color;
}
.submit:hover {
background-color: #0f7ae5;
color: #fff;
}
.submit:focus {
background-color: #1e87f0;
color: #fff;
outline: none;
}
Option 2
Or, you can do everything without using react-select, which I'd highly recommend as it excludes yet another dependency! As such, you have the option to style it as you wish (entirely through css, entirely through css-in-js or a combination).
Working example: https://codesandbox.io/s/w72k49nn27 (this example only uses css)

Related

How to add a state and a list of links displayed in react js and storybook?

I need to have the accordion component that will change over a state when user clicks on the icon.
When user clicks on the icon, the icon and color of the title should get changed and list of links should display.
So far I managed to add onClick option to the component but what will be the best approach to achieve the result of a list displaying over a click?
How to make the icon change over click and display a list with storybook?
Could you please kindly give some advise?
Any help would be appreciated.
I already added two options for the icon, so I think I can add somehow a state to it and make it display on storybook, but not really sure how :(
Here is the component:
import { string, oneOf, func, bool } from "prop-types"
import Icon, { icons } from "design-system/components/icon"
import * as Styled from "./Button.styled"
const Button = ({
href,
text,
iconStart,
iconEnd,
variant,
color,
size,
active,
onClick,
}) => (
<Styled.Component
as={href ? `a` : `button`}
variant={variant}
color={color}
size={size}
href={href}
onClick={onClick}
>
{iconStart && (
<Styled.Icon>
<Icon name={iconStart} size={size} />
</Styled.Icon>
)}
<Styled.Text variant={variant} color={color} active={active}>
{text}
</Styled.Text>
{iconEnd && (
<Styled.Icon>
<Icon name={iconEnd} size={size} />
</Styled.Icon>
)}
</Styled.Component>
)
Button.propTypes = {
text: string.isRequired,
href: string,
iconStart: oneOf(Object.keys(icons)),
iconEnd: oneOf(Object.keys(icons)),
variant: oneOf(["fill", "border", "text", "textLine"]),
color: oneOf(["primary", "black", "white"]),
size: oneOf(["small", "medium", "large"]),
active: bool,
onClick: func,
}
Button.defaultProps = {
href: null,
iconStart: null,
iconEnd: null,
variant: "fill",
color: "primary",
size: "medium",
active: null,
onClick: null,
}
export default Button
Here are the styles:
import styled from "#emotion/styled"
import { css } from "#emotion/react"
import theme from "design-system/theme"
const sizes = {
small: {
typography: theme.typography.desktop.bodySmall,
padding: "8px 32px",
},
medium: {
typography: theme.typography.desktop.h5,
padding: "10px 40px",
},
large: {
typography: theme.typography.desktop.h4,
padding: "12px 48px",
},
}
const colors = {
primary: {
mainColor: theme.colors.primary[500],
filledText: theme.colors.neutrals[100],
},
black: {
mainColor: theme.colors.grey[600],
filledText: theme.colors.neutrals[100],
},
white: {
mainColor: theme.colors.neutrals[100],
filledText: theme.colors.primary[300],
},
}
export const Component = styled.a`
text-align: center;
border-radius: 10px;
padding: 19px;
position: relative;
color: ${({ theme }) => theme.colors.grey[600]};
background-color: ${({ theme }) => theme.colors.complementary[100]};
`
export const Text = styled.span`
${({ variant }) =>
variant === "textLine" &&
css`
text-decoration: underline;
`}
${({ active, variant, color }) =>
active &&
variant === "text" &&
css`
border-bottom: 2px solid ${colors[color].mainColor};
`}
`
export const Icon = styled.span`
display: inline-flex;
align-items: center;
justify-content: center;
`
and storybook:
import { Meta, Canvas, Story, ArgsTable } from "#storybook/addon-docs"
import Button from "design-system/components/button"
import Icon from "design-system/components/icon"
<Meta title="Components/Button" component={Button} />
# Button
<Canvas>
<Story
name="Overview - button icon end"
args={{
text: "O nas",
iconEnd: "arrowDown",
variant: "fill",
size: "medium",
}}
>
{Template.bind()}
</Story>
</Canvas>
<ArgsTable />
export const Template = (args) => <Button {...args} />

How to make scroll appear only the content exceeds specified width?

I am creating custom tabs using react and material UI. In these tabs, we don't have a fixed tab count, based on the data, the length of the tab might increase and decrease. So we planned to add scrollable functionality If the tabs count is not occupied in the given space.
But by default, the scroll is appearing even if we have only one data.
below is the code for it.
import { Key, useState } from "react";
import { styled } from "#mui/material/styles";
import Button from "#mui/material/Button";
import { ReactComponent as Plus } from "./plus.svg";
import React from "react";
const Tabs = styled("div")`
width: 100%;
overflow: hidden;
margin: 1em 0 2em;
`;
const TabContainer = styled("ul")(() => ({
padding: 0,
margin: 0,
display: "flex",
flex: "1 1 auto",
overflowX: "auto",
overflowY: "hidden",
"& li": {
"&:first-of-type": {
marginLeft: 0,
"#media (max-width: 991px)": {
marginLeft: 10
}
}
},
"#media (max-width: 400px)": {
display: "unset"
}
}));
const Nav = styled("nav")(() => ({
display: "flex",
"#media (max-width: 991px)": {
textAlign: "center"
}
}));
const Tab = styled("li")(({ theme }) => ({
border: `2px solid ${theme.palette.grey[900]}`,
borderBottom: "none",
margin: "0 10px",
display: "block",
float: "left",
position: "relative",
borderTopRightRadius: 5,
borderTopLeftRadius: 5,
backgroundColor: theme.palette.common.white,
"#media (max-width: 991px)": {
float: "unset",
textAlign: "center"
},
"&.tab-current": {
border: `2px solid ${theme.palette.primary.main}`,
borderBottom: "none",
zIndex: 100,
"&::before": {
content: '""',
position: "absolute",
height: "2px",
right: "100%",
bottom: 0,
width: "1000px",
background: theme.palette.primary.main
},
"&::after": {
content: '""',
position: "absolute",
height: "2px",
right: "100%",
left: "100%",
bottom: 0,
width: "4000px",
background: theme.palette.primary.main
},
"& span": {
color: theme.palette.primary.main
}
}
}));
const Span = styled("span")(({ theme }) => ({
color: theme.palette.grey[900],
display: "block",
fontSize: "24px",
lineHeight: 2.5,
padding: "0 14px",
cursor: "pointer",
fontWeight: 400,
overflow: "hidden",
maxWidth: "ch",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}));
const AddGoalCTA = styled("span")(({ theme }) => ({
color: theme.palette.grey[900],
display: "block",
fontSize: "24px",
lineHeight: 2.5,
padding: "0 24px",
cursor: "pointer",
fontWeight: 900,
overflow: "hidden",
whiteSpace: "nowrap"
}));
const ButtonContainer = styled("div")(() => ({
float: "right",
"#media (max-width: 991px)": {
display: "none"
},
"& .MuiButton-root": {
padding: "10px"
}
}));
const PlusIcon = styled("span")(() => ({
width: "24px",
color: "black"
}));
const tabsData = ["Save For College", "Retirement Saving", "Save For Bike"];
// const tabsData = ["Save For College", "Retirement Saving", "Save For Bike", "Legacy Saving", "Save For Poker", "Save For Money"]
const TabsComponent = ({ hideEditButton, showAddTab = true }: any) => {
const [toggleState, setToggleState] = useState(0);
const toggleTab = (index: any) => {
setToggleState(index);
};
return (
<>
<Tabs>
<Nav>
<TabContainer>
{tabsData?.map((value: string, index: Key | null | undefined) => (
<Tab
className={toggleState === index ? "tab-current" : ""}
onClick={() => toggleTab(index)}
key={index}
tabIndex={0}
role="tab"
>
<Span>{value}</Span>
</Tab>
))}
{showAddTab && (
<Tab
onClick={() => {}}
tabIndex={0}
role="tab"
onKeyPress={() => {}}
>
<AddGoalCTA>
<PlusIcon as={Plus} />
</AddGoalCTA>
</Tab>
)}
</TabContainer>
{!hideEditButton && (
<ButtonContainer>
<Button variant="contained" onClick={() => {}}>
Edit
</Button>
</ButtonContainer>
)}
</Nav>
</Tabs>
</>
);
};
export default TabsComponent;
Here you can find the working demo - https://codesandbox.io/s/mui-tabs-9sgt89?file=/tab.tsx:0-4092
Please help me to resolve this one.
I checked your code. Actually for the current tab &::after has fixed width width: "4000px", which is causing the issue. you can reduce it to 1000px or to your convenience.
Hope this helps!!
Thanks

How to change TextField input's focus border using Material-UI theme

I'm trying to create my own theme with Material-Ui v.5.4.0. And I faced with problem. I can't change TextField focused border color and width. I spend hours for research and didn't find working solution. And I started to think is it even possible to do that in theme? But it's not logical.
My current theme code:
import { createTheme } from '#mui/material/styles'
// Colors
const blue = '#5A5BD4'
const blueDark = '#4f4fd8'
// Parameters
const buttonsBorderRadius = 20
const buttonPadding = '5px 15px'
const inputBorderRadius = 10
const theme = createTheme({
components: {
// Buttons
MuiButton: {
variants: [
// Blue button
{
props: { variant: 'blueButton' },
style: {
backgroundColor: blue,
color: '#ffffff',
borderRadius: buttonsBorderRadius,
textTransform: 'none',
padding: buttonPadding,
'&:hover': {
backgroundColor: blueDark
}
}
},
// Transparent button
{
props: { variant: 'transparentButton' },
style: {
color: blue,
borderRadius: buttonsBorderRadius,
textTransform: 'none',
padding: buttonPadding
}
}
]
},
// Inputs
MuiOutlinedInput: {
styleOverrides: {
root: {
borderRadius: inputBorderRadius,
'& fieldset': {
border: `1px solid ${blue}`
}
},
focus: {
border: `1px solid ${blueDark}`
}
}
}
}
})
export default theme
My input code:
<TextField
size='small'
variant='outlined'
label={t('paslelbimo_data_nuo')}
type='date'
InputLabelProps={{
shrink: true
}}
fullWidth
value={publicationDateFrom}
onChange={(e) => setPublicationDateFrom(e.target.value)}
/>
Since I wasn't able to tell exactly what your desired effect was on focus vs not focused, I decided to just create a generic example, with overly dramatic styling, that may be useful to modify for your needs:
Essentially, I'm just overriding .MuiOutlinedInput-notchedOutline for both the focused an unfocused states:
const theme = createTheme({
components: {
// Inputs
MuiOutlinedInput: {
styleOverrides: {
root: {
...
"& .MuiOutlinedInput-notchedOutline": {
border: `5px solid green`,
},
"&.Mui-focused": {
"& .MuiOutlinedInput-notchedOutline": {
border: `5px dotted red`,
},
}
},
}
}
}
});
Working example CodeSandbox: https://codesandbox.io/s/customstyles-material-demo-forked-uri26?file=/theme.js:84-531

Scroll not working in overflowing content in Material UI

I am trying to map dummyData in div. The problem is the scroll doesn't work(especially in mobile screen) when data overflows out of the screen vertically. I tried using the Material UI list instead of div with no success. Here is the CodeSandbox.
import React from "react";
import makeStyles from "#material-ui/core/styles/makeStyles";
const useStyles = makeStyles(theme => ({
container: {
bottom: 0,
position: "fixed"
},
bubbleContainer: {
width: "100%"
},
bubble: {
border: "0.5px solid black",
borderRadius: "10px",
margin: "5px",
padding: "10px",
paddingTop: "150px",
paddingBottom: "150px",
display: "inline-block"
}
}));
const Demo = () => {
const classes = useStyles();
const dummyData = [
{
message: "Some random text"
},
{
message: "Some random text"
},
{
message: "Some random text"
},
{
message: "Some random text"
},
{
message: "Some random text"
}
];
const chatBubbles = dummyData.map((obj, i = 0) => (
<div className={classes.bubbleContainer}>
<div key={i++} className={classes.bubble}>
<div>{obj.message}</div>
</div>
</div>
));
return <div className={classes.container}>{chatBubbles}</div>;
};
export default Demo;

How to make Material-UI Snackbar not take up the whole screen width using anchorOrigin?

I have a class in React which uses an input field which is part of the website header:
If the input is invalid then I want to display a snackbar. I'm using Material-UI components.
The problem is I defined anchorOrigin to be center and top as per Material-UI API. However the snackbar takes up the whole screen width while I want it to only take up the top center location of the screen. My message is quite short, for example "Value invalid" but if it's longer then I should be able to use newlines. I'm not sure if there's some setting in Material-UI API to alter this (I couldn't find one) or I need to use CSS.
This is my code:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '#material-ui/core/InputBase';
import Snackbar from '#material-ui/core/Snackbar';
import SnackbarMessage from './SnackbarMessage.js';
const classes = theme => ({
inputRoot: {
color: 'inherit',
width: '100%',
},
inputInput: {
paddingTop: theme.spacing.unit,
paddingRight: theme.spacing.unit,
paddingBottom: theme.spacing.unit,
paddingLeft: theme.spacing.unit * 10,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: 120,
'&:focus': {
width: 200,
},
},
}
});
class Test extends Component {
state = {
appId: '',
snackBarOpen: false
}
render() {
return (
<div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
value={'test'} />
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center'
}}
open={true}
autoHideDuration={5000}
>
<SnackbarMessage
variant="warning"
message={"test message"}
/>
</Snackbar>
</div>
)
}
}
Material-UI set Snackbars to full viewport-width below the breakpoint "md" (600px).
You can use overrides (https://material-ui.com/customization/overrides/) and set new values to the default CSS classes of the component described in the components API (i.e. https://material-ui.com/api/snackbar/). So you can override the class anchorOriginTopCenter as follows:
const styles = theme => ({
anchorOriginTopCenter: {
[theme.breakpoints.down('md')]: {
top: "your value/function here",
justifyContent: 'center',
},
},
root: {
[theme.breakpoints.down('md')]: {
borderRadius: 4,
minWidth: "your value / function here",
},
},
});
The first objects overrides the default class {anchorOriginTopCenter}, the second 'root' is applied to first element in your snackbar (probably a 'div').
I do not know if we can add some style to the component anchor origin field. I think the div needs to be managed using CSS. It's an anchor, not style.
<Snakbar
className = "my-snakbar"
{/*All your other stuff*/}
>
{//Stuff}
</Snakbar>
CSS
.my-snakbar {
width: 200px;
//Maybe use flexbox for positioning then
}
Let me know your thoughts
Daniel
Improved Answer
Code copied from origional question and modified
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Snackbar from '#material-ui/core/Snackbar';
const classes = theme => ({
inputRoot: {
color: 'inherit',
width: '100%',
},
inputInput: {
paddingTop: theme.spacing.unit,
paddingRight: theme.spacing.unit,
paddingBottom: theme.spacing.unit,
paddingLeft: theme.spacing.unit * 10,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: 120,
'&:focus': {
width: 200,
},
},
}
});
class ComingSoon extends Component {
render() {
const styles = {
container: {
position: "fixed",
top: "0px",
width: "100%",
height: "30px"
},
snakbar: {
background: "black",
color: "white",
width: "100px",
height: "100%",
display: "flex",
justifyContent: "center",
alignContent: "center",
margin: "0 auto"
}
};
return (
<div className = "snakbar-container" style = {styles.container}>
<Snackbar
className = "my-snakbar"
style = {styles.snakbar}
anchorOrigin={{
vertical: 'top',
horizontal: 'center'
}}
open={true}
autoHideDuration={5000}
>
<span>My Message</span>
</Snackbar>
</div>
)
}
}
export default ComingSoon;
Screen shot:
Let me know if this helped
Daniel

Resources