Skip to content

Commit

Permalink
Merge pull request #66 from aversini/feat-adding-aria-live-region-on-…
Browse files Browse the repository at this point in the history
…error

feat: adding aria-live region on error
  • Loading branch information
aversini authored Nov 22, 2023
2 parents 32cb46b + 5303b50 commit 5a52cb9
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 28 deletions.
25 changes: 25 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier",
"./configuration/eslint-rules/best-practices.cjs",
"./configuration/eslint-rules/possible-errors.cjs",
"./configuration/eslint-rules/variables.cjs",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh", "simple-import-sort"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
};
4 changes: 3 additions & 1 deletion bundlemon.config.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-undef */

module.exports = {
reportOutput: ["github"],
baseDir: "./packages/bundlesize/dist",
Expand All @@ -9,7 +11,7 @@ module.exports = {
},
{
path: "assets/index.js",
maxSize: "60kb",
maxSize: "10kb",
},
{
path: "assets/style.css",
Expand Down
2 changes: 2 additions & 0 deletions configuration/eslint-rules/best-practices.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-undef */

module.exports = {
rules: {
// enforce return statements in callbacks of array methods
Expand Down
2 changes: 2 additions & 0 deletions configuration/eslint-rules/possible-errors.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-undef */

module.exports = {
rules: {
// disallow assignment operators in conditional expressions
Expand Down
2 changes: 2 additions & 0 deletions configuration/eslint-rules/variables.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-undef */

module.exports = {
rules: {
// disallow catch clause parameters from shadowing variables in the outer scope
Expand Down
2 changes: 2 additions & 0 deletions configuration/lint-staged.config.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-undef */

module.exports = {
"*.{ts,js,tsx,jsx}": [
"eslint --ext ts,tsx --report-unused-disable-directives --fix",
Expand Down
9 changes: 9 additions & 0 deletions configuration/vite.common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const externalDependencies = [
"@floating-ui/react",
"@tailwindcss/typography",
"react",
"react/jsx-runtime",
"react-dom",
"react-dom/server",
"tailwindcss",
];
11 changes: 3 additions & 8 deletions packages/bundlesize/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fs from "fs-extra";
import { defineConfig } from "vite";

import { externalDependencies } from "../../configuration/vite.common";

const packageJson = fs.readJSONSync("package.json");

const buildTime = new Date()
Expand All @@ -23,14 +25,7 @@ export default defineConfig({
},
build: {
rollupOptions: {
external: [
"@floating-ui/react",
"@tailwindcss/typography",
"react",
"react/jsx-runtime",
"react-dom",
"tailwindcss",
],
external: externalDependencies,
output: {
assetFileNames: "assets/style[extname]",
entryFileNames: "assets/[name].js",
Expand Down
1 change: 0 additions & 1 deletion packages/ui-components/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ export const TEXT_INPUT_CLASSNAME = "av-text-input";
export const TEXT_INPUT_WRAPPER_CLASSNAME = "av-text-input-wrapper";
export const TEXT_INPUT_HELPER_TEXT_CLASSNAME = "av-text-input-helper-text";

export const RAW_CLASSNAME = "av-raw";
export const VISUALLY_HIDDEN_CLASSNAME = "av-visually-hidden";
8 changes: 8 additions & 0 deletions packages/ui-components/src/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import useUniqueId from "../../common/hooks/useUniqueId";
import { LiveRegion } from "../private/LiveRegion/LiveRegion";
import type { TextInputProps } from "./TextInputTypes";
import { getTextInputClasses } from "./utilities";

Expand All @@ -24,6 +25,7 @@ export const TextInput = ({
...extraProps
}: TextInputProps) => {
const inputId = useUniqueId({ id, prefix: "av-text-input-" });
const liveErrorMessage = `${name} error, ${helperText}`;
const textInputClassName = getTextInputClasses({
className,
error,
Expand Down Expand Up @@ -53,6 +55,7 @@ export const TextInput = ({
placeholder={!raw ? " " : undefined}
disabled={disabled}
{...(helperText && { "aria-describedby": `${inputId}-helper` })}
{...(error && { "aria-invalid": "true" })}
className={textInputClassName.input}
/>
{!raw && (
Expand All @@ -70,6 +73,11 @@ export const TextInput = ({
{helperText}
</div>
)}
{error && helperText && (
<LiveRegion politeness="polite" clearAnnouncementDelay={500}>
{liveErrorMessage}
</LiveRegion>
)}
</span>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("TextInput modifiers", () => {
it("should render a raw text input with no styling", async () => {
render(<TextInput label="toto" name="toto" raw data-testid="txtnpt-1" />);
const input = await screen.findByTestId("txtnpt-1");
expect(input.className).toBe("av-raw");
expect(input.className).toBe("");
});
});

Expand Down Expand Up @@ -74,3 +74,42 @@ describe("TextInput methods", () => {
expect(spyOnChange).toHaveBeenCalledTimes(2);
});
});

describe("TextInput accessibility", () => {
it("should render a text input with an error message", async () => {
render(
<TextInput
error
helperText="error message"
label="hello world"
name="toto"
/>,
);
const errorMessage = await screen.findByText("error message");
expect(errorMessage.className).toContain("text-copy-error-dark");

const input = await screen.findByLabelText("hello world");
expect(input.getAttribute("aria-invalid")).toBe("true");
expect(input.getAttribute("aria-describedby")).toContain("av-text-input-");
expect(input.getAttribute("aria-describedby")).toContain("-helper");
});

it("should render a text input with a live region update", () => {
vi.useFakeTimers();
const clearTimeout = 500;

render(
<TextInput
error
helperText="error message"
label="hello world"
name="toto"
/>,
);
const liveRegion = screen.getByText("toto error, error message");
expect(liveRegion.getAttribute("aria-live")).toBe("polite");
expect(liveRegion.textContent).toBe("toto error, error message");
vi.advanceTimersByTime(clearTimeout);
expect(liveRegion.textContent).toBe("");
});
});
21 changes: 11 additions & 10 deletions packages/ui-components/src/components/TextInput/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import clsx from "clsx";

import {
RAW_CLASSNAME,
TEXT_INPUT_CLASSNAME,
TEXT_INPUT_HELPER_TEXT_CLASSNAME,
TEXT_INPUT_WRAPPER_CLASSNAME,
Expand Down Expand Up @@ -67,11 +66,13 @@ const getTextInputLabelClasses = (
});
};

const getTextInputHelperTextClasses = (error: boolean) => {
return clsx(TEXT_INPUT_HELPER_TEXT_CLASSNAME, "text-xs", {
"text-copy-error-dark": error,
"text-copy-medium": !error,
});
const getTextInputHelperTextClasses = (error: boolean, raw: boolean) => {
return raw
? undefined
: clsx(TEXT_INPUT_HELPER_TEXT_CLASSNAME, "text-xs", {
"text-copy-error-dark": error,
"text-copy-medium": !error,
});
};

export const getTextInputClasses = ({
Expand All @@ -86,13 +87,13 @@ export const getTextInputClasses = ({
error,
}: getTextInputClassesProps) => {
const wrapper = raw
? clsx(RAW_CLASSNAME)
? undefined
: clsx(TEXT_INPUT_WRAPPER_CLASSNAME, {
"w-full": fullWidth,
});

const input = raw
? clsx(RAW_CLASSNAME, className)
? className
: clsx(
TEXT_INPUT_CLASSNAME,
className,
Expand All @@ -106,11 +107,11 @@ export const getTextInputClasses = ({
},
);

const topLabel = raw ? "" : VISUALLY_HIDDEN_CLASSNAME;
const topLabel = raw ? undefined : VISUALLY_HIDDEN_CLASSNAME;

const bottomLabel = getTextInputLabelClasses(kind, disabled, raw);

const helperText = getTextInputHelperTextClasses(error);
const helperText = getTextInputHelperTextClasses(error, raw);

return {
wrapper,
Expand Down
10 changes: 3 additions & 7 deletions packages/ui-components/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { resolve } from "node:path";
import fs from "fs-extra";
import { defineConfig } from "vite";

import { externalDependencies } from "../../configuration/vite.common";

const packageJson = fs.readJSONSync("package.json");

const buildTime = new Date()
Expand All @@ -27,13 +29,7 @@ export default defineConfig({
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: [
"@floating-ui/react",
"@tailwindcss/typography",
"react",
"react/jsx-runtime",
"tailwindcss",
],
external: externalDependencies,
output: {
assetFileNames: "style[extname]",
entryFileNames: "[name].js",
Expand Down

0 comments on commit 5a52cb9

Please sign in to comment.