Last active
August 5, 2022 16:36
-
-
Save craigerskine/7ea36f139cf818b045e5e1d2afa5ee11 to your computer and use it in GitHub Desktop.
Twind + Vue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en" class="selection:(bg-gray-500 text-gray-50) motion-safe:(scroll-smooth)" hidden> | |
<head> | |
<meta charset="utf-8" /> | |
<meta http-equiv="x-ua-compatible" content="ie=edge" /> | |
<title></title> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<link href="https://fonts.gstatic.com" rel="preconnect" /> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap" rel="stylesheet" /> | |
</head> | |
<body class="bg-gray-200 text-gray-700 dark:(bg-gray-800 text-gray-400)"> | |
<div id="app" class="app min-h-screen flex(& col)" v-cloak> | |
<header class="py-8 px-4"> | |
<div class="container mx-auto md:(flex items-center)"> | |
<a href="/" class="py-2 text(pri-500 3xl) leading-none font-bold uppercase flex(& wrap) items-center justify-center gap-x-4 transform motion-safe:(transition) hover:(-rotate-3) md:(justify-start) dark:(text-pri-400)"> | |
<b class="w-12 h-12 bg-current relative z-20 rounded-full"><b class="ring(1 current) absolute inset-0 -z-10 rounded-full animate-ping"></b></b> | |
<span> | |
{{ siteCompany }} | |
<small class="text-sm tracking-widest font-semibold block opacity-50" v-if="siteSlogan !== ''" v-text="siteSlogan"></small> | |
</span> | |
</a> | |
<nav class="py-5 md:(flex(& 1) justify-end)"> | |
<ul class="flex(& wrap) items-center justify-center gap-5 font-bold uppercase md:(justify-end)"> | |
<li v-for="(item, index) in nav"> | |
<a :href="item.href" class="group py-1 block relative motion-safe:(transition) hover:(text-pri-500 dark:(text-white))"> | |
{{ item.name }} | |
<b class="h-[2px] w-full text-pri-500 flex justify-start absolute inset-x-0 bottom-0 dark:(text-pri-400)"><b class="w-0 bg-current motion-safe:(transition-all) group-hover:(w-full)"></b></b> | |
</a> | |
</li> | |
</ul> | |
</nav> | |
</div> | |
</header> | |
<main> | |
<section class="mb-4 py-4 px-4 bg-gradient-to-b from-gray-300 md:(mb-16 py-16) dark:(from-gray-900)"> | |
<h1 class="mx-auto max-w-prose text-gray-600 first-line:(text-gray-900 font-black) font-semibold leading-tight text-3xl md:(w-3/4 max-w-none text-[calc(3vw+3vh+.5vmin)] tracking-tighter) dark:(text-gray-500 first-line:(text-gray-50))"> | |
Welcome to my amazing site! Dynamic text size is still cool or something, right?... | |
</h1> | |
</section> | |
<section class="mx-auto px-4 max-w-prose"> | |
<article class="prose prose-pri"> | |
<h2>Typography</h2> | |
<p>Paragraph <strong>Lorem</strong> ipsum, <em>dolor</em>, sit amet <a href="#">consectetur</a> adipisicing elit. Et atque necessitatibus blanditiis eos saepe officia explicabo vel tempore quisquam reprehenderit omnis vero inventore, iusto, nihil dicta perferendis sed.</p> | |
<hr /> | |
<blockquote> | |
<p>Blockquote looks like this. Lorem ipsum dolor, sit amet consectetur adipisicing elit. Enim, hic laborum labore quibusdam nostrum esse.</p> | |
</blockquote> | |
<div class="md:(grid(& cols-2) gap-5)"> | |
<ul> | |
<li>Unordered List</li> | |
<li>Unordered List</li> | |
<li>Unordered List</li> | |
</ul> | |
<ol> | |
<li>Ordered List</li> | |
<li>Ordered List</li> | |
<li>Ordered List</li> | |
</ol> | |
</div> | |
</article> | |
<!-- VUE STUFF --> | |
<h2 class="heading-hr my-6 text-xl font-black"> | |
<span class="flex-none">Some Vue Stuff</span> | |
</h2> | |
<ul class="mb-5 flex(& wrap) gap-5"> | |
<li><btn variant="primary">Primary</btn></li> | |
<li><btn variant="secondary">Secondary</btn></li> | |
<li><btn>Default</btn> | |
</ul> | |
</section> | |
<section class="mx-auto px-4 max-w-prose"> | |
<fieldset class="relative"> | |
<label for="search" class="w-8 h-8 flex items-center justify-center absolute z-10 right-5 top-1/2 transform -translate-y-1/2"><i class="m-auto fa fa-fw fa-search" role="presentation" aria-label="Search"></i></label> | |
<input type="search" id="search" v-model="list_search" class="appearance-none py-3 px-6 w-full bg(white opacity-50) placeholder:(text-gray-400) block rounded-md motion-safe:(transition) focus:(bg-white outline-none shadow-xl ring(& white)) relative z-20 dark:(bg(gray-500 opacity-20) placeholder:(text-gray-500) focus:(bg-white text-gray-700))" placeholder="Search" /> | |
</fieldset> | |
<section v-if="!list_results.length" class="py-8 text(gray-500 opacity-50) font-bold text-2xl uppercase tracking-tight text-center"><i class="fa fa-fw fa-ban opacity-50" role="presentation" aria-hidden="true"></i> No Results</section> | |
<ul class="mt-5 bg(white opacity-20) divide(y gray-500 opacity-20) rounded-lg shadow-lg dark:(bg-opacity-5)"> | |
<li v-for="(item, index) in list_results" class="p-5 flex items-center gap-x-3 dark:()"> | |
<i class="fa fa-fw fa-lg fa-check-circle opacity-30"></i> | |
<div> | |
{{ item.name }} | |
<ol v-if="item.tags" class="-mx-2 px-1 text-xs uppercase flex flex-wrap"> | |
<li v-for="(tag, index) in item.tags" class="p-1"><b class="px-1 py-px inline-block rounded bg-gray-500 bg-opacity-25">{{ tag.name }}</b></li> | |
</ol> | |
</div> | |
</li> | |
</ul> | |
</section> | |
</main> | |
<footer class="mt-auto py-8 px-4 text(xs center gray-500) font-bold uppercase"> | |
<div class="container mx-auto"> | |
Made with | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" role="img" class="-mt-1 mx-1 fill-current text-red-400 inline-block"> | |
<title>Love</title> | |
<polygon points="8 3 11 0 12 0 16 4 16 5 9 12 7 12 0 5 0 4 4 0 5 0" transform="translate(0,2)"></polygon> | |
</svg> | |
by <a href="https://craigerskine.com/" class="link mx-1">Craig Erskine</a> | |
</div> | |
</footer> | |
<div class="fixed top-2 right-2 z-50"><a @click.prevent="isDark = !isDark, dark(isDark)" href="#" :class="['w-7 h-7 bg(black opacity-10) text-gray-400 flex ring(1 current) rounded-full motion-safe:(transition) hover:(text-white)', isDark ? 'bg-opacity-50' : '']"><i class="m-auto fa fa-fw fa-moon"></i></a></div> | |
</div> | |
<div class="fixed inset-0 z-[-1] opacity-10 dark:(opacity-50)" aria-hidden="true"><div class="bg(grid fixed) absolute inset-0"></div></div> | |
<script src="https://cdn.jsdelivr.net/combine/npm/twind/twind.umd.js,npm/twind/observe/observe.umd.js,npm/twind/colors/colors.umd.js,npm/@twind/typography/typography.umd.js"></script> | |
<script src="https://unpkg.com/vue"></script> | |
<script src="https://unpkg.com/@fortawesome/fontawesome-free/js/all.min.js"></script> | |
<script> | |
// data | |
var app = Vue.createApp({ | |
data() { | |
return { | |
siteCompany: 'Company', | |
siteSlogan: 'Optional Slogan', | |
pageTitle: 'Some Title', | |
isDark: false, | |
nav: [ | |
{ name: 'Nav Item 1', href: '#', }, | |
{ name: 'Nav Item 2', href: '#', }, | |
], | |
list_search: null, | |
list: [ | |
{ | |
name: 'Chicken colleries unexhaustibly', | |
tags: [{ name: 'Tag 1' }, { name: 'Tag 2' }, { name: 'Tag 3' },], | |
}, | |
{ | |
name: 'Chicken reliableness slippingly', | |
}, | |
{ | |
name: 'Monkey nyctalopy interacinous', | |
tags: [{ name: 'Tag 2' },], | |
}, | |
{ | |
name: 'Monkey surculose lovesickness', | |
}, | |
{ | |
name: 'Okapi nonfrosted manicure', | |
tags: [{ name: 'Tag 3' },], | |
}, | |
], | |
} | |
}, | |
methods: { | |
dark: function(mode){ | |
if(mode){ | |
document.documentElement.classList.toggle('dark'); | |
} else { | |
document.documentElement.classList.remove('dark'); | |
} | |
}, | |
slug: function(text) { | |
return text | |
.toString() | |
.toLowerCase() | |
.trim() | |
.replace(/\.+/g, '-') // . to - (this can be removed - my specific usecase) | |
.normalize('NFKD') // unicode normalization | |
.replace(/[^\w\-]+/g, '') // remove other crazy chars | |
.replace(/\s+/g, '-') // space to - | |
.replace(/\-\-+/g, '-') // multiple - to single - | |
.replace(/^-+/, '') // remove - from start | |
.replace(/-+$/, '') // remove - from end | |
}, | |
}, | |
computed: { | |
list_results(){ | |
if(this.list_search){ | |
return this.list.filter((item)=>{ | |
const tags = item.tags?.map(({ | |
name | |
}) => name.toLowerCase()) ?? [] | |
const arr = [item.name.toLowerCase(), ...tags] | |
return arr.some(e => e.includes(this.list_search)) | |
}) | |
} else { | |
return this.list; | |
} | |
} | |
}, | |
mounted() { | |
document.title = this.pageTitle +' :: '+ this.siteCompany; | |
}, | |
}); | |
app.component('btn', { | |
template: ` | |
<button :class="[ | |
'px-2.5 py-1 inline-block rounded motion-safe:(transition)', | |
variant === 'primary' ? 'bg-pri-500 text-white hover:(bg-pri-700)' : | |
(variant === 'secondary') ? 'bg-sec-400 text-sec-900 hover:(bg-sec-600 text-white)' : | |
'text-gray-700 ring(1 inset current) hover:(opacity-60) dark:(text-gray-500)', | |
off ? 'bg-opacity-50 cursor-not-allowed' : '' | |
]" :disabled="off"><slot></slot></button> | |
`, | |
props: { | |
'variant': String, | |
'off': Boolean, | |
}, | |
}); | |
app.mount('.app'); | |
twind.setup({ | |
mode: 'silent', | |
darkMode: 'class', | |
theme: { | |
extend: { | |
colors: { | |
// brand colors | |
pri: twindColors.indigo, | |
sec: twindColors.yellow, | |
// extra colors | |
cyan: twindColors.cyan, | |
fuchsia: twindColors.fuchsia, | |
lime: twindColors.lime, | |
orange: twindColors.orange, | |
rose: twindColors.rose, | |
sky: twindColors.sky, | |
teal: twindColors.teal, | |
}, | |
fontFamily: (theme) => ({ | |
sans: 'Inter,'+ theme('fontFamily.sans'), | |
//mono: 'Inconsolata,'+ theme('fontFamily.mono'), | |
}), | |
}, | |
}, | |
plugins: { | |
...twindTypography(), | |
'scroll-smooth': { 'scroll-behavior': 'smooth' }, | |
'ratio-16x9': { '@apply': 'pb-[56.25%] overflow-hidden relative' }, | |
'ratio-4x3': { '@apply': 'pb-[75%] overflow-hidden relative' }, | |
'bg-grid': { 'background-image': 'url("data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 4 4\' width=\'4\' height=\'4\'><rect x=\'0\' y=\'0\' width=\'2\' height=\'2\' fill=\'rgba(5,5,5,.33)\'></rect></svg>")', }, | |
'link': { '@apply': 'text-pri-500 font-semibold motion-safe:(transition) hover:(text-pri-800 underline) dark:(text-pri-400 hover:(text-pri-100))' }, | |
}, | |
}) | |
twind.tw(() => ({ | |
'@global': { | |
'.prose': { '@apply': '!text-current dark:(text-current)' }, | |
'.prose a': { '@apply': 'text-pri-500 font-semibold motion-safe:(transition) hover:(text-pri-800 underline) dark:(text-pri-400 hover:(text-pri-100))' }, | |
'.prose h1,.prose h2,.prose h3': { '@apply': 'font-black' }, | |
'.prose h4,.prose h5,.prose h6': { '@apply': 'font-bold' }, | |
'.prose strong,.prose b': { '@apply': '!text-current dark:(text-current)' }, | |
'.prose blockquote': { '@apply': 'border(l-4 gray-500 opacity-25) font-semibold italic' }, | |
'.prose ul': { '@apply': 'ml-4 list-disc' }, | |
'.prose ol': { '@apply': 'ml-4 list-decimal' }, | |
'.prose ul > li,.prose ol > li': { 'padding-left': '.25rem !important' }, | |
'.prose ul > li::marker,.prose ol > li::marker': { '@apply': 'text(gray-500 opacity-70))' }, | |
'.prose hr': { '@apply': 'border(1 gray-500 opacity-25)' }, | |
'.prose table': { '@apply': 'w-full' }, | |
'.prose table th': { '@apply': 'text-left' }, | |
'.prose code': { '@apply': 'font-bold' }, | |
'.prose pre': { '@apply': 'bg-gray-800 text-gray-100 overflow-x-auto' }, | |
//':root [v-cloak]': { '@apply': 'hidden', }, | |
} | |
})) | |
twindObserve.observe(document.documentElement); | |
document.documentElement.removeAttribute('hidden'); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment