Initial commit
This commit is contained in:
commit
0415751046
33 changed files with 1218 additions and 0 deletions
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
33
app.vue
Normal file
33
app.vue
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Nav />
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { setVariables, style } from "~/src/ts/Styles";
|
||||||
|
import { variableValues } from "~/src/ts/Shared";
|
||||||
|
|
||||||
|
function createStyle(): HTMLStyleElement {
|
||||||
|
let el = document.createElement("style");
|
||||||
|
document.querySelector("head")?.appendChild(el);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStyle(el: HTMLStyleElement, content: string) {
|
||||||
|
el.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let el = createStyle();
|
||||||
|
watch(style, s => {
|
||||||
|
setStyle(el, s);
|
||||||
|
});
|
||||||
|
setStyle(el, style.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
setVariables(variableValues);
|
||||||
|
|
||||||
|
</script>
|
71
components/Components.vue
Normal file
71
components/Components.vue
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import { Export, type Result } from "~/src/ts/Export";
|
||||||
|
import { variableValues, components } from "~/src/ts/Shared";
|
||||||
|
|
||||||
|
let data = reactive<Data>({
|
||||||
|
results: [],
|
||||||
|
vars: null
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
results: Result[],
|
||||||
|
vars: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportCss() {
|
||||||
|
let exp = new Export(variableValues, components);
|
||||||
|
data.results = await exp.process();
|
||||||
|
data.vars = exp.vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function kbSize(value: number) {
|
||||||
|
return `${Math.round(value/1024*100)/100} kB`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(name: string, str: string) {
|
||||||
|
let a = document.createElement("a");
|
||||||
|
a.download = name;
|
||||||
|
a.href=`data:text/plain;charset=utf-8,${encodeURIComponent(str)}`;
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showVars() {
|
||||||
|
console.log(data.vars);
|
||||||
|
alert("Check console logs!");
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h2>Components</h2>
|
||||||
|
<div class="components">
|
||||||
|
<form class="box" @submit.prevent="exportCss">
|
||||||
|
<div class="item" v-for="component in components">
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="checkbox" v-model="component.selected">
|
||||||
|
<span>{{ component.name }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
Generate CSS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="box export" v-if="data.results.length && data.vars">
|
||||||
|
<b>CSS Export</b>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-primary" v-for="r in data.results" @click="download(r.name, r.css)">
|
||||||
|
{{ r.name }}
|
||||||
|
<small>{{ kbSize(r.size_gzip) }} (gzip) · {{ kbSize(r.size) }}</small>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#" @click.prevent="showVars">Show Synergy variables</a> ·
|
||||||
|
<a href="#" @click.prevent="download('variables.css', data.vars)">download</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
53
components/Example.vue
Normal file
53
components/Example.vue
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div class="example">
|
||||||
|
<div class="demo">
|
||||||
|
<div v-if="title" class="title">{{ title }}</div>
|
||||||
|
<div ref="example">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ClientOnly>
|
||||||
|
<highlightjs language="html" :code="data.code" />
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import html from "html";
|
||||||
|
|
||||||
|
let example = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
let data = reactive({
|
||||||
|
code: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
let { selector, inner } = defineProps<Props>();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let parent = example.value!;
|
||||||
|
let src = parent.innerHTML;
|
||||||
|
if (selector) {
|
||||||
|
parent = parent.querySelector(selector)!;
|
||||||
|
src = inner ? parent.innerHTML : parent.outerHTML;
|
||||||
|
}
|
||||||
|
data.code = html.prettyPrint(src.replace(/<!--[\s\S]*?-->/g, ""), {indent_size: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
selector?: string,
|
||||||
|
inner?: boolean,
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.example {box-shadow: inset 0 0 0 1px #5553; border-radius: var(--synergy-border-radius); overflow: hidden; margin: 30px 0;}
|
||||||
|
|
||||||
|
.example .demo {padding: 15px;}
|
||||||
|
.example pre {margin: 0;}
|
||||||
|
.example .title {margin-bottom: 12px;}
|
||||||
|
|
||||||
|
</style>
|
25
components/Header.vue
Normal file
25
components/Header.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="inner">
|
||||||
|
<div class="grid">
|
||||||
|
<Logo />
|
||||||
|
<h1>Simple framework with CSS-only <span class="color">UI components</span></h1>
|
||||||
|
<p>Modern ready-to-use components made only with CSS.</p>
|
||||||
|
<div class="btn-row" style="margin: 30px 0;">
|
||||||
|
<NuxtLink to="/get-started" class="btn btn-lg btn-primary">Get started</NuxtLink>
|
||||||
|
<NuxtLink to="/components" class="btn btn-lg">Components</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
header > * {margin: 15px 0;}
|
||||||
|
header h1 .color, .gradient-text {background-clip: text; -webkit-background-clip: text; background-image: linear-gradient(10deg, var(--synergy-border-active), var(--synergy-border)); color: transparent;}
|
||||||
|
|
||||||
|
header .grid {max-width: 450px; text-align: center; margin: auto;}
|
||||||
|
header .btn-row {justify-content: center;}
|
||||||
|
|
||||||
|
</style>
|
12
components/Logo.vue
Normal file
12
components/Logo.vue
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<svg class="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="0 0 74.08 18.52">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="a">
|
||||||
|
<stop offset="0" style="stop-color:var(--synergy-border);stop-opacity:1"/>
|
||||||
|
<stop offset="1" style="stop-color:var(--synergy-border-active);stop-opacity:1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient xlink:href="#a" id="b" x1="48.66" x2="46.25" y1="1.83" y2="22.35" gradientTransform="translate(-.05 .57)" gradientUnits="userSpaceOnUse"/>
|
||||||
|
</defs>
|
||||||
|
<path d="M13.03 5.65c-3.06.08-6.19 1.7-7.72 5.95 3.22-3.41 6.6-6.18 11.34-3.53-2.83.58-4.62 2.51-5.67 4.58-2.94 5.75-6.72 2.05-8.41-.3 1.76 7.13 7.4 5.82 9.62 1.32 1.47-2.97 4.5-5.9 8.75-3.04-.15-2.65-3.97-5.08-7.9-4.98Zm20.01 3.91c-2.16 0-3.6 1.11-3.6 2.8 0 3.1 4.74 2.09 4.74 3.93 0 .65-.57 1.07-1.7 1.07-.82 0-1.82-.2-2.7-.57l-.49 1.36c.97.43 2.07.7 3.12.7 2.21 0 3.54-1.08 3.54-2.78 0-3.26-4.77-2.13-4.77-3.95 0-.7.64-1.08 1.76-1.08.76 0 1.58.14 2.22.39l.48-1.35a6.58 6.58 0 0 0-2.6-.52Zm-13.46 1.59c-1.9 0-3.87 1.45-4.99 3.7-1.47 2.97-4.48 5.9-8.74 3.04.26 4.7 12.13 8.75 15.62-.97-3.22 3.41-6.6 6.18-11.33 3.53 2.83-.58 4.61-2.51 5.67-4.58 2.94-5.75 6.72-2.05 8.41.3-.88-3.56-2.73-5.02-4.64-5.02zm34.47 1.11c-1.89 0-3.15 1.36-3.15 3.32 0 2.04 1.32 3.27 3.48 3.27.75 0 1.55-.14 2.24-.43l-.36-1.16c-.51.21-1.07.33-1.6.33-1.15 0-1.84-.53-2.04-1.56h4.1c.04-.26.07-.66.07-.96 0-1.72-1.06-2.8-2.74-2.8zm11.97 0c-1.74 0-2.94 1.33-2.94 3.35 0 1.97 1.12 3.24 2.73 3.24.7 0 1.29-.24 1.75-.67v.52c0 1.1-.6 1.67-1.73 1.67a4.8 4.8 0 0 1-1.75-.38l-.33 1.25c.68.32 1.37.48 2.09.48 2.02 0 3.35-1.24 3.35-3.09V12.4H68l-.2.58a2.35 2.35 0 0 0-1.79-.7zm-18.73.03a3 3 0 0 0-2.04.8l-.29-.7h-1.18v6.33h1.65v-4.5a2 2 0 0 1 1.29-.49c.8 0 1.23.46 1.23 1.32v3.67h1.66v-3.9c0-1.57-.88-2.53-2.32-2.53zm14.33.05c-.76 0-1.46.37-1.94 1l-.16-.95h-1.31v6.33h1.65v-4.1c.38-.46.92-.72 1.5-.72.27 0 .57.05.8.13l.4-1.52c-.28-.1-.6-.17-.94-.17zm-25.3.05 2.45 6.33-1.14 2.83h1.78l1.02-2.83 2.44-6.33H41.2l-1.58 4.82-1.48-4.82Zm33.78 0 2.46 6.33-1.15 2.83h1.79l1.01-2.83 2.44-6.33H75l-1.58 4.82-1.49-4.82zm-16.16 1.16c.82 0 1.29.52 1.29 1.46h-2.66c.1-.96.57-1.46 1.37-1.46zm12.32.12c.55 0 1 .23 1.3.66v2.58c-.29.35-.71.55-1.23.55-.96 0-1.57-.76-1.57-1.96 0-1.12.56-1.83 1.5-1.83z" style="opacity:1;fill:url(#b);stroke-width:.342417" transform="translate(-2.57 -4.99)"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
8
components/Nav.vue
Normal file
8
components/Nav.vue
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<template>
|
||||||
|
<nav>
|
||||||
|
<div class="inner">
|
||||||
|
<NuxtLink to="/">Home</NuxtLink>
|
||||||
|
<NuxtLink to="/components">Components</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
81
components/Preview.vue
Normal file
81
components/Preview.vue
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<template>
|
||||||
|
<div class="preview">
|
||||||
|
<form class="form" @submit.prevent>
|
||||||
|
|
||||||
|
<div class="inp inp-fancy">
|
||||||
|
<input type="text" placeholder=" ">
|
||||||
|
<label>Name</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inp inp-fancy">
|
||||||
|
<input type="text" placeholder=" ">
|
||||||
|
<label>Surname</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inp inp-fancy select">
|
||||||
|
<select>
|
||||||
|
<option disabled>Disabled</option>
|
||||||
|
<option>Guest</option>
|
||||||
|
<option>User</option>
|
||||||
|
<option>Administrator</option>
|
||||||
|
</select>
|
||||||
|
<label>Role</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inp inp-fancy">
|
||||||
|
<textarea placeholder=" ">Some very long text...</textarea>
|
||||||
|
<label>Long text</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-text">
|
||||||
|
<label for="toggle1">Toggle me! I'm a toggle.</label>
|
||||||
|
<div class="toggle">
|
||||||
|
<input id="toggle1" type="checkbox">
|
||||||
|
<div class="indicator"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cbox-row">
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="radio" name="radios" checked>
|
||||||
|
<span>Radio 1</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="radio" name="radios">
|
||||||
|
<span>Radio 2</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="checkbox">
|
||||||
|
<span>Check me!</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs" checked>
|
||||||
|
<div>Home</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs">
|
||||||
|
<div>Account</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs">
|
||||||
|
<div>Settings</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-primary">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
<button type="reset" class="btn">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
63
components/Settings.vue
Normal file
63
components/Settings.vue
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { variables } from "~/src/ts/Synergy";
|
||||||
|
import { Preset } from "~/src/ts/Preset";
|
||||||
|
import { variableValues } from "~/src/ts/Shared";
|
||||||
|
import { setVariables } from "~/src/ts/Styles";
|
||||||
|
import Components from "./Components.vue";
|
||||||
|
|
||||||
|
const variableTypeIcon = {
|
||||||
|
color: "ic:outline-color-lens",
|
||||||
|
number: "mdi:numeric"
|
||||||
|
};
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
setVariables(variableValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPreset(preset: Preset) {
|
||||||
|
const variables = preset.getColorVariables() as ComboObject;
|
||||||
|
Object.keys(variables).forEach(key => {
|
||||||
|
variableValues[key] = variables[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="settings">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Variables</h2>
|
||||||
|
<div class="variables">
|
||||||
|
<template v-for="v in variables">
|
||||||
|
<label :for="v.name">
|
||||||
|
<Icon :name="variableTypeIcon[v.type]" />
|
||||||
|
{{ v.name }}
|
||||||
|
</label>
|
||||||
|
<div v-if="v.type == 'color'">
|
||||||
|
<div class="colori" :style="{ background: variableValues[v.name] }" />
|
||||||
|
<div class="inp inp-sm">
|
||||||
|
<input v-model="variableValues[v.name]" type="string" :id="v.name" @change="update">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="inp inp-sm">
|
||||||
|
<input v-model="variableValues[v.name]" type="string" :id="v.name" @change="update">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Presets</h2>
|
||||||
|
<div class="presets">
|
||||||
|
<div v-for="preset in Preset.presets" @click="applyPreset(preset)">
|
||||||
|
<div :style="`background-color: ${c?.hexFormat()}`" v-for="c in preset.colors"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Components />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
5
declaration.d.ts
vendored
Normal file
5
declaration.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module '*.css';
|
||||||
|
|
||||||
|
declare interface ComboObject {
|
||||||
|
[U: string]: any
|
||||||
|
};
|
15
nuxt.config.ts
Normal file
15
nuxt.config.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: "2024-08-19",
|
||||||
|
|
||||||
|
css: [
|
||||||
|
"~/src/css/style.css",
|
||||||
|
"~/src/css/demo.css"
|
||||||
|
],
|
||||||
|
modules: [
|
||||||
|
"@nuxt/icon"
|
||||||
|
],
|
||||||
|
devtools: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
})
|
21
package.json
Normal file
21
package.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
|
"@iconify-json/ic": "^1.1.18",
|
||||||
|
"@iconify-json/mdi": "^1.1.68",
|
||||||
|
"@nuxt/icon": "^1.4.5",
|
||||||
|
"html": "^1.0.0",
|
||||||
|
"nuxt": "^3.12.4",
|
||||||
|
"nuxt-icon": "^1.0.0-beta.7",
|
||||||
|
"vue": "latest"
|
||||||
|
}
|
||||||
|
}
|
60
pages/components/button.vue
Normal file
60
pages/components/button.vue
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Buttons</h1>
|
||||||
|
|
||||||
|
<p>Synergy contains primary and secondary buttons.</p>
|
||||||
|
|
||||||
|
<Example title="Button variants" selector=".btn-row" :inner="true">
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-primary">Primary</button>
|
||||||
|
<button class="btn">Secondary</button>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>Available button sizes are large, normal and small.</p>
|
||||||
|
|
||||||
|
<Example selector=".btn-row" :inner="true">
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-lg">Large</button>
|
||||||
|
<button class="btn">Normal</button>
|
||||||
|
<button class="btn btn-sm">Small</button>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>It is also possible to make buttons rounded.</p>
|
||||||
|
|
||||||
|
<Example selector=".btn-row" :inner="true">
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn btn-rounded btn-primary">Primary</button>
|
||||||
|
<button class="btn btn-rounded">Secondary</button>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<h2>Elements</h2>
|
||||||
|
|
||||||
|
<p>It is possible to use <code>.btn</code> class names on multiple HTML elements.</p>
|
||||||
|
|
||||||
|
<Example selector=".btn-row" :inner="true">
|
||||||
|
<div class="btn-row">
|
||||||
|
<a href="#" class="btn">Link</a>
|
||||||
|
<button class="btn">Button</button>
|
||||||
|
<input class="btn" type="button" value="Input" />
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<h2>Button rows</h2>
|
||||||
|
|
||||||
|
<p>You can place buttons in a row using <code>.btn-row</code> class name.</p>
|
||||||
|
|
||||||
|
<Example selector=".btn-row">
|
||||||
|
<div class="btn-row">
|
||||||
|
<button class="btn btn-primary">Get started</button>
|
||||||
|
<button class="btn">Information</button>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
22
pages/components/checkbox.vue
Normal file
22
pages/components/checkbox.vue
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Checkbox</h1>
|
||||||
|
|
||||||
|
<Example selector=".cbox-row">
|
||||||
|
<div class="cbox-row">
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<span>Select me</span>
|
||||||
|
</label>
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<span>Select me</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
44
pages/components/index.vue
Normal file
44
pages/components/index.vue
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner text-center">
|
||||||
|
|
||||||
|
<h1>Components</h1>
|
||||||
|
<p>All available components in Synergy.</p>
|
||||||
|
|
||||||
|
<div class="component-grid">
|
||||||
|
<NuxtLink v-for="c in components" :to="`/components/${c.url}`">
|
||||||
|
<Icon :name="c.icon" />
|
||||||
|
<h3>{{ c.title }}</h3>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
let components = [
|
||||||
|
{url: "button", title: "Button", icon: "mdi:button-cursor"},
|
||||||
|
{url: "input", title: "Input", icon: "mdi:form-textbox"},
|
||||||
|
{url: "select", title: "Select", icon: "mdi:form-select"},
|
||||||
|
{url: "textarea", title: "Textarea", icon: "mdi:card-text-outline"},
|
||||||
|
{url: "toggle", title: "Toggle", icon: "mdi:toggle-switch-off-outline"},
|
||||||
|
{url: "radio", title: "Radio", icon: "mdi:radiobox-marked"},
|
||||||
|
{url: "checkbox", title: "Checkbox", icon: "mdi:checkbox-outline"},
|
||||||
|
{url: "tabs", title: "Tabs", icon: "ic:baseline-tab"}
|
||||||
|
];
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.component-grid {display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 70px 0;}
|
||||||
|
|
||||||
|
.component-grid > * {display: flex; flex-direction: column; align-items: center; border: var(--synergy-border-width) solid var(--synergy-border); border-radius: var(--synergy-border-radius); padding: 20px; gap: 10px; text-decoration: none;}
|
||||||
|
.component-grid > *:hover {border-color: var(--synergy-border-active);}
|
||||||
|
|
||||||
|
.component-grid .iconify {font-size: 24px;}
|
||||||
|
.component-grid > * > * {margin: 0;}
|
||||||
|
|
||||||
|
</style>
|
57
pages/components/input.vue
Normal file
57
pages/components/input.vue
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Inputs</h1>
|
||||||
|
|
||||||
|
<p>Synergy treats inputs, <NuxtLink to="/components/select">selects</NuxtLink> and <NuxtLink to="/components/textarea">text areas</NuxtLink> all like inputs. This page apply to all of them.</p>
|
||||||
|
|
||||||
|
<Example selector=".inp">
|
||||||
|
<div class="form">
|
||||||
|
<input type="text" class="inp" placeholder="Enter your name">
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>Inputs can be placed into divs with the same class.</p>
|
||||||
|
|
||||||
|
<Example selector=".inp">
|
||||||
|
<div class="form">
|
||||||
|
<div class="inp">
|
||||||
|
<input type="text" placeholder="Enter your name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<h2>Sizes</h2>
|
||||||
|
|
||||||
|
<p>All inputs have 3 available sizes: large, normal and small.</p>
|
||||||
|
|
||||||
|
<Example selector=".form" :inner="true">
|
||||||
|
<div class="form">
|
||||||
|
<input type="text" class="inp inp-lg" placeholder="Large input" />
|
||||||
|
<input type="text" class="inp inp" placeholder="Normal input" />
|
||||||
|
<input type="text" class="inp inp-sm" placeholder="Small input" />
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<h2>Fancy inputs</h2>
|
||||||
|
|
||||||
|
<p>Synergy also provides an alternative style for inputs with Material-like labels inside of inputs.</p>
|
||||||
|
<p>These inputs support only normal size.</p>
|
||||||
|
|
||||||
|
<Example selector=".inp">
|
||||||
|
<div class="form">
|
||||||
|
<div class="inp inp-fancy">
|
||||||
|
<input type="text" placeholder="">
|
||||||
|
<label>Label</label>
|
||||||
|
</div>
|
||||||
|
<div class="inp inp-fancy">
|
||||||
|
<input type="text" placeholder="" value="Text">
|
||||||
|
<label>Label</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
22
pages/components/radio.vue
Normal file
22
pages/components/radio.vue
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Radio</h1>
|
||||||
|
|
||||||
|
<Example selector=".cbox-row">
|
||||||
|
<div class="cbox-row">
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="radio" name="radios" checked />
|
||||||
|
<span>Option 1</span>
|
||||||
|
</label>
|
||||||
|
<label class="cbox">
|
||||||
|
<input type="radio" name="radios" />
|
||||||
|
<span>Option 2</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
35
pages/components/select.vue
Normal file
35
pages/components/select.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Select</h1>
|
||||||
|
|
||||||
|
<p>Selects in Synergy are treated as <NuxtLink to="/components/input">inputs</NuxtLink> and are used in a similar way.</p>
|
||||||
|
|
||||||
|
<Example selector=".inp">
|
||||||
|
<div class="form">
|
||||||
|
<select class="inp">
|
||||||
|
<option>Option 1</option>
|
||||||
|
<option>Option 2</option>
|
||||||
|
<option disabled>Option 3</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>Synergy provides a way to show a custom dropdown icon as shown below.</p>
|
||||||
|
|
||||||
|
<Example selector=".inp">
|
||||||
|
<div class="form">
|
||||||
|
<div class="inp select">
|
||||||
|
<select>
|
||||||
|
<option>Apple</option>
|
||||||
|
<option>Orange</option>
|
||||||
|
<option disabled>Avocado</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
56
pages/components/tabs.vue
Normal file
56
pages/components/tabs.vue
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Tabs</h1>
|
||||||
|
|
||||||
|
<Example>
|
||||||
|
<div class="tabs">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs1" checked>
|
||||||
|
<div>Home</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs1">
|
||||||
|
<div>Account</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs1">
|
||||||
|
<div>Settings</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>There is also another variant of tabs called full tabs, it is shown below.</p>
|
||||||
|
|
||||||
|
<Example>
|
||||||
|
<div class="tabs tabs-full">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs2" checked>
|
||||||
|
<div>Tab 1</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs2">
|
||||||
|
<div>Tab 2</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<p>If you need to remove the padding on sides of the tabs, you can use class <code>.tabs-no-padding</code>.</p>
|
||||||
|
|
||||||
|
<Example>
|
||||||
|
<div class="tabs tabs-no-padding">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs2" checked>
|
||||||
|
<div>Tab 1</div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="tabs2">
|
||||||
|
<div>Tab 2</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
17
pages/components/textarea.vue
Normal file
17
pages/components/textarea.vue
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Text area</h1>
|
||||||
|
|
||||||
|
<p>Text areas in Synergy are treated as <NuxtLink to="/components/input">inputs</NuxtLink> and are used in a similar way.</p>
|
||||||
|
|
||||||
|
<Example selector=".form" :inner="true">
|
||||||
|
<div class="form">
|
||||||
|
<textarea class="inp">Text inside</textarea>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
28
pages/components/toggle.vue
Normal file
28
pages/components/toggle.vue
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Toggle</h1>
|
||||||
|
|
||||||
|
<Example>
|
||||||
|
<div class="toggle">
|
||||||
|
<input id="toggle1" type="checkbox">
|
||||||
|
<div class="indicator"></div>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
<Example selector=".form" :inner="true">
|
||||||
|
<div class="form">
|
||||||
|
<div class="toggle-text">
|
||||||
|
<label for="toggle2">Toggle me! I'm a toggle.</label>
|
||||||
|
<div class="toggle">
|
||||||
|
<input id="toggle2" type="checkbox">
|
||||||
|
<div class="indicator"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
9
pages/get-started.vue
Normal file
9
pages/get-started.vue
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h1>Get started</h1>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
21
pages/index.vue
Normal file
21
pages/index.vue
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<section class="sec-bg" id="settings">
|
||||||
|
<div class="inner">
|
||||||
|
<ClientOnly>
|
||||||
|
<Settings />
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="inner" id="preview">
|
||||||
|
<Preview />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
10
plugins/highlight.client.ts
Normal file
10
plugins/highlight.client.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import hljs from 'highlight.js/lib/core'
|
||||||
|
import xml from 'highlight.js/lib/languages/xml'
|
||||||
|
import highlightJS from '@highlightjs/vue-plugin'
|
||||||
|
import 'highlight.js/styles/atom-one-dark.css'
|
||||||
|
import { defineNuxtPlugin } from 'nuxt/app';
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
hljs.registerLanguage('html', xml)
|
||||||
|
nuxtApp.vueApp.use(highlightJS)
|
||||||
|
});
|
66
src/css/demo.css
Normal file
66
src/css/demo.css
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
:root {
|
||||||
|
--site-padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
:root {
|
||||||
|
--site-padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {background: var(--synergy-site-bg);}
|
||||||
|
html, body {color: var(--synergy-text-color); font-family: Cantarell, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Open Sans, Helvetica Neue, Arial, Noto Sans, Roboto, sans-serif; font-size: 18px; margin: 0;}
|
||||||
|
|
||||||
|
*, *::before, *::after {box-sizing: border-box;}
|
||||||
|
|
||||||
|
:is(header, section, footer, nav) > .inner {margin: 70px auto; padding: 0 var(--site-padding); max-width: 1000px;}
|
||||||
|
|
||||||
|
nav {background: var(--synergy-tab-highlight); border-bottom: var(--synergy-border-width) solid var(--synergy-border); overflow: hidden;}
|
||||||
|
nav > .inner {margin: 15px auto; display: flex; gap: 25px; flex-wrap: wrap; align-items: center;}
|
||||||
|
nav a {text-decoration: none;}
|
||||||
|
nav svg {max-height: 33px;}
|
||||||
|
|
||||||
|
.logo {max-width: 200px;}
|
||||||
|
|
||||||
|
.sec-bg {background-color: var(--synergy-tab-highlight); padding: .1px; border: var(--synergy-border-width) solid var(--synergy-border); border-right-width: 0; border-left-width: 0;}
|
||||||
|
|
||||||
|
.settings {display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: start;}
|
||||||
|
.settings > * {position: sticky; top: 0;}
|
||||||
|
.settings h2 {text-align: center;}
|
||||||
|
.settings > * > :first-child {margin-top: 0;}
|
||||||
|
.settings .variables {display: grid; gap: 10px 20px; align-items: center; grid-template-columns: max-content 1fr;}
|
||||||
|
.settings .variables > * {display: flex; gap: 10px; align-items: center;}
|
||||||
|
|
||||||
|
.settings .presets {display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px;}
|
||||||
|
.settings .presets > * {display: flex; border-radius: 10px; overflow: hidden; cursor: pointer; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .1);}
|
||||||
|
.settings .presets > * > * {height: 100px; flex: 1;}
|
||||||
|
|
||||||
|
.settings .components .item {margin-bottom: 10px;}
|
||||||
|
.settings .components .btn-row {margin-top: 15px;}
|
||||||
|
.settings .components .box {margin-top: 15px; background-color: var(--synergy-bg); border: var(--synergy-border-width) solid var(--synergy-border); padding: 15px; border-radius: var(--synergy-border-radius);}
|
||||||
|
.settings .components .btn small {display: block; font-weight: 400; font-size: 11px; margin-top: 3px;}
|
||||||
|
.settings .components .export .btn-row {margin: 15px 0;}
|
||||||
|
|
||||||
|
@media screen and (max-width: 850px) {
|
||||||
|
.settings {display: flex; flex-direction: column-reverse; align-items: normal;}
|
||||||
|
.settings > * {position: static;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.settings .variables {display: block;}
|
||||||
|
.settings .variables label {margin: 20px 0 10px;}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {display: flex; flex-direction: column; gap: 15px; max-width: 450px;}
|
||||||
|
.form > * {margin: 0;}
|
||||||
|
|
||||||
|
.colori {aspect-ratio: 1 / 1; height: 36px; border-radius: var(--synergy-border-radius);}
|
||||||
|
|
||||||
|
a {color: inherit;}
|
||||||
|
|
||||||
|
h1 {font-size: 2em;}
|
||||||
|
h2 {font-size: 1.5em;}
|
||||||
|
|
||||||
|
code:not(.hljs) {font-size: .9em; background-color: #aaa2; padding: 3px 5px; border-radius: var(--synergy-border-radius);}
|
||||||
|
|
||||||
|
.text-center {text-align: center;}
|
6
src/css/style.css
Normal file
6
src/css/style.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@import url("~/public/src/button.css");
|
||||||
|
@import url("~/public/src/input.css");
|
||||||
|
@import url("~/public/src/fancy-input.css");
|
||||||
|
@import url("~/public/src/toggle.css");
|
||||||
|
@import url("~/public/src/checkbox.css");
|
||||||
|
@import url("~/public/src/tabs.css");
|
72
src/ts/Color.ts
Normal file
72
src/ts/Color.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
export class Color {
|
||||||
|
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
a: number;
|
||||||
|
|
||||||
|
constructor(r: number, g: number, b: number, a: number = 1) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
this.a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Color(this.r, this.g, this.b, this.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromHex(hex: string) {
|
||||||
|
let [r, g, b] = this.hexToRgb(hex);
|
||||||
|
return new Color(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static hexToRgb(hex: string) {
|
||||||
|
hex = hex.replace(/^#/, '');
|
||||||
|
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
||||||
|
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
||||||
|
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
||||||
|
return [r, g, b];
|
||||||
|
}
|
||||||
|
|
||||||
|
alpha(alpha: number) {
|
||||||
|
return new Color(this.r, this.g, this.b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
rgbFormat() {
|
||||||
|
let rgb = `${this.r*255}, ${this.g*255}, ${this.b*255}`;
|
||||||
|
return this.a == 1 ? `rgb(${rgb})` : `rgba(${rgb}, ${this.a})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
hexFormat() {
|
||||||
|
let hex = `#${(1 << 24 | (this.r*255) << 16 | (this.g*255) << 8 | (this.b*255)).toString(16).slice(1)}`;
|
||||||
|
return this.a != 1 ? `${hex}${(Math.floor(this.a * 255).toString(16).padStart(2, '0'))}` : hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
contrast(otherColor: Color) {
|
||||||
|
const getRelativeLuminance = (rgb: number) => {
|
||||||
|
const sRGB = rgb / 255;
|
||||||
|
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4);
|
||||||
|
};
|
||||||
|
const luminance1 = getRelativeLuminance(this.r) * 0.2126 +
|
||||||
|
getRelativeLuminance(this.g) * 0.7152 +
|
||||||
|
getRelativeLuminance(this.b) * 0.0722;
|
||||||
|
const luminance2 = getRelativeLuminance(otherColor.r) * 0.2126 +
|
||||||
|
getRelativeLuminance(otherColor.g) * 0.7152 +
|
||||||
|
getRelativeLuminance(otherColor.b) * 0.0722;
|
||||||
|
const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05);
|
||||||
|
return (contrastRatio*100)-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(otherColor: Color) {
|
||||||
|
return this.r == otherColor.r && this.g == otherColor.g && this.b == otherColor.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
mix(color: Color, ratio: number): Color {
|
||||||
|
const r = Math.round(this.r*255 * (1 - ratio) + color.r*255 * ratio);
|
||||||
|
const g = Math.round(this.g*255 * (1 - ratio) + color.g*255 * ratio);
|
||||||
|
const b = Math.round(this.b*255 * (1 - ratio) + color.b*255 * ratio);
|
||||||
|
return new Color(r/255, g/255, b/255);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
92
src/ts/Export.ts
Normal file
92
src/ts/Export.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { type Component, type Variable, variables } from "./Synergy";
|
||||||
|
|
||||||
|
export class Export {
|
||||||
|
|
||||||
|
private variables: ComboObject;
|
||||||
|
private components: Component[];
|
||||||
|
|
||||||
|
public vars: string | null = null;
|
||||||
|
public css: Result[] = [];
|
||||||
|
|
||||||
|
constructor(variables: ComboObject, components: Component[]) {
|
||||||
|
this.variables = variables;
|
||||||
|
this.components = components;
|
||||||
|
}
|
||||||
|
|
||||||
|
async process() {
|
||||||
|
|
||||||
|
let cssParts = [];
|
||||||
|
let selectedComponents = new Set<string>();
|
||||||
|
for (let c of this.components) {
|
||||||
|
if (!c.selected) continue;
|
||||||
|
let value = await (await fetch(`./src/${c.id}.css`)).text();
|
||||||
|
value = `/* ${c.name} */\n\n${value}`;
|
||||||
|
cssParts.push(value);
|
||||||
|
selectedComponents.add(c.id);
|
||||||
|
}
|
||||||
|
let css = cssParts.join("\n\n/* ------------------- */\n\n");
|
||||||
|
|
||||||
|
this.vars = this.getVariables(selectedComponents);
|
||||||
|
cssParts.unshift(this.vars);
|
||||||
|
|
||||||
|
this.css = [];
|
||||||
|
await this.addResult("synergy.min.css", this.minify(css));
|
||||||
|
await this.addResult("synergy.css", css);
|
||||||
|
|
||||||
|
return this.css;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getVariables(selectedComponents: Set<string>) {
|
||||||
|
let root: string[] = [];
|
||||||
|
for (let v of variables) {
|
||||||
|
if (!this.required(v, selectedComponents)) continue;
|
||||||
|
root.push(`\t--synergy-${v.name}: ${this.variables[v.name]};`);
|
||||||
|
}
|
||||||
|
return `:root {\n${root.join("\n")}\n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
required(variable: Variable, selectedComponents: Set<string>) {
|
||||||
|
if (!variable.requiredBy) return true;
|
||||||
|
for (let id of variable.requiredBy) {
|
||||||
|
if (selectedComponents.has(id)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addResult(name: string, css: string) {
|
||||||
|
this.css.push({
|
||||||
|
name,
|
||||||
|
css,
|
||||||
|
size: this.getSize(css),
|
||||||
|
size_gzip: await this.getCompressedSize(css)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompressedSize(content: string) {
|
||||||
|
let ds = new CompressionStream("gzip");
|
||||||
|
let blob = new Blob([content]);
|
||||||
|
let compressedStream = blob.stream().pipeThrough(ds);
|
||||||
|
return (await new Response(compressedStream).blob()).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSize(content: string) {
|
||||||
|
return (new TextEncoder().encode(content)).length
|
||||||
|
}
|
||||||
|
|
||||||
|
minify(value: string) {
|
||||||
|
return value
|
||||||
|
.replace(/([^0-9a-zA-Z\.#])\s+/g, "$1")
|
||||||
|
.replace(/\s([^0-9a-zA-Z\.#]+)/g, "$1")
|
||||||
|
.replace(/;}/g, "}")
|
||||||
|
.replace(/\/\*.*?\*\//g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Result {
|
||||||
|
name: string,
|
||||||
|
css: string,
|
||||||
|
size: number,
|
||||||
|
size_gzip: number
|
||||||
|
}
|
105
src/ts/Preset.ts
Normal file
105
src/ts/Preset.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { Color } from "./Color";
|
||||||
|
|
||||||
|
export class Preset {
|
||||||
|
|
||||||
|
static readonly presets: Preset[] = [
|
||||||
|
new Preset({main: "#337e2c", text: "#031601", bg: "#f3f7f2"}),
|
||||||
|
new Preset({main: "#1c71d8", text: "#030e1c", bg: "#ffffff"}),
|
||||||
|
new Preset({main: "#9141ac", text: "#613583", bg: "#ffffff", "site-bg": "#f6edf7"}),
|
||||||
|
new Preset({main: "#a51d2d", text: "#3d3846", bg: "#f6f2f1", "site-bg": "#f1e9e8"}),
|
||||||
|
new Preset({main: "#865e3c", text: "#63452c", bg: "#f9f7f4", "site-bg": "#ffffff"}),
|
||||||
|
new Preset({main: "#F45662", text: "#c4aeae", bg: "#181218"}),
|
||||||
|
new Preset({main: "#E66993", text: "#dddddd", bg: "#18181b"}),
|
||||||
|
new Preset({main: "#ffab9b", text: "#E8C3BC", bg: "#191e23"}),
|
||||||
|
new Preset({main: "#9b8fe4", text: "#cfcef4", bg: "#090818", "site-bg": "#100E22"}),
|
||||||
|
];
|
||||||
|
|
||||||
|
readonly colors: Colors<Color>;
|
||||||
|
|
||||||
|
constructor(colors: Colors<Color | string>) {
|
||||||
|
this.colors = Preset.convertColors(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectColor(colors: ColorType | ColorType[]): Color {
|
||||||
|
let input = Array.isArray(colors) ? colors : [colors];
|
||||||
|
for (let key of input) {
|
||||||
|
let val = this.colors[key];
|
||||||
|
if (val) return val;
|
||||||
|
}
|
||||||
|
return this.colors["main"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getColorVariables() {
|
||||||
|
const cMain = this.selectColor("main");
|
||||||
|
const cBg = this.selectColor("bg");
|
||||||
|
const cSiteBg = this.selectColor(["site-bg", "bg"]);
|
||||||
|
const cText = this.selectColor("text");
|
||||||
|
const cGray = new Color(.6, .6, .6);
|
||||||
|
const cBtn = cBg.mix(cGray, .6).mix(cMain, .1);
|
||||||
|
const colors = {
|
||||||
|
"border": cMain.mix(cBg, .6),
|
||||||
|
"border-active": cMain,
|
||||||
|
"focus-highlight": cMain.alpha(.25),
|
||||||
|
"tab-highlight": cMain.alpha(.1),
|
||||||
|
"label": cMain.mix(cBg, .2),
|
||||||
|
"label-active": cMain,
|
||||||
|
"btn-primary-bg": cMain.mix(cBg, .2),
|
||||||
|
"btn-primary-bg-hover": cMain.mix(cBg, .1),
|
||||||
|
"btn-primary-text-color": this.cSelectContrast(cMain, cText, cBg, Color.fromHex("#fff"), Color.fromHex("#000")),
|
||||||
|
"btn-focus-highlight": cBtn.alpha(.25),
|
||||||
|
"btn-bg": cBtn.mix(cBg, .7),
|
||||||
|
"btn-bg-hover": cBtn.mix(cBg, .45),
|
||||||
|
"text-color": cText,
|
||||||
|
"bg": cBg,
|
||||||
|
"site-bg": cSiteBg
|
||||||
|
};
|
||||||
|
return this.convertColorsToHex(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private cSelectContrast(color: Color, ...colors: Color[]) {
|
||||||
|
let bestContrast = 0;
|
||||||
|
let bestColor;
|
||||||
|
for (const otherColor of colors) {
|
||||||
|
const contrast = color.contrast(otherColor);
|
||||||
|
if (contrast > bestContrast) {
|
||||||
|
bestContrast = contrast;
|
||||||
|
bestColor = otherColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertColorsToHex(colors: ComboObject) {
|
||||||
|
const hexColors: ComboObject = {};
|
||||||
|
for (const key in colors) {
|
||||||
|
if (colors.hasOwnProperty(key)) {
|
||||||
|
hexColors[key] = colors[key].hexFormat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hexColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static convertColors(input: Colors<Color | string>) {
|
||||||
|
return Object.entries(input).reduce<Partial<Colors<Color>>>((acc, [key, value]) => {
|
||||||
|
acc[key as ColorType] = value instanceof Color ? value : Color.fromHex(value);
|
||||||
|
return acc;
|
||||||
|
}, {}) as Colors<Color>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandom() {
|
||||||
|
return this.presets[Math.floor(Math.random() * this.presets.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AcceptedColor = Color | string;
|
||||||
|
|
||||||
|
// TODO: add more colors, use them if available and more specific
|
||||||
|
export interface Colors<T> {
|
||||||
|
main: T,
|
||||||
|
text: T,
|
||||||
|
bg: T,
|
||||||
|
"site-bg"?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorType = keyof Colors<any>;
|
11
src/ts/Shared.ts
Normal file
11
src/ts/Shared.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { reactive, watch } from "vue";
|
||||||
|
import { components as synergyComponents, getDefaultVariables, type Component } from "./Synergy";
|
||||||
|
import { setVariables } from "./Styles";
|
||||||
|
|
||||||
|
export let variableValues = reactive<ComboObject>(getDefaultVariables());
|
||||||
|
|
||||||
|
export let components = reactive<Component[]>(synergyComponents);
|
||||||
|
|
||||||
|
watch(() => variableValues, () => {
|
||||||
|
setVariables(variableValues);
|
||||||
|
}, {deep: true});
|
15
src/ts/Styles.ts
Normal file
15
src/ts/Styles.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export let style = ref("");
|
||||||
|
|
||||||
|
function parseVariables(variables: ComboObject) {
|
||||||
|
let vars: string[] = [];
|
||||||
|
Object.keys(variables).forEach(key => {
|
||||||
|
if (variables[key] == null) return;
|
||||||
|
vars.push(`--synergy-${key}: ${variables[key]};`);
|
||||||
|
});
|
||||||
|
return vars.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setVariables(variables: ComboObject) {
|
||||||
|
const vars = parseVariables(variables);
|
||||||
|
style.value = `:root {${vars}}`;
|
||||||
|
}
|
56
src/ts/Synergy.ts
Normal file
56
src/ts/Synergy.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { Preset } from "./Preset";
|
||||||
|
|
||||||
|
export const variables: Variable[] = [
|
||||||
|
{name: "border", type: "color", requiredBy: ["input", "checkbox", "toggle", "tabs"]},
|
||||||
|
{name: "border-active", type: "color", requiredBy: ["input", "checkbox", "toggle", "tabs"]},
|
||||||
|
{name: "border-width", type: "number", requiredBy: ["input", "checkbox", "toggle"]},
|
||||||
|
{name: "border-radius", type: "number", requiredBy: ["input", "button", "checkbox", "tabs"]},
|
||||||
|
{name: "focus-highlight", type: "color", requiredBy: ["input", "button", "checkbox"]},
|
||||||
|
{name: "tab-highlight", type: "color", requiredBy: ["tabs"]},
|
||||||
|
{name: "tab-bar-height", type: "number", requiredBy: ["tabs"]},
|
||||||
|
{name: "label", type: "color", requiredBy: ["input"]},
|
||||||
|
{name: "label-active", type: "color", requiredBy: ["input"]},
|
||||||
|
{name: "btn-primary-bg", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "btn-primary-bg-hover", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "btn-primary-text-color", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "btn-focus-highlight", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "btn-bg", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "btn-bg-hover", type: "color", requiredBy: ["button"]},
|
||||||
|
{name: "text-color", type: "color", requiredBy: ["input", "button"]},
|
||||||
|
{name: "bg", type: "color", requiredBy: ["input", "checkbox", "toggle"]},
|
||||||
|
{name: "site-bg", type: "color"}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const components: Component[] = [
|
||||||
|
{name: "Button", id: "button", selected: true},
|
||||||
|
{name: "Input", id: "input", selected: true},
|
||||||
|
{name: "Checkbox & radio", id: "checkbox", selected: true},
|
||||||
|
{name: "Fancy input", id: "fancy-input"},
|
||||||
|
{name: "Tabs", id: "tabs", selected: true},
|
||||||
|
{name: "Toggle", id: "toggle", selected: true},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface Variable {
|
||||||
|
name: string,
|
||||||
|
type: VariableType,
|
||||||
|
requiredBy?: ComponentID[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ComponentID = "button" | "input" | "fancy-input" | "checkbox" | "tabs" | "toggle";
|
||||||
|
|
||||||
|
export interface Component {
|
||||||
|
name: string,
|
||||||
|
id: ComponentID,
|
||||||
|
selected?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VariableType = "color" | "number";
|
||||||
|
|
||||||
|
export function getDefaultVariables() {
|
||||||
|
return {
|
||||||
|
"border-width": "2px",
|
||||||
|
"border-radius": ".375rem",
|
||||||
|
"tab-bar-height": "2px",
|
||||||
|
...Preset.getRandom().getColorVariables()
|
||||||
|
};
|
||||||
|
}
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Reference in a new issue