dev21w36a

This commit is contained in:
Victor Westerlund 2021-09-06 15:33:57 +02:00
parent 6d31cd8e0d
commit ff474e1786
7 changed files with 155 additions and 58 deletions

View file

@ -4,10 +4,6 @@ body {
transition: var(--transition) background-color; transition: var(--transition) background-color;
} }
body.modalActive {
background-color: var(--color-contrast);
}
body main .screen { body main .screen {
transition: var(--transition) transform; transition: var(--transition) transform;
} }
@ -22,6 +18,7 @@ body.modalActive main .screen {
/* -- Boilerplate -- */ /* -- Boilerplate -- */
.modal { .modal {
transition: var(--transition) background-color;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@ -29,8 +26,37 @@ body.modalActive main .screen {
height: 100vh; height: 100vh;
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
box-sizing: border-box;
background-color: rgba(var(--comp-inverted),0);
padding: var(--padding);
} }
.modal.active { .modal.active {
pointer-events: all; pointer-events: all;
background-color: rgba(var(--comp-inverted),.4);
}
.modal .button {
background-color: rgba(var(--comp-inverted),.3);
}
/* -- Cards -- */
.modal.card .inner {
transition: var(--transition) transform, var(--transition) opacity;
position: relative;
background-color: var(--color-contrast);
width: calc(100vw - var(--padding));
max-width: 600px;
align-self: flex-end;
box-sizing: border-box;
padding: var(--padding);
transform: translateY(1vh);
border-radius: var(--border-radius);
opacity: 0;
}
.modal.card.active .inner {
transform: translateY(0);
opacity: 1;
} }

View file

@ -2,10 +2,12 @@
:root { :root {
--comp-background: 255,255,255; --comp-background: 255,255,255;
--comp-inverted: 0,0,0;
--comp-contrast: 33,33,33; --comp-contrast: 33,33,33;
--comp-accent: 22,183,255; --comp-accent: 22,183,255;
--color-background: rgb(var(--comp-background)); --color-background: rgb(var(--comp-background));
--color-inverted: rgb(var(--comp-inverted));
--color-contrast: rgb(var(--comp-contrast)); --color-contrast: rgb(var(--comp-contrast));
--color-accent: rgb(var(--comp-accent)); --color-accent: rgb(var(--comp-accent));
@ -17,10 +19,12 @@
.dark { .dark {
--comp-background: 33,33,33; --comp-background: 33,33,33;
--comp-inverted: 255,255,255;
--comp-contrast: 255,255,255; --comp-contrast: 255,255,255;
--comp-accent: 255,255,255; --comp-accent: 255,255,255;
--color-background: rgb(var(--comp-background)); --color-background: rgb(var(--comp-background));
--color-inverted: rgb(var(--comp-inverted));
--color-contrast: rgb(var(--comp-contrast)); --color-contrast: rgb(var(--comp-contrast));
--color-accent: rgb(var(--comp-accent)); --color-accent: rgb(var(--comp-accent));
} }
@ -228,8 +232,7 @@ header .logo {
} }
.screen.landingpage img { .screen.landingpage img {
width: 100%; width: clamp(100px,80vw,40vh);
max-width: 300px;
align-self: flex-end; align-self: flex-end;
} }

View file

@ -0,0 +1,34 @@
// Copyright © Victor Westerlund - No libraries! 😲
// UI component constructor
class Component {
constructor(tag) {
this.element = document.createElement(tag); // Root element
}
getElement() {
return this.element;
}
}
// ⬇ UI Components ⬇
export class Button extends Component {
constructor(properties) {
super("div");
this.element.classList.add("button");
this.setText(properties.text);
this.setAction(properties.action);
}
setText(text) {
const textElement = document.createElement("p");
textElement.innerText = text;
this.element.appendChild(textElement);
}
setAction(action) {
this.element.setAttribute("data-action",action);
}
}

View file

@ -0,0 +1,22 @@
// Copyright © Victor Westerlund - No libraries! 😲
class Debug {
constructor() {
console.log("Debug mode is enabled.\nList debug functions by running window._debug.list()");
}
list() {
const functions = [
"list",
"openContactsModal"
];
console.log("Available functions:",functions.map(f => `window._debug.${f}();`));
}
openContactsModal() {
document.getElementsByClassName("hamburger")[0].click();
document.querySelector("div[data-action='openContactCard']").click();
}
}
export default window._debug = new Debug();

View file

@ -1,6 +1,7 @@
// Copyright © Victor Westerlund - No libraries! 😲 // Copyright © Victor Westerlund - No libraries! 😲
import { default as Interaction, destroy } from "./UI.mjs"; import { default as Interaction, destroy } from "./UI.mjs";
import { Button } from "./Components.mjs";
// Boilerplate for creating element overlays // Boilerplate for creating element overlays
class Modal extends Interaction { class Modal extends Interaction {
@ -11,26 +12,19 @@ class Modal extends Interaction {
this.close(); this.close();
} }
}; };
// Combine template and incoming interactions into one object
interactions = Object.assign(interactions,extendedInteractions); interactions = Object.assign(interactions,extendedInteractions);
super(element,interactions); super(interactions,element);
this.element = this.applyTemplate(element); this.element = this.applyTemplate(element);
this.importStyleSheet(); this.importStyleSheet();
document.body.appendChild(this.element);
} }
// Import the companion CSS rules // Import the companion CSS rules
importStyleSheet() { importStyleSheet() {
let sheet = "css/modal.css"; let sheet = "assets/css/modal.css";
const element = document.createElement("link");
// Import stylesheet with CSS module script if supported
if(document.adoptedStyleSheets) {
sheet = "../../" + sheet;
const module = import(sheet, {
assert: { type: "css" }
});
module.then(style => document.adoptedStyleSheets = [style.default]);
return;
}
// Exit if the stylesheet has already been imported // Exit if the stylesheet has already been imported
if(document.head.querySelector("link[data-async-modalcss]")) { if(document.head.querySelector("link[data-async-modalcss]")) {
@ -38,8 +32,6 @@ class Modal extends Interaction {
} }
// Import the stylesheet with a link tag // Import the stylesheet with a link tag
sheet = "assets/" + sheet;
const element = document.createElement("link");
element.setAttribute("rel","stylesheet"); element.setAttribute("rel","stylesheet");
element.setAttribute("href",sheet); element.setAttribute("href",sheet);
element.setAttribute("data-async-modalcss",""); element.setAttribute("data-async-modalcss","");
@ -49,20 +41,24 @@ class Modal extends Interaction {
// Apply a modal template to the provided element // Apply a modal template to the provided element
applyTemplate(element) { applyTemplate(element) {
// The inner div will contain modal content // The inner div will contain modal content
const inner = document.createElement("div"); this.inner = document.createElement("div");
inner.classList.add("inner"); this.inner.classList.add("inner");
element.appendChild(inner); element.appendChild(this.inner);
element.classList.add("modal"); element.classList.add("modal");
// PointerEvents on the outer div will close the modal // PointerEvents on the outer div will close the modal
element.addEventListener("click",() => this.close()); element.addEventListener("click",event => {
if(event.target == element) {
this.close();
}
});
return element; return element;
} }
open() { open() {
document.body.classList.add("modalActive"); document.body.classList.add("modalActive");
this.element.classList.add("active"); setTimeout(() => this.element.classList.add("active"),this.transition / 2);
} }
// Close the modal and remove it from the DOM // Close the modal and remove it from the DOM
@ -70,7 +66,7 @@ class Modal extends Interaction {
const activeModals = document.getElementsByClassName("modal"); const activeModals = document.getElementsByClassName("modal");
if(!activeModals || activeModals.length === 1) { if(!activeModals || activeModals.length === 1) {
// Remove active effects if all modals have been closed // Remove active effects if all modals have been closed
document.body.classList.remove("modalActive"); setTimeout(() => document.body.classList.remove("modalActive"),this.transition / 2);
} }
this.element.classList.remove("active"); this.element.classList.remove("active");
@ -91,9 +87,17 @@ export class Card extends Modal {
this.element.insertAdjacentHTML("beforeend",content); this.element.insertAdjacentHTML("beforeend",content);
} }
init() { init(slim) {
this.element.classList.add("card"); this.element.classList.add("card");
document.body.appendChild(this.element); this.element.classList.add("center");
const closeButton = new Button({
text: "close",
action: "close"
});
const closeButtonElement = closeButton.getElement();
this.bind(closeButtonElement);
this.inner.appendChild(closeButtonElement);
} }
openPage(page) { openPage(page) {

View file

@ -2,30 +2,6 @@
import { default as Logging } from "./Logging.mjs"; import { default as Logging } from "./Logging.mjs";
const interactions = {
toggleMenu: () => {
const speed = 200;
const menu = document.getElementsByTagName("main")[0];
menu.style.setProperty("transition",`${speed}ms`);
menu.classList.toggle("active");
setTimeout(() => menu.style.removeProperty("transition"),speed + 1);
},
openContactCard: () => {
const module = import("./Modals.mjs");
const interactions = {
hello: () => {
console.log("Hello world");
}
};
module.then(modals => {
const card = new modals.Card(interactions);
card.openPage("contact_card");
});
}
}
// Remove an element and its subtree // Remove an element and its subtree
export function destroy(family) { export function destroy(family) {
while(family.firstChild) { while(family.firstChild) {
@ -36,18 +12,23 @@ export function destroy(family) {
// General-purpose scoped event handler // General-purpose scoped event handler
export default class Interaction extends Logging { export default class Interaction extends Logging {
constructor(scope = document.body) { constructor(interactions,scope) {
super(); super();
this.interactions = interactions;
this.attribute = "data-action"; // Target elements with this attribute this.attribute = "data-action"; // Target elements with this attribute
// Bind listeners to the target attribute within the provided scope // Bind listeners to the target attribute within the provided scope
const elements = scope.querySelectorAll(`[${this.attribute}]`); const elements = scope.querySelectorAll(`[${this.attribute}]`);
for(const element of elements) { for(const element of elements) {
element.addEventListener("click",event => this.pointerEvent(event)); this.bind(element);
} }
} }
// Update the theme-color for Chrome on Android devices bind(element) {
element.addEventListener("click",event => this.pointerEvent(event));
}
// Set the page theme color (and the theme-color meta tag)
setThemeColor(color) { setThemeColor(color) {
const meta = document.head.querySelector("meta[name='theme-color']"); const meta = document.head.querySelector("meta[name='theme-color']");
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
@ -67,6 +48,7 @@ export default class Interaction extends Logging {
color = color.replaceAll(" ",""); color = color.replaceAll(" ","");
} }
document.body.style.setProperty("background-color",color);
meta.setAttribute("content",color); meta.setAttribute("content",color);
} }
@ -75,12 +57,12 @@ export default class Interaction extends Logging {
const target = event.target.closest(`[${this.attribute}]`); const target = event.target.closest(`[${this.attribute}]`);
const action = target?.getAttribute(this.attribute) ?? null; const action = target?.getAttribute(this.attribute) ?? null;
if(!target || !action || !Object.keys(interactions).includes(action)) { if(!target || !action || !Object.keys(this.interactions).includes(action)) {
// Exit if the interaction is invalid or action doesn't exist // Exit if the interaction is invalid or action doesn't exist
return false; return false;
} }
// Execute the function from the data-action attribute // Execute the function from the data-action attribute
interactions[action](event); this.interactions[action](event);
// The button has requested a theme-color change // The button has requested a theme-color change
if(target.hasAttribute("data-theme-color")) { if(target.hasAttribute("data-theme-color")) {

View file

@ -1,8 +1,34 @@
// Copyright © Victor Westerlund - No libraries! 😲 // Copyright © Victor Westerlund - No libraries! 😲
import { default as Interaction } from "./modules/UI.mjs"; import { default as Interaction } from "./modules/UI.mjs";
import { default as Debug } from "./modules/Debugging.mjs";
// All default interactions
const interactions = {
toggleMenu: () => {
const speed = 200;
const menu = document.getElementsByTagName("main")[0];
menu.style.setProperty("transition",`${speed}ms`);
menu.classList.toggle("active");
setTimeout(() => menu.style.removeProperty("transition"),speed + 1);
},
openContactCard: () => {
const module = import("./modules/Modals.mjs");
const interactions = {
hello: () => {
console.log("Hello world");
}
};
module.then(modals => {
const card = new modals.Card(interactions);
card.openPage("contact_card");
});
}
}
const theme = window.matchMedia("(prefers-color-scheme: dark)"); const theme = window.matchMedia("(prefers-color-scheme: dark)");
const main = new Interaction(); const main = new Interaction(interactions,document.body);
function updateTheme() { function updateTheme() {
const media = window.matchMedia("(prefers-color-scheme: dark)"); const media = window.matchMedia("(prefers-color-scheme: dark)");