Add accessibility

This commit is contained in:
Md Afiq Iskandar 2024-10-08 14:48:28 +08:00
parent 540bbaa443
commit 924e424251
8 changed files with 402 additions and 207 deletions

View File

@ -17,7 +17,7 @@ onMounted(() => {
}, 1000);
// Get theme from localStorage
let theme = localStorage.getItem("theme") || "default";
let theme = localStorage.getItem("theme") || "biasa";
document.documentElement.setAttribute("data-theme", theme);
});
</script>

View File

@ -1,4 +1,4 @@
html[data-theme="default"] {
html[data-theme="biasa"] {
--color-primary: 0, 165, 154;
--color-secondary: 97, 176, 183;
--color-accent: 243, 244, 246;
@ -36,7 +36,7 @@ html[data-theme="default"] {
--tw-shadow: #e5eaf2;
}
html[data-theme="dark"] {
html[data-theme="gelap"] {
--color-primary: 97, 176, 183;
--color-secondary: 13, 27, 42;
--color-accent: 15, 23, 42;
@ -48,6 +48,11 @@ html[data-theme="dark"] {
--border-color: 30, 41, 59;
--bg-1: 15, 23, 42;
--bg-2: 30, 41, 59;
--sidebar: 38, 50, 55;
--sidebar-menu: 26, 35, 38;
--sidebar-text: 255, 255, 255;
--header: 49, 65, 71;
--header-text: 255, 255, 255;
--scroll-color: 170, 170, 170;
--scroll-hover-color: 155, 155, 155;
--fk-border-color: 71, 85, 105;
@ -68,53 +73,143 @@ html[data-theme="dark"] {
--tw-shadow: #e5eaf2;
}
html[data-theme="nier"] {
--color-primary: 99, 95, 84;
--color-secondary: 207, 107, 83;
--color-accent: 243, 88, 106;
--color-success: 79, 192, 103;
--color-info: 65, 133, 242;
--color-warning: 246, 141, 32;
--color-danger: 229, 83, 69;
--text-color: 78, 75, 66;
--border-color: 153, 152, 131;
--bg-1: 205, 200, 176;
--bg-2: 218, 212, 187;
--scroll-color: 99, 95, 84;
--scroll-hover-color: 99, 95, 84;
--fk-border-color: 99, 95, 84;
--fk-placeholder-color: 188, 175, 145;
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 0.875rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="custom1"] {
--color-primary: 9, 76, 90;
--color-secondary: 62, 72, 83;
--color-accent: 0, 103, 236;
--color-success: 11, 152, 76;
--color-info: 208, 89, 41;
--color-warning: 229, 197, 13;
--color-danger: 238, 5, 34;
--text-color: 15, 15, 14;
--border-color: 153, 152, 131;
--bg-1: 214, 215, 206;
--bg-2: 235, 232, 217;
--scroll-color: 9, 76, 90;
--scroll-hover-color: 9, 76, 90;
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
html[data-theme="biru"] {
--color-primary: 0, 102, 204;
--color-secondary: 51, 153, 255;
--color-accent: 255, 204, 0;
--color-success: 46, 204, 113;
--color-info: 52, 152, 219;
--color-warning: 241, 196, 15;
--color-danger: 231, 76, 60;
--text-color: 0, 0, 0;
--border-color: 200, 200, 200;
--bg-1: 240, 248, 255;
--bg-2: 230, 240, 250;
--sidebar: 38, 50, 55;
--sidebar-menu: 26, 35, 38;
--sidebar-text: 255, 255, 255;
--header: 49, 65, 71;
--header-text: 255, 255, 255;
--scroll-color: 180, 180, 180;
--scroll-hover-color: 150, 150, 150;
--fk-border-color: 200, 200, 200;
--fk-placeholder-color: 150, 150, 150;
--box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 1.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="merah"] {
--color-primary: 204, 0, 0;
--color-secondary: 255, 102, 102;
--color-accent: 255, 255, 153;
--color-success: 46, 204, 113;
--color-info: 52, 152, 219;
--color-warning: 241, 196, 15;
--color-danger: 231, 76, 60;
--text-color: 0, 0, 0;
--border-color: 200, 200, 200;
--bg-1: 255, 240, 240;
--bg-2: 255, 230, 230;
--sidebar: 38, 50, 55;
--sidebar-menu: 26, 35, 38;
--sidebar-text: 255, 255, 255;
--header: 49, 65, 71;
--header-text: 255, 255, 255;
--scroll-color: 180, 180, 180;
--scroll-hover-color: 150, 150, 150;
--fk-border-color: 200, 200, 200;
--fk-placeholder-color: 150, 150, 150;
--box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 1.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="ungu"] {
--color-primary: 75, 0, 130;
--color-secondary: 138, 43, 226;
--color-accent: 255, 215, 0;
--color-success: 46, 204, 113;
--color-info: 52, 152, 219;
--color-warning: 241, 196, 15;
--color-danger: 231, 76, 60;
--text-color: 0, 0, 0;
--border-color: 200, 200, 200;
--bg-1: 240, 248, 255;
--bg-2: 230, 240, 250;
--sidebar: 38, 50, 55;
--sidebar-menu: 26, 35, 38;
--sidebar-text: 255, 255, 255;
--header: 49, 65, 71;
--header-text: 255, 255, 255;
--scroll-color: 180, 180, 180;
--scroll-hover-color: 150, 150, 150;
--fk-border-color: 200, 200, 200;
--fk-placeholder-color: 150, 150, 150;
--box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;
--rounded-badge: 1.9rem;
--animation-btn: 0.25s;
--animation-input: 0.2s;
--btn-text-case: uppercase;
--btn-focus-scale: 0.95;
--padding-btn: 0.625rem 1.5rem;
--border-btn: 1px;
--tab-border: 1px;
--tab-radius: 0.5rem;
--tw-shadow: #e5eaf2;
}
html[data-theme="oren"] {
--color-primary: 255, 103, 0;
--color-secondary: 255, 159, 64;
--color-accent: 0, 128, 128;
--color-success: 46, 204, 113;
--color-info: 52, 152, 219;
--color-warning: 241, 196, 15;
--color-danger: 231, 76, 60;
--text-color: 0, 0, 0;
--border-color: 200, 200, 200;
--bg-1: 255, 250, 240;
--bg-2: 255, 245, 230;
--sidebar: 38, 50, 55;
--sidebar-menu: 26, 35, 38;
--sidebar-text: 255, 255, 255;
--header: 49, 65, 71;
--header-text: 255, 255, 255;
--scroll-color: 180, 180, 180;
--scroll-hover-color: 150, 150, 150;
--fk-border-color: 200, 200, 200;
--fk-placeholder-color: 150, 150, 150;
--box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
--cp-bg: 255, 255, 255;
--rounded-box: 0.5rem;
--rounded-btn: 0.5rem;

View File

@ -0,0 +1,35 @@
<template>
<div class="voice-reader">
<rs-button
@click="toggleReading"
:variant="isReading ? 'danger' : 'primary'"
class="p-2 rounded-full"
>
<Icon
:name="isReading ? 'ph:ear' : 'ph:ear-slash'"
:class="['text-2xl', { 'animate-pulse text-white': isReading }]"
/>
</rs-button>
<span ref="announceElement" class="sr-only" aria-live="polite"></span>
</div>
</template>
<script setup>
import { useVoiceReader } from "~/composables/useVoiceReader";
const { isReading, toggleReading, announceElement } = useVoiceReader();
</script>
<style scoped>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>

View File

@ -11,6 +11,7 @@ const langList = languageList();
const locale = ref("en");
const themes = themeList();
const themes2 = themeList2();
function setTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
@ -85,6 +86,66 @@ onMounted(() => {
</div>
<div class="flex gap-2 item-center justify-items-end">
<VoiceReader class="ml-4" />
<!-- New dropdown for themeList.js -->
<VDropdown placement="bottom-end" distance="13" name="theme">
<button class="icon-btn h-10 w-10 rounded-full">
<Icon size="22px" name="ph:paint-brush-broad" />
</button>
<template #popper>
<ul class="header-dropdown w-full md:w-52">
<li v-for="(val, index) in themes" :key="index">
<a
@click="setTheme(val.theme)"
class="flex justify-between items-center cursor-pointer py-2 px-4 hover:bg-[rgb(var(--bg-1))]"
>
<span class="capitalize"> {{ val.theme }} </span>
<div class="flex items-center gap-x-1">
<div
v-for="(color, colorIndex) in val.colors"
:key="colorIndex"
class="h-[25px] w-[10px] rounded-lg"
:style="{
backgroundColor: rgbToHex(color.value),
}"
></div>
</div>
</a>
</li>
</ul>
</template>
</VDropdown>
<!-- New dropdown for themeList2.js -->
<VDropdown placement="bottom-end" distance="13" name="theme2">
<button class="icon-btn h-10 w-10 rounded-full">
<Icon size="22px" name="ph:wheelchair" />
</button>
<template #popper>
<ul class="header-dropdown w-full md:w-52">
<li v-for="(val, index) in themes2" :key="index">
<a
@click="setTheme(val.theme)"
class="flex justify-between items-center cursor-pointer py-2 px-4 hover:bg-[rgb(var(--bg-1))]"
>
<span class="capitalize"> {{ val.theme }} </span>
<div class="flex items-center gap-x-1">
<div
v-for="(color, colorIndex) in val.colors"
:key="colorIndex"
class="h-[25px] w-[10px] rounded-lg"
:style="{
backgroundColor: rgbToHex(color.value),
}"
></div>
</div>
</a>
</li>
</ul>
</template>
</VDropdown>
<VDropdown placement="bottom-end" distance="13" name="notification">
<button class="relative icon-btn h-10 w-10 rounded-full">
<span

View File

@ -1,7 +1,7 @@
export default function () {
return [
{
theme: "default",
theme: "biasa",
colors: [
{
name: "primary",
@ -18,7 +18,7 @@ export default function () {
],
},
{
theme: "dark",
theme: "gelap",
colors: [
{
name: "primary",
@ -34,39 +34,5 @@ export default function () {
},
],
},
{
theme: "nier",
colors: [
{
name: "primary",
value: "99, 95, 84",
},
{
name: "secondary",
value: "207, 107, 83",
},
{
name: "accent",
value: "243, 88, 106",
},
],
},
{
theme: "custom1",
colors: [
{
name: "primary",
value: "9, 76, 90",
},
{
name: "secondary",
value: "62, 72, 83",
},
{
name: "accent",
value: "0, 103, 236",
},
],
},
];
}

104
composables/themeList2.js Normal file
View File

@ -0,0 +1,104 @@
export default function () {
return [
{
theme: "biru",
colors: [
{
name: "primary",
value: "0, 102, 204", // Strong blue
},
{
name: "secondary",
value: "51, 153, 255", // Lighter blue
},
{
name: "accent",
value: "255, 204, 0", // Gold
},
{
name: "background",
value: "240, 248, 255", // Alice blue
},
{
name: "text",
value: "0, 0, 0", // Black
},
],
},
{
theme: "merah",
colors: [
{
name: "primary",
value: "204, 0, 0", // Strong red
},
{
name: "secondary",
value: "255, 102, 102", // Lighter red
},
{
name: "accent",
value: "255, 255, 153", // Light yellow
},
{
name: "background",
value: "255, 240, 240", // Very light pink
},
{
name: "text",
value: "0, 0, 0", // Black
},
],
},
{
theme: "ungu",
colors: [
{
name: "primary",
value: "75, 0, 130", // Indigo
},
{
name: "secondary",
value: "138, 43, 226", // Blue violet
},
{
name: "accent",
value: "255, 215, 0", // Gold
},
{
name: "background",
value: "240, 248, 255", // Alice blue
},
{
name: "text",
value: "0, 0, 0", // Black
},
],
},
{
theme: "oren",
colors: [
{
name: "primary",
value: "255, 103, 0", // Dark orange
},
{
name: "secondary",
value: "255, 159, 64", // Lighter orange
},
{
name: "accent",
value: "0, 128, 128", // Teal
},
{
name: "background",
value: "255, 250, 240", // Floral white
},
{
name: "text",
value: "0, 0, 0", // Black
},
],
},
];
}

View File

@ -0,0 +1,55 @@
export function useVoiceReader() {
const isReading = ref(false);
const announceElement = ref(null);
let speechSynthesis;
let speechUtterance;
onMounted(() => {
speechSynthesis = window.speechSynthesis;
speechUtterance = new SpeechSynthesisUtterance();
window.addEventListener("keydown", handleKeydown);
});
onUnmounted(() => {
if (speechSynthesis) {
speechSynthesis.cancel();
}
window.removeEventListener("keydown", handleKeydown);
});
const toggleReading = () => {
if (!speechSynthesis) return;
if (isReading.value) {
speechSynthesis.pause();
isReading.value = false;
announce("Reading paused");
} else {
const textToRead = document.body.innerText;
speechUtterance.text = textToRead;
speechSynthesis.speak(speechUtterance);
isReading.value = true;
announce("Reading started");
}
};
const handleKeydown = (event) => {
if (event.ctrlKey && event.key === "r") {
event.preventDefault();
toggleReading();
}
};
const announce = (message) => {
if (announceElement.value) {
announceElement.value.textContent = message;
}
};
return {
isReading,
toggleReading,
announceElement,
};
}

View File

@ -1,58 +1,4 @@
export default [
{
header: "Forensik",
description: "",
child: [
{
title: "FOR-01",
icon: "ph:number-circle-one-fill",
child: [
{
title: "Permohonan Temujanji",
path: "/permohonan-temujanji/senarai",
child: [],
meta: {},
},
{
title: "Kaunter Semakan ",
path: "/kemaskini-daftar/senarai",
child: [],
meta: {},
},
// {
// title: "Kemaskini Daftar",
// path: "/kemaskini-daftar/senarai",
// child: [],
// meta: {},
// },
],
},
{
title: "FOR-02",
icon: "ph:number-circle-two-fill",
child: [
{
title: "Pengesanan Penyamaran",
path: "/pengesanan-penyamaran/senarai",
child: [],
meta: {},
},
],
},
{
title: "FOR-03",
icon: "ph:number-circle-two-fill",
child: [
{
title: "e-library",
path: "/e-library",
child: [],
meta: {},
},
],
},
],
},
{
header: "Utama",
description: "",
@ -176,71 +122,4 @@ export default [
],
meta: {},
},
{
header: "Pengurusan",
description: "",
child: [
{
title: "Konfigurasi",
icon: "ic:outline-settings",
child: [
{
title: "Persekitaran",
path: "/devtool/config/environment",
},
],
},
{
title: "Penyelia Menu",
icon: "ci:menu-alt-03",
path: "/devtool/menu-editor",
child: [],
},
{
title: "Penyelia Pengguna",
path: "/devtool/user-management",
icon: "ph:user-circle-gear",
child: [
{
title: "Pengguna",
path: "/devtool/user-management/user-list",
icon: "",
child: [],
},
{
title: "Peranan",
path: "/devtool/user-management/role-list",
icon: "",
child: [],
},
],
},
{
title: "Kandungan",
icon: "mdi:pencil-ruler",
child: [
{
title: "Penyelia Kandungan",
path: "/devtool/content-editor",
},
{
title: "Templat",
path: "/devtool/content-editor/template",
},
],
},
{
title: "Penyelia API",
path: "/devtool/api-editor",
icon: "material-symbols:api-rounded",
child: [],
},
],
meta: {
auth: {
role: ["Developer"],
},
},
},
];