mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-14 13:58:21 +00:00
Add customizer
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user