Skip to content

Commit

Permalink
🪟 🎨 🧹 Migrate Input and TextArea to SCSS (airbytehq#16378)
Browse files Browse the repository at this point in the history
* Migrate TextArea component to SCSS and add Storybook

* Move Input styles to SCSS, add Storybook

* Fix Input stylelint issues

* Fix hover selector on Input container to avoid hovering on focus

* Fix Input focus test by using style file

* Add missing & to Textarea style

* Fix styleint inssue in Input

* Move input testid before props
  • Loading branch information
edmundito authored and robbinhan committed Sep 29, 2022
1 parent 8d219a0 commit 9c1e1ba
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 119 deletions.
78 changes: 78 additions & 0 deletions airbyte-webapp/src/components/base/Input/Input.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@use "../../../scss/colors";

.container {
width: 100%;
position: relative;
background-color: colors.$grey-50;
border: 1px solid colors.$grey-50;
border-radius: 4px;

&.light {
background-color: colors.$white;
}

&.error {
background-color: colors.$grey-100;
border-color: colors.$red;
}

&:not(.disabled, .focused):hover {
background-color: colors.$grey-100;
border-color: colors.$grey-100;

&.light {
background-color: colors.$white;
}

&.error {
border-color: colors.$red;
}
}

&.focused {
background-color: colors.$primaryColor12;
border-color: colors.$blue;

&.light {
background-color: colors.$white;
}
}
}

.input {
outline: none;
width: 100%;
padding: 7px 8px;
font-size: 14px;
line-height: 20px;
font-weight: normal;
border: none;
background: none;
color: colors.$dark-blue;
caret-color: colors.$blue;

&:not(.disabled).password {
width: calc(100% - 22px);
}

&::placeholder {
color: colors.$grey-300;
}

&.disabled {
pointer-events: none;
color: colors.$grey-400;
}
}

button.visibilityButton {
position: absolute;
right: 0;
top: 0;
display: flex;
height: 100%;
width: 30px;
align-items: center;
justify-content: center;
border: none;
}
6 changes: 4 additions & 2 deletions airbyte-webapp/src/components/base/Input/Input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { act } from "react-dom/test-utils";
import { render } from "test-utils/testutils";

import { Input } from "./Input";
// eslint-disable-next-line css-modules/no-unused-class
import styles from "./Input.module.scss";

describe("<Input />", () => {
test("renders text input", async () => {
Expand Down Expand Up @@ -117,7 +119,7 @@ describe("<Input />", () => {
fireEvent.focus(inputEl);
fireEvent.focus(inputEl);

expect(getByTestId("input-container")).toHaveClass("input-container--focused");
expect(getByTestId("input-container")).toHaveClass(styles.focused);
});

test("does not have focused class after blur", async () => {
Expand All @@ -128,7 +130,7 @@ describe("<Input />", () => {
fireEvent.blur(inputEl);
fireEvent.blur(inputEl);

expect(getByTestId("input-container")).not.toHaveClass("input-container--focused");
expect(getByTestId("input-container")).not.toHaveClass(styles.focused);
});

test("calls onFocus if passed as prop", async () => {
Expand Down
105 changes: 27 additions & 78 deletions airbyte-webapp/src/components/base/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,85 +4,16 @@ import classNames from "classnames";
import React, { useCallback, useRef, useState } from "react";
import { useIntl } from "react-intl";
import { useToggle } from "react-use";
import styled from "styled-components";
import { Theme } from "theme";

import Button from "../Button";

type IStyleProps = InputProps & { theme: Theme };

const getBackgroundColor = (props: IStyleProps) => {
if (props.error) {
return props.theme.greyColor10;
} else if (props.light) {
return props.theme.whiteColor;
}

return props.theme.greyColor0;
};
import styles from "./Input.module.scss";

export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: boolean;
light?: boolean;
}

const InputContainer = styled.div<InputProps>`
width: 100%;
position: relative;
background: ${(props) => getBackgroundColor(props)};
border: 1px solid ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor0)};
border-radius: 4px;
${({ disabled, theme, light, error }) =>
!disabled &&
`
&:hover {
background: ${light ? theme.whiteColor : theme.greyColor20};
border-color: ${error ? theme.dangerColor : theme.greyColor20};
}
`}
&.input-container--focused {
background: ${({ theme, light }) => (light ? theme.whiteColor : theme.primaryColor12)};
border-color: ${({ theme }) => theme.primaryColor};
}
`;

const InputComponent = styled.input<InputProps & { isPassword?: boolean }>`
outline: none;
width: ${({ isPassword, disabled }) => (isPassword && !disabled ? "calc(100% - 22px)" : "100%")};
padding: 7px 8px 7px 8px;
font-size: 14px;
line-height: 20px;
font-weight: normal;
border: none;
background: none;
color: ${({ theme }) => theme.textColor};
caret-color: ${({ theme }) => theme.primaryColor};
&::placeholder {
color: ${({ theme }) => theme.greyColor40};
}
&:disabled {
pointer-events: none;
color: ${({ theme }) => theme.greyColor55};
}
`;

const VisibilityButton = styled(Button)`
position: absolute;
right: 0px;
top: 0;
display: flex;
height: 100%;
width: 30px;
align-items: center;
justify-content: center;
border: none;
`;

const Input: React.FC<InputProps> = ({ ...props }) => {
export const Input: React.FC<InputProps> = ({ light, error, ...props }) => {
const { formatMessage } = useIntl();

const inputRef = useRef<HTMLInputElement | null>(null);
Expand Down Expand Up @@ -137,15 +68,34 @@ const Input: React.FC<InputProps> = ({ ...props }) => {
};

return (
<InputContainer
className={classNames("input-container", { "input-container--focused": focused })}
<div
className={classNames(styles.container, {
[styles.disabled]: props.disabled,
[styles.focused]: focused,
[styles.light]: light,
[styles.error]: error,
})}
data-testid="input-container"
onFocus={onContainerFocus}
onBlur={onContainerBlur}
>
<InputComponent data-testid="input" {...props} ref={inputRef} type={type} isPassword={isPassword} />
<input
data-testid="input"
{...props}
ref={inputRef}
type={type}
className={classNames(
styles.input,
{
[styles.disabled]: props.disabled,
[styles.password]: isPassword,
},
props.className
)}
/>
{isVisibilityButtonVisible ? (
<VisibilityButton
<Button
className={styles.visibilityButton}
ref={buttonRef}
iconOnly
onClick={() => {
Expand All @@ -159,11 +109,10 @@ const Input: React.FC<InputProps> = ({ ...props }) => {
data-testid="toggle-password-visibility-button"
>
<FontAwesomeIcon icon={isContentVisible ? faEyeSlash : faEye} fixedWidth />
</VisibilityButton>
</Button>
) : null}
</InputContainer>
</div>
);
};

export default Input;
export { Input };
19 changes: 19 additions & 0 deletions airbyte-webapp/src/components/base/Input/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";

import Input from "./Input";

export default {
title: "Ui/Input",
component: Input,
argTypes: {
disabled: { control: "boolean" },
type: { control: { type: "select", options: ["text", "number", "password"] } },
},
} as ComponentMeta<typeof Input>;

const Template: ComponentStory<typeof Input> = (args) => <Input {...args} />;

export const Primary = Template.bind({});
Primary.args = {
placeholder: "Enter text here...",
};
52 changes: 52 additions & 0 deletions airbyte-webapp/src/components/base/TextArea/TextArea.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@use "../../../scss/colors";
@use "../../../scss/variables";

.textarea {
outline: none;
resize: none;
width: 100%;
padding: 7px 8px;
border-radius: 4px;
font-size: 14px;
line-height: 20px;
font-weight: normal;
border: 1px solid colors.$grey-50;
background-color: colors.$grey-50;
color: colors.$dark-blue;
caret-color: colors.$blue;

&.error {
border-color: colors.$red;
}

&::placeholder {
color: colors.$grey-300;
}

&:hover {
background-color: colors.$grey-100;
border-color: colors.$grey-100;

&.light {
background-color: colors.$white;
}

&.error {
border-color: colors.$red;
}
}

&:focus {
background-color: colors.$primaryColor12;
border-color: colors.$blue;

&.light {
background-color: colors.$white;
}
}

&:disabled {
pointer-events: none;
color: colors.$grey-400;
}
}
59 changes: 20 additions & 39 deletions airbyte-webapp/src/components/base/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
import classNames from "classnames";
import React from "react";
import styled from "styled-components";

type TextAreaProps = {
import styles from "./TextArea.module.scss";

interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
error?: boolean;
light?: boolean;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

const TextArea = styled.textarea<TextAreaProps>`
outline: none;
resize: none;
width: 100%;
padding: 7px 8px;
border-radius: 4px;
font-size: 14px;
line-height: 20px;
font-weight: normal;
border: 1px solid ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor0)};
background: ${({ theme }) => theme.greyColor0};
color: ${({ theme }) => theme.textColor};
caret-color: ${({ theme }) => theme.primaryColor};
&::placeholder {
color: ${({ theme }) => theme.greyColor40};
}
&:hover {
background: ${({ theme, light }) => (light ? theme.whiteColor : theme.greyColor20)};
border-color: ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor20)};
}
&:focus {
background: ${({ theme, light }) => (light ? theme.whiteColor : theme.primaryColor12)};
border-color: ${({ theme }) => theme.primaryColor};
}
&:disabled {
pointer-events: none;
color: ${({ theme }) => theme.greyColor55};
}
`;
}

export { TextArea };
export type { TextAreaProps };
export const TextArea: React.FC<TextAreaProps> = ({ error, light, children, className, ...textAreaProps }) => (
<textarea
{...textAreaProps}
className={classNames(
styles.textarea,
{
[styles.error]: error,
[styles.light]: light,
},
className
)}
>
{children}
</textarea>
);
Loading

0 comments on commit 9c1e1ba

Please sign in to comment.