Add icon customizer

This commit is contained in:
Eric Fennis
2023-05-01 22:45:55 +02:00
parent 7de9cab371
commit f88ab918a9
19 changed files with 582 additions and 305 deletions
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
modelValue: string
id: string
}>()
const emit = defineEmits(['update:modelValue'])
const value = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
</script>
<template>
<div class="color-picker">
<div class="color-input-wrapper">
<input
type="color"
:id="id"
:name="id"
class="color-input"
v-model="value"
/>
</div>
<!-- TODO: This should have an hidden label -->
<input
type="text"
:id="`${id}-input`"
:name="`${id}-input`"
class="color-input-text"
v-model="value"
/>
</div>
</template>
<style scoped>
.color-input {
width: 34px;
height: 34px;
position: absolute;
top: -5px;
left: -5px;
}
.color-input-wrapper {
height: 24px;
width: 24px;
overflow: hidden;
position: relative;
border-radius: 12px;
flex-shrink: 0;
}
.color-picker {
background: var(--color-picker-bg, var(--vp-c-bg-soft));
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 4px 8px;
height: auto;
font-size: 14px;
text-align: left;
border: 1px solid transparent;
cursor: text;
display: flex;
align-items: center;
gap: 2px;
}
.color-input-text {
width: 100%;
height: 100%;
padding: 0 8px;
border: none;
background: transparent;
color: var(--vp-c-text-2);
font-size: 14px;
text-align: left;
border-radius: 8px;
cursor: text;
}
.color-picker:hover, .color-picker:focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
</style>
+14 -3
View File
@@ -7,18 +7,29 @@
<style scoped>
.home-container {
position: relative;
padding: 24px;
padding-inline: 24px;
margin-block: 24px;
}
@media (min-width: 640px) {
.home-container {
padding: 48px;
padding-inline: 48px;
margin-block: 48px;
}
}
@media (min-width: 960px) {
.home-container {
padding: 64px;
padding-inline: 64px;
margin-block: 64px;
}
}
@media (min-width: 1280px) {
.home-container {
margin: 64px auto;
max-width: 1152px;
padding: 0;
}
}
</style>
@@ -32,12 +32,16 @@ function insert() {
items.value[replaceIndex] = newIcon
}
function startInterval() {
intervalTime.value = setInterval(() => {
insert()
}, 2000)
}
// TODO: Try maybe something else for better pref performance
// onMounted(() => {
// intervalTime.value = setInterval(() => {
// insert()
// }, 2000)
// })
onMounted(() => {
window.addEventListener('mousemove', startInterval, { once: true })
})
onBeforeUnmount(() => {
clearInterval(intervalTime.value)
@@ -76,7 +80,7 @@ onBeforeUnmount(() => {
/* padding: 0 24px; */
margin-left: auto;
margin-bottom: auto;
margin-top: 48px;;
margin-top: 48px;
}
.icons-card {
background: var(--vp-c-bg-alt);
@@ -0,0 +1,175 @@
<script setup lang="ts">
import { syncRef, useCssVar } from '@vueuse/core'
import HomeContainer from './HomeContainer.vue'
import { rotateCw } from '../../iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import RangeSlider from './RangeSlider.vue'
import IconButton from './IconButton.vue'
import InputField from './InputField.vue'
import ColorPicker from './ColorPicker.vue'
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue'
import { computed, ref } from 'vue'
const iconContainer = ref<HTMLElement | null>()
const color = ref('currentColor')
const strokeWidth = ref(2)
const size = ref(24)
const colorCssVar = useCssVar(
'--customize-color',
iconContainer,
{
initialValue: 'default'
}
)
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
iconContainer,
{
initialValue: '2'
}
)
const sizeCssVar = useCssVar(
'--customize-size',
iconContainer,
{
initialValue: '24'
}
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
size.value = 24
}
</script>
<template>
<HomeContainer>
<div class="card">
<div class="card-column">
<h2 class="title">
Style as you please
</h2>
<p class="copy">
Lucide has a lot of customization options to match the icons with you UI.
</p>
<div class="customizer">
<InputField
id="icon-color"
label="Color"
class="color-picker-field"
>
<template #display>
<ColorPicker v-model="color" id="icon-color" />
</template>
</InputField>
<InputField
id="stroke-width"
label="Stroke width"
>
<template #display>
<span class="customize-label">{{ strokeWidth }}px</span>
</template>
<RangeSlider
id="stroke-width"
name="stroke-width"
v-model="strokeWidth"
:min="1"
:max="4"
:steps="0.5"
/>
</InputField>
<InputField
id="size"
label="Size"
>
<template #display>
<span class="customize-label">{{ size }}px</span>
</template>
<RangeSlider
id="size"
name="size"
v-model="size"
:min="16"
:max="48"
/>
</InputField>
</div>
</div>
<div class="icons-container card-column" ref="iconContainer">
<HomeIconCustomizerIcons />
</div>
</div>
</HomeContainer>
</template>
<style scoped>
.card {
display: block;
border-radius: 12px;
height: 100%;
background-color: var(--vp-c-bg-soft);
padding: 24px;
--slider-bar-color: var(--vp-c-bg-soft-down);
--color-picker-bg: var(--vp-c-bg-soft-down);
}
.title {
line-height: 32px;
font-size: 24px;
font-weight: 600;
}
.copy {
padding-top: 8px;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.customizer {
margin-top: 32px;
padding: 0;
background: none;
max-width: 280px;
}
@media (min-width: 640px) {
.card {
display: grid;
grid-template-columns: 1fr 1fr;
}
/*
.card-column {
flex: 1;
} */
}
@media (min-width: 960px) {
.card {
grid-template-columns: 1fr 2fr;
}
}
.color-picker-field:deep(.display-value) {
width: 130px;
}
</style>
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { ref } from 'vue';
import { data } from './HomeHeroIconsCard.data'
import IconButton from './IconButton.vue';
import LucideIcon from './LucideIcon.vue'
const getInitialItems = () => data.icons.slice(0, 64)
const items = ref(getInitialItems())
</script>
<template>
<div class="icon-grid">
<div
v-for="icon in items"
class="icon-grid-item"
>
<LucideIcon v-bind="icon" class="lucide-icon"/>
</div>
</div>
</template>
<style scoped>
.icon-grid {
display: grid;
gap: 1px;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(56px, 1fr));
width: 100%;
height:100%;
max-height: 300px;
gap: 1px;
overflow: hidden;
position: relative;
border-radius: 12px;
border: 8px solid var(--vp-c-bg);
position: relative;
top: 48px;
right: 0;
box-shadow: var(--vp-shadow-4);
}
.icon-grid-item {
display: flex;
align-items: center;
justify-content: center;
/* width: 56px;
height: 56px; */
background: var(--vp-c-bg);
}
@media (min-width: 640px) {
.icon-grid {
top: 0;
right: -48px;
}
}
.lucide-icon {
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>
@@ -1,244 +0,0 @@
<script setup>
import { useCssVar, syncRef } from '@vueuse/core'
import { useIconStyleContext } from '../composables/useIconStyle'
import RangeSlider from './RangeSlider.vue'
import IconButton from './IconButton.vue'
import { rotateCw } from '../../iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
const { color, strokeWidth, size } = useIconStyleContext()
const colorCssVar = useCssVar(
'--customize-color',
typeof document !== 'undefined' ? document?.documentElement : undefined,
{
initialValue: 'default'
}
)
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
typeof document !== 'undefined' ? document?.documentElement : undefined,
{
initialValue: '2'
}
)
const sizeCssVar = useCssVar(
'--customize-size',
typeof document !== 'undefined' ? document?.documentElement : undefined,
{
initialValue: '24'
}
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = '2'
size.value = '24'
}
</script>
<template>
<div class="card">
<div class="card-header">
<h2 class="card-title">
Customizer
</h2>
<IconButton class="reset-button" @click="resetStyle">
<RotateIcon size="20"/>
</IconButton>
</div>
<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">
<input
type="color"
id="icon-color"
name="icon-color"
class="color-input"
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>
<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">{{ size }}px</span>
</div>
</div>
<RangeSlider
id="size"
name="size"
v-model="size"
:min="16"
:max="48"
/>
</div>
</div>
</template>
<style scoped>
.customize-label {
line-height: 20px;
font-size: 13px;
font-weight: 600;
color: var(--vt-c-text-1);
transition: color .5s;
display: block;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.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: 12px 24px 24px;
border-radius: 12px;
margin-bottom: 24px;
position: relative;
z-index: 0;
}
.color-input {
width: 34px;
height: 34px;
position: absolute;
top: -5px;
left: -5px;
}
.color-input-wrapper {
height: 24px;
width: 24px;
overflow: hidden;
position: relative;
border-radius: 12px;
flex-shrink: 0;
}
.color-picker {
background: var(--vp-c-bg-soft);
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 4px 8px;
height: auto;
font-size: 14px;
text-align: left;
border: 1px solid transparent;
cursor: text;
display: flex;
align-items: center;
gap: 2px;
}
.color-input-text {
width: 100%;
height: 100%;
padding: 0 8px;
border: none;
background: transparent;
color: var(--vp-c-text-2);
font-size: 14px;
text-align: left;
border-radius: 8px;
cursor: text;
}
.color-picker:hover, .color-picker:focus {
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;
}
.reset-button {
background: none;
padding: 0;
transition: ease-in-out 0.1s transform;
}
.reset-button:hover {
background: none;
border-color: transparent;
}
/* a rotate css animation keyframes */
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.reset-button:active {
transform: rotate(45deg);
}
</style>
+48
View File
@@ -0,0 +1,48 @@
<script setup lang="ts">
defineProps<{
label: string
id: string
}>()
</script>
<template>
<div class="input-field">
<div class="input-label">
<label :for="id" class="customize-label">
{{ label }}
</label>
<div class="display-value" >
<slot name="display"/>
</div>
</div>
<slot />
</div>
</template>
<style scoped>
.customize-label {
line-height: 20px;
font-size: 13px;
font-weight: 600;
color: var(--vt-c-text-1);
transition: color .5s;
display: block;
}
.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 -1
View File
@@ -36,10 +36,11 @@ const value = computed({
<template>
<Input
ref="input"
type="search"
v-bind="$attrs"
v-model="value"
class="input-wrapper" ref="searchInput"
class="input-wrapper"
>
<template #icon>
<component :is="SearchIcon" class="search-icon" />
+4 -1
View File
@@ -5,6 +5,7 @@ interface Props {
modelValue: number;
min?: number;
max?: number;
steps?: number;
}
const props = withDefaults(defineProps<Props>(), {
@@ -15,6 +16,7 @@ const props = withDefaults(defineProps<Props>(), {
defineEmits(['update:modelValue'])
const percentage = computed<string>(() => `${((props.modelValue - props.min) / (props.max - props.min)) * 100}%`);
// TODO: Steps must be implemented
</script>
<template>
@@ -37,6 +39,7 @@ const percentage = computed<string>(() => `${((props.modelValue - props.min) / (
width: 100%;
line-height: 10px;
height: 20px;
--bar-color: var(--slider-bar-color, var(--vp-c-bg-soft));
}
.slider:hover input{
@@ -64,7 +67,7 @@ const percentage = computed<string>(() => `${((props.modelValue - props.min) / (
border-bottom-left-radius: 4px;
}
.slider .bar:after {
background: var(--vp-c-bg-soft);
background: var(--bar-color);
width: calc(100% - v-bind(percentage));
right: 0;
border-top-right-radius: 4px;
@@ -0,0 +1,164 @@
<script setup lang="ts">
import { shallowRef, type Ref } from 'vue'
import { useCssVar, syncRef } from '@vueuse/core'
import { useIconStyleContext } from '../composables/useIconStyle'
import RangeSlider from './RangeSlider.vue'
import IconButton from './IconButton.vue'
import InputField from './InputField.vue'
import ColorPicker from './ColorPicker.vue'
import { rotateCw } from '../../iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
const props = defineProps<{
rootEl?: Ref<HTMLElement>
}>()
const { color, strokeWidth, size } = useIconStyleContext()
const documentRef = shallowRef<HTMLElement | undefined>(typeof document !== 'undefined' ? document?.documentElement : undefined)
const colorCssVar = useCssVar(
'--customize-color',
props.rootEl?.value ?? documentRef.value,
{
initialValue: 'default'
}
)
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
props.rootEl?.value ?? documentRef.value,
{
initialValue: '2'
}
)
const sizeCssVar = useCssVar(
'--customize-size',
props.rootEl?.value ?? documentRef.value,
{
initialValue: '24'
}
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
size.value = 24
}
</script>
<template>
<div class="customizer-card">
<div class="card-header">
<h2 class="card-title">
Customizer
</h2>
<IconButton class="reset-button" @click="resetStyle">
<RotateIcon :size="20"/>
</IconButton>
</div>
<InputField
id="icon-color"
label="Color"
>
<template #display>
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
</template>
</InputField>
<InputField
id="stroke-width"
label="Stroke width"
>
<template #display>
<span class="customize-label">{{ strokeWidth }}px</span>
</template>
<RangeSlider
id="stroke-width"
name="stroke-width"
v-model="strokeWidth"
:min="1"
:max="4"
:steps="0.5"
/>
</InputField>
<InputField
id="size"
label="Size"
>
<template #display>
<span class="customize-label">{{ size }}px</span>
</template>
<RangeSlider
id="size"
name="size"
v-model="size"
:min="16"
:max="48"
/>
</InputField>
</div>
</template>
<style scoped>
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.card-title {
font-weight: 700;
color: var(--vp-c-text-1);
line-height: 32px;
font-size: 16px;
/* margin-bottom: 12px; */
}
.customizer-card {
background: var(--vp-c-bg);
padding: 12px 24px 24px;
border-radius: 12px;
margin-bottom: 24px;
position: relative;
z-index: 0;
}
.color-picker {
margin-left: auto;
}
.reset-button {
background: none;
padding: 0;
transition: ease-in-out 0.1s transform;
}
.reset-button:hover {
background: none;
border-color: transparent;
}
/* a rotate css animation keyframes */
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.reset-button:active {
transform: rotate(45deg);
}
</style>
-5
View File
@@ -1,5 +0,0 @@
<template>
<div>
hellow world
</div>
</template>
+5 -21
View File
@@ -42,11 +42,7 @@ const getIconCodes = (name: string): CodeExampleType => {
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
<${pascalCase}/>
);
};
@@ -63,7 +59,7 @@ export default App;
{
language: 'vue',
code: `<template>
<${pascalCase} color="red" :size="32" />
<${pascalCase} />
</template>
<script setup>
@@ -84,11 +80,7 @@ import { ${pascalCase} } from 'lucide-vue-next';
import { ${pascalCase} } from 'lucide-svelte';
</script>
<${pascalCase}
color={red}
size={48}
strokeWidth={1.5}
/>
<${pascalCase}/>
`,
metastring: '{2,5}',
},
@@ -104,11 +96,7 @@ import { ${pascalCase} } from 'lucide-svelte';
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
<${pascalCase}/>
);
};
@@ -128,11 +116,7 @@ export default App;
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
<${pascalCase}/>
);
};
@@ -2,7 +2,7 @@
import { useData } from 'vitepress'
import CategoryList from '../../components/CategoryList.vue'
import IconCustomizer from '../../components/IconCustomizer.vue'
import SidebarIconCustomizer from '../../components/SidebarIconCustomizer.vue'
const { page } = useData()
@@ -10,7 +10,7 @@ const { page } = useData()
<template>
<div>
<IconCustomizer v-if="page?.relativePath?.startsWith?.('icons')"/>
<SidebarIconCustomizer v-if="page?.relativePath?.startsWith?.('icons')"/>
<CategoryList v-if="page?.relativePath?.startsWith?.('icons')"/>
</div>
</template>
+1
View File
@@ -16,6 +16,7 @@
.VPNavBarTitle .logo {
height: 36px;
width: 36px;
}
.VPNavBarTitle .title {
-10
View File
@@ -1,10 +0,0 @@
{
"cleanUrls" : true,
"redirects": [
{
"source": "/icon/:path*",
"destination":"/icons/:path*",
"permanent": true
}
]
}
+1 -1
View File
@@ -54,7 +54,7 @@ const codeExample = computed(() => params.value.codeExamples?.map(
max-width: 240px;
}
@media (min-width: 640px) {
(min-width: 640px) {
.layout {
align-items: flex-start;
display: grid;
+2
View File
@@ -50,6 +50,8 @@ features:
<script setup>
import HomePackagesSection from './.vitepress/components/HomePackagesSection.vue'
import HomeIconCustomizer from './.vitepress/components/HomeIconCustomizer.vue'
</script>
<HomePackagesSection />
<HomeIconCustomizer />
-10
View File
@@ -1,10 +0,0 @@
{
"cleanUrls" : true,
"routes": [
{
"source": "/icon/:path*",
"destination": "/icons/:path*",
"permanent": true
}
]
}
+1 -1
View File
@@ -6,7 +6,7 @@ const currentDir = process.cwd();
const vercelRouteConfig = {
version: 3,
overrides: {},
// cleanUrls: true,
cleanUrls: true,
routes: [
{
handle: 'filesystem',