Toggling a class on a Tailwind/Alpine.js accordion - tailwind-css

I've created an accordion with TailwindCSS and Alpine.js which works fine except that I also want to change the icon in the button that expands the content when it's clicked.
This is what I have:
<div x-data="{selected:null,open:true}">
<dl class="faqs mx-auto max-w-2xl">
<dt>
<span class="faq-q">Question</span>
<button
type="button"
class="faq-toggle"
#click="selected !== 1 ? selected = 1 : selected = null, open = open"
:class="{ 'faq-open': open, 'faq-close': !(open) }"
>
<span>+</span>
<span class="hidden">-</span>
</button>
</dt>
<dd
class="faq-a overflow-hidden transition-all max-h-0 duration-700"
style="" x-ref="container1" x-bind:style="selected == 1 ? 'max-height: ' + $refs.container1.scrollHeight + 'px' : ''"
>
<div class="inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure rerum in tempore sit ducimus doloribus quod commodi eligendi ipsam porro non fugiat nisi eaque delectus harum aspernatur recusandae incidunt quasi.
</div>
</dd>
</dl>
</div>
and a link to a CodePen.
What I want to do is toggle the class of the button from faq-open to faq-close when the button is clicked. Although I may actually need to toggle a class on the parent dt too.
At the moment, the accordion expands when you click on the button, but the class doesn't change.

The problem is on this line
#click="selected !== 1 ? selected = 1 : selected = null, open = open"
You never change value of open, it is always the value at initialization, and that is open: true.
You need to switch it:
#click="selected !== 1 ? selected = 1 : selected = null, open = !open"
By the way, you don't need extra variable selected to control the hidden text, just one open variable is enough. Something like this:
<div x-data="{open: true}">
<dl class="faqs mx-auto max-w-2xl">
<dt>
<span class="faq-q">Question</span>
<button
type="button"
class="faq-toggle"
#click="open = !open"
:class="open ? 'faq-open' : 'faq-close'"
>
<span :class="open ? '' : 'hidden'">+</span>
<span :class="open ? 'hidden' : ''">-</span>
</button>
</dt>
<dd
class="faq-a overflow-hidden transition-all max-h-0 duration-700"
style="" x-ref="container1" x-bind:style="open ? 'max-height: ' + $refs.container1.scrollHeight + 'px' : ''"
>
<div class="inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure rerum in tempore sit ducimus doloribus quod commodi eligendi ipsam porro non fugiat nisi eaque delectus harum aspernatur recusandae incidunt quasi.
</div>
</dd>
</dl>

import 'twin.macro';
import React, { useRef, useState } from 'react';
import { BiPlus, BiMinus } from 'react-icons/bi';
const Accordion = ({ title, children }) => {
const [active, setActive] = useState(false);
const [height, setHeight] = useState('0px');
const contentSpace = useRef(null);
function toggleAccordion() {
setActive(!active);
setHeight(active ? '0px' : `${contentSpace.current?.scrollHeight}px`);
}
return (
<div tw="flex flex-col outline-none" role="button" tabIndex={0}>
<div
tw="flex flex-row items-center justify-between py-4 border-b border-t border-black "
onClick={toggleAccordion}>
<h1 tw="cursor-pointer text-base font-bold">{title}</h1>
{active ? <BiMinus /> : <BiPlus />}
</div>
<div
ref={contentSpace}
style={{ maxHeight: `${height}` }}
tw="overflow-auto overflow-y-hidden duration-700 ease-in-out">
<p tw="my-4">{children}</p>
</div>
</div>
);
};
export default Accordion;

Related

Responsive slider timeline (NextJs project) with Swiper

