refactor: various dark mode fixes

scarlett
NGPixel 3 days ago
parent 064e378b48
commit cffa39c1e0
No known key found for this signature in database

@ -1,6 +1,11 @@
<template>
<w-card class="icon-picker" flat style="width: 460px">
<w-tabs class="text-primary" v-model="state.currentTab" no-caps inline-label>
<!-- -> `primary` is a mid-tone for white; on the dark card the tabs need the lightened mix -->
<w-tabs
class="text-primary dark:text-primary-light"
v-model="state.currentTab"
no-caps
inline-label>
<w-tab name="icon" icon="la:icons" :label="t(`iconPicker.icons`)" />
<w-tab name="image" icon="la:image" :label="t(`iconPicker.image`)" />
</w-tabs>
@ -13,11 +18,11 @@
<div class="flex flex-wrap gap-2">
<div class="min-w-0 flex-1">
<w-input
ref="iptSearch"
v-model="state.query"
outlined
dense
clearable
autofocus
:label="t(`iconPicker.search`)"
:aria-label="t(`iconPicker.search`)"
@update:model-value="queueSearch">
@ -75,6 +80,7 @@
but it is not something anyone should have to know to type.
-->
<w-input
ref="iptImage"
class="mt-2"
v-model="state.image"
outlined
@ -118,7 +124,7 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { computed, onMounted, reactive } from 'vue'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { notify } from '@/composables/notify'
import { useDark } from '@/composables/dark'
@ -164,6 +170,11 @@ const ICONIFY_REF = /^[a-z0-9-]+:[a-z0-9.-]+$/
/** What marks a reference as an image rather than an icon, per WIcon. */
const IMAGE_PREFIX = 'img:'
// REFS
const iptSearch = ref(null)
const iptImage = ref(null)
// COMPOSABLES
const dark = useDark()
@ -286,6 +297,16 @@ onMounted(async () => {
state.selected = props.modelValue
state.results = [props.modelValue]
}
/*
Focus whichever field the picker just opened on -- the search box, or the image path when an
`img:` value brought us to that tab. Two ticks: the first renders the tab switch above, and the
field being focused only exists after the panel it lives in is the visible one.
*/
await nextTick()
await nextTick()
;(state.currentTab === 'image' ? iptImage : iptSearch).value?.focus()
await loadSets()
})
</script>

@ -6,11 +6,13 @@
class="rounded"
v-if="editorStore.isActive"
padding="none"
size="37px"
size="64px"
color="primary"
flat
:aria-label="t(`editor.props.icon`)">
<w-icon :name="pageStore.icon" size="37px" />
:aria-label="t(`editor.props.icon`)"
style="min-height: 64px">
<!-- -> 64px, the size the icon has when the page is merely being read; see the branch below -->
<w-icon :name="pageStore.icon" size="64px" />
<w-badge color="grey" floating rounded>
<w-icon name="la:pen" size="xs" padding="xs xs" />
</w-badge>
@ -21,14 +23,14 @@
<!-- PAGE HEADER -->
<div class="min-w-0 flex-1 p-4">
<div class="text-h4 page-header-title">
<span>{{pageStore.title}}</span>
<span>{{ pageStore.title }}</span>
<template v-if="editorStore.isActive">
<span class="text-grey" v-if="!pageStore.title">{{ t(`editor.props.title`)}}</span>
<span class="text-grey" v-if="!pageStore.title">{{ t(`editor.props.title`) }}</span>
<w-btn class="acrylic-btn ml-4" icon="la:pen" flat padding="xs" size="sm">
<w-popup-edit v-model="pageStore.title" auto-save v-slot="scope">
<w-input
outlined
style="width: 450px;"
style="width: 450px"
v-model="scope.value"
dense
autofocus
@ -41,12 +43,14 @@
<div class="text-subtitle2 page-header-subtitle">
<span>{{ pageStore.description }}</span>
<template v-if="editorStore.isActive">
<span class="text-grey" v-if="!pageStore.description">{{ t(`editor.props.shortDescription`)}}</span>
<span class="text-grey" v-if="!pageStore.description">{{
t(`editor.props.shortDescription`)
}}</span>
<w-btn class="acrylic-btn ml-4" icon="la:pen" flat padding="none xs" size="xs">
<w-popup-edit v-model="pageStore.description" auto-save v-slot="scope">
<w-input
outlined
style="width: 450px;"
style="width: 450px"
v-model="scope.value"
dense
autofocus
@ -132,8 +136,12 @@
flat
icon="la:times"
color="negative"
:label="editorStore.hasPendingChanges ? t(`common.actions.discard`) : t(`common.actions.close`)"
:aria-label="editorStore.hasPendingChanges ? t(`common.actions.discard`) : t(`common.actions.close`)"
:label="
editorStore.hasPendingChanges ? t(`common.actions.discard`) : t(`common.actions.close`)
"
:aria-label="
editorStore.hasPendingChanges ? t(`common.actions.discard`) : t(`common.actions.close`)
"
no-caps
@click="discardChanges" />
<w-btn
@ -206,7 +214,6 @@ import { useUserStore } from '@/stores/user'
import IconPickerDialog from '@/components/IconPickerDialog.vue'
import SocialSharingMenu from '@/components/SocialSharingMenu.vue'
// STORES
const editorStore = useEditorStore()

