Add customizer

This commit is contained in:
Eric Fennis
2023-04-25 22:49:28 +02:00
parent b15a2dc707
commit ec16fd0ed4
5 changed files with 252 additions and 50 deletions
+94 -41
View File
@@ -1,11 +1,16 @@
<script setup>
import { useCssVar } from '@vueuse/core'
import { useCssVar, syncRef } from '@vueuse/core'
import { useIconSizeContext } from '../composables/useSharedSize'
import RangeSlider from './RangeSlider.vue'
const { size } = useIconSizeContext()
const color = useCssVar(
'--customize-color',
document.documentElement,
{
initialValue: 'currentColor'
initialValue: 'default'
}
)
@@ -17,7 +22,7 @@ const strokeWidth = useCssVar(
}
)
const size = useCssVar(
const iconSize = useCssVar(
'--customize-size',
document.documentElement,
{
@@ -25,57 +30,80 @@ const size = useCssVar(
}
)
syncRef(size, iconSize)
</script>
<template>
<div class="card">
<label for="icon-color" class="customize-label">
Color
</label>
<div class="color-picker">
<h2 class="card-title">
Customizer
</h2>
<div class="input-field">
<div class="input-label">
<label for="icon-color" class="customize-label">
Color
</label>
<div class="color-picker">
<div class="color-input-wrapper">
<div class="color-input-wrapper">
<input
type="color"
id="icon-color"
name="icon-color"
class="color-input"
v-model="color"
/>
</div>
<input
type="color"
id="icon-color"
name="icon-color"
class="color-input"
type="text"
id="icon-color-input"
name="icon-color-input"
class="color-input-text"
v-model="color"
/>
</div>
<input
type="text"
id="icon-color-input"
name="icon-color-input"
class="color-input-text"
v-model="color"
</div>
</div>
<div class="input-field">
<div class="input-label">
<label for="stroke" class="customize-label">
Stroke width
</label>
<div class="display-value">
<span class="customize-label">{{ strokeWidth }}px</span>
</div>
</div>
<RangeSlider
id="stroke"
name="stroke"
v-model="strokeWidth"
:min="0"
:max="8"
/>
</div>
<div class="input-field">
<div class="input-label">
<label for="size" class="customize-label">
Size
</label>
<div class="display-value">
<span class="customize-label">{{ iconSize }}px</span>
</div>
</div>
<RangeSlider
id="size"
name="size"
v-model="iconSize"
:min="16"
:max="48"
/>
</div>
<label for="stroke" class="customize-label">
Stroke width
</label>
<input
type="number"
id="stroke"
name="stroke"
v-model="strokeWidth"
/>
<label for="size" class="customize-label">
Size
</label>
<input
type="number"
id="size"
name="size"
v-model="size"
/>
</div>
</template>
<style scoped>
.customize-label {
padding-bottom: 8px;
line-height: 20px;
font-size: 13px;
font-weight: 600;
@@ -83,9 +111,17 @@ const size = useCssVar(
transition: color .5s;
display: block;
}
.card-title {
font-weight: 700;
color: var(--vp-c-text-1);
line-height: 32px;
font-size: 16px;
margin-bottom: 12px;
}
.card {
background: var(--vp-c-bg);
padding: 24px;
padding: 12px 24px 24px;
border-radius: 12px;
margin-bottom: 24px;
}
@@ -108,7 +144,7 @@ const size = useCssVar(
background: var(--vp-c-bg-soft);
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 8px 12px;
padding: 4px 8px;
height: auto;
font-size: 14px;
text-align: left;
@@ -116,7 +152,7 @@ const size = useCssVar(
cursor: text;
display: flex;
align-items: center;
gap: 4px;
gap: 2px;
}
.color-input-text {
@@ -136,4 +172,21 @@ const size = useCssVar(
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.input-field:not(:last-child) {
margin-bottom: 16px;
}
.display-value {
font-size: 13px;
color: var(--vp-c-text-2);
}
.input-label {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
gap: 12px;
}
</style>
+9 -8
View File
@@ -2,7 +2,7 @@
import type { IconEntity } from '../types'
import { computed } from 'vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import CloseButton from './CloseButton.vue';
import { useIconSizeContext } from '../composables/useSharedSize';
const props = defineProps<{
name: IconEntity['name']
@@ -10,9 +10,9 @@ const props = defineProps<{
customizable?: boolean
}>()
const size = 24
const { size } = useIconSizeContext()
const gridLines = computed(() => Array.from({ length:(size - 1) }))
const gridLines = computed(() => Array.from({ length:(size.value - 1) }))
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null
@@ -20,10 +20,14 @@ const iconComponent = computed(() => {
})
</script>
<template>
<div class="icon-container">
<component :is="iconComponent" class="icon-component" :class="{ customizable }"/>
<component
:is="iconComponent"
class="icon-component"
:class="{ customizable }"
:size="size"
/>
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">
<line :key="`horizontal-${i}`" :x1="0" :y1="i + 1" :x2="size" :y2="i + 1" />
@@ -34,7 +38,6 @@ const iconComponent = computed(() => {
</template>
<style scoped>
.icon-grid {
width: 100%;
height: 100%;
@@ -63,7 +66,5 @@ const iconComponent = computed(() => {
will-change: width, height, stroke-width, stroke;
color: var(--customize-color, currentColor);
stroke-width: var(--customize-strokeWidth, 2);
width: calc(var(--customize-size, 24) * 1px);
height: calc(var(--customize-size, 24) * 1px);
}
</style>
+114
View File
@@ -0,0 +1,114 @@
<script lang="ts" setup>
import { computed } from "vue";
interface Props {
modelValue: number;
min?: number;
max?: number;
}
const props = withDefaults(defineProps<Props>(), {
min: 0,
max: 48,
})
defineEmits(['update:modelValue'])
const percentage = computed<string>(() => `${((props.modelValue - props.min) / (props.max - props.min)) * 100}%`);
</script>
<template>
<div class="slider">
<input
type="range"
v-bind="$attrs"
v-bind:value="modelValue"
v-on:input="$emit('update:modelValue', Number($event.target.value))"
:min="min"
:max="max"
/>
<div class="bar"></div>
</div>
</template>
<style>
.slider {
position: relative;
width: 100%;
line-height: 10px;
height: 20px;
}
.slider:hover input{
opacity: 1;
}
.slider .bar {
position: absolute;
top: 8px;
left: 0;
width: 100%;
height: 100%;
}
.slider .bar:before, .slider .bar:after {
content: "";
position: absolute;
height: 4px;
pointer-events: none;
}
.slider .bar:before {
width: v-bind(percentage);
z-index: 1;
left: 0;
background: var(--vp-c-brand);
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.slider .bar:after {
background: var(--vp-c-bg-soft);
width: calc(100% - v-bind(percentage));
right: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.slider input {
-webkit-appearance: none;
width: calc(100% + 20px);
height: 20px;
left: -10px;
position: relative;
z-index: 2;
background: transparent;
outline: none;
/* opacity: 0.7; */
-webkit-transition: 0.2s;
transition: opacity 0.2s;
margin: 0;
cursor: pointer;
/* @apply
md:opacity-0 */
}
.slider input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #FFFFFF;
border-radius: 10px;
box-shadow: var(--vp-shadow-2);
cursor: pointer;
}
.slider input::-moz-range-thumb {
width: 20px;
height: 20px;
appearance: none;
background: #FFFFFF;
border-radius: 10px;
outline: none;
border: none;
box-shadow: var(--vp-shadow-2);
cursor: pointer;
}
</style>
@@ -0,0 +1,31 @@
/* eslint-disable no-console */
import {
ref, provide, inject, Ref
} from 'vue';
export const SIZE_CONTEXT_NAME = Symbol('size');
interface IconSizeContext {
size: Ref<number>
}
export const context = {
size: ref(24),
};
export function useIconSizeProvider(): IconSizeContext {
provide(SIZE_CONTEXT_NAME, context);
return context;
}
export function useIconSizeContext(): IconSizeContext{
const context = inject<IconSizeContext>(SIZE_CONTEXT_NAME);
if (!context) {
throw new Error('useIconSizeContext must be used with useIconSizeProvider');
}
return context;
}
+4 -1
View File
@@ -4,6 +4,7 @@ import './style.css'
import { Theme } from 'vitepress'
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue'
import HomeHeroIconsCard from '../components/HomeHeroIconsCard.vue'
import { SIZE_CONTEXT_NAME, context } from '../composables/useSharedSize'
const theme: Partial<Theme> = {
extends: DefaultTheme,
@@ -13,7 +14,9 @@ const theme: Partial<Theme> = {
'home-hero-image': () => h(HomeHeroIconsCard)
})
},
enhanceApp({ app }) {
app.provide(SIZE_CONTEXT_NAME,context)
}
}
export default theme