I want to create custom block that works like columns block in Gutenberg. It's easy to make it horizontally in frontend with CSS but how can I make it appear that way in editor?
import { registerBlockType } from '#wordpress/blocks';
import { useBlockProps, InnerBlocks } from '#wordpress/block-editor';
import metadata from '../config/card-row-block.json';
const cardRowTemplate = [
[ 'cgbms/card-block' ]
];
const allowedBlocks = [
'cgbms/card-block',
];
registerBlockType(metadata, {
edit: (props) => {
const blockProps = useBlockProps();
return <div className="cgbms_cards_row" { ...blockProps }>
<InnerBlocks allowedBlocks={ allowedBlocks } renderAppender={ InnerBlocks.ButtonBlockAppender } template={cardRowTemplate} orientation="horizontal" />
</div>
},
save: (props) => {
const blockProps = useBlockProps.save();
return <div className="cgbms_cards_row" { ...blockProps }>
<InnerBlocks.Content />
</div>
}
});
So here is the solution: https://wordpress.stackexchange.com/questions/390696/innerblocks-breaks-flexbox-and-css-grid-styles/390699
I need to use useInnerBlocksProps instead of InnerBlocks which is how the blocks that come with core do it.
Related
I'm using React Hook Form to build a basic page builder application and it's been brilliant so far, I've been using the useFieldArray hook to create lists that contain items, however, I haven't found a way to move items between lists.
I know I can use the move() function to reorder items within the same list, however, since each list has its own nested useFieldArray I can't move the item from one list component to another list component.
If anyone knows of a way around this it would be much appreciated!
Here is a very simplified example of my current setup:
export const App = () => {
const methods = useForm({
defaultValues: {
lists: [
{
list_id: 1,
items: [
{
item_id: 1,
name: 'Apple'
},
{
item_id: 2,
name: 'Orange'
}
]
},
{
list_id: 2,
items: [
{
item_id: 3,
name: 'Banana'
},
{
item_id: 4,
name: 'Lemon'
}
]
}
]
}
});
return (
<FormProvider {...methods}>
<Page/>
</FormProvider>
)
}
export const Page = () => {
const { control } = useFormContext();
const { fields } = useFieldArray({
control,
name: 'lists'
})
return (
<ul>
{fields?.map((field, index) => (
<List listIdx={index} />
))}
</ul>
)
}
export const List = ({ listIdx }) => {
const { control, watch } = useFormContext();
const { fields, move } = useFieldArray({
control,
name: `lists[${sectionIdx}].items`
})
const handleMove = (prevIdx, nextIdx) => {
// this allows me to move within lists but not between them
move(prevIdx, nextIdx);
}
return (
<li>
<p>ID: {watch(lists[${listIdx}].list_id)}</p>
<ul>
{fields?.map((field, index) => (
<Item listIdx={index} itemIdx={index} handleMove={handleMove}/>
))}
</ul>
</li>
)
}
export const Item = ({ listIdx, itemIdx, handleMove }) => {
const { control, register } = useFormContext();
return (
<li>
<p>ID: {watch(lists[${listIdx}].items[${itemIdx}].item_id)}</p>
<label
Name:
<input { ...register('lists[${listIdx}].items[${itemIdx}]) }/>
/>
<button onClick={() => handleMove(itemIdx, itemIdx - 1)}>Up</button>
<button onClick={() => handleMove(itemIdx, itemIdx + 1)}>Down</button>
</div>
)
}
Thanks in advance!
If you'd not like to alter your default values (your data structure), I think the best way to handle this is using update method returning from useFieldArray. You have the data of both inputs that are going to be moved around, knowing their list index and item index, you could easily update their current positions with each other's data.
I wish to place a list of posts on my home page instead of having to create a seperate dynamic page. This is my gatsby-node.js file
// DYNAMICALLY CREATE PAGES FOR EACH POST
module.exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions;
const postTemplate = path.resolve('src/templates/news.js');
const postResult = await graphql(`
query {
allContentfulPost {
edges {
node {
slug
}
}
}
}
`);
// Handle errors
if (postResult.errors) {
reporter.panicOnBuild('Error while running GraphQL query.');
return;
}
// Create the pages for each markdown file
postResult.data.allContentfulPost.edges.forEach(({ node }) => {
createPage({
component: postTemplate,
path: `/news/${node.slug}`,
context: {
slug: node.slug,
},
});
});
// PAGINATION FOR BLOG POSTS
const postsResult = await graphql(`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
slug
}
}
}
}
`);
if (postsResult.errors) {
reporter.panicOnBuild('Error while running GraphQL query.');
return;
}
// Create blog-list pages
const posts = postsResult.data.allContentfulPost.edges;
const postsPerPage = 12;
const postNumPages = Math.ceil(posts.length / postsPerPage);
Array.from({ length: postNumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/' : `/news/${i + 1}`,
component: path.resolve('./src/templates/news-list.js'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
postNumPages,
currentPage: i + 1,
},
});
});
};
And this is my news-list.js file
import React from 'react';
import { Link, graphql } from 'gatsby';
import Layout from '../components/layout';
import SEO from '../components/seo';
export const query = graphql`
query ($skip: Int!, $limit: Int!) {
allContentfulPost(sort: { fields: date, order: DESC }, limit: $limit, skip: $skip) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
const NewList = (props) => {
// const { postNumPages } = props.pageContext;
const posts = props.data.allContentfulPost.edges;
return (
<Layout>
<SEO title='News' />
{posts.map(({ node }) => {
const title = node.title || node.slug;
return (
<div className='container mx-auto prose prose-lg'>
<div className='mb-2'>
<Link to={`/posts/${node.slug}`}>
<h3 className='underline font-sans mb-1'>{title}</h3>
</Link>
<div className='flex items-center justify-between'>
<span className='font-mono text-sm'>{node.date}</span>
</div>
</div>
</div>
);
})}
</Layout>
);
};
export default NewList;
I have tried to import the above news-list.js as component from my templates folder into my index.js folder. However I am getting the Error:
TypeError: Cannot read property 'allContentfulPost' of undefined
But if i add path: i === 0 ? '/news' : /news/${i + 1}, into my node file and go to localhost/news i get the list of posts.
But I want them on the home page.. So i thought If I was to just have / it would work turns out no.
How can i get the posts that are listed at LH/news to be displayed on my homepage instead.
Update
New Component after latest answer
import React from 'react';
import { useStaticQuery, graphql, Link } from 'gatsby';
import Layout from '../components/layout';
// import News from '../components/news';
// import NewsList from '../templates/news-list';
export const query = graphql`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
const Index = ({ data }) => {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
companyname
}
}
}
`
);
return (
<Layout>
<section className='c-mt-10'>
<div className=''>
<div className='font-mono md:flex md:justify-between'>
<div className='mb-5'>
<a href={`mailto:hello#${site.siteMetadata.companyname}.co.uk`}>
hello#pfb{site.siteMetadata.companyname}.co.uk
</a>
<br />
<br />
<tel>+44 020 3925 6054</tel>
</div>
<a
href='https://www.google.com/maps/place/Warnford+Court,+29+Throgmorton+St,+London+EC2N+2AT/#51.5154096,-0.0890419,17z/data=!3m1!4b1!4m5!3m4!1s0x48761cacb440b98d:0x9742679143333ff!8m2!3d51.5154096!4d-0.0868479'
target='_blank'
rel='noreferrer'>
<address className='text-right'>
Warnford Court
<br />
29 Throgmorton Street
<br /> London, EC2N 2AT
</address>
</a>
</div>
</div>
<div>
<h2>Company News</h2>
<ul>
{data.allContentfulPost.edges.map(({ node }) => (
<li key={node.title}>
<Link to={node.slug}>{node.title}</Link>
</li>
))}
</ul>
</div>
</section>
</Layout>
);
};
export default Index;
I think you are mixing a lot of concepts.
One thing is the gatsby-node.js queries, useful to create dynamic pages based on dynamic data (from Contentful CMS in your case) based on a parameter (slug in your case).
Another thing is page queries, a way of retrieving data in a top-level components (pages or templates, not components).
If you want to list all your post in your homepage, you just need to create a GraphQL query and loop through the results just like:
const IndexPage = ({ data }) => {
return <Layout>
<ul>
{data.allContentfulPost.edges.map(({node})=> <li key={node.title}><Link to={node.slug}>{title}</Link></li>)}
</ul>
</Layout>
}
export const query = graphql`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
When using page queries, your data is stored inside props.data so you can destructure them directly into data.
In your case, you were importing a template inside a page, which doesn't make much sense because you don't have, among other things, the query.
I am using better-react-mathjax for equation writing and reading. Basically, I used the MathJax in my equation with a question. When it first loads it does not create a problem.
But when I use to filter using React sate it creates the Typesetting problem and the next app is crashed.
How can I fix the problem?
MathJax
import React from 'react'
import { MathJax, MathJaxContext } from "better-react-mathjax";
const config = {
loader: { load: ["input/asciimath"] }
};
export const MathJaxOutput = ({ text }) => {
return <MathJaxContext config={config} version={3}>
<MathJax dynamic inline>
{text}
</MathJax>
</MathJaxContext>
}
And the error screenshot is
When mark and course name changed automatically loads the related questions.
state = {
courseName: '',
selectedTopics: [],
mark: null,
questions:[]
}
componentDidUpdate(prevProps, prevState) {
if (this.state.courseName !== prevState.courseName || this.state.selectedTopics !== prevState.selectedTopics || this.state.mark !== prevState.mark) {
if (this.state.courseName) {
this.props.getCourseQuestions(this.state.courseName, this.state.selectedTopics, this.state.mark);
}
}
}
Output render
{
this.state.questions.map((question, questionIndex) => (
<div className='input-question-field-items' key={questionIndex}>
<div className='preview-field-item'>
<MathJaxOutput text={<p>{question.questionInput.question}</p>} />
</div>
</div>
}
I am trying to drag and drop any html element in nested level of container.
First level of drag and drop of elements are working but nested level is not working.
Nested level means "Dropping button inside card element which also an element".
I am taking card as control and container.
I am developing in reactjs, react-dnd.
Code :
app.js
const App = props =>{
const [controlsList, setControlList]= useState([
{ email_txt }, { button }, { card } , {textarea } ...
])
return (
<>
<div className="draggable">
{
controlsList.map(({_id, type, title}, index)=>{
<ControlsAndContainers _id={_id} type={type} title={title} />
})
}
</div>
<div className="droppable">
<DropBox/>
</div>
</>
)
}
ControlsAndContainer.js
import { useDrag } from 'react-dnd'
const ControlsAndContainer = ({_id, type, title })=>{
const [ {opacity}, drag ] = useDrag(()=>({
type,
item: { _id, type, title },
end: (item, monitor)=>{
//
},
collect: (monitor) =>({
opacity: monitor.isDragging()? 0.4 : 1
})
}), [title, type]);
const box_style = {
cursor: 'move', border: '1px dashed gray'
}
return (
<div ref={drag} style={{ ...box_style, opacity}}>
{title}
</div>
)
}
dropbox.js
import { useDrop } from 'react-dnd'
const DropBox = () =>{
let temp =[];
const [dataState, setDataState] = useState([]);
const [{isOver }, drop] = useDrop(()=> ({
accept: ['button', 'email', 'card', 'textarea'],
drop(item, monitor){
temp.push(item);
setDataState(temp);
},
collect:(monitor)=>{
isOver: monitor.isOver(),
}
}), []);
const ButtonControl = () => {
return ( <div> <button>Button</button> </div>)
}
.... email, textarea
// card code is from react-bootsrap
const CardControl = () => {
<Card style={{ width: '18rem' }}>
<Card.Header>Header</Card.Header>
<Card.Body>
Drop other element here
</Card.Body>
<Card.Footer>Footer</Card.Footer>
</Card>
}
return (
<div ref={drop}>
dataState.map((data,index)=>{
let container;
switch(data.type){
case 'button': container=<ButtonControl />
break;
case 'button': container=<CardControl />
break;
default: break;
}
return (
<> <div key={data._id}> { container } </div></>
)
})
</div>
)
}
I am trying to drag and drop button inside "Card" control which is not working but card drag and drop is working and outside the card is also working.
What I am missing ?
Please somebody help
I solved this problem. There are two ways to solve it.
create 2nd drop ref, I mean
const [, nestedDrop] = useDrop(()=>{ accept, drop, ... }));
use nestedDrop inside inner container like this ,
<Card.Body>
<div ref={nestedDrop}></div>
</Card.Body>
way is inspired from this official example :
nested drop area
you can customize nested dropbox according to your need.
I'm follow the steps of this dependencie:
http://jossmac.github.io/react-images/
And it isn't work. No picture showing and there is showing an error message:
index.js:2178 Warning: Failed prop type: The prop onClose is marked
as required in Lightbox, but its value is undefined
Here is my code:
import React, { Component } from "react";
import Lightbox from "react-images";
const URL_INTERIORES = "http://localhost:3001/interiores";
const LIGHTBOX_IMAGE_SET = [
{
src: "/images/int_02.jpg",
caption: "A forest",
// As an array
srcSet: ["/images/int_02.jpg", "/images/int_02.jpg"]
},
{
src: "/images/int_02.jpg",
// As a string
srcSet: "/images/int_02.jpg 1024w, /images/int_02.jpg 800w, /images/int_02.jpg 500w, /images/int_02.jpg 320w"
}
];
class Interiores extends Component {
render() {
const { open } = this.state;
return (
<div>
<div>
<Lightbox
images={LIGHTBOX_IMAGE_SET}
isOpen={this.state.lightboxIsOpen}
onClickPrev={this.gotoPrevLightboxImage}
onClickNext={this.gotoNextLightboxImage}
onClose={this.closeLightbox}
/>
</div>
</div>
);
}
}
export default Interiores;
Does anybody know how to solve it? Tahnk you
Consider adding all the missing handlers & state in your class:
class Interiores extends Component {
state = {
lightboxIsOpen: false
}
gotoPrevLightboxImage() {
// Add the logic here
}
gotoNextLightboxImage() {
// Add the logic here
}
closeLightbox(e) {
// Add the logic here
}
render() {
const { lightboxIsOpen } = this.state;
return (
<div>
<Lightbox
images={LIGHTBOX_IMAGE_SET}
isOpen={lightboxIsOpen}
onClickPrev={() => this.gotoPrevLightboxImage()}
onClickNext={() => this.gotoNextLightboxImage()}
onClose={e => this.closeLightbox(e)}
/>
</div>
);
}
}