How to use components in different ways (React) - css

I want to use simple components in different way and different ui rendering
For example a dropdown rendering a list may have several ui according to the page or context (=> padding, margins, font size and other css properties might change)
should I:
implement it by overwriting in the parent component (target css properties of the child component and apply them my css needs - at cost that if change happens in the child component like change in classname or what might break the parent design)
Pass flags to the component to handle those design and at cost that each component handle the design of each parent

There are different approaches to this and everybody has his own preferences.
I usually solve this by supporting the className property. The class is accepted as a prop and applied to the root. So it is easy to change things like outer margins or the background-color. I usually discourage modifications of deeply nested elements.
Example:
import classnames from 'clsx';
import style from './button.module.scss';
export const Button = ({ content, onClick, className }) => {
return (
<div
className={classnames(style.buttonRoot, className)}
onClick={onClick}>
{content}
</div>
);
};
and if I want to modify it anywhere I can do it thus:
import { Button } from './Button';
import style from './productView.module.scss';
// ...
<Button content={'Show products'} className={style.showProdButton} onClick={showProd} />
and
.show-prod-button {
background-color: #562873;
margin-left: 32px;
}

Related

How to style a component "from the outside" with scoped css

I'm using scoped CSS with https://github.com/gaoxiaoliangz/react-scoped-css and am trying to follow the following rules (besides others):
Scoped component CSS should only include styles that manipulate the "inside" of the component. E.g. manipulating padding, background-color etc. is fine whilst I try to stay away from manipulating stuff like margin, width, flex etc. from within the component CSS
Manipulating the "outside" of a component (margin, width, flex etc.) should only be done by "consuming" or parent components
This is rule is somewhat derived from some of the ideas behind BEM (and probably other CSS methodologies as well) and allows for a rather modular system where components can be used without "touching their outside" but letting the parent decide how their internal layouts etc. works.
Whilst this is all fine in theory, I don't really know how to best manipulate the "outside styles" of a component from the consuming code which is best shown with an example:
search-field.scoped.css (the component)
.input-field {
background: lightcoral;
}
search-field.tsx (the component)
import './search-field.scoped.css';
type SearchFieldProps = {
className: string;
};
export const SearchField = (props: SearchFieldProps) => {
return <input className={`input-field ${props.className}`} placeholder="Search text" />;
};
sidebar.scoped.css (the consumer)
.sidebar-search-field {
margin: 16px;
}
sidebar.tsx (the consumer)
import './sidebar.scoped.css';
// ...
export const Sidebar = () => {
return (
<SearchField className="sidebar-search-field" />
(/* ... */)
);
};
In the above example, the CSS from the class sidebar-search-field in sidebar.scoped.css is not applied because the class passed to SearchField is scoped to the Sidebar and the final selector .sidebar-search-field[data-sidebarhash] simply doesn't match as the input element of the SearchField (obviously) doesn't have the data attribute data-sidebarhash but data-searchfieldhash.
ATM, I tend to create wrapper elements in situations like this which works but is rather cumbersome & clutters the markdown unnecessarily:
// ...
export const Sidebar = () => {
return (
<div className="sidebar-search-field">
<SearchField />
</div>
(/* ... */)
);
};
Question
Is there any way to "style scoped CSS component from the outside"?
Ps.: I'm not sure if all the above also applies to scoped styles in Vue. If not, please let me know how it works there so that I can create a feature request in https://github.com/gaoxiaoliangz/react-scoped-css.

How would I apply Material-UI managed styles to non-material-ui, non-react elements?

