@arcblock/ux 1.17.7 → 1.17.10

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.
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _react = _interopRequireDefault(require("react"));
9
+
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+
12
+ var _styledComponents = _interopRequireDefault(require("styled-components"));
13
+
14
+ var _Box = _interopRequireDefault(require("@material-ui/core/Box"));
15
+
16
+ var _useTheme = _interopRequireDefault(require("@material-ui/core/styles/useTheme"));
17
+
18
+ var _useMediaQuery = _interopRequireDefault(require("@material-ui/core/useMediaQuery"));
19
+
20
+ var _Menu = _interopRequireDefault(require("@material-ui/icons/Menu"));
21
+
22
+ var _IconButton = _interopRequireDefault(require("@material-ui/core/IconButton"));
23
+
24
+ const _excluded = ["logo", "brand", "brandAddon", "description", "children", "addons", "mobile", "onToggleMenu"];
25
+
26
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
27
+
28
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
29
+
30
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
31
+
32
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33
+
34
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
35
+
36
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
37
+
38
+ /**
39
+ * Header 组件
40
+ * TODO: Layout/dashboard 可以复用此处的 header
41
+ */
42
+ function Header(_ref) {
43
+ let {
44
+ logo,
45
+ brand,
46
+ brandAddon,
47
+ description,
48
+ children,
49
+ addons,
50
+ mobile,
51
+ onToggleMenu
52
+ } = _ref,
53
+ rest = _objectWithoutProperties(_ref, _excluded);
54
+
55
+ const theme = (0, _useTheme.default)();
56
+ const isMobile = (0, _useMediaQuery.default)(theme.breakpoints.down('sm'));
57
+
58
+ const _mobile = _objectSpread(_objectSpread({}, Header.defaultProps.mobile), mobile); // 暂时不要通过 display: none 隐藏 logo, https://blog.patw.me/archives/1820/inline-svg-same-id-and-display-none-issue/
59
+
60
+
61
+ const hideLogo = isMobile && _mobile.hideLogo;
62
+ return /*#__PURE__*/_react.default.createElement(Root, Object.assign({}, rest, {
63
+ $theme: theme
64
+ }), _mobile.enableMenu && /*#__PURE__*/_react.default.createElement(_IconButton.default, {
65
+ color: "inherit",
66
+ edge: "start",
67
+ className: "header-menu",
68
+ onClick: onToggleMenu
69
+ }, /*#__PURE__*/_react.default.createElement(_Menu.default, null)), !hideLogo && /*#__PURE__*/_react.default.createElement("div", {
70
+ className: "header-logo"
71
+ }, logo), /*#__PURE__*/_react.default.createElement("div", {
72
+ className: "header-brand"
73
+ }, /*#__PURE__*/_react.default.createElement("div", {
74
+ className: "header-brand-title"
75
+ }, /*#__PURE__*/_react.default.createElement("h2", null, brand), /*#__PURE__*/_react.default.createElement("div", {
76
+ className: "header-brand-addon"
77
+ }, brandAddon)), /*#__PURE__*/_react.default.createElement("div", {
78
+ className: "header-brand-desc"
79
+ }, description)), children, /*#__PURE__*/_react.default.createElement(_Box.default, {
80
+ flexGrow: 1
81
+ }), /*#__PURE__*/_react.default.createElement("div", {
82
+ className: "header-addons"
83
+ }, addons));
84
+ }
85
+
86
+ Header.propTypes = {
87
+ logo: _propTypes.default.any,
88
+ // 相当于 Title
89
+ brand: _propTypes.default.string,
90
+ // brand 右侧的内容区域, 可显示一个 badge 或 tag
91
+ brandAddon: _propTypes.default.any,
92
+ // brand 下方的描述
93
+ description: _propTypes.default.string,
94
+ children: _propTypes.default.any,
95
+ // 右侧区域, 可以放置 icons/actions/login 等
96
+ addons: _propTypes.default.any,
97
+ // 窄屏下配置
98
+ mobile: _propTypes.default.shape({
99
+ enableMenu: _propTypes.default.bool,
100
+ hideLogo: _propTypes.default.bool
101
+ }),
102
+ onToggleMenu: _propTypes.default.func
103
+ };
104
+ Header.defaultProps = {
105
+ logo: null,
106
+ brand: null,
107
+ brandAddon: null,
108
+ description: null,
109
+ children: null,
110
+ addons: null,
111
+ mobile: {
112
+ enableMenu: true,
113
+ hideLogo: true
114
+ },
115
+ onToggleMenu: () => {}
116
+ };
117
+
118
+ const Root = _styledComponents.default.div.withConfig({
119
+ displayName: "Header__Root",
120
+ componentId: "sc-ea5eli-0"
121
+ })(["display:flex;align-items:center;position:relative;z-index:", ";height:64px;padding:12px 24px;font-size:14px;background:#fff;.header-menu{display:none;}.header-logo{display:inline-flex;justify-content:center;position:relative;height:40px;margin-right:16px;img,svg{width:auto;height:100%;}> a{height:100%;line-height:1;}> a::before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:'';}}.header-brand{display:flex;flex-direction:column;margin-right:16px;.header-brand-title{display:flex;align-items:center;h2{font-size:16px;}.header-brand-addon{margin-left:8px;}}.header-brand-desc{color:#9397a1;}}.header-addons{display:flex;align-items:center;}", "{height:56px;padding:8px 16px;.header-menu{display:inline-block;}.header-logo{height:32px;}.header-brand{display:none;}}"], props => props.$theme.zIndex.drawer + 1, props => props.$theme.breakpoints.down('sm'));
122
+
123
+ var _default = Header;
124
+ exports.default = _default;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _navMenu.default;
10
+ }
11
+ });
12
+
13
+ var _navMenu = _interopRequireDefault(require("./nav-menu"));
14
+
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _react = _interopRequireWildcard(require("react"));
9
+
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+
12
+ var _styledComponents = _interopRequireDefault(require("styled-components"));
13
+
14
+ var _clsx = _interopRequireDefault(require("clsx"));
15
+
16
+ require("./style.css");
17
+
18
+ const _excluded = ["items", "orientation", "children", "defaultActiveId", "textColor", "activeTextColor", "bgColor"],
19
+ _excluded2 = ["id", "icon", "label", "active", "onClick"],
20
+ _excluded3 = ["id", "icon", "label", "children"];
21
+
22
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
+
24
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
25
+
26
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
27
+
28
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
29
+
30
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
31
+
32
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33
+
34
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
35
+
36
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
37
+
38
+ const NavMenuContext = /*#__PURE__*/(0, _react.createContext)(); // 过滤 children 中的 Item/Sub, 忽略其它类型的 element
39
+
40
+ function filterItems(children) {
41
+ if (children) {
42
+ return _react.default.Children.toArray(children).filter(child => child.type === Item || child.type === Sub);
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ function useUniqueId(id) {
49
+ const _id = (0, _react.useRef)(id || "navmenu-item-id-".concat(Math.random().toString(36).slice(2)));
50
+
51
+ return _id.current;
52
+ }
53
+ /**
54
+ * NavMenu, 导航组件, 可用于 header/footer/sidebar
55
+ */
56
+
57
+
58
+ function NavMenu(_ref) {
59
+ var _children;
60
+
61
+ let {
62
+ items,
63
+ orientation,
64
+ children,
65
+ defaultActiveId,
66
+ textColor,
67
+ activeTextColor,
68
+ bgColor
69
+ } = _ref,
70
+ rest = _objectWithoutProperties(_ref, _excluded);
71
+
72
+ // eslint-disable-next-line no-param-reassign
73
+ children = filterItems(children);
74
+
75
+ if (!(items !== null && items !== void 0 && items.length) && !((_children = children) !== null && _children !== void 0 && _children.length)) {
76
+ return new Error("One of 'items' or 'children' is required by NavMenu component.");
77
+ }
78
+
79
+ const [state, setState] = (0, _react.useState)({
80
+ activeId: defaultActiveId,
81
+ openedIds: []
82
+ });
83
+ const contextValue = (0, _react.useMemo)(() => {
84
+ return _objectSpread(_objectSpread({}, state), {}, {
85
+ activate: id => {
86
+ setState(prev => _objectSpread(_objectSpread({}, prev), {}, {
87
+ activeId: id
88
+ }));
89
+ },
90
+ open: id => {
91
+ setState(prev => _objectSpread(_objectSpread({}, prev), {}, {
92
+ openedIds: [...prev.openedIds, id]
93
+ }));
94
+ },
95
+ close: id => {
96
+ setState(prev => _objectSpread(_objectSpread({}, prev), {}, {
97
+ openedIds: prev.openedIds.filter(item => item !== id)
98
+ }));
99
+ }
100
+ });
101
+ }, [state]);
102
+ const classes = (0, _clsx.default)('navmenu', "navmenu--".concat(orientation), rest.className);
103
+
104
+ const renderItem = (item, index) => {
105
+ if (item.children) {
106
+ return /*#__PURE__*/_react.default.createElement(Sub, {
107
+ key: index,
108
+ id: item.id,
109
+ icon: item.icon,
110
+ label: item.label
111
+ }, item.children.map(renderItem));
112
+ }
113
+
114
+ return (
115
+ /*#__PURE__*/
116
+ // eslint-disable-next-line react/no-array-index-key
117
+ _react.default.createElement(Item, {
118
+ key: index,
119
+ id: item.id,
120
+ icon: item.icon,
121
+ label: item.label,
122
+ active: item.active
123
+ })
124
+ );
125
+ };
126
+
127
+ return /*#__PURE__*/_react.default.createElement(NavMenuContext.Provider, {
128
+ value: contextValue
129
+ }, /*#__PURE__*/_react.default.createElement(Root, Object.assign({}, rest, {
130
+ className: classes,
131
+ $textColor: textColor,
132
+ $activeTextColor: activeTextColor,
133
+ $bgColor: bgColor
134
+ }), /*#__PURE__*/_react.default.createElement("ul", {
135
+ className: "navmenu-root"
136
+ }, items ? items.map(renderItem) : children)));
137
+ }
138
+
139
+ NavMenu.propTypes = {
140
+ items: _propTypes.default.array,
141
+ // 默认水平方向布局
142
+ orientation: _propTypes.default.oneOf(['horizontal', 'vertical']),
143
+ children: _propTypes.default.array,
144
+ defaultActiveId: _propTypes.default.string,
145
+ textColor: _propTypes.default.string,
146
+ activeTextColor: _propTypes.default.string,
147
+ bgColor: _propTypes.default.string
148
+ };
149
+ NavMenu.defaultProps = {
150
+ items: null,
151
+ orientation: 'horizontal',
152
+ children: null,
153
+ defaultActiveId: null,
154
+ textColor: '#9397a1',
155
+ activeTextColor: '#25292f',
156
+ bgColor: '#fff'
157
+ };
158
+
159
+ const Root = _styledComponents.default.nav.withConfig({
160
+ displayName: "nav-menu__Root",
161
+ componentId: "sc-1atwjw0-0"
162
+ })(["--text-color:", ";--active-text-color:", ";--bg-color:", ";padding:8px 16px;background-color:var(--bg-color);font-size:14px;ul{list-style:none;margin:0;padding:0;}.navmenu-item,.navmenu-item a,.navmenu-sub{color:var(--text-color);}.navmenu-item--active,.navmenu-item:hover,.navmenu-item:hover a,.navmenu-item--active a,.navmenu-sub--opened{color:var(--active-text-color);}"], props => props.$textColor, props => props.$activeTextColor, props => props.$bgColor);
163
+ /**
164
+ * Item
165
+ */
166
+
167
+
168
+ function Item(_ref2) {
169
+ let {
170
+ id: _id,
171
+ icon,
172
+ label,
173
+ active,
174
+ onClick
175
+ } = _ref2,
176
+ rest = _objectWithoutProperties(_ref2, _excluded2);
177
+
178
+ const id = useUniqueId(_id);
179
+ const {
180
+ activeId,
181
+ activate
182
+ } = (0, _react.useContext)(NavMenuContext);
183
+ const classes = (0, _clsx.default)('navmenu-item', {
184
+ 'navmenu-item--active': activeId === id
185
+ }, rest.className);
186
+
187
+ _react.default.useEffect(() => {
188
+ if (active) {
189
+ activate(id);
190
+ }
191
+ }, []);
192
+
193
+ const handleClick = () => {
194
+ onClick === null || onClick === void 0 ? void 0 : onClick();
195
+ activate(id);
196
+ };
197
+
198
+ return /*#__PURE__*/_react.default.createElement(ItemRoot, Object.assign({}, rest, {
199
+ className: classes,
200
+ onClick: handleClick
201
+ }), icon && /*#__PURE__*/_react.default.createElement("span", {
202
+ className: "navmenu-item-icon"
203
+ }, icon), /*#__PURE__*/_react.default.createElement("span", {
204
+ className: "navmenu-item-content"
205
+ }, label));
206
+ }
207
+
208
+ Item.propTypes = {
209
+ id: _propTypes.default.string,
210
+ icon: _propTypes.default.element,
211
+ label: _propTypes.default.node,
212
+ // 未显式指定各个 item#id 时, 不方便设置 NavMenu#defaultActiveId, 此时可以给 item 指定 active, 表示 nav menu 默认激活该 item
213
+ // (仅在初始化时有效, 后续更新无效)
214
+ active: _propTypes.default.bool,
215
+ onClick: _propTypes.default.func
216
+ };
217
+ Item.defaultProps = {
218
+ id: null,
219
+ icon: null,
220
+ label: '',
221
+ active: false,
222
+ onClick: null
223
+ };
224
+
225
+ const ItemRoot = _styledComponents.default.li.withConfig({
226
+ displayName: "nav-menu__ItemRoot",
227
+ componentId: "sc-1atwjw0-1"
228
+ })(["position:relative;cursor:pointer;a{text-decoration:none;white-space:nowrap;}a::before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:'';}"]);
229
+ /**
230
+ * Sub
231
+ */
232
+
233
+
234
+ function Sub(_ref3) {
235
+ let {
236
+ id: _id,
237
+ icon,
238
+ label,
239
+ children
240
+ } = _ref3,
241
+ rest = _objectWithoutProperties(_ref3, _excluded3);
242
+
243
+ const id = useUniqueId(_id);
244
+ const {
245
+ openedIds,
246
+ open,
247
+ close
248
+ } = (0, _react.useContext)(NavMenuContext);
249
+ const classes = (0, _clsx.default)('navmenu-sub', {
250
+ 'navmenu-sub--opened': openedIds.includes(id)
251
+ }, rest.className);
252
+ return /*#__PURE__*/_react.default.createElement(SubRoot, Object.assign({}, rest, {
253
+ className: classes,
254
+ onMouseEnter: () => open(id),
255
+ onMouseLeave: () => close(id)
256
+ }), icon && /*#__PURE__*/_react.default.createElement("span", {
257
+ className: "navmenu-sub-icon"
258
+ }, icon), /*#__PURE__*/_react.default.createElement("span", {
259
+ className: "navmenu-sub-content"
260
+ }, label), /*#__PURE__*/_react.default.createElement("div", {
261
+ className: "navmenu-sub-container"
262
+ }, /*#__PURE__*/_react.default.createElement("ul", {
263
+ className: "navmenu-sub-list"
264
+ }, filterItems(children))));
265
+ }
266
+
267
+ Sub.propTypes = {
268
+ id: _propTypes.default.string,
269
+ icon: _propTypes.default.element,
270
+ label: _propTypes.default.node.isRequired,
271
+ children: _propTypes.default.array.isRequired
272
+ };
273
+ Sub.defaultProps = {
274
+ id: null,
275
+ icon: null
276
+ };
277
+
278
+ const SubRoot = _styledComponents.default.li.withConfig({
279
+ displayName: "nav-menu__SubRoot",
280
+ componentId: "sc-1atwjw0-2"
281
+ })(["position:relative;cursor:pointer;.navmenu-sub-container{display:none;position:absolute;}&.navmenu-sub--opened > .navmenu-sub-container{display:block;top:100%;}"]);
282
+
283
+ NavMenu.Item = Item;
284
+ NavMenu.Sub = Sub;
285
+ var _default = NavMenu;
286
+ exports.default = _default;
@@ -0,0 +1,48 @@
1
+ .navmenu .navmenu-root {
2
+ display: flex;
3
+ align-items: center;
4
+ }
5
+ .navmenu-item,
6
+ .navmenu-sub {
7
+ display: flex;
8
+ align-items: center;
9
+ }
10
+ .navmenu-root > .navmenu-item,
11
+ .navmenu-root > .navmenu-sub {
12
+ margin-left: 24px;
13
+ }
14
+ .navmenu-root > .navmenu-item:first-child,
15
+ .navmenu-root > .navmenu-sub:first-child {
16
+ margin-left: 0;
17
+ }
18
+ .navmenu-item-icon,
19
+ .navmenu-sub-icon {
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ height: 20px;
24
+ line-height: 1;
25
+ }
26
+ .navmenu-item-icon > *,
27
+ .navmenu-sub-icon > * {
28
+ width: auto !important;
29
+ height: 100% !important;
30
+ }
31
+ .navmenu-item-icon {
32
+ margin-right: 4px;
33
+ }
34
+
35
+ /* 二级列表 */
36
+ .navmenu-root > .navmenu-sub > .navmenu-sub-container {
37
+ left: 50%;
38
+ transform: translateX(-50%);
39
+ padding-top: 16px;
40
+ }
41
+ .navmenu-root .navmenu-sub-list {
42
+ padding: 16px;
43
+ border-radius: 4px;
44
+ background: #fff;
45
+ }
46
+ .navmenu-sub-list > .navmenu-item + .navmenu-item {
47
+ margin-top: 8px;
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "1.17.7",
3
+ "version": "1.17.10",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -53,10 +53,10 @@
53
53
  "react": ">=16.12.0",
54
54
  "react-ga": "^2.7.0"
55
55
  },
56
- "gitHead": "df7dc8e912096fb066271f0e185dfed5ec3d4127",
56
+ "gitHead": "5be9cb0f8aa802a2c7d198f3258f74e4bcc65c64",
57
57
  "dependencies": {
58
- "@arcblock/icons": "^1.17.7",
59
- "@arcblock/react-hooks": "^1.17.7",
58
+ "@arcblock/icons": "^1.17.10",
59
+ "@arcblock/react-hooks": "^1.17.10",
60
60
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
61
61
  "@fontsource/lato": "^4.5.3",
62
62
  "@material-ui/core": "^4.12.3",
@@ -0,0 +1,156 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import Box from '@material-ui/core/Box';
5
+ import useTheme from '@material-ui/core/styles/useTheme';
6
+ import useMediaQuery from '@material-ui/core/useMediaQuery';
7
+ import MenuIcon from '@material-ui/icons/Menu';
8
+ import Button from '@material-ui/core/IconButton';
9
+
10
+ /**
11
+ * Header 组件
12
+ * TODO: Layout/dashboard 可以复用此处的 header
13
+ */
14
+ function Header({
15
+ logo,
16
+ brand,
17
+ brandAddon,
18
+ description,
19
+ children,
20
+ addons,
21
+ mobile,
22
+ onToggleMenu,
23
+ ...rest
24
+ }) {
25
+ const theme = useTheme();
26
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
27
+ const _mobile = { ...Header.defaultProps.mobile, ...mobile };
28
+ // 暂时不要通过 display: none 隐藏 logo, https://blog.patw.me/archives/1820/inline-svg-same-id-and-display-none-issue/
29
+ const hideLogo = isMobile && _mobile.hideLogo;
30
+ return (
31
+ <Root {...rest} $theme={theme}>
32
+ {_mobile.enableMenu && (
33
+ <Button color="inherit" edge="start" className="header-menu" onClick={onToggleMenu}>
34
+ <MenuIcon />
35
+ </Button>
36
+ )}
37
+ {!hideLogo && <div className="header-logo">{logo}</div>}
38
+ <div className="header-brand">
39
+ <div className="header-brand-title">
40
+ <h2>{brand}</h2>
41
+ <div className="header-brand-addon">{brandAddon}</div>
42
+ </div>
43
+ <div className="header-brand-desc">{description}</div>
44
+ </div>
45
+ {children}
46
+ <Box flexGrow={1} />
47
+ <div className="header-addons">{addons}</div>
48
+ </Root>
49
+ );
50
+ }
51
+
52
+ Header.propTypes = {
53
+ logo: PropTypes.any,
54
+ // 相当于 Title
55
+ brand: PropTypes.string,
56
+ // brand 右侧的内容区域, 可显示一个 badge 或 tag
57
+ brandAddon: PropTypes.any,
58
+ // brand 下方的描述
59
+ description: PropTypes.string,
60
+ children: PropTypes.any,
61
+ // 右侧区域, 可以放置 icons/actions/login 等
62
+ addons: PropTypes.any,
63
+ // 窄屏下配置
64
+ mobile: PropTypes.shape({
65
+ enableMenu: PropTypes.bool,
66
+ hideLogo: PropTypes.bool,
67
+ }),
68
+ onToggleMenu: PropTypes.func,
69
+ };
70
+
71
+ Header.defaultProps = {
72
+ logo: null,
73
+ brand: null,
74
+ brandAddon: null,
75
+ description: null,
76
+ children: null,
77
+ addons: null,
78
+ mobile: { enableMenu: true, hideLogo: true },
79
+ onToggleMenu: () => {},
80
+ };
81
+
82
+ const Root = styled.div`
83
+ display: flex;
84
+ align-items: center;
85
+ position: relative;
86
+ z-index: ${props => props.$theme.zIndex.drawer + 1};
87
+ height: 64px;
88
+ padding: 12px 24px;
89
+ font-size: 14px;
90
+ background: #fff;
91
+ .header-menu {
92
+ display: none;
93
+ }
94
+ .header-logo {
95
+ display: inline-flex;
96
+ justify-content: center;
97
+ position: relative;
98
+ height: 40px;
99
+ margin-right: 16px;
100
+ img,
101
+ svg {
102
+ width: auto;
103
+ height: 100%;
104
+ }
105
+ > a {
106
+ height: 100%;
107
+ line-height: 1;
108
+ }
109
+ > a::before {
110
+ position: absolute;
111
+ top: 0;
112
+ right: 0;
113
+ bottom: 0;
114
+ left: 0;
115
+ background-color: transparent;
116
+ content: '';
117
+ }
118
+ }
119
+ .header-brand {
120
+ display: flex;
121
+ flex-direction: column;
122
+ margin-right: 16px;
123
+ .header-brand-title {
124
+ display: flex;
125
+ align-items: center;
126
+ h2 {
127
+ font-size: 16px;
128
+ }
129
+ .header-brand-addon {
130
+ margin-left: 8px;
131
+ }
132
+ }
133
+ .header-brand-desc {
134
+ color: #9397a1;
135
+ }
136
+ }
137
+ .header-addons {
138
+ display: flex;
139
+ align-items: center;
140
+ }
141
+ ${props => props.$theme.breakpoints.down('sm')} {
142
+ height: 56px;
143
+ padding: 8px 16px;
144
+ .header-menu {
145
+ display: inline-block;
146
+ }
147
+ .header-logo {
148
+ height: 32px;
149
+ }
150
+ .header-brand {
151
+ display: none;
152
+ }
153
+ }
154
+ `;
155
+
156
+ export default Header;
@@ -0,0 +1 @@
1
+ export { default } from './nav-menu';
@@ -0,0 +1,246 @@
1
+ import React, { createContext, useContext, useMemo, useState, useRef } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import clsx from 'clsx';
5
+ import './style.css';
6
+
7
+ const NavMenuContext = createContext();
8
+
9
+ // 过滤 children 中的 Item/Sub, 忽略其它类型的 element
10
+ function filterItems(children) {
11
+ if (children) {
12
+ return React.Children.toArray(children).filter(
13
+ child => child.type === Item || child.type === Sub
14
+ );
15
+ }
16
+ return null;
17
+ }
18
+
19
+ function useUniqueId(id) {
20
+ const _id = useRef(id || `navmenu-item-id-${Math.random().toString(36).slice(2)}`);
21
+ return _id.current;
22
+ }
23
+
24
+ /**
25
+ * NavMenu, 导航组件, 可用于 header/footer/sidebar
26
+ */
27
+ function NavMenu({
28
+ items,
29
+ orientation,
30
+ children,
31
+ defaultActiveId,
32
+ textColor,
33
+ activeTextColor,
34
+ bgColor,
35
+ ...rest
36
+ }) {
37
+ // eslint-disable-next-line no-param-reassign
38
+ children = filterItems(children);
39
+ if (!items?.length && !children?.length) {
40
+ return new Error("One of 'items' or 'children' is required by NavMenu component.");
41
+ }
42
+ const [state, setState] = useState({ activeId: defaultActiveId, openedIds: [] });
43
+ const contextValue = useMemo(() => {
44
+ return {
45
+ ...state,
46
+ activate: id => {
47
+ setState(prev => ({ ...prev, activeId: id }));
48
+ },
49
+ open: id => {
50
+ setState(prev => ({ ...prev, openedIds: [...prev.openedIds, id] }));
51
+ },
52
+ close: id => {
53
+ setState(prev => ({ ...prev, openedIds: prev.openedIds.filter(item => item !== id) }));
54
+ },
55
+ };
56
+ }, [state]);
57
+ const classes = clsx('navmenu', `navmenu--${orientation}`, rest.className);
58
+ const renderItem = (item, index) => {
59
+ if (item.children) {
60
+ return (
61
+ <Sub key={index} id={item.id} icon={item.icon} label={item.label}>
62
+ {item.children.map(renderItem)}
63
+ </Sub>
64
+ );
65
+ }
66
+ return (
67
+ // eslint-disable-next-line react/no-array-index-key
68
+ <Item key={index} id={item.id} icon={item.icon} label={item.label} active={item.active} />
69
+ );
70
+ };
71
+ return (
72
+ <NavMenuContext.Provider value={contextValue}>
73
+ <Root
74
+ {...rest}
75
+ className={classes}
76
+ $textColor={textColor}
77
+ $activeTextColor={activeTextColor}
78
+ $bgColor={bgColor}>
79
+ <ul className="navmenu-root">{items ? items.map(renderItem) : children}</ul>
80
+ </Root>
81
+ </NavMenuContext.Provider>
82
+ );
83
+ }
84
+
85
+ NavMenu.propTypes = {
86
+ items: PropTypes.array,
87
+ // 默认水平方向布局
88
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
89
+ children: PropTypes.array,
90
+ defaultActiveId: PropTypes.string,
91
+ textColor: PropTypes.string,
92
+ activeTextColor: PropTypes.string,
93
+ bgColor: PropTypes.string,
94
+ };
95
+ NavMenu.defaultProps = {
96
+ items: null,
97
+ orientation: 'horizontal',
98
+ children: null,
99
+ defaultActiveId: null,
100
+ textColor: '#9397a1',
101
+ activeTextColor: '#25292f',
102
+ bgColor: '#fff',
103
+ };
104
+
105
+ const Root = styled.nav`
106
+ --text-color: ${props => props.$textColor};
107
+ --active-text-color: ${props => props.$activeTextColor};
108
+ --bg-color: ${props => props.$bgColor};
109
+
110
+ padding: 8px 16px;
111
+ background-color: var(--bg-color);
112
+ font-size: 14px;
113
+ ul {
114
+ list-style: none;
115
+ margin: 0;
116
+ padding: 0;
117
+ }
118
+ .navmenu-item,
119
+ .navmenu-item a,
120
+ .navmenu-sub {
121
+ color: var(--text-color);
122
+ }
123
+ .navmenu-item--active,
124
+ .navmenu-item:hover,
125
+ .navmenu-item:hover a,
126
+ .navmenu-item--active a,
127
+ .navmenu-sub--opened {
128
+ color: var(--active-text-color);
129
+ }
130
+ `;
131
+
132
+ /**
133
+ * Item
134
+ */
135
+ function Item({ id: _id, icon, label, active, onClick, ...rest }) {
136
+ const id = useUniqueId(_id);
137
+ const { activeId, activate } = useContext(NavMenuContext);
138
+ const classes = clsx('navmenu-item', { 'navmenu-item--active': activeId === id }, rest.className);
139
+ React.useEffect(() => {
140
+ if (active) {
141
+ activate(id);
142
+ }
143
+ }, []);
144
+ const handleClick = () => {
145
+ onClick?.();
146
+ activate(id);
147
+ };
148
+ return (
149
+ <ItemRoot {...rest} className={classes} onClick={handleClick}>
150
+ {icon && <span className="navmenu-item-icon">{icon}</span>}
151
+ <span className="navmenu-item-content">{label}</span>
152
+ </ItemRoot>
153
+ );
154
+ }
155
+
156
+ Item.propTypes = {
157
+ id: PropTypes.string,
158
+ icon: PropTypes.element,
159
+ label: PropTypes.node,
160
+ // 未显式指定各个 item#id 时, 不方便设置 NavMenu#defaultActiveId, 此时可以给 item 指定 active, 表示 nav menu 默认激活该 item
161
+ // (仅在初始化时有效, 后续更新无效)
162
+ active: PropTypes.bool,
163
+ onClick: PropTypes.func,
164
+ };
165
+
166
+ Item.defaultProps = {
167
+ id: null,
168
+ icon: null,
169
+ label: '',
170
+ active: false,
171
+ onClick: null,
172
+ };
173
+
174
+ const ItemRoot = styled.li`
175
+ position: relative;
176
+ cursor: pointer;
177
+ a {
178
+ text-decoration: none;
179
+ white-space: nowrap;
180
+ }
181
+ a::before {
182
+ position: absolute;
183
+ top: 0;
184
+ right: 0;
185
+ bottom: 0;
186
+ left: 0;
187
+ background-color: transparent;
188
+ content: '';
189
+ }
190
+ `;
191
+
192
+ /**
193
+ * Sub
194
+ */
195
+ function Sub({ id: _id, icon, label, children, ...rest }) {
196
+ const id = useUniqueId(_id);
197
+ const { openedIds, open, close } = useContext(NavMenuContext);
198
+ const classes = clsx(
199
+ 'navmenu-sub',
200
+ { 'navmenu-sub--opened': openedIds.includes(id) },
201
+ rest.className
202
+ );
203
+ return (
204
+ <SubRoot
205
+ {...rest}
206
+ className={classes}
207
+ onMouseEnter={() => open(id)}
208
+ onMouseLeave={() => close(id)}>
209
+ {icon && <span className="navmenu-sub-icon">{icon}</span>}
210
+ <span className="navmenu-sub-content">{label}</span>
211
+ <div className="navmenu-sub-container">
212
+ <ul className="navmenu-sub-list">{filterItems(children)}</ul>
213
+ </div>
214
+ </SubRoot>
215
+ );
216
+ }
217
+
218
+ Sub.propTypes = {
219
+ id: PropTypes.string,
220
+ icon: PropTypes.element,
221
+ label: PropTypes.node.isRequired,
222
+ children: PropTypes.array.isRequired,
223
+ };
224
+
225
+ Sub.defaultProps = {
226
+ id: null,
227
+ icon: null,
228
+ };
229
+
230
+ const SubRoot = styled.li`
231
+ position: relative;
232
+ cursor: pointer;
233
+ .navmenu-sub-container {
234
+ display: none;
235
+ position: absolute;
236
+ }
237
+ &.navmenu-sub--opened > .navmenu-sub-container {
238
+ display: block;
239
+ top: 100%;
240
+ }
241
+ `;
242
+
243
+ NavMenu.Item = Item;
244
+ NavMenu.Sub = Sub;
245
+
246
+ export default NavMenu;
@@ -0,0 +1,48 @@
1
+ .navmenu .navmenu-root {
2
+ display: flex;
3
+ align-items: center;
4
+ }
5
+ .navmenu-item,
6
+ .navmenu-sub {
7
+ display: flex;
8
+ align-items: center;
9
+ }
10
+ .navmenu-root > .navmenu-item,
11
+ .navmenu-root > .navmenu-sub {
12
+ margin-left: 24px;
13
+ }
14
+ .navmenu-root > .navmenu-item:first-child,
15
+ .navmenu-root > .navmenu-sub:first-child {
16
+ margin-left: 0;
17
+ }
18
+ .navmenu-item-icon,
19
+ .navmenu-sub-icon {
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ height: 20px;
24
+ line-height: 1;
25
+ }
26
+ .navmenu-item-icon > *,
27
+ .navmenu-sub-icon > * {
28
+ width: auto !important;
29
+ height: 100% !important;
30
+ }
31
+ .navmenu-item-icon {
32
+ margin-right: 4px;
33
+ }
34
+
35
+ /* 二级列表 */
36
+ .navmenu-root > .navmenu-sub > .navmenu-sub-container {
37
+ left: 50%;
38
+ transform: translateX(-50%);
39
+ padding-top: 16px;
40
+ }
41
+ .navmenu-root .navmenu-sub-list {
42
+ padding: 16px;
43
+ border-radius: 4px;
44
+ background: #fff;
45
+ }
46
+ .navmenu-sub-list > .navmenu-item + .navmenu-item {
47
+ margin-top: 8px;
48
+ }