Making customizable css properties with React and CSS Modules/PostCSS - css

I'm trying to make a library of react components that's external to an application. This will be an npm module, loaded with Webpack. I'm styling the component using CSS Modules, and I'm trying to see how to make some of its properties customizable. For instace, background color.
I would like to use css variables for this to have for instance this syntax in the css file:
.myClass {
backgrond-color: var(--backgroundColor, red);
}
Where --backgroundColor is a variable I can set, and red is the default. My question is, is there a way I can pass variables to the .css file when loading it from the .jsx file? So I could pass a variables object to the component, which then would influence how it loads it style? Could I use PostCSS for this?
Thanks.
PS: I know this could be solved by using inline JS styles, but I'm trying to give CSS a shot first.

You cannot inject javascript into a css file and PostCSS can only transform your css files, but not inject/replace variables.
However, one way of doing this would be to create .scss (sass) files with default variables, e.g. $background-color: red; One could then import your module and .scss files to their .scss files and overwrite any variables like $background-color with their own variables if they wish.

I'm not sure I understood you right, but here what I'm thinking of:
When you are requiring .css file with Webpack it adds this css as a string to the <head> element of the page behind the scene.
Why don't you do what Webpack does using your own function, like so.
Your module:
import $ from 'jquery';
/* this function builds string like this:
:root {
--bg: green;
--fontSize: 25px;
}
from the options object like this:
{
bg: 'green',
fontSize: '25px'
}
*/
function buildCssString(options) {
let str = ':root {\n'
for(let key in options) {
str += '\t--' + key + ': ' + options[key] + ';\n'
}
str += '}';
return str;
}
/* this function adds css string to head element */
export const configureCssVariables = function(options) {
$(function() {
var $head = $('head'),
$style = $('<style></style>');
$style.text(buildCssString(options));
$style.appendTo($head)
});
}
Usage:
import {configureCssVariables} from './your-module';
configureCssVariables({
bg: 'green',
fontSize: '25px'
});
And your css is simple
/* default variables that will be overwritten
by calling configureCssVariables()
*/
:root {
--bg: yellow;
--fontSize: 16px;
}
.myClass {
backgrond-color: var(--bg);
font-size: var(--fontSize);
}

It can be acheived by adding PostCSS and the postcss-custom-properties plugin in your pipeline. It has a variables option which will inject JS defined variables (properties) to any file being processed.
This eliminate the need to #import anything inside every CSS module file.
const theme = {
'color-primary': 'green',
'color-secondary': 'blue',
'color-danger': 'red',
'color-gray': '#ccc',
};
require('postcss-custom-properties')({
variables: theme,
});
See how to use it with babel-plugin-css-modules-transform https://github.com/pascalduez/react-module-boilerplate/blob/master/src/theme/index.js and https://github.com/pascalduez/react-module-boilerplate/blob/master/.babelrc#L21 but that works with Webpack as well.

I actually found a solution that already does this and takes advantage of the latest standardized JavaScript features
https://github.com/styled-components/styled-components
It may just be what I was looking for.

Related

How to get a Tailwind theme color's actual value from resolveConfig?