I have an application where I'm using Material UI and its theme provider (using JSS).
I'm now incorporating fullcalendar-react, which isn't really a fully fledged React library - it's just a thin React component wrapper around the original fullcalendar code.
That is to say, that I don't have access to things like render props to control how it styles its elements.
It does however, give you access to the DOM elements directly, via a callback that is called when it renders them (eg. the eventRender method).
Here's a basic demo sandbox.
Now what I'm wanting to do is make Full Calendar components (eg, the buttons) share the same look and feel as the rest of my application.
One way to do this, is that I could manually override all of the styles by looking at the class names it's using and implementing the style accordingly.
Or - I could implement a Bootstrap theme - as suggested in their documentation.
But the problem with either of these solutions, is that that:
It would be a lot of work
I would have synchronisation problems, if I made changes to my MUI theme and forgot to update the calendar theme they would look different.
What I would like to do is either:
Magically convert the MUI theme to a Bootstrap theme.
Or create a mapping between MUI class names and the calendar class names, something like:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary
I wouldn't mind having to massage the selectors etc to make it work (ie. For example - MUI Buttons have two internal spans, whereas Full Calendar have just one). It's mostly about when I change the theme - don't want to have to change it in two places.
Using something like Sass with its #extend syntax would is what I have in mind. I could create the full-calendar CSS with Sass easily enough - but how would Sass get access to the MuiTheme?
Perhaps I could take the opposite approach - tell MUI 'Hey these class names here should be styled like these MUI classes'.
Any concrete suggestions on how I would solve this?
Here is my suggestion (obviously, it's not straight forward). Take the styles from the MUI theme and generate style tag based on it using react-helmet. To do it event nicely, I created a "wrapper" component that do the map. I implemented only the primary rule but it can be extended to all the others.
This way, any change you will do in the theme will affect the mapped selectors too.
import React from "react";
import { Helmet } from "react-helmet";
export function MuiAdapter({ theme }) {
if (!theme.palette) {
return <></>;
}
return (
<Helmet>
<style type="text/css">{`
.fc-button-primary {
background: ${theme.palette.primary.main}
}
/* more styles go here */
`}</style>
</Helmet>
);
}
And the use of the adapter
<MuiAdapter theme={theme} />
Working demo: https://codesandbox.io/s/reverent-mccarthy-3o856
You could create a mapping between MUI class names and the calendar class names by going through ref's. It's possible that this is not what some would call "best practice"...but it's a solution :). Note that I updated your component from a functional component to a class component, but you could accomplish this with hooks in a functional component.
Add refs
Add a ref to the MUI element you want to set as a reference, in your case the Button.
<Button
color="primary"
variant="contained"
ref={x => {
this.primaryBtn = x;
}}
>
And a ref to a wrapping div around the component you want to map to. You can't add it directly to the component since that wouldn't give us access to children.
<div
ref={x => {
this.fullCal = x;
}}
>
<FullCalendar
...
/>
</div>
Map classes
From componentDidMount() add whatever logic you need to target the correct DOM node (for your case, I added logic for type and matchingClass). Then run that logic on all FullCalendar DOM nodes and replace the classList on any that match.
componentDidMount() {
this.updatePrimaryBtns();
}
updatePrimaryBtns = () => {
const children = Array.from(this.fullCal.children);
// Options
const type = "BUTTON";
const matchingClass = "fc-button-primary";
this.mapClassToElem(children, type, matchingClass);
};
mapClassToElem = (arr, type, matchingClass) => {
arr.forEach(elem => {
const { tagName, classList } = elem;
// Check for match
if (tagName === type && Array.from(classList).includes(matchingClass)) {
elem.classList = this.primaryBtn.classList.value;
}
// Run on any children
const next = elem.children;
if (next.length > 0) {
this.mapClassToElem(Array.from(next), type, matchingClass);
}
});
};
This is maybe a little heavy handed, but it meets your future proof requirement for when you updated update Material UI. It would also allow you to alter the classList as you pass it to an element, which has obvious benefits.
Caveats
If the 'mapped-to' component (FullCalendar) updated classes on the elements you target (like if it added .is-selected to a current button) or adds new buttons after mounting then you'd have to figure out a way to track the relevant changes and rerun the logic.
I should also mention that (obviously) altering classes might have unintended consequences like a breaking UI and you'll have to figure out how to fix them.
Here's the working sandbox: https://codesandbox.io/s/determined-frog-3loyf

How to add background image on a material ui Dialog component

I'm using material-ui version 3.9.3 in my React application. I want to add a background image on a dialog. I'm using the Dialog component for it but I'm unable to add a background image on the whole dialog.
For example:
<Dialog
fullScreen={fullScreen}
open={this.props.open}
onClose={this.handleClose}
aria-labelledby='responsive-dialog-title'
>
<DialogTitle
id='customized-dialog-title'
onClose={this.handleClose}
noCloseButtonNeeded={noCloseButtonNeeded}
>
{/* some jsx */}
</DialogTitle>
<DialogContent>
{children}
</DialogContent>
</Dialog>
I have tried to add an image using classes and custom CSS but I'm unable to do it.
Can anyone help me out to add it? Thanks in advance :)
First, you can define the background image in a styles object that can be used with the withStyles higher-order component to apply it to the dialog:
const styles = {
dialog: {
backgroundImage: "url(https://i.imgur.com/HeGEEbu.jpg)"
}
};
When you pass this object to the withStyles HOC, it will supply your component with a classes prop containing properties with the same names as the properties on styles that you've defined.
Next, you can apply this class to the Dialog by taking advantage of the classes prop (the specific overrides made available for the Dialog component are detailed here):
<Dialog ... classes={{paper: classes.dialog}}>
{/* ... */}
</Dialog>
This is telling material-ui to merge the styles you have defined in styles.dialog with the default styles on the Paper element that is used with the Dialog.
You'll need to make sure that you're wrapping your component in the withStyles HoC. If you have a class component, it will look something like this:
export default withStyles(styles)(DialogWithBackgroundImage);
For functional components, it would look something like:
export default withStyles(styles)(({open, children, classes}) => (<Dialog ...></Dialog>))
Here's a working example that ties everything together: https://codesandbox.io/embed/q3zy4r2np4

