Handling image components as properties in React with styled-components - css

I have a React project and the goal is to have no .css files. I'm currently refactoring existing .css code to make use of styled-components in a Typescript React project.
Previously images were being called as background in a .css class
a.item.overview {
background: url(../img/overview.svg) no-repeat 16px 50% / 20px 20px;
margin-top: 20px;
}
The approach I've taken for the images was converting the .svg files into react components.
import React from 'react'
import styled from 'styled-components'
import Icon from './Icon'
const Svg = styled(Icon)`
margin-top: 20px;
fill: red;
height: 20px;
width: 20px;
fill-rule:evenodd;
clip-rule:evenodd;
`
export const OverviewIcon = () => (
<Svg viewBox="0 0 20 20">
<path
d="X4.2,2.5k11.2c0...."
/>
</Svg>
)
What i'm trying to achieve now is to create a component that will allow me to pass my image component into it as a property.
Ideally I would have one menuItem component that I can just pass an image component to and add some text. Something like the example below.
I'm relatively new to React so if my approach is incorrect or convoluted, I'm happy to make a change.
This is the approach I've attempted but it hasn't worked
type Props = {
image: React.ReactNode
}
export const LMenuItem = ({image}: Props) => (
<div>{image}</div>
)

This approach finally worked.
type Props = {
children: React.ReactNode
}
export const LMenuItem = ({children}: Props) => (
<div>{children}</div>
)

Related

How to write an mdx story for react component?

