Skip to content

Commit

Permalink
Merge pull request #69 from aversini/feat!-Refactor-TextInput-for-bet…
Browse files Browse the repository at this point in the history
…ter-accessibility-

feat!: Refactor TextInput for better accessibility
  • Loading branch information
aversini authored Nov 23, 2023
2 parents 02aec86 + 887d9bf commit fb5312f
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 78 deletions.
28 changes: 13 additions & 15 deletions packages/documentation/src/stories/TextInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ const meta: Meta<typeof TextInput> = {
disabled: false,
helperText: "",
raw: false,
kind: "dark",
focus: "light",
border: "dark",
fullWidth: false,
focusKind: "light",
borderKind: "dark",
errorKind: "light",
error: false,
},
argTypes: {
Expand All @@ -35,21 +34,18 @@ const meta: Meta<typeof TextInput> = {
raw: {
control: "boolean",
},
fullWidth: {
control: "boolean",
},
error: {
control: "boolean",
},
kind: {
focusKind: {
options: ["dark", "light"],
control: { type: "radio" },
},
focus: {
borderKind: {
options: ["dark", "light"],
control: { type: "radio" },
},
border: {
errorKind: {
options: ["dark", "light"],
control: { type: "radio" },
},
Expand All @@ -62,10 +58,12 @@ type Story = StoryObj<typeof TextInput>;

export const Basic: Story = {
render: (args) => (
<form noValidate>
<div className="flex gap-2">
<TextInput {...args} />
</div>
</form>
<div className="min-h-10 bg-slate-500 p-11">
<form noValidate>
<div className="flex gap-2">
<TextInput {...args} />
</div>
</form>
</div>
),
};
7 changes: 4 additions & 3 deletions packages/ui-components/lib/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import colors from "tailwindcss/colors";

const errorColorDark = "#d80000";
const errorColorLight = "#ff3f3f";

export const tokens = {
colors: {
Expand Down Expand Up @@ -28,22 +29,22 @@ export const tokens = {
"copy-medium": colors.slate[400],
"copy-light": colors.slate[200],
"copy-error-dark": errorColorDark,
"copy-error-light": colors.slate[200],
"copy-error-light": errorColorLight,

/**
* Border tokens.
*/
"border-dark": colors.slate[900],
"border-light": colors.slate[300],
"border-error-dark": errorColorDark,
"border-error-light": errorColorDark,
"border-error-light": errorColorLight,

/**
* Focus tokens.
*/
"focus-dark": colors.slate[900],
"focus-light": colors.slate[300],
"focus-error-dark": errorColorDark,
"focus-error-light": errorColorDark,
"focus-error-light": errorColorLight,
},
};
15 changes: 7 additions & 8 deletions packages/ui-components/src/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export const TextInput = ({
error = false,
raw = false,
className,
kind = "dark",
focus = "light",
border = "dark",
fullWidth,
focusKind = "light",
borderKind = "dark",
errorKind = "light",

disabled = false,
noBorder = false,

Expand All @@ -30,12 +30,11 @@ export const TextInput = ({
className,
error,
raw,
kind,
focus,
fullWidth,
focusKind,
disabled,
noBorder,
border,
borderKind,
errorKind,
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ export type TextInputProps = {
labelId?: string;
helperText?: string;
error?: boolean;
kind?: "dark" | "light";
focus?: "dark" | "light";
border?: "dark" | "light";
focusKind?: "dark" | "light";
borderKind?: "dark" | "light";
errorKind?: "dark" | "light";
raw?: boolean;
fullWidth?: boolean;
noBorder?: boolean;
} & React.InputHTMLAttributes<HTMLInputElement>;
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("TextInput modifiers", () => {
/>,
);
const errorMessage = await screen.findByText("error message");
expect(errorMessage.className).toContain("text-copy-error-dark");
expect(errorMessage.className).toContain("text-copy-error-light");
});

it("should render a text input with no borders", async () => {
Expand Down Expand Up @@ -86,7 +86,7 @@ describe("TextInput accessibility", () => {
/>,
);
const errorMessage = await screen.findByText("error message");
expect(errorMessage.className).toContain("text-copy-error-dark");
expect(errorMessage.className).toContain("text-copy-error-light");

const input = await screen.findByLabelText("hello world");
expect(input.getAttribute("aria-invalid")).toBe("true");
Expand Down
117 changes: 74 additions & 43 deletions packages/ui-components/src/components/TextInput/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,108 +10,139 @@ import {
type getTextInputClassesProps = {
className?: string;
raw: boolean;
kind: string;
focus: string;
border: string;
focusKind: string;
borderKind: string;
errorKind: string;
disabled: boolean;
fullWidth?: boolean;
slim?: boolean;
noBorder: boolean;
error: boolean;
};

const getTextInputBaseClasses = () => {
return "rounded-md text-base";
return "rounded-md text-base h-20";
};

const getTextInputSizesClasses = () => {
return "px-4 py-4";
return "px-4";
};

const getTextInputColorClasses = (kind: string) => {
return `bg-surface-${kind} text-copy-light caret-slate-100`;
const getTextInputColorClasses = () => {
return `bg-surface-dark text-copy-light caret-slate-100`;
};

const getTextInputFocusClasses = (focus: string, error: boolean) => {
const getTextInputFocusClasses = ({
focusKind,
error,
errorKind,
}: {
focusKind: string;
error: boolean;
errorKind: string;
}) => {
return clsx("focus:outline-none focus:ring-offset-0", {
"focus:ring-2": !error,
"focus:ring-1": error,
[`focus:ring-focus-${focus}`]: !error,
[`focus:ring-focus-error-${focus}`]: error,
[`focus:ring-focus-${focusKind}`]: !error,
[`focus:ring-focus-error-${errorKind}`]: error,
});
};

const getTextInputBorderClasses = (
noBorder: boolean,
error: boolean,
border: string,
) => {
const getTextInputBorderClasses = ({
noBorder,
error,
borderKind,
errorKind,
}: {
noBorder: boolean;
error: boolean;
borderKind: string;
errorKind: string;
}) => {
const borderOpacity = noBorder ? "0" : "100";
return error
? "border-2 border-border-error-dark"
: `border-2 border-border-${border}/${borderOpacity}`;
? `border-2 border-border-error-${errorKind}`
: `border-2 border-border-${borderKind}/${borderOpacity}`;
};

const getTextInputLabelClasses = (
kind: string,
disabled: boolean,
raw: boolean,
) => {
const getTextInputLabelClasses = ({
disabled,
raw,
error,
errorKind,
}: {
disabled: boolean;
raw: boolean;
error: boolean;
errorKind: string;
}) => {
return raw
? ""
: clsx("cursor-text", {
"text-copy-medium": kind === "dark",
"text-copy-light": kind === "light",
: clsx("cursor-text font-medium", {
[`text-copy-error-${errorKind}`]: error,
"text-copy-medium": !error,
"cursor-not-allowed opacity-50": disabled,
});
};

const getTextInputHelperTextClasses = (error: boolean, raw: boolean) => {
const getTextInputHelperTextClasses = ({
error,
raw,
errorKind,
}: {
error: boolean;
raw: boolean;
errorKind: string;
}) => {
return raw
? undefined
: clsx(TEXT_INPUT_HELPER_TEXT_CLASSNAME, "text-xs", {
"text-copy-error-dark": error,
: clsx(TEXT_INPUT_HELPER_TEXT_CLASSNAME, "font-medium", {
[`text-copy-error-${errorKind}`]: error,
"text-copy-medium": !error,
});
};

export const getTextInputClasses = ({
className,
raw,
kind,
focus,
border,
focusKind,
borderKind,
errorKind,
disabled,
fullWidth,
noBorder,
error,
}: getTextInputClassesProps) => {
const wrapper = raw
? undefined
: clsx(TEXT_INPUT_WRAPPER_CLASSNAME, {
"w-full": fullWidth,
});
const wrapper = raw ? undefined : `${TEXT_INPUT_WRAPPER_CLASSNAME} w-full`;

const input = raw
? className
: clsx(
TEXT_INPUT_CLASSNAME,
className,
getTextInputFocusClasses(focus, error),
getTextInputBaseClasses(),
getTextInputSizesClasses(),
getTextInputColorClasses(kind),
getTextInputBorderClasses(noBorder, error, border),
getTextInputColorClasses(),
getTextInputFocusClasses({ focusKind, error, errorKind }),
getTextInputBorderClasses({ noBorder, error, borderKind, errorKind }),
{
"disabled:cursor-not-allowed disabled:opacity-50": disabled,
},
);

const topLabel = raw ? undefined : VISUALLY_HIDDEN_CLASSNAME;

const bottomLabel = getTextInputLabelClasses(kind, disabled, raw);
const bottomLabel = getTextInputLabelClasses({
disabled,
raw,
error,
errorKind,
});

const helperText = getTextInputHelperTextClasses(error, raw);
const helperText = getTextInputHelperTextClasses({
error,
raw,
errorKind,
});

return {
wrapper,
Expand Down
6 changes: 3 additions & 3 deletions packages/ui-components/src/components/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
.av-text-input-wrapper label {
position: absolute;
/* move the label inline */
transform: translate(18px, 18px) scale(1);
transform: translate(18px, 28px) scale(1);
transform-origin: top left;
transition: all 0.2s ease-out;
}
Expand All @@ -53,12 +53,12 @@
.av-text-input:focus + label,
.av-text-input:not(:placeholder-shown) + label {
/* move the label up */
transform: translate(18px, 2px) scale(0.65);
transform: translate(18px, 2px) scale(0.75);
}

.av-text-input-helper-text {
position: absolute;
transform: translate(18px, 61px) scale(1);
transform: translate(18px, 59px) scale(0.75);
transform-origin: top left;
transition: all 0.2s ease-out;
}
Expand Down

0 comments on commit fb5312f

Please sign in to comment.