Skip to content

Commit 60684d1

Browse files
Add Textarea To Components (codesandbox#3292)
* start textarea * fix counter * update input to use text component * remove invisible label * small fixes * Update packages/components/src/components/Textarea/index.tsx Co-Authored-By: Siddharth Kshetrapal <siddharth.kshetrapal@gmail.com> * update styles * replace css with style * get right input * add type * fix ts * clran up types * fix padding * add --exit-zero-on-changes * Update package.json Co-authored-by: Siddharth Kshetrapal <siddharth.kshetrapal@gmail.com>
1 parent 132041c commit 60684d1

File tree

8 files changed

+170
-33
lines changed

8 files changed

+170
-33
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ commands:
197197
$BROWSER_STACK_KEY || true'
198198
background: true
199199
- run:
200-
command: npm run chromatic
200+
command: npm run chromatic --exit-zero-on-changes
201201
- run:
202202
name: Test Integrations
203203
command: |

packages/components/.storybook/config.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Fragment } from 'react';
1+
import React from 'react';
22

33
import { withKnobs } from '@storybook/addon-knobs';
44
import { withA11y } from '@storybook/addon-a11y';
@@ -32,18 +32,24 @@ const viewports = {
3232
// using sidebar as the styles for body for now 🤷
3333
const GlobalStyle = createGlobalStyle`
3434
${global};
35-
body {
35+
html body {
3636
font-family: 'Inter', sans-serif;
3737
background-color: ${theme.colors.sideBar.background};
3838
color: ${theme.colors.sideBar.foreground};
39+
margin: 0;
40+
padding: 20px;
41+
42+
* {
43+
box-sizing: border-box;
44+
}
3945
}
4046
`;
4147

4248
export const withGlobal = cb => (
43-
<Fragment>
49+
<>
4450
<GlobalStyle />
4551
{cb()}
46-
</Fragment>
52+
</>
4753
);
4854

4955
addDecorator(withA11y);

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"start": "(yarn tsc --watch & yarn cpx \"src/**/*.{css,svg,png,jpg,woff,woff2}\" lib --watch)",
2222
"start:storybook": "start-storybook",
2323
"typecheck": "tsc --noEmit",
24-
"chromatic": "CHROMATIC_APP_CODE=nffds42ndde ./node_modules/.bin/chromatic --build-script-name=build:storybook",
24+
"chromatic": "CHROMATIC_APP_CODE=nffds42ndde ./node_modules/.bin/chromatic --build-script-name=build:storybook --exit-zero-on-changes",
2525
"publish": "np"
2626
},
2727
"dependencies": {

packages/components/src/components/Example/index.tsx

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/components/src/components/Input/index.stories.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export default {
88

99
// replace the text inside with Text variants when available
1010
export const Basic = () => <Input />;
11-
export const InvisibleLabel = () => <Input invisibleLabel="Fill your name" />;
1211
export const Placeholder = () => <Input placeholder="Your name" />;
1312
export const Label = () => (
1413
<Input label="Your full name" placeholder="John Doe" />

packages/components/src/components/Input/index.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styled from 'styled-components';
33
import css from '@styled-system/css';
44
import VisuallyHidden from '@reach/visually-hidden';
55
import { uniqueId } from 'lodash-es';
6+
import { Text } from '../Text';
67

78
const placeholderStyles = {
89
color: 'input.placeholderForeground',
@@ -16,7 +17,7 @@ export const InputComponent = styled.input(
1617
fontSize: 3,
1718
borderRadius: 'small',
1819
backgroundColor: 'input.background',
19-
borderBottom: '1px solid',
20+
border: '1px solid',
2021
borderColor: 'input.border',
2122
color: 'input.foreground',
2223
'::-webkit-input-placeholder': placeholderStyles,
@@ -25,15 +26,6 @@ export const InputComponent = styled.input(
2526
})
2627
);
2728

28-
const Label = styled.label(
29-
css({
30-
fontSize: 2,
31-
paddingBottom: 2,
32-
color: 'sidebar.foreground',
33-
display: 'block',
34-
})
35-
);
36-
3729
interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
3830
label?: string;
3931
}
@@ -52,7 +44,15 @@ export const Input: React.FC<IInputProps> = ({
5244
<label htmlFor={id}>{props.placeholder}</label>
5345
</VisuallyHidden>
5446
) : null}
55-
{label ? <Label htmlFor={id}>{label}</Label> : null}
47+
<Text
48+
as="label"
49+
size={2}
50+
marginBottom={2}
51+
htmlFor={id}
52+
style={{ display: 'block' }}
53+
>
54+
{label}
55+
</Text>
5656
<InputComponent id={id} {...props} />
5757
</>
5858
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import { Textarea } from '.';
3+
4+
export default {
5+
title: 'components/Textarea',
6+
component: Textarea,
7+
};
8+
9+
const Wrapper = ({ children }) => <div style={{ width: 400 }}>{children}</div>;
10+
11+
// replace the text inside with Text variants when available
12+
export const Basic = () => (
13+
<Wrapper>
14+
<Textarea />
15+
</Wrapper>
16+
);
17+
export const Placeholder = () => (
18+
<Wrapper>
19+
<Textarea placeholder="Your name" />
20+
</Wrapper>
21+
);
22+
export const Label = () => (
23+
<Wrapper>
24+
<Textarea label="Your full name" placeholder="John Doe" />
25+
</Wrapper>
26+
);
27+
28+
export const MaxLength = () => (
29+
<Wrapper>
30+
<Textarea maxLength={10} label="Your full name" placeholder="John Doe" />
31+
</Wrapper>
32+
);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { useState, useCallback } from 'react';
2+
import styled, {
3+
StyledComponent,
4+
StyledComponentInnerOtherProps,
5+
} from 'styled-components';
6+
import css from '@styled-system/css';
7+
import VisuallyHidden from '@reach/visually-hidden';
8+
import { uniqueId } from 'lodash-es';
9+
import { Text } from '../Text';
10+
import { Stack } from '../Stack';
11+
import { InputComponent } from '../Input';
12+
13+
interface ITextareaProps
14+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
15+
label?: string;
16+
maxLength?: number;
17+
}
18+
19+
export const TextareaComponent: any = styled(InputComponent).attrs({
20+
as: 'textarea',
21+
})(
22+
css({
23+
height: 64,
24+
padding: 2,
25+
width: '100%',
26+
resize: 'none',
27+
})
28+
) as StyledComponent<
29+
'textarea',
30+
any,
31+
StyledComponentInnerOtherProps<typeof InputComponent>
32+
>;
33+
34+
const Count = styled.div<{ limit: boolean }>(({ limit }) =>
35+
css({
36+
fontSize: 2,
37+
paddingTop: 1,
38+
color: limit ? 'errorForeground' : 'input.placeholderForeground',
39+
alignSelf: 'flex-end',
40+
})
41+
);
42+
43+
export const Textarea: React.FC<ITextareaProps> = ({
44+
label,
45+
maxLength,
46+
...props
47+
}) => {
48+
const id = props.id || uniqueId('form_');
49+
const [wordCount, setWordCount] = useState(0);
50+
const [value, setValue] = useState('');
51+
52+
const Wrapper = useCallback(
53+
({ children }) =>
54+
maxLength ? <Stack direction="vertical">{children}</Stack> : children,
55+
[maxLength]
56+
);
57+
58+
// eslint-disable-next-line consistent-return
59+
const update = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
60+
if (props.onChange) props.onChange(e);
61+
if (maxLength) {
62+
const trimmedText = e.target.value.substring(0, maxLength);
63+
setValue(trimmedText);
64+
setWordCount(trimmedText.length);
65+
} else {
66+
setValue(e.target.value);
67+
}
68+
};
69+
70+
const keyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
71+
if (props.onKeyPress) props.onKeyPress(e);
72+
if (maxLength) {
73+
if (maxLength <= wordCount) {
74+
return false;
75+
}
76+
}
77+
78+
return true;
79+
};
80+
81+
return (
82+
<>
83+
{props.placeholder && !label ? (
84+
<VisuallyHidden>
85+
<label htmlFor={id}>{props.placeholder}</label>
86+
</VisuallyHidden>
87+
) : null}
88+
{label ? (
89+
<Text
90+
as="label"
91+
size={2}
92+
marginBottom={2}
93+
htmlFor={id}
94+
style={{ display: 'block' }}
95+
>
96+
{label}
97+
</Text>
98+
) : null}
99+
<Wrapper>
100+
<TextareaComponent
101+
value={value}
102+
onChange={update}
103+
onKeyPress={keyPress}
104+
id={id}
105+
{...props}
106+
/>
107+
{maxLength ? (
108+
<Count limit={maxLength <= wordCount}>
109+
{wordCount}/{maxLength}
110+
</Count>
111+
) : null}
112+
</Wrapper>
113+
</>
114+
);
115+
};

0 commit comments

Comments
 (0)