Skip to content

Commit

Permalink
Merge pull request #347 from dcos-labs/mp/feat/DCOS-54882-textarea-co…
Browse files Browse the repository at this point in the history
…mponent

DCOS-54882: add textarea component
  • Loading branch information
mperrotti authored Jun 18, 2019
2 parents 6f13af4 + 27e5c2a commit 21cc7f9
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export {
NumberCell
} from "./table";
export { TextInput, TextInputWithIcon } from "./textInput";
export { Textarea } from "./textarea";
export { Toaster, Toast } from "./toaster";
export { ToggleContent } from "./toggleContent";
export { ToggleInput } from "./toggleInput";
Expand Down
5 changes: 5 additions & 0 deletions packages/textarea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Textarea

A Textarea is used to input a large amount of text data in a form field. They're very similar to a regular text input, but they support multiple lines of text.

By default, the Textarea height is big enough to hold 3 lines of text. Adjust this using the `rows` prop to give users a hint of how much content the field expects.
114 changes: 114 additions & 0 deletions packages/textarea/components/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as React from "react";
import { InputAppearance } from "../../shared/types/inputAppearance";
import FormFieldWrapper from "../../shared/components/FormFieldWrapper";
import { cx } from "emotion";
import {
inputReset,
tintText,
visuallyHidden
} from "../../shared/styles/styleUtils";
import {
getInputAppearanceStyle,
inputContainer,
getLabelStyle
} from "../../shared/styles/formStyles";
import { textarea } from "../style";
import { themeError } from "../../design-tokens/build/js/designTokens";

export interface TextareaProps extends React.HTMLProps<HTMLTextAreaElement> {
/**
* Unique identifier used for the form textarea element
*/
id: string;
/**
* Sets the current appearance of the component. This defaults to InputAppearance.Standard, but supports `InputAppearance.Error` & `InputAppearance.Success` appearances as well.
*/
appearance: InputAppearance;
/**
* Sets the contents of the label. This can be a `string` or any `ReactNode`.
*/
inputLabel: React.ReactNode;
/**
* Defaults to `true`, but can be set to `false` to visibly hide the `Textarea`'s label. The `inputLabel` should still be set even when hidden for accessibility support.
*/
showInputLabel: boolean;
/**
* Text or a ReactNode that is displayed directly under the textarea with additional information about the expected input.
*/
hintContent?: React.ReactNode;
/**
* Sets the contents for validation errors. This will be displayed below the textarea element. Errors are only visible when the `Textarea` appearance is also set to `InputAppearance.Error`.
*/
errors?: React.ReactNode[];
}

class Textarea extends React.PureComponent<TextareaProps, {}> {
public static defaultProps: Partial<TextareaProps> = {
appearance: InputAppearance.Standard,
showInputLabel: true,
rows: 3
};

public render() {
const {
appearance,
errors,
hintContent,
id,
inputLabel,
required,
showInputLabel,
value,
...other
} = this.props;
const hasError = appearance === InputAppearance.Error;
let { onChange } = other;
if (onChange == null && value != null) {
onChange = Function.prototype as (
event: React.FormEvent<HTMLTextAreaElement>
) => void;
}

return (
<FormFieldWrapper id={id} errors={errors} hintContent={hintContent}>
{({ getValidationErrors, getHintContent, isValid, describedByIds }) => (
<div>
<label
className={cx(getLabelStyle(hasError), {
[visuallyHidden]: !showInputLabel
})}
htmlFor={id}
>
{inputLabel}
{required ? (
<span className={cx(tintText(themeError))}> *</span>
) : null}
</label>
<textarea
aria-invalid={!isValid}
aria-describedby={describedByIds}
value={value}
id={id}
className={cx(
inputReset,
inputContainer,
getInputAppearanceStyle(this.getInputAppearance()),
textarea
)}
required={required}
{...{ ...other, onChange }}
/>
{getHintContent}
{hasError && getValidationErrors}
</div>
)}
</FormFieldWrapper>
);
}

private getInputAppearance(): string {
return this.props.disabled ? "disabled" : this.props.appearance;
}
}