I am new to NextJs/ReactJs and I was trying to implement the Responsive slide with Swiperjs which is available in codepen.io
Here is the link to codepen.
Responsive slider timeline with Swiper
Below are my steps I carried out;
Installed JQuery (npm install)
Imported both swiper.min.css and swiper.min.js CDNs to my _Document.js file
Added the css files to Globals.css for time being
Created a TimelineComponent.js to have the swiper component separately.
imported TimelineComponent.js in the index.js
When I run the project it nicely shows you the expected output as below.
But when I resize my browser it behaves abnormally like below
import React, { useEffect } from 'react';
import $ from 'jquery';
function TimeLineComponent() {
function operateTimeline() {
var timelineSwiper = new Swiper('.timeline .swiper-container', {
direction: 'vertical',
loop: false,
speed: 1600,
pagination: '.swiper-pagination',
paginationBulletRender: function (swiper, index, className) {
var year = document
.querySelectorAll('.swiper-slide')
[index].getAttribute('data-year');
return '<span class="' + className + '">' + year + '</span>';
},
paginationClickable: true,
nextButton: '.swiper-button-next',
prevButton: '.swiper-button-prev',
breakpoints: {
768: {
direction: 'horizontal',
},
},
});
}
useEffect(() => {
$(window).on('load resize', operateTimeline);
operateTimeline();
return () => {
$(window).off('load resize');
};
}, []);
return (
<div className="timeline-container">
<div className="timeline">
<div className="swiper-container">
<div className="swiper-wrapper">
<div
className="swiper-slide"
style={{
backgroundImage: "url('https://unsplash.it/1920/500?image=11')",
}}
data-year="2011"
>
<div className="swiper-slide-content">
<span className="timeline-year">2011</span>
<h4 className="timeline-title">Our nice super title</h4>
<p className="timeline-text">
Lorem ipsum dolor site amet, consectetur adipscing elit, sed
do eisumod tempor incididut ut labore et dolore magna aliqua.
Ut enim ad mimim venjam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
</p>
</div>
</div>
<div
className="swiper-slide"
style={{
backgroundImage: "url('https://unsplash.it/1920/500?image=12')",
}}
data-year="2012"
>
<div className="swiper-slide-content">
<span className="timeline-year">2012</span>
<h4 className="timeline-title">Our nice super title</h4>
<p className="timeline-text">
Lorem ipsum dolor site amet, consectetur adipscing elit, sed
do eisumod tempor incididut ut labore et dolore magna aliqua.
Ut enim ad mimim venjam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
</p>
</div>
</div>
</div>
<div className="swiper-button-prev"></div>
<div className="swiper-button-next"></div>
<div className="swiper-pagination"></div>
</div>
</div>
</div>
);
}
export default TimeLineComponent;
Error can be replicated in the above codepen example as well. Most probably this is with the styling
Your help is much appreciated.

Next JS Error product.map Is Not a Function

Hello everyone i have a question according to my case, i have
index.js code bellow :
export default function Home({ productList }) {
return (
<div className={styles.container}>
<Head>
<title>OlResto UI Design With Next JS</title>
<meta name="description" content="This is stunning website build using next js" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Featured />
<ProductList productList={ productList } />
</div>
);
};
export const getServerSideProps = async () => {
const res = await axios.get("http://localhost:3000/api/products");
return {
props: {
productList: res.data
}
}
};
the second one i have file call ProductList.jsx
code below:
const ProductList = ({ productList }) => {
return (
<div className={styles.container}>
<h1 className={styles.title}>THE BEST PIZZA IN TOWN</h1>
<p className={styles.desc}>Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint, nemo at, labore porro reiciendis rem ad tempora blanditiis adipisci a eum animi obcaecati, dignissimos non perspiciatis natus corrupti consectetur libero.</p>
<div className={styles.wrapper}>
{productList.map(product => (
<ProductCard key={product._id} product={product} />
))}
</div>
</div>
);
};
export default ProductList;
Those files produce an error like this :
TypeError: productList.map is not a function
<div className={styles.wrapper}>
{productList.map(product => (
^
<ProductCard key={product._id} product={product} />
))}
</div>
Sorry if my question messed up, hope you guys can help my problem, thank you in advance

Issues with CSS transitions on Next.js project

