抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

React 即时通信 UI 实战第八章。React 即时通信 UI 实战为峰华前端工程师推出的 React 实战课程,以聊天(即时通信)为原型,构建了一整套的 UI 组件库,课程重点在于 UI 组件的分析和实现,力求打造自用组件库。本章包括设置页面中的 InputText、Select(表单)、Radio、Switch 开关等组件。以下为我在学习和实战练习过程中所做的笔记,可供参考。

一、文本输入框组件开发

使用 Hygen 创建一个 InputText 组件,并移动到 src/components/Input 组件目录下,作为 Input 的子组件导出:

1
hygen component new InputText

再使用 Hygen 创建一个 LabelContainer 组件,对 Label 和控件进行布局:

1
hygen component new LabelContainer

编辑 src/components/LabelContainer/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import PropTypes from "prop-types";
import StyledLabelContainer from "./style";
import Text from "components/Text";

function LabelContainer({ children, label, ...rest }) {
return (
<StyledLabelContainer {...rest}>
{label && <Text style={{ marginBottom: "8px" }}>{label}: </Text>}
{children}
</StyledLabelContainer>
);
}

LabelContainer.propTypes = {
children: PropTypes.any,
};

export default LabelContainer;

编辑 src/components/LabelContainer/style.js 文件:

1
2
3
4
5
6
7
8
9
import styled from "styled-components";

const StyledLabelContainer = styled.label`
display: flex;
flex-direction: column;
font-size: ${({ theme }) => theme.normal};
`;

export default StyledLabelContainer;

编辑 src/components/Input/InputText/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import styled from "styled-components";

const InputUnderline = styled.input`
border: none;
border-bottom: 1px solid ${({ theme }) => theme.gray4};
font-size: ${({ theme }) => theme.normal};
width: 100%;
background: none;

&::placeholder {
color: ${({ theme }) => theme.gray5};
}

:focus,
:hover {
border-bottom-color: ${({ theme }) => theme.primaryColor};
}
`;

const StyledInputText = styled.div``;

export default StyledInputText;
export { InputUnderline };

编辑 src/components/Input/InputText/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
import PropTypes from "prop-types";
import StyledInputText, { InputUnderline } from "./style";
import LabelContainer from "components/LabelContainer";

function InputText({ label, placeholder = "请输入内容", ...rest }) {
const input = <InputUnderline type="text" placeholder={placeholder} />;

return (
<StyledInputText>
{label ? <LabelContainer label={label}>{input}</LabelContainer> : input}
</StyledInputText>
);
}

InputText.propTypes = {
label: PropTypes.string,
placeholder: PropTypes.string,
};

export default InputText;

编辑 src/components/Input/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React from "react";
import PropTypes from "prop-types";
import StyledInput, { InputContainer, Prefix, Suffix } from "./style";
import Icon from "components/Icon";

import { ReactComponent as SearchIcon } from "assets/icon/search.svg";
import { useTheme } from "styled-components";
import InputText from "./InputText";

function Input({ placeholder = "请输入内容...", prefix, suffix, ...rest }) {
return (
<InputContainer {...rest}>
{prefix && <Prefix>{prefix}</Prefix>}
<StyledInput placeholder={placeholder} />
{suffix && <Suffix>{suffix}</Suffix>}
</InputContainer>
);
}

function Search({ placeholder = "请输入搜索内容...", ...rest }) {
const theme = useTheme();
return (
<Input
placeholder={placeholder}
prefix={
<Icon icon={SearchIcon} color={theme.gray3} width={18} height={18} />
}
{...rest}
/>
);
}

Input.Search = Search;
Input.Text = InputText;

Input.propTypes = {
placeholder: PropTypes.string,
prefix: PropTypes.any,
suffix: PropTypes.any,
};

export default Input;

src/components/Input/input.stories.js 文件中添加两行 stories:

1
2
export const InputTextWithLabel = () => <Input.Text label="昵称" />;
export const InputTextWithoutLabel = () => <Input.Text />;

使用 yarn 命令启动 storybook:

1
yarn run storybook

二、重构 Select 组件

编辑 src/components/Select/index.js 文件::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from "react";
import PropTypes from "prop-types";
import StyledSelect from "./style";
import LabelContainer from "components/LabelContainer";

function Select({ label, type, children, ...rest }) {
const selectWithoutLabel = (
<StyledSelect type={type} {...rest}>
{children}
</StyledSelect>
);
return label ? (
<LabelContainer label={label}>selectWithoutLabel</LabelContainer>
) : (
selectWithoutLabel
);
}

