Add accessibility
This commit is contained in:
parent
540bbaa443
commit
924e424251
2
app.vue
2
app.vue
@ -17,7 +17,7 @@ onMounted(() => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Get theme from localStorage
|
// Get theme from localStorage
|
||||||
let theme = localStorage.getItem("theme") || "default";
|
let theme = localStorage.getItem("theme") || "biasa";
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
html[data-theme="default"] {
|
html[data-theme="biasa"] {
|
||||||
--color-primary: 0, 165, 154;
|
--color-primary: 0, 165, 154;
|
||||||
--color-secondary: 97, 176, 183;
|
--color-secondary: 97, 176, 183;
|
||||||
--color-accent: 243, 244, 246;
|
--color-accent: 243, 244, 246;
|
||||||
@ -36,7 +36,7 @@ html[data-theme="default"] {
|
|||||||
--tw-shadow: #e5eaf2;
|
--tw-shadow: #e5eaf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="dark"] {
|
html[data-theme="gelap"] {
|
||||||
--color-primary: 97, 176, 183;
|
--color-primary: 97, 176, 183;
|
||||||
--color-secondary: 13, 27, 42;
|
--color-secondary: 13, 27, 42;
|
||||||
--color-accent: 15, 23, 42;
|
--color-accent: 15, 23, 42;
|
||||||
@ -48,6 +48,11 @@ html[data-theme="dark"] {
|
|||||||
--border-color: 30, 41, 59;
|
--border-color: 30, 41, 59;
|
||||||
--bg-1: 15, 23, 42;
|
--bg-1: 15, 23, 42;
|
||||||
--bg-2: 30, 41, 59;
|
--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-color: 170, 170, 170;
|
||||||
--scroll-hover-color: 155, 155, 155;
|
--scroll-hover-color: 155, 155, 155;
|
||||||
--fk-border-color: 71, 85, 105;
|
--fk-border-color: 71, 85, 105;
|
||||||
@ -68,53 +73,143 @@ html[data-theme="dark"] {
|
|||||||
--tw-shadow: #e5eaf2;
|
--tw-shadow: #e5eaf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="nier"] {
|
html[data-theme="biru"] {
|
||||||
--color-primary: 99, 95, 84;
|
--color-primary: 0, 102, 204;
|
||||||
--color-secondary: 207, 107, 83;
|
--color-secondary: 51, 153, 255;
|
||||||
--color-accent: 243, 88, 106;
|
--color-accent: 255, 204, 0;
|
||||||
--color-success: 79, 192, 103;
|
--color-success: 46, 204, 113;
|
||||||
--color-info: 65, 133, 242;
|
--color-info: 52, 152, 219;
|
||||||
--color-warning: 246, 141, 32;
|
--color-warning: 241, 196, 15;
|
||||||
--color-danger: 229, 83, 69;
|
--color-danger: 231, 76, 60;
|
||||||
--text-color: 78, 75, 66;
|
--text-color: 0, 0, 0;
|
||||||
--border-color: 153, 152, 131;
|
--border-color: 200, 200, 200;
|
||||||
--bg-1: 205, 200, 176;
|
--bg-1: 240, 248, 255;
|
||||||
--bg-2: 218, 212, 187;
|
--bg-2: 230, 240, 250;
|
||||||
--scroll-color: 99, 95, 84;
|
--sidebar: 38, 50, 55;
|
||||||
--scroll-hover-color: 99, 95, 84;
|
--sidebar-menu: 26, 35, 38;
|
||||||
--fk-border-color: 99, 95, 84;
|
--sidebar-text: 255, 255, 255;
|
||||||
--fk-placeholder-color: 188, 175, 145;
|
--header: 49, 65, 71;
|
||||||
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
--header-text: 255, 255, 255;
|
||||||
--cp-bg: 255, 255, 255;
|
--scroll-color: 180, 180, 180;
|
||||||
--rounded-box: 0.5rem;
|
--scroll-hover-color: 150, 150, 150;
|
||||||
--rounded-btn: 0.5rem;
|
--fk-border-color: 200, 200, 200;
|
||||||
--rounded-badge: 1.9rem;
|
--fk-placeholder-color: 150, 150, 150;
|
||||||
--animation-btn: 0.25s;
|
--box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
--animation-input: 0.2s;
|
0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||||
--btn-text-case: uppercase;
|
--cp-bg: 255, 255, 255;
|
||||||
--btn-focus-scale: 0.95;
|
--rounded-box: 0.5rem;
|
||||||
--padding-btn: 0.625rem 0.875rem;
|
--rounded-btn: 0.5rem;
|
||||||
--border-btn: 1px;
|
--rounded-badge: 1.9rem;
|
||||||
--tab-border: 1px;
|
--animation-btn: 0.25s;
|
||||||
--tab-radius: 0.5rem;
|
--animation-input: 0.2s;
|
||||||
--tw-shadow: #e5eaf2;
|
--btn-text-case: uppercase;
|
||||||
}
|
--btn-focus-scale: 0.95;
|
||||||
|
--padding-btn: 0.625rem 1.5rem;
|
||||||
html[data-theme="custom1"] {
|
--border-btn: 1px;
|
||||||
--color-primary: 9, 76, 90;
|
--tab-border: 1px;
|
||||||
--color-secondary: 62, 72, 83;
|
--tab-radius: 0.5rem;
|
||||||
--color-accent: 0, 103, 236;
|
--tw-shadow: #e5eaf2;
|
||||||
--color-success: 11, 152, 76;
|
}
|
||||||
--color-info: 208, 89, 41;
|
|
||||||
--color-warning: 229, 197, 13;
|
html[data-theme="merah"] {
|
||||||
--color-danger: 238, 5, 34;
|
--color-primary: 204, 0, 0;
|
||||||
--text-color: 15, 15, 14;
|
--color-secondary: 255, 102, 102;
|
||||||
--border-color: 153, 152, 131;
|
--color-accent: 255, 255, 153;
|
||||||
--bg-1: 214, 215, 206;
|
--color-success: 46, 204, 113;
|
||||||
--bg-2: 235, 232, 217;
|
--color-info: 52, 152, 219;
|
||||||
--scroll-color: 9, 76, 90;
|
--color-warning: 241, 196, 15;
|
||||||
--scroll-hover-color: 9, 76, 90;
|
--color-danger: 231, 76, 60;
|
||||||
--box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
--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;
|
--cp-bg: 255, 255, 255;
|
||||||
--rounded-box: 0.5rem;
|
--rounded-box: 0.5rem;
|
||||||
--rounded-btn: 0.5rem;
|
--rounded-btn: 0.5rem;
|
||||||
|
35
components/VoiceReader.vue
Normal file
35
components/VoiceReader.vue
Normal 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>
|
@ -11,6 +11,7 @@ const langList = languageList();
|
|||||||
const locale = ref("en");
|
const locale = ref("en");
|
||||||
|
|
||||||
const themes = themeList();
|
const themes = themeList();
|
||||||
|
const themes2 = themeList2();
|
||||||
|
|
||||||
function setTheme(theme) {
|
function setTheme(theme) {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
@ -85,6 +86,66 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2 item-center justify-items-end">
|
<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">
|
<VDropdown placement="bottom-end" distance="13" name="notification">
|
||||||
<button class="relative icon-btn h-10 w-10 rounded-full">
|
<button class="relative icon-btn h-10 w-10 rounded-full">
|
||||||
<span
|
<span
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export default function () {
|
export default function () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
theme: "default",
|
theme: "biasa",
|
||||||
colors: [
|
colors: [
|
||||||
{
|
{
|
||||||
name: "primary",
|
name: "primary",
|
||||||
@ -18,7 +18,7 @@ export default function () {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
theme: "dark",
|
theme: "gelap",
|
||||||
colors: [
|
colors: [
|
||||||
{
|
{
|
||||||
name: "primary",
|
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
104
composables/themeList2.js
Normal 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
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
55
composables/useVoiceReader.js
Normal file
55
composables/useVoiceReader.js
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
@ -1,58 +1,4 @@
|
|||||||
export default [
|
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",
|
header: "Utama",
|
||||||
description: "",
|
description: "",
|
||||||
@ -176,71 +122,4 @@ export default [
|
|||||||
],
|
],
|
||||||
meta: {},
|
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"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user