I'm working on a project in Next.js (React) for the first time. It's a static site (only frontend).
Somehow I run into lots of problems related to the CSS transitions and I can't catch what am I missing.
When using some ready components, for example Modal from react-bootstrap, the transition seems to be working on Modal show, but not on hide. Transitions from Accordion, from the same module, work fine, but didn't work from other Accordion component I tried before that.
I will put some code here, but I have a filling, that there is some fundamental css/js related topic that I don't understand fully, but my searches didn't bring me the answer yet.
Here is the sample of the component, the related CSS is the default bootstrap-css (for the sake of simplicity):
Nav.tsx
import Link from 'next/link'
import navStyles from '../styles/Nav.module.css'
import homeStyles from "../styles/Home.module.css"
import {useState} from "react";
import Modal from "react-bootstrap/cjs/Modal"
import Button from "react-bootstrap/cjs/Button";
const MyVerticallyCenteredModal = (props) => {
return (
<Modal
{...props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header>
<Button onClick={props.onHide}>X</Button>
</Modal.Header>
<Modal.Body>
<p style={{color: "white"}}>Lorem ipsum dolor sit amet, consectetur adipisicing
elit. Aliquid at consequuntur cum ducimus enim fuga hic laudantium magni
maiores, molestias nesciunt nihil nisi nostrum pariatur perspiciatis rem rerum
suscipit, unde?</p>
</Modal.Body>
<Modal.Footer>
<Button onClick={props.onHide}>Close</Button>
</Modal.Footer>
</Modal>
);
}
const Nav = () => {
const [modalShow, setModalShow] = useState(false);
const NavComp = () => {
return (
<>
<div className={navStyles.wrapper}>
<div className={`${homeStyles.home} ${homeStyles.mobile}`}>
<div className={navStyles.footer}>
<button
className={`${navStyles.footerBtn}`}
onClick={() => setModalShow(true)}
style={{borderLeft: "1px rgba(70,70,70,0.5) solid"}}
>
Contact
</button>
<MyVerticallyCenteredModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</div>
</div>
</div>
</>
)
}
return (
<nav className={navStyles.nav}>
<div className={navStyles.container}>
<div className={navStyles.logo}>
<Link href='/'>
<div>
<p className={navStyles.kaveem}>KAVEEM</p>
</div>
</Link>
</div>
<NavComp/>
</div>
</nav>
);
};
export default Nav;

CSS selector for label without child elements

First of all the solution in that question: CSS selector to bold only labels without child elements does not solve my issue.
I have labels with text only and others with text and child input, select and textarea form's elements.
i.e:
<label class="control-label">State
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
...
</select>
</label>
and other like:
<label for="defect" class="control-label">Error</label>
I need to set white-space: nowrap to labels that has no child HTML elements only and as the above question answers stated, I tried the following:
label{
white-space: nowrap; /* Label without HTML*/
}
label + label {
white-space: normal; /* Label with HTML */
}
However, it does not work.
One solution would be adding a class to the element and using CSS to format it accordingly.
label.empty {
white-space: nowrap;
}
Link to the documentation: https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors
The other comment points to using :empty but in your case the <label> contains some text and doesn't apply as empty
AFAIK there is no solution using CSS selectors. The solution proposed by #FabioPontes to control via an additional class name would be the most straight-forward.
Following is a javascript solution that verifies for an element's child nodes and applies a white-space:nowrap if (1) there is only one child node and (2) this node is of type text. Please view node types.
var elements = document.getElementsByClassName("control-label");
for (i = 0; i < elements.length; i++) {
if (elements[i].childNodes.length == 1 && elements[i].childNodes[0].nodeType == 3) {
elements[i].style.whiteSpace = "nowrap";
}
}
<div>
<label class="control-label">State - we need a really long text to check if the white-space nowrap is actually working so lets place this text here.
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</label>
</div>
<div style="margin-top:20px;">
<label for="defect" class="control-label">Error - we need a really long text to check if the white-space nowrap is actually working so lets place this text here.</label>
</div>
The option to add a class has already been suggested by Fabio Pontes and is a good one, but if you don't want to add classes, here are a couple options.
The first thing you could do is modify your markup to wrap each label in a div and then leverage the :only-child pseudo selector. In order for this to work, you'll have to include the select element as a sibling of the label, rather than as a child of it.
.control-label-wrapper label:only-child {
white-space: nowrap;
}
<div class="control-label-wrapper">
<label class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</div>
<div class="control-label-wrapper">
<label for="defect" class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>
</div>
Another option which may not require modifying your makup at all is to use an attribute selector. Perhaps you're already using an attribute for all these childless labels. The example HTML in your question suggests you may be.
label[for="defect"] {
white-space: nowrap;
}
<label class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</label>
<label for="defect" class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>

Get children inside a controller to display on twig template

I've I'm trying to build a page which collects together all of the pages on a site and displays them along with their children (if they have any).
So far I've managed to make it loop through all of the top level pages and display them, however I'm getting some difficulty in making it display the child pages in the right place. It seems that I can either retrieve them but have them appear at the end or not retreive them at all.
I would need it to look like this:
<div id="parent" class="guide-item">
<h2>Parent</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
<div id="child" class="guide-item">
<h2>Child</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</div>
</div>
Here is my controller (including my attempt at getting it to loop through the children:
// Render pages from database
public function sectorAction($sector)
{
$em = $this->getDoctrine()->getManager();
$pages = $this->getDoctrine()
->getRepository('acmeStyleGuideBundle:pageSector')
->findBySectorJoinedToUrlTopLevel($sector);
if (!$pages) throw $this->createNotFoundException('Unable to find any matching sectors');
foreach ($pages as $page) {
if ($page->getChildPages()) {
$children = $this->getDoctrine()
->getRepository('acmeStyleGuideBundle:pageContent')
->findBySectorAndParent($sector, $page->getPageUrl());
}
}
return $this->render(
'acmeStyleGuideBundle:Page:pages.html.twig',
array(
'Pages' => $pages,
'Children' => $children,
'header' => $sector
)
);
}
Here are the relavent repositories:
public function findBySectorJoinedToUrlTopLevel($sector)
{
$query = $this->getEntityManager()
->createQuery('
SELECT p, s FROM acmeStyleGuideBundle:PageContent p
JOIN p.pageSector s
LEFT JOIN p.pageTypes t
WHERE s.sectorName = :sector
AND t.typeName != :type
AND p.parentPage IS NULL'
)
->setParameter('type', 'Section Headers')
->setParameter('sector', $sector);
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
public function findBySectorAndParent($sector, $pageParent)
{
$query = $this->getEntityManager()
->createQuery('
SELECT p, s, c FROM acmeStyleGuideBundle:PageContent p
JOIN p.pageSector s
LEFT JOIN p.pageTypes t
LEFT JOIN p.parentPage c
WHERE s.sectorName = :sector
AND p.pageUrl = :parent
AND t.typeName != :type'
)
->setParameter('sector', $sector)
->setParameter('parent', $pageParent)
->setParameter('type', 'Section Headers');
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
And here is my twig template where I want to output the code:
{% extends 'acmeStyleGuideBundle::landing.html.twig' %}
{% block definition %}
<article class="js-load pageLoad">
<h1>
{% if header is defined %}
{{ header | title }}
{% else %}
{{ Pages[0].pageName }}
{% endif %}
</h1>
{% for pe in Pages %}
<div id="{{ pe.pageUrl | lower}}" class="guide-item">
<h2>{{ pe.pageName }}</h2>
{{ pe.richText | raw }}
</div>
{% endfor %}
</article>
{% endblock %}
I thought that I might hae been able to loop through the children in the controller and then apply it to the twig template as 'children' instead of 'pages' but that didn't seem to work as the following code just constantly repeated the last element in the database which WASN'T a child page.:
{% for ce in Children %}
<div class="childPage">
<div id="{{ ce.pageUrl | lower}}" class="guide-item">
<h2>{{ ce.pageName }}</h2>
{{ ce.richText | raw }}
<div class="explanation">
<div class="card active">
<h3>Example</h3>
{# ce.example | raw#}
</div>
<div class="card">
<h3>Code Example</h3>
<pre name="code" class="{#ce.lang#}">{# ce.example #}</pre>
</div>
</div>
</div>
</div>
{% endfor %}
I've been looking at this problem for a while now so I probably can no longer see the wood for the trees so feel free to pull my code to pieces. I'm a learner at symfony/twig/doctrine anyway so I'll gladly take any feedback you care to give me.
I managed to find a solution.
Instead of trying to do everything in one controller and template, I created a new template called childpages.html.twig and put in this code:
{% for pe in Pages %}
<div id="{{ pe.pageUrl | lower}}" class="guide-item child">
<h2>{{ pe.pageName }}</h2>
{{ pe.richText | raw }}
{{ render(controller('acmeStyleGuideBundle:Page:findCodeExamples', { 'pageParent': pe.codeExample })) }}
</div>
{% endfor %}
I then wrote a new controller:
// Render children from database (Similar to showSubAction but without the $pageUrl requirement)
public function findChildrenAction($pageParent, $sector)
{
$em = $this->getDoctrine()->getManager();
$pages = $this->getDoctrine()
->getRepository('acmeStyleGuideBundle:PageSector')
->findBySectorJoinedToParent($sector, $pageParent);
return $this->render(
'acmeStyleGuideBundle:Page:childPages.html.twig',
array(
'Pages' => $pages,
'sector' => $sector
)
);
}
and referenced it in the place I needed it to appear in my original template:
{{
render(controller('acmeStyleGuideBundle:Page:findChildren', {
'pageParent': pe.id,
'sector': 'residential'
}))
}}
Not sure if it's the most elegant solution but it worked.

Resources