Cannot create a toggle switch with React JSS (react-jss) [duplicate] - reactjs

I have seen in a lot of the MUI code that they use pseudo selectors in their react styled components. I thought I would try to do it myself and I cannot get it to work. I'm not sure what I am doing wrong or if this is even possible.
I am trying to make some CSS that will offset this element against the fixed header.
import React from 'react';
import { createStyles, WithStyles, withStyles, Typography } from '#material-ui/core';
import { TypographyProps } from '#material-ui/core/Typography';
import GithubSlugger from 'github-slugger';
import Link from './link';
const styles = () =>
createStyles({
h: {
'&::before': {
content: 'some content',
display: 'block',
height: 60,
marginTop: -60
}
}
});
interface Props extends WithStyles<typeof styles>, TypographyProps {
children: string;
}
const AutolinkHeader = ({ classes, children, variant }: Props) => {
// I have to call new slugger here otherwise on a re-render it will append a 1
const slug = new GithubSlugger().slug(children);
return (
<Link to={`#${slug}`}>
<Typography classes={{ root: classes.h }} id={slug} variant={variant} children={children} />
</Link>
);
};
export default withStyles(styles)(AutolinkHeader);

I found out that the content attribute needed to be double quoted like this
const styles = () =>
createStyles({
h: {
'&::before': {
content: '"some content"',
display: 'block',
height: 60,
marginTop: -60
}
}
});
and then everything worked like expected

As #Eran Goldin said, check the value of your content property and make sure it's set to a string "". Odds are, you are doing something like this:
'&::before': {
content: '',
...
}
Which doesn't set the content property at all in the output stylesheet
.makeStyles-content-154:before {
content: ;
...
}
In Material-UI style object, the content of the string is the css value, including the double quote, to fix it simply write
'&::before': {
content: '""', // "''" will also work.
...
}

Without having to explicitly set a height
The above solutions require explicit height. If you want height to be dynamic, you can do something like this:
&::after {
content: '_'; // Any character can go here
visibility: hidden;
}

Related

How to set customized color for a Text component with styled-components?

I want to set a Text component's color dynamically. When there is a disable condition then change to another color.
import React from "react";
import styled, { css } from "styled-components";
type TextType = {
text: string;
disable: string;
};
export const Text: React.FC<TextType> = ({ text, disable }) => {
return <TextStyle disable>{text}</TextStyle>;
};
const TextStyle = styled.span`
${({ theme }) => css`
color: ${(props) =>
props.disable ? theme.color.text : theme.color.disable};
`}
`;
The props.disable got error
Property 'disable' does not exist on type 'ThemeProps<DefaultTheme>'. [2339]
The theme is
export const theme = {
color: {
text: black,
disable: blue,
},
};
I want to use it as
<Text disable text={text} />
Or
<Text text={text} disable={disable} />
Is it necessary to define a disable property in theme const?
i think it is because the first argument in your styled component Text is 'theme', while it should be props. so in your case there is a confusion between theme and props.
try this see if it works :
import React from "react";
import styled, { css } from "styled-components";
type TextType = {
text: string;
disable: string;
};
export const Text: React.FC<TextType> = ({ text, disable }) => {
return <TextStyle disable={disable} >{text}</TextStyle>;
};
const TextStyle = styled.span`
${(props) => css`
color: ${ props.disable ? props.theme.color.text : props.theme.color.disable};
`}
`;
Also, the disable prop that you are passing in your component is not the same as the disable property in your theme. If you do not wish to have a different name for each as to avoid confusion, may be you should at least say that the disable you are passing into your component as a prop is of type boolean, like so :
type TextType = {
text: string;
disable: boolean;
};

React jss overrides parent styles and retains child style