Select.propTypes = {
type: PropTypes.oneOf(["form"]),
children: PropTypes.any,
};

export default Select;

再在 style.js 中修改样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import styled, { css } from "styled-components";
import CaretDown from "assets/icon/caret_down.svg";
import CaretDown2 from "assets/icon/caretDown2.svg";

const typeVariants = {
form: css`
background-image: url(${CaretDown2});
`,
};
const StyledSelect = styled.select`
appearance: none;
background-image: url(${CaretDown});
background-repeat: no-repeat;
background-position: right center;
background-color: transparent;
border: none;
padding-right: 14px;
font-size: ${({ theme }) => theme.normal};
color: ${({ theme }) => theme.grayDark};

${({ type }) => type && typeVariants[type]}

::-ms-expand {
display: none;
}
`;

export default StyledSelect;

最后在 select.stories.js 文件中添加一个 story:

1
2
3
4
5
6
7
8
export const FormSelect = () => {
return (
<Select type="form">
<Option>北京市</Option>
<Option>河北省</Option>
</Select>
);
};

三、Radio 单选按钮组件开发

使用 Hygen 创建一个 Radio 组件:

1
hygen component new Radio

编辑 src/components/Radio/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from "react";
import PropTypes from "prop-types";
import StyledRadio, { RadioButton, Circle, StyledRadioGroup } from "./style";
import LabelContainer from "components/LabelContainer";

function Radio({ name, children, ...rest }) {
return (
<StyledRadio {...rest}>
{children}
<RadioButton name={name} />
<Circle />
</StyledRadio>
);
}

function RadioGroup({ label, children, ...rest }) {
return (
<LabelContainer label={label}>
<StyledRadioGroup {...rest}>{children}</StyledRadioGroup>
</LabelContainer>
);
}

Radio.Group = RadioGroup;

Radio.propTypes = {
name: PropTypes.string,
children: PropTypes.any,
};

RadioGroup.propTypes = {
label: PropTypes.string,
children: PropTypes.any,
};

export default Radio;

编辑 src/components/Radio/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import styled from "styled-components";

const Circle = styled.span`
line-height: 16px;
width: 16px;
height: 16px;
border: 1px solid ${({ theme }) => theme.primaryColor};
border-radius: 50%;
position: absolute;
left: 0;
top: 0;

::after {
content: "";
width: 10px;
height: 10px;
background-color: ${({ theme }) => theme.primaryColor};
border-radius: 50%;
position: absolute;
left: 2px;
top: 2px;

opacity: 0;
transform: scale(0);
transition: 0.2s ease;
}
`;

const RadioButton = styled.input.attrs({ type: "radio" })`
width: 0;
height: 0;
opacity: 0;

:checked + ${Circle}::after {
opacity: 1;
transform: scale(1);
}

:not(:checked) + ${Circle}::after {
opacity: 0;
transform: scale(0);
}
`;

const StyledRadioGroup = styled.div`
display: flex;
& > *:not(:first-child) {
margin-left: 24px;
}
`;

const StyledRadio = styled.label`
position: relative;
padding-left: 22px;
cursor: pointer;
display: inline-block;
line-height: 16px;
font-size: ${({ theme }) => theme.normal};
`;

export default StyledRadio;
export { RadioButton, Circle, StyledRadioGroup };

src/components/Radio/radio.stories.js 文件中添加 stories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import Radio from ".";

export default {
title: "UI 组件/Input/Radio",
component: Radio,
};

export const Default = () => <Radio>选项</Radio>;

export const RadioGroup = () => (
<Radio.Group label="请选择">
<Radio name="option">选项1</Radio>
<Radio name="option">选项2</Radio>
<Radio name="option">选项3</Radio>
</Radio.Group>
);

四、Switch 开关组件开发

使用 Hygen 创建一个 Switch 组件:

1
hygen component new Switch

编辑 src/components/LabelContainer/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import PropTypes from "prop-types";
import StyledSwitch, { Checkbox, Slider } from "./style";

function Switch({ children, ...rest }) {
return (
<StyledSwitch {...rest}>
<Checkbox />
<Slider />
</StyledSwitch>
);
}

Switch.propTypes = {
children: PropTypes.any,
};

export default Switch;

编辑 src/components/LabelContainer/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import styled from "styled-components";

