diff --git a/client/src/templates/Challenges/classic/editor.css b/client/src/templates/Challenges/classic/editor.css index 27d67d2f29f..f55ffb258c0 100644 --- a/client/src/templates/Challenges/classic/editor.css +++ b/client/src/templates/Challenges/classic/editor.css @@ -192,3 +192,53 @@ textarea.inputarea { .hint-status { margin-bottom: 18px; } + +/* overwriting default arrow styles for accessibility */ +:root { + --monaco-scrollbar-arrow-box-size: 0; + --monaco-scrollbar-arrow-icon-top-bottom: 0; + --monaco-scrollbar-arrow-icon-left: 0; + --monaco-scrollbar-arrow-icon-size: 11px; + --monaco-scrollbar-arrow-icon-font-size: 11px; +} + +/* z-index is needed for arrox box and icons because the scrollbar may + overlap the arrow buttons when zoomed in */ + +/* arrow box - by default the width is the same as the scrollbar, + so we only need to ajdust the height */ +.vs .monaco-scrollable-element .scrollbar .arrow-background { + height: var(--monaco-scrollbar-arrow-box-size) !important; + background-color: #666; + z-index: 998; +} + +/* arrow icons - includes both up and down */ +.vs .monaco-scrollable-element .scrollbar .scra { + top: var(--monaco-scrollbar-arrow-icon-top-bottom) !important; + left: var(--monaco-scrollbar-arrow-icon-left) !important; + width: var(--monaco-scrollbar-arrow-icon-size) !important; + height: var(--monaco-scrollbar-arrow-icon-size) !important; + font-size: var(--monaco-scrollbar-arrow-icon-font-size) !important; + color: white; + z-index: 999; +} + +/* down arrow icon only */ +.vs .monaco-scrollable-element .scrollbar .codicon-scrollbar-button-down { + top: unset !important; + bottom: var(--monaco-scrollbar-arrow-icon-top-bottom) !important; +} + +/* we can live with with no color change for hover state if browser doesn't + support :has (the mouse cursor will still change to pointer) */ +@supports (selector(html:has(body))) { + .vs .monaco-scrollable-element .scrollbar .arrow-background:hover, + .vs + .monaco-scrollable-element + .scrollbar + .arrow-background:has(+ .codicon:hover) { + cursor: pointer; + background-color: #222 !important; + } +} diff --git a/client/src/templates/Challenges/classic/editor.tsx b/client/src/templates/Challenges/classic/editor.tsx index 75f568e2880..1d63f1c1e84 100644 --- a/client/src/templates/Challenges/classic/editor.tsx +++ b/client/src/templates/Challenges/classic/editor.tsx @@ -61,7 +61,10 @@ import { isChallengeCompletedSelector } from '../redux/selectors'; import GreenPass from '../../../assets/icons/green-pass'; -import { enhancePrismAccessibility } from '../utils/index'; +import { + enhancePrismAccessibility, + setScrollbarArrowStyles +} from '../utils/index'; import { getScrollbarWidth } from '../../../utils/scrollbar-width'; import LowerJaw from './lower-jaw'; @@ -291,9 +294,12 @@ const Editor = (props: EditorProps): JSX.Element => { scrollbar: { horizontal: 'hidden', vertical: 'visible', - verticalHasArrows: false, + verticalHasArrows: true, useShadows: false, - verticalScrollbarSize: getScrollbarWidth() + verticalScrollbarSize: getScrollbarWidth(), + // this helps the scroll bar fit properly between the arrows, + // but doesn't do anything for the arrows themselves + arrowSize: getScrollbarWidth() }, parameterHints: { enabled: false @@ -602,6 +608,9 @@ const Editor = (props: EditorProps): JSX.Element => { scrollGutterNode ); editor.addContentWidget(scrollGutterWidget); + + // update scrollbar arrows + setScrollbarArrowStyles(getScrollbarWidth()); }; const toggleAriaRoledescription = () => { diff --git a/client/src/templates/Challenges/utils/index.ts b/client/src/templates/Challenges/utils/index.ts index 6d9c5fd796b..e0da65dc269 100644 --- a/client/src/templates/Challenges/utils/index.ts +++ b/client/src/templates/Challenges/utils/index.ts @@ -74,3 +74,38 @@ export function enhancePrismAccessibility( }) ); } + +// Adjusts scrollbar arrows based on scrollbar width +export function setScrollbarArrowStyles(scrollbarWidth: number): void { + const root = document.documentElement; + + // make the arrow box a square + root.style.setProperty( + '--monaco-scrollbar-arrow-box-size', + `${scrollbarWidth}px` + ); + + // adjust arrow icon size to fit arrow box + const iconSize = scrollbarWidth < 11 ? scrollbarWidth : scrollbarWidth - 5; + const iconFontSize = + scrollbarWidth < 11 ? scrollbarWidth : scrollbarWidth - 5; + root.style.setProperty('--monaco-scrollbar-arrow-icon-size', `${iconSize}px`); + root.style.setProperty( + '--monaco-scrollbar-arrow-icon-font-size', + `${iconFontSize}px` + ); + + // position arrow icon in arrow box + const iconTopBottom = + scrollbarWidth < 11 ? 0 : scrollbarWidth / 2 - iconFontSize / 2 - 1; + const iconLeftPosition = + scrollbarWidth < 11 ? 0 : (scrollbarWidth - iconFontSize) / 2; + root.style.setProperty( + '--monaco-scrollbar-arrow-icon-top-bottom', + `${iconTopBottom}px` + ); + root.style.setProperty( + '--monaco-scrollbar-arrow-icon-left', + `${iconLeftPosition}px` + ); +}