export default Textarea;
1 change: 1 addition & 0 deletions packages/textarea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Textarea } from "./components/Textarea";
166 changes: 166 additions & 0 deletions packages/textarea/stories/Textarea.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import * as React from "react";
import { storiesOf } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import { withReadme } from "storybook-readme";
import { Textarea } from "../index";
import styled from "react-emotion";
import { InputAppearance } from "../../shared/types/inputAppearance";

const readme = require("../README.md");

const InputStoryWrapper = styled("div")`
max-width: 300px;
& > div {
margin-bottom: 1.5em;
}
`;

storiesOf("Forms/Textarea", module)
.addDecorator(withReadme([readme]))
.add("default", () => (
<InputStoryWrapper>
<div>
<Textarea
id="standard"
inputLabel="Standard"
placeholder="Placeholder"
/>
</div>
<div>
<Textarea
appearance={InputAppearance.Error}
id="error"
inputLabel="Error"
placeholder="Placeholder"
/>
</div>
<div>
<Textarea
appearance={InputAppearance.Success}
id="success"
inputLabel="Success"
placeholder="Placeholder"
/>
</div>
<div>
<Textarea
id="value"
inputLabel="With Value"
placeholder="Placeholder"
value="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
/>
</div>
<div>
<Textarea
id="disabled"
inputLabel="Disabled"
placeholder="Placeholder"
disabled={true}
/>
</div>
<div>
<Textarea
id="disabledValue"
inputLabel="Disabled w/ Value"
value="This is Disabled"
disabled={true}
/>
</div>
</InputStoryWrapper>
))
.add("more rows", () => (
<InputStoryWrapper>
<Textarea
id="tenRows"
inputLabel="Standard"
placeholder="Placeholder"
rows={10}
/>
</InputStoryWrapper>
))
.add("required", () => (
<InputStoryWrapper>
<div>
<Textarea
id="required"
inputLabel="Required"
placeholder="Placeholder"
required={true}
/>
</div>
<div>
<Textarea
appearance={InputAppearance.Error}
id="required"
inputLabel="Required"
placeholder="Placeholder"
errors={["Please enter a value"]}
required={true}
/>
</div>
</InputStoryWrapper>
))
.add("with hint", () => (
<InputStoryWrapper>
<Textarea
id="hint"
inputLabel="Standard"
placeholder="Placeholder"
hintContent="Enter a body of text here"
/>
</InputStoryWrapper>
))
.add("error with message", () => (
<InputStoryWrapper>
<Textarea
appearance={InputAppearance.Error}
id="error"
inputLabel="Error"
placeholder="Placeholder"
errors={["Something is wrong here"]}
/>
</InputStoryWrapper>
))
.add("error with messages", () => (
<InputStoryWrapper>
<Textarea
appearance={InputAppearance.Error}
id="error"
inputLabel="Error"
placeholder="Placeholder"
errors={["Something is wrong here", "Another error message"]}
/>
</InputStoryWrapper>
))
.add("hidden label", () => (
<InputStoryWrapper>
<Textarea
id="hiddenlabel"
inputLabel="Standard"
placeholder="Placeholder"
showInputLabel={false}
/>
</InputStoryWrapper>
))
.add("with onChange", () => (
<InputStoryWrapper>
<Textarea
id="onChange"
inputLabel="Standard"
placeholder="Placeholder"
onChange={action("onChange happened")}
/>
</InputStoryWrapper>
))
.add("with onChange delegated", () => (
<InputStoryWrapper>
<form onChange={action("onChange delegated")}>
<Textarea
id="onChangeDelegated"
inputLabel="Standard"
placeholder="Placeholder"
/>
</form>
</InputStoryWrapper>
));
9 changes: 9 additions & 0 deletions packages/textarea/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from "emotion";
import { spaceM } from "../design-tokens/build/js/designTokens";

export const textarea = css`
box-sizing: border-box;
height: unset;
padding: ${spaceM};
width: 100%;
`;
Loading

0 comments on commit 21cc7f9

Please sign in to comment.