Skip to content

Commit 76f06b0

Browse files
siddharthkpSaraVieira
authored andcommitted
Many improvements to component composition (codesandbox#3347)
* add sidebar row component * add comment for sidebar * add text block stories * add animation to collasible * add missing full width to select wrapper * update examples based on changes * remove debug statement * i should have commited sooner * add Label component * use label where needed instead of text
1 parent 41752fd commit 76f06b0

File tree

25 files changed

+354
-311
lines changed

25 files changed

+354
-311
lines changed

packages/components/.storybook/config.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const vsCodeThemes = allThemes.map(b => makeTheme(b, b.name));
5454
const blackCodesandbox = vsCodeThemes.find(
5555
theme => theme.name === 'CodeSandbox Black'
5656
);
57-
console.log(isChromatic());
57+
5858
if (!isChromatic()) {
5959
const withGlobal = (cb: any) => (
6060
<>

packages/components/src/components/Button/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const variantStyles = {
8383
};
8484

8585
export const Button = styled(Element).attrs({ as: 'button' })<{
86+
type?: 'submit' | 'button' | 'reset';
8687
variant?: 'primary' | 'secondary' | 'link' | 'danger';
8788
}>(({ variant = 'primary', ...props }) =>
8889
css(

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
export const Basic = () => (
1212
<Collapsible title="Sandbox Info">
1313
<Element marginX={2}>
14-
<Text weight="medium" marginBottom={2}>
14+
<Text weight="medium" block marginBottom={2}>
1515
The move from Cerebral
1616
</Text>
1717
<Text variant="muted">This is a static template with no bundling</Text>
@@ -22,7 +22,7 @@ export const Basic = () => (
2222
export const DefaultOpen = () => (
2323
<Collapsible title="Sandbox Info" defaultOpen>
2424
<Element marginX={2}>
25-
<Text as="div" weight="medium" marginBottom={2}>
25+
<Text as="div" weight="medium" block marginBottom={2}>
2626
The move from Cerebral
2727
</Text>
2828
<Text variant="muted">This is a static template with no bundling</Text>

packages/components/src/components/Collapsible/index.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@ import styled from 'styled-components';
33
import css from '@styled-system/css';
44
import { Element } from '../Element';
55
import { Text } from '../Text';
6+
import { SidebarRow } from '../SidebarRow';
67

78
export const Section = styled(Element).attrs({ as: 'section' })(
89
css({
910
fontSize: 3,
1011
})
1112
);
1213

13-
export const Header = styled.div(
14+
export const Header = styled(SidebarRow).attrs({ gap: 2 })(
1415
css({
15-
display: 'flex',
16-
alignItems: 'center',
17-
height: 6,
1816
paddingX: 3,
1917
borderBottom: '1px solid',
2018
// Note: sideBarSectionHeader exists but we dont use it because it is rarely implemented
@@ -36,18 +34,26 @@ export const Icon = styled.svg<{
3634
open?: boolean;
3735
}>(props =>
3836
css({
39-
marginRight: 2,
4037
transform: props.open ? 'rotate(0)' : 'rotate(-90deg)',
38+
transition: 'transform',
39+
transitionDuration: theme => theme.speeds[1],
4140
color: 'grays.400',
4241
})
4342
);
4443

45-
export const Body = styled.div(
44+
export const Body = styled(Element)<{
45+
open?: boolean;
46+
}>(props =>
4647
css({
4748
borderBottom: '1px solid',
4849
borderColor: 'sideBar.border',
49-
paddingTop: 4,
50-
paddingBottom: 8,
50+
maxHeight: props.open ? '1000px' : 0,
51+
overflow: props.open ? 'auto' : 'hidden',
52+
paddingTop: props.open ? 4 : 0,
53+
paddingBottom: props.open ? 8 : 0,
54+
opacity: props.open ? 1 : 0,
55+
transition: 'all',
56+
transitionDuration: theme => theme.speeds[4],
5157
})
5258
);
5359

@@ -86,7 +92,8 @@ export const Collapsible: React.FC<ICollapsibleProps> = ({
8692
<ToggleIcon open={open} />
8793
<Text weight="medium">{title}</Text>
8894
</Header>
89-
{open ? <Body>{children}</Body> : null}
95+
96+
<Body open={open}>{children}</Body>
9097
</Section>
9198
);
9299
};

packages/components/src/components/Element/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import styled from 'styled-components';
22
import css from '@styled-system/css';
33

4-
export const Element = styled.div<{
4+
export interface IElementProps {
55
margin?: number;
66
marginX?: number;
77
marginY?: number;
88
marginBottom?: number;
99
marginTop?: number; // prefer margin bottom to top
1010
css?: Object;
11-
}>(props =>
11+
}
12+
13+
export const Element = styled.div<IElementProps>(props =>
1214
css({
1315
margin: props.margin || null,
1416
marginX: props.marginX || null,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react';
2+
import { FormField } from '.';
3+
import {
4+
Input,
5+
Switch,
6+
Select,
7+
Collapsible,
8+
Element,
9+
Text,
10+
Button,
11+
} from '../..';
12+
13+
export default {
14+
title: 'components/FormField',
15+
component: FormField,
16+
};
17+
18+
export const Horizontal = () => (
19+
<Element
20+
css={{ width: 200, border: '1px solid', borderColor: 'sideBar.border' }}
21+
>
22+
<FormField label="Frozen" direction="horizontal">
23+
<Switch defaultOn />
24+
</FormField>
25+
</Element>
26+
);
27+
28+
export const Vertical = () => (
29+
<Element
30+
css={{ width: 200, border: '1px solid', borderColor: 'sideBar.border' }}
31+
>
32+
<FormField direction="vertical" label="Repository name">
33+
<Select placeholder="Please select an option">
34+
<option>One</option>
35+
<option>Two</option>
36+
</Select>
37+
</FormField>
38+
</Element>
39+
);
40+
41+
export const hideLabel = () => (
42+
<Element
43+
css={{ width: 200, border: '1px solid', borderColor: 'sideBar.border' }}
44+
>
45+
<Collapsible title="GitHub" defaultOpen>
46+
<Text block marginX={2} marginBottom={2}>
47+
Export Sandbox to GitHub
48+
</Text>
49+
<FormField direction="vertical" label="Repository name" hideLabel>
50+
<Input placeholder="codesandbox/components" />
51+
</FormField>
52+
<Element marginX={2}>
53+
<Button variant="secondary">Create Repository</Button>
54+
</Element>
55+
<Text
56+
size={2}
57+
block
58+
marginTop={6}
59+
marginX={2}
60+
css={{ breakWord: 'none' }}
61+
>
62+
Sometimes you don&apos;t need to show the label to a visual user because
63+
the section header and description give enough context. This however is
64+
not true for a user that&apos;s using a screen reader they would still
65+
require a description label to be read out. For this purpose, we
66+
visually hide the label but still keep in the elements tree.
67+
</Text>
68+
</Collapsible>
69+
</Element>
70+
);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from 'react';
2+
import VisuallyHidden from '@reach/visually-hidden';
3+
import { useId } from '@reach/auto-id';
4+
import { Element, Stack, Label } from '../../index';
5+
6+
interface IFormFieldProps {
7+
id?: string;
8+
// always ask for a label
9+
label: string;
10+
// sometimes you don't need to show the label to a visual user
11+
// because the section header and description give enough context.
12+
// this however is not true for a user that's using a screen reader
13+
// they would still require a description label to be read out.
14+
// for this purpose, we visually hide the label but still keep in the
15+
// elements tree.
16+
hideLabel?: boolean;
17+
direction?: 'horizontal' | 'vertical';
18+
}
19+
20+
export const FormField: React.FC<IFormFieldProps> = ({
21+
label,
22+
id,
23+
hideLabel = false,
24+
direction = 'horizontal',
25+
...props
26+
}) => {
27+
const inputId = useId(id);
28+
29+
const LabelWrapper = hideLabel ? VisuallyHidden : React.Fragment;
30+
const InputElement = React.Children.map(props.children, child =>
31+
React.cloneElement(child as React.ReactElement<any>, {
32+
id: inputId,
33+
})
34+
);
35+
36+
if (direction === 'horizontal') {
37+
return (
38+
<>
39+
<Stack
40+
direction="horizontal"
41+
justify="space-between"
42+
align="center"
43+
css={{ height: 8, paddingX: 2 }}
44+
{...props}
45+
>
46+
<LabelWrapper>
47+
<Label htmlFor={inputId} size={3}>
48+
{label}
49+
</Label>
50+
</LabelWrapper>
51+
{InputElement}
52+
</Stack>
53+
</>
54+
);
55+
}
56+
57+
return (
58+
<Element
59+
css={{
60+
paddingX: 2,
61+
}}
62+
{...props}
63+
>
64+
<LabelWrapper>
65+
<Label htmlFor={inputId} size={3} block>
66+
{label}
67+
</Label>
68+
</LabelWrapper>
69+
<Stack direction="horizontal" align="center" css={{ minHeight: 8 }}>
70+
{props.children}
71+
</Stack>
72+
</Element>
73+
);
74+
};

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,7 @@ export default {
88
};
99

1010
// replace the text inside with Text variants when available
11-
export const Basic = () => <Input />;
12-
export const Placeholder = () => <Input placeholder="Your name" />;
13-
export const Label = () => (
14-
<Input label="Your full name" placeholder="John Doe" />
15-
);
11+
export const Placeholder = () => <Input placeholder="Your name" marginX={4} />;
1612
export const onChange = () => (
17-
<Input
18-
label="Your full name"
19-
placeholder="John Doe"
20-
onChange={action('input change')}
21-
/>
13+
<Input placeholder="John Doe" onChange={action('input change')} />
2214
);
Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import React from 'react';
21
import styled from 'styled-components';
32
import css from '@styled-system/css';
4-
import VisuallyHidden from '@reach/visually-hidden';
5-
import { useId } from '@reach/auto-id';
6-
import { Text } from '../Text';
3+
import { Element } from '../Element';
74

85
const placeholderStyles = {
96
color: 'input.placeholderForeground',
107
fontSize: 3,
118
};
129

13-
export const InputComponent = styled.input(
10+
export const Input = styled(Element).attrs({ as: 'input' })(
1411
css({
1512
height: 6,
13+
width: '100%',
1614
paddingX: 2,
1715
fontSize: 3,
1816
borderRadius: 'small',
@@ -25,34 +23,3 @@ export const InputComponent = styled.input(
2523
'::placeholder': placeholderStyles,
2624
})
2725
);
28-
29-
interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
30-
label?: string;
31-
}
32-
33-
export const Input: React.FC<IInputProps> = ({
34-
type = 'text',
35-
label,
36-
...props
37-
}) => {
38-
const id = useId(props.id);
39-
return (
40-
<>
41-
{props.placeholder && !label ? (
42-
<VisuallyHidden>
43-
<label htmlFor={id}>{props.placeholder}</label>
44-
</VisuallyHidden>
45-
) : null}
46-
<Text
47-
as="label"
48-
size={2}
49-
marginBottom={2}
50-
htmlFor={id}
51-
style={{ display: 'block' }}
52-
>
53-
{label}
54-
</Text>
55-
<InputComponent id={id} {...props} />
56-
</>
57-
);
58-
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import styled from 'styled-components';
2+
import css from '@styled-system/css';
3+
import { Text } from '../Text';
4+
5+
export const Label = styled(Text).attrs({ as: 'label' })<{ htmlFor: string }>(
6+
css({})
7+
);

0 commit comments

Comments
 (0)