@ -317,7 +317,10 @@ const floatColorClass = computed(() => {
if (hasError.value) {
return 'text-negative'
}
return hasFocus.value ? 'text-primary' : 'text-black/60 dark:text-white/70'
// -> `primary` is picked to read on white; on a dark field it needs the lightened mix
return hasFocus.value
? 'text-primary dark:text-primary-light'
: 'text-black/60 dark:text-white/70'
})
const controlClasses = computed(() => [

@ -672,7 +672,8 @@ const floatColorClass = computed(() => {
if (errorMessage.value) {
return 'text-negative'
}
return isOpen.value ? 'text-primary' : 'text-black/60 dark:text-white/70'
// -> Lightened brand blue on a dark field, matching WInput
return isOpen.value ? 'text-primary dark:text-primary-light' : 'text-black/60 dark:text-white/70'
})
const controlClasses = computed(() => [

@ -84,6 +84,13 @@
--color-header: var(--q-header);
--color-sidebar: var(--q-sidebar);
/*
The brand blue lightened for text on a dark surface: `primary` itself is a mid-tone picked to
read on white, and at #1976d2 on `dark-3` it lands around 2.7:1 -- too dim for a heading. Mixing
towards white rather than naming a palette blue keeps a re-themed site's own hue.
*/
--color-primary-light: color-mix(in srgb, var(--color-primary) 60%, white);
/*
Restated here rather than inherited from Tailwind's default theme: the defaults live in a
prunable block, and components resolve `var(--color-white)` at runtime where the scanner cannot
@ -705,6 +712,7 @@
body.body--dark .w-section-header {
--w-section-header-surface: var(--color-dark-3);
color: var(--color-primary-light);
}
.w-unstyled {

@ -249,6 +249,16 @@ watch(
iconify-icon {
color: $primary;
}
// -> Same lightened brand blue as the section headings; `$primary` is too dim on this surface
@at-root .body--dark & {
color: var(--color-primary-light);
.w-icon,
iconify-icon {
color: var(--color-primary-light);
}
}
}
}
}
@ -264,54 +274,6 @@ watch(
}
}
.section-header {
font-weight: 500;
font-size: 17px;
padding: 0 16px 6px 16px;
color: $primary;
position: relative;
background:
linear-gradient(to left, #fff, transparent),
linear-gradient(to top, rgba($primary, 0.075), transparent);
margin-bottom: 10px;
@at-root .body--dark & {
background:
linear-gradient(to left, $dark-3, transparent),
linear-gradient(to top, rgba($primary, 0.075), transparent);
}
&:before {
content: '';
width: 100%;
height: 10px;
background:
linear-gradient(to left, #fff, transparent),
linear-gradient(to bottom, rgba($primary, 0.05), transparent);
position: absolute;
bottom: -13px;
left: 0;
z-index: 0;
@at-root .body--dark & {
background:
linear-gradient(to left, $dark-3, transparent),
linear-gradient(to bottom, rgba($primary, 0.05), transparent);
}
}
&:after {
content: '';
width: 100%;
height: 1px;
background: linear-gradient(to left, transparent, rgba($primary, 0.25));
position: absolute;
bottom: -2px;
left: 0;
z-index: 0;
}
}
.actions-bar {
display: flex;
padding: 16px;

@ -25,7 +25,7 @@
<w-card-actions align="right">
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:plus-circle"
:label="t(`common.actions.new`)"
:disable="!userStore.can(`manage:sites`)"
@ -33,7 +33,7 @@
<w-separator vertical />
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:sitemap"
:label="t(`common.actions.manage`)"
:disable="!userStore.can(`manage:sites`)"
@ -54,7 +54,7 @@
<w-card-actions align="right">
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:plus-circle"
:label="t(`common.actions.new`)"
:disable="!userStore.can(`manage:users`)"
@ -62,7 +62,7 @@
<w-separator vertical />
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:users"
:label="t(`common.actions.manage`)"
:disable="!userStore.can(`manage:users`)"
@ -83,7 +83,7 @@
<w-card-actions align="right">
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:user-plus"
:label="t(`common.actions.new`)"
:disable="!userStore.can(`manage:users`)"
@ -91,7 +91,7 @@
<w-separator vertical />
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:user-friends"
:label="t(`common.actions.manage`)"
:disable="!userStore.can(`manage:users`)"
@ -129,7 +129,7 @@
<w-card-actions align="right">
<w-btn
flat
color="primary"
:color="actionColor"
icon="la:chart-area"
:label="t(`admin.analytics.title`)"
:disable="!flagsStore.experimental"
@ -166,9 +166,11 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
import { useMeta } from '@/composables/meta'
import { dialog } from '@/composables/dialog'
import { useDark } from '@/composables/dark'
import { useFlagsStore } from '@/stores/flags'
import { useUserStore } from '@/stores/user'
@ -185,6 +187,17 @@ const adminStore = useAdminStore()
const flagsStore = useFlagsStore()
const userStore = useUserStore()
// COMPOSABLES
const dark = useDark()
/*
WBtn emits its colour as an inline style, so no `dark:` class can reach it -- the theme has to be
read here. `primary` is a mid-tone picked to read on white; on the dark card it needs the lightened
mix, the same one the section headings use.
*/
const actionColor = computed(() => (dark.isActive ? 'primary-light' : 'primary'))
// ROUTER
const router = useRouter()

@ -2,7 +2,10 @@
<w-page class="flex flex-col">
<div class="page-breadcrumbs py-2 px-4 flex flex-wrap" v-if="!editorStore.isActive">
<div class="min-w-0 flex-1">
<w-breadcrumbs :items="breadcrumbs" active-color="grey-7" separator-color="grey">
<w-breadcrumbs
:items="breadcrumbs"
:active-color="dark.isActive ? `grey-5` : `grey-7`"
separator-color="grey">
<template #separator><w-icon name="la:angle-right" /></template>
</w-breadcrumbs>
</div>
@ -11,14 +14,18 @@
<div class="text-caption text-accent"><strong>Unpublished</strong></div>
<w-separator class="mx-2" vertical />
</template>
<div class="text-caption text-grey-6">Last modified on <strong>{{lastModified}}</strong></div>
<div class="text-caption text-grey-6">
Last modified on <strong>{{ lastModified }}</strong>
</div>
</div>
</div>
<page-header />
<div class="page-container flex flex-nowrap items-stretch" style="flex: 1 1 100%;">
<div class="min-w-0 flex-1" :style="siteStore.theme.tocPosition === `left` ? `order: 2;` : `order: 1;`">
<div class="page-container flex flex-nowrap items-stretch" style="flex: 1 1 100%">
<div
class="min-w-0 flex-1"
:style="siteStore.theme.tocPosition === `left` ? `order: 2;` : `order: 1;`">
<component :is="editorComponents[editorStore.editor]" v-if="editorStore.isActive" />
<w-scroll-area class="page-container-scrl" v-else style="height: 100%;">
<w-scroll-area class="page-container-scrl" v-else style="height: 100%">
<div class="p-4">
<div class="page-contents" ref="pageContents" v-html="pageStore.render" />
<template v-if="pageStore.relations && pageStore.relations.length > 0">
@ -35,8 +42,10 @@
:key="`rel-id-` + rel.id">
<w-icon :name="rel.icon" />
<div class="flex flex-col text-left pl-4">
<div class="text-body2"><strong>{{rel.label}}</strong></div>
<div class="text-caption">{{rel.caption}}</div>
<div class="text-body2">
<strong>{{ rel.label }}</strong>
</div>
<div class="text-caption">{{ rel.caption }}</div>
</div>
</w-btn>
</div>
@ -63,8 +72,10 @@
v-for="rel of relationsRight"
:key="`rel-id-` + rel.id">
<div class="flex flex-col text-left pr-4">
<div class="text-body2"><strong>{{rel.label}}</strong></div>
<div class="text-caption">{{rel.caption}}</div>
<div class="text-body2">
<strong>{{ rel.label }}</strong>
</div>
<div class="text-caption">{{ rel.caption }}</div>
</div>
<w-icon :name="rel.icon" />
</w-btn>
@ -154,6 +165,7 @@ import { computed, defineAsyncComponent, nextTick, onMounted, reactive, ref, wat
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useDark } from '@/composables/dark'
import { useMeta } from '@/composables/meta'
import { notify } from '@/composables/notify'
import { loading } from '@/composables/loading'
@ -200,6 +212,10 @@ const route = useRoute()
const { t } = useI18n()
// COMPOSABLES
const dark = useDark()
// META
useMeta({
@ -406,9 +422,18 @@ function refreshTocExpanded(baseToc, lvl) {
background: linear-gradient(to bottom, $grey-1 0%, $grey-3 100%);
border-bottom: 1px solid $grey-4;
}
/*
The bar sets a background per theme, so it owes a foreground too: the LAST crumb -- the current
page -- deliberately inherits rather than taking `active-color`, and what it was inheriting in
dark mode was the document's black.
*/
@at-root .body--light & {
color: var(--color-black);
}
@at-root .body--dark & {
background: linear-gradient(to bottom, $dark-3 0%, $dark-4 100%);
border-bottom: 1px solid $dark-3;
color: var(--color-white);
}
}
.page-header {

@ -1,6 +1,6 @@
<template>
<w-page class="py-4">
<div class="section-header">{{ t('profile.auth') }}</div>
<div class="w-section-header">{{ t('profile.auth') }}</div>
<div class="p-4">
<div class="text-body2">{{ t('profile.authInfo') }}</div>
<w-list class="mt-6" bordered separator>
@ -44,7 +44,7 @@
</w-list>
</div>
<div class="section-header mt-4">{{ t('profile.passkeys') }}</div>
<div class="w-section-header mt-4">{{ t('profile.passkeys') }}</div>
<div class="p-4">
<div class="text-body2">{{ t('profile.passkeysIntro') }}</div>
<w-list v-if="state.passkeys?.length > 0" class="mt-6" bordered separator>

@ -1,6 +1,6 @@
<template>
<w-page class="py-4">
<div class="section-header">{{ t('profile.avatar') }}</div>
<div class="w-section-header">{{ t('profile.avatar') }}</div>
<div class="mt-10 flex flex-wrap gap-6">
<div class="flex-1 text-center">
<w-avatar

@ -1,6 +1,6 @@
<template>
<w-page class="py-4">
<div class="section-header">{{ t('profile.groups') }}</div>
<div class="w-section-header">{{ t('profile.groups') }}</div>
<div class="p-4">
<div class="text-body2">{{ t('profile.groupsInfo') }}</div>
<w-list class="mt-6" bordered separator>

@ -1,6 +1,6 @@
<template>
<w-page class="py-4">
<div class="section-header">{{ t('profile.myInfo') }}</div>
<div class="w-section-header">{{ t('profile.myInfo') }}</div>
<w-item v-if="!canEdit">
<w-item-section>
<w-card class="bg-negative rounded text-white" flat>
@ -99,7 +99,7 @@
:readonly="!canEdit" />
</w-item-section>
</w-item>
<div class="section-header mt-6">{{ t('profile.preferences') }}</div>
<div class="w-section-header mt-6">{{ t('profile.preferences') }}</div>
<w-item>
<blueprint-icon icon="timezone" />
<w-item-section>
@ -181,7 +181,7 @@
:aria-label="t(`profile.appearance`)" />
</w-item-section>
</w-item>
<div class="section-header mt-6">{{ t('profile.accessibility') }}</div>
<div class="w-section-header mt-6">{{ t('profile.accessibility') }}</div>
<w-item>
<blueprint-icon icon="visualy-impaired" />
<w-item-section>

@ -550,6 +550,8 @@ onUnmounted(() => {
border-bottom: 1px solid $grey-3;
}
@at-root .body--dark & {
// -> Same lightened brand blue as `.w-section-header`; `$primary` on this surface is ~2.7:1
color: var(--color-primary-light);
background-color: $dark-3;
border-bottom: 1px solid $dark-2;
}

Loading…
Cancel
Save