I'm using Next.js and Tailwind. I have a theme defined as a set of CSS vars. How do I access the actual value that results from Tailwind theme colors defined with alphavalue?
When I try to access it via resolveConfig I just get the string rgb(var(--color-one) / <alpha-value>), but the result I would like to get is rgb(255, 0, 0) or rgba(255, 0, 0, 1).
I need to access it for use inside a TSX component file which has an SVG for two different stopColors in the same gradient. Eventually there will be multiple themes, so its best if I can fetch it since the gradient should change with the theme.
global.css
#layer base {
:root {
--color-one: 255 0 0;
}
}
tailwind.config.js
module.exports = {
content: ['./pages/**/*.tsx', './components/**/*.tsx'],
theme: {
extend: {
textColor: {
skin: {
one: 'rgb(var(--color-one) / <alpha-value>)',
}
},
}
}
My TSX component
const { one } = (twFullConfig?.theme?.textColor as any)?.skin || {}
console.log(one)
This logs rgb(var(--color-one) / <alpha-value>) to the console.
How can I get the actual value of the color, rgb(255,0,0), instead of the Tailwind string?
I came up with what feels like an extremely hacky way of doing this: putting classes on a div that is hidden/has no height/width, and then putting a ref on that div, and using window.getComputedStyles to access the styles on the div. If there is a better way to do this I'd like to know because this seems like an ugly solution.

Generating CSS from Angular SCSS?

Is there a way to see the CSS that corresponds to the SCSS when we are using SCSS as the preprocessor for Angular?
There is an answer here:
When using angular with scss, how can I see the translated css?
And it mentions using the --extract-css option, however when I try that it looks like it has been deprecated:
sandbox/project $ ng build --extract-css
Unknown option: '--extract-css'
Thoughts?
Styles in Angular Build Files
In your build files the styles of components will actually be compiled in the main.js file. You can find it in the network tab of your browsers developertools.
You will also see a file called styles.css, but this will only contain your global styles. This is because of Angulars view-encapsulation of styles per component. The behavior of angular may change if you change the view-encapsulation strategy as explained here to:
Emulated (default)
ShadowDOM
None
I would not recommend doing that though.
However, if you want you can compile your sass files into css using the command line tool you can install as explained on the official sass website.
You can also just use online sass converters like thisone.
If you are just interested in the global styles here's a reference to How you can switch the format from scss to css in your browser.
Example
app.component.scss
p {
background-color: orange;
}
styles.scss
#import 'default';
p {
color: red;
&:hover {
color: blue;
}
}
default.scss
h1 {
color: teal;
}
Result in Build
styles.css:
h1 {
color: teal;
}
p {
color: red;
}
p:hover {
color: blue;
}
main.js:
AppComponent.ɵfac = function AppComponent_Factory(t) { return new (t || AppComponent)(); };
AppComponent.ɵcmp = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdefineComponent"]({ type: AppComponent, selectors: [["app-root"]], decls: 1, vars: 0, template: function AppComponent_Template(rf, ctx) { if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelement"](0, "lib-my-lib");
} }, directives: [my_lib__WEBPACK_IMPORTED_MODULE_1__.MyLibComponent], styles: ["p[_ngcontent-%COMP%] {\n background-color: orange;\n}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5jb21wb25lbnQuc2NzcyIsIi4uXFwuLlxcLi5cXC4uXFxBbmd1bGFyJTIwUHJvamVjdHNcXGxpYi1leGFtcGxlXFxzcmNcXGFwcFxcYXBwLmNvbXBvbmVudC5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0Usd0JBQUE7QUNDRiIsImZpbGUiOiJhcHAuY29tcG9uZW50LnNjc3MiLCJzb3VyY2VzQ29udGVudCI6WyJwIHtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiBvcmFuZ2U7XHJcbn1cclxuIiwicCB7XG4gIGJhY2tncm91bmQtY29sb3I6IG9yYW5nZTtcbn0iXX0= */"] });
*Note the orange background-color in the last line.
This is just a complement to the accepted answer. I wrote it up in a medium article, as it was not immediately obvious as styles.scss is opened first when selecting elements in Chrome Developer Tooling, but styles.css is in the tab right next to it.
https://fireflysemantics.medium.com/viewing-generated-global-css-for-angular-sass-projects-857a6887ff0b

HIghlighting row during runtime