const Slider = styled.span`
background-color: ${({ theme }) => theme.gray4};
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border-radius: 16px;
transition: 0.4s;

&::before {
display: block;
content: "";
position: absolute;
width: 28px;
height: 28px;
top: 1px;
left: 1px;
background-color: white;
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.05), 0px 2px 2px rgba(0, 0, 0, 0.1),
0px 3px 1px rgba(0, 0, 0, 0.0510643);
border-radius: 50%;
transition: 0.4s;
}
`;

const Checkbox = styled.input.attrs({ type: "checkbox" })`
width: 0;
height: 0;
opacity: 0;
:checked + ${Slider} {
background-color: ${({ theme }) => theme.primaryColor};

::before {
transform: translateX(20px);
}
}
`;

const StyledSwitch = styled.label`
width: 51px;
height: 31px;
position: relative;
display: inline-block;
`;

export default StyledSwitch;
export { Checkbox, Slider };

src/components/Input/input.stories.js 文件中添加 stories:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
import Switch from ".";

export default {
title: "UI 组件/Input/Switch",
component: Switch,
};

export const Default = () => (
<div style={{ padding: "1vw" }}>
<Switch />
</div>
);

五、组装设置页编辑个人资料页面

使用 Hygen 创建一个 EditProfile 组件:

1
hygen component new EditProfile

index.js 文件中编辑组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React, { useState } from "react";
import PropTypes from "prop-types";
import StyledEditProfile, {
GroupTitle,
GenderAndRegion,
SelectGroup,
StyledIconInput,
} from "./style";
import Profile from "components/Profile";
import face from "assets/images/face-male-1.jpg";
import Avatar from "components/Avatar";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import Button from "components/Button";

import "styled-components/macro";
import InputText from "components/Input/InputText";
import Radio from "components/Radio";
import LabelContainer from "components/LabelContainer";
import Select from "components/Select";
import Option from "components/Option";
import Icon from "components/Icon";
import {
faWeibo,
faGithub,
faLinkedin,
} from "@fortawesome/free-brands-svg-icons";

function EditProfile({ children, ...rest }) {
const [showEdit, setShowEdit] = useState(false);
if (!showEdit) {
return (
<Profile
onEdit={() => setShowEdit(true)}
showEditBtn
showCloseIcon={false}
/>
);
}
return (
<StyledEditProfile {...rest}>
<Avatar
src={face}
size="160px"
css={`
grid-area: 1 / 1 / 2 / 2;
justify-self: center;
margin-bottom: 12px;
`}
/>
<Button
size="52px"
css={`
grid-area: 1 / 1 / 3 / 2;
z-index: 10;
align-self: end;
justify-self: end;
`}
>
<FontAwesomeIcon icon={faCheck} onClick={() => setShowEdit(false)} />
</Button>
<GroupTitle>基本信息</GroupTitle>
<InputText label="昵称" />
<GenderAndRegion>
<Radio.Group label="性别">
<Radio name="gender"></Radio>
<Radio name="gender"></Radio>
</Radio.Group>
<LabelContainer label="地区">
<SelectGroup>
<Select type="form">
<Option>省份</Option>
</Select>
<Select type="form">
<Option>城市</Option>
</Select>
<Select type="form">
<Option>区县</Option>
</Select>
</SelectGroup>
</LabelContainer>
</GenderAndRegion>
<InputText label="个性签名" />

<GroupTitle>联系信息</GroupTitle>
<InputText label="联系电话" />
<InputText label="电子邮箱" />
<InputText label="个人网站" />

<GroupTitle>社交信息</GroupTitle>
<IconInput icon={faWeibo} bgColor="#F06767" />
<IconInput icon={faGithub} bgColor="black" />
<IconInput icon={faLinkedin} bgColor="#2483C0" />
</StyledEditProfile>
);
}

function IconInput({ icon, bgColor, ...rest }) {
return (
<StyledIconInput>
<Icon.Social icon={icon} bgColor={bgColor} />
<InputText {...rest} />
</StyledIconInput>
);
}

EditProfile.propTypes = {
children: PropTypes.any,
};

export default EditProfile;

再在 style.js 中修改样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import styled from "styled-components";
import Text from "components/Text";

const GroupTitle = styled(Text).attrs({ size: "large" })`
align-self: end;
`;

