MaterialUI stepper use alternative color - javascript

How can I change the colour that a material ui Stepper uses? By default the material UI stepper's icons use the primary colour for the "active" as well as "completed" steps.
class HorizontalLinearStepper extends React.Component {
state = {
activeStep: 1,
skipped: new Set()
};
render() {
const { classes } = this.props;
const steps = getSteps();
const { activeStep } = this.state;
return (
<div className={classes.root}>
<Stepper activeStep={activeStep}>
{steps.map((label, index) => {
const props = {};
const labelProps = {};
return (
<Step key={label} {...props}>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</div>
);
}
}
Now the stepper uses the theme's main colour as "icon colour". How can I change this to use the secondary colour instead? Adding a color props to any of the Stepper, Step or StepLabel doesn't seem to work, neither does style={{color: 'red', backgroundColor: 'red'}} give the expected results in any of those things.
How can I modify the colour?
A fiddle can be found here.

You can use the StepIconProps prop on StepLabel to customise the classes and change the colour e.g. https://codesandbox.io/s/k1wp19vz6o

Related

Conditionally use material-ui default prop

I have a StepLabel component in material ui. Depending on the props passed to the parent, I may want to change the icon prop of the StepLabel:
interface Props {
customClasses?: ClassNameMap;
customIcon?: ReactNode;
}
const MyStepComponent = (props: Props) => {
const { customClasses, customIcon } = props
return (
<Stepper {...stepperProps}>
<Step
icon={
customIcon
? React.cloneElement(customIcon as React.ReactElement, {
className: customClasses?.stepIcon
})
: null
}
{...otherStepProps}
/>
</Stepper>
)
}
So in this scenario, if a customIcon is passed to MyStepComponent it will render that icon instead of the default, and if customClasses is passed as well, it will apply some of those custom classes to that icon. Great.
But in the event that customIcon is not passed, I would like for the Step component to just use its default icon (which is the blue circle with the step number in it). However, in my code, it does not render the default icon, it renders null.
How can I tell the Step to use my customIcon if it exists, but if not, use the default?
if the customIcon is truthy then the first if will be triggered and the component with the custom icon will be rendered.
const MyStepComponent = (props: Props) => {
const { customClasses, customIcon } = props
if (customIcon) {
return (
<Stepper {...stepperProps}>
<Step
icon={ React.cloneElement(customIcon as React.ReactElement, {
className: customClasses?.stepIcon
})
}
{...otherStepProps}
/>
</Stepper>
)
}
return (
<Stepper {...stepperProps}>
<Step
icon={defaultIcon}
{...otherStepProps}
/>
</Stepper>
)
}

open ant design popover from two link

I have two component: 1:StudentList 2: Major in react and antd.
StudentList Component rendered a list of students.
Major Component made a list of majors that you can pick them. After selecting major, the selected major title display on the top of the students list. and the list will be filtered according to the selected major.
This is StudentList component contain Major component:
class StudentList extends Component {
render(){
return(
<>
<Major/>
<h5>20 student found in <a>selected major</a></h5>
<List>
//this is the list of students and is not related to this question
</List>
</>);
}
}
This is Major Component with a filter button to open the popover:
class Major extends Component {
render() {
return (
<Popover
trigger="click"
content={content} //list of majors
>
<Button>
<FilterOutlined /> Select major to filter
</Button>
</Popover>
);
}
}
When I click on the Select major to filter button, the popover open to select majors. I want to change the code in order to open this popover from two place:
1- click on Select major to filter button in the Major component
2- click on selected major in the title in StudentList component.
Notice: I want to open the same popover in the same place (similar to when I click on Select major to filter button)
Maybe it could handle with state and handleVisibleChange function. but I don't know how to handle it from 2 components. I glad to hearing your solutions.
You can use the visible and onVisibleChange property from Antd's tooltip because they are used by the PopOver as well. You can find an easy example from Andt how to control a PopOver by visible in the docs.
To get the button click you can use onClick from antd's Button Api.
The desired example using React Components:
class Major extends Component {
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.value !== prevProps.value) {
this.setState({ visible: this.props.value });
}
}
state = {
visible: false
};
hide = () => {
this.setState({
visible: false
});
};
handleVisibleChange = visible => {
this.setState({ visible });
// this.props.onChange(visible); // add me to open popover on every click on studenlist
};
render() {
return (
<Popover
trigger="click"
content={<a onClick={this.hide}>Close</a>}
visible={this.state.visible}
onVisibleChange={this.handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
}
class StudentList extends Component {
state = {
visible: false
};
onClick = () => {
this.setState({ visible: !this.state.visible });
};
render() {
return (
<>
{/* <Major value={this.state.visible} onChange={setVisible} /> */}
<Major value={this.state.visible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={this.onClick}>Select major from Studenlist</Button>
</>
);
}
}
Component example as a CodeSandBox.
Here is a simple example for your request using react hooks and simple buttons to open the PopOver:
function Major({ value, onChange }) {
const [visible, setVisible] = useState(value);
useEffect(() => {
value && setVisible(value);
}, [value]);
const hide = () => setVisible(false);
const handleVisibleChange = visible => {
setVisible(visible);
onChange(visible);
};
return (
<Popover
trigger="click"
content={<a onClick={hide}>Close</a>}
visible={visible}
onVisibleChange={handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
function StudentList() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<>
<Major value={visible} onChange={setVisible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={onClick}>Select major from Studenlist</Button>
</>
);
}
The depended working CodeSandBox.
Edit1: Added React Component example.

How to call child component's method from a parent component in React

I have a Grid with 3*3 squares.
When a click on a square , we change the background color to green.
So, I tried to put the all the states in the parent GridContainer.
state = {
gridCells: []
};
This will hold the indices that are clicked.
GridContainer nests Grid and Grid nests Square.
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
Here is my current implementation.
Now how do I clear the background cells when I reset clicks and make the background back to white again?
function Square(props) {
const liClickHandler = event => {
event.target.classList.add("highlight");
props.clickAction();
};
return <li onClick={e => liClickHandler(e)} />;
}
function Grid(props) {
const gridHandler = index => {
props.action(index);
};
return (
<ul>
{Array.from(new Array(9)).map((item, index) => (
<Square key={index} clickAction={() => gridHandler(index)} />
))}
</ul>
);
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
handler = index => {
let temp = [...this.state.gridCells];
temp.push(index + 1);
this.setState({
gridCells: temp
});
};
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
}
So when I click a Sqaure , it calls a method clickAction that calls handler
that updates the state and we have an array which indices were clicked in order.
How do I implement Reset clicks that updates the background of those Sqaures back to white ? How do I let know my child know.
Am I maintaining the state wrong?
Sandbox link : https://codesandbox.io/s/3-x-3-grids-s0b43?file=/src/index.js:640-1563
I'd advise to rethink the way how your components are structured.
Each component should be independent unit with it's own logic and state (if needed of course). I'm saying if needed for state, cause ideally components should be stateless.
There are several problems with Square class:
It adds class via event.target, which is not react way to go. React works with virtual DOM and has it's own set of methods to interact with html. Working with DOM directly - will bite your later, starting from writing tests for your code in the future.
It does not contain incoming information whether it should be highlighted or not
Both these problems result in fact that you cannot reset presentation of your squares easily.
I've updated your sample: https://codesandbox.io/s/3-x-3-grids-uflhr?file=/src/index.js
It's still not ideal, but you can notice that gridCells is passed from top via props. And then each square gets own props param. This allows state to come through the flow and let squares rerender with updated class.
In react you should think the "react" way:
pass the necessary state down through the props
pass down the callbacks so that children can update the parent state
Here is corrected version of the demo:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Square(props) {
return (
<li onClick={props.onClick} className={props.active ? "highlight" : ""} />
);
}
function Grid(props) {
let squares = [];
for (let i = 0; i < 9; i++) {
squares.push(
<Square
key={i}
onClick={() => props.onCellClick(i)}
active={props.cells[i]}
/>
);
}
return <ul>{squares}</ul>;
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
onCellClick = index => {
this.setState(prevState => {
const newCells = [...prevState.gridCells];
newCells[index] = true;
return {
gridCells: newCells
};
});
};
render() {
return (
<div>
<Grid cells={this.state.gridCells} onCellClick={this.onCellClick} />
<button
onClick={() => {
let that = this; //we could bind the callback aswell
that.setState(() => ({ gridCells: [] }));
}}
>
Reset Clicks
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<GridContainer />, rootElement);

Sending custom props to custom component is failing

I have created a custom component which takes a color name from the parent component and updates that color in the state in the parent component. Currently, after I have done all the code, it does not save the new color, and therefore, does not update the the state.
This is for a react-native android app that I am building. I have looked at the ReactNative documentation for flatlist and textinput. I have looked at Stack overflow for solutions too
Set up a react native project. this is my parent component
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "blue",
availableColors: [
{name: 'red'}
]
}
this.changeColor = this.changeColor.bind(this)
this.newColor = this.newColor.bind(this)
}
changeColor(backgroundColor){
this.setState({
backgroundColor,
})
}
newColor(color){
const availableColors = [
...this.state.availableColors,
color
]
this.setState({
availableColors
})
}
renderHeader = ()=>{
return(
<ColorForm onNewColor={this.newColor} />
)
}
render() {
const { container, row, sample, text, button } = style
const { backgroundColor, availableColors } = this.state
return (
<View style={[container,{backgroundColor}, {flex: 1}]} >
<FlatList
data={availableColors}
renderItem={
({item}) =>
<ColorButton
backgroundColor={item.name}
onSelect={(color)=>{this.changeColor(color)}}>
{item.name}
</ColorButton>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={this.renderHeader}
>
</FlatList>
</View>
);
}
}
this is the code for ColorForm component
class ColorForm extends Component {
constructor(props) {
super(props);
this.state = {
txtColor:'',
}
this.submit = this.submit.bind(this)
}
submit() {
this.props.onNewColor(this.state.txtColor.toLowerCase())
this.setState({
txtColor: 'yellow',
})
}
render() {
const {container, txtInput, button} = style
return (
<View style={container}>
<TextInput style={txtInput}
placeholder="Enter a color"
onChangeText={(txtColor)=>this.setState({txtColor})}
value={this.state.txtColor}></TextInput>
<Text
style={button}
onPress={this.submit}>Add</Text>
</View> );
}
}
and below is the code for ColorButton component
export default ({backgroundColor, onSelect=f=>f}) => {
const {button, row, sample, text} = style
return (
<TouchableHighlight onPress={()=>{onSelect(backgroundColor)}} underlayColor="orange" style={button}>
<View style={row}>
<View style={[sample,{backgroundColor}]}></View>
<Text style={text}>{backgroundColor}</Text>
</View>
</TouchableHighlight>
)
}
The imports and stylesheets are setup as standard and do not effect the code so I have chosen to not show them.
EDIT: Adding the expo snack here.
Expected Behavior:
When I press "ADD" on the ColorForm component, it should take that color and add that to the this.state.availableColor array and therefore visible in the ColorButton component. And when I touch the button, it should make that change
Current behaviour:
When I enter a color and press on add, it makes an empty button in the ColorButton component - NOT the color i entered in the color I entered in the ColorForm component.
EDIT: Adding the expo snack here.
Your state is updating but the FlatList is not updating. Because your data={availableColors} in flatlist is not changing but your state is changing .
Try to add extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Try this
<FlatList
extraData={this.state.backgroundColor}
Updated Answer
the problem is in this function newColor(color)
const availableColors = [
...this.state.availableColors,
color
]
you just receive a string of color but you have defined object like this {name: 'red'}
please use this code
newColor(color){
const availableColors = [
...this.state.availableColors,
{name: color}
]
this.setState({
availableColors
})
}
Snack link with example : https://snack.expo.io/#mehran.khan/healthy-cake-state-sample
Also add export default to main component to remove error of launch
export default class HomePage extends Component {
App Preview
I had many problems using setState() in the way you are using now. I recommend you to use in this way setState(), with a callback:
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
This is one of the article that talks about it.
setState() does not always immediately update the component. It may
batch or defer the update until later.
From react website setState().

ReactJS: How to dynamically render Material-UI's <MenuItem/> inside <DropDownMenu/>?

Using ReactJS + Material-UI, I have an array called colors and contains strings of different colors. Say for example the array colors has 3 color strings: "white", "blue", "green. Then I would like to render each color string has a <MenuItem/> inside a <DropDownMenu/> (http://www.material-ui.com/#/components/dropdown-menu). And once a <MenuItem/> is selected, I'd like to console log that particular color like, say chose "white": console.log("white").
So I used .forEach yet the does not show any strings and it is empty. What could I be doing wrong?
Here is the code:
constructor() {
super()
this.state = {
value: 1,
}
}
dropDownColorChange(event, index, value) {
this.setState({value: value})
//Not sure how to implement here dynamically based on array size. Would like to console.log the color string of the selected
}
render() {
var colors = ["white", "blue", "green"] //would be able to handle any array size
return (
<div>
<DropDownMenu
value={this.state.valueTwo}
onChange={this.dropDownColorChange}
>
{
<MenuItem value={1} primaryText="Select" />
colors.forEach(color => {
<MenuItem primaryText={color}/>
})
}
</DropDownMenu>
</div>
)
}
Thank you
You've almost got it right. You have to map over available colors and return a MenuItem for each color:
const colors = ['white', 'blue', 'green'];
class ColorChanger extends Component {
constructor() {
super();
this.state = {
selectedColorValue: 1,
};
}
handleColorChange(event, index, value) {
console.log(`You have selected ${colors[value]} color`);
this.setState({
selectedColorValue: value
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selectedColorValue} onChange={this.handleColorChange}>
{colors.map((color, index) =>
<MenuItem key={index} value={index} primaryText={color} />
)}
</DropDownMenu>
</div>
);
}
}
map (contrary to forEach) returns an array where each element is the return value of predicate function. In your case it returns a <MenuItem />.
I used the react hook to set the menu items on clicking my menu icon and I also set the value I want to pass to my action method.
const [menuItems, setMenuItems] = React.useState<IMenuItem[]>();
const [menuValue, setMenuValue] = React.useState<IMenuValue>();
const handleClickMenu = (
event: React.MouseEvent<HTMLElement>,
value: IMenuValue,
) => {
setMenuItems(value.menuItems);
setMenuTransaction(value);
setMenuAnchorEl(event.currentTarget);
};
return (
// ... code ...
<PositionedVertMenu
data-testid={`menu`}
open={Boolean(menuAnchorEl)}
anchorEl={menuAnchorEl}
onClick={(event: React.MouseEvent<HTMLElement>) => handleClickMenu(event, value)}
onClose={handleCloseMenu}
>
{menuValue &&
menuItems?.map((option, menuIndex) => (
<MenuItem
data-testid={`menu-item-${menuIndex}`}
onClick={() => option.action(menuValue, handleCloseMenu)}
>
<Typography>{translate(option.text)}</Typography>
</MenuItem>
))}
</PositionedVertMenu>
)

Categories