@arcblock/ux 1.16.0 → 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/CodeBlock/index.js +3 -1
- package/package.json +6 -5
- package/src/ActionButton/index.js +65 -0
- package/src/ActivityIndicator/index.js +186 -0
- package/src/Alert/index.js +104 -0
- package/src/Async/index.js +39 -0
- package/src/Badge/index.js +71 -0
- package/src/Blocklet/index.js +335 -0
- package/src/Button/index.js +4 -0
- package/src/Button/wrap.js +88 -0
- package/src/ButtonGroup/index.js +19 -0
- package/src/Center/index.js +17 -0
- package/src/ClickToCopy/index.js +90 -0
- package/src/CodeBlock/index.js +160 -0
- package/src/Colors/index.js +1 -0
- package/src/Colors/themes/default.js +53 -0
- package/src/ContactForm/index.js +240 -0
- package/src/CookieConsent/index.js +90 -0
- package/src/CountDown/index.js +151 -0
- package/src/Dialog/confirm.js +76 -0
- package/src/Dialog/dialog.js +162 -0
- package/src/Dialog/index.js +2 -0
- package/src/DriftBot/index.js +81 -0
- package/src/Earth/countries.json +8057 -0
- package/src/Earth/index.js +511 -0
- package/src/Earth/util.js +69 -0
- package/src/Empty/index.js +41 -0
- package/src/Footer/index.js +84 -0
- package/src/Icon/image.js +55 -0
- package/src/Icon/index.js +69 -0
- package/src/Img/index.js +172 -0
- package/src/InfoRow/index.js +83 -0
- package/src/Layout/dashboard/header.js +145 -0
- package/src/Layout/dashboard/index.js +140 -0
- package/src/Layout/dashboard/sidebar.js +120 -0
- package/src/Layout/index.js +318 -0
- package/src/Locale/browser-lang.js +63 -0
- package/src/Locale/context.js +88 -0
- package/src/Locale/images/globe-dark.png +0 -0
- package/src/Locale/images/globe-light.png +0 -0
- package/src/Locale/selector.js +138 -0
- package/src/Logo/images/logo-dark-text.svg +3 -0
- package/src/Logo/images/logo-dark-top.svg +6 -0
- package/src/Logo/images/logo-light-text.svg +3 -0
- package/src/Logo/images/logo-light-top.svg +6 -0
- package/src/Logo/index.js +47 -0
- package/src/Metric/index.js +115 -0
- package/src/NFTDisplay/README.md +59 -0
- package/src/NFTDisplay/aspect-ratio-container.js +34 -0
- package/src/NFTDisplay/broken.js +18 -0
- package/src/NFTDisplay/index.js +230 -0
- package/src/NFTDisplay/loading.js +17 -0
- package/src/NFTDisplay/svg-embedder/img.js +36 -0
- package/src/NFTDisplay/svg-embedder/inline-svg.js +37 -0
- package/src/PageScroller/index.js +342 -0
- package/src/PageScroller/usePrevValue.js +12 -0
- package/src/PricingTable/PricingPlan.js +112 -0
- package/src/PricingTable/index.js +43 -0
- package/src/Screenshot/devices.css +1366 -0
- package/src/Screenshot/index.js +181 -0
- package/src/Spinner/index.js +33 -0
- package/src/Switch/index.js +78 -0
- package/src/Tabs/index.js +46 -0
- package/src/Tag/index.js +73 -0
- package/src/Terminal/Player.js +364 -0
- package/src/Terminal/index.js +150 -0
- package/src/Terminal/player.css +378 -0
- package/src/Terminal/util.js +167 -0
- package/src/Terminal/xterm.css +171 -0
- package/src/TextCollapse/index.js +92 -0
- package/src/Theme/index.js +169 -0
- package/src/Theme/responsiveFontSizes.js +94 -0
- package/src/Toast/index.js +118 -0
- package/src/Util/index.js +264 -0
- package/src/Video/index.js +72 -0
- package/src/Wallet/Action.js +105 -0
- package/src/Wallet/Download.js +130 -0
- package/src/Wallet/Open.js +50 -0
- package/src/Wallet/images/abtwallet.png +0 -0
- package/src/Wallet/images/android_download.svg +23 -0
- package/src/Wallet/images/app-store.svg +20 -0
- package/src/Wallet/images/google-play.svg +70 -0
- package/src/WechatPrompt/images/android.png +0 -0
- package/src/WechatPrompt/images/ios.png +0 -0
- package/src/WechatPrompt/index.js +81 -0
- package/src/index.js +63 -0
- package/src/withTheme/index.js +72 -0
- package/src/withTracker/README.md +34 -0
- package/src/withTracker/error_boundary.js +34 -0
- package/src/withTracker/index.js +70 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import withTheme from '@material-ui/core/styles/withTheme';
|
|
5
|
+
|
|
6
|
+
import { withRouter, Link } from 'react-router-dom';
|
|
7
|
+
import Button from '@material-ui/core/Button';
|
|
8
|
+
import Typography from '@material-ui/core/Typography';
|
|
9
|
+
import teal from '@material-ui/core/colors/teal';
|
|
10
|
+
|
|
11
|
+
import ImageIcon from '../../Icon/image';
|
|
12
|
+
import Logo from '../../Logo';
|
|
13
|
+
|
|
14
|
+
function Sidebar({ location, theme, images, links, prefix, addons, ...rest }) {
|
|
15
|
+
const isSelected = (url, name) => {
|
|
16
|
+
const pattern = new RegExp(`/${name}`);
|
|
17
|
+
return pattern.test(location.pathname);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<MenuItems {...rest}>
|
|
22
|
+
<Link to="/" className="sidebar-logo">
|
|
23
|
+
<Logo showText={false} size={20} />
|
|
24
|
+
</Link>
|
|
25
|
+
{links.map(({ url, name, title, showBadge }) => {
|
|
26
|
+
const selected = isSelected(url, name);
|
|
27
|
+
return (
|
|
28
|
+
<MenuItem component={Link} key={url} selected={selected} to={url}>
|
|
29
|
+
<ImageIcon
|
|
30
|
+
name={images[name]}
|
|
31
|
+
size={36}
|
|
32
|
+
color={selected ? '#00c2c4' : theme.typography.color.main}
|
|
33
|
+
prefix={prefix}
|
|
34
|
+
showBadge={showBadge}
|
|
35
|
+
/>
|
|
36
|
+
<Typography component="span" className="menu-title">
|
|
37
|
+
{title}
|
|
38
|
+
</Typography>
|
|
39
|
+
</MenuItem>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
<div style={{ flex: 1 }} />
|
|
43
|
+
{addons}
|
|
44
|
+
</MenuItems>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Sidebar.propTypes = {
|
|
49
|
+
location: PropTypes.object.isRequired,
|
|
50
|
+
theme: PropTypes.object.isRequired,
|
|
51
|
+
images: PropTypes.object.isRequired,
|
|
52
|
+
links: PropTypes.array.isRequired,
|
|
53
|
+
prefix: PropTypes.string,
|
|
54
|
+
addons: PropTypes.any,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Sidebar.defaultProps = {
|
|
58
|
+
prefix: '/images',
|
|
59
|
+
addons: null,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const MenuItems = React.memo(styled.div`
|
|
63
|
+
flex: 1;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
|
|
67
|
+
&& .sidebar-logo {
|
|
68
|
+
display: none;
|
|
69
|
+
border-bottom: 1px solid #eee;
|
|
70
|
+
background: ${props => props.theme.palette.background.default};
|
|
71
|
+
position: sticky;
|
|
72
|
+
top: 0;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
padding: 10px 0;
|
|
75
|
+
text-align: center;
|
|
76
|
+
font-size: 0;
|
|
77
|
+
}
|
|
78
|
+
@media (max-width: ${props => props.theme.breakpoints.values.md}px) {
|
|
79
|
+
&& .sidebar-logo {
|
|
80
|
+
display: block;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
`);
|
|
84
|
+
|
|
85
|
+
const gradient = 'linear-gradient(32deg, rgba(144, 255, 230, 0.1), rgba(144, 255, 230, 0))';
|
|
86
|
+
|
|
87
|
+
const MenuItem = styled(Button)`
|
|
88
|
+
&& {
|
|
89
|
+
display: block;
|
|
90
|
+
width: 100%;
|
|
91
|
+
transition: all 200ms ease-in-out;
|
|
92
|
+
background: ${props => (props.selected ? gradient : '')};
|
|
93
|
+
padding: 24px 0;
|
|
94
|
+
border-left: 2px solid ${props => (props.selected ? teal.A700 : 'transparent')};
|
|
95
|
+
|
|
96
|
+
&:hover {
|
|
97
|
+
background: ${gradient};
|
|
98
|
+
border-left-color: ${teal.A700};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.menu-title {
|
|
102
|
+
margin-top: 8px;
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
font-weight: 500;
|
|
105
|
+
text-align: center;
|
|
106
|
+
text-transform: capitalize;
|
|
107
|
+
letter-spacing: normal;
|
|
108
|
+
width: 80%;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.MuiButton-label {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
align-items: center;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
export default withRouter(withTheme(Sidebar));
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import Helmet from 'react-helmet';
|
|
5
|
+
import Link from '@material-ui/core/Link';
|
|
6
|
+
|
|
7
|
+
import AppBar from '@material-ui/core/AppBar';
|
|
8
|
+
import Container from '@material-ui/core/Container';
|
|
9
|
+
import Drawer from '@material-ui/core/Drawer';
|
|
10
|
+
import Divider from '@material-ui/core/Divider';
|
|
11
|
+
import Toolbar from '@material-ui/core/Toolbar';
|
|
12
|
+
import Typography from '@material-ui/core/Typography';
|
|
13
|
+
import List from '@material-ui/core/List';
|
|
14
|
+
import ListItem from '@material-ui/core/ListItem';
|
|
15
|
+
import ListItemText from '@material-ui/core/ListItemText';
|
|
16
|
+
import IconButton from '@material-ui/core/IconButton';
|
|
17
|
+
import MenuIcon from '@material-ui/icons/Menu';
|
|
18
|
+
|
|
19
|
+
import Footer from '../Footer';
|
|
20
|
+
import OpenInWallet from '../Wallet/Open';
|
|
21
|
+
import Icon from '../Icon';
|
|
22
|
+
import Logo from '../Logo';
|
|
23
|
+
|
|
24
|
+
export default function Layout({
|
|
25
|
+
title,
|
|
26
|
+
brand,
|
|
27
|
+
description,
|
|
28
|
+
links,
|
|
29
|
+
logo,
|
|
30
|
+
showLogo,
|
|
31
|
+
addons,
|
|
32
|
+
footer,
|
|
33
|
+
baseUrl,
|
|
34
|
+
homeUrl,
|
|
35
|
+
children,
|
|
36
|
+
variant,
|
|
37
|
+
contentOnly,
|
|
38
|
+
...rest
|
|
39
|
+
}) {
|
|
40
|
+
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
41
|
+
const onToggleDrawer = () => {
|
|
42
|
+
setDrawerOpen(!drawerOpen);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (contentOnly) {
|
|
46
|
+
return <Container>{children}</Container>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let activeLink = '';
|
|
50
|
+
const { pathname: currentPath } = new URL(window.location);
|
|
51
|
+
links.forEach(link => {
|
|
52
|
+
if (currentPath.startsWith(link.url) && link.url.length > activeLink.length) {
|
|
53
|
+
activeLink = link.url;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const drawer = (
|
|
58
|
+
<div>
|
|
59
|
+
<Toolbar className="toolbar toolbar--drawer">
|
|
60
|
+
{showLogo ? <div className="menu-logo">{logo}</div> : <></>}
|
|
61
|
+
<div style={{ flexGrow: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}>{brand}</div>
|
|
62
|
+
</Toolbar>
|
|
63
|
+
<Divider />
|
|
64
|
+
<List>
|
|
65
|
+
{links.map(x => (
|
|
66
|
+
<Link className="nav-link" key={x.url} href={x.url}>
|
|
67
|
+
<ListItem button className={activeLink === x.url ? 'drawer-highlight-nav' : ''}>
|
|
68
|
+
<ListItemText>
|
|
69
|
+
{x.icon && (
|
|
70
|
+
<Icon
|
|
71
|
+
name={x.icon}
|
|
72
|
+
size={18 * (x.iconZoom || 1)}
|
|
73
|
+
color="inherit"
|
|
74
|
+
style={{ marginRight: '5px' }}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
{x.title}
|
|
78
|
+
</ListItemText>
|
|
79
|
+
</ListItem>
|
|
80
|
+
</Link>
|
|
81
|
+
))}
|
|
82
|
+
</List>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
<Div {...rest}>
|
|
89
|
+
<Helmet title={title} />
|
|
90
|
+
<AppBar
|
|
91
|
+
position="fixed"
|
|
92
|
+
className={`appbar appbar--${variant}`}
|
|
93
|
+
color="default"
|
|
94
|
+
style={{ height: 56 }}>
|
|
95
|
+
<Container disableGutters>
|
|
96
|
+
<Toolbar className="toolbar">
|
|
97
|
+
<IconButton
|
|
98
|
+
color="inherit"
|
|
99
|
+
aria-label="open drawer"
|
|
100
|
+
edge="start"
|
|
101
|
+
onClick={onToggleDrawer}
|
|
102
|
+
className="menu-button">
|
|
103
|
+
<MenuIcon />
|
|
104
|
+
</IconButton>
|
|
105
|
+
{showLogo ? <div className="menu-logo">{logo}</div> : <></>}
|
|
106
|
+
<Typography
|
|
107
|
+
href={homeUrl}
|
|
108
|
+
component="a"
|
|
109
|
+
variant="h5"
|
|
110
|
+
color="inherit"
|
|
111
|
+
noWrap
|
|
112
|
+
display="block"
|
|
113
|
+
className="brand">
|
|
114
|
+
{brand}
|
|
115
|
+
</Typography>
|
|
116
|
+
{description && (
|
|
117
|
+
<Typography
|
|
118
|
+
component="small"
|
|
119
|
+
variant="subtitle2"
|
|
120
|
+
color="inherit"
|
|
121
|
+
noWrap
|
|
122
|
+
className="description">
|
|
123
|
+
{description}
|
|
124
|
+
</Typography>
|
|
125
|
+
)}
|
|
126
|
+
<div style={{ flexGrow: 1 }} />
|
|
127
|
+
<div className="nav-links">
|
|
128
|
+
{links.map(x => (
|
|
129
|
+
<Link
|
|
130
|
+
key={x.url}
|
|
131
|
+
href={x.url}
|
|
132
|
+
className={`nav-link ${activeLink === x.url ? 'highlight-nav' : ''}`}
|
|
133
|
+
color={x.color}>
|
|
134
|
+
{x.icon && (
|
|
135
|
+
<Icon
|
|
136
|
+
name={x.icon}
|
|
137
|
+
size={20 * (x.iconZoom || 1)}
|
|
138
|
+
color="inherit"
|
|
139
|
+
style={{ marginRight: '5px' }}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
{x.title}
|
|
143
|
+
</Link>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
{addons}
|
|
147
|
+
</Toolbar>
|
|
148
|
+
</Container>
|
|
149
|
+
</AppBar>
|
|
150
|
+
<div className="toolbar" />
|
|
151
|
+
<Container style={{ marginTop: 16, flex: 1 }}>{children}</Container>
|
|
152
|
+
{footer}
|
|
153
|
+
{!!baseUrl && <OpenInWallet locale="zh" link={baseUrl} />}
|
|
154
|
+
</Div>
|
|
155
|
+
<DrawerDiv>
|
|
156
|
+
<Drawer
|
|
157
|
+
variant="temporary"
|
|
158
|
+
open={drawerOpen}
|
|
159
|
+
onClose={onToggleDrawer}
|
|
160
|
+
classes={{
|
|
161
|
+
paper: 'drawer-paper',
|
|
162
|
+
}}
|
|
163
|
+
ModalProps={{
|
|
164
|
+
keepMounted: true, // Better open performance on mobile.
|
|
165
|
+
disablePortal: true,
|
|
166
|
+
}}>
|
|
167
|
+
{drawer}
|
|
168
|
+
</Drawer>
|
|
169
|
+
</DrawerDiv>
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Layout.propTypes = {
|
|
175
|
+
title: PropTypes.string.isRequired,
|
|
176
|
+
brand: PropTypes.any.isRequired,
|
|
177
|
+
description: PropTypes.any,
|
|
178
|
+
addons: PropTypes.any,
|
|
179
|
+
showLogo: PropTypes.bool,
|
|
180
|
+
logo: PropTypes.any,
|
|
181
|
+
links: PropTypes.array,
|
|
182
|
+
children: PropTypes.any.isRequired,
|
|
183
|
+
baseUrl: PropTypes.string,
|
|
184
|
+
homeUrl: PropTypes.string,
|
|
185
|
+
variant: PropTypes.oneOf(['shadow', 'border']),
|
|
186
|
+
footer: PropTypes.any,
|
|
187
|
+
contentOnly: PropTypes.bool,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
Layout.defaultProps = {
|
|
191
|
+
contentOnly: false,
|
|
192
|
+
baseUrl: '',
|
|
193
|
+
homeUrl: '/',
|
|
194
|
+
links: [],
|
|
195
|
+
showLogo: false,
|
|
196
|
+
logo: <Logo showText={false} style={{ width: '40px', height: '40px' }} />,
|
|
197
|
+
variant: 'shadow',
|
|
198
|
+
addons: undefined,
|
|
199
|
+
description: undefined,
|
|
200
|
+
footer: (
|
|
201
|
+
<Container>
|
|
202
|
+
<Footer />
|
|
203
|
+
</Container>
|
|
204
|
+
),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const Div = styled.div`
|
|
208
|
+
width: 100%;
|
|
209
|
+
min-height: 100vh;
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-direction: column;
|
|
212
|
+
.appbar {
|
|
213
|
+
&.appbar--border {
|
|
214
|
+
box-shadow: none;
|
|
215
|
+
&::before {
|
|
216
|
+
content: '';
|
|
217
|
+
position: absolute;
|
|
218
|
+
left: 0;
|
|
219
|
+
right: 0;
|
|
220
|
+
height: 1px;
|
|
221
|
+
bottom: -1px;
|
|
222
|
+
display: block;
|
|
223
|
+
background-color: rgba(0, 0, 0, 0.12);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.toolbar {
|
|
229
|
+
min-height: 56px;
|
|
230
|
+
background: inherit;
|
|
231
|
+
white-space: nowrap;
|
|
232
|
+
.menu-logo {
|
|
233
|
+
font-size: 0;
|
|
234
|
+
margin-right: 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.nav-links {
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
.nav-link {
|
|
241
|
+
margin: 8px 12px;
|
|
242
|
+
font-size: 16px;
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.highlight-nav {
|
|
248
|
+
font-weight: bolder;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
.brand {
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
text-decoration: none;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
text-overflow: ellipsis;
|
|
256
|
+
flex-shrink: 1;
|
|
257
|
+
}
|
|
258
|
+
.description {
|
|
259
|
+
color: #999;
|
|
260
|
+
font-size: 15px;
|
|
261
|
+
margin-left: 10px;
|
|
262
|
+
font-weight: normal;
|
|
263
|
+
flex-shrink: 999999;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@media (min-width: ${props => props.theme.breakpoints.values.md}px) {
|
|
268
|
+
.toolbar {
|
|
269
|
+
.menu-button {
|
|
270
|
+
display: none;
|
|
271
|
+
}
|
|
272
|
+
.menu-logo {
|
|
273
|
+
& + .brand {
|
|
274
|
+
padding-left: 45px;
|
|
275
|
+
margin-left: -45px;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@media (max-width: ${props => props.theme.breakpoints.values.md - 1}px) {
|
|
282
|
+
.toolbar {
|
|
283
|
+
.nav-links,
|
|
284
|
+
.menu-logo,
|
|
285
|
+
.description {
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
|
|
292
|
+
const DrawerDiv = styled.nav`
|
|
293
|
+
width: 240px;
|
|
294
|
+
.drawer-paper {
|
|
295
|
+
width: 240px;
|
|
296
|
+
}
|
|
297
|
+
.toolbar {
|
|
298
|
+
min-height: 56px;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
a:hover,
|
|
302
|
+
a:active,
|
|
303
|
+
a:visited,
|
|
304
|
+
a:focus {
|
|
305
|
+
text-decoration: none;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.drawer-highlight-nav {
|
|
309
|
+
background-color: #eee;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.toolbar--drawer {
|
|
313
|
+
font-size: 18px;
|
|
314
|
+
.menu-logo {
|
|
315
|
+
display: inline-flex;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
function startsWith(string, target, position) {
|
|
3
|
+
const { length } = string;
|
|
4
|
+
position = position == null ? 0 : position;
|
|
5
|
+
if (position < 0) {
|
|
6
|
+
position = 0;
|
|
7
|
+
} else if (position > length) {
|
|
8
|
+
position = length;
|
|
9
|
+
}
|
|
10
|
+
target = `${target}`;
|
|
11
|
+
// eslint-disable-next-line eqeqeq
|
|
12
|
+
return string.slice(position, position + target.length) == target;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getBrowserLang() {
|
|
16
|
+
if (typeof window === 'undefined') {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lang =
|
|
21
|
+
(window.navigator.languages && window.navigator.languages[0]) ||
|
|
22
|
+
window.navigator.language ||
|
|
23
|
+
window.navigator.browserLanguage ||
|
|
24
|
+
window.navigator.userLanguage ||
|
|
25
|
+
window.navigator.systemLanguage ||
|
|
26
|
+
null;
|
|
27
|
+
|
|
28
|
+
return lang;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeCode(code) {
|
|
32
|
+
return (code || '').toLowerCase().replace(/-/, '_');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPreferredLanguage(options) {
|
|
36
|
+
if (!options) {
|
|
37
|
+
return getBrowserLang();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { languages, fallback } = options;
|
|
41
|
+
if (!options.languages) {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// some browsers report language as en-US instead of en_US
|
|
46
|
+
const browserLanguage = normalizeCode(getBrowserLang());
|
|
47
|
+
|
|
48
|
+
if (!browserLanguage) {
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const match = languages.filter(lang => normalizeCode(lang) === browserLanguage);
|
|
53
|
+
|
|
54
|
+
if (match.length > 0) {
|
|
55
|
+
return match[0] || fallback;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// en == en_US
|
|
59
|
+
const matchCodeOnly = languages.filter(lang => startsWith(browserLanguage, lang));
|
|
60
|
+
return matchCodeOnly[0] || fallback;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default getPreferredLanguage;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* eslint-disable no-prototype-builtins */
|
|
2
|
+
import React, { useState, useEffect, useContext } from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import Cookie from 'js-cookie';
|
|
5
|
+
|
|
6
|
+
import browserLang from './browser-lang';
|
|
7
|
+
import { getCookieOptions } from '../Util';
|
|
8
|
+
|
|
9
|
+
const cookieName = 'nf_lang';
|
|
10
|
+
const languages = [
|
|
11
|
+
{ value: 'en', text: 'English' },
|
|
12
|
+
{ value: 'zh', text: '简体中文' },
|
|
13
|
+
];
|
|
14
|
+
const langParams = {
|
|
15
|
+
languages: ['zh', 'en'],
|
|
16
|
+
fallback: 'en',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 跨应用传递多语言选择的方式是在 query string 中添加 locale 参数,LocaleSelector 要高优先级遵守这个参数
|
|
20
|
+
const getLocaleFromSearchParams = (url = window.location.href) => {
|
|
21
|
+
const locale = new URL(url).searchParams.get('locale');
|
|
22
|
+
if (languages.find(x => x.value === locale)) {
|
|
23
|
+
return locale;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getLocale = locale =>
|
|
29
|
+
locale || getLocaleFromSearchParams() || Cookie.get(cookieName) || browserLang(langParams);
|
|
30
|
+
const setLocale = locale => Cookie.set(cookieName, locale, getCookieOptions());
|
|
31
|
+
|
|
32
|
+
const replace = (template, data) =>
|
|
33
|
+
template.replace(/{(\w*)}/g, (m, key) => (data.hasOwnProperty(key) ? data[key] : ''));
|
|
34
|
+
|
|
35
|
+
const LocaleContext = React.createContext();
|
|
36
|
+
const { Provider, Consumer } = LocaleContext;
|
|
37
|
+
|
|
38
|
+
function LocaleProvider({ children, locale, translations, ...rest }) {
|
|
39
|
+
const [currentLocale, setCurrentLocale] = useState(getLocale(locale));
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setLocale(currentLocale);
|
|
43
|
+
}, [currentLocale]);
|
|
44
|
+
|
|
45
|
+
const changeLocale = newLocale => {
|
|
46
|
+
setCurrentLocale(newLocale);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const translate = (key, data) => {
|
|
50
|
+
if (!translations[currentLocale] || !translations[currentLocale][key]) {
|
|
51
|
+
console.warn(`Warning: no ${key} translation of ${currentLocale}`);
|
|
52
|
+
return key;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return replace(translations[currentLocale][key], data);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Provider value={{ locale: currentLocale, changeLocale, t: translate, ...rest }}>
|
|
60
|
+
{children}
|
|
61
|
+
</Provider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function useLocaleContext() {
|
|
66
|
+
const context = useContext(LocaleContext);
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
LocaleProvider.propTypes = {
|
|
71
|
+
children: PropTypes.any.isRequired,
|
|
72
|
+
translations: PropTypes.object.isRequired,
|
|
73
|
+
locale: PropTypes.string,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
LocaleProvider.defaultProps = {
|
|
77
|
+
locale: '',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
LocaleProvider,
|
|
82
|
+
Consumer as LocaleConsumer,
|
|
83
|
+
LocaleContext,
|
|
84
|
+
useLocaleContext,
|
|
85
|
+
setLocale,
|
|
86
|
+
getLocale,
|
|
87
|
+
languages,
|
|
88
|
+
};
|
|
Binary file
|
|
Binary file
|