From 0f46050ff05c81989b852fdce94919292d93c1e3 Mon Sep 17 00:00:00 2001 From: Arno V Date: Wed, 22 Nov 2023 08:36:04 -0500 Subject: [PATCH 1/5] feat: adding aria-live region on error --- .../src/components/TextInput/TextInput.tsx | 8 ++++ .../TextInput/__tests__/TextInput.test.tsx | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/ui-components/src/components/TextInput/TextInput.tsx b/packages/ui-components/src/components/TextInput/TextInput.tsx index 1d3926a7..6d3fc8ac 100644 --- a/packages/ui-components/src/components/TextInput/TextInput.tsx +++ b/packages/ui-components/src/components/TextInput/TextInput.tsx @@ -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"; @@ -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, @@ -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 && ( @@ -70,6 +73,11 @@ export const TextInput = ({ {helperText} )} + {error && helperText && ( + + {liveErrorMessage} + + )} ); }; diff --git a/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx b/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx index 8691df3a..f3e93f18 100644 --- a/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx +++ b/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx @@ -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( + , + ); + 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( + , + ); + 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(""); + }); +}); From 7b4817d4d81918677b312efd9b5bdd3054df6f0e Mon Sep 17 00:00:00 2001 From: Arno V Date: Wed, 22 Nov 2023 08:45:54 -0500 Subject: [PATCH 2/5] chore: minor refactor --- .../ui-components/src/common/constants.ts | 1 - .../TextInput/__tests__/TextInput.test.tsx | 2 +- .../src/components/TextInput/utilities.ts | 21 ++++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ui-components/src/common/constants.ts b/packages/ui-components/src/common/constants.ts index 374ccaf3..597b707b 100644 --- a/packages/ui-components/src/common/constants.ts +++ b/packages/ui-components/src/common/constants.ts @@ -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"; diff --git a/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx b/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx index f3e93f18..05135ad2 100644 --- a/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx +++ b/packages/ui-components/src/components/TextInput/__tests__/TextInput.test.tsx @@ -44,7 +44,7 @@ describe("TextInput modifiers", () => { it("should render a raw text input with no styling", async () => { render(); const input = await screen.findByTestId("txtnpt-1"); - expect(input.className).toBe("av-raw"); + expect(input.className).toBe(""); }); }); diff --git a/packages/ui-components/src/components/TextInput/utilities.ts b/packages/ui-components/src/components/TextInput/utilities.ts index 058ef70e..8d466f48 100644 --- a/packages/ui-components/src/components/TextInput/utilities.ts +++ b/packages/ui-components/src/components/TextInput/utilities.ts @@ -1,7 +1,6 @@ import clsx from "clsx"; import { - RAW_CLASSNAME, TEXT_INPUT_CLASSNAME, TEXT_INPUT_HELPER_TEXT_CLASSNAME, TEXT_INPUT_WRAPPER_CLASSNAME, @@ -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 = ({ @@ -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, @@ -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, From 76ca7e11cdf9016237b52dd6d473d51c4017821a Mon Sep 17 00:00:00 2001 From: Arno V Date: Wed, 22 Nov 2023 08:56:07 -0500 Subject: [PATCH 3/5] refactor: caught react-dom-server in the final bundle --- bundlemon.config.cjs | 4 +++- packages/ui-components/vite.config.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bundlemon.config.cjs b/bundlemon.config.cjs index 3944c63f..9d2a17b8 100644 --- a/bundlemon.config.cjs +++ b/bundlemon.config.cjs @@ -9,11 +9,13 @@ module.exports = { }, { path: "assets/index.js", - maxSize: "60kb", + maxSize: "10kb", + maxPercentIncrease: 5, }, { path: "assets/style.css", maxSize: "4kb", + maxPercentIncrease: 5, }, { path: "assets/**/*.{png,svg}", diff --git a/packages/ui-components/vite.config.ts b/packages/ui-components/vite.config.ts index e159bff4..0621c7b0 100644 --- a/packages/ui-components/vite.config.ts +++ b/packages/ui-components/vite.config.ts @@ -32,6 +32,8 @@ export default defineConfig({ "@tailwindcss/typography", "react", "react/jsx-runtime", + "react-dom", + "react-dom/server", "tailwindcss", ], output: { From 9341605ae63e4fd37df9b31c1348c98622b634d6 Mon Sep 17 00:00:00 2001 From: Arno V Date: Wed, 22 Nov 2023 10:26:17 -0500 Subject: [PATCH 4/5] fix: bundlesize incorrect values --- .eslintrc.cjs | 25 +++++++++++++++++++ bundlemon.config.cjs | 2 ++ configuration/eslint-rules/best-practices.cjs | 2 ++ .../eslint-rules/possible-errors.cjs | 2 ++ configuration/eslint-rules/variables.cjs | 2 ++ configuration/lint-staged.config.cjs | 2 ++ configuration/vite.common.js | 9 +++++++ packages/bundlesize/vite.config.ts | 11 +++----- packages/ui-components/vite.config.ts | 12 +++------ 9 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 .eslintrc.cjs create mode 100644 configuration/vite.common.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..39dab947 --- /dev/null +++ b/.eslintrc.cjs @@ -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", + }, +}; diff --git a/bundlemon.config.cjs b/bundlemon.config.cjs index 9d2a17b8..691fb976 100644 --- a/bundlemon.config.cjs +++ b/bundlemon.config.cjs @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ + module.exports = { reportOutput: ["github"], baseDir: "./packages/bundlesize/dist", diff --git a/configuration/eslint-rules/best-practices.cjs b/configuration/eslint-rules/best-practices.cjs index 31dd3ec8..4de896a6 100644 --- a/configuration/eslint-rules/best-practices.cjs +++ b/configuration/eslint-rules/best-practices.cjs @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ + module.exports = { rules: { // enforce return statements in callbacks of array methods diff --git a/configuration/eslint-rules/possible-errors.cjs b/configuration/eslint-rules/possible-errors.cjs index 83b3c461..d37af721 100644 --- a/configuration/eslint-rules/possible-errors.cjs +++ b/configuration/eslint-rules/possible-errors.cjs @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ + module.exports = { rules: { // disallow assignment operators in conditional expressions diff --git a/configuration/eslint-rules/variables.cjs b/configuration/eslint-rules/variables.cjs index 7227478e..5abcca87 100644 --- a/configuration/eslint-rules/variables.cjs +++ b/configuration/eslint-rules/variables.cjs @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ + module.exports = { rules: { // disallow catch clause parameters from shadowing variables in the outer scope diff --git a/configuration/lint-staged.config.cjs b/configuration/lint-staged.config.cjs index bdc63e4e..2cd6ab16 100644 --- a/configuration/lint-staged.config.cjs +++ b/configuration/lint-staged.config.cjs @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ + module.exports = { "*.{ts,js,tsx,jsx}": [ "eslint --ext ts,tsx --report-unused-disable-directives --fix", diff --git a/configuration/vite.common.js b/configuration/vite.common.js new file mode 100644 index 00000000..b7a90683 --- /dev/null +++ b/configuration/vite.common.js @@ -0,0 +1,9 @@ +export const externalDependencies = [ + "@floating-ui/react", + "@tailwindcss/typography", + "react", + "react/jsx-runtime", + "react-dom", + "react-dom/server", + "tailwindcss", +]; diff --git a/packages/bundlesize/vite.config.ts b/packages/bundlesize/vite.config.ts index 361652a5..678353ce 100644 --- a/packages/bundlesize/vite.config.ts +++ b/packages/bundlesize/vite.config.ts @@ -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() @@ -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", diff --git a/packages/ui-components/vite.config.ts b/packages/ui-components/vite.config.ts index 0621c7b0..d0c2fc38 100644 --- a/packages/ui-components/vite.config.ts +++ b/packages/ui-components/vite.config.ts @@ -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() @@ -27,15 +29,7 @@ export default defineConfig({ fileName: (format) => `index.${format}.js`, }, rollupOptions: { - external: [ - "@floating-ui/react", - "@tailwindcss/typography", - "react", - "react/jsx-runtime", - "react-dom", - "react-dom/server", - "tailwindcss", - ], + external: externalDependencies, output: { assetFileNames: "style[extname]", entryFileNames: "[name].js", From 5303b5055f7b2fd0e3541c2946511fdda1f0c7d7 Mon Sep 17 00:00:00 2001 From: Arno V Date: Wed, 22 Nov 2023 10:32:12 -0500 Subject: [PATCH 5/5] Update bundlemon.config.cjs --- bundlemon.config.cjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bundlemon.config.cjs b/bundlemon.config.cjs index 691fb976..cc717dbb 100644 --- a/bundlemon.config.cjs +++ b/bundlemon.config.cjs @@ -12,12 +12,10 @@ module.exports = { { path: "assets/index.js", maxSize: "10kb", - maxPercentIncrease: 5, }, { path: "assets/style.css", maxSize: "4kb", - maxPercentIncrease: 5, }, { path: "assets/**/*.{png,svg}",