I would like to add a 33% to the Wordpress Block "Button". So far it has 25%,50%,75% and 100%. Is it possible to insert my new value into the existing width selector?
I'm guessing Block Filters are the way to go.
I think I also found the way to get the settings object which might then help me to find out what I need to overwrite. However simply adding this code to my admin.js does not produce any output. Where would I need to load this?
const filterBlocks = (settings) => {
if (settings.name !== 'core/buttons') {
return settings
}
console.log(settings);
return settings;
}
Quick solution: Add a custom CSS class in the Buttons' block properties under "Advanced > Additional CSS class(es)" then define the custom width in your theme style.css
Detailed solution:
By using wp.hooks.addFilter() you can add a new control to the Button block with as many extra custom width options as you need. The Button blocks preset widths are defined within the function WidthPanel() of the blocks edit.js function:
function WidthPanel( { selectedWidth, setAttributes } ) {
...
return (
...
<ButtonGroup aria-label={ __( 'Button width' ) }>
{ [ 25, 50, 75, 100 ].map( ( widthValue ) => {
...
}
}
To add a new width value of 33% to the block, we need to add our own new button control to the InspectorControls and then use wp.hooks.addFilter() to add this to the existing core Button block, eg:
index.js
import { createHigherOrderComponent } from '#wordpress/compose';
import { Fragment } from '#wordpress/element';
import { InspectorControls } from '#wordpress/block-editor';
import { PanelBody, Button } from '#wordpress/components';
const withInspectorControls = createHigherOrderComponent((BlockEdit) => {
return (props) => {
const { setAttributes } = props;
let widthValue = 33; // must be a number
return (
<Fragment>
<BlockEdit {...props} />
<InspectorControls>
<PanelBody title="Custom Width">
<Button
key={widthValue}
isSmall
variant={widthValue}
onClick={() => setAttributes({ width: widthValue })}
>
{widthValue}%
</Button>
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'withInspectorControl');
wp.hooks.addFilter(
'editor.BlockEdit',
'core/button',
withInspectorControls
);
Next, a new additional css style needs to be added that (matches the existing width presets structure) for the new custom width, eg:
style.scss
$blocks-block__margin: 0.5em;
&.wp-block-button__width-33 {
width: calc(33.33% - #{ $blocks-block__margin });
}
And there you have it..
The easiest way to put all the code above together/working is to create your own Gutenberg block (and that in itself can be challenging if you aren't familiar with the process or ReactJS). I too have come across similiar challenges with Gutenberg, so I wanted to provide a detailed solution for this kind of issue that works.
I made a toggle component (Accordion to be exact)
I am mapping through an array of objects and listing them like:
{object.map((o) => (
<Accordion key={o.id} title={o.question} className="item">
<div className="text"> { o.answer } <div/>
</Accordion>
))}
It renders something like this:
> Question 1
> Question 2
> Question 3
Now, every time I click a question, it toggles down to show the answer. All this works fine(I used hooks).
I want to be able to change the opacity of all the un toggled elements in this list when ONE of the questions is opened.
So if I open question 2, it becomes the "current item" and the opacity of question 2 and its answer should be 100% and all others(question1 an question3) should dim out or turn 50% opacity.. I am able to do it using :hover using css but that only works on hover.
Basically in theory, I should be able to select an item and remove the base class from all other items except the selected one. I don't know how to do that in reality. Help.
I feel like I'm missing something obvious.
const Accordion = ({ title, children, opened = false }) => {
const [show, setShow] = useState(opened);
const rotation = classnames('icon', {
'rotate': show,
});
const content = classnames('contents', {
'closed': !show,
});
useEffect(() => {
setShow(opened);
}, [opened]);
const toggle = useCallback(() => {
setShow(!show);
}, [show]);
return (
<div className='titleContainer' onClick={toggle}>
<div className={rotations}>
<i className='icon' />
</div>
<h5 className='title'>{title}</h5>
</div>
<div className={content}>{children}</div>
);
};
I finally understand what you mean, I think this is the answer:
const questionColor = (questionIndex, activeQuestion) => {
if (activeQuestion !== null && activeQuestion !== questionIndex) {
return "rgba(0,0,0,0.1)";
} else return "rgba(0,0,0,1)";
};
Working solution here:
https://codesandbox.io/s/cocky-hellman-fxrmc
My pagination component is showing an extra, undesired item when the user is at or near either the beginning or the end of the item list. I believe I am matching my props exactly to what I am inputting in the documentation 'options' example, so I'm not sure where I'm going wrong.
Current behavior GIF
Desired behavior GIF
class MyPagination extends React.Component<PaginationProps> {
static defaultProps = {
boundaryRange: 0,
defaultActivePage: 1,
showEllipsis: false,
siblingRange: 2,
};
render() {
const {
boundaryRange,
defaultActivePage,
showEllipsis,
siblingRange,
totalPages,
} = this.props;
return (
<Pagination
boundaryRange={boundaryRange}
defaultActivePage={defaultActivePage}
ellipsisItem={showEllipsis ? undefined : null}
siblingRange={siblingRange}
totalPages={totalPages}
/>
);
}
}
export default MyPagination;
I'm instantiating like this:
<MyPagination totalPages={50} />
And my props are coming out as expected:
This issue is a bug and was fixed in semantic-ui-react#0.84.0 by https://github.com/Semantic-Org/Semantic-UI-React/pull/3271.
I'm currently experimenting with StencilJS to create some web components.
Now I know that there is <slot /> and named slots and all that stuff. Coming from React, I guess slot is similar to children in React. You can do a lot of stuff using children in React. Things I often did:
Check if any children are provided
Iterate over children to do something to each child (e.g. wrap it in a div with a class etc.)
How would you do that using slot/web components/stencilJS?
I can get the Host Element of my web component in Stencil using
#Element() hostElement: HTMLElement;
I use my component like
<my-custom-component>
<button>1</button>
<button>2</button>
<button>3</button>
</my-custom-component>
I want to render something like
render() {
return slottedChildren ?
<span>No Elements</span> :
<ul class="my-custom-component">
slottedChildren.map(child => <li class="my-custom-element>{child}</li>)
</ul>;
}
Kind regards
Using slots you don't need to put a condition in your render function. You can put the no children element (in your example the span) inside the slot element and if no children are provided to the slot it will fall back to it.
For example:
render() {
return (
<div>
<slot><span>no elements</span></slot>
</div>
);
}
Answering the comment you wrote - you can do such a thing but with some coding and not out of the box. Every slot element has an assignedNodes function. Using that knowledge and the understanding of Stencil component life cycle you can do something such as:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
#State() children: Array<any> = [];
componentWillLoad() {
let slotted = this.host.shadowRoot.querySelector('slot') as HTMLSlotElement;
this.children = slotted.assignedNodes().filter((node) => { return node.nodeName !== '#text'; });
}
render() {
return (
<div>
<slot />
<ul>
{this.children.map(child => { return <li innerHTML={child.outerHTML}></li>; })}
</ul>
</div>
);
}
}
This is not an optimal solution and it will require that the style of the slot should have display set to none (cause you don't want to show it).
Also, it will only work with simple elements that only need rendering and not requiring events or anything else (cause it only uses them as html string and not as objects).
Thank you for the answer Gil.
I was thinking of something similar before (setting state etc. - because of timing issues that might come up). I didn't like the solution though, because you're then doing a state change within componentDidLoad, which will trigger another load just after the component did load. This seems dirty and unperfomant.
The little bit with innerHTML={child.outerHTML} helped me alot though.
It seems like you can also simply do:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
render() {
return (
<div>
<ul>
{Array.from(this.host.children)
.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
I thought you might run into timing issues, because during render() the child elements of the host have already been removed to make space for whatever render() returns. But since shadow-dom and light-dom coexist nicely within the host component, I guess there shouldn't be any issues.
I don't really know why you have to use innerHTML though. Coming from React I'm used to doing:
{Array.from(this.host.children)
.map(child => <li>{child}</li>)}
And I thought that is basic JSX syntax and that since Stencil is also using JSX I could do that, too. Doesn't work though. innerHTML does the trick for me. Thanks again.
EDIT: The timing issues I mentioned will appear if you're not using shadow-dom though. Some strange things start to happen an you'll end up with a lot of duplicate children.
Though you can do (might have side effects):
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
children: Element[];
#Element() host: HTMLDivElement;
componentWillLoad() {
this.children = Array.from(this.host.children);
this.host.innerHTML = '';
}
render() {
return (
<div>
<ul>
{this.children.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
I have a react component that needs to know its dimensions ahead of time, before it renders itself.
When I'd make a widget in jquery I could just $('#container').width() and get the width of the container ahead of time when I build my component.
<div id='container'></div>
these container's dimensions are defined in CSS, along with a bunch of other containers on the page. who defines the height and width and placement of the components in React? I'm used to CSS doing that and being able to access that. But in React it seems I can only access that information after the component has rendered.
The example below uses react hook useEffect.
Working example here
import React, { useRef, useLayoutEffect, useState } from "react";
const ComponentWithDimensions = props => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({ width:0, height: 0 });
useLayoutEffect(() => {
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}, []);
return (
<div ref={targetRef}>
<p>{dimensions.width}</p>
<p>{dimensions.height}</p>
</div>
);
};
export default ComponentWithDimensions;
Some Caveats
useEffect will not be able to detect it's own influence to width and height
For example if you change the state hook without specifying initial values (eg const [dimensions, setDimensions] = useState({});), the height would read as zero when rendered because
no explicit height was set on the component via css
only content drawn before useEffect can be used to measure width and height
The only component contents are p tags with the height and width variables, when empty will give the component a height of zero
useEffect will not fire again after setting the new state variables.
This is probably not an issue in most use cases, but I thought I would include it because it has implications for window resizing.
Window Resizing
I also think there are some unexplored implications in the original question. I ran into the issue of window resizing for dynamically drawn components such as charts.
I'm including this answer even though it wasn't specified because
It's fair to assume that if the dimensions are needed by the application, they will probably be needed on window resize.
Only changes to state or props will cause a redraw, so a window resize listener is also needed to monitor changes to the dimensions
There's a performance hit if you redraw the component on every window resize event with more complex components. I found
introducing setTimeout and clearInterval helped. My component
included a chart, so my CPU spiked and the browser started to crawl.
The solution below fixed this for me.
code below, working example here
import React, { useRef, useLayoutEffect, useState } from 'react';
const ComponentWithDimensions = (props) => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({});
// holds the timer for setTimeout and clearInterval
let movement_timer = null;
// the number of ms the window size must stay the same size before the
// dimension state variable is reset
const RESET_TIMEOUT = 100;
const test_dimensions = () => {
// For some reason targetRef.current.getBoundingClientRect was not available
// I found this worked for me, but unfortunately I can't find the
// documentation to explain this experience
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}
// This sets the dimensions on the first render
useLayoutEffect(() => {
test_dimensions();
}, []);
// every time the window is resized, the timer is cleared and set again
// the net effect is the component will only reset after the window size
// is at rest for the duration set in RESET_TIMEOUT. This prevents rapid
// redrawing of the component for more complex components such as charts
window.addEventListener('resize', ()=>{
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
return (
<div ref={ targetRef }>
<p>{ dimensions.width }</p>
<p>{ dimensions.height }</p>
</div>
);
}
export default ComponentWithDimensions;
re: window resizing timeout - In my case I'm drawing a dashboard with charts downstream from these values and I found 100ms on RESET_TIMEOUT seemed to strike a good balance for me between CPU usage and responsiveness. I have no objective data on what's ideal, so I made this a variable.
As it was already mentioned, you can't get any element's dimensions until it is rendered to DOM. What you can do in React is to render only a container element, then get it's size in componentDidMount, and then render rest of the content.
I made a working example.
Please note that using setState in componentDidMount is an anti-pattern but in this case is fine, as it is exactly what are we trying to achieve.
Cheers!
Code:
import React, { Component } from 'react';
export default class Example extends Component {
state = {
dimensions: null,
};
componentDidMount() {
this.setState({
dimensions: {
width: this.container.offsetWidth,
height: this.container.offsetHeight,
},
});
}
renderContent() {
const { dimensions } = this.state;
return (
<div>
width: {dimensions.width}
<br />
height: {dimensions.height}
</div>
);
}
render() {
const { dimensions } = this.state;
return (
<div className="Hello" ref={el => (this.container = el)}>
{dimensions && this.renderContent()}
</div>
);
}
}
You cannot. Not reliably, anyway. This is a limitation of browser behavior in general, not React.
When you call $('#container').width(), you are querying the width of an element that has rendered in the DOM. Even in jQuery you can't get around this.
If you absolutely need an element's width before it renders, you will need to estimate it. If you need to measure before being visible you can do so while applying visibility: hidden, or render it somewhere discretely on the page then moving it after measurement.
There's an unexpected "gotcha" with #shane's approach for handling window resizing: The functional component adds a new event listener on every re-render, and never removes an event listener, so the number of event listeners grows exponentially with each resize. You can see that by logging each call to window.addEventListener:
window.addEventListener("resize", () => {
console.log(`Resize: ${dimensions.width} x ${dimensions.height}`);
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
This could be fixed by using an event cleanup pattern. Here's some code that's a blend of #shane's code and this tutorial, with the resizing logic in a custom hook:
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useLayoutEffect, useRef } from "react";
// Usage
function App() {
const targetRef = useRef();
const size = useDimensions(targetRef);
return (
<div ref={targetRef}>
<p>{size.width}</p>
<p>{size.height}</p>
</div>
);
}
// Hook
function useDimensions(targetRef) {
const getDimensions = () => {
return {
width: targetRef.current ? targetRef.current.offsetWidth : 0,
height: targetRef.current ? targetRef.current.offsetHeight : 0
};
};
const [dimensions, setDimensions] = useState(getDimensions);
const handleResize = () => {
setDimensions(getDimensions());
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useLayoutEffect(() => {
handleResize();
}, []);
return dimensions;
}
export default App;
There's a working example here.
This code doesn't use a timer, for simplicity, but that approach is further discussed in the linked tutorial.
As stated, it is a limitation of the browsers - they render in one go and "in one thread" (from JS perspective) between your script that manipulates the DOM, and between event handlers execution. To get the dimensions after manipulating / loading the DOM, you need to yield (leave your function) and let the browser render, and react to some event that rendering is done.
But try this trick:
You could try to set CSS display: hidden; position: absolute; and restrict it to some invisible bounding box to get the desired width. Then yield, and when the rendering is done, call $('#container').width().
The idea is: Since display: hidden makes the element occupy the space it would take if visible, the computation must be done in the background.
I am not sure if that qualifies as "before render".
Disclaimer:
I haven't tried it, so let me know if it worked.
And I am not sure how it would blend with React.
#Stanko's solution is nice and terse, but it's post-render. I have a different scenario, rendering a <p> element inside an SVG <foreignObject> (in a Recharts chart). The <p> contains text that wraps, and the final height of the width-constrained <p> is hard to predict. The <foreignObject> is basically a viewport and if too long it would block clicks/taps to underlying SVG elements, too short and it chops off the bottom of the <p>. I need a tight fit, the DOM's own style-determined height before the React render. Also, no JQuery.
So in my functional React component I create a dummy <p> node, place it to the live DOM outside the document's client viewport, measure it, and remove it again. Then use that measurement for the <foreignObject>.
[Edited with method using CSS classes]
[Edited: Firefox hates findCssClassBySelector, stuck with hardcoding for now.]
const findCssClassBySelector = selector => [...document.styleSheets].reduce((el, f) => {
const peg = [...f.cssRules].find(ff => ff.selectorText === selector);
if(peg) return peg; else return el;
}, null);
// find the class
const eventLabelStyle = findCssClassBySelector("p.event-label")
// get the width as a number, default 120
const eventLabelWidth = eventLabelStyle && eventLabelStyle.style ? parseInt(eventLabelStyle.style.width) : 120
const ALabel = props => {
const {value, backgroundcolor: backgroundColor, bordercolor: borderColor, viewBox: {x, y}} = props
// create a test DOM node, place it out of sight and measure its height
const p = document.createElement("p");
p.innerText = value;
p.className = "event-label";
// out of sight
p.style.position = "absolute";
p.style.top = "-1000px";
// // place, measure, remove
document.body.appendChild(p);
const {offsetHeight: calcHeight} = p; // <<<< the prize
// does the DOM reference to p die in garbage collection, or with local scope? :p
document.body.removeChild(p);
return <foreignObject {...props} x={x - eventLabelWidth / 2} y={y} style={{textAlign: "center"}} width={eventLabelWidth} height={calcHeight} className="event-label-wrapper">
<p xmlns="http://www.w3.org/1999/xhtml"
className="event-label"
style={{
color: adjustedTextColor(backgroundColor, 125),
backgroundColor,
borderColor,
}}
>
{value}
</p>
</foreignObject>
}
Ugly, lots of assumptions, probably slow and I'm nervous about the garbage, but it works. Note that the width prop has to be a number.
All the solutions I found on Stack overflow were either very slow, or out of date with modern React conventions. Then I stumbled across:
https://github.com/wellyshen/react-cool-dimensions
A React hook that measure an element's size and handle responsive components with highly-performant way, using ResizeObserver.
It's fast and works much better than the solutions I tried here.
import { useState, useEffect } from 'react'
const useContainerDimensions = containerRef => {
const getDimensions = () => ({
width: containerRef.current.offsetWidth,
height: containerRef.current.offsetHeight
})
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
useEffect(() => {
const handleResize = () => {
setDimensions(getDimensions())
}
let dimensionsTimeout = setTimeout(() => {
if(containerRef.current) {
setDimensions(getDimensions())
}
}, 100)
window.addEventListener("resize", handleResize)
return () => {
clearTimeout(dimensionsTimeout)
window.removeEventListener("resize", handleResize)
}
}, [containerRef])
return dimensions
}
export default useContainerDimensions
You can use useContainerDimensions Custom hook. if you need width and height as pixel you can use clientWidth and clientHeight instead of offsetWidth and offsetHeight.