material-UI: Unable to set Color of SVG Icon inside Tab - reactjs

Have the following inside a react class render():
import {blue900} from 'material-ui/styles/colors';
import {Tabs, Tab} from 'material-ui/Tabs';
import SocialPeople from 'material-ui/svg-icons/social/people';
var myClass = React.createClass({
render: function() {
return (
<Tabs>
<Tab icon={<SocialPeople color={blue900} />}/>
</Tabs>
);
}
});
The SVG icon color is ignored above, whereas it works below:
var myClass = React.createClass({
render: function() {
return (
<SocialPeople color={blue900} />
);
}
});
Can anyone explain this?
How to set icon color inside the tab element?

It seems like this is not possible at the moment without changing the library. I believe it's a bug. I had a hard time finding a solution to this. Then I just had a look at the code.
What I found out is that when an icon element is passed to Tab component, the props of the Tab component are changed including the style that you pass. So it makes a copy of the icon but with different props and styles and renders that.
if (icon && React.isValidElement(icon)) {
const iconProps = {
style: {
fontSize: 24,
color: styles.root.color,
marginBottom: label ? 5 : 0,
},
};
if (icon.type.muiName !== 'FontIcon') {
iconProps.color = styles.root.color;
}
iconElement = React.cloneElement(icon, iconProps);
}
So the color that you send is neglected and it takes color from the MuiTheme.
https://github.com/callemall/material-ui/blob/master/src/Tabs/Tab.js
Have a look at the code. Lines 5 to 25 and 110 to 126..
So you will need to change the library to get the color. Just follow these steps..
Open the file node_modules/material-ui/Tabs/Tab.js
Go to line 128 which reads
iconProps.color = styles.root.color
and replace it with this
if (icon.props) {
if (icon.props.style && icon.props.style.color) {
iconProps.color = icon.props.style.color;
} else if (icon.props.color) {
iconProps.color = icon.props.color;
} else {
iconProps.color = styles.root.color;
}
if (icon.props.hoverColor) {
iconProps.hoverColor = icon.props.hoverColor;
}
}
That's it.. Now your icon element should be something like this
<SocialPeople style={{ color: 'red' }} />

Related

Custom button on the leaflet map with React-leaflet version3

I'm a new leaflet learner with React typescript. Want to create a custom button on the map. On clicking the button a popup will appear. I saw many example but they are all based on older version and I also tried to create my own but no luck. The documentation also not providing much help. Even a functional custom control component is also very effective for my app. Any help on this will be much appreciated. Here is my code,
Custom button
import React, { Component } from "react";
import { useMap } from "react-leaflet";
import L, { LeafletMouseEvent, Map } from "leaflet";
class Description extends React.Component<{props: any}> {
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map : Map) => {
const helpDiv = L.DomUtil.create("button", ""); //how to pass here the button name and
//other property ?
//a bit clueless how to add a click event listener to this button and then
// open a popup div on the map
}
});
return new MapHelp({ position: "bottomright" });
}
componentDidMount() {
const { map } = this.props as any;
const control = this.createButtonControl();
control.addTo(map);
}
render() {
return null;
}
}
function withMap(Component : any) {
return function WrappedComponent(props : any) {
const map = useMap();
return <Component {...props} map={map} />;
};
}
export default withMap(Description);
The way I want to call it
<MapContainer
center={defaultPosition}
zoom={6}
zoomControl={false}
>
<Description />
<TileLayer
attribution="Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
/>
<ZoomControl position={'topright'}/>
</MapContainer>
You're close. Sticking with the class component, you just need to continue creating your buttons instance. You can use a prop on Description to determine what your button will say and do:
<Description
title={"My Button Title"}
markerPosition={[20.27, -157]}
description="This is a custom description!"
/>
In your decsription's createButtonControl, you're almost there. You just need to fill it out a bit:
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map) => {
const helpDiv = L.DomUtil.create("button", "");
this.helpDiv = helpDiv;
// set the inner content from the props
helpDiv.innerHTML = this.props.title;
// add the event listener that will create a marker on the map
helpDiv.addEventListener("click", () => {
console.log(map.getCenter());
const marker = L.marker()
.setLatLng(this.props.markerPosition)
.bindPopup(this.props.description)
.addTo(map);
marker.openPopup();
});
// return the button div
return helpDiv;
}
});
return new MapHelp({ position: "bottomright" });
}
Working codesandbox
There's a million ways to vary this, but hopefully that will get you going.