I am trying to highlight a row based user input. I am using Angular 5, with ag-grid-angular 19.1.2. Setting the style with gridOptions.getRowStyle changes the background, but I would rather use scss classes if possible. The function setHighlight() is called in the html file through (change)=setHighlight()
setHighlight() {
const nextChronoId = this.getNextChronoDateId();
// this.highlightWithStyle(nextChronoId); // Working solution
this.highlightWithClass(nextChronoId);
const row = this.gridApi.getDisplayedRowAtIndex(nextChronoId);
this.gridApi.redrawRows({ rowNodes: [row]})
}
Function definitions:
highlightWithStyle(id: number) {
this.gridApi.gridCore.gridOptions.getRowStyle = function(params) {
if (params.data.Id === id) {
return { background: 'green' }
}
}
}
highlightWithClass(id: number) {
this.gridApi.gridCore.gridOptions.getRowClass = function(params) {
if (params.data.Id === id) {
return 'highlighted'
}
}
}
My scss class:
/deep/ .ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last, .highlighted{
background-color: green;
}
My issue
Using getRowClass does not apply my highlighted class correctly to the rowNode. After reading (and trying) this, I think that my custom scss class overwritten by the ag-classes. The same problem occurs when using rowClassRules.
Question
How can I make Angular 5 and ag-grid work together in setting my custom scss class correctly?
Stepping with the debugger shows the class is picked up and appended to the native ag-grid classes.
In rowComp.js:
Addition, screen dump from dev tools:
angular's ViewEncapsulationis the culprit here.
First be aware that all shadow piercing selectors like /deep/ or ::ng-deep are or will be deprecated.
this leaves, to my knowledge, two options.
use ViewEncapsulation.None
add your highlighted class to the global stylesheet
setting ViewEncapsulation.None brings its own possible problems:
All components styles would become globally available styles.
I would advise to go with option two.
this answers sums it up pretty well.
Additionally:
.ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last
this selector will never match anything, you should change it to
.ag-theme-balham .ag-row.ag-row-no-focus.ag-row-even.ag-row-level0.ag-row-last
every class after ag-theme-balham exists on the same element.
with the selector you wrote, you would denote a hierarchy.
Hope this helps

How to make React CSS import component-scoped?

