mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Compare commits
6 Commits
feature/mo
...
6f04e21146
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f04e21146 | ||
|
|
97b0d57dbd | ||
|
|
c434d0843f | ||
|
|
5f8767f04d | ||
|
|
b4ee0955c3 | ||
|
|
1ec1229fe4 |
@@ -1,3 +1,6 @@
|
||||
> [!WARNING]
|
||||
> This repository is no longer maintained. We recommend checking out our [Astro](https://github.com/locomotivemtl/astro-boilerplate) or [Craft](https://github.com/locomotivemtl/craft-boilerplate) boilerplates instead.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/locomotivemtl/locomotive-boilerplate">
|
||||
<img src="https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png" height="140">
|
||||
|
||||
@@ -46,8 +46,6 @@ const CSS_CLASS = Object.freeze({
|
||||
// Custom js events
|
||||
const CUSTOM_EVENT = Object.freeze({
|
||||
RESIZE_END: 'loco.resizeEnd',
|
||||
VISIT_START: 'visit.start',
|
||||
MODAL_OPEN: 'modal.open',
|
||||
// ...
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import svg4everybody from 'svg4everybody';
|
||||
import { ENV } from './config';
|
||||
|
||||
// Dynamic imports for development mode only
|
||||
@@ -11,11 +10,6 @@ let gridHelper;
|
||||
})();
|
||||
|
||||
export default function () {
|
||||
/**
|
||||
* Use external SVG spritemaps
|
||||
*/
|
||||
svg4everybody();
|
||||
|
||||
/**
|
||||
* Add grid helper
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export {default as Example} from './modules/Example';
|
||||
export {default as Load} from './modules/Load';
|
||||
export {default as Modal} from './modules/Modal';
|
||||
export {default as Scroll} from './modules/Scroll';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { module } from 'modujs';
|
||||
import modularLoad from 'modularload';
|
||||
import { CUSTOM_EVENT } from '../config';
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
@@ -15,12 +14,6 @@ export default class extends module {
|
||||
}
|
||||
});
|
||||
|
||||
load.on('loading', (transition, oldContainer) => {
|
||||
const args = { transition, oldContainer };
|
||||
// Dispatch custom event
|
||||
window.dispatchEvent(new CustomEvent(CUSTOM_EVENT.VISIT_START, { detail: args }))
|
||||
});
|
||||
|
||||
load.on('loaded', (transition, oldContainer, newContainer) => {
|
||||
this.call('destroy', oldContainer, 'app');
|
||||
this.call('update', newContainer, 'app');
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { createFocusTrap } from 'focus-trap'
|
||||
import { module as Module } from 'modujs'
|
||||
import { $html } from '../utils/dom'
|
||||
import { CUSTOM_EVENT } from '../config'
|
||||
|
||||
/**
|
||||
* Generic component to display a modal.
|
||||
*
|
||||
*/
|
||||
export default class Modal extends Module {
|
||||
/**
|
||||
* Creates a new Modal.
|
||||
*
|
||||
* @param {object} options - The module options.
|
||||
* @param {string} options.dataName - The module data attribute name.
|
||||
* @throws {TypeError} If the class does not have an active CSS class defined.
|
||||
*/
|
||||
|
||||
static CLASS = {
|
||||
EL: 'is-open',
|
||||
HTML: 'has-modal-open',
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
super(options)
|
||||
|
||||
// Data
|
||||
this.moduleName = options.name
|
||||
this.dataName = this.getData('name') || options.dataName
|
||||
|
||||
// Bindings
|
||||
this.toggle = this.toggle.bind(this)
|
||||
this.onModalOpen = this.onModalOpen.bind(this)
|
||||
this.onVisitStart = this.onVisitStart.bind(this)
|
||||
|
||||
// UI
|
||||
this.$togglers = document.querySelectorAll(`[data-${this.dataName}-toggler]`)
|
||||
this.$focusTrapTargets = Array.from(this.el.querySelectorAll(`[data-${this.dataName}-target]`))
|
||||
|
||||
// Focus trap options
|
||||
this.focusTrapOptions = {
|
||||
/**
|
||||
* There is a delay between when the class is applied
|
||||
* and when the element is focusable
|
||||
*/
|
||||
checkCanFocusTrap: (trapContainers) => {
|
||||
const results = trapContainers.map((trapContainer) => {
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (
|
||||
getComputedStyle(trapContainer).visibility !==
|
||||
'hidden'
|
||||
) {
|
||||
resolve()
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 5)
|
||||
})
|
||||
})
|
||||
|
||||
// Return a promise that resolves when all the trap containers are able to receive focus
|
||||
return Promise.all(results)
|
||||
},
|
||||
|
||||
onActivate: () => {
|
||||
this.el.classList.add(Modal.CLASS.EL)
|
||||
$html.classList.add(Modal.CLASS.HTML)
|
||||
$html.classList.add('has-'+this.dataName+'-open')
|
||||
this.el.setAttribute('aria-hidden', false)
|
||||
this.isOpen = true
|
||||
|
||||
this.onActivate?.();
|
||||
},
|
||||
|
||||
onPostActivate: () => {
|
||||
this.$togglers.forEach(($toggler) => {
|
||||
$toggler.setAttribute('aria-expanded', true)
|
||||
})
|
||||
},
|
||||
|
||||
onDeactivate: () => {
|
||||
this.el.classList.remove(Modal.CLASS.EL)
|
||||
$html.classList.remove(Modal.CLASS.HTML)
|
||||
$html.classList.remove('has-'+this.dataName+'-open')
|
||||
this.el.setAttribute('aria-hidden', true)
|
||||
this.isOpen = false
|
||||
|
||||
this.onDeactivate?.();
|
||||
},
|
||||
|
||||
onPostDeactivate: () => {
|
||||
this.$togglers.forEach(($toggler) => {
|
||||
$toggler.setAttribute('aria-expanded', false)
|
||||
})
|
||||
},
|
||||
|
||||
clickOutsideDeactivates: true,
|
||||
}
|
||||
|
||||
this.isOpen = false
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Lifecycle
|
||||
/////////////////
|
||||
init() {
|
||||
this.onBeforeInit?.()
|
||||
|
||||
this.focusTrap = createFocusTrap(
|
||||
this.$focusTrapTargets.length > 0 ? this.$focusTrapTargets : [this.el],
|
||||
this.focusTrapOptions
|
||||
)
|
||||
|
||||
this.bindEvents()
|
||||
|
||||
this.onInit?.()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.focusTrap?.deactivate?.({
|
||||
returnFocus: false,
|
||||
})
|
||||
|
||||
this.unbindEvents()
|
||||
|
||||
this.onDestroy?.()
|
||||
|
||||
super.destroy()
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Events
|
||||
/////////////////
|
||||
bindEvents() {
|
||||
window.addEventListener(CUSTOM_EVENT.VISIT_START, this.onVisitStart)
|
||||
window.addEventListener(CUSTOM_EVENT.MODAL_OPEN, this.onModalOpen)
|
||||
|
||||
this.$togglers.forEach(($toggler) => {
|
||||
$toggler.addEventListener('click', this.toggle)
|
||||
})
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
window.removeEventListener(CUSTOM_EVENT.VISIT_START, this.onVisitStart)
|
||||
window.removeEventListener(CUSTOM_EVENT.MODAL_OPEN, this.onModalOpen)
|
||||
|
||||
this.$togglers.forEach(($toggler) => {
|
||||
$toggler.removeEventListener('click', this.toggle)
|
||||
})
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Callbacks
|
||||
/////////////////
|
||||
onVisitStart() {
|
||||
// Close the modal on page change
|
||||
this.close()
|
||||
}
|
||||
|
||||
onModalOpen(event) {
|
||||
// Close the modal if another one is opened
|
||||
if (event.detail !== this.el) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Methods
|
||||
/////////////////
|
||||
toggle(event) {
|
||||
if (this.el.classList.contains(Modal.CLASS.EL)) {
|
||||
this.close(event)
|
||||
} else {
|
||||
this.open(event)
|
||||
}
|
||||
}
|
||||
|
||||
open(args) {
|
||||
if (this.isOpen) return
|
||||
|
||||
this.focusTrap?.activate?.()
|
||||
|
||||
this.onOpen?.(args)
|
||||
|
||||
window.dispatchEvent(new CustomEvent(CUSTOM_EVENT.MODAL_OPEN, { detail: this.el }))
|
||||
}
|
||||
|
||||
close(args) {
|
||||
if (!this.isOpen) return
|
||||
|
||||
this.focusTrap?.deactivate?.()
|
||||
|
||||
this.onClose?.(args)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ $input-icon-color: 424242; // No #
|
||||
.c-form_input {
|
||||
padding: rem(10px);
|
||||
border: 1px solid lightgray;
|
||||
background-color: color(lightest);
|
||||
background-color: colorCode(lightest);
|
||||
|
||||
&:hover {
|
||||
border-color: darkgray;
|
||||
@@ -71,7 +71,7 @@ $checkbox-icon-color: $input-icon-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: color(lightest);
|
||||
background-color: colorCode(lightest);
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Components / Modal
|
||||
// ==========================================================================
|
||||
|
||||
.c-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100dvh;
|
||||
max-width: inherit;
|
||||
max-height: 100lvh;
|
||||
margin: 0;
|
||||
padding: 0 var(--grid-margin);
|
||||
background: transparent;
|
||||
border: none;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
// Backdrop
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: color(darkest, 0.5);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html.is-first-loaded & {
|
||||
transition: visibility speed(normal);
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.c-modal_inner {
|
||||
width: 100%;
|
||||
max-width: rem(500px);
|
||||
padding: $unit-small;
|
||||
background-color: color(lightest);
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: color(darkest);
|
||||
background-color: colorCode(darkest);
|
||||
opacity: 0.5;
|
||||
width: 7px;
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
@import "node_modules/locomotive-scroll/dist/locomotive-scroll";
|
||||
@import "../../node_modules/locomotive-scroll/dist/locomotive-scroll";
|
||||
|
||||
// Elements
|
||||
// ==========================================================================
|
||||
@@ -57,7 +57,6 @@
|
||||
@import "components/text";
|
||||
@import "components/button";
|
||||
@import "components/form";
|
||||
@import "components/modal";
|
||||
|
||||
// Utilities
|
||||
// ==========================================================================
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use 'sass:color';
|
||||
|
||||
// ==========================================================================
|
||||
// Settings / Config / Colors
|
||||
// ==========================================================================
|
||||
@@ -18,7 +20,7 @@ $colors: (
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// color: color(primary);
|
||||
// color: colorCode(primary);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
@@ -26,7 +28,7 @@ $colors: (
|
||||
// @param {number} $alpha - The alpha for the color value.
|
||||
// @return {color}
|
||||
|
||||
@function color($key, $alpha: 1) {
|
||||
@function colorCode($key, $alpha: 1) {
|
||||
@if not map-has-key($colors, $key) {
|
||||
@error "Unknown '#{$key}' in $colors.";
|
||||
}
|
||||
@@ -44,13 +46,13 @@ $colors: (
|
||||
// ==========================================================================
|
||||
|
||||
// Link
|
||||
$color-link: color(primary);
|
||||
$color-link-focus: color(primary);
|
||||
$color-link-hover: darken(color(primary), 10%);
|
||||
$color-link: colorCode(primary);
|
||||
$color-link-focus: colorCode(primary);
|
||||
$color-link-hover: color.adjust(colorCode(primary), $lightness: -10%);
|
||||
|
||||
// Selection
|
||||
$color-selection-text: color(darkest);
|
||||
$color-selection-background: color(lightest);
|
||||
$color-selection-text: colorCode(darkest);
|
||||
$color-selection-background: colorCode(lightest);
|
||||
|
||||
// Socials
|
||||
$color-facebook: #3B5998;
|
||||
|
||||
@@ -17,7 +17,7 @@ $assets-path: "../" !default;
|
||||
// Base
|
||||
$font-size: 16px;
|
||||
$line-height: math.div(24px, $font-size);
|
||||
$font-color: color(darkest);
|
||||
$font-color: colorCode(darkest);
|
||||
|
||||
// Weights
|
||||
$font-weight-light: 300;
|
||||
|
||||
@@ -10,12 +10,9 @@ import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { basename } from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
import * as sass from 'sass';
|
||||
import { PurgeCSS } from 'purgecss';
|
||||
|
||||
const sassRender = promisify(sass.render);
|
||||
|
||||
let postcssProcessor;
|
||||
|
||||
/**
|
||||
@@ -24,16 +21,15 @@ let postcssProcessor;
|
||||
* @const {object} productionSassOptions - The predefined Sass options for production.
|
||||
*/
|
||||
export const defaultSassOptions = {
|
||||
omitSourceMapUrl: true,
|
||||
sourceMapIncludeSources: true,
|
||||
sourceMap: true,
|
||||
sourceMapContents: true,
|
||||
};
|
||||
|
||||
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
|
||||
outputStyle: 'expanded',
|
||||
style: 'expanded',
|
||||
});
|
||||
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
|
||||
outputStyle: 'compressed',
|
||||
style: 'compressed',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -127,10 +123,7 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
|
||||
infile = resolve(infile);
|
||||
outfile = resolve(outfile);
|
||||
|
||||
let result = await sassRender(Object.assign({}, sassOptions, {
|
||||
file: infile,
|
||||
outFile: outfile,
|
||||
}));
|
||||
let result = sass.compile(infile, sassOptions);
|
||||
|
||||
if (supportsPostCSS && postcssOptions) {
|
||||
if (typeof postcssProcessor === 'undefined') {
|
||||
|
||||
6378
package-lock.json
generated
6378
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -14,23 +14,21 @@
|
||||
"build": "node --no-warnings build/build.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"focus-trap": "^7.5.4",
|
||||
"locomotive-scroll": "^5.0.0-beta.13",
|
||||
"locomotive-scroll": "^5.0.0-beta.21",
|
||||
"modujs": "^1.4.2",
|
||||
"modularload": "^1.2.6",
|
||||
"svg4everybody": "^2.1.9"
|
||||
"modularload": "^1.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"browser-sync": "^3.0.2",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"browser-sync": "^3.0.4",
|
||||
"common-path": "^1.0.1",
|
||||
"concat": "^1.0.3",
|
||||
"esbuild": "^0.21.5",
|
||||
"esbuild": "^0.25.8",
|
||||
"kleur": "^4.1.5",
|
||||
"node-notifier": "^10.0.1",
|
||||
"postcss": "^8.4.38",
|
||||
"purgecss": "^6.0.0",
|
||||
"sass": "^1.77.6",
|
||||
"postcss": "^8.5.6",
|
||||
"purgecss": "^7.0.2",
|
||||
"sass": "^1.89.2",
|
||||
"svg-mixer": "^2.3.14",
|
||||
"tiny-glob": "^0.2.9"
|
||||
},
|
||||
@@ -39,8 +37,8 @@
|
||||
"ua-parser-js": "^1.0.33"
|
||||
},
|
||||
"svg-mixer": {
|
||||
"micromatch": "^4.0.4",
|
||||
"postcss": "^8.4.38"
|
||||
"micromatch": "^4.0.8",
|
||||
"postcss": "^8.4.49"
|
||||
},
|
||||
"svg-mixer-utils": {
|
||||
"anymatch": "^3.1.3"
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
<main data-module-example>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Hello</h1>
|
||||
<button data-modal-toggler>Open modal</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -67,18 +66,6 @@
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
||||
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
|
||||
<div class="c-modal" data-module-modal>
|
||||
<div class="c-modal_inner" data-modal-target>
|
||||
<button data-modal-toggler>Close</button>
|
||||
<h2>Modal</h2>
|
||||
<p>Content</p>
|
||||
<form action="">
|
||||
<input type="text">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user