feat: image variants settings screens
This commit is contained in:
1 parent
f1d56ef50e
commit
f567866161
42 files changed
+7192
-11
No files matched your search
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
const MIN = 0
|
||||
const MAX = 100
|
||||
const STEP = 1
|
||||
const LARGE_STEP = 10
|
||||
const PATTERN =
|
||||
'bg-[repeating-linear-gradient(135deg,color-mix(in_srgb,var(--ui-text-muted)_15%,transparent)_0_1px,transparent_1px_12px)]'
|
||||
const VARIANT_PATTERN =
|
||||
'bg-[repeating-linear-gradient(45deg,color-mix(in_srgb,var(--ui-text-muted)_25%,transparent)_0_2px,transparent_2px_6px)]'
|
||||
const CHIP =
|
||||
'absolute top-2 flex h-5 items-center border border-accented bg-default px-1.5 text-xs font-medium text-highlighted'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
comparable: boolean
|
||||
}>()
|
||||
|
||||
const position = defineModel<number>({ default: 50 })
|
||||
|
||||
const shownName = computed(() => props.name || t('name'))
|
||||
|
||||
function clamp(value: number) {
|
||||
return Math.min(MAX, Math.max(MIN, value))
|
||||
}
|
||||
|
||||
function onInput(event: Event) {
|
||||
position.value = clamp(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
|
||||
function stepFor(event: KeyboardEvent) {
|
||||
return event.shiftKey ? LARGE_STEP : STEP
|
||||
}
|
||||
|
||||
const KEY_ACTIONS: Record<string, (event: KeyboardEvent) => number> = {
|
||||
ArrowRight: (event) => position.value + stepFor(event),
|
||||
ArrowUp: (event) => position.value + stepFor(event),
|
||||
ArrowLeft: (event) => position.value - stepFor(event),
|
||||
ArrowDown: (event) => position.value - stepFor(event),
|
||||
Home: () => MIN,
|
||||
End: () => MAX,
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
const action = KEY_ACTIONS[event.key]
|
||||
if (!action) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
position.value = clamp(action(event))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-testid="image-compare"
|
||||
class="relative aspect-3/2 shrink-0 overflow-hidden border border-default"
|
||||
>
|
||||
<div
|
||||
data-testid="compare-original"
|
||||
class="absolute inset-0 flex items-center justify-center bg-(--ui-color-secondary-200) text-muted dark:bg-(--ui-color-secondary-300)"
|
||||
:class="PATTERN"
|
||||
>
|
||||
<UIcon name="i-lucide-image" class="size-8 opacity-55" />
|
||||
</div>
|
||||
<template v-if="comparable">
|
||||
<div
|
||||
data-testid="compare-variant"
|
||||
class="absolute inset-0 bg-accented"
|
||||
:class="VARIANT_PATTERN"
|
||||
:style="{ clipPath: `inset(0 0 0 ${position}%)` }"
|
||||
/>
|
||||
<span :class="[CHIP, 'left-2']">{{ t('Original') }}</span>
|
||||
<span :class="[CHIP, 'right-2 font-mono']">{{ shownName }}</span>
|
||||
<input
|
||||
type="range"
|
||||
:min="MIN"
|
||||
:max="MAX"
|
||||
:step="STEP"
|
||||
:value="position"
|
||||
:aria-label="t('Compare original and {0}', [shownName])"
|
||||
:aria-valuenow="position"
|
||||
:aria-valuetext="t('{0}% {1}', [position, shownName])"
|
||||
class="peer absolute inset-0 z-10 size-full cursor-ew-resize opacity-0"
|
||||
@input="onInput"
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<div
|
||||
data-testid="compare-divider"
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-y-0 -ml-px w-0.5 bg-default outline outline-accented"
|
||||
:style="{ left: `${position}%` }"
|
||||
/>
|
||||
<span
|
||||
data-testid="compare-handle"
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute top-1/2 flex size-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center border border-accented bg-default text-highlighted peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-primary max-sm:size-11"
|
||||
:style="{ left: `${position}%` }"
|
||||
>
|
||||
<UIcon name="i-lucide-chevrons-left-right" class="size-4" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :class="[CHIP, 'left-2']">{{ t('Original') }}</span>
|
||||
<p
|
||||
class="absolute right-3 bottom-3 left-3 border border-default bg-default px-3 py-2 text-toned"
|
||||
>
|
||||
{{ t('Set a width or height to see a preview.') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user