fix(site): Small adjustments color picker and add clear button search bar (#3851)

* Small adjustments

* Format code

* format code

* Remove default value

* format code

* update yml file

* Format code

* Update docs/.vitepress/theme/components/base/Input.vue

Co-authored-by: Karsa <contact@karsa.org>

* Add extra check if null or undefined

---------

Co-authored-by: Karsa <contact@karsa.org>
This commit is contained in:
Eric Fennis
2025-12-12 09:25:39 +01:00
committed by GitHub
parent 124572c83b
commit 3b0d158ea1
13 changed files with 299 additions and 218 deletions

View File

@@ -1,5 +1,5 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react'; import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon'; import { type LucideProps, type IconNode } from 'lucide-react/src/types';
import { IconEntity } from '../theme/types'; import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from './generateZip'; import { IconContent } from './generateZip';

View File

@@ -1,23 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import { useData } from 'vitepress';
import { computed } from 'vue'; import { computed } from 'vue';
const props = defineProps<{ const props = defineProps<{
modelValue: string modelValue: string;
id: string id: string;
}>() }>();
const emit = defineEmits(['update:modelValue']) const { isDark } = useData();
const emit = defineEmits(['update:modelValue']);
const value = computed({ const value = computed({
get: () => props.modelValue, get: () => {
set: (val) => emit('update:modelValue', val) if (props.modelValue == null || props.modelValue === 'currentColor') {
}) return isDark.value ? '#ffffff' : '#000000';
}
return props.modelValue;
},
set: (val) => emit('update:modelValue', val),
});
</script> </script>
<template> <template>
<div class="color-picker"> <div class="color-picker">
<div class="color-input-wrapper"> <div class="color-input-wrapper">
<!-- TODO: Add currentColor div if value is currentColor -->
<input <input
type="color" type="color"
:id="id" :id="id"
@@ -33,6 +41,7 @@ const value = computed({
class="color-input-text" class="color-input-text"
aria-label="Color picker input" aria-label="Color picker input"
v-model="value" v-model="value"
placeholder="[default]"
/> />
</div> </div>
</template> </template>
@@ -45,19 +54,21 @@ const value = computed({
top: -5px; top: -5px;
left: -5px; left: -5px;
} }
.color-input-wrapper { .color-input-wrapper {
height: 24px; height: 24px;
width: 24px; width: 24px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border-radius: 12px; border-radius: 4px;
flex-shrink: 0; flex-shrink: 0;
} }
.color-picker { .color-picker {
background: var(--color-picker-bg, var(--vp-c-bg-alt)); background: var(--color-picker-bg, var(--vp-c-bg-alt));
border-radius: 8px; border-radius: 8px;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
padding: 4px 8px; padding: 3px 8px 3px 3px;
height: auto; height: auto;
font-size: 14px; font-size: 14px;
text-align: left; text-align: left;
@@ -66,6 +77,10 @@ const value = computed({
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 2px;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
} }
.color-input-text { .color-input-text {
@@ -79,15 +94,18 @@ const value = computed({
text-align: left; text-align: left;
border-radius: 8px; border-radius: 8px;
cursor: text; cursor: text;
transition: border-color 0.25s, background 0.4s ease; transition:
border-color 0.25s,
background 0.4s ease;
letter-spacing: 1px;
} }
.color-picker:hover, .color-picker:focus { .color-picker:hover,
.color-picker:focus {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt); background: var(--vp-c-bg-alt);
} }
.color-input[value="currentColor"] { .color-input[value='currentColor'] {
} }
</style> </style>

View File

@@ -1,22 +1,27 @@
<script setup> <script setup>
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import Icon from 'lucide-vue-next/src/Icon';
import { search } from '../../../data/iconNodes' import { search } from '../../../data/iconNodes';
const SearchIcon = createLucideIcon('search', search)
defineProps({ defineProps({
shortcut: { shortcut: {
type: String, type: String,
required: false required: false,
} },
}) });
</script> </script>
<template> <template>
<button class="fake-input"> <button class="fake-input">
<component :is="SearchIcon" class="search-icon"/> <Icon
<slot/> :iconNode="search"
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd> class="search-icon"
/>
<slot />
<kbd
v-if="shortcut"
class="shortcut"
>{{ shortcut }}</kbd
>
</button> </button>
</template> </template>
@@ -34,10 +39,14 @@ defineProps({
cursor: text; cursor: text;
display: flex; display: flex;
gap: 12px; gap: 12px;
transition: color 0.25s, border-color 0.25s, background-color 0.25s; transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
} }
.fake-input:hover, .fake-input:focus { .fake-input:hover,
.fake-input:focus {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt); background: var(--vp-c-bg-alt);
} }

View File

@@ -5,7 +5,6 @@
</template> </template>
<style scoped> <style scoped>
.icon-button { .icon-button {
display: inline-flex; display: inline-flex;
border: 1px solid transparent; border: 1px solid transparent;

View File

@@ -1,60 +1,90 @@
<script lang="ts"> <script lang="ts">
export default { export default {
inheritAttrs: false, inheritAttrs: false,
} };
export interface InputProps { export interface InputProps {
type: string type: string;
modelValue: string modelValue: string;
shortcut?: string shortcut?: string;
} }
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue' import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from 'lucide-vue-next/src/Icon';
import { x } from '../../../data/iconNodes';
import IconButton from './IconButton.vue';
const props = withDefaults(defineProps<InputProps>(), { const props = withDefaults(defineProps<InputProps>(), {
type: 'text' type: 'text',
}) });
const input = ref() const input = ref();
const wrapperEl = ref() const wrapperEl = ref();
const shortcutEl = ref() const shortcutEl = ref();
defineEmits(['change', 'input', 'update:modelValue']) const emit = defineEmits(['change', 'input', 'update:modelValue']);
const updateShortcutSpacing = () => { const updateShortcutSpacing = () => {
nextTick(() => { nextTick(() => {
if (shortcutEl.value && wrapperEl.value) { if (shortcutEl.value && wrapperEl.value) {
const shortcutWidth = shortcutEl.value.offsetWidth const shortcutWidth = shortcutEl.value.offsetWidth;
wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`) wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`);
} }
}) });
} };
onMounted(updateShortcutSpacing) onMounted(updateShortcutSpacing);
watch(() => props.shortcut, updateShortcutSpacing) watch(() => props.shortcut, updateShortcutSpacing);
function onClear() {
emit('update:modelValue', '');
input.value.focus();
}
defineExpose({ defineExpose({
focus: () => { focus: () => {
input.value.focus() input.value.focus();
} },
}) });
</script> </script>
<template> <template>
<div class="input-wrapper" ref="wrapperEl"> <div
<slot name="icon" class="icon" /> class="input-wrapper"
ref="wrapperEl"
>
<slot
name="icon"
class="icon"
/>
<input <input
:type="type" :type="type"
class="input" class="input"
:class="{'has-icon': $slots.icon, 'has-shortcut': shortcut}" :class="{ 'has-icon': $slots.icon, 'has-shortcut': shortcut }"
ref="input" ref="input"
:value="modelValue" :value="modelValue"
v-bind="$attrs" v-bind="$attrs"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
/> />
<kbd v-if="shortcut" class="shortcut" ref="shortcutEl">{{ shortcut }}</kbd> <IconButton
@click="onClear"
v-if="type === 'search' && modelValue"
class="clear-button"
aria-label="Clear input"
>
<Icon
:iconNode="x"
:size="20"
/>
</IconButton>
<kbd
v-if="shortcut"
class="shortcut"
ref="shortcutEl"
>{{ shortcut }}</kbd
>
</div> </div>
</template> </template>
@@ -62,6 +92,7 @@ defineExpose({
.input-wrapper { .input-wrapper {
position: relative; position: relative;
} }
.input { .input {
justify-content: flex-start; justify-content: flex-start;
border: 1px solid transparent; border: 1px solid transparent;
@@ -71,13 +102,18 @@ defineExpose({
height: 40px; height: 40px;
background-color: var(--vp-c-bg-alt); background-color: var(--vp-c-bg-alt);
font-size: 14px; font-size: 14px;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
} }
.input.has-shortcut { .input.has-shortcut {
padding-right: calc(var(--shortcut-width, 40px) + 22px); padding-right: calc(var(--shortcut-width, 40px) + 22px);
} }
.input:hover, .input:focus { .input:hover,
.input:focus {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt); background: var(--vp-c-bg-alt);
} }
@@ -86,6 +122,14 @@ defineExpose({
padding-left: 52px; padding-left: 52px;
} }
.clear-button {
position: absolute;
right: 56px;
top: 9px;
padding: 4px;
transition: background-color .25s;
}
.shortcut { .shortcut {
position: absolute; position: absolute;
right: 12px; right: 12px;
@@ -111,7 +155,7 @@ defineExpose({
</style> </style>
<style> <style>
.input-wrapper svg { .input-wrapper > svg {
position: absolute; position: absolute;
left: 16px; left: 16px;
top: 12px; top: 12px;

View File

@@ -1,38 +1,36 @@
<script lang="ts"> <script lang="ts">
export default { export default {
inheritAttrs: false, inheritAttrs: false,
} };
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue';
import Input from './Input.vue' import Input from './Input.vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import Icon from 'lucide-vue-next/src/Icon';
import { search } from '../../../data/iconNodes' import { search } from '../../../data/iconNodes';
const SearchIcon = createLucideIcon('search', search)
interface Props { interface Props {
modelValue: string modelValue: string;
shortcut?: string shortcut?: string;
} }
const props = defineProps<Props>() const props = defineProps<Props>();
const input = ref() const input = ref();
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue']);
defineExpose({ defineExpose({
focus: () => { focus: () => {
input.value.focus() input.value.focus();
} },
}) });
const value = computed({ const value = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => emit('update:modelValue', val) set: (val) => emit('update:modelValue', val),
}) });
</script> </script>
<template> <template>
@@ -46,7 +44,10 @@ const value = computed({
class="input-wrapper" class="input-wrapper"
> >
<template #icon> <template #icon>
<component :is="SearchIcon" class="search-icon" /> <Icon
:iconNode="search"
class="search-icon"
/>
</template> </template>
</Input> </Input>
</template> </template>
@@ -62,7 +63,8 @@ const value = computed({
background-color: var(--vp-c-bg-alt); background-color: var(--vp-c-bg-alt);
} }
.input:hover, .input:focus { .input:hover,
.input:focus {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt); background: var(--vp-c-bg-alt);
} }

View File

@@ -1,14 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes' import { rotateCw } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import Icon from 'lucide-vue-next/src/Icon';
import IconButton from "./IconButton.vue"; import IconButton from './IconButton.vue';
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
</script> </script>
<template> <template>
<IconButton class="reset-button"> <IconButton class="reset-button">
<RotateIcon :size="20"/> <Icon
:size="20"
:iconNode="rotateCw"
/>
</IconButton> </IconButton>
</template> </template>
@@ -32,6 +33,7 @@ const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
} }
100% { 100% {
transform: rotate(359deg); transform: rotate(359deg);
} }

View File

@@ -22,16 +22,16 @@ export default {
logo: '/framework-logos/svelte.svg', logo: '/framework-logos/svelte.svg',
label: 'Lucide documentation for Svelte', label: 'Lucide documentation for Svelte',
}, },
{
name: 'lucide-preact',
logo: '/framework-logos/preact.svg',
label: 'Lucide documentation for Preact',
},
{ {
name: 'lucide-solid', name: 'lucide-solid',
logo: '/framework-logos/solid.svg', logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid', label: 'Lucide documentation for Solid',
}, },
{
name: 'lucide-preact',
logo: '/framework-logos/preact.svg',
label: 'Lucide documentation for Preact',
},
{ {
name: 'lucide-angular', name: 'lucide-angular',
logo: '/framework-logos/angular.svg', logo: '/framework-logos/angular.svg',
@@ -48,11 +48,6 @@ export default {
logo: '/framework-logos/react-native.svg', logo: '/framework-logos/react-native.svg',
label: 'Lucide documentation for React Native', label: 'Lucide documentation for React Native',
}, },
{
name: 'lucide-flutter',
logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter',
},
], ],
}; };
}, },

View File

@@ -2,45 +2,48 @@
import { useData } from 'vitepress'; import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core'; import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue'; import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue' import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
import { x } from '../../../data/iconNodes' import { x } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'; import Icon from 'lucide-vue-next/src/Icon';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
const { theme } = useData() const { theme } = useData();
const showAd = useSessionStorage('show-carbon-ads', true) const showAd = useSessionStorage('show-carbon-ads', true);
const carbonLoaded = ref(true) const carbonLoaded = ref(true);
defineProps<{ defineProps<{
drawerOpen: boolean drawerOpen: boolean;
}>() }>();
const CloseIcon = createLucideIcon('Close', x)
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (window?._carbonads == null) { if (window?._carbonads == null) {
carbonLoaded.value = false carbonLoaded.value = false;
} }
}, 5000) }, 5000);
}) });
</script> </script>
<template> <template>
<div <div
:class="{ :class="{
'drawer-open': drawerOpen, 'drawer-open': drawerOpen,
'hide-ad': !(showAd && carbonLoaded) 'hide-ad': !(showAd && carbonLoaded),
}" }"
class="floating-ad" class="floating-ad"
v-if="theme.carbonAds" v-if="theme.carbonAds"
> >
<IconButton @click="showAd = false" class="hide-button"> <IconButton
<component :is="CloseIcon" :size="20" absoluteStrokeWidth /> @click="showAd = false"
</IconButton> class="hide-button"
<VPDocAsideCarbonAds >
:carbon-ads="theme.carbonAds" <Icon
:iconNode="x"
:size="20"
absoluteStrokeWidth
/> />
</IconButton>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
</div> </div>
</template> </template>
@@ -51,7 +54,9 @@ onMounted(() => {
bottom: 32px; bottom: 32px;
width: 224px; width: 224px;
right: 32px; right: 32px;
transition: opacity 0.5s, transform 0.25s ease; transition:
opacity 0.5s,
transform 0.25s ease;
} }
.floating-ad.drawer-open { .floating-ad.drawer-open {
@@ -67,8 +72,11 @@ onMounted(() => {
transform: translateY(-288px) translateX(224px); transform: translateY(-288px) translateX(224px);
} }
.floating-ad.drawer-open, .floating-ad.hide-ad { .floating-ad.drawer-open,
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1); .floating-ad.hide-ad {
transition:
opacity 0.25s,
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {

View File

@@ -1,70 +1,68 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import ButtonMenu from '../base/ButtonMenu.vue' import ButtonMenu from '../base/ButtonMenu.vue';
import { useIconStyleContext } from '../../composables/useIconStyle'; import { useIconStyleContext } from '../../composables/useIconStyle';
import useConfetti from '../../composables/useConfetti'; import useConfetti from '../../composables/useConfetti';
import getSVGIcon from '../../utils/getSVGIcon'; import getSVGIcon from '../../utils/getSVGIcon';
import downloadData from '../../utils/downloadData'; import downloadData from '../../utils/downloadData';
const downloadText = 'Download!' const downloadText = 'Download!';
const copiedText = 'Copied!' const copiedText = 'Copied!';
const confettiText = ref(copiedText) const confettiText = ref(copiedText);
const props = defineProps<{ const props = defineProps<{
name: string name: string;
popoverPosition?: 'top' | 'bottom' popoverPosition?: 'top' | 'bottom';
}>() }>();
const { size } = useIconStyleContext() const { size } = useIconStyleContext();
const { animate, confetti } = useConfetti() const { animate, confetti } = useConfetti();
function copySVG() { function copySVG() {
confettiText.value = copiedText confettiText.value = copiedText;
const svgString = getSVGIcon() const svgString = getSVGIcon();
navigator.clipboard.writeText(svgString) navigator.clipboard.writeText(svgString);
confetti() confetti();
} }
function copyDataUrl() { function copyDataUrl() {
confettiText.value = copiedText confettiText.value = copiedText;
const svgString = getSVGIcon() const svgString = getSVGIcon();
// Create SVG data url // Create SVG data url
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}` const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`;
navigator.clipboard.writeText(dataUrl) navigator.clipboard.writeText(dataUrl);
confetti() confetti();
} }
function downloadSVG() { function downloadSVG() {
confettiText.value = downloadText confettiText.value = downloadText;
const svgString = getSVGIcon() const svgString = getSVGIcon();
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`) downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`);
confetti() confetti();
} }
function downloadPNG() { function downloadPNG() {
confettiText.value = downloadText confettiText.value = downloadText;
const svgString = getSVGIcon() const svgString = getSVGIcon();
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = size.value; canvas.width = size.value;
canvas.height = size.value; canvas.height = size.value;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
const image = new Image(); const image = new Image();
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`; image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
image.onload = function() { image.onload = function () {
ctx.drawImage(image, 0, 0); ctx.drawImage(image, 0, 0);
downloadData(`${props.name}.png`, canvas.toDataURL('image/png')) downloadData(`${props.name}.png`, canvas.toDataURL('image/png'));
confetti() confetti();
} };
} }
</script> </script>
<template> <template>
@@ -75,10 +73,10 @@ function downloadPNG() {
:data-confetti-text="confettiText" :data-confetti-text="confettiText"
:popoverPosition="popoverPosition" :popoverPosition="popoverPosition"
:options="[ :options="[
{ text: 'Copy SVG' , onClick: copySVG }, { text: 'Copy SVG', onClick: copySVG },
{ text: 'Copy Data URL' , onClick: copyDataUrl }, { text: 'Copy Data URL', onClick: copyDataUrl },
{ text: 'Download SVG' , onClick: downloadSVG }, { text: 'Download SVG', onClick: downloadSVG },
{ text: 'Download PNG' , onClick: downloadPNG }, { text: 'Download PNG', onClick: downloadPNG },
]" ]"
/> />
</template> </template>

View File

@@ -1,41 +1,44 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, useSlots } from 'vue'; import { computed, useSlots } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon' import { copy } from '../../../data/iconNodes';
import { copy } from '../../../data/iconNodes'
import useConfetti from '../../composables/useConfetti'; import useConfetti from '../../composables/useConfetti';
const { animate, confetti } = useConfetti() import Icon from 'lucide-vue-next/src/Icon';
const slots = useSlots() const { animate, confetti } = useConfetti();
const slots = useSlots();
const copiedText = computed(() => slots.default?.()[0].children) const copiedText = computed(() => slots.default?.()[0].children);
function copyText() { function copyText() {
navigator.clipboard.writeText(copiedText.value) navigator.clipboard.writeText(copiedText.value);
confetti() confetti();
} }
const Copy = createLucideIcon('ChevronUp', copy)
</script> </script>
<template> <template>
<h1 <h1
class="icon-name confetti-button" class="icon-name confetti-button"
:class="{animate}" :class="{ animate }"
data-confetti-text="Copied!" data-confetti-text="Copied!"
@click="copyText" @click="copyText"
> >
<slot /> <slot />
<Copy :size="20" class="copy-icon"/> <Icon
:iconNode="copy"
:size="20"
class="copy-icon"
/>
</h1> </h1>
</template> </template>
<style scoped> <style scoped>
@import './confetti.css'; @import './confetti.css';
.icon-name { .icon-name {
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 500;
line-height: 32px; line-height: 32px;
transition: background ease-in .15s;; transition: background ease-in 0.15s;
padding: 2px 8px; padding: 2px 8px;
border-radius: 8px; border-radius: 8px;
width: auto; width: auto;
@@ -48,7 +51,7 @@ const Copy = createLucideIcon('ChevronUp', copy)
} }
.icon-name:hover .copy-icon { .icon-name:hover .copy-icon {
opacity: .9; opacity: 0.9;
} }
.icon-name:before, .icon-name:before,
@@ -65,10 +68,10 @@ const Copy = createLucideIcon('ChevronUp', copy)
opacity: 0; opacity: 0;
margin-left: 12px; margin-left: 12px;
margin-top: 6px; margin-top: 6px;
transition:ease .3s opacity; transition: ease 0.3s opacity;
} }
.icon-name:hover .copy-icon:hover { .icon-name:hover .copy-icon:hover {
opacity: .6; opacity: 0.6;
} }
</style> </style>

View File

@@ -1,75 +1,72 @@
<script setup lang="ts"> <script setup lang="ts">
import { shallowRef, type Ref, watch, computed } from 'vue' import { shallowRef, type Ref, watch, computed } from 'vue';
import { useCssVar, syncRef } from '@vueuse/core' import { useCssVar, syncRef } from '@vueuse/core';
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle' import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle';
import RangeSlider from '../base/RangeSlider.vue' import RangeSlider from '../base/RangeSlider.vue';
import InputField from '../base/InputField.vue' import InputField from '../base/InputField.vue';
import ColorPicker from '../base/ColorPicker.vue' import ColorPicker from '../base/ColorPicker.vue';
import ResetButton from '../base/ResetButton.vue' import ResetButton from '../base/ResetButton.vue';
import Switch from '../base/Switch.vue' import Switch from '../base/Switch.vue';
const props = defineProps<{ const props = defineProps<{
rootEl?: Ref<HTMLElement> rootEl?: Ref<HTMLElement>;
}>() }>();
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext() const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext();
const documentRef = shallowRef<HTMLElement | undefined>(typeof document !== 'undefined' ? document?.documentElement : undefined) const documentRef = shallowRef<HTMLElement | undefined>(
typeof document !== 'undefined' ? document?.documentElement : undefined,
);
const colorCssVar = useCssVar( const colorCssVar = useCssVar('--customize-color', props.rootEl?.value ?? documentRef.value, {
'--customize-color', initialValue: `${STYLE_DEFAULTS.color}`,
props.rootEl?.value ?? documentRef.value, });
{
initialValue: `${STYLE_DEFAULTS.color}`
}
)
const strokeWidthCssVar = useCssVar( const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth', '--customize-strokeWidth',
props.rootEl?.value ?? documentRef.value, props.rootEl?.value ?? documentRef.value,
{ {
initialValue: `${STYLE_DEFAULTS.strokeWidth}` initialValue: `${STYLE_DEFAULTS.strokeWidth}`,
} },
) );
const sizeCssVar = useCssVar( const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
'--customize-size', initialValue: `${STYLE_DEFAULTS.size}`,
props.rootEl?.value ?? documentRef.value, });
{
initialValue: `${STYLE_DEFAULTS.size}`
}
)
syncRef(color, colorCssVar, { direction: 'ltr' }) syncRef(color, colorCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' }) syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
syncRef(size, sizeCssVar, { direction: 'ltr' }) syncRef(size, sizeCssVar, { direction: 'ltr' });
function resetStyle () { function resetStyle() {
color.value = STYLE_DEFAULTS.color color.value = STYLE_DEFAULTS.color;
strokeWidth.value = STYLE_DEFAULTS.strokeWidth strokeWidth.value = STYLE_DEFAULTS.strokeWidth;
size.value = STYLE_DEFAULTS.size size.value = STYLE_DEFAULTS.size;
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
} }
watch(absoluteStrokeWidth, (enabled) => { watch(absoluteStrokeWidth, (enabled) => {
const htmlEl = document.documentElement const htmlEl = document.documentElement;
htmlEl.classList.toggle('absolute-stroke-width', enabled) htmlEl.classList.toggle('absolute-stroke-width', enabled);
}) });
const customizingActive = computed(() => { const customizingActive = computed(() => {
return color.value !== STYLE_DEFAULTS.color return (
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth color.value !== STYLE_DEFAULTS.color ||
|| size.value !== STYLE_DEFAULTS.size strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth size.value !== STYLE_DEFAULTS.size ||
}) absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
);
});
</script> </script>
<template> <template>
<div class="customizer-card" :class="{ customized: customizingActive }"> <div
class="customizer-card"
:class="{ customized: customizingActive }"
>
<div class="card-header"> <div class="card-header">
<h2 class="card-title"> <h2 class="card-title">Customizer</h2>
Customizer
</h2>
<ResetButton @click="resetStyle"></ResetButton> <ResetButton @click="resetStyle"></ResetButton>
</div> </div>
<InputField <InputField
@@ -77,7 +74,11 @@ const customizingActive = computed(() => {
label="Color" label="Color"
> >
<template #display> <template #display>
<ColorPicker v-model="color" id="icon-color" class="color-picker"/> <ColorPicker
v-model="color"
id="icon-color"
class="color-picker"
/>
</template> </template>
</InputField> </InputField>
@@ -117,7 +118,7 @@ const customizingActive = computed(() => {
<InputField <InputField
id="absolute-stroke-width" id="absolute-stroke-width"
label="Absolute Stroke width" label="Absolute stroke width"
> >
<Switch <Switch
id="absolute-stroke-width" id="absolute-stroke-width"
@@ -143,6 +144,7 @@ const customizingActive = computed(() => {
font-size: 16px; font-size: 16px;
/* margin-bottom: 12px; */ /* margin-bottom: 12px; */
} }
.customizer-card { .customizer-card {
background: var(--vp-c-bg); background: var(--vp-c-bg);
padding: 12px 24px 24px; padding: 12px 24px 24px;
@@ -151,7 +153,7 @@ const customizingActive = computed(() => {
position: relative; position: relative;
z-index: 0; z-index: 0;
border: 1px solid transparent; border: 1px solid transparent;
transition: border-color .4s ease-in-out; transition: border-color 0.4s ease-in-out;
} }
.customizer-card.customized { .customizer-card.customized {

View File

@@ -1,4 +1,4 @@
import { IconNode } from 'lucide-vue-next/src/createLucideIcon'; import { type IconNode } from 'lucide-vue-next/src/types';
import Vue from 'vue'; import Vue from 'vue';
declare module '*.vue' { declare module '*.vue' {
@@ -20,5 +20,6 @@ declare module 'node:module' {
} }
declare module '*.node.json' { declare module '*.node.json' {
export default IconNode; const value: IconNode;
export default value;
} }