I have created simple image component and imported the image. Now, I would like to display it with storybook, but not sure how to pass this component correctly inside a story.mdx file. Could you please help?
Here is the image component:
import { string, urlPropType } from "prop-types"
import * as Styled from "./Image.styled"
import image from "./image/cutedog.jpg"
const Image = ({ src, alt }) => (
<Styled.Image>
<img src={image} alt={dog} />
</Styled.Image>
)
Image.propTypes = {
src: urlPropType.isRequired,
alt: string.isRequired,
}
export default Image
here is styled.component:
import styled from "#emotion/styled"
export const Image = styled.div`
display: inline block;
float: left;
width: 100%;
border radius: 5rem;
`
And I already started to write mdx story, but it si not working so far :(
import { Meta, Canvas, Story, ArgsTable } from "#storybook/addon-docs"
import Image from "../Image"
<Meta title="Components/Image" component={Image} />
# Image
<Canvas>
<Story
name="Overview"
args={{
src: "arrowDown",
alt: "medium",
}}
>
{Template.bind()}
</Story>
</Canvas>
<ArgsTable />
export const Template = (args) => <Image {...args} />
maybe someone could help ?

svg image not showing on react.js

I have been following a YouTube tutorial for making a website using react.js.
index.js:
import { Img, ImgWrap } from './InfoElements';
const InfoSection = ({ img, alt)} => {
return(
<>
<ImgWrap>
<Img src={img} alt={alt}/>
</ImgWrap>
</>
)
}
export default InfoSection
I have been exporting image src from another folder named Data.js
Data.js:
export const homeObjOne = {
img: require("../../images/svg-1.svg"),
alt: 'error404'
}
I used styled-components for style
InfoElements.js:
export const ImgWrap = styled.div`
max-width: 555px;
height: 100%;
`;
export const Img = styled.img`
width: 100%;
margin: 0 0 10px 0;
padding-right: 0;
`;
Here image is placed on
src->images->svg-1.svg
I tried using other images like jpe, jpg and png rather than svg but still its not working. The image is not showing on background.
Here is the picture the way image return:
Here is the path of image folder:
I have been tried all other image format and result is same..Is their anything I done wrong
Your structure is falling victim to a default export naming issue. When you require your image in Data.js, you're setting it as a module. If you want to access the URL of the image, you need to use:
export const homeObjOne = {
img: require("../../images/svg-1.svg").default, //note the default part at the end
alt: 'error404'
}
Although this could be made cleaner using imports:
import homeObjOne from "../../images/svg-1.svg";
export const homeObjOne = {
img: homeObjOne, //no need for default here, thanks to import
alt: 'error404'
}
Note: If you inspect your image whilst it is broken, you'll see the src will be [object Object].
Follow this article https://www.freecodecamp.org/news/unable-to-find-images-based-on-url-in-react/.
Move images folder under /public not /src.
In data.js
img: "./images/svg-1.svg"
Or use you can use import, like this import image1 from "./images/1.jpg

How to test CSS properties defined inside a class with react testing library

I am trying to test CSS properties that i have defined inside a class in css, wing the react testing library. However I am unable to do so.
Adding the simplified snippets.
import React from "react";
import { render, screen } from "#testing-library/react";
import '#testing-library/jest-dom/extend-expect';
import styled from "styled-components";
const Title = styled.span`
display: none;
background: red;
`
test("testRender", () => {
render(
<div>
<Title>Test</Title>
</div>
)
const spanElement = screen.getByText("Test");
const elementStyle = window.getComputedStyle(spanElement);
expect(elementStyle.display).toBe('none');
});
The test fails at the expect statement. I have tried refactoring to traditional css, there also the test fails. In both cases, I have tested it manually and the styles are taking effect.
I also understand that we should not directly test CSS properties, but I have tried testing the visibility with toBeVisible(), but that only works if the display: none is directly entered as a style, and not as part of a class.
This should be a very simple thing, that works out of the box, but I have been at it for some time now, without any luck.
Any help is appreciated.
I agree with #ourmaninamsterdam answer.
In addition, for checking appearance or disappearance of any element, you can also use .not.toBeInTheDocument like so:
expect(screen.queryByText("Test")).not.toBeInTheDocument();
NOTE: You must use queryByText instead of getByText in this case since queryByText wont throw an error if it doesn't find the element (it will return null).
Official docs Reference - https://testing-library.com/docs/guide-disappearance#nottobeinthedocument
You can use expect(screen.getByText("Test")).not.toBeVisible();
import React from "react";
import { render, screen } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import styled from "styled-components";
it("does display", () => {
const Title = styled.span`
display: block;
background: red;
`;
render(
<div>
<Title>Test</Title>
</div>
);
expect(screen.getByText("Test")).toBeVisible();
});
it("doesn't display", () => {
const Title = styled.span`
display: none;
background: red;
`;
render(
<div>
<Title>Test</Title>
</div>
);
expect(screen.getByText("Test")).not.toBeVisible();
});
...see the sandbox: https://codesandbox.io/s/blazing-river-l6rn6?file=/App.test.js

How do I use a custom SVG file with material-ui Icon Component?

In my project, I am using custom svg files as part of the requirement.
I am using material-ui#3.9.3 to achieve this.
I looked at the example that is available on the official documentation -> https://material-ui.com/style/icons/#svg-icons
However, in my version, the custom icons do not appear the same way.
My forked version is available at https://codesandbox.io/s/mqnq9qrqn8
What I am looking for is a way to use the custom icons that can work well with Material-UI's <Icon> Component.
Could someone please help me with this?
Thank you
You can also use the Material-UI IconButton component and wrap it around your svg img as shown below
import React from 'react'
import { Icon } from "#material-ui/core";
import YourLogo from './yourlogo.svg'
export const Logo = () => (
<Icon>
<img src={YourLogo} height={25} width={25}/>
</Icon>
)
You can import the svg file as a React Component and then use it directly inside an SvgIcon Component from Material UI.
This will also allow you to style your component.
import React from 'react';
import { SvgIcon, makeStyles } from '#material-ui/core';
import { ReactComponent as MySvg } from './img/someSvgFile.svg';
const useStyles = makeStyles(theme => {
mySvgStyle:{
fillColor: theme.palette.primary.main
}
})
function MySvgIcon() {
classes = useStyles();
return(
<SvgIcon className={classes.mySvgStyle}>
<MySvg/>
</SvgIcon>
)
}
The answer lies in the viewBox attribute ( MDN )
When you are messing with SVGs, especially copy/pasted ones, you have to finesse the viewBox to frame your paths correctly. I usually start with <svg viewBox="0 0 100 100" /> and scale up or down, to taste. After some fiddling, "0 0 60 50" looks pretty good (link).
Looking at the MaterialUI docs (link), they planned for this sort of thing and allow it as a prop on the component.
just put an <img/> inside your <Icon/> or any other widgets...
import { Icon } from "#material-ui/core"
import YourImage from './yourImage.png'
export const YourComponent = () => (
<Icon>
<img src={YourImage}/>
</Icon>
)

Styling material UI components

Not really a problem but something I’m not happy with. I'm using react + typescript + css modules + https://material-ui-next.com/. Problem is that when I need to style material ui components I have to use !important a lot. Question is if there is a way to create styles without important. I create a sample project to reproduce the problem https://github.com/halkar/test-css-modules
material-ui exposes many of their components for styling. There two ways to go about doing this.
Apply styles globally
You could style the components globally and apply it to the theme. An example of this would be something like this (copied from the docs http://www.material-ui.com/#/customization/themes):
import React from 'react';
import {cyan500} from 'material-ui/styles/colors';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import AppBar from 'material-ui/AppBar';
// This replaces the textColor value on the palette
// and then update the keys for each component that depends on it.
// More on Colors: http://www.material-ui.com/#/customization/colors
const muiTheme = getMuiTheme({
palette: {
textColor: cyan500,
},
appBar: {
height: 50,
},
});
class Main extends React.Component {
render() {
// MuiThemeProvider takes the theme as a property and passed it down the hierarchy
// using React's context feature.
return (
<MuiThemeProvider muiTheme={muiTheme}>
<AppBar title="My AppBar" />
</MuiThemeProvider>
);
}
}
export default Main;
As you can see in here, appBar component have a height of 50px meaning that every time you add an appbar component to your app down the tree where you applied the muiTheme, it will give it a height of 50px. This is a list of all the styles you can apply for each component https://github.com/callemall/material-ui/blob/master/src/styles/getMuiTheme.js.
Apply styles using style attribute
To apply the styles to individual components, you can usually use the style property and pass it the styles you want.
This is another example from the docs where a margin of 12px is applied to a RaisedButton.
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
const style = {
margin: 12,
};
const RaisedButtonExampleSimple = () => (
<div>
<RaisedButton label="Default" style={style} />
<RaisedButton label="Primary" primary={true} style={style} />
<RaisedButton label="Secondary" secondary={true} style={style} />
<RaisedButton label="Disabled" disabled={true} style={style} />
<br />
<br />
<RaisedButton label="Full width" fullWidth={true} />
</div>
);
export default RaisedButtonExampleSimple;
Now, the styles are defined in the same file but you could define them in a separate file and import them to the file where you are using the components.
If you want to apply multiple styles then you can use the spread operator like so: style={{...style1,...style2}}.
Usually, you are styling a specific thing in the component (root element) with the style property but some components have more than one property to style different elements of the component. Under properties in this page http://www.material-ui.com/#/components/raised-button, you can see that there are style property, labelStyle and rippleStyle to style different parts of RaisedButton.
Check the properties under the component that you are using and see which style property you could use, otherwise check the available global style properties you could override. Hope this helps!
I should've used JssProvider and tell it to put material UI styles before mine in the page head section.
import JssProvider from 'react-jss/lib/JssProvider';
import { create } from 'jss';
import { createGenerateClassName, jssPreset } from 'material-ui/styles';
const generateClassName = createGenerateClassName();
const jss = create(jssPreset());
// We define a custom insertion point that JSS will look for injecting the styles in the DOM.
jss.options.insertionPoint = document.getElementById('jss-insertion-point');
function App() {
return (
<JssProvider jss={jss} generateClassName={generateClassName}>
...
</JssProvider>
);
}
export default App;
you have to use the component API's. You can't set style to the components imported from libraries just with css if the component has API's to get style.
*Update
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Button from 'material-ui/Button';
const styles = {
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px',
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .30)',
},
label: {
textTransform: 'capitalize',
},
};
function Classes(props) {
return (
<Button
classes={{
root: props.classes.root, // class name, e.g. `classes-root-x`
label: props.classes.label, // class name, e.g. `classes-label-x`
}}
>
{props.children ? props.children : 'classes'}
</Button>
);
}
Classes.propTypes = {
children: PropTypes.node,
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Classes);

Resources