Pass a stylesheet to a Lit-Element Web component - javascript

Is it possible to pass a stylesheet to a web component using lit-element?
I mean in a similiar way to the one used to set up the properties of the web component.
For example, I have this web component and I want to pass it, from the outside, a stylesheet that has to be pushed to the array returned inside "static get styles" after "SharedStyles".
class MyComponent extends LitElement {
static get properties() {
return {
name: { type: String }
};
}
static get styles () {
return [
super.styles,
SharedStyles,
css`
`
];
}
}
If I want to set the property "name" i do:
<my-component .name="${"Fred"}"></my-component>
Is there a way to pass a stylesheet to my-component?

Yes you can. But you need to export css file first.
step 1: create styles.js
import { css } from 'lit-element';
export const styleSheet = css`
:host{
// global css
}
.cssProp1{
// your properties
}
.cssProp2{
// your properties
}
`
Step 2: import css in to your component
import { styleSheet } from styles.js;
static get styles(){
return styleSheet;
}
Step 3: render html:
render(){
return html`
<div class="cssProp1"></div>
<div class="cssProp2"></div>
`
}
This will do the job

Related

Cannot override CSS Module class style with more specific one

I'm having a problem applying a CSS Module to a React Component (in an app created with 'create-react-app' and using webpack). I have an element that has a 'className' and an 'id' attribute. The 'className' style get applied, but when I attempt to 'override' it with a more specific 'id' style, it doesn't take. My Use Case is that the more-specific style override is in a separate file, but the problem exists with the two styles in the same CSS file.
Here's my component:
import React from 'react';
import styles from './styles.module.css'
class QuestionTag extends React.Component {
render() {
let c1 = "qumultiline";
return (
<>
<div className={`${styles[c1]}`} id={`${c1}`}>
Hello there
</div>
</>
);
}
}
export default QuestionTag;
This is how the element gets rendered:
<div class="styles_qumultiline__iXUv1" id="qumultiline">Hello there</div>
Here's styles.module.css:
.qumultiline {
background-color: #006699;
color: white;
}
div#qumultiline {
background-color: red;
color: red;
}
Is there something special I need to do to be able to override the 'className'?

How to add custom class to body element for some routes - nexjts

