modalitor
Version:
A modern, accessible modal system with focus management, URL hash support, and rich animations
410 lines (364 loc) • 9.18 kB
CSS
/**
* @name Modalitor
* @author HamidReza Yazdani
* @license MIT
* @version 1.0.3
* @description A modern, accessible modal system with focus management, URL hash support, and rich animations
*/
/* Base modal styles */
.modalitor {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 0.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
display: none;
max-height: calc(100vh - 2rem);
overflow: hidden;
width: 100%;
margin: 1rem;
flex-direction: column;
overflow-y: auto;
}
.modalitor.active {
display: flex;
}
/* Modal sizes */
.modalitor-sm { max-width: 400px; }
.modalitor-md { max-width: 600px; }
.modalitor-lg { max-width: 800px; }
.modalitor-xl { max-width: 1000px; }
.modalitor-full {
width: 100%;
height: 100%;
max-height: 100vh;
margin: 0;
border-radius: 0;
}
/* Modal sections */
.modalitor-header {
padding: 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
position: sticky;
top: 0;
z-index: 1;
background: #fff;
}
.modalitor-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
line-height: 1.5;
}
.modalitor-content {
position: relative;
flex: 1 1 auto;
padding: 1rem;
overflow-y: auto;
}
.modalitor-footer {
padding: 0.75rem 1rem;
border-top: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
flex-shrink: 0;
position: sticky;
bottom: 0;
z-index: 1;
background: #fff;
}
/* Close button */
.modalitor-close {
position: relative;
padding: 0.5rem;
background: none;
border: none;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.375rem;
cursor: pointer;
color: #666;
transition: background-color 0.2s, color 0.2s;
}
.modalitor-close:hover {
background-color: rgba(0, 0, 0, 0.05);
color: #000;
}
.modalitor-close::before {
content: "×";
font-size: 1.5rem;
line-height: 1;
}
/* Overlay */
.modalitor-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: none;
}
.modalitor-overlay.active {
display: block;
}
/* Base Animation Setup */
.modalitor[class*="animation-"] {
opacity: 0;
display: flex ;
}
.modalitor[class*="animation-"].active {
opacity: 1;
}
/* Animation Keyframes */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes unfoldIn {
0% {
transform: translate(-50%, -50%) scaleY(.005) scaleX(0);
}
50% {
transform: translate(-50%, -50%) scaleY(.005) scaleX(1);
}
100% {
transform: translate(-50%, -50%) scaleY(1) scaleX(1);
}
}
@keyframes unfoldOut {
0% {
transform: translate(-50%, -50%) scaleY(1) scaleX(1);
}
50% {
transform: translate(-50%, -50%) scaleY(.005) scaleX(1);
}
100% {
transform: translate(-50%, -50%) scaleY(.005) scaleX(0);
}
}
@keyframes zoomIn {
from {
transform: translate(-50%, -50%) scale3d(0.3, 0.3, 0.3);
opacity: 0;
}
to {
transform: translate(-50%, -50%) scale3d(1, 1, 1);
opacity: 1;
}
}
@keyframes zoomOut {
from {
transform: translate(-50%, -50%) scale3d(1, 1, 1);
opacity: 1;
}
to {
transform: translate(-50%, -50%) scale3d(0.3, 0.3, 0.3);
opacity: 0;
}
}
@keyframes slideDown {
from { transform: translate(-50%, -100%); }
to { transform: translate(-50%, -50%); }
}
@keyframes slideUp {
from { transform: translate(-50%, 0%); }
to { transform: translate(-50%, -50%); }
}
@keyframes slideLeft {
from { transform: translate(-100%, -50%); }
to { transform: translate(-50%, -50%); }
}
@keyframes slideRight {
from { transform: translate(0%, -50%); }
to { transform: translate(-50%, -50%); }
}
@keyframes flipIn {
from {
transform: translate(-50%, -50%) perspective(400px) rotate3d(1, 0, 0, 60deg);
opacity: 0;
}
70% {
transform: translate(-50%, -50%) perspective(400px) rotate3d(1, 0, 0, -15deg);
opacity: 1;
}
to {
transform: translate(-50%, -50%) perspective(400px);
opacity: 1;
}
}
@keyframes flipOut {
from {
transform: translate(-50%, -50%) perspective(400px);
opacity: 1;
}
30% {
transform: translate(-50%, -50%) perspective(400px) rotate3d(1, 0, 0, -15deg);
opacity: 1;
}
to {
transform: translate(-50%, -50%) perspective(400px) rotate3d(1, 0, 0, 45deg);
opacity: 0;
}
}
@keyframes rotateIn {
from {
transform: translate(-50%, -50%) rotate3d(0, 0, 1, -180deg) scale3d(0.3, 0.3, 0.3);
opacity: 0;
}
to {
transform: translate(-50%, -50%) rotate3d(0, 0, 1, 0) scale3d(1, 1, 1);
opacity: 1;
}
}
@keyframes rotateOut {
from {
transform: translate(-50%, -50%) rotate3d(0, 0, 1, 0) scale3d(1, 1, 1);
opacity: 1;
}
to {
transform: translate(-50%, -50%) rotate3d(0, 0, 1, 180deg) scale3d(0.3, 0.3, 0.3);
opacity: 0;
}
}
@keyframes doorIn {
from {
transform: translate(-50%, -50%) scale3d(0, 1, 1);
opacity: 0;
}
to {
transform: translate(-50%, -50%) scale3d(1, 1, 1);
opacity: 1;
}
}
@keyframes doorOut {
from {
transform: translate(-50%, -50%) scale3d(1, 1, 1);
opacity: 1;
}
60% {
transform: translate(-50%, -50%) scale3d(0.01, 1, 1);
opacity: 1;
}
to {
transform: translate(-50%, -50%) scale3d(0, 1, 0.1);
opacity: 0;
}
}
@keyframes roadRunnerIn {
0% {
transform: translate(-1500px, -50%) skewX(30deg) scaleX(1.3);
}
70% {
transform: translate(-30px, -50%) skewX(0deg) scaleX(.9);
}
100% {
transform: translate(-50%, -50%) skewX(0deg) scaleX(1);
}
}
@keyframes roadRunnerOut {
0% {
transform: translate(-50%, -50%) skewX(0deg) scaleX(1);
}
30% {
transform: translate(-30px, -50%) skewX(-5deg) scaleX(.9);
}
100% {
transform: translate(1500px, -50%) skewX(30deg) scaleX(1.3);
}
}
/* Animation Classes */
.modalitor.animation-fade {
&.active { animation: fadeIn 0.3s forwards; }
&.closing { animation: fadeOut 0.3s forwards; }
}
.modalitor.animation-unfold {
&.active { animation: unfoldIn 0.6s cubic-bezier(0.165, 0.840, 0.440, 1.000) forwards; }
&.closing { animation: unfoldOut 0.6s cubic-bezier(0.165, 0.840, 0.440, 1.000) forwards; }
}
.modalitor.animation-zoom {
&.active { animation: zoomIn 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: zoomOut 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
}
.modalitor.animation-slide-down {
&.active { animation: slideDown 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: slideUp 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
}
.modalitor.animation-slide-up {
&.active { animation: slideUp 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: slideDown 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
}
.modalitor.animation-slide-left {
&.active { animation: slideLeft 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: slideRight 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
}
.modalitor.animation-slide-right {
&.active { animation: slideRight 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: slideLeft 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
}
.modalitor.animation-flip {
perspective: 1300px;
&.active {
animation: flipIn 0.6s ease-in forwards;
backface-visibility: visible ;
}
&.closing {
animation: flipOut 0.6s ease-out forwards;
backface-visibility: visible ;
}
}
.modalitor.animation-rotate {
&.active {
animation: rotateIn 0.6s ease forwards;
transform-origin: center;
}
&.closing {
animation: rotateOut 0.6s ease forwards;
transform-origin: center;
}
}
.modalitor.animation-door {
&.active { animation: doorIn 0.5s cubic-bezier(0.4, 0, 0, 1.5) forwards; }
&.closing { animation: doorOut 0.5s ease forwards; }
}
.modalitor.animation-roadrunner {
&.active { animation: roadRunnerIn 0.8s cubic-bezier(0.36, 0, 0.66, -0.56) forwards; }
&.closing { animation: roadRunnerOut 0.5s ease forwards; }
}
/* Responsive adjustments */
@media (max-width: 640px) {
.modalitor {
margin: 1rem;
max-height: calc(100vh - 2rem);
}
.modalitor-sm,
.modalitor-md,
.modalitor-lg,
.modalitor-xl {
max-width: calc(100vw - 2rem);
}
}
/* Mobile animations override */
@media (max-width: 1024px) {
.modalitor[class*="animation-"] {
&.active { animation: slideUp 0.4s ease forwards; }
&.closing { animation: slideDown 0.4s ease forwards; }
}
}