I have several components which have the following CSS/component structure
About/style.css
.AboutContainer {
# Some style
}
p > code {
# Some style
}
And I import the CSS in the componet as follows
About/index.js
import './style.css';
export default class About extends Component {
render() {
# Return some component
}
}
However, the CSS is imported in the <header> section and stays global-scope.
I was expecting CSS to be:
Component-scoped in a way that the style is only applied to things that are only rendered within this component.
Style for this component would disappear if the component is unmounted.
However, when inspecting from the browser, the styles are specified at the <header> section and gets applied to all the components
<header>
// Stuff
<style type="text/css">style for component About</style>
<style type="text/css">style for component B</style>
<style type="text/css">style for component C</style>
// Stuff
</header>
How do I import CSS to be component-scoped? It seems like I'm understanding CSS import in React ES6 incorrectly.
I was following this tutorial
Edit
Answer by Brett is correct. However, my problem turns out to be somewhere else. I created my app using create-react-app which basically simplifies setups required to do React. It include WebPack, Babel and other things to get started. The default WebPack config that it uses did not set module option for the css-loader so it defaulted to false, and as a result the local-scoping was not enabled.
Just for additional info, it seems like create-react-app does not have straightforward way to customize WebPack config, but there seem to be numerous how-to workarounds on the web.
It sounds like CSS Modules, or many of the other CSS-in-JS packages, does what you want. Others include Emotion (my current favorite), Styled Components, or many of the packages here.
A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. All URLs (url(...)) and #imports are in module request format (./xxx and ../xxx means relative, xxx and xxx/yyy means in modules folder, i. e. in node_modules).
Here's a quick example:
Let's say we have a React component like:
import React from 'react';
import styles from './styles/button.css';
class Button extends React.Component {
render() {
return (
<button className={styles.button}>
Click Me
</button>
);
}
}
export default Button;
and some CSS in ./styles/button.css of:
.button {
border-radius: 3px;
background-color: green;
color: white;
}
After CSS Modules performs it's magic the generated CSS will be something like:
.button_3GjDE {
border-radius: 3px;
background-color: green;
color: white;
}
where the _3DjDE is a randomly generated hash - giving the CSS class a unique name.
An Alternative
A simpler alternative would be to avoid using generic selectors (like p, code, etc) and adopt a class-based naming convention for components and elements. Even a convention like BEM would help in preventing the conflicts you're encountering.
Applying this to your example, you might go with:
.aboutContainer {
# Some style
}
.aboutContainer__code {
# Some style
}
Essentially all elements you need to style would receive a unique classname.
Maybe react-scoped-css will help. Btw, I'm the author of this lib, if you find anything broken or simply want to improve it, you can always raise an issue or send a pr.
Because you mentioned you used create-react-app, the solution here is quite easy change just style.css to style.module.css, it will look like this:
import styles from "./style.module.css"
<button className={styles.button}>blabla</button>
More info on this article:
https://blog.bitsrc.io/how-to-use-sass-and-css-modules-with-create-react-app-83fa8b805e5e
You can use SASS (.scss) to imitate scoped CSS.
Say you need to use bootstrap in only one component (to avoid conflicts). Wrap the component in <div className='use-bootstrap'> and then created a .scss file like so:
.use-bootstrap {
// Paste bootstrap.min.css here
}
Use this file naming convention [name].module.css
and see documentation: https://create-react-app.dev/docs/adding-a-sass-stylesheet
JSX File
import React from 'react';
import styles from './index.module.scss';
const MyPage = () => {
return (
<div className={styles}>
<h1>My Page</h1>
</div>
);
};
export default MyPage;
Styles File
h1 {
color: #f3f3f3;
font-family: "Cambria";
font-weight: normal;
font-size: 2rem;
}
For me, the simple solution (without using: Css-modules or css-in-js) is to add a suffix to your class selectors like this:
className="btn__suffix"
if your component is named: FileUpload.tsx so your __suffix would be __fu, i took the first character of each word (here: File and Upload).
the end result would be:
import './style.css';
export default class About extends Component {
render() {
Return (
<div className="container__fu">
...
</div>
)
}
}
And in the Css part, your file would be:
.container__fu {
...
}
I created a rollup plugin to have scoped scss/css within a vite react project with regular import, you can check it out if it can solve your issue!
https://www.npmjs.com/package/rollup-plugin-react-scoped-css

vuejs multiple themes with scoped css in single file vue components

So let's assume we have a variables scss file like the following
$darken-percentage: 15%;
$primary-color: #2aaae1;
$dim-primary-color: darken($primary-color, $darken-percentage);
$complementary-color: #faaf48;
$dim-complementary-color: darken($complementary-color, $darken-percentage);
$background-color: #1d1f29;
$middleground-color: #313444;
$dim-middleground-color: darken($middleground-color, $darken-percentage);
$light-middleground-color: lighten($middleground-color, $darken-percentage);
In the main.js we could use #import 'variables.scss'
what if I have two themes and I want to change on user action I could have 2 variables files and conditionally import either based on user actions but what about single file vue components
like
<style scoped lang="scss">
#import '../../theme/_variables.scss';
.bm-upload{
background: $primary-color;
}
</style>
Then the import will not work so is there anyway for me to have global variables files and use it in other files without re importing it
The easiest is to import both style files and change a parent element's class. The browser will immediately apply the styles when the class changes.
You can, for example, change the body's class with classList and have all styles depend on that.
created(){
let body = document.getElementsByTagName('body')[0];
body.classList.add("theme1");
},
methods: {
changeTheme(){
let body = document.getElementsByTagName('body')[0];
body.classList.remove("theme1");
body.classList.add("theme2");
}
}
Style should inherit from the theme class name, like this:
.theme1 {
.exampleClass {
padding: 0;
}
}

Resources