in using the jss lib in react with a custom theme that merges two themes together for use. Previously there was no problems but to type it correctly and create an ease of development, it was modified a bit of how the createUseStyles works. Here is an example of the usage before the change:
import { cn, createUseStyles, useTheme } from "#styling";
const useStyles = createUseStyles({
container: {
width: "500px",
height: "100px",
}
});
const Child = ({extraStyles}:{extraStyles:string[]}) => {
const theme = useTheme();
const styles = useStyles({theme});
return (
<div className={cn(styles.container, ...extraStyles)}>
</div>
)
}
// parent
import { cn, createUseStyles, useTheme } from "#styling";
import Child from "#child";
const useStyles = createUseStyles({
large: {
width: "999px",
height: "999px",
}
});
const Parent = () => {
const theme = useTheme();
const styles = useStyles({theme});
return (
<Child extraStyles={[styles.large]}/>
)
}
where cn just combined the classes together. This would result in a stylesheet where the parents styles would override the childs, allowing for flexibility.
After some necessary modification the createUseStyles is typed and generated within the styling. It looks something like this:
//styling
import {
createUseStyles as createStyles,
useTheme,
ThemeProvider,
Styles,
} from "react-jss";
const createUseStyles = (styles) => {
return (props) => {
const tempTheme = useTheme();
const temp = createStyles(styles);
return temp({ ...props, theme: tempTheme });
};
};
export { ThemeProvider, createUseStyles };
I have excluded the extra typing, but it was essentially to save the need to typescript createUseStyles and the usage of useTheme every single time within a component.
...But in doing so & in usage the exact same example as provided above with this new createUseStyle, the child injects it's style above the parents and doesn't pull in the props styling. Using !important on every single prop style is extremely tedious.
I'm confused why just pulling useTheme outside would break the injection structure (assuming that is the cause of this problem)?
Possible, you can add index for createUseStyles to set order of injection, something like this:
// Child
const useStyles = createUseStyles({
container: {
width: "500px",
height: "100px",
}
}, { index: 2 });
// Parent
const useStyles = createUseStyles({
large: {
width: "999px",
height: "999px",
}
}, { index: 1 });
I don't know how it works, if you need to add into every componente style or in entire stylesheet. The official documentation of React JSS isn't clear but it has in JSS API.

TypeScript Reassign InputHTMLAttributes

i think i know the answer to this already but i'm effectively trying to overwrite a React InputHTMLAttribute when using an interface with styled-components
import { InputHTMLAttributes } from 'react'
import styled from 'styled-components'
export interface IInputStyles
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
/** The size of the input */
size?: 'large' | 'small'
}
export const StyledInput = styled.input<IInputStyles>({}, ({ size }) => ({
height: size === 'large' ? 48 : 32,
}))
i always get var size: never
Is there a way to reassign size or does that defeat the point of type safety?
"typescript": "^3.9.7",
i believe you cannot overwrite HTML attributes like you want, but what you can do is:
export interface IInputStyles extends InputHTMLAttributes<HTMLInputElement> {
large?: boolean;
}
export const StyledInput = styled.input.attrs<IInputStyles>((props) => ({
style: {
height: (props.large ? "48" : "32") + "px",
},
}))<IInputStyles>`
...other css styles...
`;
So, if you create a StyledInput with large attribute, the height should be 48px;
<StyledInput large />;
else the height should be 32px;
<StyledInput />;

CSS empty definition necessary to have the type as specificity selector (material UI in React)

The styling for the unselected toggle button works nicely.
But the style of the selected toggle button does not appear when you don't define an empty class selector:
./App.js
import * as React from "react";
import { render } from "react-dom";
import { createStyles, WithStyles } from "#material-ui/core";
import { ToggleButtonGroup, ToggleButton } from "#material-ui/lab";
import { withStyles } from "#material-ui/core/styles";
const styles = () =>
createStyles({
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "yellow"
}
},
// this empty definition needs to be here otherwise it won't work
selected: {}
});
export interface IAppProps extends WithStyles<typeof styles> {}
const App: React.FC<IAppProps> = ({ classes }: IAppProps) => {
return (
<ToggleButton
classes={{
root: classes.root,
selected: classes.selected
}}
selected={true}
>
option 1
</ToggleButton>
);
};
const AppWithStyles = withStyles(styles)(App);
const rootElement = document.getElementById("root");
render(<AppWithStyles />, rootElement);
This perhaps has something to do with the type definition in the props. When you remove the selector 'selected' from the styles definition, it is not available anymore in the interface IAppProps.
How can you define this type in the interface?
Working example: https://codesandbox.io/s/elated-sammet-y7wh7?fontsize=14
update 1: Also tried Augmenting the props as described in the material-ui docs:
const styles = () =>
createStyles({
toggleButton: {
backgroundColor: "blue",
"&$toggleButtonSelected": {
color: "blue",
backgroundColor: "yellow"
}
},
});
export interface IAppProps {
classes: {
toggleButton: string;
toggleButtonSelected: string;
};
}
const App: React.FC<IAppProps> = ({ classes }: IAppProps) => {
// ...
With no luck.
update 2: using a hook makes the type casting redundant, but it also won't fix this:
import * as React from "react";
import { render } from "react-dom";
import { createStyles, makeStyles } from "#material-ui/core";
import { ToggleButtonGroup, ToggleButton } from "#material-ui/lab";
const useStyles = makeStyles(() =>
createStyles({
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "red"
}
},
// this still needs to be there...
// selected: {}
})
)
export interface IAppProps {}
const App: React.FC<IAppProps> = () => {
const classes = useStyles();
return (
// ...
)
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
I believe you just have a misunderstanding of how JSS works and the meaning of some of the syntax. The relevant documentation is here.
When you define your styles object (or function taking in the theme and returning an object), each key in that object is referred to by JSS as a "rule". The key is the rule name and JSS will translate the value into a CSS class. The classes object that you get back from useStyles or that gets injected as a prop when using withStyles then maps the rule names to the generated CSS class names.
The $ruleName syntax is a way to refer to the CSS class name of one of the other rules in your styles object. The & refers to the parent rule. In your example you have rules called root and selected (when it isn't commented out).
The following:
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "red"
}
},
selected: {}
would compile to CSS like the following:
.root-0 {
background-color: blue;
}
.root-0.selected-0 {
color: blue;
background-color: red;
}
By passing the following to Material-UI:
classes={{
root: classes.root,
selected: classes.selected
}}
selected={true}
You are telling it to apply "root-0 selected-0" as class names in addition to the class names applied for the default styling. Without the empty selected: {} rule name, you can't refer to $selected from the root rule (JSS should be giving you a warning in the console if you do).
There is a slightly simpler alternative (as of v4) for referring to the selected class name. selected is one of the Material-UI special states that it refers to as pseudo-classes and the documentation provides the default class name for each (e.g. Mui-selected).
This means you can do the following:
root: {
backgroundColor: "blue",
"&.Mui-selected": {
color: "blue",
backgroundColor: "red"
}
}
This is no longer referencing another rule, so selected: {} isn't needed and neither is selected: classes.selected needed in the classes prop. Instead this is referencing the actual class name that Material-UI applies for the default styling when selected={true}.

