Skip to content

Instantly share code, notes, and snippets.

@neisdev
Created December 30, 2021 14:29
Show Gist options
  • Save neisdev/50fd20faebc5b0982e6ff74107e8a6a7 to your computer and use it in GitHub Desktop.
Save neisdev/50fd20faebc5b0982e6ff74107e8a6a7 to your computer and use it in GitHub Desktop.
Flexbox Grid Cards with Action Menus

Flexbox Grid Cards with Action Menus

A card pattern for a user dashboard using flexbox for grid layout. Keyboard accessible, a11y friendly, and built using Progressive Enhancement.

A Pen by Jerry Jones on CodePen.

License.

<!-- symbol defs-->
<svg style="position: absolute; width: 0; height: 0;" width="0" height="0" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<svg id="icon-chevron" viewBox="0 0 1024 1024">
<title>Click to Show or Hide Menu</title>
<path class="path1" d="M316 334l196 196 196-196 60 60-256 256-256-256z"></path>
</svg>
</defs>
</svg>
<ul class="dash-list">
<li id="dash-item--1" class="dash-item dash-item--published">
<div class="dash-item__header">
<h3 class="dash-item__title"><a href="#">Semantic HTML Quiz</a></h3>
<ul id="dash-item__nav--1" class="dash-item__nav">
<li class="dash-item__nav__item"><a href="#">Results</a></li><li class="dash-item__nav__item"><a href="#">Settings</a></li><li class="dash-item__nav__item"><a href="#">Embed</a></li><li class="dash-item__nav__item"><a href="#">Delete</a></li>
</ul>
</div>
<div class="dash-item__content">
<ul class="quiz-results">
<li class="quiz-results__item quiz-results__item--views">
<span class="quiz-results__number quiz-results__number--views">832</span>
<div class="quiz-results__label">Views</div>
</li>
<li class="quiz-results__item quiz-results__item--finishes">
<span class="quiz-results__number quiz-results__number--finishes">588</span>
<div class="quiz-results__label">Finishes</div>
</li>
<li class="quiz-results__item quiz-results__item--average-score">
<span class="quiz-results__number quiz-results__number--average-score">80</span>
<div class="quiz-results__label">Average</div>
</li>
</ul>
</div>
</li>
<li id="dash-item--2" class="dash-item dash-item--published">
<div class="dash-item__header">
<h3 class="dash-item__title"><a href="#">How well do you know CSS?</a></h3>
<ul id="dash-item__nav--2" class="dash-item__nav">
<li class="dash-item__nav__item"><a href="#">Results</a></li><li class="dash-item__nav__item"><a href="#">Settings</a></li><li class="dash-item__nav__item"><a href="#">Embed</a></li><li class="dash-item__nav__item dash-item__nav__item--delete"><a href="#">Delete</a></li>
</ul>
</div>
<div class="dash-item__content">
<ul class="quiz-results">
<li class="quiz-results__item quiz-results__item--views">
<span class="quiz-results__number quiz-results__number--views">938</span>
<div class="quiz-results__label">Views</div>
</li>
<li class="quiz-results__item quiz-results__item--finishes">
<span class="quiz-results__number quiz-results__number--finishes">856</span>
<div class="quiz-results__label">Finishes</div>
</li>
<li class="quiz-results__item quiz-results__item--average-score">
<span class="quiz-results__number quiz-results__number--average-score">90</span>
<div class="quiz-results__label">Average</div>
</li>
</ul>
</div>
</li>
<li id="dash-item--3" class="dash-item dash-item--draft">
<div class="dash-item__header">
<h3 class="dash-item__title"><a href="#">Draft: Test Your JS Skills</a></h3>
<ul id="dash-item__nav--3" class="dash-item__nav">
<li class="dash-item__nav__item"><a href="#">Edit</a></li><li class="dash-item__nav__item"><a href="#">Preview</a></li><li class="dash-item__nav__item"><a href="#">Delete</a></li>
</ul>
</div>
<div class="dash-item__content">
<ul class="quiz-results">
<li class="quiz-results__item quiz-results__item--views">
<span class="quiz-results__number quiz-results__number--views">0</span>
<div class="quiz-results__label">Views</div>
</li>
<li class="quiz-results__item quiz-results__item--finishes">
<span class="quiz-results__number quiz-results__number--finishes">0</span>
<div class="quiz-results__label">Finishes</div>
</li>
<li class="quiz-results__item quiz-results__item--average-score">
<span class="quiz-results__number quiz-results__number--average-score">0</span>
<div class="quiz-results__label">Average</div>
</li>
</ul>
</div>
</li>
</ul>
<p><a class="twitter__username-link" href="https://twitter.com/juryjowns" target="_top">@juryjowns</a></p>
jQuery( document ).ready( function( $ ) {
// add our class to the action menu to set the styles if javascript is enabled
$('.dash-item__nav').each(function() {
$(this).addClass('dash-item__nav--collapsible')
.attr('aria-hidden', true)
.before('<button class="dash-item__menu-action" type="button" aria-expanded="false" aria-controls="'+$(this).attr('id')+'"><svg class="dash-item__menu-action__icon dash-item__menu-action__icon--bottom"><use xlink:href="#icon-chevron" /></svg><svg class="dash-item__menu-action__icon dash-item__menu-action__icon--top"><use xlink:href="#icon-chevron" /></svg></button>');
});
$(document).on('click', '.dash-item__menu-action', function() {
var dashItem = $(this).closest('.dash-item');
if(dashItem.hasClass('dash-item--menu-active')) {
// remove states from the active menu item
removeActiveMenuStates(dashItem);
$('body').removeClass('dash-list--focus-one');
} else {
// remove states from any active menu item, if there is one
var previouslyActiveMenu = $('.dash-item--menu-active');
if(0 < previouslyActiveMenu.length ) {
removeActiveMenuStates(previouslyActiveMenu);
}
// add in the active states to the clicked dashItem
addActiveMenuStates(dashItem);
// add a class to the body telling that there's a dashItem active
$('body').addClass('dash-list--focus-one');
// move focus to first item in newly opened menu
$('.dash-item__nav__item:eq(0) a', dashItem).focus();
}
});
function addActiveMenuStates(dashItem) {
// add the new active states in
dashItem.addClass('dash-item--menu-active');
// button to activate the menu
$('.dash-item__menu-action', dashItem).attr('aria-expanded', true);
// menu
$('.dash-item__nav', dashItem).attr('aria-hidden', false);
}
function removeActiveMenuStates(dashItem) {
// dash item card
dashItem.removeClass('dash-item--menu-active');
// button to activate the menu
$('.dash-item__menu-action', dashItem).attr('aria-expanded', false)
// menu
$('.dash-item__nav', dashItem).attr('aria-hidden', true);
}
});
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
$green: darken(#58C88F, 10);
$blue: darken(#5887C0, 7.5);
$link: $blue;
$fastInEaseOut: cubic-bezier(0.000, 0, .3, 1);
body {
background: #e9f3f0;
max-width: 70rem;
margin: 0 auto;
padding: 2rem 20px 3rem;
font-family: "ff-tisa-sans-web-pro", Trebuchet MS,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Tahoma, sans-serif;
transition: all .2s $fastInEaseOut;
}
a {
color: $link;
text-decoration: none;
transition: color .2s;
touch-action: manipulation;
&:focus {
outline: 1px dotted $link;
}
&:hover,
&:focus {
color: darken($link, 12.5);
}
}
.dash-list {
display: flex;
flex-flow: row wrap;
align-items: stretch;
list-style: none;
margin-left: 0;
}
.dash-item {
display: flex;
flex-direction: column;
align-content: flex-end;
justify-content: flex-end;
padding: 1.2rem 1.4rem 1rem;
background: #fff;
list-style: none;
transition: all .5s;
margin: 0 0 0.8rem;
position: relative;
overflow: hidden;
transition: all .2s $fastInEaseOut;
@media (min-width: 500px) {
flex-basis: 49%;
margin: 0 2% 0.8rem 0;
&:nth-child(2n) {
margin-right: 0;
}
}
@media (min-width: 800px) {
flex-basis: 28%;
margin-right: 1.25%;
&:nth-child(2n) {
margin-right: 1.25%;
}
&:nth-child(3n) {
margin-right: 0;
}
}
}
.dash-item--published {
box-shadow: inset 4px 0 0 $green, 0 1px 0 rgba(0,0,0,.1);
}
.dash-item--draft {
box-shadow: inset 4px 0 0 lighten($green, 25%), 0 1px 0 rgba(0,0,0,.1);
}
.dash-item__header {
position: relative;
z-index: 99;
background: #fff;
border-bottom: 1px solid #ddd;
}
.dash-item__title {
font-size: 1rem;
padding: 0 24px 0.5rem 0;
margin: 0;
}
.dash-item__content {
padding: 1.8rem 0 0.375rem;
position: relative;
transition: all .2s $fastInEaseOut;
}
.dash-item__status {
font-size: .7rem;
font-weight: 300;
text-transform: uppercase;
color: #888;
position: absolute;
}
.dash-item__nav {
position: absolute;
right: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
.dash-item__nav__item {
display: inline-block;
font-size: 0.85rem;
margin-bottom: 0;
padding-bottom: 0;
margin-right: 10px;
&:last-of-type {
margin-right: 0;
}
}
.dash-item__nav--collapsible {
display: none;
position: absolute;
background: #fff;
bottom: auto;
right: auto;
top: 100%;
margin-top: 0.2rem;
transform: translate3d(0,0,0);
transition: all .2s $fastInEaseOut;
.dash-item__nav__item {
padding: 0.4rem 0;
}
}
.dash-item__menu-action {
background: none;
border: none;
color: #444;
padding: 0;
position: absolute;
bottom: 0;
right: 0;
cursor: pointer;
}
.dash-item__menu-action__icon {
width: 24px;
height: 24px;
fill: #444;
transition: all .3s $fastInEaseOut;
}
.dash-item__menu-action__icon--bottom {
position: absolute;
left: 0;
}
.dash-list--focus-one {
background: #bcd4ce;
.dash-item {
opacity: 0.675;
}
.dash-item--menu-active {
transform: translate3d(0,-3px,0);
box-shadow: inset 4px 0 0 $green, 0 2px 2px rgba(0,0,0,.2);
opacity: 1;
}
}
.dash-item--menu-active {
.dash-item__menu-action__icon--bottom {
transform: translateY(-15.5%);
}
.dash-item__menu-action__icon--top {
transform: rotateX(-180deg) translateY(0px);
}
.dash-item__nav--collapsible {
display: block;
animation: slideInTop .25s $fastInEaseOut forwards;
}
.dash-item__content {
opacity: 0.4;
transform: translate3d(0, 0.675rem, 0);
}
}
.quiz-results {
display: flex;
justify-content: space-between;
list-style: none;
margin: 0;
padding: 0;
text-align: center;
}
.quiz-results__item {
margin: 0 2% 0 0;
padding: 0;
color: #454545;
@media (max-width:280px) {
width: 100%;
padding: 0;
margin-bottom: 0.6rem;
}
}
.quiz-results__number {
font-size: 1.6rem;
line-height: 1;
position: relative;
}
.dash-item--draft .quiz-results__number {
opacity: 0.5;
}
.quiz-results__number--average-score {
color: darken($green, 8);
&:after {
content: '%';
font-size: .9rem;
position: absolute;
top: .3rem;
right: -0.6rem;
}
}
.quiz-results__label {
text-transform: uppercase;
font-size: .7rem;
font-weight: 300;
color: #888;
}
@keyframes slideInTop {
0% {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.twitter__username-link {
position: relative;
top: 2rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment