mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2024-10-06 19:13:36 +00:00
feat(a11y): add no transparency setting detection (#2455)
Also refactored styles to use UnoCSS and fixed some type issues Signed-off-by: Fernando Fernández <ferferga@hotmail.com>
This commit is contained in:
parent
92c702c434
commit
3c592fd667
@ -4,7 +4,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: var(--j-background-color);
|
||||
background-color: var(--j-color-background);
|
||||
}
|
||||
|
||||
.j-splash > img {
|
||||
|
@ -162,7 +162,7 @@ const hasClick = computed(() => !!attrs.onClick);
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card-box:hover .card-overlay-hover {
|
||||
background: rgba(var(--v-theme-background), 0.5);
|
||||
background: rgba(var(--j-color-background), 0.5);
|
||||
}
|
||||
.card-box:hover .card-overlay-hover .card-overlay-hover-hidden {
|
||||
opacity: 1;
|
||||
|
@ -61,13 +61,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, type Ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { windowScroll, isConnectedToServer } from '@/store';
|
||||
import { windowScroll, isConnectedToServer, prefersNoTransparency } from '@/store';
|
||||
import { clientSettings } from '@/store/client-settings';
|
||||
import { remote } from '@/plugins/remote';
|
||||
|
||||
const route = useRoute();
|
||||
const { y } = windowScroll;
|
||||
const transparentAppBar = computed(() => route.meta.layout.transparent && y.value < 10);
|
||||
const transparentAppBar = computed(() => !prefersNoTransparency.value && route.meta.layout.transparent && y.value < 10);
|
||||
|
||||
/**
|
||||
* Cycle between the different color schemas
|
||||
|
@ -2,8 +2,8 @@
|
||||
<JTransition>
|
||||
<div
|
||||
v-if="blurhash"
|
||||
:key="`backdrop-${blurhash}`"
|
||||
class="backdrop sizing"
|
||||
:key="blurhash"
|
||||
class="uno-fixed uno-left-0 uno-top-0 uno-h-screen uno-w-screen uno-bg-cover uno-color-background"
|
||||
:style="{
|
||||
opacity,
|
||||
}">
|
||||
@ -11,19 +11,21 @@
|
||||
:hash="blurhash"
|
||||
:width="32"
|
||||
:height="32"
|
||||
class="sizing" />
|
||||
class="uno-fixed uno-left-0 uno-top-0 uno-h-screen uno-w-screen uno-bg-cover" />
|
||||
</div>
|
||||
</JTransition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRef, type MaybeRefOrGetter, shallowRef, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { toRef, type MaybeRefOrGetter, shallowRef, onMounted, onBeforeUnmount, computed } from 'vue';
|
||||
import { watchImmediate } from '@vueuse/core';
|
||||
import { isNil } from '@/utils/validation';
|
||||
import { prefersNoTransparency } from '@/store';
|
||||
|
||||
const DEFAULT_OPACITY = 0.25;
|
||||
const requested_opacity = shallowRef(DEFAULT_OPACITY);
|
||||
const _blurhash = shallowRef<string>();
|
||||
const _opacity = shallowRef(DEFAULT_OPACITY);
|
||||
const _opacity = computed(() => prefersNoTransparency.value ? 0 : requested_opacity.value);
|
||||
|
||||
/**
|
||||
* Reactively sets the backdrop properties. Can be used in 2 ways:
|
||||
@ -40,14 +42,12 @@ export function useBackdrop(hash?: MaybeRefOrGetter<string | undefined>, opacity
|
||||
}
|
||||
|
||||
if (!isNil(opacity)) {
|
||||
watchImmediate(toRef(opacity), val => _opacity.value = val ?? DEFAULT_OPACITY);
|
||||
watchImmediate(toRef(opacity), val => requested_opacity.value = val ?? DEFAULT_OPACITY);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => _blurhash.value = undefined);
|
||||
onBeforeUnmount(() => _opacity.value = DEFAULT_OPACITY);
|
||||
|
||||
return { backdrop: _blurhash, opacity: _opacity.value };
|
||||
onBeforeUnmount(() => requested_opacity.value = DEFAULT_OPACITY);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -55,18 +55,3 @@ export function useBackdrop(hash?: MaybeRefOrGetter<string | undefined>, opacity
|
||||
const blurhash = _blurhash;
|
||||
const opacity = _opacity;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.backdrop {
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
}
|
||||
|
||||
.sizing {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
|
@ -43,6 +43,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { RouteNamedMap } from 'vue-router/auto-routes';
|
||||
import type { getLibraryIcon } from '@/utils/items';
|
||||
import { prefersNoTransparency } from '@/store';
|
||||
|
||||
export interface DrawerItem {
|
||||
icon: ReturnType<typeof getLibraryIcon>;
|
||||
@ -59,7 +60,7 @@ const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const drawer = inject<Ref<boolean>>('NavigationDrawer');
|
||||
const transparentLayout = computed(() => route.meta.layout.transparent);
|
||||
const transparentLayout = computed(() => !prefersNoTransparency.value && route.meta.layout.transparent);
|
||||
|
||||
const items = [
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
<template v-if="isLoading">
|
||||
cursor: wait;
|
||||
</template>
|
||||
--j-background-color: rgb(var(--v-theme-background));
|
||||
--j-color-background: rgb(var(--v-theme-background));
|
||||
}
|
||||
</component>
|
||||
<!-- eslint-enable @intlify/vue-i18n/no-raw-text vue/require-component-is -->
|
||||
|
@ -3,7 +3,7 @@
|
||||
:is="group ? TransitionGroup : Transition"
|
||||
class="j-transition"
|
||||
v-bind="$attrs"
|
||||
:name="prefersNoMotion || disabled || isSlow ? undefined : `j-transition-${name}`"
|
||||
:name="forcedDisable || disabled ? undefined : `j-transition-${name}`"
|
||||
@before-leave="leaving = true"
|
||||
@after-leave="onNoLeave"
|
||||
@leave-cancelled="onNoLeave">
|
||||
@ -11,8 +11,8 @@
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Transition, TransitionGroup, type TransitionProps, shallowRef } from 'vue';
|
||||
<script lang="ts">
|
||||
import { Transition, TransitionGroup, type TransitionProps, shallowRef, computed } from 'vue';
|
||||
import { prefersNoMotion, isSlow } from '@/store';
|
||||
import { usePausableEffect } from '@/composables/use-pausable-effect';
|
||||
|
||||
@ -29,7 +29,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export type JTransitionProps = TransitionProps & Props;
|
||||
const forcedDisable = computed(() => prefersNoMotion.value || isSlow.value);
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { name = 'fade', group, disabled } = defineProps<Props>();
|
||||
const leaving = shallowRef(false);
|
||||
const onNoLeave = () => leaving.value = false;
|
||||
|
@ -6,7 +6,7 @@ import { getBlurhash } from '@/utils/images';
|
||||
/**
|
||||
* Same as useBackdrop, but is a shorthand for items only.
|
||||
*/
|
||||
export function useItemBackdrop(item: MaybeRefOrGetter<Nullish<BaseItemDto>>, ...args: ExcludeFromTuple<Parameters<typeof useBackdrop>, 0>) {
|
||||
export function useItemBackdrop(item: MaybeRefOrGetter<Nullish<BaseItemDto>>, ...args: Tail<Parameters<typeof useBackdrop>>) {
|
||||
useBackdrop(() => getBlurhash(toValue(item) ?? {}, ImageType.Primary), ...args);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
width="100%"
|
||||
height="100%">
|
||||
<div
|
||||
class="d-flex flex-column justify-space-between align-center player-overlay">
|
||||
class="d-flex flex-column align-center justify-space-between player-overlay">
|
||||
<div class="osd-top pt-s pl-s pr-s">
|
||||
<div class="d-flex align-center py-2 px-4">
|
||||
<div class="d-flex">
|
||||
@ -214,22 +214,22 @@ watch(staticOverlay, (val) => {
|
||||
padding-bottom: 5em;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(var(--v-theme-background), 0.75) 0%,
|
||||
rgb(var(--v-theme-background), 0.74) 8.1%,
|
||||
rgb(var(--v-theme-background), 0.714) 15.5%,
|
||||
rgb(var(--v-theme-background), 0.672) 22.5%,
|
||||
rgb(var(--v-theme-background), 0.618) 29%,
|
||||
rgb(var(--v-theme-background), 0.556) 35.3%,
|
||||
rgb(var(--v-theme-background), 0.486) 41.2%,
|
||||
rgb(var(--v-theme-background), 0.412) 47.1%,
|
||||
rgb(var(--v-theme-background), 0.338) 52.9%,
|
||||
rgb(var(--v-theme-background), 0.264) 58.8%,
|
||||
rgb(var(--v-theme-background), 0.194) 64.7%,
|
||||
rgb(var(--v-theme-background), 0.132) 71%,
|
||||
rgb(var(--v-theme-background), 0.078) 77.5%,
|
||||
rgb(var(--v-theme-background), 0.036) 84.5%,
|
||||
rgb(var(--v-theme-background), 0.01) 91.9%,
|
||||
rgb(var(--v-theme-background), 0) 100%
|
||||
rgb(var(--j-color-background), 0.75) 0%,
|
||||
rgb(var(--j-color-background), 0.74) 8.1%,
|
||||
rgb(var(--j-color-background), 0.714) 15.5%,
|
||||
rgb(var(--j-color-background), 0.672) 22.5%,
|
||||
rgb(var(--j-color-background), 0.618) 29%,
|
||||
rgb(var(--j-color-background), 0.556) 35.3%,
|
||||
rgb(var(--j-color-background), 0.486) 41.2%,
|
||||
rgb(var(--j-color-background), 0.412) 47.1%,
|
||||
rgb(var(--j-color-background), 0.338) 52.9%,
|
||||
rgb(var(--j-color-background), 0.264) 58.8%,
|
||||
rgb(var(--j-color-background), 0.194) 64.7%,
|
||||
rgb(var(--j-color-background), 0.132) 71%,
|
||||
rgb(var(--j-color-background), 0.078) 77.5%,
|
||||
rgb(var(--j-color-background), 0.036) 84.5%,
|
||||
rgb(var(--j-color-background), 0.01) 91.9%,
|
||||
rgb(var(--j-color-background), 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@ -237,22 +237,22 @@ watch(staticOverlay, (val) => {
|
||||
padding-top: 6em;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgb(var(--v-theme-background), 0.75) 0%,
|
||||
rgb(var(--v-theme-background), 0.74) 8.1%,
|
||||
rgb(var(--v-theme-background), 0.714) 15.5%,
|
||||
rgb(var(--v-theme-background), 0.672) 22.5%,
|
||||
rgb(var(--v-theme-background), 0.618) 29%,
|
||||
rgb(var(--v-theme-background), 0.556) 35.3%,
|
||||
rgb(var(--v-theme-background), 0.486) 41.2%,
|
||||
rgb(var(--v-theme-background), 0.412) 47.1%,
|
||||
rgb(var(--v-theme-background), 0.338) 52.9%,
|
||||
rgb(var(--v-theme-background), 0.264) 58.8%,
|
||||
rgb(var(--v-theme-background), 0.194) 64.7%,
|
||||
rgb(var(--v-theme-background), 0.132) 71%,
|
||||
rgb(var(--v-theme-background), 0.078) 77.5%,
|
||||
rgb(var(--v-theme-background), 0.036) 84.5%,
|
||||
rgb(var(--v-theme-background), 0.01) 91.9%,
|
||||
rgb(var(--v-theme-background), 0) 100%
|
||||
rgb(var(--j-color-background), 0.75) 0%,
|
||||
rgb(var(--j-color-background), 0.74) 8.1%,
|
||||
rgb(var(--j-color-background), 0.714) 15.5%,
|
||||
rgb(var(--j-color-background), 0.672) 22.5%,
|
||||
rgb(var(--j-color-background), 0.618) 29%,
|
||||
rgb(var(--j-color-background), 0.556) 35.3%,
|
||||
rgb(var(--j-color-background), 0.486) 41.2%,
|
||||
rgb(var(--j-color-background), 0.412) 47.1%,
|
||||
rgb(var(--j-color-background), 0.338) 52.9%,
|
||||
rgb(var(--j-color-background), 0.264) 58.8%,
|
||||
rgb(var(--j-color-background), 0.194) 64.7%,
|
||||
rgb(var(--j-color-background), 0.132) 71%,
|
||||
rgb(var(--j-color-background), 0.078) 77.5%,
|
||||
rgb(var(--j-color-background), 0.036) 84.5%,
|
||||
rgb(var(--j-color-background), 0.01) 91.9%,
|
||||
rgb(var(--j-color-background), 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -27,4 +27,4 @@ if ('darkMode' in parsedStore) {
|
||||
}
|
||||
}
|
||||
|
||||
document.body.style.setProperty('--j-background-color', colorToApply);
|
||||
document.body.style.setProperty('--j-color-background', colorToApply);
|
||||
|
@ -49,8 +49,12 @@ export const mediaWebAudio = {
|
||||
/**
|
||||
* Reactively tracks if the user wants animations (false) or not (true).
|
||||
*/
|
||||
export const prefersNoMotion = useMediaQuery('(prefers-reduced-motion)');
|
||||
export const prefersNoMotion = useMediaQuery('(prefers-reduced-motion:reduce)');
|
||||
|
||||
/**
|
||||
* Reactively tracks if the user wants transparency effects (true) or not (false).
|
||||
*/
|
||||
export const prefersNoTransparency = useMediaQuery('(prefers-reduced-transparency:reduce)');
|
||||
/**
|
||||
* IWhether the user is using a pointer with high precision (like a mouse)
|
||||
*/
|
||||
|
8
frontend/types/global/util.d.ts
vendored
8
frontend/types/global/util.d.ts
vendored
@ -16,13 +16,9 @@ type Mutable<T> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Omits an specific element from a tuple.
|
||||
* Gets the last item of a tuple
|
||||
*/
|
||||
type ExcludeFromTuple<T, K extends number, I extends unknown[] = []> = T extends [infer F, ...infer R]
|
||||
? I['length'] extends K
|
||||
? [...ExcludeFromTuple<R, K, [F, ...I]>]
|
||||
: [F, ...ExcludeFromTuple<R, K, [F, ...I]>]
|
||||
: [];
|
||||
type Tail<L> = L extends readonly [] ? L : L extends readonly [unknown?, ...infer LTail] ? LTail : L;
|
||||
|
||||
/**
|
||||
* Sets a type as nullish
|
||||
|
@ -6,5 +6,10 @@ export default defineConfig({
|
||||
prefix: 'uno-',
|
||||
preflight: false
|
||||
})
|
||||
]
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
background: 'rgb(var(--j-color-background))'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user