Cannot crop image using react-image-crop

I trying to crop image using react-image-crop (https://www.npmjs.com/package/react-image-crop/v/6.0.7). Actually I cannot move cropped area or stretch it. I also use there React DragZone to upload image. Could you explain what I'm doing wrong and what could be the problem ? React-image-crop css is also imported. Can anybody help?
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
import ReactCrop from 'react-image-crop'
import ReactDOM from "react-dom";
import 'react-image-crop/dist/ReactCrop.css';
import "../Styles/ImageUpload.css"
function ImageUpload(files, addFile) {
const crop = {
disabled: false,
locked: false,
unit: 'px', // default, can be 'px' or '%'
x: 130,
y: 50,
width: 200,
height: 200
}
const onCrop = (image, pixelCrop, fileName) => {
}
const onImageLoaded = (image) => {
}
const onCropComplete = async crop => {
}
const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
if (Object.keys(rejectedFiles).length !== 0) {
}
else {
files.addFile(acceptedFiles);
let popupBody = document.getElementsByClassName("popup-container")[0];
popupBody.innerHTML = "";
let cropElement = (<ReactCrop src = {URL.createObjectURL(acceptedFiles[0])} crop={crop}
onImageLoaded={onImageLoaded}
onComplete={onCropComplete}
onChange={onCrop} />);
ReactDOM.render(cropElement, popupBody);
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop, noKeyboard: true})
return (
<div className = "popup-container">
<p>Upload new photo</p>
{
(() => {
if (files.length == undefined) {
return (<div>
<input className = "testinput" {...getInputProps()} />
<div className = "popup-body" {...getRootProps()}>
<img src={require("../Resources/upload-image.png")} className = "image-upload-img"/>
<p style = {{'font-family':'Verdana'}}>Chouse a <b className = "file-manualy-
upload">file</b> or drag it here</p>
</div> </div>)
}
else {
}
})()}
</div>
)
}
I also face the same issue, Here you are trying to add disabled, locked in crop property but it is invalid because crop property takes following values:
{
unit: 'px', // default, can be 'px' or '%'
x: 130,
y: 50,
width: 200,
height: 200
}
To solve this add disabled, locked as a props in component instead of crop property.
I'm not sure sure why you would do a ReactDom.render from within the function. It seems to be counter to what React is and guessing that the library isn't binding events properly.
I would suggest, after the user drops the image, you load the image using a file reader and set it as state and change the display to show the <ReactCrop src=={this.state.img} />. The crop also needs to be in a state, so that when it changes, it updates the crop rectangle.
Here's a working example from the creator himself: https://codesandbox.io/s/72py4jlll6

Set dynamic style for Checkbox in Ant Design