How do you change the color of a stepper in material-ui in React

Referring to this:
https://material-ui.com/demos/steppers/
It is blue by default.
I want it to be material-ui's orange200
I tried the following code (from this stackoverflow answer) but it did not work.
import getMuiTheme from 'material-ui/styles/getMuiTheme'
const muiTheme = getMuiTheme({
stepper: {
iconColor: 'green' // or logic to change color
}
})
<MuiThemeProvider muiTheme={muiTheme}>
<Stepper>
...
</Stepper>
</MuiThemeProvider>
Use Chrome DevTools (or other browsers' dev tools) to find out a class that will give you an information about an element to override.
For example, assuming you found that the class name is
.MuiStepIcon-root-78. The formulae is Mui[component name]-[style rule name]-[UUID], where Mui is a default prefix and 78 is just an id. So, the element's name is MuiStepIcon and a subsequent attribute is root.
Say, you want to change the color. If you do the hierarchy MuiStepIcon -> root -> color, you will change the default color only. To change any other colors, watch out for pseudo classes. For example, using devtools, if the class is .MuiStepIcon-root-78.MuiStepIcon-active-80, then the pseudo class is active, and your code should be MuiStepIcon -> root -> '&$active' -> color. Look at the code below for references.
Look at the docs for more info https://material-ui.com/customization/overrides/#overriding-with-classes
You can also determine available elements to override by referring to createMuiTheme -> overrides, which will take you to overrides.d.ts file. There is an interface that lists all components names, like MuiStepIcon, though it won't give you other information as devtools do.
import React, { Component } from 'react';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
const muiTheme = createMuiTheme({
overrides: {
MuiStepIcon: {
root: {
color: '#000000', // or 'rgba(0, 0, 0, 1)'
'&$active': {
color: '#000000',
},
'&$completed': {
color: '#000000',
},
},
},
}
});
const otherStyles = theme => ({
root: {
// Whatever needed
},
});
class MyComponent extends Component {
render(){
return (
<MuiThemeProvider theme={muiTheme}>
{
// Your stepper here, should be within MuiThemeProvider
}
</MuiThemeProvider>
);
}
};
export default withStyles(otherStyles, { withTheme: true })(MyComponent);
overrides: {
MuiStepIcon: {
root: {
color: '#000000', // or 'rgba(0, 0, 0, 1)'
'&$active': {
color: '#000000',
},
'&$completed': {
color: '#000000',
},
},
}
did work for me
One way to change the color and styling of icon of stepper material UI is to pass icon prop in StepLabel as:
<StepLabel
icon = <div style={{backgroundColor: 'orange', width:'11px', padding: '2px', textAlign: 'center', height: '11px', fontSize: '10px', borderRadius: '50%'}}>{index}</div>
>{label}</StepLabel>

Resources