Commit 08e3a6c
Changed files (7)
src
components
widgets
SideToolBar
i18n
src/components/widgets/SideToolBar/TocButton.vue
@@ -1,4 +1,6 @@
<script setup lang="ts">
+import I18nKey from '@i18n/I18nKey';
+import { i18n } from '@i18n/translation';
import { onMounted, onUnmounted, ref } from 'vue';
const isOpen = ref(false);
@@ -63,12 +65,14 @@ onUnmounted(() => {
ref="buttonRef"
class="btn btn-circle btn-secondary btn-sm"
@click="isOpen = !isOpen"
+ :title="i18n(I18nKey.toc)"
>
<slot name="icon" />
</button>
<div
ref="tocWrapper"
class="rounded-box absolute w-[calc(100vw-4rem)] -translate-x-1/2 -translate-y-1/2 max-w-72 backdrop-blur-md duration-300 text-base-content text-start"
+ :inert="!isOpen || isWideScreen"
:class="{
'-translate-x-[calc(100%+0.5rem)]! -translate-y-[calc(100%-2.5rem)]!':
isOpen && !isWideScreen,
src/components/widgets/DarkModeButton.astro
@@ -13,20 +13,34 @@ const { class: className, showText, ...rest } = Astro.props;
---
<Button
- class:list={['darkmode-btn swap', className]}
+ class:list={['darkmode-btn swap swap-rotate', className]}
{...rest}
data-text-light={i18n(I18nKey.lightMode)}
data-text-dark={i18n(I18nKey.darkMode)}
data-text-auto={i18n(I18nKey.systemMode)}
+ role="switch"
+ aria-label={i18n(I18nKey.themeToggle)}
>
- <input type="checkbox" />
- <Icon class="darkmode-icon-light swap-off" name="material-symbols:light-mode-rounded" />
- <Icon class="darkmode-icon-dark swap-on" name="material-symbols:dark-mode-rounded" />
+ <input type="checkbox" inert />
+ <Icon
+ class="darkmode-icon-light swap-off"
+ name="material-symbols:light-mode-rounded"
+ role="presentation"
+ aria-hidden
+ />
+ <Icon
+ class="darkmode-icon-dark swap-on"
+ name="material-symbols:dark-mode-rounded"
+ role="presentation"
+ aria-hidden
+ />
<Icon
class="darkmode-icon-auto swap-indeterminate"
name="material-symbols:night-sight-auto-rounded"
+ role="presentation"
+ aria-hidden
/>
- {showText && <span class="darkmode-text pl-6" />}
+ <span class={showText ? 'darkmode-text pl-6' : 'sr-only'}></span>
</Button>
<script>
@@ -57,7 +71,7 @@ const { class: className, showText, ...rest } = Astro.props;
darkmodeBtns.forEach((btn) => {
const checkbox = btn.querySelector('input[type="checkbox"]') as HTMLInputElement;
- const text = btn.querySelector('.darkmode-text');
+ const text = btn.querySelector('.darkmode-text') || btn.querySelector('.sr-only');
// 更新UI状态和文本
function updateUI(mode: 'system' | 'light' | 'dark') {
@@ -65,19 +79,20 @@ const { class: className, showText, ...rest } = Astro.props;
checkbox.indeterminate = true;
} else {
checkbox.indeterminate = false;
- checkbox.checked = mode === 'dark';
}
+ checkbox.checked = mode === 'dark';
- if (text) {
- const textContent =
- mode === 'system'
- ? btn.getAttribute('data-text-auto')
- : mode === 'dark'
- ? btn.getAttribute('data-text-dark')
- : btn.getAttribute('data-text-light');
+ const textContent =
+ mode === 'system'
+ ? btn.getAttribute('data-text-auto')
+ : mode === 'dark'
+ ? btn.getAttribute('data-text-dark')
+ : btn.getAttribute('data-text-light');
+
+ btn.setAttribute('title', textContent || '');
+ if (text) {
text.textContent = textContent;
- btn.setAttribute('title', textContent || '');
}
}
src/components/SideToolBar.astro
@@ -1,5 +1,7 @@
---
import { articleConfig, toolBarConfig } from '@/config';
+import I18nKey from '@i18n/I18nKey';
+import { i18n } from '@i18n/translation';
import { Icon } from 'astro-icon/components';
import Button from './widgets/Button.astro';
import DarkModeButton from './widgets/DarkModeButton.astro';
@@ -7,40 +9,10 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
---
<div id="side-toolbar" class="fixed right-0 bottom-10 z-30 grid grid-cols-1 gap-2">
- <div
- id="stb-show"
- class="peer order-2 grid translate-x-full grid-cols-1 gap-2 pr-4 duration-500 ease-in-out"
- >
- <Button id="stb-show-more" class="btn-circle btn-secondary btn-sm">
- <input
- type="checkbox"
- class="absolute z-10 h-8 w-8 cursor-pointer appearance-none border-0"
- checked
- />
- <Icon name="material-symbols:settings-rounded" class="animate-spin" />
- </Button>
- {
- articleConfig.toc && (
- <TocButton client:media="(width <= 80rem)">
- <Icon name="material-symbols:toc-rounded" slot="icon" />
- </TocButton>
- )
- }
- <Button id="stb-back-to-top" class="group btn-circle btn-secondary btn-sm">
- <span
- id="stb-read-percentage"
- class="absolute text-sm opacity-0 duration-300 group-hover:opacity-0">0</span
- >
- <Icon
- id="stb-back-to-top-icon"
- name="material-symbols:arrow-upward-rounded"
- class="duration-300 group-hover:opacity-100"
- />
- </Button>
- </div>
<div
id="stb-hide"
- class="order-1 grid grid-cols-1 gap-2 pr-4 duration-500 ease-in-out peer-[:first-child:has(:checked)]:translate-x-full"
+ class="grid translate-x-full grid-cols-1 gap-2 pr-4 duration-500 ease-in-out"
+ inert
>
{
toolBarConfig.items.map((item) => {
@@ -73,6 +45,38 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
}
<DarkModeButton id="stb-dark-mode" class="btn-circle btn-secondary btn-sm" />
</div>
+ <div
+ id="stb-show"
+ class="grid translate-x-full grid-cols-1 gap-2 pr-4 duration-500 ease-in-out"
+ >
+ <Button
+ id="stb-show-more"
+ class="btn-circle btn-secondary btn-sm"
+ aria-expanded="false"
+ aria-label={i18n(I18nKey.more)}
+ aria-controls="stb-hide"
+ >
+ <Icon name="material-symbols:settings-rounded" class="animate-spin" />
+ </Button>
+ {
+ articleConfig.toc && (
+ <TocButton client:media="(width <= 80rem)">
+ <Icon name="material-symbols:toc-rounded" slot="icon" />
+ </TocButton>
+ )
+ }
+ <Button id="stb-back-to-top" class="group btn-circle btn-secondary btn-sm">
+ <span
+ id="stb-read-percentage"
+ class="absolute text-sm opacity-0 duration-300 group-hover:opacity-0">0</span
+ >
+ <Icon
+ id="stb-back-to-top-icon"
+ name="material-symbols:arrow-upward-rounded"
+ class="duration-300 group-hover:opacity-100"
+ />
+ </Button>
+ </div>
</div>
<script>
@@ -83,11 +87,26 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
function setup() {
const stbShow = document.getElementById('stb-show');
+ const stbHide = document.getElementById('stb-hide');
const stbShowMore = document.getElementById('stb-show-more');
const stbBackToTop = document.getElementById('stb-back-to-top');
const stbBackToTopIcon = document.getElementById('stb-back-to-top-icon');
const stbReadPercent = document.getElementById('stb-read-percentage');
+ let isExpanded = JSON.parse(stbShowMore?.getAttribute('aria-expanded') || 'false');
+
+ stbShowMore?.addEventListener('click', () => {
+ isExpanded = !isExpanded;
+ stbShowMore.setAttribute('aria-expanded', String(isExpanded));
+ if (isExpanded) {
+ stbHide?.classList.remove('translate-x-full');
+ stbHide?.removeAttribute('inert');
+ } else {
+ stbHide?.classList.add('translate-x-full');
+ stbHide?.setAttribute('inert', 'true');
+ }
+ });
+
stbBackToTop?.addEventListener('click', () => {
window.scrollTo({
top: 0,
@@ -108,7 +127,9 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
stbShow?.classList.remove('translate-x-full');
} else {
stbShow?.classList.add('translate-x-full');
- stbShowMore!.querySelector('input')!.checked = true;
+ stbShowMore?.setAttribute('aria-expanded', 'false');
+ isExpanded = false;
+ document.getElementById('stb-hide')?.classList.add('translate-x-full');
}
// 控制进度条
const scrolledPercentage = getReadingProgress(bottomPos);
src/i18n/langs/en.ts
@@ -39,6 +39,9 @@ export const en: Translation = {
[Key.categoryCount]: 'category',
[Key.categoriesCount]: 'categories',
+ [Key.toc]: 'Table of Content',
+
+ [Key.themeToggle]: 'Toggle Theme',
[Key.lightMode]: 'Light',
[Key.darkMode]: 'Dark',
[Key.systemMode]: 'System',
src/i18n/langs/zh_CN.ts
@@ -39,6 +39,9 @@ export const zh_CN: Translation = {
[Key.categoryCount]: '个分类',
[Key.categoriesCount]: '个分类',
+ [Key.toc]: '目录',
+
+ [Key.themeToggle]: '主题切换',
[Key.lightMode]: '亮色',
[Key.darkMode]: '暗色',
[Key.systemMode]: '跟随系统',
src/i18n/langs/zh_TW.ts
@@ -39,6 +39,9 @@ export const zh_TW: Translation = {
[Key.categoryCount]: '個分類',
[Key.categoriesCount]: '個分類',
+ [Key.toc]: '目錄',
+
+ [Key.themeToggle]: '主題切換',
[Key.lightMode]: '亮色',
[Key.darkMode]: '暗色',
[Key.systemMode]: '跟隨系統',
src/i18n/I18nKey.ts
@@ -36,6 +36,9 @@ enum I18nKey {
categoryCount = 'categoryCount',
categoriesCount = 'categoriesCount',
+ toc = 'toc',
+
+ themeToggle = 'themeToggle',
lightMode = 'lightMode',
darkMode = 'darkMode',
systemMode = 'systemMode',