I want to add my custom class to some pages. for example
all pages must be this class fixed-header exception this routes:
/cart/step-1
/login
this class add or remove to body element.
<body className="bg-gray fixed-header"
but I don't know how I can handle this scenario?
The easiest and quickest solution. Add this code into each component where you want different classes on the <body>.
useEffect( () => { document.querySelector("body").classList.add("bg-gray fixed-header") } );
Create a custom _document.js and _app.js in your pages directory.
A small util to check if class exists on body (to avoid duplicate class, thanks to suggestion by #juliomalves):
// ./utils/hasClasses
const hasClasses = () =>
document.body.classList.contains("bg-gray") &&
document.body.classList.contains("fixed-header");
export default hasClasses;
Server Side rendering
In _document.js, use the __NEXT_DATA__ prop to get access to the current page,
check if the page is in your allowed routes, and add the classes to body.
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
// Add more routes here if you want to use the same classes
allowedRoutes = ["/login", "/cart/step-1"];
getColor() {
const { page } = this.props?.__NEXT_DATA__;
if (this.allowedRoutes.includes(page))
return "bg-gray fixed-header";
return "";
}
render() {
return (
<Html>
<Head />
<body className={this.getColor()}>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
The above code always runs on the server. Classes doesn't get appended to the body on client-side navigation.
Client side rendering
To fix the above issue, use the same logic in _app.js in a useEffect, so that it adds the correct class when rendering on the client.
import { useEffect } from "react";
import { useRouter } from "next/router";
import "../styles.css";
import hasClasses from "./utils/hasClasses";
function MyApp({ Component, pageProps }) {
const { pathname: page } = useRouter();
const allowedRoutes = ["/login", "/cart/step-1"];
useEffect(() => {
if (!hasClasses() && allowedRoutes.includes(page))
document.body.className += "bg-gray fixed-header";
else if (hasClasses()) {
// Don't want the styles in other pages, remove if exists.
// Up to your implementation on how you want to handle this.
document.body.classList.remove("bg-gray");
document.body.classList.remove("fixed-header");
}
});
return <Component {...pageProps} />;
}
export default MyApp;
This solves the issue where client side navigation correctly applies the class on the allowed route. The code in _document.js makes sure that when a page is server rendered, it is sent downstream with the correct class applied so that it doesn't cause a flash of incorrect styles on the client.

Onepage with Gatsby JS and Contentful, how to import my first simple string

I am trying to useStatic Query and GraphQL to get a simple title from
Contentful, pass it to state and then show in the render. I cant make it work. I am attaching an image showing my current setup and errors.
Possible problems: 1. the query returns an array, and I need to change it into a string, or access 0 element, the first one, because my content type is just one, as it is a onepage.
Placing of the query in the component, I am not sure if it can be in the constructor of an component
For comparison: in the screen from my file you can see a variable name showing Josh Perez, when I uncomment it and add it to this.state = { dataTest: name}, then in RENDER: this.state.dataTest returns the name Josh Perez well, so passing a variable to state works, but passing a string from graphql query is not possible for me...
I have a limitation which is that I need to create my page component with a class, because of the fact that in the Component did mount I am placing some JQuery, which works well for me.
THIS IS MY TEST CODE
1. In Constructor
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
const name = 'Josh Perez';
this.state = { dataTest: name };
}
In render
{this.state.dataTest}
This works, the variable name is passed to state and shown in render.
However, I want to show in this way a simple text string from Contentful. So I am trying code like this (error message is shown in the screens):
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
//const name = 'Josh Perez';
const data = useStaticQuery(graphql`
query {
allContentfulHomepage (limit: 1) {
edges {
node {
section1Title
}
}
}
}
`)
this.state = { dataTest: data };
It turns out, that the below suggested solution works. I am putting below
my attempt at callingfurther content. It does not work. It displays the following error "Cannot read property 'map' of undefined". I would be very grateful for a suggestion how to improve it, how to make it work.
export default class Test extends Component {
state = {
dataTest: this.props.data.test.edges.map(({ node: test }) =>
test.section1Title),
dataTest2: this.props.data.test.edges.map(({ node: test }) =>
test.section2Lead),
dataTest3: this.props.data.test.edges.map(({ node: test }) =>
test.section1Text.json)
}
render() {
return <div>
<h1>{this.state.dataTest}</h1>
<h1>{this.state.dataTest2}</h1>
{documentToReactComponents(this.state.dataTest3)}
</div>
}
}
export const query = graphql`
{
test:allContentfulHomepage(limit: 1) {
edges {
node {
section1Title
section2Lead
section1Text {
json
}
}
}
}
}
`
If you're writing a page component as a class, you don't need to use the UseStaticQuery, you can use the simple PageQuery for this purpose.
To loop through arrays, the map() method works as well.
UPDATE
import React, { Component } from 'react';
import { graphql } from 'gatsby';
export default class Test extends Component {
render() {
const { edges } = this.props.data.test;
return (
<div>
{edges.map(({ node: itemFromContentful }) => (
<h1>{itemFromContentful.section1Title}</h1>
<h1>{itemFromContentful.section2Lead}</h1>
{documentToReactComponents(section1Text.json)}
))}
</div>
);
}
}
export const query = graphql`
{
test:allContentfulHomePage(limit: 1) {
edges {
node {
section1Title
}
}
}
}
`
Whats happening:
The GraphQL query you're using is bringing the data you want from the Contentful;
The React Stateful Component (class Test) is receiving all the data available from the query as a prop;
We're accessing this data on the render() method using the destructing assignment;
we're accessing the data nodes through the map method (the one I suggested you to take a look;
The curly braces into the JSX allows you to use JS to manipulate what you want - In this case, to render the information.

Typescript type definition: TS2604: JSX element type 'something' does not have any construct or call signatures

I have an abstract class in "File1":
// File1.tsx
import * as React from 'react';
export interface IProps {
prop1: string;
prop2: number;
}
export abstract class Something extends React.Component<IProps> {
public typeName: string;
}
then, in other file (File2) i define infinite classes extending from abstract class Something:
// File2.tsx
import { Something } from './File1';
export class Something1 extends Something {
public typeName: string = 'Type1';
public render() {
return <div>Something 1</div>
}
}
export class Something2 extends Something {
public typeName: string = 'Type2';
public render() {
return <div>Something 2</div>
}
}
Now, here is my problem:
In a 3rd File, i import the classes defined before (Something1 or Something2) and then i passing this class to 2 different components: ReadProperty and RenderComponent. In the first component i need accessing to the property typeName of the class and do some stuff and in the second file i need render that class:
// File3.tsx
import { Something } from './File1';
import { Something1, Something2 } from './File2';
interface IReadProperty {
something: Something;
};
const ReadProperty: React.SFC<IReadProperty> = ({ something }) => {
// here i can access to property typeName. No problem here.
something.typeName // Type1 | Type2 | ... Infinite
...do some stuff
}
interface IRenderComponent {
something: Something;
}
const RenderComponent: React.SFC<IRenderComponent> = ({ something }) => {
// i need do some stuff here before render "something" component
// here i get an error when i try render the component
return <something />;
}
const Main: React.SFC = () => {
return (
<div>
<ReadProperty something={Something1} />
<RenderComponent something={Something1} />
</div>
);
}
but when i try to render the component in RenderComponent, i get the follow error: TS2604: JSX element type 'something' does not have any construct or call signatures.
What is wrong here? i defined type 'something' as abstract Class Something because i can define infinite Classes that extend from Something, so i can't define using: something: Something1 | Something2 | Something50 ...;
here is an example that i trying to do: https://codesandbox.io/s/18yzvkx8jj
The issue is that in your definition of IRenderComponent, you're saying that something is an instance of Something, whereas what you want is a constructor type. One other thing is that React generally complains when you try to instantiate components with lowercase names. Try this:
interface IRenderComponent {
Something: new (props: IProps) => Something1;
};
const RenderComponent: React.SFC<IRenderComponent> = ({ Something }) => {
return <Something prop1="foo" prop2={3} />;
}
For IReadProperty, it looks like you do want an instance of Something (since you want to access typeName, which is an instance property). However, you can't actually pass an instantiated component:
<ReadProperty something={<Something1 prop1="" prop2={3} />;} /> //Error
This is because <Something1 ... /> isn't actually an instance of Something1 - it's an instance of JSX.Element. You could pass an instance of Something1, like this:
<ReadProperty something={new Something1({prop1: "", prop2: 3})} />
But I imagine that's not what you want, since you can't actually use the resulting instance in rendering.
The best way to address the IReadProperty issue depends on what you're actually trying to accomplish. In general, React favors composition over inheritance and reflection, so it might be easier to consider how to achieve your goal by starting with base components and composing them, or using higher-order components.
Make sure that you have jsx:react in tsconfig.json
Make sure that you have consistent version of react react-dom #types/react and #types/react-dom package versions.

Next.js custom class on body using _document.js

I'm using the example here
https://github.com/zeit/next.js#custom-document
and I want to change the custom_class on the body tag
I have tried passing props on calling components but nothing works. I want to add a 'dark' class based on some condition but I have no idea how to change this.
EDIT:
I'm not sure this is possible. After getting help from the nextjs slack channel I was told
"Pages get rendered client side with next/link
And body is not touched after server side rendering"
I'm going to try and wrap things in another tag that I generate and try and change that.
The cleanest solution I found is not declarative, but it works well:
import Head from "next/head"
class HeadElement extends React.Component {
componentDidMount() {
const {bodyClass} = this.props
document.querySelector("body").classList.add(bodyClass || "light")
}
render() {
return <Head>
{/* Whatever other stuff you're using in Head */}
</Head>
}
}
export default HeadElement
With this Head component you would pass in "dark" or "light" (following the question's example for light/dark themes) as the bodyClass prop from the page.
As of current Next (10) and React (17) versions, If you'd like to change the body class from a page, you can can do it like this:
// only example: maybe you'll want some logic before,
// and maybe pass a variable to classList.add()
useEffect( () => { document.querySelector("body").classList.add("home") } );
Please note that useEffect is a Hook, so it can be used only in modern function components, not class ones.
The useEffect Hook can be used instead of the 'old' componentDidMount LifeCycle.
https://reactjs.org/docs/hooks-effect.html
https://reactjs.org/docs/hooks-overview.html
https://reactjs.org/docs/components-and-props.html
The only way to directly access the body tag on Next js is via the _document.js file but this file is rendered server side as stated in the Documentation.
The work around I suggest is to access the body tag from the component directly. Example:
const handleClick = (e) => {
document.querySelector('body').classList.toggle('dark')
}
<div onClick={handleClick}>Toggle</div>
The solution I came up with for my situation where I don't need a lot of unique body classes was to create a component called BodyClass.js and import that component into my Layout.js component.
BodyClass.js
import { Component } from 'react';
class BodyClass extends Component {
componentDidMount() {
if (window.location.pathname == '/') {
this.setBodyClass('home');
} else if (window.location.pathname == '/locations') {
this.setBodyClass('locations');
}
}
setBodyClass(className) {
// remove other classes
document.body.className ='';
// assign new class
document.body.classList.add(className);
}
render() {
return null;
}
}
export default BodyClass;
Layout.js
import Header from './Header';
import Footer from './Footer';
import BodyClass from '../BodyClass';
const Layout = ({ children }) => {
return (
<React.Fragment>
<BodyClass />
<Header />
{children}
<Footer />
</React.Fragment>
)
}
export default Layout;
Unfortunately, next/head does not allow specifying body class like React Helmet does:
<Helmet>
<body className="foo"/>
</Helmet>
Luckily, you can use this.props.__NEXT_DATA__.props.pageProps inside _document.js to get access to the page props and use them to to set the class on the <body> element:
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
const pageProps = this.props?.__NEXT_DATA__?.props?.pageProps;
console.log('pageProps => ', pageProps);
return (
<Html>
<Head />
<body className={pageProps.bodyClassName}>
<Main />
<NextScript />
</body>
</Html>
);
}
}
More info here.
Directly, it's not possible. But with another way, you can use framework like tailwind and insert the class directly in your css.
Here an example using tailwind:
.dark {
background-color: black;
color: white;
}
body {
#apply dark
}
It's not exactly the answer you are looking for, but I was able to accomplish the same thing functionally by passing a prop through the component which then updates a class on a top-level element that wraps everything in my app and sits just under the . This way each page can have it's own unique class and operates the same way I'd use a body class.
Here is where I pass the prop through
<Layout page_title={meta_title} page_desc={meta_desc} main_class={'contact_page'}>
And here is where I use it in the Layout component:
<main className={props.main_class}>
<Header page_title={props.page_title} page_desc={props.page_desc} />
{props.children}
<Footer />
</main>
This is my CSS only solution:
(I first looked at https://stackoverflow.com/a/66358460/729221 but that felt too complex for changing a bit of styling.)
(This uses Tailwind CSS, but does not need to.)
index.css:
body:has(#__next .set-bg-indigo-50-on-body) {
#apply bg-indigo-50;
}
layout.tsx:
//…
return (
<div className="set-bg-indigo-50-on-body">{children}</div>
)
This will work, as long as that div is a direct parent of <div id="__next">. Otherwise you need to update the css :has-rule.

Categories

Resources