const GenderAndRegion = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
justify-items: space-between;
`;

const SelectGroup = styled.div`
> * {
margin: 0 4px;
}
margin: 0 -4px;
`;

const StyledIconInput = styled.div`
display: grid;
grid-template-columns: 38px 1fr;
align-items: end;
`;

const StyledEditProfile = styled.div`
display: grid;
grid-template-columns: 1fr;
row-gap: 20px;
padding: 30px;
max-height: 100vh;
overflow-y: auto;
`;

export default StyledEditProfile;
export { GroupTitle, GenderAndRegion, SelectGroup, StyledIconInput };

最后编辑 editProfile.stories.js 文件:

1
2
3
4
5
6
7
8
9
import React from "react";
import EditProfile from ".";

export default {
title: "页面组件/EditProfile",
component: EditProfile,
};

export const Default = () => <EditProfile />;

六、设置项组件开发

使用 Hygen 创建一个 Settings 组件:

1
hygen component new Settings

编辑 src/components/Settings/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React from "react";
import PropTypes from "prop-types";
import StyledSettings, {
StyledSettingsItem,
SettingsItemControl,
} from "./style";
import { ReactComponent as ArrowMenuRight } from "assets/icon/arrowMenuRight.svg";
import Paragraph from "components/Paragraph";
import Switch from "components/Switch";
import Icon from "components/Icon";
import Seperator from "components/Seperator";

function Settings({ children, ...rest }) {
return <StyledSettings {...rest}>{children}</StyledSettings>;
}

export function SettingsItem({
type = "switch",
label,
description,
children,
...rest
}) {
return (
<StyledSettingsItem {...rest}>
<SettingsItemControl>
<Paragraph size="large">{label}</Paragraph>
{type === "switch" && <Switch />}
{type === "menu" && <Icon icon={ArrowMenuRight} />}
</SettingsItemControl>

{description && (
<Paragraph type="secondary" style={{ margin: "4px 0" }}>
{description}
</Paragraph>
)}

<Seperator style={{ marginTop: "8px", marginBottom: "20px" }} />
</StyledSettingsItem>
);
}

Settings.propTypes = {
type: PropTypes.string,
label: PropTypes.string,
description: PropTypes.string,
children: PropTypes.any,
};

export default Settings;

编辑 src/components/Settings/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
import styled from "styled-components";

const StyledSettingsItem = styled.div``;

const SettingsItemControl = styled.div`
display: flex;
justify-content: space-between;
`;

const StyledSettings = styled.div``;

export default StyledSettings;
export { StyledSettingsItem, SettingsItemControl };

src/components/Settings/settings.stories.js 文件中添加两行 stories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
import Settings, { SettingsItem } from ".";

export default {
title: "页面组件/Settings",
component: Settings,
};

export const Default = () => <Settings>默认</Settings>;

export const WithoutDescription = () => (
<SettingsItem label="这是一个没有描述的设置项" />
);

export const WithDescription = () => (
<SettingsItem label="这是一个有描述的设置项" description="这是设置项描述" />
);

export const WithMenu = () => (
<SettingsItem label="有子菜单的设置项" type="menu" />
);

七、组装设置页面

编辑 src/components/Settings/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import styled from "styled-components";

const StyledSettingsItem = styled.div``;

const SettingsItemControl = styled.div`
display: flex;
justify-content: space-between;
`;

const StyledSettingsGroup = styled.div``;

const StyledSettings = styled.div`
padding: 72px;
`;

export default StyledSettings;
export { StyledSettingsItem, SettingsItemControl, StyledSettingsGroup };

编辑 src/components/Settings/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import React from "react";
import PropTypes from "prop-types";
import StyledSettings, {
StyledSettingsItem,
SettingsItemControl,
StyledSettingsGroup,
} from "./style";
import { ReactComponent as ArrowMenuRight } from "assets/icon/arrowMenuRight.svg";
import Paragraph from "components/Paragraph";
import Switch from "components/Switch";
import Icon from "components/Icon";
import Seperator from "components/Seperator";

function Settings({ children, ...rest }) {
return (
<StyledSettings {...rest}>
<SettingsGroup groupName="隐私设置">
<SettingsItem label="添加好友时需要验证" />
<SettingsItem
label="推荐通讯录好友"
description="上传的通讯录只用来匹配好友列表,本应用不会记录和发送任何信息给其它机构或"
/>
<SettingsItem label="只能通过手机号找到我" />
</SettingsGroup>
<SettingsGroup groupName="通知设置">
<SettingsItem label="新消息通知" />
<SettingsItem label="语音和视频通话提醒" />
<SettingsItem label="显示通知详情" />
<SettingsItem label="声音" />
<SettingsItem label="查看已静音的好友列表" type="menu" />
</SettingsGroup>
</StyledSettings>
);
}

function SettingsGroup({ groupName, children, ...rest }) {
return (
<StyledSettingsGroup>
<Paragraph size="xxlarge" style={{ paddingBottom: "24px" }}>
{groupName}
</Paragraph>
{children}
</StyledSettingsGroup>
);
}

export function SettingsItem({
type = "switch",
label,
description,
children,
...rest
}) {
return (
<StyledSettingsItem {...rest}>
<SettingsItemControl>
<Paragraph size="large">{label}</Paragraph>
{type === "switch" && <Switch />}
{type === "menu" && <Icon icon={ArrowMenuRight} />}
</SettingsItemControl>

{description && (
<Paragraph type="secondary" style={{ margin: "4px 0" }}>
{description}
</Paragraph>
)}

<Seperator style={{ marginTop: "8px", marginBottom: "20px" }} />
</StyledSettingsItem>
);
}

Settings.propTypes = {
type: PropTypes.string,
label: PropTypes.string,
description: PropTypes.string,
children: PropTypes.any,
};

export default Settings;

src/components/Settings/settings.stories.js 文件中更改 Default Story:

1
export const Default = () => <Settings />;

八、屏蔽列表组件开发

使用 Hygen 创建一个 BlockedList 组件:

1
hygen component new BlockedList

编辑 src/components/BlockedList/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from "react";
import PropTypes from "prop-types";
import StyledBlockedList, {
SettingsMenu,
ClosableAvatar,
BlockedAvatar,
CloseIcon,
BlockedName,
FriendList,
} from "./style";
import { ReactComponent as ArrowMenuLeft } from "assets/icon/arrowMenuLeft.svg";
import { ReactComponent as closeCircle } from "assets/icon/closeCircle.svg";
import face from "assets/images/face-male-1.jpg";
import "styled-components/macro";
import Icon from "components/Icon";
import Text from "components/Text";

function BlockedList({ children, ...rest }) {
return (
<StyledBlockedList {...rest}>
<SettingsMenu>
<Icon
icon={ArrowMenuLeft}
css={`
cursor: pointer;
`}
/>
<Text size="xxlarge">已屏蔽的好友</Text>
</SettingsMenu>
<FriendList>
{new Array(8).fill(0).map((_, i) => {
return (
<ClosableAvatar key={i}>
<BlockedAvatar size="105px" src={face} />
<CloseIcon width={46} height={51} icon={closeCircle} />
<BlockedName>李浩</BlockedName>
</ClosableAvatar>
);
})}
</FriendList>
</StyledBlockedList>
);
}

BlockedList.propTypes = {
children: PropTypes.any,
};

export default BlockedList;

编辑 src/components/BlockedList/style.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import styled from "styled-components";
import StyledText from "components/Text/style";
import Avatar from "components/Avatar";
import Text from "components/Text";
import Icon from "components/Icon";

const BlockedAvatar = styled(Avatar)`
grid-area: avatar;
`;

const BlockedName = styled(Text).attrs({ size: "xlarge" })`
grid-area: name;
margin-top: 20px;
`;

const CloseIcon = styled(Icon)`
grid-area: 2 / 3 / 5 / 4;
z-index: 10;
margin-top: 10px;
`;

const ClosableAvatar = styled.div`
display: grid;
grid-template-areas:
"avatar avatar avatar"
"avatar avatar avatar"
"avatar avatar avatar"
"name name name";

justify-items: center;
`;

const SettingsMenu = styled.div`
height: 148px;
display: grid;
grid-template-columns: 10px 1fr;
align-items: center;

${StyledText} {
grid-column: span 1/-1;
justify-self: center;
}
`;

const FriendList = styled.div`
display: grid;
grid-template-columns: repeat(5, 1fr);
row-gap: 24px;
justify-items: center;
`;

const StyledBlockedList = styled.div`
padding: 2vh 4vw;
`;

export default StyledBlockedList;
export {
SettingsMenu,
ClosableAvatar,
BlockedAvatar,
BlockedName,
CloseIcon,
FriendList,
};

修改 src/components/BlockedList/blockedList.stories.js 文件:

1
2
3
4
5
6
7
8
9
import React from "react";
import BlockedList from ".";

export default {
title: "页面组件/BlockedList",
component: BlockedList,
};

export const Default = () => <BlockedList />;

评论



Copyright © 2020 - 2022 Zhihao Zhuang. All rights reserved

本站访客数: 人,
总访问量: