fix(site): Fix reset button and rename absolute stroke width to non-scaling stroke (#4956)

* Fix reset button and rename absolute stroke width

* Adjust props

* Adjust icon preview

* Rename switch ID for non-scaling stroke

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Eric Fennis
2026-10-06 09:48:14 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent 851bc6deed
commit ccb4b193d2
13 changed files with 69 additions and 61 deletions
@@ -73,7 +73,7 @@ function hidePopup() {
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
nonScalingStroke
/>
</Button>
@@ -6,6 +6,10 @@
modelValue: {
type: Boolean,
default: false
},
id: {
type: String,
default: ''
}
})
@@ -23,6 +27,7 @@
@update:model-value="emit('update:modelValue', $event)"
class="switch"
:class="{ enabled: isHydrated && modelValue }"
:id="id"
>
<span class="thumb" />
</Switch>
@@ -9,7 +9,7 @@ import ResetButton from '../base/ResetButton.vue';
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue';
import Switch from '../base/Switch.vue';
const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle } = useIconStyle();
const { color, strokeWidth, size, nonScalingStroke, isCustomized, resetStyle } = useIconStyle();
const iconStyle = computed(() => ({
'--home-icon-color': color.value,
@@ -85,14 +85,14 @@ const iconStyle = computed(() => ({
</InputField>
<InputField
id="absolute-stroke-width"
label="Absolute Stroke width"
id="non-scaling-stroke"
label="Non-scaling stroke"
>
<template #display>
<Switch
id="absolute-stroke-width-switch"
name="absolute-stroke-width"
v-model="absoluteStrokeWidth"
id="non-scaling-stroke-switch"
name="non-scaling-stroke"
v-model="nonScalingStroke"
/>
</template>
</InputField>
@@ -101,7 +101,7 @@ const iconStyle = computed(() => ({
<div class="icons-container card-column">
<HomeIconCustomizerIcons
:class="{ 'absolute-stroke-width': absoluteStrokeWidth }"
:class="{ 'non-scaling-stroke': nonScalingStroke }"
:style="iconStyle"
/>
</div>
@@ -79,7 +79,7 @@ const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting
max-height: 3rem;
}
.icon-grid.absolute-stroke-width .lucide-icon {
.icon-grid.non-scaling-stroke .lucide-icon {
stroke-width: calc(var(--home-icon-stroke-width, 2) * 24 / var(--home-icon-size, 24));
}
</style>
@@ -48,7 +48,7 @@ onMounted(() => {
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
nonScalingStroke
/>
</Button>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
@@ -9,7 +9,7 @@ const props = defineProps<{
name: string;
popoverPosition?: 'top' | 'bottom';
}>();
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const { size, color, strokeWidth, nonScalingStroke } = useIconStyleContext();
const { animate, confetti } = useConfetti();
const componentName = computed(() => {
return (toPascalCase(props.name) as string).replace(/\s/g, '');
@@ -30,8 +30,8 @@ function copyJSX() {
attrs.push(`strokeWidth={${strokeWidth.value}}`);
}
if (absoluteStrokeWidth.value) {
attrs.push(`absoluteStrokeWidth`);
if (nonScalingStroke.value) {
attrs.push(`nonScalingStroke`);
}
const code = `<${componentName.value}${attrs.join(' ')} />`;
@@ -60,8 +60,8 @@ function copyVue() {
attrs.push(`:stroke-width="${strokeWidth.value}"`);
}
if (absoluteStrokeWidth.value) {
attrs.push(`absoluteStrokeWidth`);
if (nonScalingStroke.value) {
attrs.push(`nonScalingStroke`);
}
const code = `<${componentName.value}${attrs.join(' ')} />`;
@@ -86,8 +86,8 @@ function copyAngular() {
attrs.push(`[strokeWidth]="${strokeWidth.value}"`);
}
if (absoluteStrokeWidth.value) {
attrs.push(`[absoluteStrokeWidth]="true"`);
if (nonScalingStroke.value) {
attrs.push(`[nonScalingStroke]="true"`);
}
const code = `<lucide-icon${attrs.join(' ')}></lucide-icon>`;
@@ -204,7 +204,7 @@ const DiamondIcon = createLucideIcon('Diamond', diamond);
max-height: 3rem;
}
html.absolute-stroke-width .lucide-icon.customizable {
html.non-scaling-stroke .lucide-icon.customizable {
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
}
</style>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { IconEntity } from '../../types';
import { computed, ref } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import Icon from '@lucide/vue/src/Icon';
import { useIconStyleContext } from '../../composables/useIconStyle';
const props = defineProps<{
@@ -12,25 +12,23 @@ const props = defineProps<{
const activeSize = ref('1000%');
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const { size, color, strokeWidth, nonScalingStroke } = useIconStyleContext();
const previewIcon = ref();
const gridLines = computed(() => Array.from({ length: size.value - 1 }));
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
</script>
<template>
<div class="icon-container">
<component
<Icon
v-if="name && iconNode"
:name="name"
:iconNode="iconNode"
ref="previewIcon"
:is="iconComponent"
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
:strokeWidth="strokeWidth"
:nonScalingStroke="nonScalingStroke"
class="preview-icon"
:data-size="activeSize"
/>
@@ -71,7 +71,7 @@ const Icon = createLucideIcon(props.name, props.iconNode);
height: calc(var(--customize-size, 24) * 1px); */
}
html.absolute-stroke-width .lucide-icon {
html.non-scaling-stroke .lucide-icon {
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
}
</style>
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useMounted } from '@vueuse/core';
import { useIconStyleContext } from '../../composables/useIconStyle';
import RangeSlider from '../base/RangeSlider.vue';
import InputField from '../base/InputField.vue';
@@ -6,19 +8,24 @@ import ColorPicker from '../base/ColorPicker.vue';
import ResetButton from '../base/ResetButton.vue';
import Switch from '../base/Switch.vue';
const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle } =
const { color, strokeWidth, size, nonScalingStroke, isCustomized, resetStyle } =
useIconStyleContext();
// The server always renders the default style. Keep the first client render identical,
// otherwise the hydrated DOM keeps the stale `disabled` attribute on the reset button.
const isMounted = useMounted();
const showCustomized = computed(() => isMounted.value && isCustomized.value);
</script>
<template>
<div
class="customizer-card"
:class="{ customized: isCustomized }"
:class="{ customized: showCustomized }"
>
<div class="card-header">
<h2 class="card-title">Customizer</h2>
<ResetButton
:disabled="!isCustomized"
:disabled="!showCustomized"
@click="resetStyle"
/>
</div>
@@ -68,13 +75,13 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
</InputField>
<InputField
id="absolute-stroke-width"
label="Absolute stroke width"
id="non-scaling-stroke"
label="Non-scaling stroke"
>
<Switch
id="absolute-stroke-width-switch"
name="absolute-stroke-width"
v-model="absoluteStrokeWidth"
id="non-scaling-stroke-switch"
name="non-scaling-stroke"
v-model="nonScalingStroke"
/>
</InputField>
</div>
@@ -6,7 +6,7 @@ const storedValues = new Map<string, string>([
['icon-size', '240'],
['icon-stroke-width', '1.5'],
['icon-color', '#ff0000'],
['icon-absolute-stroke-width', 'true'],
['icon-non-scaling-stroke', 'true'],
]);
const styleProperties = new Map<string, string>();
const classes = new Set<string>();
@@ -70,7 +70,7 @@ test('creates a non-persisted icon style from the defaults', () => {
assert.equal(style.size.value, 24);
assert.equal(style.strokeWidth.value, 2);
assert.equal(style.color.value, 'currentColor');
assert.equal(style.absoluteStrokeWidth.value, false);
assert.equal(style.nonScalingStroke.value, false);
style.size.value = 48;
@@ -84,12 +84,12 @@ test('restores persisted settings and applies the shared CSS variables', () => {
assert.equal(style.size.value, 240);
assert.equal(style.strokeWidth.value, 1.5);
assert.equal(style.color.value, '#ff0000');
assert.equal(style.absoluteStrokeWidth.value, true);
assert.equal(style.nonScalingStroke.value, true);
assert.equal(style.isCustomized.value, true);
assert.equal(styleProperties.get('--customize-size'), '240');
assert.equal(styleProperties.get('--customize-strokeWidth'), '1.5');
assert.equal(styleProperties.get('--customize-color'), '#ff0000');
assert.ok(classes.has('absolute-stroke-width'));
assert.ok(classes.has('non-scaling-stroke'));
});
test('resetting updates refs, CSS variables, and persisted values', async () => {
@@ -100,14 +100,14 @@ test('resetting updates refs, CSS variables, and persisted values', async () =>
assert.equal(style.size.value, 24);
assert.equal(style.strokeWidth.value, 2);
assert.equal(style.color.value, 'currentColor');
assert.equal(style.absoluteStrokeWidth.value, false);
assert.equal(style.nonScalingStroke.value, false);
assert.equal(style.isCustomized.value, false);
assert.equal(styleProperties.get('--customize-size'), '24');
assert.equal(styleProperties.get('--customize-strokeWidth'), '2');
assert.equal(styleProperties.get('--customize-color'), 'currentColor');
assert.equal(classes.has('absolute-stroke-width'), false);
assert.equal(classes.has('non-scaling-stroke'), false);
assert.equal(storedValues.get('icon-size'), '24');
assert.equal(storedValues.get('icon-stroke-width'), '2');
assert.equal(storedValues.get('icon-color'), 'currentColor');
assert.equal(storedValues.get('icon-absolute-stroke-width'), 'false');
assert.equal(storedValues.get('icon-non-scaling-stroke'), 'false');
});
@@ -7,18 +7,18 @@ interface IconStyleContext {
size: Ref<number>;
strokeWidth: Ref<number>;
color: Ref<string>;
absoluteStrokeWidth: Ref<boolean>;
nonScalingStroke: Ref<boolean>;
isCustomized: ComputedRef<boolean>;
resetStyle: () => void;
}
type IconStyle = Pick<IconStyleContext, 'size' | 'strokeWidth' | 'color' | 'absoluteStrokeWidth'>;
type IconStyle = Pick<IconStyleContext, 'size' | 'strokeWidth' | 'color' | 'nonScalingStroke'>;
export const STYLE_DEFAULTS = {
size: 24,
strokeWidth: 2,
color: 'currentColor',
absoluteStrokeWidth: false,
nonScalingStroke: false,
};
const persistedIconStyle = {
@@ -27,11 +27,9 @@ const persistedIconStyle = {
initOnMounted: false,
}),
color: useLocalStorage('icon-color', STYLE_DEFAULTS.color, { initOnMounted: false }),
absoluteStrokeWidth: useLocalStorage(
'icon-absolute-stroke-width',
STYLE_DEFAULTS.absoluteStrokeWidth,
{ initOnMounted: false },
),
nonScalingStroke: useLocalStorage('icon-non-scaling-stroke', STYLE_DEFAULTS.nonScalingStroke, {
initOnMounted: false,
}),
};
function createIconStyleContext(style: IconStyle): IconStyleContext {
@@ -40,7 +38,7 @@ function createIconStyleContext(style: IconStyle): IconStyleContext {
style.color.value !== STYLE_DEFAULTS.color ||
style.strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
style.size.value !== STYLE_DEFAULTS.size ||
style.absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
style.nonScalingStroke.value !== STYLE_DEFAULTS.nonScalingStroke
);
});
@@ -48,7 +46,7 @@ function createIconStyleContext(style: IconStyle): IconStyleContext {
style.color.value = STYLE_DEFAULTS.color;
style.strokeWidth.value = STYLE_DEFAULTS.strokeWidth;
style.size.value = STYLE_DEFAULTS.size;
style.absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
style.nonScalingStroke.value = STYLE_DEFAULTS.nonScalingStroke;
}
return {
@@ -65,7 +63,7 @@ export function useIconStyle() {
size: ref(STYLE_DEFAULTS.size),
strokeWidth: ref(STYLE_DEFAULTS.strokeWidth),
color: ref(STYLE_DEFAULTS.color),
absoluteStrokeWidth: ref(STYLE_DEFAULTS.absoluteStrokeWidth),
nonScalingStroke: ref(STYLE_DEFAULTS.nonScalingStroke),
});
}
@@ -79,9 +77,9 @@ if (typeof document !== 'undefined') {
persistedIconStyle.size,
persistedIconStyle.strokeWidth,
persistedIconStyle.color,
persistedIconStyle.absoluteStrokeWidth,
persistedIconStyle.nonScalingStroke,
],
([size, strokeWidth, color, absoluteStrokeWidth]) => {
([size, strokeWidth, color, nonScalingStroke]) => {
const root = document.documentElement;
root.style.setProperty('--customize-size', String(Math.min(256, Math.max(16, size))));
root.style.setProperty(
@@ -89,7 +87,7 @@ if (typeof document !== 'undefined') {
String(Math.min(3, Math.max(0.5, strokeWidth))),
);
if (CSS.supports('color', color)) root.style.setProperty('--customize-color', color);
root.classList.toggle('absolute-stroke-width', absoluteStrokeWidth);
root.classList.toggle('non-scaling-stroke', nonScalingStroke);
},
{ immediate: true, flush: 'sync' },
);
+3 -3
View File
@@ -5,8 +5,8 @@
const size = globalThis.localStorage.getItem('icon-size');
const stroke = globalThis.localStorage.getItem('icon-stroke-width');
const color = globalThis.localStorage.getItem('icon-color');
const absolute = globalThis.localStorage.getItem('icon-absolute-stroke-width');
if ([size, stroke, color, absolute].every((value) => value === null)) return;
const nonScaling = globalThis.localStorage.getItem('icon-non-scaling-stroke');
if ([size, stroke, color, nonScaling].every((value) => value === null)) return;
if (size !== null && Number.isFinite(Number(size))) {
root.style.setProperty('--customize-size', String(Math.min(256, Math.max(16, Number(size)))));
}
@@ -17,7 +17,7 @@
);
if (color !== null && globalThis.CSS.supports('color', color))
root.style.setProperty('--customize-color', color);
root.classList.toggle('absolute-stroke-width', absolute === 'true');
root.classList.toggle('non-scaling-stroke', nonScaling === 'true');
} catch {
// Storage can be unavailable, for example in private browsing mode.
}