Is it possible to set tick container's style backgroundColor and borderColor in Checkbox dynamically from JSX?
I can do it with CSS/LESS, but I need to set specific color based on data from API.
Example:
.
If the colors from API are completely unknown to you until the time of running, then there is no way to accomplish the task with Antd library. Because the class you need to update colors for (.ant-checkbox-checked .ant-checkbox-inner) is nested inside the parent component , and can be changed only in your .less file.
As you can see in Rafael's example, you can only control the colors of the parent (.ant-checkbox-wrapper) from .js file.
On the other hand, if you know there will always be, let's say "#1890FF", "#FA8072", and "#008100" colors, you just don't know in what order, you can easily change the colors dynamically, by creating your logic around CSS classes. Which means you can map through all your checkboxes and give the classNames based on the color you get from API (getColor function). In order to do so, in your .js file import the data from API and define getColor function:
import React, { PureComponent } from "react";
import { Checkbox } from "antd";
export default class ColoredCheckboxes extends PureComponent {
getColor = color => {
let checkboxClassName = "checkbox-green";
if (color === "#FA8072") {
checkboxClassName = "checkbox-orange"
}
if (color === "#1890FF") {
checkboxClassName = "checkbox-blue"
}
return checkboxClassName;
};
render() {
const dataFromAPI = [
{
key: "first",
name: "First",
color: "#008100",
},
{
key: "second",
name: "Second",
color: "#FA8072",
},
{
key: "third",
name: "Third",
color: "#1890FF",
},
]
return (
<div>
{dataFromAPI.map(item => (
<div key={item.key}>
<Checkbox className={this.getColor(item.color)}>
{item.name}
</Checkbox>
</div>
))}
</div>
);
}
}
Then in your .less file reassign the colors for ".ant-checkbox-checked .ant-checkbox-inner" class originally coming from Antd Library:
.checkbox-green {
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #008100;
border-color: #008100;
}
}
.checkbox-orange {
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #FA8072;
border-color: #FA8072;
}
}
.checkbox-blue {
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #1890FF;
border-color: #1890FF;
}
}
You just styled it, something like
<Checkbox
style={{
backgroundColor: data.backgroundColor,
borderColor : data.borderColor
}}
/>

Toggling font awesome Icon using React

I want to toggle a fontAwesome icon class name on click. When clicked, the icon should change the color and also call a service which adds an object to a favorite list in the server (hence, why I use e.currentTarget: I need to remember which icon was clicked). This code works on the first click, but fails to change the class back on the second click (doing an inspect, it says the FA´s classname equals "Object object"). Any idea how I could fix it?
<FontAwesomeIcon onClick={this.ToggleClass} size={"sm"} icon={faHeart} />
ToggleClass = (e) => {
const heart = {
color: "#E4002B",
}
const clicked = {
color: "#E4002B",
background: "red"
}
if (e.currentTarget.className.baseVal != heart && e.currentTarget.className.baseVal != clicked) {
return e.currentTarget.className.baseVal === clicked;
#Callservicehere
}
else if (e.currentTarget.className.baseVal === clicked) {
e.currentTarget.className.baseVal = heart;
#callservicehere
}
}
You're not thinking in React yet :)
Accessing the event target and imperatively manipulating the DOM bypasses React's rendering - you might as well just be using jQuery. Not that there's anything bad about that, but it's not the right way to go about things in React.
In React, if you need to change the DOM in response to user interaction you do it in the render method, i.e. output different JSX based on the component's current state or props.
A couple things that might help here:
clicked and heart are both objects which means that you cannot compare them without using a deep comparison method.
var a = { id: 1 }
var b = { id: 1 }
console.log(a == b) //false
console.log(a === b) //false
If you want to compare them, you can convert them both to strings using the toString() method
heart.toString() === clicked.toString()
In your first if condition, it looks like you're returning a true/false value instead of assigning a desired classname to your target.
return e.currentTarget.className.baseVal === clicked // true/false
e.currentTarget.className.baseVal = clicked // assigned
You could also take the approach of keeping your classnames as strings and adding your styled objects inside of css
class MysteryComponent extends React.Component {
state = {
className: 'heart'
}
toggleClass = (e) => {
if (this.state.className === 'heart') {
this.setState({ className: 'clicked' })
} else if (this.state.className === 'clicked') {
this.setState({ className: 'heart' })
}
}
render() {
return (
<div className={this.state.className}>
<FontAwesomeIcon onClick={this.toggleClass} size={"sm"} icon={faHeart} />
</div>
)
}
}
// css
.heart {
color: "#E4002B";
}
.clicked {
color: "#E4002B";
background: "red";
}
I see. You want to fill/unfill the color of the heart as the user clicks. The reason why the results are not meeting your expectations is because of the event.targets are especially funky with FontAwesome. You may think you're clicking on it, but it manipulates the DOM in a way that when you try extract the className, it's value is often inconsistent.
This is why everyone is recommending that you make use of React's state. The logic that determines how elements are styled is now more controlled by the component itself instead of the FontAwesome library. Consider the code below, we only care about whether the item was clicked, not what class it initially has.
class Example extends React.Component{
state = {
clicked: false
}
handleOnCLick = () => {
this.setState({
clicked: !this.state.clicked
})
}
render(){
var clicked = this.state.clicked
return(
<button onClick={this.handleOnClick}>
<i
class={ clicked ? "fas fa-heart" : "fas fa-circle"}
</i>
</button>
)
}
}

