From 75a80353c9d7de6d315a24e271a96918e9c8da4d Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Wed, 11 Jan 2023 15:40:57 -0500 Subject: [PATCH 1/8] adds aria-label to clear search button --- src/components/header/Search.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/header/Search.tsx b/src/components/header/Search.tsx index 34fb1919..a43d3361 100644 --- a/src/components/header/Search.tsx +++ b/src/components/header/Search.tsx @@ -76,6 +76,7 @@ export function Search() { From 3db3fdb37e5d2c83c2e4f3d1d742c0c55d1f8f4a Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Wed, 11 Jan 2023 15:47:15 -0500 Subject: [PATCH 2/8] persists selected skin tone to localStorage --- src/components/context/PickerContext.tsx | 15 +++++++------- src/components/header/SkinTonePicker.tsx | 4 +++- src/config/config.ts | 4 ++++ src/dataUtils/skinTone.ts | 25 ++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 src/dataUtils/skinTone.ts diff --git a/src/components/context/PickerContext.tsx b/src/components/context/PickerContext.tsx index ce38dcd7..fa4f0c72 100644 --- a/src/components/context/PickerContext.tsx +++ b/src/components/context/PickerContext.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { useDefaultSkinToneConfig } from '../../config/useConfig'; import { DataEmoji } from '../../dataUtils/DataTypes'; import { alphaNumericEmojiIndex } from '../../dataUtils/alphaNumericEmojiIndex'; +import { getSkinTone, setSkinTone } from '../../dataUtils/skinTone'; import { useDebouncedState } from '../../hooks/useDebouncedState'; import { useDisallowedEmojis } from '../../hooks/useDisallowedEmojis'; import { FilterDict } from '../../hooks/useFilter'; @@ -61,7 +62,7 @@ const PickerContext = React.createContext<{ searchTerm: [string, (term: string) => Promise]; suggestedUpdateState: [number, (term: number) => void]; activeCategoryState: ReactState; - activeSkinTone: ReactState; + activeSkinTone: [SkinTones, (skinTone: SkinTones) => void]; emojisThatFailedToLoadState: ReactState>; isPastInitialLoad: boolean; emojiVariationPickerState: ReactState; @@ -71,18 +72,18 @@ const PickerContext = React.createContext<{ disallowMouseRef: React.MutableRefObject; disallowedEmojisRef: React.MutableRefObject>; }>({ - activeCategoryState: [null, () => {}], - activeSkinTone: [SkinTones.NEUTRAL, () => {}], + activeCategoryState: [null, () => { }], + activeSkinTone: [getSkinTone(), (skinTone) => setSkinTone(skinTone)], disallowClickRef: { current: false }, disallowMouseRef: { current: false }, disallowedEmojisRef: { current: {} }, - emojiVariationPickerState: [null, () => {}], - emojisThatFailedToLoadState: [new Set(), () => {}], + emojiVariationPickerState: [null, () => { }], + emojisThatFailedToLoadState: [new Set(), () => { }], filterRef: { current: {} }, isPastInitialLoad: true, searchTerm: ['', () => new Promise(() => undefined)], - skinToneFanOpenState: [false, () => {}], - suggestedUpdateState: [Date.now(), () => {}] + skinToneFanOpenState: [false, () => { }], + suggestedUpdateState: [Date.now(), () => { }] }); type Props = Readonly<{ diff --git a/src/components/header/SkinTonePicker.tsx b/src/components/header/SkinTonePicker.tsx index 17a6d26b..696838c4 100644 --- a/src/components/header/SkinTonePicker.tsx +++ b/src/components/header/SkinTonePicker.tsx @@ -4,8 +4,9 @@ import * as React from 'react'; import { ClassNames } from '../../DomUtils/classNames'; import { useSkinTonesDisabledConfig } from '../../config/useConfig'; import skinToneVariations, { - skinTonesNamed + skinTonesNamed, } from '../../data/skinToneVariations'; +import { setSkinTone } from '../../dataUtils/skinTone'; import { useCloseAllOpenToggles } from '../../hooks/useCloseAllOpenToggles'; import { useFocusSearchInput } from '../../hooks/useFocus'; import { SkinTones } from '../../types/exposedTypes'; @@ -82,6 +83,7 @@ export function SkinTonePicker({ onClick={() => { if (isOpen) { setActiveSkinTone(skinToneVariation); + setSkinTone(skinToneVariation) focusSearchInput(); } else { setIsOpen(true); diff --git a/src/config/config.ts b/src/config/config.ts index 066bcf9a..4876fbe6 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,5 +1,6 @@ import { GetEmojiUrl } from '../components/emoji/Emoji'; import { emojiUrlByUnified } from '../dataUtils/emojiSelectors'; +import { getSkinTone } from '../dataUtils/skinTone'; import { EmojiClickData, EmojiStyle, @@ -30,6 +31,8 @@ export function mergeConfig( suggestionMode: config.suggestedEmojisMode }); + const activeSkinTone = getSkinTone() + const skinTonePickerLocation = config.searchDisabled ? SkinTonePickerLocation.PREVIEW : config.skinTonePickerLocation; @@ -37,6 +40,7 @@ export function mergeConfig( return { ...config, categories, + defaultSkinTone: activeSkinTone, previewConfig, skinTonePickerLocation }; diff --git a/src/dataUtils/skinTone.ts b/src/dataUtils/skinTone.ts new file mode 100644 index 00000000..a1d203e7 --- /dev/null +++ b/src/dataUtils/skinTone.ts @@ -0,0 +1,25 @@ +import { SkinTones } from '../types/exposedTypes'; + +const SKINTONE_LS_KEY = 'epr_skin_tone'; + + +export function getSkinTone(): SkinTones { + try { + if (!window?.localStorage) { + return SkinTones.NEUTRAL; + } + + return JSON.parse(window?.localStorage.getItem(SKINTONE_LS_KEY) ?? SkinTones.NEUTRAL) + } catch { + return SkinTones.NEUTRAL; + } +} + +export function setSkinTone(skinTone: SkinTones) { + try { + window?.localStorage.setItem(SKINTONE_LS_KEY, JSON.stringify(skinTone)); + // Prevents the change from being seen immediately. + } catch { + // ignore + } +} \ No newline at end of file From 8e4cad3501cf25f618e424730f72248ba1520d59 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Wed, 11 Jan 2023 15:48:39 -0500 Subject: [PATCH 3/8] adds tab-ability to skin tone picker --- src/components/Layout/Relative.tsx | 21 +++++- src/components/header/SkinTonePicker.tsx | 86 ++++++++++++++++++++---- src/hooks/useKeyboardNavigation.ts | 2 +- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/components/Layout/Relative.tsx b/src/components/Layout/Relative.tsx index ceaa4fb2..b2876aa5 100644 --- a/src/components/Layout/Relative.tsx +++ b/src/components/Layout/Relative.tsx @@ -4,10 +4,27 @@ type Props = Readonly<{ children: React.ReactNode; className?: string; style?: React.CSSProperties; + button?: boolean; + tabIndex?: number; + onKeyDown?: (event: React.KeyboardEvent) => void; }>; -export default function Relative({ children, className, style }: Props) { - return ( +export default function Relative({ + children, + className, + style, + button, + tabIndex, + onKeyDown +}: Props) { + return button ? ( + + ) : (
{children}
diff --git a/src/components/header/SkinTonePicker.tsx b/src/components/header/SkinTonePicker.tsx index 696838c4..8a4c0cca 100644 --- a/src/components/header/SkinTonePicker.tsx +++ b/src/components/header/SkinTonePicker.tsx @@ -9,6 +9,7 @@ import skinToneVariations, { import { setSkinTone } from '../../dataUtils/skinTone'; import { useCloseAllOpenToggles } from '../../hooks/useCloseAllOpenToggles'; import { useFocusSearchInput } from '../../hooks/useFocus'; +import { KeyboardEvents } from '../../hooks/useKeyboardNavigation'; import { SkinTones } from '../../types/exposedTypes'; import Absolute from '../Layout/Absolute'; import Relative from '../Layout/Relative'; @@ -16,7 +17,7 @@ import { Button } from '../atoms/Button'; import { useSkinTonePickerRef } from '../context/ElementRefContext'; import { useActiveSkinToneState, - useSkinToneFanOpenState + useSkinToneFanOpenState, } from '../context/PickerContext'; import './SkinTonePicker.css'; @@ -24,6 +25,7 @@ const ITEM_SIZE = 28; type Props = { direction?: SkinTonePickerDirection; + fanOutDirection?: SkinTonePickerFanOutDirection; }; export function SkinTonePickerMenu() { @@ -37,7 +39,8 @@ export function SkinTonePickerMenu() { } export function SkinTonePicker({ - direction = SkinTonePickerDirection.HORIZONTAL + direction = SkinTonePickerDirection.HORIZONTAL, + fanOutDirection = SkinTonePickerFanOutDirection.LEFT, }: Props) { const SkinTonePickerRef = useSkinTonePickerRef(); const isDisabled = useSkinTonesDisabledConfig(); @@ -56,16 +59,52 @@ export function SkinTonePicker({ const vertical = direction === SkinTonePickerDirection.VERTICAL; + const getHorizontalTranslation = ({ + ix, + fanOutDirection, + isOpen, + }: { + ix: number; + fanOutDirection: SkinTonePickerFanOutDirection; + isOpen: boolean; + }): string => { + // By fanning out to the left, the focus remains on the last (right-most) tone in the array, + // so tabbing takes a user out of the SkinTonePicker. In order to tab through the tones, a user + // must first tab backwards. + // + // Fanning out to the right keeps the focus on the first (left-most) tone in the array so a user + // can tab from left to right. + if (fanOutDirection === SkinTonePickerFanOutDirection.LEFT) { + return `translateX(-${ix * (isOpen ? ITEM_SIZE : 0)}px)`; + } + return `translateX(${ix * (isOpen ? ITEM_SIZE : 0) - + (isOpen ? (skinToneVariations.length - 1) * ITEM_SIZE : 0) + }px)`; + }; + + const buttonStyle = { backgroundColor: "transparent", border: "none" } + return ( ) => { + const { key } = event; + if (key === KeyboardEvents.Enter) { + if (!isOpen) { + setIsOpen(true) + } + closeAllOpenToggles(); + } + }} >
{skinToneVariations.map((skinToneVariation, i) => { @@ -76,9 +115,13 @@ export function SkinTonePicker({ transform: clsx( vertical ? `translateY(-${i * (isOpen ? ITEM_SIZE : 0)}px)` - : `translateX(-${i * (isOpen ? ITEM_SIZE : 0)}px)`, + : getHorizontalTranslation({ + ix: i, + fanOutDirection, + isOpen, + }), isOpen && active && 'scale(1.3)' - ) + ), }} onClick={() => { if (isOpen) { @@ -90,15 +133,28 @@ export function SkinTonePicker({ } closeAllOpenToggles(); }} + // When tabbed onto the SkinTonePicker, allow Enter to open and close the fan of tones + onKeyDown={(event) => { + const { key } = event; + if (key === KeyboardEvents.Enter) { + if (isOpen) { + setActiveSkinTone(skinToneVariation); + setSkinTone(skinToneVariation) + focusSearchInput(); + } else { + setIsOpen(true); + } + closeAllOpenToggles(); + } + }} + tabIndex={isOpen ? 0 : -1} key={skinToneVariation} className={clsx(`epr-tone-${skinToneVariation}`, 'epr-tone', { - [ClassNames.active]: active + [ClassNames.active]: active, })} - tabIndex={isOpen ? 0 : -1} aria-pressed={active} - aria-label={`Skin tone ${ - skinTonesNamed[skinToneVariation as SkinTones] - }`} + aria-label={`Skin tone ${skinTonesNamed[skinToneVariation as SkinTones] + }`} > ); })} @@ -109,5 +165,9 @@ export function SkinTonePicker({ export enum SkinTonePickerDirection { VERTICAL = ClassNames.vertical, - HORIZONTAL = ClassNames.horizontal + HORIZONTAL = ClassNames.horizontal, +} +export enum SkinTonePickerFanOutDirection { + LEFT = 'LEFT', + RIGHT = 'RIGHT', } diff --git a/src/hooks/useKeyboardNavigation.ts b/src/hooks/useKeyboardNavigation.ts index e460634a..19161fb5 100644 --- a/src/hooks/useKeyboardNavigation.ts +++ b/src/hooks/useKeyboardNavigation.ts @@ -43,7 +43,7 @@ import { useIsSkinToneInSearch } from './useShouldShowSkinTonePicker'; -enum KeyboardEvents { +export enum KeyboardEvents { ArrowDown = 'ArrowDown', ArrowUp = 'ArrowUp', ArrowLeft = 'ArrowLeft', From 26ee74376629aa58f4da3ae54c3d08b71a3aa5d1 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Thu, 2 Mar 2023 14:53:33 -0500 Subject: [PATCH 4/8] Undo component changes creating broken HTML --- src/components/Layout/Relative.tsx | 17 ++--------------- src/components/header/SkinTonePicker.tsx | 11 ----------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/components/Layout/Relative.tsx b/src/components/Layout/Relative.tsx index b2876aa5..f09f39f8 100644 --- a/src/components/Layout/Relative.tsx +++ b/src/components/Layout/Relative.tsx @@ -4,27 +4,14 @@ type Props = Readonly<{ children: React.ReactNode; className?: string; style?: React.CSSProperties; - button?: boolean; - tabIndex?: number; - onKeyDown?: (event: React.KeyboardEvent) => void; }>; export default function Relative({ children, className, - style, - button, - tabIndex, - onKeyDown + style }: Props) { - return button ? ( - - ) : ( + return (
{children}
diff --git a/src/components/header/SkinTonePicker.tsx b/src/components/header/SkinTonePicker.tsx index 8a4c0cca..cb01b8d2 100644 --- a/src/components/header/SkinTonePicker.tsx +++ b/src/components/header/SkinTonePicker.tsx @@ -94,17 +94,6 @@ export function SkinTonePicker({ ? { flexBasis: expandedSize, height: expandedSize, ...buttonStyle } : { flexBasis: expandedSize, ...buttonStyle } } - button={true} - tabIndex={0} - onKeyDown={(event: React.KeyboardEvent) => { - const { key } = event; - if (key === KeyboardEvents.Enter) { - if (!isOpen) { - setIsOpen(true) - } - closeAllOpenToggles(); - } - }} >
{skinToneVariations.map((skinToneVariation, i) => { From 05ff883575be4818cbba149852bb8539765220a0 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Thu, 2 Mar 2023 14:54:26 -0500 Subject: [PATCH 5/8] Convert function to named function and move to below return for magazine code org approach --- src/components/header/SkinTonePicker.tsx | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/header/SkinTonePicker.tsx b/src/components/header/SkinTonePicker.tsx index cb01b8d2..6e433fde 100644 --- a/src/components/header/SkinTonePicker.tsx +++ b/src/components/header/SkinTonePicker.tsx @@ -59,29 +59,6 @@ export function SkinTonePicker({ const vertical = direction === SkinTonePickerDirection.VERTICAL; - const getHorizontalTranslation = ({ - ix, - fanOutDirection, - isOpen, - }: { - ix: number; - fanOutDirection: SkinTonePickerFanOutDirection; - isOpen: boolean; - }): string => { - // By fanning out to the left, the focus remains on the last (right-most) tone in the array, - // so tabbing takes a user out of the SkinTonePicker. In order to tab through the tones, a user - // must first tab backwards. - // - // Fanning out to the right keeps the focus on the first (left-most) tone in the array so a user - // can tab from left to right. - if (fanOutDirection === SkinTonePickerFanOutDirection.LEFT) { - return `translateX(-${ix * (isOpen ? ITEM_SIZE : 0)}px)`; - } - return `translateX(${ix * (isOpen ? ITEM_SIZE : 0) - - (isOpen ? (skinToneVariations.length - 1) * ITEM_SIZE : 0) - }px)`; - }; - const buttonStyle = { backgroundColor: "transparent", border: "none" } return ( @@ -150,6 +127,29 @@ export function SkinTonePicker({
); + + function getHorizontalTranslation({ + ix, + fanOutDirection, + isOpen, + }: { + ix: number; + fanOutDirection: SkinTonePickerFanOutDirection; + isOpen: boolean; + }): string { + // By fanning out to the left, the focus remains on the last (right-most) tone in the array, + // so tabbing takes a user out of the SkinTonePicker. In order to tab through the tones, a user + // must first tab backwards. + // + // Fanning out to the right keeps the focus on the first (left-most) tone in the array so a user + // can tab from left to right. + if (fanOutDirection === SkinTonePickerFanOutDirection.LEFT) { + return `translateX(-${ix * (isOpen ? ITEM_SIZE : 0)}px)`; + } + return `translateX(${ix * (isOpen ? ITEM_SIZE : 0) - + (isOpen ? (skinToneVariations.length - 1) * ITEM_SIZE : 0) + }px)`; + } } export enum SkinTonePickerDirection { From 33e18bc8d550ac1b84a9bd59bd10e35b184c2506 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Thu, 2 Mar 2023 14:55:30 -0500 Subject: [PATCH 6/8] Remove incorrect comment, formatting --- src/dataUtils/skinTone.ts | 1 - src/hooks/useKeyboardNavigation.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dataUtils/skinTone.ts b/src/dataUtils/skinTone.ts index a1d203e7..25a469fe 100644 --- a/src/dataUtils/skinTone.ts +++ b/src/dataUtils/skinTone.ts @@ -18,7 +18,6 @@ export function getSkinTone(): SkinTones { export function setSkinTone(skinTone: SkinTones) { try { window?.localStorage.setItem(SKINTONE_LS_KEY, JSON.stringify(skinTone)); - // Prevents the change from being seen immediately. } catch { // ignore } diff --git a/src/hooks/useKeyboardNavigation.ts b/src/hooks/useKeyboardNavigation.ts index 19161fb5..f28fd8a2 100644 --- a/src/hooks/useKeyboardNavigation.ts +++ b/src/hooks/useKeyboardNavigation.ts @@ -12,7 +12,7 @@ import { focusNextVisibleEmoji, focusPrevVisibleEmoji, focusVisibleEmojiOneRowDown, - focusVisibleEmojiOneRowUp + focusVisibleEmojiOneRowUp, } from '../DomUtils/keyboardNavigation'; import { useScrollTo } from '../DomUtils/scrollTo'; import { buttonFromTarget } from '../DomUtils/selectors'; From 9f23f5ad97a5affb29650120f5108af638d956ee Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Thu, 2 Mar 2023 14:55:49 -0500 Subject: [PATCH 7/8] Use existing Search input keyboard nav to tab from search into picker --- src/hooks/useKeyboardNavigation.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useKeyboardNavigation.ts b/src/hooks/useKeyboardNavigation.ts index f28fd8a2..a1961a49 100644 --- a/src/hooks/useKeyboardNavigation.ts +++ b/src/hooks/useKeyboardNavigation.ts @@ -50,7 +50,8 @@ export enum KeyboardEvents { ArrowRight = 'ArrowRight', Escape = 'Escape', Enter = 'Enter', - Space = ' ' + Space = ' ', + Tab = "Tab" } export function useKeyboardNavigation() { @@ -132,6 +133,7 @@ function useSearchInputKeyboardEvents() { const { key } = event; switch (key) { + case KeyboardEvents.Tab: case KeyboardEvents.ArrowRight: if (!isSkinToneInSearch) { return; From 5157e6f6bb2bb04bcfe99dd72e76fcf3607b55b4 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Thu, 2 Mar 2023 14:59:34 -0500 Subject: [PATCH 8/8] undo unnecessary formatting change --- src/components/Layout/Relative.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Layout/Relative.tsx b/src/components/Layout/Relative.tsx index f09f39f8..ceaa4fb2 100644 --- a/src/components/Layout/Relative.tsx +++ b/src/components/Layout/Relative.tsx @@ -6,11 +6,7 @@ type Props = Readonly<{ style?: React.CSSProperties; }>; -export default function Relative({ - children, - className, - style -}: Props) { +export default function Relative({ children, className, style }: Props) { return (
{children}