<section class="card">
<h2 class="card__header">Markets</h2>
<ul class="accordion accordion-js">
<li class="accordion__item">
<h3 class="accordion-item__title">
<button
data-accordion-control="text"
data-accordion-expanded="false"
class="accordion-item__button"
>
Total Points
<span class="accordion-item__arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="8"
viewBox="0 0 14 8"
fill="none"
>
<path
d="M1 1L7 7L13 1"
stroke="white"
stroke-width="2"
stroke-linejoin="round"
/>
</svg>
</span>
</button>
</h3>
<div
class="accordion-item__content"
data-accordion-expanded="false"
data-accordion-content="text"
hidden
>
<ul class="user-list">
<li class="user-list__item">
Jacob Mullins
<span class="user-list__budget">$4.42</span>
</li>
<li class="user-list__item">
Frank Scott
<span class="user-list__budget">$0.62</span>
</li>
<li class="user-list__item">
Brad Butter
<span class="user-list__budget">$1.89</span>
</li>
<li class="user-list__item">
Sasha Avocado
<span class="user-list__budget">$3.55</span>
</li>
<li class="user-list__item">
Mike Bad
<span class="user-list__budget">$2.12</span>
</li>
<li class="user-list__item">
Anna Milk
<span class="user-list__budget">$2.31</span>
</li>
</ul>
</div>
</li>
<!-- <li class="accordion__item">
<h3 class="accordion-item__title">
<button
data-accordion-control="text2"
data-accordion-expanded="false"
class="accordion-item__button"
>
Total Points
<span class="accordion-item__arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="8"
viewBox="0 0 14 8"
fill="none"
>
<path
d="M1 1L7 7L13 1"
stroke="white"
stroke-width="2"
stroke-linejoin="round"
/>
</svg>
</span>
</button>
</h3>
<div
class="accordion-item__content"
data-accordion-content="text2"
data-accordion-expanded="false"
hidden
>
<ul class="user-list">
<li class="user-list__item">
Jacob Mullins
<span class="user-list__budget">$4.42</span>
</li>
<li class="user-list__item">
Frank Scott
<span class="user-list__budget">$0.62</span>
</li>
<li class="user-list__item">
Brad Butter
<span class="user-list__budget">$1.89</span>
</li>
<li class="user-list__item">
Sasha Avocado
<span class="user-list__budget">$3.55</span>
</li>
<li class="user-list__item">
Mike Bad
<span class="user-list__budget">$2.12</span>
</li>
<li class="user-list__item">
Anna Milk
<span class="user-list__budget">$2.31</span>
</li>
</ul>
</div>
</li>
<li class="accordion__item">
<h3 class="accordion-item__title">
<button
data-accordion-control="text3"
class="accordion-item__button"
data-accordion-expanded="false"
>
Total Points
<span class="accordion-item__arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="8"
viewBox="0 0 14 8"
fill="none"
>
<path
d="M1 1L7 7L13 1"
stroke="white"
stroke-width="2"
stroke-linejoin="round"
/>
</svg>
</span>
</button>
</h3>
<div
class="accordion-item__content"
data-accordion-content="text3"
data-accordion-expanded="false"
hidden
>
<ul class="user-list">
<li class="user-list__item">
Jacob Mullins
<span class="user-list__budget">$4.42</span>
</li>
<li class="user-list__item">
Frank Scott
<span class="user-list__budget">$0.62</span>
</li>
<li class="user-list__item">
Brad Butter
<span class="user-list__budget">$1.89</span>
</li>
<li class="user-list__item">
Sasha Avocado
<span class="user-list__budget">$3.55</span>
</li>
<li class="user-list__item">
Mike Bad
<span class="user-list__budget">$2.12</span>
</li>
<li class="user-list__item">
Anna Milk
<span class="user-list__budget">$2.31</span>
</li>
</ul>
</div>
</li> -->
</ul>
<div class="control">
<input type="checkbox" id="contain" />
<label for="contain">contain: size</label>
</div>
</section>
html {
height: 100%;
}
body {
margin: 0;
padding: 0;
line-height: 1.6;
font-size: 16px;
font-family: 'Poppins', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-color: #f6483a;
overflow-x: hidden;
color: #41413b;
}
.card {
background-color: #f0f0f6;
border-radius: 8px;
padding: 32px 24px 68px;
box-sizing: border-box;
width: 340px;
position: relative;
}
.card__header {
margin: 0;
font-size: 16px;
line-height: 1;
margin-bottom: 1em;
}
.accordion {
list-style: none;
margin: 0;
padding: 0;
}
.accordion__item:not(:last-of-type) {
margin-bottom: 12px;
}
.accordion-item__title {
margin: 0;
}
.accordion-item__button {
display: flex;
background-color: #fff;
border-radius: 4px;
border: 0;
width: 100%;
cursor: pointer;
padding: 12px;
position: relative;
font-weight: 600;
font-family: inherit;
color: inherit;
justify-content: space-between;
align-items: center;
z-index: 1;
&[data-accordion-expanded='true'] {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
box-shadow: 0px 7px 12px 0px rgba(0, 0, 0, 0.1);
}
}
.accordion-item__arrow {
background-color: #41413b;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
position: relative;
align-items: center;
justify-content: center;
& > svg {
transform: translateY(1px);
}
}
.accordion-item__button[data-accordion-control-animation='true']
.accordion-item__arrow {
transition: 250ms background-color ease, 250ms transform ease;
will-change: transform;
}
.accordion-item__button[data-accordion-expanded='true'] .accordion-item__arrow {
background-color: #f6483a;
transform: rotate(180deg);
}
.accordion-item__content {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background-color: #fff;
padding: 0 12px;
}
.accordion-item__content[data-accordion-expanded='true'] {
margin-top: 4px;
}
.accordion-item__content[data-accordion-content-animation] {
transition-property: height;
transition-duration: 300ms;
transition-timing-function: ease-in-out;
overflow: hidden;
}
.user-list {
list-style: none;
padding: 0;
margin: 0;
font-weight: 300;
}
.user-list__item {
display: flex;
font-size: 0.9em;
justify-content: space-between;
align-items: center;
padding: 10px 0;
&:not(:last-of-type) {
border-bottom: 1px solid #f0f0f6;
}
}
.user-list__budget {
background-color: #46c581;
padding: 2px 12px;
border-radius: 4px;
font-weight: 400;
color: #fff;
min-width: 44px;
text-align: center;
}
.control {
position: absolute;
left: 0;
right: 0;
bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.accordion__item {
border: 1px dashed #015fcc;
border-radius: 4px;
box-shadow: 0 0px 1px 1px #015fcc;
}
.size {
contain: size;
}
View Compiled
interface Subject {
subscribe(observer: AccordionObserver): void;
unsubscribe(observer: AccordionObserver): void;
notify(): void;
}
class Accordion implements Subject {
private observers: Set<AccordionObserver> = new Set<AccordionObserver>();
public activeItem: string = '';
subscribe(observer: AccordionObserver): void {
const inObserverExists = this.observers.has(observer);
if (inObserverExists) {
return;
}
this.observers.add(observer);
}
unsubscribe(observer: AccordionObserver): void {
const inObserverExists = this.observers.has(observer);
if (inObserverExists) {
return;
}
this.observers.delete(observer);
}
notify() {
for (const observer of Array.from(this.observers)) {
observer.update(this);
}
}
updateActiveItem(activeItem: string): void {
this.activeItem = this.activeItem === activeItem ? '' : activeItem;
this.notify();
}
}
abstract class AccordionObserver {
abstract update(context: Accordion): void;
static controlAttribute = 'data-accordion-control';
static contentAttribute = 'data-accordion-content';
public mainElement: HTMLElement = null;
constructor(mainItem: string) {
this.mainElement = document.querySelector(mainItem);
}
protected getActiveControl(activeItem: string): HTMLElement | null {
if (!activeItem) {
return null;
}
const activeControlSelector = `[${AccordionObserver.controlAttribute}=${activeItem}]`;
return this.mainElement.querySelector(activeControlSelector);
}
protected getActiveContent(activeItem: string): HTMLElement | null {
if (!activeItem) {
return null;
}
const activeContentSelector = `[${AccordionObserver.contentAttribute}=${activeItem}]`;
return this.mainElement.querySelector(activeContentSelector);
}
protected get controls(): HTMLElement[] {
const controlsSelector = `[${AccordionObserver.controlAttribute}]`;
return Array.from(this.mainElement.querySelectorAll(controlsSelector));
}
protected get contents(): HTMLElement[] {
const contentsSelector = `[${AccordionObserver.contentAttribute}]`;
return Array.from(this.mainElement.querySelectorAll(contentsSelector));
}
static addEventListener(mainItem: string, accordion: Accordion) {
const mainElement = document.querySelector(mainItem);
const controlsSelector = `[${AccordionObserver.controlAttribute}]`;
const controls = mainElement.querySelectorAll(controlsSelector);
Array.from(controls).forEach((control) => {
control.addEventListener('click', (e: MouseEvent) => {
const targetElement = e.target as HTMLElement;
const controlValue = targetElement.getAttribute(
AccordionObserver.controlAttribute
);
if (!controlValue) {
return;
}
accordion.updateActiveItem(controlValue);
});
});
}
}
class AccordionInterfaceObserver extends AccordionObserver {
update(context: Accordion): void {
this.updateInterface(context.activeItem);
}
private resetInterface() {
this.contents.forEach((content) => {
content.setAttribute('hidden', 'true');
content.setAttribute('data-accordion-expanded', 'false');
});
this.controls.forEach((controls) => {
controls.setAttribute('data-accordion-expanded', 'false');
});
}
private updateInterface(activeItem: string): void {
const activeContent = this.getActiveContent(activeItem);
const activeControl = this.getActiveControl(activeItem);
this.resetInterface();
if (!activeContent) {
return;
}
activeContent.removeAttribute('hidden');
activeControl.setAttribute('data-accordion-expanded', 'true');
activeContent.setAttribute('data-accordion-expanded', 'true');
}
}
class AccordionAriaObserver extends AccordionObserver {
constructor(mainItem: string) {
super(mainItem);
this.initAriaAttributes();
}
update(context: Accordion): void {
this.updateAria(context.activeItem);
}
private initAriaAttributes(): void {
this.controls.forEach((control) => {
const controlValue = control.getAttribute('data-accordion-control');
control.setAttribute('aria-expanded', 'false');
control.setAttribute('aria-controls', controlValue);
});
this.contents.forEach((content) => {
const contentValue = content.getAttribute('data-accordion-content');
content.setAttribute('id', contentValue);
});
}
private resetAria(): void {
this.controls.forEach((control) => {
control.setAttribute('aria-expanded', 'false');
});
}
private updateAria(activeItem: string): void {
const activeControl = this.getActiveControl(activeItem);
this.resetAria();
if (!activeControl) {
return;
}
activeControl.setAttribute('aria-expanded', 'true');
}
}
class AccordionAnimationObserver extends AccordionObserver {
static CONTROLS_ANIMATION_ATTR: string = 'data-accordion-control-animation';
static CONTENT_ANIMATION_ATTR: string = 'data-accordion-content-animation';
prevContent: string;
constructor(mainItem: string) {
super(mainItem);
this.prevContent = '';
this.setControlAnimation();
this.setContentAnimation();
}
update(context: Accordion): void {
this.animate(context.activeItem);
}
private setControlAnimation(): void {
this.controls.forEach((control) => {
control.setAttribute(
AccordionAnimationObserver.CONTROLS_ANIMATION_ATTR,
'true'
);
});
}
private setContentAnimation(): void {
this.contents.forEach((control) => {
control.setAttribute(
AccordionAnimationObserver.CONTENT_ANIMATION_ATTR,
'false'
);
});
}
private animateActiveContent(activeItem: string): void {
const activeContent = this.getActiveContent(activeItem);
if (!activeContent) {
return;
}
const animateToHeight = activeContent.offsetHeight;
activeContent.style.height = '0px';
requestAnimationFrame(() => {
requestAnimationFrame(() => {
activeContent.style.height = `${animateToHeight}px`;
activeContent.setAttribute(
AccordionAnimationObserver.CONTENT_ANIMATION_ATTR,
'true'
);
});
});
}
private hidePreviousContent(): void {
if (!this.prevContent) {
return;
}
const activePrevContent = this.getActiveContent(this.prevContent);
activePrevContent.removeAttribute('hidden');
activePrevContent.style.height = '0px';
activePrevContent.setAttribute(
AccordionAnimationObserver.CONTENT_ANIMATION_ATTR,
'false'
);
activePrevContent.addEventListener(
'transitionend',
function hidePrevContent() {
activePrevContent.setAttribute('hidden', 'true');
activePrevContent.style.height = '';
activePrevContent.removeEventListener(
'transitionend',
hidePrevContent
);
}
);
}
private animate(activeItem: string): void {
this.hidePreviousContent();
this.animateActiveContent(activeItem);
this.prevContent = activeItem;
}
}
const accordion = new Accordion();
const accordionInterfaceObserver = new AccordionInterfaceObserver(
'.accordion-js'
);
const accordionAriaObserver = new AccordionAriaObserver('.accordion-js');
const accordionAnimationObserver = new AccordionAnimationObserver(
'.accordion-js'
);
accordion.subscribe(accordionInterfaceObserver);
accordion.subscribe(accordionAriaObserver);
accordion.subscribe(accordionAnimationObserver);
AccordionObserver.addEventListener('.accordion-js', accordion);
const checkBox = document.getElementById('contain')
const itemElement = document.querySelector('.accordion__item')
checkBox.addEventListener('input', (etv) => {
console.log(etv.target.checked)
if (etv.target.checked) {
itemElement.classList.add('size')
} else {
itemElement.classList.remove('size')
}
})
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.