Get height of tab bar on any device in React-Navigation

I'd like to position a component just above the createBottomTabNavigator TabBar in React-Navigation V2.
The height of the tab bar seems to differ on various devices (iOS devices especially). Is there a way to calculate the height of the tab bar as it is displayed on a device?
As you check the source code for react-navigation-tabs which react-navigation uses for createBottomTabNavigator, you can see that there is only 2 different bottom tab bar heights. Compact and default, which changes between some conditions. You can also set your component's position according to these conditions manually.
React Navigation 5 +
You now have two options to get the height of the bottomTabBar.
To get the height of the bottom tab bar, you can use BottomTabBarHeightContext with React's Context API or useBottomTabBarHeight, which is a custom Hook:
import { BottomTabBarHeightContext } from '#react-navigation/bottom-tabs';
// ...
<BottomTabBarHeightContext.Consumer>
{tabBarHeight => (
/* render something */
)}
</BottomTabBarHeightContext.Consumer>
or
import { useBottomTabBarHeight } from '#react-navigation/bottom-tabs';
// ...
const tabBarHeight = useBottomTabBarHeight();
Make sure you use version 5.11.9 or greater
To avoid Ipnone X issues they use react-native-safe-area-view inside.
You just need to know padding at bottom:
import { getInset } from 'react-native-safe-area-view'
const bottomOffset = getInset('bottom')
It solved problem for us.
We also use specific component position.
Updated according to library update:
import { SafeAreaConsumer } from 'react-native-safe-area-context'
<SafeAreaConsumer>
{insets => (
<TouchableOpacity
style={{
paddingBottom: 11 + insets.bottom,
}}
>
...
</TouchableOpacity>
)}
</SafeAreaConsumer>
or hook:
const insets = useSafeArea();
For your issue of how to position something above the tab bar, you can also achieve this without absolute positioning. This way you aren't relying on how the logic of determining the height of the bar is implemented (which may also change in the future).
import { createBottomTabNavigator, BottomTabBar } from "react-navigation"
createBottomTabNavigator({
// Your tabs
}, {
tabBarComponent: (props) => <BottomTabBar {...props} />
})
For example, if you wanted a little red bar above your tabs, you could do the following
tabBarComponent: (props) => (
<View>
<View style={{ backgroundColor: "red", height: 10 }} />
<BottomTabBar {...props} />
</View>
)
The other answer by benny points to where you need to go, but doesn't give you an easy way to check if . To complete the answer, I'll elaborate on the exact checks required to know which height to use. First we need to know if the tab bar is in adaptive mode or not. If you haven't passed "adaptive" as a parameter, adaptive is set to true for all iOS devices with iOS 11+. If it's not iOS11+, then adaptive is false. So, if you HAVE NOT passed "adaptive" as a parameter to tabBarOptions, the function is:
import {Platform, Dimensions} from 'react-native';
const isLandscape = () => {
const dim = Dimensions.get('screen');
return dim.width >= dim.height;
};
function tabBarHeight() {
const majorVersion = parseInt(Platform.Version, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
if(Platform.isPad) return 49;
if(isIOS11 && !isLandscape()) return 49;
return 29;
}

Resources