├── banner.png ├── lib ├── components │ ├── Accessibilik │ │ ├── accessibilik.module.scss │ │ └── index.tsx │ ├── buttons │ │ ├── content │ │ │ ├── HighlightTitlesButton │ │ │ │ ├── HighlightTitlesButton.module.scss │ │ │ │ ├── HighlightTitlesButton.tsx │ │ │ │ └── useHighlightTitlesButton.ts │ │ │ ├── ZoomButton │ │ │ │ ├── ZoomButton.module.scss │ │ │ │ ├── useZoomButton.ts │ │ │ │ └── ZoomButton.tsx │ │ │ ├── LineHeightButton │ │ │ │ ├── LineHeightButton.module.scss │ │ │ │ ├── useLineHeightButton.ts │ │ │ │ └── LineHeightButton.tsx │ │ │ ├── DyslexiaFontButton │ │ │ │ ├── useDyslexiaFontButton.ts │ │ │ │ ├── DyslexiaFontButton.tsx │ │ │ │ └── style.ts │ │ │ ├── FontWeightButton │ │ │ │ ├── useFontWeightButton.ts │ │ │ │ └── FontWeightButton.tsx │ │ │ ├── HighlightLinksButton │ │ │ │ ├── HighlightLinksButton.tsx │ │ │ │ └── useHighlightLinksButton.ts │ │ │ ├── AdjustFontSize │ │ │ │ ├── useAdjustFontSize.ts │ │ │ │ └── AdjustFontSize.tsx │ │ │ ├── AlignTextButton │ │ │ │ ├── AlignTextButton.tsx │ │ │ │ └── useAlignTextButton.ts │ │ │ ├── LetterSpacingButton │ │ │ │ ├── useLetterSpacingButton.ts │ │ │ │ └── LetterSpacingButton.tsx │ │ │ └── WordSpacingButton │ │ │ │ ├── useWordSpacingButton.ts │ │ │ │ └── WordSpacingButton.tsx │ │ ├── AccValueControl │ │ │ ├── AccValueControl.module.scss │ │ │ └── AccValueControl.tsx │ │ ├── colors │ │ │ ├── BrightnessControl │ │ │ │ ├── BrightnessControl.module.scss │ │ │ │ ├── useBrightnessControl.ts │ │ │ │ └── BrightnessControl.tsx │ │ │ ├── TextColorPickerButton │ │ │ │ ├── TextColorPickerButton.module.scss │ │ │ │ ├── useTextColorPickerButton.ts │ │ │ │ └── TextColorPickerButton.tsx │ │ │ ├── MonochromeButton │ │ │ │ ├── MonochromeButton.tsx │ │ │ │ └── useMonochromeButton.ts │ │ │ ├── DarkContrastButton │ │ │ │ ├── DarkContrastButton.tsx │ │ │ │ └── useDarkContrastButton.ts │ │ │ ├── VisualImpairmentButton │ │ │ │ ├── VisualImpairmentButton.tsx │ │ │ │ └── useVisualImpairmentButton.ts │ │ │ ├── LightContrastButton │ │ │ │ ├── LightContrastButton.tsx │ │ │ │ └── useLightContrastButton.ts │ │ │ ├── BlueLightFilterButton │ │ │ │ ├── BlueLightFilterButton.tsx │ │ │ │ └── useBlueLightFilterButton.ts │ │ │ ├── HighContrastButton │ │ │ │ ├── useHighContrastButton.ts │ │ │ │ └── HighContrastButton.tsx │ │ │ ├── LowSaturationButton │ │ │ │ ├── useLowSaturationButton.ts │ │ │ │ └── LowSaturationButton.tsx │ │ │ └── HighSaturationButton │ │ │ │ ├── useHighSaturationButton.ts │ │ │ │ └── HighSaturationButton.tsx │ │ ├── tools │ │ │ ├── ReadingGuide │ │ │ │ ├── ReadingGuide.module.scss │ │ │ │ ├── ReadingGuide.tsx │ │ │ │ └── useReadingGuide.ts │ │ │ ├── BigCursorButton │ │ │ │ ├── cursor.tsx │ │ │ │ ├── BigCursorButton.tsx │ │ │ │ └── useBigCursorButton.ts │ │ │ └── TextToSpeech │ │ │ │ ├── TextToSpeech.module.scss │ │ │ │ └── TextToSpeech.tsx │ │ ├── AccValueControlButton │ │ │ ├── AccValueControlButton.module.scss │ │ │ └── AccValueControlButton.tsx │ │ ├── AccessibilityButton │ │ │ ├── AccessibilityButton.tsx │ │ │ └── AccessibilityButton.module.scss │ │ └── AccButton │ │ │ ├── AccButton.module.scss │ │ │ └── AccButton.tsx │ ├── AccMenuContent │ │ ├── AccMenuContent.module.scss │ │ └── AccMenuContent.tsx │ ├── Footer │ │ ├── Footer.module.scss │ │ └── Footer.tsx │ ├── main.tsx │ ├── RcSlider │ │ ├── RcSlider.module.scss │ │ └── RcSlider.tsx │ ├── Header │ │ ├── Header.module.scss │ │ └── Header.tsx │ ├── AccTools │ │ └── AccTools.tsx │ ├── AccessibilityMenu │ │ ├── AccessibilityMenu.module.scss │ │ └── AccessibilityMenu.tsx │ ├── Portal │ │ └── Portal.tsx │ ├── AccMenuContentBlock │ │ ├── AccMenuContentBlock.module.scss │ │ └── AccMenuContentBlock.tsx │ ├── AccColors │ │ └── AccColors.tsx │ └── AccContent │ │ └── AccContent.tsx ├── main.ts ├── vite-env.d.ts ├── assets │ └── icons │ │ ├── remove.svg │ │ ├── add.svg │ │ ├── highlightTitles.svg │ │ ├── expand.svg │ │ ├── close.svg │ │ ├── textAlign.svg │ │ ├── highcontrast.svg │ │ ├── lineHeight.svg │ │ ├── content.svg │ │ ├── adjustFontSize.svg │ │ ├── highSaturation.svg │ │ ├── init.svg │ │ ├── lowSaturation.svg │ │ ├── blueLight.svg │ │ ├── highlightLinks.svg │ │ ├── fontWeight.svg │ │ ├── dyslexia.svg │ │ ├── textToSpeach.svg │ │ ├── darkContrast.svg │ │ ├── monochrome.svg │ │ ├── zoom.svg │ │ ├── questionMark.svg │ │ ├── visualImpairment.svg │ │ ├── readingGuide.svg │ │ ├── brightness.svg │ │ ├── letterSpacing.svg │ │ ├── bigCursor.svg │ │ ├── accessibleIcon.svg │ │ ├── tools.svg │ │ ├── platte.svg │ │ └── wordSpacing.svg ├── main.tsx ├── constants.ts ├── types.ts ├── index.css ├── hooks │ ├── useFontSizeTraverse.ts │ ├── useFontSizeMutationObserver.ts │ └── usePersistenceLayout │ │ └── usePersistenceLayout.ts ├── i18 │ └── locale │ │ ├── en.json │ │ └── index.ts ├── utils.ts └── config.ts ├── tsconfig-build.json ├── src ├── vite-env.d.ts ├── index.css ├── main.tsx ├── App.tsx └── assets │ └── react.svg ├── postcss.config.js ├── tsconfig.node.json ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── tsconfig.json ├── LICENSE.md ├── public └── vite.svg ├── vite.config.ts ├── package.json └── README.md /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RosenGray/accessibilik/HEAD/banner.png -------------------------------------------------------------------------------- /lib/components/Accessibilik/accessibilik.module.scss: -------------------------------------------------------------------------------- 1 | .Accessibilik { 2 | 3 | 4 | } -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["lib"] 4 | } -------------------------------------------------------------------------------- /lib/main.ts: -------------------------------------------------------------------------------- 1 | 2 | import Accessibilik from './components/Accessibilik' 3 | 4 | export default Accessibilik; -------------------------------------------------------------------------------- /lib/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /lib/components/buttons/content/HighlightTitlesButton/HighlightTitlesButton.module.scss: -------------------------------------------------------------------------------- 1 | .HighlightTitlesButton { 2 | border:4px solid red; 3 | 4 | } 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // Using ES Module syntax 2 | import autoprefixer from 'autoprefixer'; 3 | 4 | export default { 5 | plugins: [ 6 | autoprefixer 7 | ] 8 | }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | 4 | /* font-size: 62.5%; */ 5 | /* 10px/16px = 62.5% -> 1rem = 10px; */ 6 | } 7 | 8 | .test{ 9 | font-size: 50px; 10 | } -------------------------------------------------------------------------------- /lib/components/buttons/content/ZoomButton/ZoomButton.module.scss: -------------------------------------------------------------------------------- 1 | .accZoomButton { 2 | height: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-around; 6 | } 7 | -------------------------------------------------------------------------------- /lib/components/buttons/AccValueControl/AccValueControl.module.scss: -------------------------------------------------------------------------------- 1 | .accValueControl{ 2 | height: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-around; 6 | } -------------------------------------------------------------------------------- /lib/components/buttons/content/LineHeightButton/LineHeightButton.module.scss: -------------------------------------------------------------------------------- 1 | .accLineHeightButton { 2 | height: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-around; 6 | } 7 | -------------------------------------------------------------------------------- /lib/components/AccMenuContent/AccMenuContent.module.scss: -------------------------------------------------------------------------------- 1 | .accMenuContent { 2 | display: grid; 3 | height: calc(100vh - 250px); 4 | display: flex; 5 | flex-direction: column; 6 | overflow: hidden; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /lib/assets/icons/remove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/BrightnessControl/BrightnessControl.module.scss: -------------------------------------------------------------------------------- 1 | .accBrightnessControl { 2 | height: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-around; 6 | position: relative; 7 | } 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /lib/assets/icons/highlightTitles.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/main.tsx: -------------------------------------------------------------------------------- 1 | 2 | import ReactDOM from 'react-dom/client' 3 | import Accessibilik from './components/Accessibilik' 4 | import React from 'react' 5 | 6 | 7 | 8 | ReactDOM.createRoot(document.getElementById('root-accessibilik')!).render() 9 | 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const textTags = ["h1","h2","h3","h4","h5","h6","span","p","a","label","i","button","img","ol","svg"] 2 | export const ACC_MENU_CONTAINER_ID = "acc-accessibility-menu"; 3 | export const APP_ID = "accessibilik"; 4 | export const PORTAL_APP_ID = `root-${APP_ID}`; -------------------------------------------------------------------------------- /lib/assets/icons/expand.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/buttons/tools/ReadingGuide/ReadingGuide.module.scss: -------------------------------------------------------------------------------- 1 | .acc-readingGuide{ 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | width: 100%; 7 | height: 0; 8 | pointer-events: none; 9 | background-color: rgba(0,0,0,.5); 10 | z-index: 1000000; 11 | } -------------------------------------------------------------------------------- /lib/assets/icons/textAlign.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/Footer/Footer.module.scss: -------------------------------------------------------------------------------- 1 | .accFooter { 2 | height: 30px; 3 | background-color: #2c2639; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | color: #fff; 8 | & > a { 9 | color: white; 10 | } 11 | @media screen and (max-width: 540px) { 12 | height: 55px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/assets/icons/highcontrast.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/lineHeight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/content.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /lib/assets/icons/adjustFontSize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/highSaturation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import styled from "./Footer.module.scss"; 3 | 4 | const Footer: FC = () => { 5 | return ( 6 | 9 | ); 10 | }; 11 | 12 | export default Footer; 13 | -------------------------------------------------------------------------------- /lib/assets/icons/init.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/lowSaturation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/blueLight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/highlightLinks.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/fontWeight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/dyslexia.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/textToSpeach.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/darkContrast.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/monochrome.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/icons/zoom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/main.tsx: -------------------------------------------------------------------------------- 1 | // // lib/main.ts 2 | // // export { Button } from './components/Button' 3 | // // export { Input } from './components/Input' 4 | // // export { Label } from './components/Label' 5 | 6 | 7 | 8 | // import ReactDOM from 'react-dom/client' 9 | // // import { Button } from "./Accessibilik" 10 | 11 | 12 | 13 | // ReactDOM.createRoot(document.getElementById('root')!).render( 14 | // <> 15 | // 20 |

{t("accessibility-menu")}

