From 2a0fdd23705b0c44e445eea834fda12cf0922bf6 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:18:34 +0200 Subject: [PATCH 1/7] Fix keyboard navigation, fix focus styling --- packages/spor-react/src/layout/RadioCard.tsx | 75 +++++++++++-------- .../spor-react/src/layout/RadioCardGroup.tsx | 11 +-- .../src/theme/components/radio-card.ts | 38 +++++----- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/packages/spor-react/src/layout/RadioCard.tsx b/packages/spor-react/src/layout/RadioCard.tsx index b04958802..33cc8aedc 100644 --- a/packages/spor-react/src/layout/RadioCard.tsx +++ b/packages/spor-react/src/layout/RadioCard.tsx @@ -38,7 +38,7 @@ export type RadioCardProps = BoxProps & { }; export const RadioCard = forwardRef( - ({ children, value = "base", isDisabled, ...props }: RadioCardProps, ref) => { + ({ children, value, isDisabled, ...props }: RadioCardProps, ref) => { const context = useContext(RadioCardGroupContext); if (!context) { @@ -51,57 +51,66 @@ export const RadioCard = forwardRef( const styles = useMultiStyleConfig("RadioCard", { variant }); - const isChecked = selectedValue === value; + const [isKeyboardUser, setKeyboardUser] = React.useState(false); + const [isFocused, setFocus] = React.useState(false); + + useEffect(() => { + const handleMouseDown = () => setKeyboardUser(false); + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === " ") { + setFocus(false); + } else { + setKeyboardUser(true); + } + }; - const radioCardId = `radio-card-${useId()}`; + window.addEventListener("mousedown", handleMouseDown); + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("mousedown", handleMouseDown); + window.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const isChecked = selectedValue === value; useEffect(() => { - if (isChecked && typeof ref !== "function" && ref?.current) { - ref.current.focus(); + if (isKeyboardUser && isChecked) { + setFocus(true); + } else { + setFocus(false); } - }, [isChecked]); + }, [isKeyboardUser, isChecked]); - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter" || event.key === " ") { - onChange(value); - } - if ( - event.key === "ArrowRight" || - event.key === "ArrowDown" || - event.key === "ArrowLeft" || - event.key === "ArrowUp" - ) { - const nextRadioCard = event.currentTarget - .nextElementSibling as HTMLElement; - if (nextRadioCard) { - nextRadioCard.focus(); - } - } - }; + const inputId = `radio-card-${useId()}`; return ( - + onChange(value)} disabled={isDisabled} __css={styles.radioInput} /> isKeyboardUser && setFocus(true)} + onBlur={() => setFocus(false)} + __css={{ + ...styles.container, + ...(isChecked && styles.checked), + ...(isFocused && styles.focused), + }} > {children} diff --git a/packages/spor-react/src/layout/RadioCardGroup.tsx b/packages/spor-react/src/layout/RadioCardGroup.tsx index c29aa28f9..0b39b3df5 100644 --- a/packages/spor-react/src/layout/RadioCardGroup.tsx +++ b/packages/spor-react/src/layout/RadioCardGroup.tsx @@ -1,7 +1,6 @@ import { BoxProps, Stack } from "@chakra-ui/react"; import React, { useState } from "react"; import { FormLabel } from "../input"; -import { RadioCard } from "./RadioCard"; /** * RadioCardGroupContext is used to pass down the state and handlers to the RadioCard components. @@ -15,6 +14,7 @@ type RadioGroupContextProps = { onChange: (value: string) => void; variant?: "base" | "floating"; defaultValue?: string; + value?: string; }; export const RadioCardGroupContext = @@ -59,14 +59,7 @@ export const RadioCardGroup: React.FC = ({ defaultValue: defaultValue || "", }} > - + {groupLabel && ( {groupLabel} diff --git a/packages/spor-react/src/theme/components/radio-card.ts b/packages/spor-react/src/theme/components/radio-card.ts index 95bad3ff9..43fbed915 100644 --- a/packages/spor-react/src/theme/components/radio-card.ts +++ b/packages/spor-react/src/theme/components/radio-card.ts @@ -5,7 +5,12 @@ import { focusVisibleStyles } from "../utils/focus-utils"; import { anatomy, mode } from "@chakra-ui/theme-tools"; import { outlineBorder } from "../utils/outline-utils"; -const parts = anatomy("radio-card").parts("container", "checked", "radioInput"); +const parts = anatomy("radio-card").parts( + "container", + "checked", + "radioInput", + "focused", +); const helpers = createMultiStyleConfigHelpers(parts.keys); const config = helpers.defineMultiStyleConfig({ @@ -17,7 +22,6 @@ const config = helpers.defineMultiStyleConfig({ display: "block", cursor: "pointer", borderRadius: "sm", - ...focusVisibleStyles(props), transitionProperty: "common", transitionDuration: "fast", _disabled: { @@ -53,9 +57,6 @@ const config = helpers.defineMultiStyleConfig({ ...baseBackground("active", props), ...baseBorder("active", props), }, - _focus: { - ...outlineBorder("focus", props), - }, }, checked: { _hover: { @@ -65,12 +66,12 @@ const config = helpers.defineMultiStyleConfig({ ...baseBackground("active", props), ...baseBorder("active", props), }, - _focus: { - outline: "4px solid", - outlineStyle: "double", - ...outlineBorder("focus", props), - outlineOffset: "-1px", - }, + }, + focused: { + outline: "4px solid", + outlineStyle: "double", + ...outlineBorder("focus", props), + outlineOffset: "-1px", }, }), floating: (props) => ({ @@ -87,9 +88,6 @@ const config = helpers.defineMultiStyleConfig({ ...floatingBackground("active", props), ...floatingBorder("active", props), }, - _focus: { - ...outlineBorder("focus", props), - }, }, checked: { _hover: { @@ -100,12 +98,12 @@ const config = helpers.defineMultiStyleConfig({ ...floatingBackground("active", props), ...floatingBorder("active", props), }, - _focus: { - outline: "4px solid", - outlineStyle: "double", - ...outlineBorder("focus", props), - outlineOffset: "-1px", - }, + }, + focused: { + outline: "4px solid", + outlineStyle: "double", + ...outlineBorder("focus", props), + outlineOffset: "-1px", }, }), }, From d36936049c827be698327b7dc3924e5a0e745e94 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:20:05 +0200 Subject: [PATCH 2/7] Fix focused on unchecked issue --- packages/spor-react/src/layout/RadioCard.tsx | 18 ++++++++++-------- .../spor-react/src/layout/RadioCardGroup.tsx | 8 +++++++- .../src/theme/components/radio-card.ts | 7 ++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/spor-react/src/layout/RadioCard.tsx b/packages/spor-react/src/layout/RadioCard.tsx index 33cc8aedc..3450d7a6f 100644 --- a/packages/spor-react/src/layout/RadioCard.tsx +++ b/packages/spor-react/src/layout/RadioCard.tsx @@ -5,7 +5,7 @@ import { forwardRef, useMultiStyleConfig, } from "@chakra-ui/react"; -import React, { useContext, useEffect, useId } from "react"; +import React, { useContext, useEffect, useId, useState } from "react"; import { RadioCardGroupContext } from "./RadioCardGroup"; /** @@ -51,8 +51,10 @@ export const RadioCard = forwardRef( const styles = useMultiStyleConfig("RadioCard", { variant }); - const [isKeyboardUser, setKeyboardUser] = React.useState(false); - const [isFocused, setFocus] = React.useState(false); + const [isKeyboardUser, setKeyboardUser] = useState(false); + const [isFocused, setFocus] = useState(false); + + const isChecked = selectedValue === value; useEffect(() => { const handleMouseDown = () => setKeyboardUser(false); @@ -73,8 +75,6 @@ export const RadioCard = forwardRef( }; }, []); - const isChecked = selectedValue === value; - useEffect(() => { if (isKeyboardUser && isChecked) { setFocus(true); @@ -86,7 +86,10 @@ export const RadioCard = forwardRef( const inputId = `radio-card-${useId()}`; return ( - + isKeyboardUser && setFocus(true)} + onBlur={() => setFocus(false)} + > isKeyboardUser && setFocus(true)} - onBlur={() => setFocus(false)} __css={{ ...styles.container, ...(isChecked && styles.checked), diff --git a/packages/spor-react/src/layout/RadioCardGroup.tsx b/packages/spor-react/src/layout/RadioCardGroup.tsx index 0b39b3df5..8eaee958c 100644 --- a/packages/spor-react/src/layout/RadioCardGroup.tsx +++ b/packages/spor-react/src/layout/RadioCardGroup.tsx @@ -1,5 +1,5 @@ import { BoxProps, Stack } from "@chakra-ui/react"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { FormLabel } from "../input"; /** @@ -44,6 +44,12 @@ export const RadioCardGroup: React.FC = ({ defaultValue || "", ); + useEffect(() => { + if (defaultValue) { + setSelectedValue(defaultValue); + } + }, [defaultValue]); + const handleChange = (value: string) => { setSelectedValue(value); onChange && onChange(value); diff --git a/packages/spor-react/src/theme/components/radio-card.ts b/packages/spor-react/src/theme/components/radio-card.ts index 43fbed915..3bd84b446 100644 --- a/packages/spor-react/src/theme/components/radio-card.ts +++ b/packages/spor-react/src/theme/components/radio-card.ts @@ -2,7 +2,7 @@ import { createMultiStyleConfigHelpers } from "@chakra-ui/react"; import { baseBackground, baseBorder, baseText } from "../utils/base-utils"; import { floatingBackground, floatingBorder } from "../utils/floating-utils"; import { focusVisibleStyles } from "../utils/focus-utils"; -import { anatomy, mode } from "@chakra-ui/theme-tools"; +import { anatomy } from "@chakra-ui/theme-tools"; import { outlineBorder } from "../utils/outline-utils"; const parts = anatomy("radio-card").parts( @@ -24,6 +24,7 @@ const config = helpers.defineMultiStyleConfig({ borderRadius: "sm", transitionProperty: "common", transitionDuration: "fast", + _disabled: { pointerEvents: "none", ...baseBackground("disabled", props), @@ -31,6 +32,10 @@ const config = helpers.defineMultiStyleConfig({ ...baseText("disabled", props), }, }, + focused: { + outline: "1px solid", + ...outlineBorder("focus", props), + }, checked: { outline: "2px solid", ...outlineBorder("focus", props), From 7d349df201789a941419dd42bea3216f4823949b Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:20:28 +0200 Subject: [PATCH 3/7] Remove useeffect --- packages/spor-react/src/layout/RadioCardGroup.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/spor-react/src/layout/RadioCardGroup.tsx b/packages/spor-react/src/layout/RadioCardGroup.tsx index 8eaee958c..1ac3ac10a 100644 --- a/packages/spor-react/src/layout/RadioCardGroup.tsx +++ b/packages/spor-react/src/layout/RadioCardGroup.tsx @@ -44,12 +44,6 @@ export const RadioCardGroup: React.FC = ({ defaultValue || "", ); - useEffect(() => { - if (defaultValue) { - setSelectedValue(defaultValue); - } - }, [defaultValue]); - const handleChange = (value: string) => { setSelectedValue(value); onChange && onChange(value); From d620e430c88f37de1fca418ca9ab1b781c967a99 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:09:12 +0200 Subject: [PATCH 4/7] Fix styling for card that is not checked but focused --- packages/spor-react/src/layout/RadioCard.tsx | 3 +- .../src/theme/components/radio-card.ts | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/spor-react/src/layout/RadioCard.tsx b/packages/spor-react/src/layout/RadioCard.tsx index 3450d7a6f..efc157ba2 100644 --- a/packages/spor-react/src/layout/RadioCard.tsx +++ b/packages/spor-react/src/layout/RadioCard.tsx @@ -111,7 +111,8 @@ export const RadioCard = forwardRef( __css={{ ...styles.container, ...(isChecked && styles.checked), - ...(isFocused && styles.focused), + ...(isFocused && !isChecked && styles.focused), + ...(isChecked && isFocused && styles.focusedChecked), }} > {children} diff --git a/packages/spor-react/src/theme/components/radio-card.ts b/packages/spor-react/src/theme/components/radio-card.ts index 3bd84b446..d21f769ba 100644 --- a/packages/spor-react/src/theme/components/radio-card.ts +++ b/packages/spor-react/src/theme/components/radio-card.ts @@ -2,21 +2,23 @@ import { createMultiStyleConfigHelpers } from "@chakra-ui/react"; import { baseBackground, baseBorder, baseText } from "../utils/base-utils"; import { floatingBackground, floatingBorder } from "../utils/floating-utils"; import { focusVisibleStyles } from "../utils/focus-utils"; -import { anatomy } from "@chakra-ui/theme-tools"; +import { anatomy, mode } from "@chakra-ui/theme-tools"; import { outlineBorder } from "../utils/outline-utils"; +import { brandBackground } from "../utils/brand-utils"; +import { tokens } from "../.."; const parts = anatomy("radio-card").parts( "container", "checked", "radioInput", "focused", + "focusedChecked", ); const helpers = createMultiStyleConfigHelpers(parts.keys); const config = helpers.defineMultiStyleConfig({ baseStyle: (props: any) => ({ container: { - border: "none", overflow: "hidden", fontSize: "inherit", display: "block", @@ -32,10 +34,6 @@ const config = helpers.defineMultiStyleConfig({ ...baseText("disabled", props), }, }, - focused: { - outline: "1px solid", - ...outlineBorder("focus", props), - }, checked: { outline: "2px solid", ...outlineBorder("focus", props), @@ -72,12 +70,23 @@ const config = helpers.defineMultiStyleConfig({ ...baseBorder("active", props), }, }, - focused: { + focusedChecked: { outline: "4px solid", outlineStyle: "double", ...outlineBorder("focus", props), outlineOffset: "-1px", }, + focused: { + outline: "2px solid", + ...outlineBorder("focus", props), + outlineOffset: "1px", + boxShadow: `inset 0 0 0 1px rgba(0, 0, 0, 0.40)`, + _hover: { + ...baseBorder("hover", props), + boxShadow: "none", + outlineOffset: "0px", + }, + }, }), floating: (props) => ({ container: { @@ -104,12 +113,23 @@ const config = helpers.defineMultiStyleConfig({ ...floatingBorder("active", props), }, }, - focused: { + focusedChecked: { outline: "4px solid", outlineStyle: "double", ...outlineBorder("focus", props), outlineOffset: "-1px", }, + focused: { + outline: "2px solid", + ...outlineBorder("focus", props), + outlineOffset: "1px", + boxShadow: `inset 0 0 0 1px rgba(0, 0, 0, 0.10)`, + _hover: { + ...floatingBorder("hover", props), + boxShadow: "md", + outlineOffset: "0px", + }, + }, }), }, }); From 313cf42f92308f8c70d4d4444edf6ea8b2a2ee09 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:13:32 +0200 Subject: [PATCH 5/7] Remove unused imports --- packages/spor-react/src/layout/RadioCardGroup.tsx | 2 +- packages/spor-react/src/theme/components/radio-card.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/spor-react/src/layout/RadioCardGroup.tsx b/packages/spor-react/src/layout/RadioCardGroup.tsx index 1ac3ac10a..0b39b3df5 100644 --- a/packages/spor-react/src/layout/RadioCardGroup.tsx +++ b/packages/spor-react/src/layout/RadioCardGroup.tsx @@ -1,5 +1,5 @@ import { BoxProps, Stack } from "@chakra-ui/react"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { FormLabel } from "../input"; /** diff --git a/packages/spor-react/src/theme/components/radio-card.ts b/packages/spor-react/src/theme/components/radio-card.ts index d21f769ba..47f4da6da 100644 --- a/packages/spor-react/src/theme/components/radio-card.ts +++ b/packages/spor-react/src/theme/components/radio-card.ts @@ -1,11 +1,8 @@ import { createMultiStyleConfigHelpers } from "@chakra-ui/react"; import { baseBackground, baseBorder, baseText } from "../utils/base-utils"; import { floatingBackground, floatingBorder } from "../utils/floating-utils"; -import { focusVisibleStyles } from "../utils/focus-utils"; -import { anatomy, mode } from "@chakra-ui/theme-tools"; +import { anatomy } from "@chakra-ui/theme-tools"; import { outlineBorder } from "../utils/outline-utils"; -import { brandBackground } from "../utils/brand-utils"; -import { tokens } from "../.."; const parts = anatomy("radio-card").parts( "container", From 59c1cc8062040c2cb4acd2b811c4d052c2fdc658 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:14:18 +0200 Subject: [PATCH 6/7] Add changeset --- .changeset/orange-coats-taste.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-coats-taste.md diff --git a/.changeset/orange-coats-taste.md b/.changeset/orange-coats-taste.md new file mode 100644 index 000000000..e6f9db517 --- /dev/null +++ b/.changeset/orange-coats-taste.md @@ -0,0 +1,5 @@ +--- +"@vygruppen/spor-react": patch +--- + +RadioCard: Fix accessibility features From 89889cc1fa6910eee56f537997994419db926189 Mon Sep 17 00:00:00 2001 From: Alice Clausen <42611957+alicemacl@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:14:35 +0200 Subject: [PATCH 7/7] Bump docs version --- apps/docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index cbd148a06..0479772ed 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "0.0.35", + "version": "0.0.36", "name": "@vygruppen/docs", "description": "The Spor documentation", "license": "MIT",