Web components nested slots - web-component

Is there a better way to pass slots to deeply nested components?
index.html
<outer-comp>
<span slot=foo>Lorem ipsum</span>
</outer-comp>
outer component:
<inner-comp>
<slot name=foo slot=foo></slot>
</inner-comp>
inner component:
<slot name=foo></slot>

That works just fine :)
class ElOuter extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<p>- outer start</p>
<el-inner>
<slot name="heading" slot="header"></slot>
</el-inner>
<slot></slot>
<p>- outer stop</p>
`;
}
}
customElements.define('el-outer', ElOuter);
class ElInner extends ElOuter {
connectedCallback() {
this.shadowRoot.innerHTML = `
<p>-- inner start</p>
<slot name="header"></slot>
<p>-- inner stop</p>
`;
}
}
customElements.define('el-inner', ElInner);
<el-outer>
<h2 slot="heading">hey</h2>
<p>I will be in the default content</p>
</el-outer>

Related

Is there a way to keep linked stylesheets localized to single components?

I have a component that relies on external stylesheets. I'm bringing the stylesheet into the component like this:
Child component
export default class Child extends Component {
render() {
return (
<div>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />
...my code here...
</div>
);
}
}
But what's happening is this is forcing those styles onto the parent component as well.
Parent Component
export default class Parent extends Component {
render() {
return (
<div>
...code here...
<Child />
... more code here...
</div>
);
}
}
Is anyone aware of a way that I can keep that stylesheet link localized to just that child component so the styles aren't applied to the parent component as well?
Edit 2
Currently trying the shadow dom route, trying to pass down some children. Getting an error after the initial render saying Cannot read properties of undefined (reading 'children'). It does render the this.props.children initially...
import React, { Component } from 'react';
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
${this.props.children}
`;
}
};
export default class Child extends Component {
render() {
return (
<div>
<script>
{!customElements.get("my-component") && customElements.define('my-component', MyComponent)}
</script>
<my-component>
<h1>Hello from shadow</h1>
</my-component>
<h1>Hello</h1>
</div>
);
}
}
You can try CSS Modules. Add :local(.className) to the class you want to use in your code which is in the font-awesome-min.css file. Then import the styles to your component. For example import styles from './font-awesome-min.css' then use the module in your code. The styles will only apply to specific element and won't affect other elements in the document. So let's say you have a class called .usericon in your css you do this in the css file.
CSS
:local(.usericon){
fill: red;
}
React Code
import styles from './font-awesome-min.css'
export default function Profile(){
return (
<i className={styles.usericon}>User Icon</i>
)
}
One way to truly isolate your CSS is with Web Components. Web Components are a browser API that allows defining custom elements with their own "shadow DOM". If a style is defined inside the shadow DOM, it is truly sandboxed with no styles going in or out. You can use whatever selectors you like:
class FancyBox extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.fancy-box {
border: solid 3px darkblue;
background: dodgerblue;
padding: 10px;
color: white;
font: 16px sans-serif;
}
</style>
<div class="fancy-box">
<slot></slot>
</div>
`;
}
}
customElements.define('fancy-box', FancyBox);
.fancy-box {
border: dashed 3px darkred !important;
background: crimson !important;
padding: 10px !important;
color: white !important;
font: 16px sans-serif;
}
<fancy-box>Safe in my shadow DOM</fancy-box>
<div class="fancy-box">I am affected by outside stylesheets</div>
Note the use of <slot></slot>. This is a placeholder for child elements of the component.
If I wanted to use this custom element from React, it needs to be defined separately so it only runs once.
class FancyBox extends HTMLElement { /*...*/ };
customElements.define('fancy-box', FancyBox);
class ReactFancyBox extends React.Component {
constructor() {
super();
this.state = { value: 'hello world!' }
}
handleChange(e) {
this.setState({ value: e.currentTarget.value });
}
render() {
return (
<div>
<fancy-box>
<strong>{this.state.value}</strong>
</fancy-box>
<input value={this.state.value} onChange={e => this.handleChange(e)} />
</div>
);
}
};

How to catch click event with addEventListener

I am having a modal with button inside. Unfortunatly, it's not properly working. During the build I got an error : TypeError: Cannot read property 'addEventListener' of null
Below is the code:
import React from "react";
import { Modal } from "react-bootstrap";
import '../../assets/styles/Login.css';
class LoginRegisterModal extends React.Component {
constructor(props, context) {
super(props);
this.state = {show: false};
}
componentDidMount(){
const signUpButton = document.getElementById('signUp');
const signInButton = document.getElementById('signIn');
const container = document.getElementById('container');
signUpButton.addEventListener('click', () => {
container.classList.add('right-panel-active');
});
signInButton.addEventListener('click', () => {
container.classList.remove('right-panel-active');
});
}
....
render() {
const styleModal = {
marginTop: "15%",
marginLeft: "30%",
padding: 0,
width:770,
height:480,
backgroundColor:"#ffffffff",
borderRadius:21.5,
}
return (
<Modal show={this.state.show} style={styleModal} >
<div class="container" id="container">
<div>
.....
</div>
<div class="overlay-container">
<div class="overlay">
<div class="overlay-panel overlay-left">
<h1>Sign in.</h1>
<p>
Nice to see you again.Login and continue the journey.
</p>
<button class="ghost" id="signIn">Sign In</button>
</div>
<div class="overlay-panel overlay-right">
<h1>Hey, new friend!</h1>
<p>New to the Village? Sign up and start your journey</p>
<button class="ghost" id="signUp">Sign Up</button>
</div>
</div>
</div>
</div>
</Modal>
);
}
}
export default LoginRegisterModal;
I have tried adding a if condition before the addListener but it's just fixing the error but not working.
Also I have tried to replace by onClick instead but it's not working the code
signUpButton = () => {
container.classList.add('right-panel-active');
}
but container is not known..
Any idea?
Thanks
Your code should be inside component did mount method componentDidMount(). That's because when you look for a element with id "signUp" it doesn't exist yet. I don't encourage you to do what you are doing. A better approach would be <button onClick={myMethod}>

How to access the ::after pseudo selector using ElementRef in Angular

How can e.g. access and set the background-color of the ::after pseudo selector of the element below?
#ViewChild('infobubble', { read: ElementRef }) infoBubbleElement: ElementRef;
ngAfterViewInit(): void {
const elem = this.infoBubbleElement.nativeElement;
elem.style.backgroundColor = 'green';
}
I was presented with the same situation, it's not possible to access to ::before or ::after HTML element from elementRef; my solution was to create another DIV within the HTML element that I need to access, to modify it with a directive and change the background of the div that would be the new :: before
Example my directive:
#Directive({selector: '[appPrimaryColor]'})
export class PrimaryColorDirective implements OnInit {
#Input() backgroundColor = false;
constructor(
private elementRef: ElementRef,
private _renderer: Renderer2,
) { }
ngOnInit() {
// Set BackgroundColor
if (this.backgroundColor) {
this._renderer.setStyle(this.elementRef.nativeElement, 'background-color', COLOR);
}
}
}
And in HTML:
<div class="icon">
<!-- step-ok-effect is the new div-->
<div
appPrimaryColor
[backgroundColor]="true"
class="step-ok-effect"></div>
<span class="icon-{{ step.state ? 'check' : step.icon }}"></span>
</div>
</div>

How to add !important into React inline CSS style

How to add !important into my inline CSS property?
If I add it after 2px it just ignore whole style.
import React from "react";
export default class Todo extends React.Component {
render() {
const {text} = this.props;
const cardStyles = {
borderWidth: '2px'
};
return (
<div class="card mb-2 border" style={cardStyles}>
<div class="card-body">
<h5 class="card-title m-0">{text}</h5>
</div>
</div>
)
}
}
Apparently it is not supported https://github.com/facebook/react/issues/1881#issuecomment-262257503
meanwhile you can use a hack doing:
<div ref={element => {
if (element) element.style.setProperty('border', '2px', 'important');
}}
/>
It appears it currently isn't supported based on this GitHub issue https://github.com/facebook/react/issues/1881

React use radium to change css under div

Here is my original code :
the css will change all the p tag under <div className="TEST">
Home.js
export default class Home extends Component {
render() {
return (
<div className="TEST">
<p>1234567</p>
<p>1234567</p>
<p>1234567</p>
<a>qqqqqqqqqqqq</a>
</div>
);
}
}
style.css
.TEST p {
color:#ffa;
}
And If I use Radium, I need to add on each p under <div className="TEST">
Is there some method I can add the style in <div className="TEST"> just one time to change the p color like the style.css way ???
import Radium from 'radium';
let styles = {
home:{
test:{
color:'#ffa',
}
}
export default class Home extends Component {
render() {
return (
<div className="TEST">
<p style={styles.test}>1234567</p>
<p style={styles.test}>1234567</p>
<p style={styles.test}>1234567</p>
<a>qqqqqqqqqqqq</a>
</div>
);
}
}
You can use the Style component of Radium (see more at: https://github.com/FormidableLabs/radium/tree/master/docs/api#style-component)
import React, { Component } from 'react'
import Radium, { Style } from 'radium'
export default class Home extends Component {
render () {
return (
<div>
<Style scopeSelector=".TEST p" rules={{ color: '#ffa' }} />
<div className="TEST">
<p>1234567</p>
<p>1234567</p>
<p>1234567</p>
</div>
</div>
)
}
}

Resources