21 | 24 | 25 | ); 26 | }; 27 | 28 | export default Header; 29 | -------------------------------------------------------------------------------- /lib/assets/icons/wordSpacing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/buttons/content/DyslexiaFontButton/useDyslexiaFontButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import DYSLEXIA_FONT_STYLE from "./style"; 3 | 4 | const styleID = "acc-dyslexia-font-style"; 5 | const rootClass = "acc-dyslexia-font"; 6 | 7 | export const useDyslexiaFontButton = ( 8 | isDyslexiaFont: boolean, 9 | isGettingReady?: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isDyslexiaFont && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = DYSLEXIA_FONT_STYLE; 19 | document.head.appendChild(style); 20 | } else if (!isDyslexiaFont && style) { 21 | document.documentElement.classList.remove(rootClass); 22 | style?.remove(); 23 | } 24 | }, [isDyslexiaFont, isGettingReady]); 25 | }; 26 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/MonochromeButton/MonochromeButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import MonochromePhotosIcon from "./../../../../assets/icons/monochrome.svg?react"; 5 | 6 | interface MonochromeButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const MonochromeButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const toggleMonochromeHandler = () => { 16 | onChangeAccState((draft) => { 17 | draft.isMonochrome = !draft.isMonochrome; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default MonochromeButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/DarkContrastButton/DarkContrastButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import Brightness4SharpIcon from './../../../../assets/icons/darkContrast.svg?react'; 5 | 6 | interface DarkContrastButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const DarkContrastButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const toggleDarkContrastHandler = () => { 16 | onChangeAccState((draft) => { 17 | draft.isDarkContrast = !draft.isDarkContrast; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default DarkContrastButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/VisualImpairmentButton/VisualImpairmentButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import BlindIcon from "./../../../../assets/icons/visualImpairment.svg?react"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | 6 | interface VisualImpairmentButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const VisualImpairmentButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const toggleVisuality = () => { 16 | onChangeAccState((draft) => { 17 | draft.isVisualImpairment = !draft.isVisualImpairment; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default VisualImpairmentButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/content/FontWeightButton/useFontWeightButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-font-weight-style"; 4 | const rootClass = "acc-font-weight"; 5 | 6 | export const useFontWeightButton = ( 7 | isFontWeightBold: boolean, 8 | isGettingReady?: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (!isGettingReady) { 12 | const style = document.getElementById(styleID); 13 | if (isFontWeightBold && !style) { 14 | document.documentElement.classList.add(rootClass); 15 | const style = document.createElement("style"); 16 | style.id = styleID; 17 | style.innerHTML = ` 18 | html.${rootClass} *, * { 19 | font-weight:700 !important; 20 | } 21 | `; 22 | document.head.appendChild(style); 23 | } else if (!isFontWeightBold && style) { 24 | document.documentElement.classList.remove(rootClass); 25 | style?.remove(); 26 | } 27 | } 28 | }, [isFontWeightBold, isGettingReady]); 29 | }; 30 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/LightContrastButton/LightContrastButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import Brightness4SharpIcon from "./../../../../assets/icons/darkContrast.svg?react"; 5 | 6 | interface LightContrastButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const LightContrastButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const toggleLightContrastHandler = () => { 16 | onChangeAccState((draft) => { 17 | draft.isLightContrast = !draft.isLightContrast; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default LightContrastButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/content/FontWeightButton/FontWeightButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import FormatBoldIcon from "./../../../../assets/icons/fontWeight.svg?react"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | 6 | interface FontWeightButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const FontWeightButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const { isFontWeightBold } = accState; 16 | 17 | const toggleFontWeightHandler = () => { 18 | onChangeAccState((draft) => { 19 | draft.isFontWeightBold = !draft.isFontWeightBold; 20 | }); 21 | }; 22 | 23 | return ( 24 | 31 | ); 32 | }; 33 | 34 | export default FontWeightButton; 35 | -------------------------------------------------------------------------------- /lib/components/buttons/content/HighlightLinksButton/HighlightLinksButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import LinkIcon from "./../../../../assets/icons/highlightLinks.svg?react"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | interface HighlightLinksButtonProps { 6 | accState: AccessibilikState; 7 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 8 | } 9 | 10 | const HighlightLinksButton: FC = ({ 11 | accState, 12 | onChangeAccState, 13 | }) => { 14 | const { highlightLinks } = accState; 15 | const toggleHighlightHander = () => { 16 | onChangeAccState((draft) => { 17 | draft.highlightLinks = !draft.highlightLinks; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default HighlightLinksButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/content/HighlightTitlesButton/HighlightTitlesButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import TitleIcon from "./../../../../assets/icons/highlightTitles.svg?react"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | interface HighlightTitlesButtonProps { 6 | accState: AccessibilikState; 7 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 8 | } 9 | 10 | const HighlightTitlesButton: FC = ({ 11 | accState, 12 | onChangeAccState, 13 | }) => { 14 | const { highlightTitles } = accState; 15 | const toggleHighlightHander = () => { 16 | onChangeAccState((draft) => { 17 | draft.highlightTitles = !draft.highlightTitles; 18 | }); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | 32 | export default HighlightTitlesButton; 33 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/BlueLightFilterButton/BlueLightFilterButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import VisibilitySharpIcon from "./../../../../assets/icons/blueLight.svg?react"; 3 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | 6 | interface BlueLightFilterButtonProps { 7 | accState: AccessibilikState; 8 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 9 | } 10 | 11 | const BlueLightFilterButton: FC = ({ 12 | accState, 13 | onChangeAccState, 14 | }) => { 15 | const { isBlueLightFilter } = accState; 16 | 17 | const toggleBlueLightFilter = () => { 18 | onChangeAccState((draft) => { 19 | draft.isBlueLightFilter = !draft.isBlueLightFilter; 20 | }); 21 | }; 22 | 23 | return ( 24 | 31 | ); 32 | }; 33 | 34 | export default BlueLightFilterButton; 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vladi Iokhim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/components/buttons/tools/BigCursorButton/BigCursorButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import BigCursorIcon from "./../../../../assets/icons/bigCursor.svg?react"; 5 | import { useBigCursorButton } from "./useBigCursorButton"; 6 | 7 | interface BigCursorButtonProps { 8 | accState: AccessibilikState; 9 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 10 | } 11 | 12 | const BigCursorButton: FC = ({ 13 | accState, 14 | onChangeAccState, 15 | }) => { 16 | const { isBigCursor } = accState; 17 | useBigCursorButton(isBigCursor); 18 | 19 | const toggleBigCursorHandler = () => { 20 | onChangeAccState((draft) => { 21 | draft.isBigCursor = !draft.isBigCursor; 22 | }); 23 | }; 24 | 25 | return ( 26 | 33 | ); 34 | }; 35 | 36 | export default BigCursorButton; 37 | -------------------------------------------------------------------------------- /lib/components/buttons/content/HighlightLinksButton/useHighlightLinksButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-highlight-links-style"; 4 | const rootClass = "acc-highlight-links"; 5 | 6 | export const useHighlightLinksButton = ( 7 | highlightLinks: boolean, 8 | isGettingReady?: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (isGettingReady) return; 12 | const style = document.getElementById(styleID); 13 | 14 | if (highlightLinks && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} a[href],a[href] { 20 | outline: 2px solid var(--highlight-color) !important; 21 | outline-offset: 2px !important; 22 | } 23 | `; 24 | document.head.appendChild(style); 25 | } else if (!highlightLinks && style) { 26 | const style = document.getElementById(styleID); 27 | document.documentElement.classList.remove(rootClass); 28 | style?.remove(); 29 | } 30 | }, [highlightLinks, isGettingReady]); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/components/buttons/tools/BigCursorButton/useBigCursorButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { getDataImageSvgBase64 } from "../../../../utils"; 3 | import cursor from "./cursor"; 4 | 5 | const styleID = "acc-big-cursor-style"; 6 | const rootClass = "acc-big-cursor"; 7 | 8 | export const useBigCursorButton = ( 9 | isBigCursor: boolean, 10 | isGettingReady?: boolean 11 | ) => { 12 | useLayoutEffect(() => { 13 | if (isGettingReady) return; 14 | const style = document.getElementById(styleID); 15 | 16 | if (isBigCursor && !style) { 17 | document.documentElement.classList.add(rootClass); 18 | const style = document.createElement("style"); 19 | style.id = styleID; 20 | style.innerHTML = ` 21 | html.${rootClass} body * { 22 | cursor:url(${getDataImageSvgBase64(cursor)}),default !important;} 23 | `; 24 | document.head.appendChild(style); 25 | } else if (!isBigCursor && style) { 26 | const style = document.getElementById(styleID); 27 | document.documentElement.classList.remove(rootClass); 28 | style?.remove(); 29 | } 30 | }, [isBigCursor, isGettingReady]); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/components/buttons/content/HighlightTitlesButton/useHighlightTitlesButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-highlight-titles-style"; 4 | const rootClass = "acc-highlight-titles"; 5 | 6 | export const useHighlightTitlesButton = ( 7 | highlightTitles: boolean, 8 | isGettingReady?: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (isGettingReady) return; 12 | const style = document.getElementById(styleID); 13 | 14 | if (highlightTitles && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} h1,h2,h3,h4,h5,h6,h1,h2,h3,h4,h5,h6 { 20 | outline: 2px solid var(--highlight-color) !important; 21 | outline-offset: 2px !important; 22 | } 23 | `; 24 | document.head.appendChild(style); 25 | } else if (!highlightTitles && style) { 26 | const style = document.getElementById(styleID); 27 | document.documentElement.classList.remove(rootClass); 28 | style?.remove(); 29 | } 30 | }, [highlightTitles, isGettingReady]); 31 | }; 32 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import Accessibilik from "../lib/main"; 3 | import "./index.css"; 4 | import { BrowserRouter, Routes, Route, Link, NavLink } from "react-router"; 5 | 6 | const Home: FC = () => { 7 | return
Home
; 8 | }; 9 | const About: FC = () => { 10 | return
About
; 11 | }; 12 | const Contact: FC = () => { 13 | return
Contact
; 14 | }; 15 | 16 | function Header() { 17 | return ( 18 | 29 | ); 30 | } 31 | 32 | const App: FC = () => { 33 | return ( 34 | 35 |
36 | 37 | 38 | } /> 39 | } /> 40 | } /> 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/MonochromeButton/useMonochromeButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-monochrome-style"; 4 | const rootClass = "acc-monochrome"; 5 | 6 | export const useMonochromeButton = ( 7 | isMonochrome: boolean, 8 | isGettingReady: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (isGettingReady) return; 12 | 13 | if (isMonochrome) { 14 | document.documentElement.classList.add(rootClass); 15 | const style = document.createElement("style"); 16 | style.id = styleID; 17 | style.innerHTML = ` 18 | html.${rootClass} { 19 | -o-filter: grayscale(100%) !important; 20 | -ms-filter: grayscale(100%) !important; 21 | -moz-filter: grayscale(100%) !important; 22 | -webkit-filter: grayscale(100%) !important; 23 | filter: grayscale(100%) !important; 24 | } 25 | `; 26 | document.head.appendChild(style); 27 | } 28 | 29 | return () => { 30 | const style = document.getElementById(styleID); 31 | document.documentElement.classList.remove(rootClass); 32 | style?.remove(); 33 | }; 34 | }, [isMonochrome, isGettingReady]); 35 | }; -------------------------------------------------------------------------------- /lib/components/buttons/content/DyslexiaFontButton/DyslexiaFontButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import SortByAlphaIcon from "./../../../../assets/icons/dyslexia.svg?react"; 5 | import { useDyslexiaFontButton } from "./useDyslexiaFontButton"; 6 | 7 | interface DyslexiaFontButtonProps { 8 | accState: AccessibilikState; 9 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 10 | } 11 | 12 | const DyslexiaFontButton: FC = ({ 13 | accState, 14 | onChangeAccState, 15 | }) => { 16 | const { isDyslexiaFont } = accState; 17 | useDyslexiaFontButton(isDyslexiaFont); 18 | const toogleDyslexiaFontHandler = () => { 19 | onChangeAccState((draft) => { 20 | draft.isDyslexiaFont = !draft.isDyslexiaFont; 21 | }); 22 | }; 23 | 24 | return ( 25 | 32 | ); 33 | }; 34 | 35 | export default DyslexiaFontButton; 36 | -------------------------------------------------------------------------------- /lib/components/buttons/tools/ReadingGuide/ReadingGuide.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import ReadingGuideIcon from "./../../../../assets/icons/readingGuide.svg?react"; 3 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | import { useReadingGuide } from "./useReadingGuide"; 6 | 7 | interface ReadingGuideProps { 8 | rgGap?: number; 9 | accState: AccessibilikState; 10 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 11 | } 12 | const ReadingGuide: FC = ({ 13 | rgGap = 100, 14 | accState, 15 | onChangeAccState, 16 | }) => { 17 | const { showReadingGuide } = accState; 18 | useReadingGuide(showReadingGuide, rgGap); 19 | 20 | const toggleReadingGuideHandler = () => { 21 | onChangeAccState((draft) => { 22 | draft.showReadingGuide = !draft.showReadingGuide; 23 | }); 24 | }; 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default ReadingGuide; 38 | -------------------------------------------------------------------------------- /lib/components/buttons/AccessibilityButton/AccessibilityButton.module.scss: -------------------------------------------------------------------------------- 1 | .AccessibilityIcon { 2 | background-color: var(--primary-color); 3 | width: 70px; 4 | height: 70px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | box-shadow: 0 5px 15px 0 rgb(37 44 97 / 15%), 9 | 0 2px 4px 0 rgb(93 100 148 / 20%); 10 | position: fixed; 11 | z-index: 500000000000000000; 12 | inset-inline-start: 20px; 13 | bottom: 20px; 14 | transition: 0.3s; 15 | cursor: pointer; 16 | &.showSpinner { 17 | cursor: auto; 18 | animation: spin 2.5s linear infinite; 19 | } 20 | 21 | &:hover { 22 | transform: scale(1.2); 23 | } 24 | 25 | & > svg { 26 | fill: var(--white); 27 | width: 75%; 28 | z-index: 1; 29 | } 30 | &:after { 31 | content: ""; 32 | position: absolute; 33 | left: 50%; 34 | top: 50%; 35 | bottom: 0; 36 | width: 80%; 37 | height: 80%; 38 | border: 2px solid white; 39 | background-color: var(--primary-color); 40 | z-index: 0; 41 | transform: translate(-50%, -50%); 42 | } 43 | } 44 | 45 | @keyframes spin { 46 | 0% { 47 | transform: rotate(0deg); 48 | } 49 | 100% { 50 | transform: rotate(360deg); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/VisualImpairmentButton/useVisualImpairmentButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-visual-impairment-style"; 4 | const rootClass = "acc-visual-impairment"; 5 | 6 | export const useVisualImpairmentButton = ( 7 | isVisualImpairment: boolean, 8 | isGettingReady: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (isGettingReady) return; 12 | 13 | if (isVisualImpairment) { 14 | document.documentElement.classList.add(rootClass); 15 | const style = document.createElement("style"); 16 | style.id = styleID; 17 | style.innerHTML = ` 18 | html.${rootClass} { 19 | -o-filter: invert(100%) !important; 20 | -ms-filter: invert(100%) !important; 21 | -moz-filter: invert(100%) !important; 22 | -webkit-filter: invert(100%) !important; 23 | filter: invert(100%) !important; 24 | } 25 | `; 26 | document.head.appendChild(style); 27 | } 28 | 29 | return () => { 30 | const style = document.getElementById(styleID); 31 | document.documentElement.classList.remove(rootClass); 32 | style?.remove(); 33 | }; 34 | }, [isVisualImpairment, isGettingReady]); 35 | }; -------------------------------------------------------------------------------- /lib/components/AccessibilityMenu/AccessibilityMenu.module.scss: -------------------------------------------------------------------------------- 1 | .accAccessibilityMenu { 2 | &__overlay { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | z-index: 10000; 9 | } 10 | } 11 | 12 | .accMenu { 13 | position: fixed; 14 | inset-inline-start: 20px; 15 | bottom: 100px; 16 | border-radius: 10px; 17 | box-shadow: 0 0 20px #00000080; 18 | opacity: 1; 19 | transition: 0.3s; 20 | z-index: 500000000000000000; 21 | overflow: hidden; 22 | background: #f9f9f9; // #e9eff4; 23 | width: 100%; 24 | max-width: 500px; 25 | line-height: 1; 26 | font-size: 16px; 27 | letter-spacing: 0.015em; 28 | display: flex; 29 | flex-direction: column; 30 | [class$="-control"] { 31 | border-radius: 0; 32 | border: 0.3px solid #2c2639; 33 | } 34 | [class$="-indicatorContainer"] { 35 | svg { 36 | fill: #2c2639; 37 | } 38 | } 39 | [class$="-indicatorSeparator"] { 40 | background-color: #2c2639; 41 | } 42 | [class$="-menu"] { 43 | margin-top: 0; 44 | z-index: 100; 45 | } 46 | @media screen and (max-width: 540px) { 47 | inset-inline-start: 0; 48 | max-width: 100%; 49 | top: 0; 50 | border-radius: 0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/DarkContrastButton/useDarkContrastButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { textTags } from "../../../../constants"; 3 | 4 | const styleID = "acc-dark-contrast-style"; 5 | const rootClass = "acc-dark-contrast"; 6 | 7 | export const useDarkContrastButton = ( 8 | isDarkContrast: boolean, 9 | isGettingReady: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | 14 | if (isDarkContrast) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | const textSelectors = textTags 19 | .map((tag) => `html.${rootClass} ${tag}`) 20 | .join(","); 21 | style.innerHTML = ` 22 | ${textSelectors},${textTags.join(",")} { 23 | color:#FFF !important; 24 | fill: #FFF !important; 25 | background-color: #000 !important; 26 | } 27 | `; 28 | document.head.appendChild(style); 29 | } 30 | 31 | return () => { 32 | const style = document.getElementById(styleID); 33 | document.documentElement.classList.remove(rootClass); 34 | style?.remove(); 35 | }; 36 | }, [isDarkContrast, isGettingReady]); 37 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/BlueLightFilterButton/useBlueLightFilterButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-blueLight-filter-style"; 4 | const rootClass = "acc-blue-light-filter"; 5 | 6 | export const useBlueLightFilterButton = ( 7 | isBlueLightFilter: boolean, 8 | isGettingReady?: boolean 9 | ) => { 10 | useLayoutEffect(() => { 11 | if (!isGettingReady) { 12 | const style = document.getElementById(styleID); 13 | if (isBlueLightFilter && !style) { 14 | document.documentElement.classList.add(rootClass); 15 | const style = document.createElement("style"); 16 | style.id = styleID; 17 | style.innerHTML = ` 18 | html.${rootClass} { 19 | -o-filter: sepia(80%) !important; 20 | -ms-filter: sepia(80%) !important; 21 | -moz-filter: sepia(80%) !important; 22 | -webkit-filter: sepia(80%) !important; 23 | filter: sepia(80%) !important; 24 | } 25 | `; 26 | document.head.appendChild(style); 27 | } else if (!isBlueLightFilter && style) { 28 | document.documentElement.classList.remove(rootClass); 29 | style?.remove(); 30 | } 31 | } 32 | }, [isBlueLightFilter, isGettingReady]); 33 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/LightContrastButton/useLightContrastButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { textTags } from "../../../../constants"; 3 | 4 | const styleID = "acc-light-contrast-style"; 5 | const rootClass = "acc-light-contrast"; 6 | 7 | export const useLightContrastButton = ( 8 | isLightContrast: boolean, 9 | isGettingReady: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | 14 | if (isLightContrast) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | const textSelectors = textTags 19 | .map((tag) => `html.${rootClass} ${tag}`) 20 | .join(","); 21 | style.innerHTML = ` 22 | ${textSelectors},${textTags.join(",")} { 23 | color:#000 !important; 24 | fill: #000 !important; 25 | background-color: #FFF !important; 26 | } 27 | `; 28 | document.head.appendChild(style); 29 | } 30 | 31 | return () => { 32 | const style = document.getElementById(styleID); 33 | document.documentElement.classList.remove(rootClass); 34 | style?.remove(); 35 | }; 36 | }, [isLightContrast, isGettingReady]); 37 | }; -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | import {Draft} from 'immer'; 2 | export type ChangeAccDraftHander = (d: Draft) =>void; 3 | 4 | export interface TextAlign { 5 | left:string | null; 6 | center:string | null; 7 | right:string | null; 8 | } 9 | 10 | export interface AccessibilikState { 11 | language:string; 12 | isBlueLightFilter:boolean; 13 | brightness:{isBrightness:boolean,brightness:number}; 14 | isDarkContrast:boolean; 15 | isLightContrast:boolean; 16 | highContrast:{isHighContrast:boolean,contrast:number}; 17 | highSaturation:{isHighSaturation:boolean,saturation:number}; 18 | lowSaturation:{isLowSaturation:boolean,saturation:number}, 19 | isMonochrome:boolean, 20 | color:string, 21 | isVisualImpairment:boolean; 22 | adjustFontSizePercentage:number; 23 | textAlign:TextAlign; 24 | isDyslexiaFont:boolean; 25 | isFontWeightBold:boolean; 26 | highlightLinks:boolean; 27 | highlightTitles:boolean; 28 | letterSpacing:number; 29 | lineHeight:{isLineHeight:boolean,lineHeight:number}; 30 | wordSpacing:number; 31 | zoom:{isZoom:boolean,zoom:number}; 32 | isBigCursor:boolean; 33 | showReadingGuide:boolean; 34 | activateTextToSpeech:boolean; 35 | } 36 | 37 | export type IconSvgComponent = React.FunctionComponent & { 38 | title?: string | undefined; 39 | }> 40 | -------------------------------------------------------------------------------- /lib/components/buttons/content/AdjustFontSize/useAdjustFontSize.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const getNodesByDataAttrAndAdjustFontSize = ( 4 | dataAttr: string, 5 | percentage: number 6 | ) => { 7 | const elements = document.querySelectorAll(`[${dataAttr}]`); 8 | elements.forEach((elem) => { 9 | if (elem && elem instanceof HTMLElement && elem.dataset.accOrgfontsize) { 10 | const prevFontSize = +elem.dataset.accOrgfontsize; 11 | const newFontSize = (prevFontSize * percentage) / 100; 12 | elem.style.fontSize = `${newFontSize}px`; 13 | } 14 | }); 15 | }; 16 | 17 | export const useAdjustFontSize = ( 18 | adjustFontSizePercentage: number, 19 | nodeListUpdated: number, 20 | isGettingReady?: boolean 21 | ) => { 22 | useLayoutEffect(() => { 23 | if (!isGettingReady) { 24 | if (nodeListUpdated > 0) { 25 | getNodesByDataAttrAndAdjustFontSize( 26 | "data-acc-mutation", 27 | adjustFontSizePercentage 28 | ); 29 | } 30 | } 31 | }, [adjustFontSizePercentage, nodeListUpdated, isGettingReady]); 32 | 33 | useLayoutEffect(() => { 34 | if (!isGettingReady) { 35 | getNodesByDataAttrAndAdjustFontSize( 36 | "data-acc-orgfontsize", 37 | adjustFontSizePercentage 38 | ); 39 | } 40 | }, [adjustFontSizePercentage, isGettingReady]); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/components/buttons/AccValueControlButton/AccValueControlButton.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC } from "react"; 2 | import styles from "./AccValueControlButton.module.scss"; 3 | import RestartAltIcon from "./../../../assets/icons/init.svg?react"; 4 | import AddIcon from "./../../../assets/icons/add.svg?react"; 5 | import RemoveIcon from "./../../../assets/icons/remove.svg?react"; 6 | import { IconSvgComponent } from "../../../types"; 7 | 8 | export type ValueControlType = "init" | "increase" | "decrease"; 9 | interface AccValueControlButtonProps { 10 | controlType: ValueControlType; 11 | onClick?: () => void; 12 | style?: CSSProperties; 13 | } 14 | 15 | const AccValueControlButton: FC = ({ 16 | controlType, 17 | onClick, 18 | style, 19 | }) => { 20 | let Icon: IconSvgComponent; 21 | switch (controlType) { 22 | case "increase": 23 | Icon = AddIcon; 24 | break; 25 | case "decrease": 26 | Icon = RemoveIcon; 27 | break; 28 | case "init": 29 | Icon = RestartAltIcon; 30 | break; 31 | default: 32 | Icon = RestartAltIcon; 33 | } 34 | 35 | return ( 36 | 43 | ); 44 | }; 45 | 46 | export default AccValueControlButton; 47 | -------------------------------------------------------------------------------- /lib/components/buttons/content/AlignTextButton/AlignTextButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import FormatAlignCenterIcon from "./../../../../assets/icons/textAlign.svg?react"; 5 | 6 | import React from "react"; 7 | 8 | type Direction = "right" | "center" | "left"; 9 | 10 | interface AlignTextButtonProps { 11 | direction: Direction; 12 | accState: AccessibilikState; 13 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 14 | translationKey: string; 15 | } 16 | 17 | const AlignTextButton: FC = ({ 18 | direction, 19 | accState, 20 | onChangeAccState, 21 | translationKey, 22 | }) => { 23 | const { textAlign } = accState; 24 | const isToggled = !!textAlign[direction]; 25 | 26 | const alignHandler = () => { 27 | onChangeAccState((d) => { 28 | const prevDirection = d.textAlign[direction]; 29 | d.textAlign[direction] = !prevDirection ? direction : null; 30 | }); 31 | }; 32 | 33 | return ( 34 | 41 | ); 42 | }; 43 | 44 | export default React.memo(AlignTextButton); 45 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/components/Portal/Portal.tsx: -------------------------------------------------------------------------------- 1 | import { createPortal } from "react-dom"; 2 | import { ReactNode, useLayoutEffect, useState } from "react"; 3 | 4 | const PORTAL_ID = "portal"; 5 | 6 | function createWrapperAndAppendToBody(wrapperId: string) { 7 | const wrapperElement = document.createElement("div"); 8 | wrapperElement.setAttribute("id", wrapperId); 9 | document.body.appendChild(wrapperElement); 10 | return wrapperElement; 11 | } 12 | 13 | interface PortalProps { 14 | children: ReactNode; 15 | wrapperElementId?: string; 16 | [key: string]: unknown; 17 | } 18 | 19 | const Portal = ({ children, wrapperElementId }: PortalProps): React.ReactPortal | null => { 20 | const [wrapperElement, setWrapperElement] = useState(null); 21 | 22 | useLayoutEffect(() => { 23 | let portal = document.getElementById(wrapperElementId ?? PORTAL_ID); 24 | let systemCreated = false; 25 | 26 | if (!portal) { 27 | systemCreated = true; 28 | portal = createWrapperAndAppendToBody(wrapperElementId ?? PORTAL_ID); 29 | } 30 | 31 | setWrapperElement(portal); 32 | 33 | return () => { 34 | // delete the programmatically created element 35 | if (systemCreated && portal && portal.parentNode) { 36 | portal.parentNode.removeChild(portal); 37 | } 38 | }; 39 | }, [wrapperElementId]); 40 | 41 | if (wrapperElement === null) return null; 42 | return createPortal(children, wrapperElement); 43 | }; 44 | 45 | export default Portal; -------------------------------------------------------------------------------- /lib/index.css: -------------------------------------------------------------------------------- 1 | :root{ 2 | --primary-color: #222e8a; 3 | --highlight-color:#0048ff; 4 | --white:#fff; 5 | } 6 | 7 | .acc-readingGuide{ 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | right: 0; 12 | width: 100%; 13 | height: 0; 14 | pointer-events: none; 15 | background-color: rgba(0,0,0,.5); 16 | z-index: 1000000; 17 | } 18 | 19 | /* @font-face { 20 | font-family: 'OpenDyslexic'; 21 | src: url('OpenDyslexic-Bold.woff') format('woff'); 22 | font-weight: bold; 23 | font-style: normal; 24 | } 25 | 26 | @font-face { 27 | font-family: 'OpenDyslexic'; 28 | src: url('OpenDyslexic3-Bold.ttf') format('truetype'); 29 | font-weight: bold; 30 | font-style: normal; 31 | } 32 | 33 | @font-face { 34 | font-family: 'OpenDyslexic'; 35 | src: url('OpenDyslexic3-Regular.ttf') format('truetype'); 36 | font-weight: normal; 37 | font-style: normal; 38 | } 39 | 40 | @font-face { 41 | font-family: 'OpenDyslexic'; 42 | src: url('OpenDyslexic-Regular.woff') format('woff'); 43 | font-weight: normal; 44 | font-style: normal; 45 | } 46 | 47 | @font-face { 48 | font-family: 'OpenDyslexic'; 49 | src: url('OpenDyslexic-Regular.otf') format('opentype'); 50 | font-weight: normal; 51 | font-style: normal; 52 | } 53 | 54 | @font-face { 55 | font-family: 'OpenDyslexic'; 56 | src: url('OpenDyslexic-Bold.otf') format('opentype'); 57 | font-weight: bold; 58 | font-style: normal; 59 | } */ 60 | -------------------------------------------------------------------------------- /lib/components/buttons/content/LetterSpacingButton/useLetterSpacingButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { PORTAL_APP_ID } from "../../../../constants"; 3 | const styleID = "acc-letter-spacing-style"; 4 | const rootClass = "acc-letter-spacing"; 5 | 6 | export const useLetterSpacingButton = ( 7 | letterSpacing: number, 8 | isGettingReady?: boolean 9 | ) => { 10 | const isLetterSpacing = !!letterSpacing; 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isLetterSpacing && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${PORTAL_APP_ID} *) { 20 | letter-spacing:${letterSpacing}px !important; 21 | } 22 | `; 23 | document.head.appendChild(style); 24 | } else if (isLetterSpacing && style) { 25 | style.innerHTML = ` 26 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${PORTAL_APP_ID} *) { 27 | letter-spacing:${letterSpacing}px !important; 28 | } 29 | `; 30 | } else if (!isLetterSpacing && style) { 31 | const style = document.getElementById(styleID); 32 | document.documentElement.classList.remove(rootClass); 33 | style?.remove(); 34 | } 35 | }, [letterSpacing, isLetterSpacing, isGettingReady]); 36 | }; 37 | -------------------------------------------------------------------------------- /lib/components/buttons/content/ZoomButton/useZoomButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { PORTAL_APP_ID } from "../../../../constants"; 3 | 4 | const styleID = "acc-zoom-style"; 5 | const rootClass = "acc-zoom"; 6 | 7 | export const useZoomButton = ( 8 | zoomState: { zoom: number; isZoom: boolean }, 9 | isGettingReady?: boolean 10 | ) => { 11 | const { zoom, isZoom } = zoomState; 12 | useLayoutEffect(() => { 13 | if (isGettingReady) return; 14 | const style = document.getElementById(styleID); 15 | if (isZoom && !style) { 16 | document.documentElement.classList.add(rootClass); 17 | const style = document.createElement("style"); 18 | style.id = styleID; 19 | style.innerHTML = ` 20 | html.${rootClass} body *:not(#${PORTAL_APP_ID}, #${PORTAL_APP_ID} *) { 21 | zoom: ${zoom.toFixed(1)} !important; 22 | } 23 | } 24 | `; 25 | document.head.appendChild(style); 26 | } else if (isZoom && style) { 27 | style.innerHTML = ` 28 | html.${rootClass} body *:not(#${PORTAL_APP_ID}, #${PORTAL_APP_ID} *) { 29 | zoom: ${zoom.toFixed(1)} !important; 30 | } 31 | } 32 | `; 33 | } else if (!isZoom && style) { 34 | const style = document.getElementById(styleID); 35 | document.documentElement.classList.remove(rootClass); 36 | style?.remove(); 37 | } 38 | }, [zoom, isZoom, isGettingReady]); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/components/buttons/content/WordSpacingButton/useWordSpacingButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { APP_ID, PORTAL_APP_ID } from "../../../../constants"; 3 | 4 | const styleID = "acc-word-spacing-style"; 5 | const rootClass = "acc-word-spacing"; 6 | 7 | export const useWordSpacingButton = ( 8 | wordSpacing: number, 9 | isGettingReady?: boolean 10 | ) => { 11 | const isWordSpacing = !!wordSpacing; 12 | useLayoutEffect(() => { 13 | if (isGettingReady) return; 14 | const style = document.getElementById(styleID); 15 | if (isWordSpacing && !style) { 16 | document.documentElement.classList.add(rootClass); 17 | const style = document.createElement("style"); 18 | style.id = styleID; 19 | style.innerHTML = ` 20 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${APP_ID} *) { 21 | word-spacing:${wordSpacing}px !important; 22 | } 23 | `; 24 | document.head.appendChild(style); 25 | } else if (isWordSpacing && style) { 26 | style.innerHTML = ` 27 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${APP_ID} *) { 28 | word-spacing:${wordSpacing}px !important; 29 | } 30 | `; 31 | } else if (!isWordSpacing && style) { 32 | const style = document.getElementById(styleID); 33 | document.documentElement.classList.remove(rootClass); 34 | style?.remove(); 35 | } 36 | }, [wordSpacing, isWordSpacing, isGettingReady]); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/hooks/useFontSizeTraverse.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useState } from "react"; 2 | 3 | import { 4 | getComputedStyleAndSetAccDataFontSize, 5 | isRuleAppliedToElement, 6 | } from "../utils"; 7 | import { textTags } from "../constants"; 8 | 9 | const useFontSizeTraverse = () => { 10 | const [isTraversing, setIsTraversing] = useState(true); 11 | 12 | useLayoutEffect(() => { 13 | const allElements = document.querySelectorAll("*"); 14 | 15 | allElements.forEach((element) => { 16 | const elem = element as HTMLElement; 17 | // Check inline styles 18 | if (elem.style.fontSize) { 19 | getComputedStyleAndSetAccDataFontSize(elem); 20 | } 21 | 22 | Array.from(document.styleSheets).forEach((sheet) => { 23 | try { 24 | Array.from(sheet.cssRules || []).forEach((rule) => { 25 | const _rule = rule as CSSStyleRule; 26 | if (_rule.style.fontSize && isRuleAppliedToElement(elem, _rule)) { 27 | getComputedStyleAndSetAccDataFontSize(elem); 28 | } 29 | }); 30 | } catch (error) { 31 | // 32 | } 33 | }); 34 | 35 | //elmenet has no font size inline or stylesheet 36 | if (elem) { 37 | const tag = elem.tagName.toLowerCase(); 38 | if (textTags.includes(tag)) { 39 | getComputedStyleAndSetAccDataFontSize(elem); 40 | } 41 | } 42 | }); 43 | 44 | setIsTraversing(false); 45 | }, []); 46 | return isTraversing 47 | }; 48 | 49 | export default useFontSizeTraverse; 50 | -------------------------------------------------------------------------------- /lib/components/buttons/content/LineHeightButton/useLineHeightButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { PORTAL_APP_ID } from "../../../../constants"; 3 | 4 | const styleID = "acc-line-height-style"; 5 | const rootClass = "acc-line-height"; 6 | 7 | export const useLineHeightButton = ( 8 | lineHeightState: { lineHeight: number; isLineHeight: boolean }, 9 | isGettingReady?: boolean 10 | ) => { 11 | const { lineHeight, isLineHeight } = lineHeightState; 12 | useLayoutEffect(() => { 13 | if (isGettingReady) return; 14 | const style = document.getElementById(styleID); 15 | if (isLineHeight && !style) { 16 | document.documentElement.classList.add(rootClass); 17 | const style = document.createElement("style"); 18 | style.id = styleID; 19 | style.innerHTML = ` 20 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${PORTAL_APP_ID} *) { 21 | line-height:${lineHeight.toFixed(1)} !important 22 | } 23 | `; 24 | document.head.appendChild(style); 25 | } else if (isLineHeight && style) { 26 | style.innerHTML = ` 27 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${PORTAL_APP_ID} *) { 28 | line-height:${lineHeight.toFixed(1)} !important 29 | } 30 | `; 31 | } else if (!isLineHeight && style) { 32 | const style = document.getElementById(styleID); 33 | document.documentElement.classList.remove(rootClass); 34 | style?.remove(); 35 | } 36 | }, [lineHeight, isLineHeight, isGettingReady]); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/i18/locale/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessibility-menu": "Accessibility menu", 3 | "content": { 4 | "title": "Content adjustment", 5 | "adjustFontSize": "Adjust Font Size", 6 | "dyslexiaFont": "Dyslexia Font", 7 | "fontWeight": "Font Weight", 8 | "textAlignLeft": "Text Align Left", 9 | "textAlignCenter": "Text Align Center", 10 | "textAlignRight": "Text Align Right", 11 | "highlightLinks": "Highlight Links", 12 | "highlightTitles": "Highlight Titles", 13 | "letterSpacing": "Letter Spacing", 14 | "wordsSpacing": "Words Spacing", 15 | "lineHeight": "Line Height", 16 | "zoom": "Zoom" 17 | }, 18 | "colors": { 19 | "title": "Color adjustment", 20 | "blueLightFilter": "Blue Light Filter", 21 | "brightnessControl": "Brightness Control", 22 | "darkContrast": "Dark Contrast", 23 | "highContrast": "High Contrast", 24 | "highSaturation": "High Saturation", 25 | "lightContrast": "Light Contrast", 26 | "lowSaturation": "Low Saturation", 27 | "monochrom": "Monochrom", 28 | "textColorPicker": "Text Color Picker", 29 | "visualImpairment": "Visual Impairment" 30 | }, 31 | "tools": { 32 | "title": "Tools", 33 | "bigCursor": "Big Cursor", 34 | "readingGuide": "Reading Guide", 35 | "textToSpeach": "Text To Speach", 36 | "textToSpeachTooltip": "In order to activate 'Text To Speech', please click this button, after which you will have to select your preferred language, and then highlight the text in that language." 37 | } 38 | } -------------------------------------------------------------------------------- /lib/components/buttons/colors/HighContrastButton/useHighContrastButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-high-contrast-style"; 4 | const rootClass = "acc-high-contrast"; 5 | 6 | export const useHighContrastButton = ( 7 | isHighContrast: boolean, 8 | contrast: number, 9 | isGettingReady?: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isHighContrast && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} { 20 | -o-filter: contrast(${contrast}%) !important; 21 | -ms-filter: contrast(${contrast}%) !important; 22 | -moz-filter: contrast(${contrast}%) !important; 23 | -webkit-filter: contrast(${contrast}%) !important; 24 | filter: contrast(${contrast}%) !important; 25 | } 26 | `; 27 | document.head.appendChild(style); 28 | } else if (isHighContrast && style) { 29 | style.innerHTML = ` 30 | html.${rootClass} { 31 | -o-filter: contrast(${contrast}%) !important; 32 | -ms-filter: contrast(${contrast}%) !important; 33 | -moz-filter: contrast(${contrast}%) !important; 34 | -webkit-filter: contrast(${contrast}%) !important; 35 | filter: contrast(${contrast}%) !important; 36 | } 37 | `; 38 | } else if (!isHighContrast && style) { 39 | document.documentElement.classList.remove(rootClass); 40 | style?.remove(); 41 | } 42 | }, [isHighContrast, contrast, isGettingReady]); 43 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/LowSaturationButton/useLowSaturationButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-low-saturation-style"; 4 | const rootClass = "acc-low-saturation"; 5 | 6 | export const useLowSaturationButton = ( 7 | isLowSaturation: boolean, 8 | saturation: number, 9 | isGettingReady?: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isLowSaturation && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} { 20 | -o-filter: saturate(${saturation}%) !important; 21 | -ms-filter: saturate(${saturation}%) !important; 22 | -moz-filter: saturate(${saturation}%) !important; 23 | -webkit-filter: saturate(${saturation}%) !important; 24 | filter: saturate(${saturation}%) !important; 25 | } 26 | `; 27 | document.head.appendChild(style); 28 | } else if (isLowSaturation && style) { 29 | style.innerHTML = ` 30 | html.${rootClass} { 31 | -o-filter: saturate(${saturation}%) !important; 32 | -ms-filter: saturate(${saturation}%) !important; 33 | -moz-filter: saturate(${saturation}%) !important; 34 | -webkit-filter: saturate(${saturation}%) !important; 35 | filter: saturate(${saturation}%) !important; 36 | } 37 | `; 38 | } else if (!isLowSaturation && style) { 39 | document.documentElement.classList.remove(rootClass); 40 | style?.remove(); 41 | } 42 | }, [isLowSaturation, saturation, isGettingReady]); 43 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/BrightnessControl/useBrightnessControl.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-brightness-control-style"; 4 | const rootClass = "acc-brightness-control"; 5 | 6 | export const useBrightnessControl = ( 7 | isBrightness: boolean, 8 | brightness: number, 9 | isGettingReady?: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isBrightness && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} { 20 | -o-filter: brightness(${brightness}%) !important; 21 | -ms-filter: brightness(${brightness}%) !important; 22 | -moz-filter: brightness(${brightness}%) !important; 23 | -webkit-filter: brightness(${brightness}%) !important; 24 | filter: brightness(${brightness}%) !important; 25 | } 26 | `; 27 | document.head.appendChild(style); 28 | } else if (isBrightness && style) { 29 | style.innerHTML = ` 30 | html.${rootClass} { 31 | -o-filter: brightness(${brightness}%) !important; 32 | -ms-filter: brightness(${brightness}%) !important; 33 | -moz-filter: brightness(${brightness}%) !important; 34 | -webkit-filter: brightness(${brightness}%) !important; 35 | filter: brightness(${brightness}%) !important; 36 | } 37 | `; 38 | } else if (!isBrightness && style) { 39 | document.documentElement.classList.remove(rootClass); 40 | style?.remove(); 41 | } 42 | }, [isBrightness, brightness, isGettingReady]); 43 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/HighSaturationButton/useHighSaturationButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | 3 | const styleID = "acc-high-saturation-style"; 4 | const rootClass = "acc-high-saturation"; 5 | 6 | export const useHighSaturationButton = ( 7 | isHighSaturation: boolean, 8 | saturation: number, 9 | isGettingReady?: boolean 10 | ) => { 11 | useLayoutEffect(() => { 12 | if (isGettingReady) return; 13 | const style = document.getElementById(styleID); 14 | if (isHighSaturation && !style) { 15 | document.documentElement.classList.add(rootClass); 16 | const style = document.createElement("style"); 17 | style.id = styleID; 18 | style.innerHTML = ` 19 | html.${rootClass} { 20 | -o-filter: saturate(${saturation}%) !important; 21 | -ms-filter: saturate(${saturation}%) !important; 22 | -moz-filter: saturate(${saturation}%) !important; 23 | -webkit-filter: saturate(${saturation}%) !important; 24 | filter: saturate(${saturation}%) !important; 25 | } 26 | `; 27 | document.head.appendChild(style); 28 | } else if (isHighSaturation && style) { 29 | style.innerHTML = ` 30 | html.${rootClass} { 31 | -o-filter: saturate(${saturation}%) !important; 32 | -ms-filter: saturate(${saturation}%) !important; 33 | -moz-filter: saturate(${saturation}%) !important; 34 | -webkit-filter: saturate(${saturation}%) !important; 35 | filter: saturate(${saturation}%) !important; 36 | } 37 | `; 38 | } else if (!isHighSaturation && style) { 39 | document.documentElement.classList.remove(rootClass); 40 | style?.remove(); 41 | } 42 | }, [isHighSaturation, saturation, isGettingReady]); 43 | }; -------------------------------------------------------------------------------- /lib/components/buttons/colors/TextColorPickerButton/useTextColorPickerButton.ts: -------------------------------------------------------------------------------- 1 | import { textTags } from "../../../../constants"; 2 | import { useLayoutEffect, useMemo } from "react"; 3 | import { PORTAL_APP_ID } from "../../../../constants"; 4 | 5 | const styleID = "acc-text-color-picker-style"; 6 | const rootClass = "acc-text-color-picker"; 7 | 8 | export const useTextColorPickerButton = ( 9 | color: string, 10 | isGettingReady: boolean 11 | ) => { 12 | const textSelectors = useMemo(() => { 13 | return textTags.reduce((acc, tag, index) => { 14 | const lastIndex = textTags.length - 1; 15 | const HTML = `html.${rootClass}`; 16 | const delimiter = index === lastIndex ? "" : ","; 17 | return (acc += `${HTML} ${tag}:not(#${PORTAL_APP_ID} *)${delimiter}`); 18 | }, ""); 19 | }, []); 20 | const joinedTags = textTags 21 | .map((tag) => `${tag}:not(#${PORTAL_APP_ID} *)`) 22 | .join(","); 23 | 24 | useLayoutEffect(() => { 25 | if (isGettingReady) return; 26 | const style = document.getElementById(styleID); 27 | if (color && !style) { 28 | document.documentElement.classList.add(rootClass); 29 | const style = document.createElement("style"); 30 | style.id = styleID; 31 | style.innerHTML = ` 32 | ${textSelectors},${joinedTags} { 33 | color: ${color} !important; 34 | } 35 | `; 36 | document.head.appendChild(style); 37 | } else if (color && style) { 38 | style.innerHTML = ` 39 | ${textSelectors},${joinedTags} { 40 | color: ${color} !important; 41 | } 42 | `; 43 | } else if (!color && style) { 44 | document.documentElement.classList.remove(rootClass); 45 | style?.remove(); 46 | } 47 | }, [color, joinedTags, textSelectors, isGettingReady]); 48 | }; 49 | -------------------------------------------------------------------------------- /lib/components/buttons/content/AdjustFontSize/AdjustFontSize.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import TextIncreaseIcon from "./../../../../assets/icons/adjustFontSize.svg?react"; 5 | import AccValueControl from "../../AccValueControl/AccValueControl"; 6 | 7 | interface AdjustFontSizeProps { 8 | accState: AccessibilikState; 9 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 10 | } 11 | 12 | const AdjustFontSize: FC = ({ 13 | accState, 14 | onChangeAccState, 15 | }) => { 16 | const { adjustFontSizePercentage } = accState; 17 | 18 | const increaseFontSizeHandler = () => { 19 | onChangeAccState((draft) => { 20 | const { adjustFontSizePercentage } = draft; 21 | if (adjustFontSizePercentage < 200) { 22 | draft.adjustFontSizePercentage += 10; 23 | } 24 | }); 25 | }; 26 | const decreaseFontSizeHandler = () => { 27 | onChangeAccState((draft) => { 28 | const { adjustFontSizePercentage } = draft; 29 | if (adjustFontSizePercentage > 10) { 30 | draft.adjustFontSizePercentage -= 10; 31 | } 32 | }); 33 | }; 34 | const initFontSizeHandler = () => { 35 | onChangeAccState((draft) => { 36 | draft.adjustFontSizePercentage = 100; 37 | }); 38 | }; 39 | 40 | return ( 41 | 48 | 53 | 54 | ); 55 | }; 56 | 57 | export default AdjustFontSize; 58 | -------------------------------------------------------------------------------- /lib/components/buttons/content/DyslexiaFontButton/style.ts: -------------------------------------------------------------------------------- 1 | const DYSLEXIA_FONT_STYLE = ` 2 | @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@300;400;700&display=swap'); 3 | 4 | @font-face { 5 | font-family: 'OpenDyslexic'; 6 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic-Bold.woff') format('woff'); 7 | font-weight: bold; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'OpenDyslexic'; 13 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic3-Bold.ttf') format('truetype'); 14 | font-weight: bold; 15 | font-style: normal; 16 | } 17 | 18 | @font-face { 19 | font-family: 'OpenDyslexic'; 20 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic3-Regular.ttf') format('truetype'); 21 | font-weight: normal; 22 | font-style: normal; 23 | } 24 | 25 | @font-face { 26 | font-family: 'OpenDyslexic'; 27 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic-Regular.woff') format('woff'); 28 | font-weight: normal; 29 | font-style: normal; 30 | } 31 | 32 | @font-face { 33 | font-family: 'OpenDyslexic'; 34 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic-Regular.otf') format('opentype'); 35 | font-weight: normal; 36 | font-style: normal; 37 | } 38 | 39 | @font-face { 40 | font-family: 'OpenDyslexic'; 41 | src: url('https://acc-landing.vercel.app/fonts/OpenDyslexic-Bold.otf') format('opentype'); 42 | font-weight: bold; 43 | font-style: normal; 44 | } 45 | 46 | 47 | html{ 48 | font-family: OpenDyslexic,Comic Neue,Arial,Helvetica,sans-serif !important 49 | 50 | } 51 | 52 | html *, *{ 53 | font-family: OpenDyslexic,Comic Neue,Arial,Helvetica,sans-serif !important 54 | } 55 | 56 | html.acc-font-weight{ 57 | font-family: OpenDyslexic,Comic Neue,Arial,Helvetica,sans-serif !important 58 | } 59 | 60 | 61 | `; 62 | 63 | export default DYSLEXIA_FONT_STYLE; 64 | -------------------------------------------------------------------------------- /lib/components/buttons/content/ZoomButton/ZoomButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import styled from "./ZoomButton.module.scss"; 3 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 4 | import ZoomInIcon from "./../../../../assets/icons/zoom.svg?react"; 5 | import AccButton from "../../AccButton/AccButton"; 6 | import AccValueControlButton from "../../AccValueControlButton/AccValueControlButton"; 7 | 8 | interface ZoomButtonProps { 9 | accState: AccessibilikState; 10 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 11 | } 12 | 13 | const ZoomButton: FC = ({ accState, onChangeAccState }) => { 14 | const { zoom } = accState.zoom; 15 | const increaseZoomHandler = () => { 16 | onChangeAccState((draft) => { 17 | draft.zoom.isZoom = true; 18 | draft.zoom.zoom += 0.1; 19 | }); 20 | }; 21 | const decreaseZoomHandler = () => { 22 | onChangeAccState((draft) => { 23 | if (draft.zoom.zoom > 0.1) { 24 | draft.zoom.isZoom = true; 25 | draft.zoom.zoom -= 0.1; 26 | } 27 | }); 28 | }; 29 | const zoomInitHandler = () => { 30 | onChangeAccState((draft) => { 31 | draft.zoom.isZoom = false; 32 | draft.zoom.zoom = 1; 33 | }); 34 | }; 35 | 36 | return ( 37 | 44 |
45 | 49 | 50 | 54 |
55 |
56 | ); 57 | }; 58 | 59 | export default ZoomButton; 60 | -------------------------------------------------------------------------------- /lib/components/AccMenuContentBlock/AccMenuContentBlock.module.scss: -------------------------------------------------------------------------------- 1 | .accMenuContentBlock { 2 | border: 0.3px solid #2c2639; 3 | display: flex; 4 | align-items: center; 5 | flex-direction: column; 6 | max-height: calc(100vh / 3 - 83px); 7 | justify-content: center; 8 | flex-grow: 1; 9 | transition: max-height 0.3s linear; 10 | padding: 10px 5px 5px 5px; 11 | position: relative; 12 | cursor: pointer; 13 | overflow: hidden; 14 | z-index: 10; 15 | &:focus { 16 | border: 1px solid #2c2639; 17 | } 18 | &.isExpanded { 19 | cursor: auto; 20 | flex: 1; 21 | max-height: 100%; 22 | border: none; 23 | 24 | // max-height: calc(100vh / 3 + 250px ); 25 | } 26 | &.isAccMenuContentActive:not(.isExpanded) { 27 | display: none; 28 | } 29 | &__titleContainer { 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | svg { 34 | font-weight: bold; 35 | width: 30px; 36 | fill: #000; 37 | } 38 | } 39 | &__title { 40 | font-size: 20px; 41 | font-weight: bold; 42 | color: #000; 43 | text-transform: capitalize; 44 | margin: 0; 45 | padding: 0.2em; 46 | } 47 | &__expendButtonContainer { 48 | width: 100%; 49 | display: flex; 50 | margin-bottom: 10px; 51 | button { 52 | font-size: 30px; 53 | width: 80%; 54 | 55 | margin: auto; 56 | inset-inline-start: 5px; 57 | height: 100%; 58 | 59 | border: none; 60 | outline: none; 61 | background-color: var(--primary-color); 62 | border-radius: 10px; 63 | & > svg { 64 | fill: white; 65 | width: 24px; 66 | } 67 | &:hover { 68 | opacity: 0.8; 69 | } 70 | } 71 | } 72 | &__content { 73 | display: grid; 74 | flex: 1; 75 | grid-template-columns: repeat(3, 1fr); 76 | grid-auto-rows: max-content; 77 | column-gap: 0.8em; 78 | overflow: auto; 79 | row-gap: 1.5em; 80 | align-self: baseline; 81 | width: 100%; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/components/buttons/content/AlignTextButton/useAlignTextButton.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { PORTAL_APP_ID } from "../../../../constants"; 3 | import { TextAlign } from "../../../../types"; 4 | 5 | const rootClass = "acc-align-text"; 6 | 7 | export const useAlignTextButton = ( 8 | textAlign: TextAlign, 9 | isGettingReady?: boolean 10 | ) => { 11 | const currentDirection = Object.values(textAlign).find((v) => v); 12 | 13 | useLayoutEffect(() => { 14 | if (isGettingReady) return; 15 | if (currentDirection) { 16 | const style = document.getElementById( 17 | `acc-align-text-style-${currentDirection}` 18 | ); 19 | if (!style) { 20 | document.documentElement.classList.add(rootClass); 21 | const style = document.createElement("style"); 22 | style.id = `acc-align-text-style-${currentDirection}`; 23 | style.innerHTML = ` 24 | html.${rootClass} *:not(#${PORTAL_APP_ID} *), *:not(#${PORTAL_APP_ID} *) { 25 | text-align:${currentDirection} !important 26 | } 27 | `; 28 | document.head.appendChild(style); 29 | } 30 | } 31 | }, [currentDirection, isGettingReady, textAlign]); 32 | 33 | useLayoutEffect(() => { 34 | if (isGettingReady) return; 35 | if (!textAlign.left) { 36 | const style = document.getElementById("acc-align-text-style-left"); 37 | if (style) { 38 | style.remove(); 39 | document.documentElement.classList.remove(rootClass); 40 | } 41 | } 42 | if (!textAlign.right) { 43 | const style = document.getElementById("acc-align-text-style-right"); 44 | if (style) { 45 | style.remove(); 46 | document.documentElement.classList.remove(rootClass); 47 | } 48 | } 49 | if (!textAlign.center) { 50 | const style = document.getElementById("acc-align-text-style-center"); 51 | if (style) { 52 | style.remove(); 53 | document.documentElement.classList.remove(rootClass); 54 | } 55 | } 56 | }, [textAlign]); 57 | }; 58 | -------------------------------------------------------------------------------- /lib/components/buttons/content/WordSpacingButton/WordSpacingButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import MenuBookIcon from "./../../../../assets/icons/wordSpacing.svg?react"; 5 | import AccValueControl from "../../AccValueControl/AccValueControl"; 6 | 7 | interface WordSpacingButtonProps { 8 | accState: AccessibilikState; 9 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 10 | } 11 | 12 | const WordSpacingButton: FC = ({ 13 | accState, 14 | onChangeAccState, 15 | }) => { 16 | const { wordSpacing } = accState; 17 | const isWordSpacing = !!wordSpacing; 18 | const increaseWordSpacingHandler = () => { 19 | onChangeAccState((draft) => { 20 | draft.wordSpacing++; 21 | }); 22 | }; 23 | const decreaseWordSpacingHandler = () => { 24 | onChangeAccState((draft) => { 25 | if (draft.wordSpacing > 0) { 26 | draft.wordSpacing--; 27 | } 28 | }); 29 | }; 30 | const toggleWordSpacingHandler = () => { 31 | onChangeAccState((draft) => { 32 | const { wordSpacing } = draft; 33 | draft.wordSpacing = !wordSpacing ? 1 : 0; 34 | }); 35 | }; 36 | 37 | const renderControlButtons = () => { 38 | if (!isWordSpacing) return null; 39 | return ( 40 | 45 | ); 46 | }; 47 | 48 | return ( 49 | 58 | {renderControlButtons()} 59 | 60 | ); 61 | }; 62 | 63 | export default WordSpacingButton; 64 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/TextColorPickerButton/TextColorPickerButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { HexColorPicker } from "react-colorful"; 3 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 4 | import ColorPickIcon from "./../../../../assets/icons/platte.svg?react"; 5 | import AccButton from "../../AccButton/AccButton"; 6 | import AccValueControlButton from "../../AccValueControlButton/AccValueControlButton"; 7 | import styled from "./TextColorPickerButton.module.scss"; 8 | 9 | interface TextColorPickerButtonProps { 10 | accState: AccessibilikState; 11 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 12 | } 13 | 14 | const TextColorPickerButton: FC = ({ 15 | accState, 16 | onChangeAccState, 17 | }) => { 18 | const { color } = accState; 19 | 20 | const handleColorChange = (value: string) => { 21 | onChangeAccState((draft) => { 22 | draft.color = value; 23 | }); 24 | }; 25 | 26 | const initColorPickerHandler = () => { 27 | handleColorChange(""); 28 | }; 29 | 30 | return ( 31 | 38 |
39 |
40 | 44 | handleColorChange(e.target.value)} 50 | /> 51 |
52 | 57 |
58 |
59 | ); 60 | }; 61 | 62 | export default TextColorPickerButton; 63 | -------------------------------------------------------------------------------- /lib/components/buttons/content/LetterSpacingButton/LetterSpacingButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import EightMpIcon from "./../../../../assets/icons/letterSpacing.svg?react"; 5 | import AccValueControl from "../../AccValueControl/AccValueControl"; 6 | 7 | interface LetterSpacingButtonProps { 8 | accState: AccessibilikState; 9 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 10 | } 11 | 12 | const LetterSpacingButton: FC = ({ 13 | accState, 14 | onChangeAccState, 15 | }) => { 16 | const { letterSpacing } = accState; 17 | const isLetterSpacing = !!letterSpacing; 18 | 19 | const increaseLetterSpacingHandler = () => { 20 | onChangeAccState((draft) => { 21 | draft.letterSpacing++; 22 | }); 23 | }; 24 | const decreaseLetterSpacingHandler = () => { 25 | onChangeAccState((draft) => { 26 | if (draft.letterSpacing > 0) { 27 | draft.letterSpacing--; 28 | } 29 | }); 30 | }; 31 | const toggleLetterSpacingHandler = () => { 32 | onChangeAccState((draft) => { 33 | const { letterSpacing } = draft; 34 | draft.letterSpacing = !letterSpacing ? 1 : 0; 35 | }); 36 | }; 37 | 38 | const renderControlButtons = () => { 39 | if (!isLetterSpacing) return null; 40 | return ( 41 | 46 | ); 47 | }; 48 | 49 | return ( 50 | 59 | {renderControlButtons()} 60 | 61 | ); 62 | }; 63 | 64 | export default LetterSpacingButton; 65 | -------------------------------------------------------------------------------- /lib/components/AccMenuContentBlock/AccMenuContentBlock.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | import classNames from "classnames"; 3 | import { useTranslation } from "react-i18next"; 4 | import { IconSvgComponent } from "../../types"; 5 | import ExpandIcon from "./../../assets/icons/expand.svg?react"; 6 | import styles from "./AccMenuContentBlock.module.scss"; 7 | import { CollapsedStateKeys } from "../../config"; 8 | 9 | interface AccMenuContentBlockProps { 10 | children: ReactNode; 11 | name: CollapsedStateKeys; 12 | onCollapse: (name: CollapsedStateKeys) => void; 13 | isExpanded: boolean; 14 | isAccMenuContentActive: boolean; 15 | Icon: IconSvgComponent; 16 | tKey: string; 17 | } 18 | const AccMenuContentBlock: FC = ({ 19 | children, 20 | name, 21 | onCollapse, 22 | isExpanded, 23 | Icon, 24 | tKey, 25 | isAccMenuContentActive, 26 | }) => { 27 | const { t } = useTranslation(); 28 | const classes = classNames(styles.accMenuContentBlock, { 29 | [styles.isExpanded]: isExpanded, 30 | [styles.isAccMenuContentActive]: isAccMenuContentActive, 31 | }); 32 | const expandBlockHandler = () => { 33 | onCollapse(name); 34 | }; 35 | const role = !isExpanded ? "button" : undefined; 36 | const tabIndex = !isExpanded ? 0 : undefined; 37 | 38 | return ( 39 |
45 | {isExpanded && ( 46 |
47 | 50 |
51 | )} 52 | {!isExpanded && ( 53 |
54 | 55 |

{t(tKey)}

56 |
57 | )} 58 | {isExpanded && ( 59 |
{children}
60 | )} 61 |
62 | ); 63 | }; 64 | 65 | export default AccMenuContentBlock; 66 | -------------------------------------------------------------------------------- /lib/components/buttons/AccButton/AccButton.module.scss: -------------------------------------------------------------------------------- 1 | .accButton { 2 | aspect-ratio: 6/5; 3 | border-radius: 4px; 4 | display: flex; 5 | flex-direction: column; 6 | padding: 5px; 7 | color: #000; 8 | background: #fff; 9 | border: 1px solid #ccc; 10 | transition: all 0.5s ease; 11 | position: relative; 12 | cursor: pointer; 13 | &:hover, 14 | &:focus { 15 | border-color: var(--primary-color); 16 | outline: none; 17 | } 18 | &.isToggled { 19 | background-color: var(--primary-color); 20 | border-color: var(--primary-color); 21 | color: #fff; 22 | svg { 23 | fill: #fff; 24 | } 25 | } 26 | &.isActive{ 27 | border-color: var(--primary-color); 28 | } 29 | &__title { 30 | font-size: 15px; 31 | font-weight: 400; 32 | text-align: center; 33 | width: 100%; 34 | color: inherit; 35 | margin: auto; 36 | margin-top: 10px; 37 | } 38 | &__icon { 39 | width: 24px; 40 | margin-bottom: 8px; 41 | &--help { 42 | position: absolute; 43 | inset-inline-end: 0; 44 | &:hover{ 45 | fill: red !important; 46 | } 47 | &:hover + span[data-tooltip]:after { 48 | opacity: 1; 49 | padding: 8px; 50 | max-height: 10000px; 51 | transition-duration: 300ms; 52 | } 53 | } 54 | } 55 | &__content { 56 | align-self: flex-end; 57 | width: 100%; 58 | margin-top: auto; 59 | flex: 0.8; 60 | } 61 | &__stats { 62 | color: var(--primary-color); 63 | font-weight: bold; 64 | position: absolute; 65 | align-self: flex-end; 66 | font-size: 14px; 67 | } 68 | span[data-tooltip] { 69 | position: absolute; 70 | left: 0; 71 | right: 0; 72 | bottom: 0; 73 | cursor: help; 74 | &:after { 75 | position: absolute; 76 | opacity: 0; 77 | pointer-events: none; 78 | content: attr(data-tooltip); 79 | color: var(--primary-color); 80 | left: 0; 81 | right: 0; 82 | bottom: 0; 83 | margin: auto; 84 | line-height: 1.5; 85 | border-radius: 3px; 86 | box-shadow: 0 0 5px 2px rgba(100, 100, 100, 0.6); 87 | background-color: white; 88 | z-index: 10; 89 | word-wrap: break-word; 90 | text-align: start; 91 | transform: translateY(100%); 92 | transition: all 150ms cubic-bezier(0.25, 0.8, 0.25, 1); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { AccessibilikState } from "./types"; 3 | 4 | export const isRuleAppliedToElement = ( 5 | element: HTMLElement, 6 | rule: CSSStyleRule 7 | ): boolean => { 8 | const matchingElements = document.querySelectorAll(rule.selectorText); 9 | return Array.from(matchingElements).includes(element); 10 | }; 11 | 12 | export const getComputedStyleAndSetAccDataFontSize = (elem: HTMLElement) => { 13 | const { fontSize } = window.getComputedStyle(elem); 14 | elem.dataset.accOrgfontsize = parseFloat(fontSize).toString(); 15 | elem.style.fontSize = fontSize; 16 | }; 17 | 18 | export const getDataImageSvgBase64 = (svg: string) => 19 | `data:image/svg+xml;base64,${window.btoa(svg)}`; 20 | 21 | export const getAccInitState = ():AccessibilikState => { 22 | return { 23 | language: localStorage.getItem('i18nextLng') ?? i18n.language, 24 | isBlueLightFilter:false, 25 | brightness:{isBrightness:false,brightness:150}, 26 | isDarkContrast:false, 27 | isLightContrast:false, 28 | highContrast:{isHighContrast:false,contrast:125}, 29 | highSaturation:{isHighSaturation:false,saturation:200}, 30 | lowSaturation:{isLowSaturation:false,saturation:50}, 31 | isMonochrome:false, 32 | color:"", 33 | isVisualImpairment:false, 34 | adjustFontSizePercentage:100, 35 | textAlign:{left:null,center:null,right:null}, 36 | isDyslexiaFont:false, 37 | isFontWeightBold:false, 38 | highlightLinks:false, 39 | highlightTitles:false, 40 | letterSpacing:0, 41 | lineHeight:{isLineHeight:false,lineHeight:0}, 42 | wordSpacing:0, 43 | zoom:{isZoom:false,zoom:1}, 44 | isBigCursor:false, 45 | showReadingGuide:false, 46 | activateTextToSpeech:false 47 | } 48 | } 49 | 50 | export const registerDomain = async () => { 51 | try { 52 | const data = { 53 | domain: window.location.hostname, 54 | created: new Date().toISOString(), 55 | }; 56 | 57 | const response = await fetch("https://acc-landing.vercel.app/api/registerDomain", { 58 | method: "POST", 59 | headers: { 60 | "Content-Type": "application/json", 61 | }, 62 | body: JSON.stringify(data), 63 | }); 64 | 65 | const resData = await response.json(); 66 | console.log(resData); 67 | } catch (err) { 68 | //err 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /lib/components/AccMenuContent/AccMenuContent.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import AccMenuContentBlock from "../AccMenuContentBlock/AccMenuContentBlock"; 3 | import AccContent from "../AccContent/AccContent"; 4 | import AccColors from "../AccColors/AccColors"; 5 | import AccTools from "../AccTools/AccTools"; 6 | import { AccessibilikState, ChangeAccDraftHander } from "../../types"; 7 | import styled from "./AccMenuContent.module.scss"; 8 | import { CollapsedState, CollapsedStateKeys } from "../../config"; 9 | 10 | interface AccMenuContentProps { 11 | accState: AccessibilikState; 12 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 13 | onCollapse: (name: CollapsedStateKeys) => void; 14 | collapsedState: CollapsedState; 15 | } 16 | const AccMenuContent: FC = ({ 17 | accState, 18 | onChangeAccState, 19 | onCollapse, 20 | collapsedState, 21 | }) => { 22 | const isAccMenuContentNotActive = Object.values(collapsedState).every( 23 | ({ isExpanded }) => !isExpanded 24 | ); 25 | 26 | return ( 27 |
28 | 36 | 37 | 38 | 46 | 47 | 48 | 56 | 57 | 58 |
59 | ); 60 | }; 61 | 62 | export default AccMenuContent; 63 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import react from "@vitejs/plugin-react"; 4 | import dts from "vite-plugin-dts"; 5 | import { libInjectCss } from "vite-plugin-lib-inject-css"; 6 | import svgr from "vite-plugin-svgr"; 7 | import { terser } from "rollup-plugin-terser"; 8 | 9 | // import { visualizer } from 'rollup-plugin-visualizer'; 10 | 11 | // export default defineConfig({ 12 | // plugins: [ 13 | // react(), 14 | // svgr(), 15 | // // terser({ 16 | // // format: { 17 | // // comments: false, 18 | // // beautify: false, 19 | // // }, 20 | // // compress: { 21 | // // passes: 2, // More passes over the code for compression 22 | // // drop_console: true, // Remove console statements 23 | // // sequences: true, // Join consecutive simple statements using the “comma operator” 24 | // // }, 25 | // // mangle: true, // Shorten variable names 26 | // // }), 27 | // ], 28 | // build: { 29 | // lib: { 30 | // entry: resolve(__dirname, "lib/main.tsx"), 31 | // name: "accessibilik", 32 | // formats: ["umd"], 33 | // }, 34 | // rollupOptions: { 35 | // external: ["react", "react-dom"], 36 | // output: { 37 | // // Provide global variables to use in the UMD build 38 | // globals: { 39 | // react: "React", 40 | // "react-dom": "ReactDOM", 41 | // }, 42 | // }, 43 | // }, 44 | // }, 45 | // }); 46 | 47 | export default defineConfig({ 48 | plugins: [ 49 | react(), 50 | svgr(), 51 | libInjectCss(), 52 | dts({ include: ["lib"], copyDtsFiles: true }), 53 | terser({ 54 | format: { 55 | comments: false, 56 | beautify: false, 57 | }, 58 | compress: { 59 | passes: 2, // More passes over the code for compression 60 | drop_console: true, // Remove console statements 61 | sequences: true, // Join consecutive simple statements using the “comma operator” 62 | }, 63 | mangle: true, // Shorten variable names 64 | }), 65 | ], 66 | build: { 67 | lib: { 68 | entry: resolve(__dirname, "lib/main.ts"), 69 | formats: ["es"], 70 | }, 71 | copyPublicDir: false, 72 | rollupOptions: { 73 | external: ["react", "react-dom"], 74 | output: { 75 | assetFileNames: "assets/[name][extname]", 76 | entryFileNames: "[name].js", 77 | globals: { 78 | react: "React", 79 | "react-dom": "ReactDOM", 80 | }, 81 | }, 82 | }, 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /lib/components/buttons/tools/ReadingGuide/useReadingGuide.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useState } from "react"; 2 | const READING_GUIDE_PORTAL_ID = "acc-portal-[readingGuide-container]"; 3 | 4 | const renderReadingGuide = (mouseY: number, height: string | 0) => { 5 | const readingGuideContainer = document.getElementById( 6 | READING_GUIDE_PORTAL_ID 7 | )!; 8 | 9 | let readingGuideTop = document.getElementById("acc-readingGuide-top"); 10 | if (!readingGuideTop) { 11 | readingGuideTop = document.createElement("div"); 12 | readingGuideTop.id = "acc-readingGuide-top"; 13 | readingGuideTop.className = "acc-readingGuide"; 14 | readingGuideTop.style.height = `${mouseY}px`; 15 | readingGuideContainer.appendChild(readingGuideTop); 16 | } 17 | readingGuideTop.style.height = `${mouseY}px`; 18 | 19 | let readingGuideBottom = document.getElementById("acc-readingGuide-bottom"); 20 | if (!readingGuideBottom) { 21 | readingGuideBottom = document.createElement("div"); 22 | readingGuideBottom.id = "acc-readingGuide-bottom"; 23 | readingGuideBottom.className = "acc-readingGuide"; 24 | readingGuideBottom.style.top = "auto"; 25 | readingGuideBottom.style.bottom = "0"; 26 | readingGuideBottom.style.height = `${height}`; 27 | readingGuideContainer.appendChild(readingGuideBottom); 28 | } 29 | readingGuideBottom.style.height = `${height}`; 30 | }; 31 | 32 | export const useReadingGuide = ( 33 | showReadingGuide: boolean, 34 | rgGap: number, 35 | isGettingReady?: boolean 36 | ) => { 37 | const [mouseY, setMouseY] = useState(0); 38 | const height = useMemo(() => { 39 | if (mouseY > 0) { 40 | return `calc(100vh - ${mouseY}px - ${rgGap}px)`; 41 | } 42 | return 0; 43 | }, [mouseY, rgGap]); 44 | 45 | useEffect(() => { 46 | if (isGettingReady) return; 47 | if (showReadingGuide) { 48 | const handleMouseMove = (event: MouseEvent) => { 49 | setMouseY(event.clientY); 50 | }; 51 | renderReadingGuide(mouseY, height); 52 | window.addEventListener("mousemove", handleMouseMove); 53 | return () => { 54 | window.removeEventListener("mousemove", handleMouseMove); 55 | }; 56 | } else { 57 | const readingGuideTop = document.getElementById("acc-readingGuide-top"); 58 | if (readingGuideTop) { 59 | readingGuideTop.remove(); 60 | } 61 | const readingGuideBottom = document.getElementById( 62 | "acc-readingGuide-bottom" 63 | ); 64 | if (readingGuideBottom) { 65 | readingGuideBottom.remove(); 66 | } 67 | } 68 | }, [showReadingGuide, isGettingReady, mouseY, height]); 69 | }; 70 | -------------------------------------------------------------------------------- /lib/components/AccColors/AccColors.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../types"; 3 | import BlueLightFilterButton from "../buttons/colors/BlueLightFilterButton/BlueLightFilterButton"; 4 | import BrightnessControl from "../buttons/colors/BrightnessControl/BrightnessControl"; 5 | import DarkContrastButton from "../buttons/colors/DarkContrastButton/DarkContrastButton"; 6 | import HighContrastButton from "../buttons/colors/HighContrastButton/HighContrastButton"; 7 | import HighSaturationButton from "../buttons/colors/HighSaturationButton/HighSaturationButton"; 8 | import LightContrastButton from "../buttons/colors/LightContrastButton/LightContrastButton"; 9 | import LowSaturationButton from "../buttons/colors/LowSaturationButton/LowSaturationButton"; 10 | import MonochromeButton from "../buttons/colors/MonochromeButton/MonochromeButton"; 11 | import TextColorPickerButton from "../buttons/colors/TextColorPickerButton/TextColorPickerButton"; 12 | import VisualImpairmentButton from "../buttons/colors/VisualImpairmentButton/VisualImpairmentButton"; 13 | 14 | interface AccColorsProps { 15 | accState: AccessibilikState; 16 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 17 | } 18 | 19 | const AccColors: FC = ({ accState, onChangeAccState }) => { 20 | return ( 21 | <> 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 55 | 59 | 63 | 64 | ); 65 | }; 66 | export default AccColors; 67 | -------------------------------------------------------------------------------- /lib/components/buttons/content/LineHeightButton/LineHeightButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import styled from "./LineHeightButton.module.scss"; 3 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 4 | import AccButton from "../../AccButton/AccButton"; 5 | import TextRotateUpIcon from "./../../../../assets/icons/lineHeight.svg?react"; 6 | import AccValueControlButton from "../../AccValueControlButton/AccValueControlButton"; 7 | 8 | interface LineHeightButtonProps { 9 | accState: AccessibilikState; 10 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 11 | } 12 | 13 | const LineHeightButton: FC = ({ 14 | accState, 15 | onChangeAccState, 16 | }) => { 17 | const { lineHeight, isLineHeight } = accState.lineHeight; 18 | 19 | const increaseLineHeightHandler = () => { 20 | onChangeAccState((draft) => { 21 | draft.lineHeight.lineHeight += 0.1; 22 | }); 23 | }; 24 | const decreaseLineHeightHandler = () => { 25 | onChangeAccState((draft) => { 26 | if (draft.lineHeight.lineHeight > 0.1) { 27 | draft.lineHeight.lineHeight -= 0.1; 28 | } 29 | }); 30 | }; 31 | const lineHeighToggleHandler = () => { 32 | onChangeAccState((draft) => { 33 | const isActive = !draft.lineHeight.isLineHeight; 34 | draft.lineHeight.isLineHeight = isActive; 35 | draft.lineHeight.lineHeight = isActive ? 3 : 0; 36 | }); 37 | }; 38 | 39 | const renderControlButtons = () => { 40 | if (!isLineHeight) return null; 41 | return ( 42 |
43 | {isLineHeight && ( 44 | 48 | )} 49 | 53 | {isLineHeight && ( 54 | 58 | )} 59 |
60 | ); 61 | }; 62 | 63 | return ( 64 | 73 | {renderControlButtons()} 74 | 75 | ); 76 | }; 77 | 78 | export default LineHeightButton; 79 | -------------------------------------------------------------------------------- /lib/hooks/useFontSizeMutationObserver.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useState } from "react"; 2 | import { 3 | getComputedStyleAndSetAccDataFontSize, 4 | isRuleAppliedToElement, 5 | } from "../utils"; 6 | import { APP_ID, PORTAL_APP_ID, textTags } from "../constants"; 7 | 8 | const useFontSizeMutationObserver = () => { 9 | const [nodeListUpdated,setNodeListUpdated] = useState(0); 10 | useLayoutEffect(() => { 11 | const observer = new MutationObserver((mutations) => { 12 | mutations.forEach((mutation) => { 13 | if (mutation.type === "childList" && mutation.addedNodes.length > 0) { 14 | 15 | mutation.addedNodes.forEach((node) => { 16 | if (node instanceof HTMLElement) { 17 | if(node.id === PORTAL_APP_ID || node.id === APP_ID) return; 18 | 19 | // handle inline font size 20 | if (node.style.fontSize) { 21 | getComputedStyleAndSetAccDataFontSize(node); 22 | node.dataset.accMutation = `true`; 23 | setNodeListUpdated(p => ++p); 24 | } 25 | // handle font size from css files 26 | Array.from(document.styleSheets).forEach((sheet) => { 27 | try { 28 | Array.from(sheet.cssRules || []).forEach((rule) => { 29 | const _rule = rule as CSSStyleRule; 30 | if ( 31 | _rule.style.fontSize && 32 | isRuleAppliedToElement(node, _rule) 33 | ) { 34 | getComputedStyleAndSetAccDataFontSize(node); 35 | node.dataset.accMutation = `true`; 36 | setNodeListUpdated(p => ++p); 37 | } 38 | }); 39 | } catch (error) { 40 | // 41 | } 42 | }); 43 | // handle textTags that the font size was not defined 44 | if (node) { 45 | const tag = node.tagName.toLowerCase(); 46 | if (textTags.includes(tag)) { 47 | getComputedStyleAndSetAccDataFontSize(node); 48 | node.dataset.accMutation = `true`; 49 | setNodeListUpdated(p => ++p); 50 | } 51 | } 52 | } 53 | }); 54 | } 55 | }); 56 | }); 57 | 58 | 59 | // Start observing 60 | observer.observe(document.body, { childList: true, subtree: true }); 61 | 62 | // Clean up 63 | return () => { 64 | setNodeListUpdated(0) 65 | observer.disconnect(); 66 | }; 67 | }, [nodeListUpdated]); 68 | 69 | return nodeListUpdated 70 | }; 71 | 72 | export default useFontSizeMutationObserver; 73 | -------------------------------------------------------------------------------- /lib/components/buttons/colors/HighContrastButton/HighContrastButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { AccessibilikState, ChangeAccDraftHander } from "../../../../types"; 3 | import AccButton from "../../AccButton/AccButton"; 4 | import HighContrastIcon from "./../../../../assets/icons/highcontrast.svg?react"; 5 | import RcSlider from "../../../RcSlider/RcSlider"; 6 | import AccValueControl from "../../AccValueControl/AccValueControl"; 7 | 8 | interface HighContrastButtonProps { 9 | accState: AccessibilikState; 10 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 11 | } 12 | 13 | const HighContrastButton: FC = ({ 14 | accState, 15 | onChangeAccState, 16 | }) => { 17 | const { isHighContrast, contrast } = accState.highContrast; 18 | 19 | const increaseHighContrastHandler = () => { 20 | onChangeAccState((draft) => { 21 | const { highContrast } = draft; 22 | if (highContrast.contrast < 200) { 23 | draft.highContrast.contrast++; 24 | } 25 | }); 26 | }; 27 | const decreaseHighContrastHandler = () => { 28 | onChangeAccState((draft) => { 29 | const { highContrast } = draft; 30 | if (highContrast.contrast > 100) { 31 | draft.highContrast.contrast--; 32 | } 33 | }); 34 | }; 35 | const toggleHighContrastHandler = () => { 36 | onChangeAccState((draft) => { 37 | const isActive = !draft.highContrast.isHighContrast; 38 | draft.highContrast.isHighContrast = isActive; 39 | draft.highContrast.contrast = isActive ? 125 : 0; 40 | }); 41 | }; 42 | 43 | const renderControlButtons = () => { 44 | if (!isHighContrast) return null; 45 | return ( 46 | 51 | ); 52 | }; 53 | 54 | return ( 55 | 64 | {renderControlButtons()} 65 | 66 | {isHighContrast && ( 67 | { 73 | onChangeAccState((draft) => { 74 | draft.highContrast.contrast = e as number; 75 | }); 76 | }} 77 | /> 78 | )} 79 | 80 | ); 81 | }; 82 | 83 | export default HighContrastButton; 84 | -------------------------------------------------------------------------------- /lib/i18/locale/index.ts: -------------------------------------------------------------------------------- 1 | export const baseUrl = 'https://acc-landing.vercel.app/locale/'; 2 | 3 | export const loadJson = async (url:string) => { 4 | const response = await fetch(url); 5 | return response.json(); 6 | } 7 | export type Translation = { 8 | translation:string; 9 | } 10 | export type Resources = Record 11 | 12 | export const languageArray = [ 13 | { lang: 'he', name: 'hebrew' }, 14 | { lang: 'en', name: 'english' }, 15 | { lang: 'ru', name: 'russian' }, 16 | { lang: 'zhcn', name: 'chineseMandarin' }, 17 | { lang: 'es', name: 'spanish' }, 18 | { lang: 'ar', name: 'arabic' }, 19 | { lang: 'bn', name: 'bengali' }, 20 | { lang: 'hi', name: 'hindi' }, 21 | { lang: 'ptpt', name: 'portuguese' }, 22 | { lang: 'ja', name: 'japanese' }, 23 | { lang: 'de', name: 'german' }, 24 | { lang: 'wuu', name: 'chinese' }, 25 | { lang: 'ko', name: 'korean' }, 26 | { lang: 'fr', name: 'french' }, 27 | { lang: 'tr', name: 'turkish' }, 28 | { lang: 'vi', name: 'vietnamese' }, 29 | { lang: 'te', name: 'telugu' }, 30 | { lang: 'mr', name: 'marathi' }, 31 | { lang: 'ta', name: 'tamil' }, 32 | { lang: 'it', name: 'italian' }, 33 | { lang: 'ur', name: 'urdu' }, 34 | { lang: 'gu', name: 'gujarati' }, 35 | { lang: 'pl', name: 'polish' }, 36 | { lang: 'uk', name: 'ukrainian' }, 37 | { lang: 'fa', name: 'persian' }, 38 | { lang: 'ml', name: 'malayalam' }, 39 | { lang: 'kn', name: 'kannada' }, 40 | { lang: 'or', name: 'oriya' }, 41 | { lang: 'ro', name: 'romanian' }, 42 | { lang: 'az', name: 'azerbaijani' }, 43 | { lang: 'ha', name: 'hausa' }, 44 | { lang: 'my', name: 'burmese' }, 45 | { lang: 'sh', name: 'serboCroatian' }, 46 | { lang: 'th', name: 'thai' }, 47 | { lang: 'nl', name: 'dutch' }, 48 | { lang: 'yo', name: 'yoruba' }, 49 | { lang: 'sd', name: 'sindhi' }, 50 | { lang: 'lv', name: 'latviski' } 51 | ]; 52 | 53 | export const getLanguagePromises = () => { 54 | return languageArray.map(langObj => { 55 | const url = `${baseUrl}${langObj.name}.json`; 56 | return loadJson(url); 57 | }); 58 | } 59 | 60 | export const languages = [ 61 | "en-US", 62 | "he-IL", 63 | "ru", 64 | "zhcn", 65 | "es", 66 | "ar", 67 | "bn", 68 | "hi", 69 | "ptpt", 70 | "ja", 71 | "de", 72 | "wuu", 73 | "ko", 74 | "fr", 75 | "tr", 76 | "vi", 77 | "te", 78 | "yue", 79 | "mr", 80 | "ta", 81 | "it", 82 | "ur", 83 | "gu", 84 | "pl", 85 | "uk", 86 | "fa", 87 | "ml", 88 | "kn", 89 | "or", 90 | "ro", 91 | "az", 92 | "ha", 93 | "my", 94 | "sh", 95 | "th", 96 | "nl", 97 | "yo", 98 | "sd", 99 | "lv", 100 | ]; 101 | 102 | 103 | 104 | export const rtlLanguages = ["ar", "fa", "he", "he-IL", "ur"]; 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /lib/components/AccessibilityMenu/AccessibilityMenu.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useRef, useState } from "react"; 2 | import { ACC_MENU_CONTAINER_ID } from "../../constants"; 3 | import Header from "../Header/Header"; 4 | import { AccessibilikState, ChangeAccDraftHander } from "../../types"; 5 | import { produce } from "immer"; 6 | import Footer from "../Footer/Footer"; 7 | import Select from "react-select"; 8 | import styled from "./AccessibilityMenu.module.scss"; 9 | import AccMenuContent from "../AccMenuContent/AccMenuContent"; 10 | import { 11 | CollapsedState, 12 | CollapsedStateKeys, 13 | collapsedStateInit, 14 | langMap, 15 | langOptions, 16 | } from "../../config"; 17 | 18 | interface AccessibilityMenuProps { 19 | display: string; 20 | accState: AccessibilikState; 21 | onChangeAccState: (fn: ChangeAccDraftHander) => void; 22 | onLangChange: (langCode: string) => void; 23 | onInit: () => void; 24 | onShow: () => void; 25 | showAcc: boolean; 26 | hasLanguages:boolean; 27 | } 28 | 29 | const AccessibilityMenu: FC = ({ 30 | accState, 31 | display, 32 | onInit, 33 | onLangChange, 34 | onChangeAccState, 35 | onShow, 36 | showAcc, 37 | hasLanguages 38 | }) => { 39 | const [collapsedState, setCollapsedState] = 40 | useState(collapsedStateInit); 41 | const { language } = accState; 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | const selectRef = useRef(null); 44 | useEffect(() => { 45 | if (selectRef.current) { 46 | selectRef.current.focus(); 47 | } 48 | }, []); 49 | 50 | const toggleCollapseHandler = (name: CollapsedStateKeys) => { 51 | setCollapsedState((p) => { 52 | return produce(collapsedStateInit, (draft) => { 53 | const prevExpandedState = p[name].isExpanded; 54 | draft[name].isExpanded = !prevExpandedState; 55 | }); 56 | }); 57 | }; 58 | 59 | return ( 60 |
61 |
62 |
63 | option.lang} 118 | getOptionValue={(option) => option.lang} 119 | /> 120 |
121 | ); 122 | }; 123 | return ( 124 | 132 | {renderLangVoices()} 133 | 134 | ); 135 | }; 136 | 137 | export default TextToSpeech; 138 | -------------------------------------------------------------------------------- /lib/hooks/usePersistenceLayout/usePersistenceLayout.ts: -------------------------------------------------------------------------------- 1 | import { AccessibilikState } from "../../types"; 2 | import { useFontWeightButton } from "../../components/buttons/content/FontWeightButton/useFontWeightButton"; 3 | import { useAdjustFontSize } from "../../components/buttons/content/AdjustFontSize/useAdjustFontSize"; 4 | import { useAlignTextButton } from "../../components/buttons/content/AlignTextButton/useAlignTextButton"; 5 | import { useDyslexiaFontButton } from "../../components/buttons/content/DyslexiaFontButton/useDyslexiaFontButton"; 6 | import { useHighlightLinksButton } from "../../components/buttons/content/HighlightLinksButton/useHighlightLinksButton"; 7 | import { useHighlightTitlesButton } from "../../components/buttons/content/HighlightTitlesButton/useHighlightTitlesButton"; 8 | import { useLetterSpacingButton } from "../../components/buttons/content/LetterSpacingButton/useLetterSpacingButton"; 9 | import { useLineHeightButton } from "../../components/buttons/content/LineHeightButton/useLineHeightButton"; 10 | import { useWordSpacingButton } from "../../components/buttons/content/WordSpacingButton/useWordSpacingButton"; 11 | import { useZoomButton } from "../../components/buttons/content/ZoomButton/useZoomButton"; 12 | import { useBigCursorButton } from "../../components/buttons/tools/BigCursorButton/useBigCursorButton"; 13 | import { useReadingGuide } from "../../components/buttons/tools/ReadingGuide/useReadingGuide"; 14 | import { useBlueLightFilterButton } from "../../components/buttons/colors/BlueLightFilterButton/useBlueLightFilterButton"; 15 | import { useDarkContrastButton } from "../../components/buttons/colors/DarkContrastButton/useDarkContrastButton"; 16 | import { useLightContrastButton } from "../../components/buttons/colors/LightContrastButton/useLightContrastButton"; 17 | import { useVisualImpairmentButton } from "../../components/buttons/colors/VisualImpairmentButton/useVisualImpairmentButton"; 18 | import { useMonochromeButton } from "../../components/buttons/colors/MonochromeButton/useMonochromeButton"; 19 | import { useHighContrastButton } from "../../components/buttons/colors/HighContrastButton/useHighContrastButton"; 20 | import { useBrightnessControl } from "../../components/buttons/colors/BrightnessControl/useBrightnessControl"; 21 | import { useHighSaturationButton } from "../../components/buttons/colors/HighSaturationButton/useHighSaturationButton"; 22 | import { useLowSaturationButton } from "../../components/buttons/colors/LowSaturationButton/useLowSaturationButton"; 23 | import { useTextColorPickerButton } from "../../components/buttons/colors/TextColorPickerButton/useTextColorPickerButton"; 24 | interface UsePersistenceLayoutProps { 25 | accState: AccessibilikState; 26 | isGettingReady: boolean; 27 | nodeListUpdated: number; 28 | } 29 | 30 | const usePersistenceLayout = ({ 31 | accState, 32 | isGettingReady, 33 | nodeListUpdated, 34 | }: UsePersistenceLayoutProps) => { 35 | useFontWeightButton(accState.isFontWeightBold, isGettingReady); 36 | useAdjustFontSize( 37 | accState.adjustFontSizePercentage, 38 | nodeListUpdated, 39 | isGettingReady 40 | ); 41 | useAlignTextButton(accState.textAlign, isGettingReady); 42 | useDyslexiaFontButton(accState.isDyslexiaFont, isGettingReady); 43 | useHighlightLinksButton(accState.highlightLinks, isGettingReady); 44 | useHighlightTitlesButton(accState.highlightTitles, isGettingReady); 45 | useLetterSpacingButton(accState.letterSpacing, isGettingReady); 46 | useLineHeightButton(accState.lineHeight, isGettingReady); 47 | useWordSpacingButton(accState.wordSpacing, isGettingReady); 48 | useZoomButton(accState.zoom, isGettingReady); 49 | useBigCursorButton(accState.isBigCursor, isGettingReady); 50 | useReadingGuide(accState.showReadingGuide, 100, isGettingReady); 51 | useBlueLightFilterButton(accState.isBlueLightFilter, isGettingReady); 52 | useDarkContrastButton(accState.isDarkContrast, isGettingReady); 53 | useLightContrastButton(accState.isLightContrast, isGettingReady); 54 | useVisualImpairmentButton(accState.isVisualImpairment, isGettingReady); 55 | useMonochromeButton(accState.isMonochrome, isGettingReady); 56 | useHighContrastButton( 57 | accState.highContrast.isHighContrast, 58 | accState.highContrast.contrast, 59 | isGettingReady 60 | ); 61 | useBrightnessControl( 62 | accState.brightness.isBrightness, 63 | accState.brightness.brightness, 64 | isGettingReady 65 | ); 66 | useHighSaturationButton( 67 | accState.highSaturation.isHighSaturation, 68 | accState.highSaturation.saturation, 69 | isGettingReady 70 | ); 71 | useLowSaturationButton( 72 | accState.lowSaturation.isLowSaturation, 73 | accState.lowSaturation.saturation, 74 | isGettingReady 75 | ); 76 | useTextColorPickerButton(accState.color, isGettingReady); 77 | }; 78 | 79 | export default usePersistenceLayout; 80 | -------------------------------------------------------------------------------- /lib/components/Accessibilik/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useState } from "react"; 2 | import { useSessionStorage } from "@uidotdev/usehooks"; 3 | import { produce } from "immer"; 4 | import styles from "./accessibilik.module.scss"; 5 | import AccessibilityButton from "../buttons/AccessibilityButton/AccessibilityButton"; 6 | import AccessibilityMenu from "../AccessibilityMenu/AccessibilityMenu"; 7 | import useFontSizeTraverse from "../../hooks/useFontSizeTraverse"; 8 | import useFontSizeMutationObserver from "../../hooks/useFontSizeMutationObserver"; 9 | import "../../index.css"; 10 | import { APP_ID, PORTAL_APP_ID } from "../../constants"; 11 | import LanguageDetector from "i18next-browser-languagedetector"; 12 | import Portal from "../Portal/Portal"; 13 | import i18n from "i18next"; 14 | import { AccessibilikState, ChangeAccDraftHander } from "../../types"; 15 | import { getAccInitState, registerDomain } from "../../utils"; 16 | import { initReactI18next } from "react-i18next"; 17 | import { 18 | Resources, 19 | getLanguagePromises, 20 | languageArray, 21 | languages, 22 | rtlLanguages, 23 | } from "./../../i18/locale"; 24 | import en from "../../i18/locale/en.json"; 25 | import usePersistenceLayout from "../../hooks/usePersistenceLayout/usePersistenceLayout"; 26 | 27 | i18n.use(LanguageDetector).use(initReactI18next); 28 | const ACC_LOCAL_STORAGE_KEY = "accessibilik"; 29 | const READING_GUIDE_PORTAL_ID = "acc-portal-[readingGuide-container]"; 30 | 31 | const Accessibilik: FC = () => { 32 | const [isLoading, setIsLoading] = useState(true); 33 | const [hasLanguages, setHasLanguages] = useState(false); 34 | const isTraversing = useFontSizeTraverse(); 35 | const nodeListUpdated = useFontSizeMutationObserver(); 36 | const [accState, setAccState] = useSessionStorage( 37 | ACC_LOCAL_STORAGE_KEY, 38 | getAccInitState() 39 | ); 40 | const [showAcc, setShowAcc] = useState(false); 41 | const isGettingReady = isTraversing || isLoading; 42 | usePersistenceLayout({ accState, isGettingReady, nodeListUpdated }); 43 | const direction = rtlLanguages.includes(accState.language) ? "rtl" : "ltr"; 44 | 45 | const changeLanguageHandler = (langCode: string) => { 46 | i18n.changeLanguage(langCode, () => { 47 | setAccState((p) => { 48 | return produce(p, (draft) => { 49 | draft.language = langCode; 50 | }); 51 | }); 52 | }); 53 | }; 54 | const changeAccessibilikStateHandler = (fn: ChangeAccDraftHander) => { 55 | setAccState((p) => { 56 | return produce(p, fn); 57 | }); 58 | }; 59 | 60 | const initAccessibilikStateHandler = () => { 61 | setAccState(getAccInitState()); 62 | }; 63 | const renderAccHandler = () => { 64 | setShowAcc((p) => !p); 65 | }; 66 | 67 | useEffect(() => { 68 | const promises = getLanguagePromises(); 69 | const resources: Resources = {}; 70 | Promise.all(promises) 71 | .then((langs) => { 72 | languageArray.forEach((item, index) => { 73 | resources[item.lang] = { 74 | translation: langs[index], 75 | }; 76 | }); 77 | i18n.init({ 78 | // debug: true, 79 | fallbackLng: "he-IL", 80 | resources, 81 | }); 82 | i18n.languages = languages; 83 | setHasLanguages(true); 84 | }) 85 | .catch(() => { 86 | i18n.init({ 87 | fallbackLng: "en", 88 | resources: { 89 | en: { 90 | translation: en, 91 | }, 92 | }, 93 | }); 94 | }) 95 | .finally(() => { 96 | setIsLoading(false); 97 | registerDomain(); 98 | }); 99 | }, []); 100 | 101 | if (isGettingReady) 102 | return ; 103 | 104 | return ( 105 | <> 106 | . 107 | 108 |
114 | 115 | 116 | 126 |
127 |
128 | 129 | ); 130 | }; 131 | 132 | export default Accessibilik; 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accessibility-react-widget", 3 | "author": "Vladi Iokhim", 4 | "license": "MIT", 5 | "description": "A React-based web accessibility widget to enhance UI/UX for all users. This powerful tool seamlessly integrates with React applications, offering a wide range of features like adjustable font sizes, text alignment options, dyslexia-friendly fonts, and color contrast adjustments. It's equipped with advanced functionalities including a BlueLight filter, brightness control, text-to-speech, and a zoom button, ensuring an optimal viewing experience for users with visual impairments. With Accessibilik, developers can easily adhere to the latest web accessibility standards (WCAG), enhancing the UI/UX for diverse audiences. Ideal for creating universally accessible web applications, Accessibilik is your go-to solution for making the web more accessible to everyone.", 6 | "keywords": [ 7 | "accessibility", 8 | "web-accessibility", 9 | "a11y", 10 | "React", 11 | "widget", 12 | "accessibilik", 13 | "Multilingual Support", 14 | "Adjust Font Size", 15 | "Align Text", 16 | "Dyslexia Font", 17 | "Font Weight", 18 | "Highlight Links", 19 | "Highlight Titles", 20 | "Letter Spacing", 21 | "Line Height", 22 | "Word Spacing", 23 | "BlueLight Filter", 24 | "Brightness Control", 25 | "DarkContrast Button", 26 | "HighContrast Button", 27 | "HighSaturation Button", 28 | "LightContrast Button", 29 | "LowSaturation Button", 30 | "Monochrome Button", 31 | "TextColor Picker", 32 | "Visual Impairment", 33 | "Zoom Button", 34 | "Big Cursor", 35 | "Reading Guide", 36 | "Text To Speech", 37 | "React Accessibility", 38 | "Accessibility for React", 39 | "Accessibility React component", 40 | "Accessibility component", 41 | "Accessibility widget", 42 | "React accessibility", 43 | "accessibility for React", 44 | "accessibility React component", 45 | "accessibility component", 46 | "accessibility widget", 47 | "WCAG compliance", 48 | "User-friendly web accessibility", 49 | "Inclusive web design", 50 | "React JS accessibility", 51 | "Accessible user interface", 52 | "Web inclusivity tools", 53 | "User experience enhancement", 54 | "Web accessibility standards", 55 | "Universal web access solutions" 56 | ], 57 | "version": "2.0.1", 58 | "type": "module", 59 | "main": "dist/main.min.js", 60 | "types": "dist/main.d.ts", 61 | "files": [ 62 | "dist" 63 | ], 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/RosenGray/accessibilik.git" 67 | }, 68 | "bugs": { 69 | "url": "https://github.com/RosenGray/accessibilik/issues" 70 | }, 71 | "scripts": { 72 | "dev": "vite", 73 | "build": "tsc --p ./tsconfig-build.json && vite build && npm run uglify && rm ./dist/main.js", 74 | "buildUmd": "tsc --p ./tsconfig-build.json && vite build && npm run uglifyUmd && rm ./dist/accessibility-react-widget.umd.cjs", 75 | "uglify": "uglifyjs ./dist/main.js -o ./dist/main.min.js --compress --mangle", 76 | "uglifyUmd": "uglifyjs ./dist/accessibility-react-widget.umd.cjs -o ./dist/accessibilik.umd.min.cjs --compress --mangle", 77 | "prepublishOnly": "npm run build", 78 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 79 | "preview": "vite preview" 80 | }, 81 | "peerDependencies": { 82 | "react": "^19.1.0", 83 | "react-dom": "^19.1.0" 84 | }, 85 | "devDependencies": { 86 | "@types/react": "^19.1.2", 87 | "@types/react-dom": "^19.1.2", 88 | "@typescript-eslint/eslint-plugin": "^6.10.0", 89 | "@typescript-eslint/parser": "^6.10.0", 90 | "@vitejs/plugin-react": "^4.2.0", 91 | "autoprefixer": "^10.4.16", 92 | "eslint": "^8.53.0", 93 | "eslint-plugin-react-hooks": "^4.6.0", 94 | "eslint-plugin-react-refresh": "^0.4.4", 95 | "glob": "^10.3.10", 96 | "postcss": "^8.4.32", 97 | "react": "^19.0.0", 98 | "react-dom": "^19.0.0", 99 | "rollup-plugin-terser": "^7.0.2", 100 | "rollup-plugin-visualizer": "^5.12.0", 101 | "sass": "^1.69.5", 102 | "typescript": "^5.6.3", 103 | "uglify-js": "^3.17.4", 104 | "vite": "^5.0.0", 105 | "vite-plugin-dts": "^3.6.4", 106 | "vite-plugin-lib-inject-css": "^1.3.0", 107 | "vite-plugin-svgr": "^4.2.0" 108 | }, 109 | "sideEffects": [ 110 | "**/*.css" 111 | ], 112 | "dependencies": { 113 | "@types/i18n": "^0.13.10", 114 | "@uidotdev/usehooks": "^2.4.1", 115 | "classnames": "^2.3.2", 116 | "i18next": "^23.7.11", 117 | "i18next-browser-languagedetector": "^7.2.0", 118 | "immer": "^10.0.3", 119 | "rc-slider": "^10.5.0", 120 | "react-colorful": "^5.6.1", 121 | "react-i18next": "^13.5.0", 122 | "react-router": "^7.5.0", 123 | "react-select": "^5.8.0" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ♿ Accessibilik: React Accessibility Widget. 2 | 3 | ![Banner Image](banner.png) 4 | 5 | [![NPM](https://img.shields.io/npm/v/accessibility-react-widget.svg)](https://www.npmjs.com/package/accessibility-react-widget) 6 | [![GitHub license](https://img.shields.io/github/license/RosenGray/accessibilik)](https://github.com/RosenGray/accessibilik/blob/master/LICENSE) 7 | 8 | 9 | 10 | Accessibilik: A React-based web accessibility widget to enhance UI/UX for all users. 11 | 12 | ``` 13 | yarn add accessibility-react-widget 14 | ``` 15 | Or 16 | ``` 17 | npm install accessibility-react-widget 18 | ``` 19 | Or 20 | ``` 21 | 22 | ``` 23 | 24 | 25 | Then use it in your app: 26 | 27 | ```js 28 | import Accessibilik from 'accessibility-react-widget'; 29 | 30 | export default function App() { 31 | 32 | return ( 33 |
34 | 35 | 36 |
37 | ); 38 | } 39 | ``` 40 | 41 | ## 🔥 Features 42 | 43 | ## Content 44 | 🇸🇨 **Multilingual Support**: Supports multiple languages **(38 languages) ** :

Hebrew, English, Russian, Chinese mandarin, Spanish, Arabic, Bengali, Hindi, Portuguese, Japanese, German, Chinese, Korean, French, Turkish, Vietnamese, Telugu, Marathi, Tamil, Italian, Urdu, Gujarati, Polish, Ukrainian, Persian, Malayalam, Kannada, Oriya, Romanian, Azerbaijani, Hausa, Burmese, Serbocroatian, Thai, Dutch, Yoruba, Sindhi

45 | 46 | 🔤 **Adjust Font Size**: offers users the ability to easily modify text size for optimal readability, enhancing accessibility for those with visual impairments or reading preferences. This feature ensures a comfortable and inclusive browsing experience for all users. 47 | 48 | 📑 **Align Text**: provides users with the capability to adjust text alignment, offering options like left, right, center, or justified, catering to personal reading preferences and enhancing overall readability. 49 | 50 | 🧠 **Dyslexia Font**: enables users to switch to a dyslexia-friendly font, improving readability and reducing visual stress for those with dyslexia, thereby fostering a more inclusive browsing experience. 51 | 52 | 🔤 **Font Weight**: This feature enables users to customize text thickness from light to bold, enhancing readability and accommodating diverse visual needs for a comfortable reading experience. 53 | 54 | 🔤 **Highlight Links**: This feature automatically highlights all hyperlinks on the page, making them more prominent and easier to locate, thus enhancing navigation and usability for all users, especially those with visual impairments. 55 | 56 | 🔤 **Highlight Titles**: Enhances the visibility of headings and titles by adding distinct highlighting, aiding users in quickly identifying key sections and improving overall content navigation. 57 | 58 | 🔤 **Letter Spacing**: Allows users to adjust the spacing between characters in texts, enhancing readability and reducing visual strain, especially beneficial for those with dyslexia or other reading difficulties. 59 | 60 | 🔤 **Line Height**: Provides the ability to alter the space between lines of text, improving legibility and comfort for reading, particularly helpful for users with visual impairments or reading disorders. 61 | 62 | 🔤 **Word Spacing**: Offers the option to modify the spacing between words, aiding in better readability and visual comfort, especially for users with dyslexia or other reading challenges. 63 | 64 | ## 🖌 Colors 65 | 66 | 🖌 **BlueLight Filter**: Reduces blue light emission from the screen, diminishing eye strain and improving viewing comfort, especially beneficial for users during extended use or in low-light conditions. 67 | 68 | 🖌 **Brightness Control**: Allows users to adjust the screen brightness directly through the website, enhancing visual comfort and reducing eye strain, especially in varying light environments. 69 | 70 | 🖌 **DarkContrast Button**: Activates a high-contrast, dark mode color scheme, reducing glare and eye strain, ideal for users with light sensitivity or those preferring a darker interface for easier reading. 71 | 72 | 🖌 **HighContrast Button**: Enables a high-contrast color mode, enhancing text and image visibility against backgrounds, crucial for users with visual impairments or color vision deficiencies. 73 | 74 | 🖌 **HighSaturation Button**: This feature enhances color saturation, making hues more vivid and distinct, which can be beneficial for users with color vision deficiencies or those who prefer more vibrant visuals. 75 | 76 | 🖌 **LightContrast Button**: Offers a low-contrast color mode, ideal for users who find high contrast visually overwhelming, providing a softer and more comfortable viewing experience. 77 | 78 | 🖌 **LowSaturation Button**: Reduces color intensity for a more subdued visual experience, ideal for users sensitive to bright colors or who prefer a less vibrant screen appearance. 79 | 80 | 🖌 **Monochrome Button**: Converts the website's colors to grayscale, simplifying the visual experience and aiding users with color perception difficulties or those who prefer minimalistic design. 81 | 82 | 🖌 **TextColor Picker**: Allows users to customize the color of text on the website, enabling personalization for better readability and comfort, especially helpful for those with visual impairments or color preferences. 83 | 84 | 🖌 **Visual Impairment**: A dedicated mode tailored for users with visual impairments, incorporating features like enhanced contrast, larger text, and voice navigation to facilitate easier and more accessible web interaction. 85 | 86 | ## 🧰 Tools 87 | 88 | 🔍 **Zoom Button**: This feature enables a full-page zoom, magnifying both text and images for enhanced visibility, catering to users with visual impairments and improving overall accessibility. 89 | 90 | 🖱 **Big Cursor**: Increases the size of the cursor on the website, enhancing its visibility and making navigation easier for users with visual impairments or those who struggle with fine motor control. 91 | 92 | 📖 **Reading Guide**: Provides an on-screen, line-by-line guide to help users focus on the text, significantly aiding those with reading difficulties or visual tracking challenges, and enhancing overall comprehension. 93 | 94 | 🎤 **Text To Speech**: Converts written text on the website into spoken words, facilitating access for users with visual impairments, reading difficulties, or those who prefer auditory learning. 95 | 96 | ``` 97 | yarn add accessibility-react-widget 98 | npm install accessibility-react-widget 99 | ``` 100 | 101 | Then use it in your app: 102 | 103 | ```js 104 | import Accessibilik from 'accessibility-react-widget'; 105 | 106 | export default function App() { 107 | 108 | return ( 109 |
110 | 111 | 112 |
113 | ); 114 | } 115 | ``` 116 | 117 | ## License 118 | 119 | MIT Licensed. Copyright (c) Vladi Iokhim 2024. 120 | --------------------------------------------------------------------------------