From b4f8d53a9e05b91abca2dfaf87afd9c9df2e8af7 Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:15:44 -0500 Subject: [PATCH 1/8] add key binding for switching panels and enable autofocus on mount --- .../debugger-toolbar.component.tsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx b/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx index 8c7d0201..256201ec 100644 --- a/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx +++ b/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import styles from "./debugger-toolbar.module.scss"; import { BoxComponent } from "@/features/common/components/box/box.component"; import { useDebuggerStore } from "@/features/debugger/services/debugger.store"; @@ -18,9 +18,26 @@ interface DebuggerToolbarComponentProps { export const DebuggerToolbarComponent: React.FC< DebuggerToolbarComponentProps > = ({ decoderDictionary, encoderDictionary, mode }) => { + const tabRefs = useRef>([]); const activeWidget$ = useDebuggerStore((state) => state.activeWidget$); - const setActiveWidget$ = useDebuggerStore((state) => state.setActiveWidget$); + const isDecoder = activeWidget$ === DebuggerWidgetValues.DECODER; + + useEffect(() => { + tabRefs.current[isDecoder ? 0 : 1]?.focus(); + }, [isDecoder]); + + const handleKeyDown = (e: React.KeyboardEvent) => { + const { key } = e; + + if (key == "ArrowRight" || key == "ArrowLeft") { + setActiveWidget$( + isDecoder ? DebuggerWidgetValues.ENCODER : DebuggerWidgetValues.DECODER + ); + e.preventDefault(); + } + tabRefs.current[isDecoder ? 0 : 1]?.focus(); + }; if (mode === DebuggerModeValues.UNIFIED) { return ( @@ -40,8 +57,15 @@ export const DebuggerToolbarComponent: React.FC< onClick={() => { setActiveWidget$(DebuggerWidgetValues.DECODER); }} + onKeyDown={handleKeyDown} data-active={activeWidget$ === DebuggerWidgetValues.DECODER} data-testid={dataTestidDictionary.debugger.decoderTab.id} + aria-selected={activeWidget$ === DebuggerWidgetValues.DECODER} + aria-controls={`${DebuggerWidgetValues.DECODER}-panel`} + ref={(el) => { + tabRefs.current[0] = el; + }} + tabIndex={activeWidget$ === DebuggerWidgetValues.DECODER ? 0 : -1} > {decoderDictionary.compactTitle} @@ -53,11 +77,18 @@ export const DebuggerToolbarComponent: React.FC<
  • { setActiveWidget$(DebuggerWidgetValues.ENCODER); }} data-active={activeWidget$ === DebuggerWidgetValues.ENCODER} data-testid={dataTestidDictionary.debugger.encoderTab.id} + aria-selected={activeWidget$ === DebuggerWidgetValues.ENCODER} + aria-controls={`${DebuggerWidgetValues.ENCODER}-panel`} + ref={(el) => { + tabRefs.current[1] = el; + }} + tabIndex={activeWidget$ === DebuggerWidgetValues.ENCODER ? 0 : -1} > {encoderDictionary.compactTitle} From 7688fdcf98f777fce79aefeb641e1eeb647bc248 Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:11:27 -0500 Subject: [PATCH 2/8] add listener to focus input on windows focus --- .../code-editor/editor.component.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/features/common/components/code-editor/editor.component.tsx b/src/features/common/components/code-editor/editor.component.tsx index 6a442eb0..ddac153b 100644 --- a/src/features/common/components/code-editor/editor.component.tsx +++ b/src/features/common/components/code-editor/editor.component.tsx @@ -18,6 +18,7 @@ type Props = React.HTMLAttributes & { textareaId?: string; textareaClassName?: string; autoFocus?: boolean; + focusOnWindowFocus?: boolean; disabled?: boolean; form?: string; maxLength?: number; @@ -87,6 +88,15 @@ export class EditorComponent extends React.Component { componentDidMount() { this._recordCurrentState(); + if (this.props.focusOnWindowFocus) { + window.addEventListener("focus", this._focusInput); + } + } + + componentWillUnmount() { + if (this.props.focusOnWindowFocus) { + window.removeEventListener("focus", this._focusInput); + } } private _recordCurrentState = () => { @@ -161,6 +171,13 @@ export class EditorComponent extends React.Component { this._history.offset++; }; + private _focusInput = () => { + const input = this._input; + + if (!input) return; + input.focus(); + }; + private _updateInput = (record: Record) => { const input = this._input; @@ -466,7 +483,7 @@ export class EditorComponent extends React.Component { selectionStart, selectionEnd, }, - true, + true ); this.props.onValueChange(value); @@ -516,6 +533,7 @@ export class EditorComponent extends React.Component { insertSpaces, ignoreTabKey, preClassName, + focusOnWindowFocus, ...rest } = this.props; From 9c63b793b606c4c14fa8f41109b0ac6961b9404f Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:11:50 -0500 Subject: [PATCH 3/8] add new props for auto focus on editor component --- .../debugger-toolbar/debugger-toolbar.component.tsx | 4 ---- src/features/decoder/components/jwt-editor.component.tsx | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx b/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx index 256201ec..c224994e 100644 --- a/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx +++ b/src/features/debugger/components/debugger-toolbar/debugger-toolbar.component.tsx @@ -23,10 +23,6 @@ export const DebuggerToolbarComponent: React.FC< const setActiveWidget$ = useDebuggerStore((state) => state.setActiveWidget$); const isDecoder = activeWidget$ === DebuggerWidgetValues.DECODER; - useEffect(() => { - tabRefs.current[isDecoder ? 0 : 1]?.focus(); - }, [isDecoder]); - const handleKeyDown = (e: React.KeyboardEvent) => { const { key } = e; diff --git a/src/features/decoder/components/jwt-editor.component.tsx b/src/features/decoder/components/jwt-editor.component.tsx index 62757a40..88bf2859 100644 --- a/src/features/decoder/components/jwt-editor.component.tsx +++ b/src/features/decoder/components/jwt-editor.component.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { EditorComponent } from "@/features/common/components/code-editor/editor.component"; interface JwtEditorComponentProps { @@ -14,6 +14,8 @@ export const JwtEditorComponent: React.FC = ({ handleJwtChange(code)} highlight={(code) => { if (!code) { From dff7409231d7d5a8eb17c01c98e81ca4e454ca1b Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:24:44 -0500 Subject: [PATCH 4/8] add/remove focus listeners from editor when prop changes --- .../components/code-editor/editor.component.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/features/common/components/code-editor/editor.component.tsx b/src/features/common/components/code-editor/editor.component.tsx index ddac153b..03987d35 100644 --- a/src/features/common/components/code-editor/editor.component.tsx +++ b/src/features/common/components/code-editor/editor.component.tsx @@ -93,12 +93,18 @@ export class EditorComponent extends React.Component { } } - componentWillUnmount() { - if (this.props.focusOnWindowFocus) { - window.removeEventListener("focus", this._focusInput); + componentDidUpdate(prevProps: Readonly): void { + if (prevProps.focusOnWindowFocus !== this.props.focusOnWindowFocus) { + this.props.focusOnWindowFocus + ? window.addEventListener("focus", this._focusInput) + : window.removeEventListener("focus", this._focusInput); } } + componentWillUnmount() { + window.removeEventListener("focus", this._focusInput); + } + private _recordCurrentState = () => { const input = this._input; From ce857b19d2fba0e7a3a21bf44ccfeb427ba9c2d0 Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:25:20 -0500 Subject: [PATCH 5/8] add autofocus prop to jwt editor component --- src/features/decoder/components/jwt-editor.component.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/features/decoder/components/jwt-editor.component.tsx b/src/features/decoder/components/jwt-editor.component.tsx index 88bf2859..36f05a36 100644 --- a/src/features/decoder/components/jwt-editor.component.tsx +++ b/src/features/decoder/components/jwt-editor.component.tsx @@ -1,21 +1,23 @@ -import React, { useEffect } from "react"; +import React from "react"; import { EditorComponent } from "@/features/common/components/code-editor/editor.component"; interface JwtEditorComponentProps { token: string; handleJwtChange: (value: string) => void; + autoFocus: boolean } export const JwtEditorComponent: React.FC = ({ token, + autoFocus, handleJwtChange, }) => { return ( handleJwtChange(code)} highlight={(code) => { if (!code) { From ab67080e04b6b9e5a19f997b266251e3fa20e8d4 Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:26:05 -0500 Subject: [PATCH 6/8] add checkbox and localstorage functionality for enabling/disabling autofocus --- .../components/jwt-input.component.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/features/decoder/components/jwt-input.component.tsx b/src/features/decoder/components/jwt-input.component.tsx index fe99a3ac..1baef8dd 100644 --- a/src/features/decoder/components/jwt-input.component.tsx +++ b/src/features/decoder/components/jwt-input.component.tsx @@ -14,6 +14,7 @@ import { CardToolbarComponent } from "@/features/common/components/card-toolbar/ import { CardToolbarCopyButtonComponent } from "@/features/common/components/card-toolbar-buttons/card-toolbar-copy-button/card-toolbar-copy-button.component"; import { CardToolbarClearButtonComponent } from "@/features/common/components/card-toolbar-buttons/card-toolbar-clear-button/card-toolbar-clear-button.component"; import styles from "./jwt-input.module.scss"; +import { CheckboxComponent } from "@/features/common/components/checkbox/checkbox.component"; type JwtInputComponentProps = { languageCode: string; @@ -24,6 +25,10 @@ export const JwtInputComponent: React.FC = ({ languageCode, dictionary, }) => { + const [autoFocusEnabled, setAutofocusEnabled] = useState(() => { + const saved = localStorage.getItem("autofocus-enabled"); + return saved ? !!JSON.parse(saved) : false + }); const handleJwtChange$ = useDecoderStore((state) => state.handleJwtChange); const jwt$ = useDecoderStore((state) => state.jwt); const decodeErrors$ = useDecoderStore((state) => state.decodingErrors); @@ -31,7 +36,7 @@ export const JwtInputComponent: React.FC = ({ const decoderInputs$ = useDebuggerStore((state) => state.decoderInputs$); const [token, setToken] = useState( - decoderInputs$.jwt || DEFAULT_JWT.token, + decoderInputs$.jwt || DEFAULT_JWT.token ); const clearValue = async () => { @@ -46,13 +51,23 @@ export const JwtInputComponent: React.FC = ({ handleJwtChange$(cleanValue); }; + const handleCheckboxChange = (selected: boolean) => { + localStorage.setItem("autofocus-enabled", JSON.stringify(selected)) + setAutofocusEnabled(selected) + } + useEffect(() => { setToken(jwt$); }, [jwt$]); return ( <> - {dictionary.headline} +
    + {dictionary.headline} + handleCheckboxChange(e)}> + Enable auto-focus + +
    = ({ ), }} > - + ); From 1f83bad0c744706f98d0feb1ff1447cfac3631dd Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:26:30 -0500 Subject: [PATCH 7/8] style checkbox label --- src/features/decoder/components/jwt-input.module.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/features/decoder/components/jwt-input.module.scss b/src/features/decoder/components/jwt-input.module.scss index e098d3ec..386b07eb 100644 --- a/src/features/decoder/components/jwt-input.module.scss +++ b/src/features/decoder/components/jwt-input.module.scss @@ -10,3 +10,11 @@ font-weight: 500; letter-spacing: 0.24px; } + +.checkbox__label { + color: var(--color_fg_default); + font-size: 0.875rem; + line-height: 1.375rem; + font-weight: 500; + letter-spacing: 0.24px; +} From 7fc54f4666d51da8762f19db9fecebeaa210477f Mon Sep 17 00:00:00 2001 From: Javier Tinoco <213990346+javiert-okta@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:26:42 -0500 Subject: [PATCH 8/8] add new Checkbox component --- .../checkbox/checkbox.component.tsx | 28 ++++++++ .../components/checkbox/checkbox.module.scss | 66 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/features/common/components/checkbox/checkbox.component.tsx create mode 100644 src/features/common/components/checkbox/checkbox.module.scss diff --git a/src/features/common/components/checkbox/checkbox.component.tsx b/src/features/common/components/checkbox/checkbox.component.tsx new file mode 100644 index 00000000..a7bfb856 --- /dev/null +++ b/src/features/common/components/checkbox/checkbox.component.tsx @@ -0,0 +1,28 @@ +import { Checkbox, type CheckboxProps } from "react-aria-components"; +import styles from './checkbox.module.scss' + +export function CheckboxComponent({ + children, + ...props +}: Omit & { + children?: React.ReactNode; +}) { + return ( + + {({ isIndeterminate }) => ( + <> +
    + +
    + {children} + + )} +
    + ); +} diff --git a/src/features/common/components/checkbox/checkbox.module.scss b/src/features/common/components/checkbox/checkbox.module.scss new file mode 100644 index 00000000..307edf43 --- /dev/null +++ b/src/features/common/components/checkbox/checkbox.module.scss @@ -0,0 +1,66 @@ +.checkbox__component { + --selected-color: var(--color_bg_state_success); + --selected-color-pressed: var(--color_fg_on_state_success_subtle); + --checkmark-color: var(--color_border_state_success); + + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: 0.571rem; + font-size: 1.143rem; + color: white; + forced-color-adjust: none; + + .checkbox { + width: 1.143rem; + height: 1.143rem; + border: 2px solid var(--color_fg_default); + border-radius: 4px; + transition: all 200ms; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + svg { + width: 1rem; + height: 1rem; + fill: none; + stroke: var(--functional-gray-0); + stroke-width: 3px; + stroke-dasharray: 22px; + stroke-dashoffset: 66; + transition: all 200ms; + } + + &[data-focus-visible] .checkbox { + outline: 2px solid var(--color_fg_selected); + outline-offset: 2px; + } + + &[data-selected], + &[data-indeterminate] { + .checkbox { + border-color: var(--selected-color); + background: var(--selected-color); + } + + &[data-pressed] .checkbox { + border-color: var(--selected-color-pressed); + background: var(--selected-color-pressed); + } + + svg { + stroke-dashoffset: 44; + } + } + + &[data-indeterminate] { + & svg { + stroke: none; + fill: var(--checkmark-color); + } + } +} \ No newline at end of file