Applying CSS stylesheet only to active component

I'm working on a ReactJS app that has a header at the top, a menu on the left, and the "frame" in the middle is where routes and their corresponding components are loaded. I want to be able to apply a CSS stylesheet to specific components only when they are loaded. I also don't want them applied all the time or to the top header or left menu.
My expectation was that adding import 'custom.css'; to a specific component would only apply the stylesheet's styles to that component and it's children when the route is active. Instead, it applies it to the entire page even when the route/component are not loaded.
I understand that an alternative approach is styled components, but, for my use-case, a design company is supplying a stylesheet (which should remain unchanged) that we need to consume only for the sub-module I'm working on and I don't want its styles to affect the rest of the app.
How can I have a stylesheet only applied to my active route/component?
Use simple CSS technique. Suppose you have two components with different css files (say about.css and contact.css). Now consider your both CSS file have one common class with different style properties, like:
about.css
.container{
max-width: 400px;
}
contact.css
.container{
max-width: 500px;
}
Yes in ReactJS both the CSS files will load at the same time and will override any one of the style. so to solve this problem add class to differentiate this styles, like:
about.css
.about-component.container{
max-width: 400px;
}
contact.css
.contact-component.container{
max-width: 500px;
}
If you want apply only when the component is mounted, you can use the lifecycle.
The follow example is based in the idea you are using sass, React, sass-node and have the loaders into webpack.
<pre>
import React from 'react';
import './styles.scss';
class MyComponent {
constructor(props) {
super(props);
this.state = { className: '' }
}
componentDidMount() {
this.setState({
className: 'myOwnClass'
});
}
render(){
return (
<div className={this.state.className}>This is a example</div>
);
}
}
export default myComponent;
</pre>
To be able to only call that specific CSS when you need it you can use CSS Modules. You may need to update your version of react.
When saving your CSS file save it with a ".module.css" eg. "styles.module.css". The CSS in these files can only be used and accessed by hte components where are they are imported. As stated in a tutorial from W3Schools.
Let's say this is your CSS code in styles.module.css:
.container {
color: white;
}
.cont-child {
background-color: red;
}
Then in your JS file you can import the CSS file like this if the JS and CSS files are in the same directory. Make sure you point to the correct path.
import styles from './styles.module.css'
Then in your HTML section you can use it like this:
class Home extends React.Component {
render() {
return(
<main className={ styles.container } >
<div className={ styles["cont-child"]} >
Some div text about something...
</div>
</main>
);
}
}
I currently use both ways to access the selectors, since the styles variable acts like an object. I placed both of them here because the second option is capable of fetching selectors named like "btn-active". Which comes in handy in some situations. Camelcasing is considered cleaner though.
Please note: I originally posted this answer as a reply to a similar question here React CSS - how to apply CSS to specific pages only
I want to be able to apply a CSS stylesheet to specific components
only when they are loaded.
Why not apply the styles inline via React.js?
Step 1. Create the style object for the component:
var componentOneStyle = {
color: 'white',
backgroundColor: 'red'
};
Step 2. Populate the component's style attribute with the style object:
ReactDOM.render(<div style={componentOneStyle}>This is Component One</div>, mountNode);

material-ui change the height of the drawer

I'm using react and material-ui in my project and I have come across a simple issue that I just dont't know how to solve.
I want to create a drawer and set its height in a way that when it will open, it wont open over the app bar.
There is no parameter in the Drawer component for the height, I also tried to override its style and setting up the height on the style object like this :
<Drawer style={{height:'90%'}} />
But it didn't work.
The only way I can think of, is editing the code of the Drawer component, but ofcourse I want to avoid that.
Any idea on how I can define the height?
Here you go:
<Drawer open={this.state.open} containerStyle={{height: 'calc(100% - 64px)', top: 64}}>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
containerStyle is prohibited in version 1.0 and above
So you need to use props classes instead
Here is an example to this nontrivial case
import {withStyles, createStyleSheet} from 'material-ui/styles'
const styleSheet = createStyleSheet({
paper: {
height: 'calc(100% - 64px)',
top: 64
}
})
class CustomDrawer extends Component {
...
render () {
const classes = this.props.classes
return (
<Drawer
classes={{paper: classes.paper}}
>
...
)
}
CustomDrawer.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styleSheet)(CustomDrawer)

Resources