Add tags
This commit is contained in:
@ -35,6 +35,7 @@
|
||||
max-height: 378px;
|
||||
margin-top: 44px;
|
||||
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 128, 0.2);
|
||||
padding-bottom: 1rem;
|
||||
|
||||
&.is-active {
|
||||
transform: rotateX(0);
|
||||
@ -59,6 +60,16 @@
|
||||
border-color: pink;
|
||||
}
|
||||
}
|
||||
|
||||
&__section {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
&__section-heading {
|
||||
text-transform: capitalize;
|
||||
color: #e3f5ff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
|
||||
@ -14,6 +14,10 @@
|
||||
margin-bottom: 1.25rem;
|
||||
margin-top: 0;
|
||||
line-height: 1.3;
|
||||
|
||||
span:not(.snippet__tag) {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
code:not([class*='lang']) {
|
||||
@ -42,16 +46,20 @@
|
||||
h4 {
|
||||
display: inline-block;
|
||||
margin: 1rem 0 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
line-height: 2;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
background: #333;
|
||||
border: 1px solid #c6d6ea;
|
||||
border-bottom-color: darken(#c6d6ea, 5);
|
||||
background: white;
|
||||
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 64, 0.15);
|
||||
|
||||
&[data-type] {
|
||||
background: #333;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid #c6d6ea;
|
||||
border-bottom-color: darken(#c6d6ea, 5);
|
||||
background: white;
|
||||
box-shadow: 0 0.25rem 0.5rem -0.1rem rgba(0, 32, 64, 0.15);
|
||||
}
|
||||
|
||||
&[data-type='HTML'] {
|
||||
color: white;
|
||||
@ -107,6 +115,9 @@
|
||||
top: 1rem;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
}
|
||||
}
|
||||
|
||||
.snippet-demo {
|
||||
|
||||
83
src/css/components/tags.scss
Normal file
83
src/css/components/tags.scss
Normal file
@ -0,0 +1,83 @@
|
||||
.tags {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&__tag {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
font-weight: bold;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: #8385aa;
|
||||
white-space: nowrap;
|
||||
border: 1px solid lighten(#8385aa, 15);
|
||||
border-radius: 2px;
|
||||
vertical-align: middle;
|
||||
line-height: 2;
|
||||
padding: 0 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
transition: all 0.1s ease-out;
|
||||
outline: 0;
|
||||
|
||||
&.is-large {
|
||||
font-size: 0.95rem;
|
||||
border-radius: 0.2rem;
|
||||
|
||||
.feather {
|
||||
top: -2px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.feather {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25rem;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
button.tags__tag {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:hover {
|
||||
background: #8385aa;
|
||||
border-color: #8385aa;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 0.25rem transparentize(#8385aa, 0.5);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0 0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.2);
|
||||
background: darken(#8385aa, 10);
|
||||
border-color: darken(#8385aa, 10);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: #7983ff;
|
||||
border-color: #7983ff;
|
||||
color: white;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 0.25rem transparentize(#7983ff, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 579px) {
|
||||
}
|
||||
@ -4,3 +4,4 @@
|
||||
@import './components/header.scss';
|
||||
@import './components/snippet.scss';
|
||||
@import './components/back-to-top-button.scss';
|
||||
@import './components/tags.scss';
|
||||
|
||||
1
src/html/components/back-to-top-button.html
Normal file
1
src/html/components/back-to-top-button.html
Normal file
@ -0,0 +1 @@
|
||||
<button class="back-to-top-button" aria-label="back to top">↑</button>
|
||||
1
src/html/components/footer.html
Normal file
1
src/html/components/footer.html
Normal file
@ -0,0 +1 @@
|
||||
<footer class="footer"></footer>
|
||||
10
src/html/components/header.html
Normal file
10
src/html/components/header.html
Normal file
@ -0,0 +1,10 @@
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<img class="header__logo" draggable="false" src="./src/img/logo.png">
|
||||
<h1 class="header__heading">30 Seconds of <strong class="header__css">CSS</strong></h1>
|
||||
<p class="header__description">
|
||||
A curated collection of useful CSS snippets you can understand in 30 seconds or less.
|
||||
</p>
|
||||
<a class="github-button header__github-button" href="https://github.com/atomiks/30-seconds-of-css" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star atomiks/30-seconds-of-css on GitHub">Star</a>
|
||||
</div>
|
||||
</header>
|
||||
4
src/html/components/main.html
Normal file
4
src/html/components/main.html
Normal file
@ -0,0 +1,4 @@
|
||||
<main class="main" id="main">
|
||||
<div class="container">
|
||||
</div>
|
||||
</main>
|
||||
9
src/html/components/sidebar.html
Normal file
9
src/html/components/sidebar.html
Normal file
@ -0,0 +1,9 @@
|
||||
<nav class="sidebar" aria-label="Table of Contents">
|
||||
<button class="hamburger hamburger--spin sidebar__menu" type="button" aria-label="Menu" aria-expanded="false">
|
||||
<span class="hamburger-box">
|
||||
<span class="hamburger-inner"></span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="sidebar__links">
|
||||
</div>
|
||||
</nav>
|
||||
1
src/html/components/tags.html
Normal file
1
src/html/components/tags.html
Normal file
@ -0,0 +1 @@
|
||||
<nav class="tags" aria-label="Filter by tags"></nav>
|
||||
15
src/html/index.html
Normal file
15
src/html/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>30 Seconds of CSS</title>
|
||||
<meta name="description" content="A curated collection of useful CSS snippets you can understand in 30 seconds or less. From foundational elements such as clearfix to gradient text color and gradient cursor tracking to CSS easing and far beyond.">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
|
||||
<script src="./src/js/index.js" defer=""></script>
|
||||
<script async="" defer="" src="https://buttons.github.io/buttons.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-wrapper"></div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,27 +1,23 @@
|
||||
import jump from '../deps/jump'
|
||||
import { select, selectAll, easeOutQuint } from '../deps/utils'
|
||||
|
||||
const menu = select('.hamburger')
|
||||
const links = select('.sidebar__links')
|
||||
const sections = selectAll('.sidebar__section')
|
||||
const ACTIVE_CLASS = 'is-active'
|
||||
|
||||
const toggle = () => {
|
||||
const els = [menu, links]
|
||||
els.forEach(el => el.classList.toggle(ACTIVE_CLASS))
|
||||
menu.setAttribute('aria-expanded', menu.classList.contains(ACTIVE_CLASS) ? 'true' : 'false')
|
||||
if (window.innerWidth <= 991) {
|
||||
const els = [menu, links]
|
||||
els.forEach(el => el.classList.toggle(ACTIVE_CLASS))
|
||||
menu.setAttribute('aria-expanded', menu.classList.contains(ACTIVE_CLASS) ? 'true' : 'false')
|
||||
}
|
||||
}
|
||||
|
||||
menu.addEventListener('click', toggle)
|
||||
|
||||
links.addEventListener('click', e => {
|
||||
setTimeout(toggle, 40)
|
||||
if (e.target.classList.contains('sidebar__link')) {
|
||||
e.preventDefault()
|
||||
jump(e.target.getAttribute('href'), {
|
||||
duration: 750,
|
||||
offset: window.innerWidth <= 768 ? -64 : -32,
|
||||
easing: easeOutQuint
|
||||
})
|
||||
setTimeout(toggle, 100)
|
||||
}
|
||||
})
|
||||
|
||||
@ -35,4 +31,13 @@ document.addEventListener('click', e => {
|
||||
}
|
||||
})
|
||||
|
||||
EventHub.on('Tag.click', data => {
|
||||
sections.forEach(section => {
|
||||
section.style.display = 'block'
|
||||
if (section.dataset.type !== data.type && data.type !== 'all') {
|
||||
section.style.display = 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default { toggle }
|
||||
13
src/js/components/Snippet.js
Normal file
13
src/js/components/Snippet.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { selectAll } from '../deps/utils'
|
||||
|
||||
const snippets = selectAll('.snippet')
|
||||
EventHub.on('Tag.click', data => {
|
||||
snippets.forEach(snippet => {
|
||||
snippet.style.display = 'block'
|
||||
if (data.type === 'all') return
|
||||
const tags = selectAll('.tags__tag', snippet)
|
||||
if (!selectAll('.tags__tag', snippet).some(el => el.dataset.type === data.type)) {
|
||||
snippet.style.display = 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
14
src/js/components/Tag.js
Normal file
14
src/js/components/Tag.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { select, selectAll, on } from '../deps/utils'
|
||||
|
||||
const tagButtons = selectAll('button.tags__tag')
|
||||
|
||||
const onClick = function() {
|
||||
tagButtons.forEach(button => button.classList.remove('is-active'))
|
||||
this.classList.add('is-active')
|
||||
|
||||
EventHub.emit('Tag.click', {
|
||||
type: this.dataset.type
|
||||
})
|
||||
}
|
||||
|
||||
tagButtons.forEach(button => on(button, 'click', onClick))
|
||||
@ -1,196 +0,0 @@
|
||||
;(function(global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory())
|
||||
: typeof define === 'function' && define.amd ? define(factory) : (global.Jump = factory())
|
||||
})(this, function() {
|
||||
'use strict'
|
||||
|
||||
// Robert Penner's easeInOutQuad
|
||||
|
||||
// find the rest of his easing functions here: http://robertpenner.com/easing/
|
||||
// find them exported for ES6 consumption here: https://github.com/jaxgeller/ez.js
|
||||
|
||||
var easeInOutQuad = function easeInOutQuad(t, b, c, d) {
|
||||
t /= d / 2
|
||||
if (t < 1) return c / 2 * t * t + b
|
||||
t--
|
||||
return -c / 2 * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
var _typeof =
|
||||
typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
|
||||
? function(obj) {
|
||||
return typeof obj
|
||||
}
|
||||
: function(obj) {
|
||||
return obj &&
|
||||
typeof Symbol === 'function' &&
|
||||
obj.constructor === Symbol &&
|
||||
obj !== Symbol.prototype
|
||||
? 'symbol'
|
||||
: typeof obj
|
||||
}
|
||||
|
||||
var jumper = function jumper() {
|
||||
// private variable cache
|
||||
// no variables are created during a jump, preventing memory leaks
|
||||
|
||||
var element = void 0 // element to scroll to (node)
|
||||
|
||||
var start = void 0 // where scroll starts (px)
|
||||
var stop = void 0 // where scroll stops (px)
|
||||
|
||||
var offset = void 0 // adjustment from the stop position (px)
|
||||
var easing = void 0 // easing function (function)
|
||||
var a11y = void 0 // accessibility support flag (boolean)
|
||||
|
||||
var distance = void 0 // distance of scroll (px)
|
||||
var duration = void 0 // scroll duration (ms)
|
||||
|
||||
var timeStart = void 0 // time scroll started (ms)
|
||||
var timeElapsed = void 0 // time spent scrolling thus far (ms)
|
||||
|
||||
var next = void 0 // next scroll position (px)
|
||||
|
||||
var callback = void 0 // to call when done scrolling (function)
|
||||
|
||||
// scroll position helper
|
||||
|
||||
function location() {
|
||||
return window.scrollY || window.pageYOffset
|
||||
}
|
||||
|
||||
// element offset helper
|
||||
|
||||
function top(element) {
|
||||
return element.getBoundingClientRect().top + start
|
||||
}
|
||||
|
||||
// rAF loop helper
|
||||
|
||||
function loop(timeCurrent) {
|
||||
// store time scroll started, if not started already
|
||||
if (!timeStart) {
|
||||
timeStart = timeCurrent
|
||||
}
|
||||
|
||||
// determine time spent scrolling so far
|
||||
timeElapsed = timeCurrent - timeStart
|
||||
|
||||
// calculate next scroll position
|
||||
next = easing(timeElapsed, start, distance, duration)
|
||||
|
||||
// scroll to it
|
||||
window.scrollTo(0, next)
|
||||
|
||||
// check progress
|
||||
timeElapsed < duration
|
||||
? window.requestAnimationFrame(loop) // continue scroll loop
|
||||
: done() // scrolling is done
|
||||
}
|
||||
|
||||
// scroll finished helper
|
||||
|
||||
function done() {
|
||||
// account for rAF time rounding inaccuracies
|
||||
window.scrollTo(0, start + distance)
|
||||
|
||||
// if scrolling to an element, and accessibility is enabled
|
||||
if (element && a11y) {
|
||||
// add tabindex indicating programmatic focus
|
||||
element.setAttribute('tabindex', '-1')
|
||||
|
||||
// focus the element
|
||||
element.focus()
|
||||
}
|
||||
|
||||
// if it exists, fire the callback
|
||||
if (typeof callback === 'function') {
|
||||
callback()
|
||||
}
|
||||
|
||||
// reset time for next jump
|
||||
timeStart = false
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
function jump(target) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
|
||||
|
||||
// resolve options, or use defaults
|
||||
duration = options.duration || 1000
|
||||
offset = options.offset || 0
|
||||
callback = options.callback // "undefined" is a suitable default, and won't be called
|
||||
easing = options.easing || easeInOutQuad
|
||||
a11y = options.a11y || false
|
||||
|
||||
// cache starting position
|
||||
start = location()
|
||||
|
||||
// resolve target
|
||||
switch (typeof target === 'undefined' ? 'undefined' : _typeof(target)) {
|
||||
// scroll from current position
|
||||
case 'number':
|
||||
element = undefined // no element to scroll to
|
||||
a11y = false // make sure accessibility is off
|
||||
stop = start + target
|
||||
break
|
||||
|
||||
// scroll to element (node)
|
||||
// bounding rect is relative to the viewport
|
||||
case 'object':
|
||||
element = target
|
||||
stop = top(element)
|
||||
break
|
||||
|
||||
// scroll to element (selector)
|
||||
// bounding rect is relative to the viewport
|
||||
case 'string':
|
||||
element = document.querySelector(target)
|
||||
stop = top(element)
|
||||
break
|
||||
}
|
||||
|
||||
// resolve scroll distance, accounting for offset
|
||||
distance = stop - start + offset
|
||||
|
||||
// resolve duration
|
||||
switch (_typeof(options.duration)) {
|
||||
// number in ms
|
||||
case 'number':
|
||||
duration = options.duration
|
||||
break
|
||||
|
||||
// function passed the distance of the scroll
|
||||
case 'function':
|
||||
duration = options.duration(distance)
|
||||
break
|
||||
}
|
||||
|
||||
// start the loop
|
||||
window.requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
// expose only the jump method
|
||||
return jump
|
||||
}
|
||||
|
||||
// export singleton
|
||||
|
||||
var singleton = jumper()
|
||||
|
||||
return (() => {
|
||||
let scrolling
|
||||
const end = () => (scrolling = false)
|
||||
return (to, options = {}) => {
|
||||
if (scrolling) return
|
||||
const scrollY = window.scrollY || window.pageYOffset
|
||||
if (to !== '.header') location.hash = to
|
||||
scroll(0, scrollY)
|
||||
scrolling = true
|
||||
setTimeout(end, options.duration || 0)
|
||||
return singleton(to, options)
|
||||
}
|
||||
})()
|
||||
})
|
||||
@ -1,8 +1,34 @@
|
||||
export const select = s => document.querySelector(s)
|
||||
export const selectAll = s => [].slice.call(document.querySelectorAll(s))
|
||||
export const select = (s, parent = document) => parent.querySelector(s)
|
||||
|
||||
export const selectAll = (s, parent = document) => [].slice.call(parent.querySelectorAll(s))
|
||||
|
||||
export const scrollY = () => window.scrollY || window.pageYOffset
|
||||
|
||||
export const easeOutQuint = (t, b, c, d) => c * ((t = t / d - 1) * t ** 4 + 1) + b
|
||||
|
||||
export const on = (el, evt, fn, opts = {}) => {
|
||||
const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e)
|
||||
el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false)
|
||||
if (opts.target) return delegatorFn
|
||||
}
|
||||
|
||||
export const createEventHub = () => ({
|
||||
hub: Object.create(null),
|
||||
emit(event, data) {
|
||||
;(this.hub[event] || []).forEach(handler => handler(data))
|
||||
},
|
||||
on(event, handler) {
|
||||
if (!this.hub[event]) this.hub[event] = []
|
||||
this.hub[event].push(handler)
|
||||
},
|
||||
off(event, handler) {
|
||||
const i = (this.hub[event] || []).findIndex(h => h === handler)
|
||||
if (i > -1) this.hub[event].splice(i, 1)
|
||||
}
|
||||
})
|
||||
|
||||
window.EventHub = createEventHub()
|
||||
|
||||
/*
|
||||
* Make iOS behave normally.
|
||||
*/
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// Deps
|
||||
import 'normalize.css'
|
||||
import 'prismjs'
|
||||
import feather from 'feather-icons'
|
||||
feather.replace()
|
||||
|
||||
// CSS
|
||||
import '../css/deps/prism.css'
|
||||
@ -10,5 +12,7 @@ import '../css/index.scss'
|
||||
import './deps/polyfills'
|
||||
|
||||
// Components
|
||||
import Menu from './components/Menu'
|
||||
import Sidebar from './components/Sidebar'
|
||||
import BackToTopButton from './components/BackToTopButton'
|
||||
import Tag from './components/Tag'
|
||||
import Snippet from './components/Snippet'
|
||||
|
||||
Reference in New Issue
Block a user