@blocklet/ui-react 2.1.9 → 2.1.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,67 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = Brand;
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 _useTheme = _interopRequireDefault(require("@mui/styles/useTheme"));
15
+
16
+ const _excluded = ["name", "logo", "description"];
17
+
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+
20
+ 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; }
21
+
22
+ 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; }
23
+
24
+ function Brand(_ref) {
25
+ let {
26
+ name,
27
+ logo,
28
+ description
29
+ } = _ref,
30
+ rest = _objectWithoutProperties(_ref, _excluded);
31
+
32
+ const theme = (0, _useTheme.default)();
33
+
34
+ if (!name && !logo && !description) {
35
+ return null;
36
+ }
37
+
38
+ const logoElement = /*#__PURE__*/_react.default.isValidElement(logo) ? logo : /*#__PURE__*/_react.default.createElement("img", {
39
+ src: logo,
40
+ alt: name
41
+ });
42
+ return /*#__PURE__*/_react.default.createElement(Root, Object.assign({}, rest, {
43
+ $theme: theme
44
+ }), /*#__PURE__*/_react.default.createElement("div", null, logo && /*#__PURE__*/_react.default.createElement("div", {
45
+ className: "footer-brand-logo"
46
+ }, logoElement), name && /*#__PURE__*/_react.default.createElement("div", {
47
+ className: "footer-brand-name"
48
+ }, name)), description && /*#__PURE__*/_react.default.createElement("div", {
49
+ className: "footer-brand-desc"
50
+ }, description));
51
+ }
52
+
53
+ Brand.propTypes = {
54
+ name: _propTypes.default.node,
55
+ logo: _propTypes.default.node,
56
+ description: _propTypes.default.string
57
+ };
58
+ Brand.defaultProps = {
59
+ name: '',
60
+ logo: '',
61
+ description: ''
62
+ };
63
+
64
+ const Root = _styledComponents.default.div.withConfig({
65
+ displayName: "brand__Root",
66
+ componentId: "sc-6z2c3k-0"
67
+ })(["display:flex;flex-direction:column;width:240px;font-size:14px;a{text-decoration:none;color:inherit;}> div:first-child{display:flex;align-items:center;}.footer-brand-logo{height:32px;margin-right:8px;img,svg{max-width:100%;max-height:100%;}}.footer-brand-name{font-size:16px;font-weight:bold;}.footer-brand-desc{margin-top:16px;}", "{width:auto;}"], props => props.$theme.breakpoints.down('sm'));
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = Copyright;
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
+ const _excluded = ["owner", "year"];
15
+
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+
18
+ 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; }
19
+
20
+ 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; }
21
+
22
+ function Copyright(_ref) {
23
+ let {
24
+ owner,
25
+ year
26
+ } = _ref,
27
+ rest = _objectWithoutProperties(_ref, _excluded);
28
+
29
+ return /*#__PURE__*/_react.default.createElement(Root, rest, "Copyright \xA9 ", year, " ", owner);
30
+ }
31
+
32
+ Copyright.propTypes = {
33
+ owner: _propTypes.default.string,
34
+ year: _propTypes.default.string
35
+ };
36
+ Copyright.defaultProps = {
37
+ owner: 'ArcBlock, Inc.',
38
+ year: "".concat(new Date().getFullYear())
39
+ };
40
+
41
+ const Root = _styledComponents.default.p.withConfig({
42
+ displayName: "copyright__Root",
43
+ componentId: "sc-g2r3zf-0"
44
+ })(["margin:0;font-size:14px;"]);
@@ -11,9 +11,21 @@ var _styledComponents = _interopRequireDefault(require("styled-components"));
11
11
 
12
12
  var _useTheme = _interopRequireDefault(require("@mui/styles/useTheme"));
13
13
 
14
+ var _Box = _interopRequireDefault(require("@mui/material/Box"));
15
+
14
16
  var _Container = _interopRequireDefault(require("@mui/material/Container"));
15
17
 
16
- var _NavMenu = _interopRequireDefault(require("@arcblock/ux/lib/NavMenu"));
18
+ var _brand = _interopRequireDefault(require("./brand"));
19
+
20
+ var _links = _interopRequireDefault(require("./links"));
21
+
22
+ var _socialMedia = _interopRequireDefault(require("./social-media"));
23
+
24
+ var _copyright = _interopRequireDefault(require("./copyright"));
25
+
26
+ var _row = _interopRequireDefault(require("./row"));
27
+
28
+ var _utils = require("../utils");
17
29
 
18
30
  var _types = require("../types");
19
31
 
@@ -21,6 +33,12 @@ const _excluded = ["meta"];
21
33
 
22
34
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
35
 
36
+ 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; }
37
+
38
+ 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; }
39
+
40
+ 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; }
41
+
24
42
  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; }
25
43
 
26
44
  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; }
@@ -45,35 +63,44 @@ function Footer(_ref) {
45
63
  appLogo,
46
64
  appName,
47
65
  theme: blockletTheme,
48
- navigation = []
49
- } = blocklet; // TODO: 支持分组的 links (#590)
50
- // 暂时只支持扁平的 links, 没有 link 属性的 navigation item 会被忽略
51
-
52
- const navMenuItems = navigation.filter(item => item.link).map(item => ({
53
- label: /*#__PURE__*/_react.default.createElement("a", {
54
- href: item.link
55
- }, item.title)
56
- }));
66
+ navigation = [],
67
+ copyrightOwner,
68
+ footerNavigation,
69
+ footerLinks,
70
+ socialMedia
71
+ } = blocklet;
72
+ const navMenuItems = (0, _utils.mapRecursive)( // TODO: 需要讨论 blocklet.yml 是否需要专门为 footer navigation 提供配置, 暂时与 header 共用 navigation 配置
73
+ footerNavigation || navigation, item => _objectSpread(_objectSpread({}, item), {}, {
74
+ label: item.title,
75
+ link: item.link
76
+ }), 'items');
57
77
  return /*#__PURE__*/_react.default.createElement(Root, Object.assign({}, rest, {
58
78
  $bgcolor: blockletTheme === null || blockletTheme === void 0 ? void 0 : blockletTheme.background,
59
79
  $theme: theme
60
- }), /*#__PURE__*/_react.default.createElement(_Container.default, null, /*#__PURE__*/_react.default.createElement("div", {
61
- className: "footer_line"
62
- }, /*#__PURE__*/_react.default.createElement("div", {
63
- className: "footer_brand"
64
- }, /*#__PURE__*/_react.default.createElement("img", {
65
- src: appLogo,
66
- alt: "logo"
67
- }), /*#__PURE__*/_react.default.createElement("span", null, appName)), /*#__PURE__*/_react.default.createElement("div", {
68
- className: "footer_nav"
69
- }, !!(navMenuItems !== null && navMenuItems !== void 0 && navMenuItems.length) && /*#__PURE__*/_react.default.createElement(_NavMenu.default, {
70
- items: navMenuItems
71
- }))), /*#__PURE__*/_react.default.createElement("div", {
72
- className: "footer_line"
73
- }, /*#__PURE__*/_react.default.createElement("div", {
74
- className: "footer_copyright"
75
- }, /*#__PURE__*/_react.default.createElement("span", null, "Powered by Blocklet Server")), /*#__PURE__*/_react.default.createElement("div", {
76
- className: "footer_extra"
80
+ }), /*#__PURE__*/_react.default.createElement(_Container.default, null, /*#__PURE__*/_react.default.createElement(_row.default, null, /*#__PURE__*/_react.default.createElement(_Box.default, null, /*#__PURE__*/_react.default.createElement(_brand.default, {
81
+ name: appName,
82
+ logo: appLogo,
83
+ description: "Official DID Wallet webapp implementation that makes it possible to manage your digital identities and assets from the browser."
84
+ }), /*#__PURE__*/_react.default.createElement(_socialMedia.default, {
85
+ items: socialMedia,
86
+ style: {
87
+ marginTop: 24
88
+ }
89
+ })), /*#__PURE__*/_react.default.createElement(_links.default, {
90
+ links: navMenuItems
91
+ })), /*#__PURE__*/_react.default.createElement(_row.default, {
92
+ autoCenter: true,
93
+ style: {
94
+ marginTop: 72
95
+ }
96
+ }, /*#__PURE__*/_react.default.createElement(_copyright.default, {
97
+ owner: copyrightOwner || appName
98
+ }), /*#__PURE__*/_react.default.createElement(_links.default, {
99
+ flowLayout: true,
100
+ links: footerLinks,
101
+ style: {
102
+ color: '#999'
103
+ }
77
104
  }))));
78
105
  }
79
106
 
@@ -87,9 +114,9 @@ Footer.defaultProps = {
87
114
  const Root = _styledComponents.default.div.withConfig({
88
115
  displayName: "Footer__Root",
89
116
  componentId: "sc-1iwl1nd-0"
90
- })(["padding:32px 40px;border-top:1px solid #eee;color:#9397a1;", " .footer_line{display:flex;justify-content:space-between;}.footer_line + .footer_line{margin-top:32px;}.footer_brand{display:flex;align-items:center;color:#777;img{height:36px;}span{margin-left:8px;font-size:20px;}}.footer_nav{.navmenu{padding-left:0;padding-right:0;}> *{background:transparent;}}", "{.footer_line{flex-direction:column;}.footer_line + .footer_line{margin-top:0;}.footer_brand{img{height:24px;}span{font-size:16px;}}}"], _ref2 => {
117
+ })(["padding:48px 0;border-top:1px solid #eee;color:", ";", ""], props => props.$theme.palette.grey[600], _ref2 => {
91
118
  let {
92
119
  $bgcolor
93
120
  } = _ref2;
94
121
  return $bgcolor && "background-color: ".concat($bgcolor, ";");
95
- }, props => props.$theme.breakpoints.down('md'));
122
+ });
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = Links;
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 _useTheme = _interopRequireDefault(require("@mui/styles/useTheme"));
15
+
16
+ var _clsx = _interopRequireDefault(require("clsx"));
17
+
18
+ const _excluded = ["links", "flowLayout"];
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ 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); }
23
+
24
+ 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; }
25
+
26
+ 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; }
27
+
28
+ 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; }
29
+
30
+ /**
31
+ * footer 中的 links (支持分组, 最多支持 2 级)
32
+ * TODO: dark/light theme
33
+ */
34
+ function Links(_ref) {
35
+ let {
36
+ links,
37
+ flowLayout
38
+ } = _ref,
39
+ rest = _objectWithoutProperties(_ref, _excluded);
40
+
41
+ const theme = (0, _useTheme.default)();
42
+ const [activeIndex, setActiveIndex] = (0, _react.useState)(-1);
43
+
44
+ if (!(links !== null && links !== void 0 && links.length)) {
45
+ return null;
46
+ } // 只要发现一项元素有子元素, 就认为是分组 (大字号突出 group title)
47
+
48
+
49
+ const isGroupMode = links.some(item => {
50
+ var _item$items;
51
+
52
+ return (_item$items = item.items) === null || _item$items === void 0 ? void 0 : _item$items.length;
53
+ });
54
+
55
+ const renderItem = _ref2 => {
56
+ let {
57
+ label,
58
+ link,
59
+ render,
60
+ props
61
+ } = _ref2;
62
+ let result = label;
63
+
64
+ if (render) {
65
+ result = render({
66
+ label,
67
+ link,
68
+ props
69
+ });
70
+ } else if (link) {
71
+ result = /*#__PURE__*/_react.default.createElement("a", Object.assign({
72
+ href: link
73
+ }, props), label);
74
+ }
75
+
76
+ return result;
77
+ };
78
+
79
+ return /*#__PURE__*/_react.default.createElement(Root, Object.assign({}, rest, {
80
+ className: (0, _clsx.default)(rest.className, {
81
+ 'footer-links--grouped': isGroupMode,
82
+ 'footer-links--flow': flowLayout
83
+ }),
84
+ $theme: theme
85
+ }), /*#__PURE__*/_react.default.createElement("div", {
86
+ className: "footer-links-inner"
87
+ }, flowLayout && links.map((item, i) => /*#__PURE__*/_react.default.createElement("span", {
88
+ key: i,
89
+ className: "footer-links-item"
90
+ }, renderItem(item))), !flowLayout && links.map((item, i) => {
91
+ const {
92
+ items
93
+ } = item;
94
+ return /*#__PURE__*/_react.default.createElement("div", {
95
+ key: i,
96
+ className: (0, _clsx.default)('footer-links-group', {
97
+ 'footer-links-group--active': i === activeIndex
98
+ }),
99
+ onClick: () => setActiveIndex(activeIndex === i ? -1 : i)
100
+ }, /*#__PURE__*/_react.default.createElement("span", {
101
+ className: "footer-links-item"
102
+ }, renderItem(item)), !!(items !== null && items !== void 0 && items.length) && /*#__PURE__*/_react.default.createElement("div", {
103
+ className: "footer-links-sub"
104
+ }, items.map((child, j) => /*#__PURE__*/_react.default.createElement("span", {
105
+ key: j,
106
+ className: "footer-links-item"
107
+ }, renderItem(child)))));
108
+ })));
109
+ }
110
+
111
+ Links.propTypes = {
112
+ links: _propTypes.default.arrayOf(_propTypes.default.shape({
113
+ label: _propTypes.default.string,
114
+ link: _propTypes.default.string,
115
+ render: _propTypes.default.func,
116
+ props: _propTypes.default.object
117
+ })),
118
+ // 流动布局, 简单的从左到右排列
119
+ flowLayout: _propTypes.default.bool
120
+ };
121
+ Links.defaultProps = {
122
+ links: [],
123
+ flowLayout: false
124
+ };
125
+
126
+ const Root = _styledComponents.default.div.withConfig({
127
+ displayName: "links__Root",
128
+ componentId: "sc-19wysi2-0"
129
+ })(["overflow:hidden;color:", ";.footer-links-inner{display:flex;justify-content:space-between;margin:0 -8px;}.footer-links-group,.footer-links-sub{display:flex;flex-direction:column;}.footer-links-item{display:inline-block;padding:0 8px;font-size:14px;line-height:1.6;}.footer-links-group + .footer-links-group{margin-left:128px;}&.footer-links--grouped{.footer-links-group{> .footer-links-item{font-weight:bold;}.footer-links-sub{margin-top:8px;}}}a{color:inherit;text-decoration:none;&:hover{text-decoration:underline;}}&.footer-links--flow{display:inline-flex;.footer-links-inner{justify-content:center;flex-wrap:wrap;margin:0 -8px;.footer-links-item{padding:0 8px;}}}", "{.footer-links-group + .footer-links-group{margin-left:56px;}}", "{.footer-links-group + .footer-links-group{margin-left:40px;}}", "{.footer-links-inner{flex-direction:column;margin:0;}.footer-links-sub{display:none;}.footer-links-group{padding:12px 0;border-top:1px solid ", ";}.footer-links-group + .footer-links-group{margin-left:0;}.footer-links-group--active{.footer-links-sub{display:flex;flex-direction:row;flex-wrap:wrap;.footer-links-item{flex:0 0 50%;}}}.footer-links-item{padding:0;font-size:13px;}&.footer-links--grouped{.footer-links-group{> .footer-links-item{font-size:14px;}}}&.footer-links--flow{.footer-links-inner{flex-direction:row;}}}"], props => props.$theme.palette.grey[600], props => props.$theme.breakpoints.down('lg'), props => props.$theme.breakpoints.down('lg'), props => props.$theme.breakpoints.down('sm'), props => props.$theme.palette.grey[200]);
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = Row;
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 _useTheme = _interopRequireDefault(require("@mui/styles/useTheme"));
15
+
16
+ var _clsx = _interopRequireDefault(require("clsx"));
17
+
18
+ const _excluded = ["children", "autoCenter"];
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ 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; }
23
+
24
+ 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; }
25
+
26
+ function Row(_ref) {
27
+ let {
28
+ children,
29
+ autoCenter
30
+ } = _ref,
31
+ rest = _objectWithoutProperties(_ref, _excluded);
32
+
33
+ const theme = (0, _useTheme.default)();
34
+
35
+ if (!children) {
36
+ return null;
37
+ }
38
+
39
+ return /*#__PURE__*/_react.default.createElement(RowRoot, Object.assign({}, rest, {
40
+ className: (0, _clsx.default)(rest.className, {
41
+ 'footer-row-auto-center': autoCenter
42
+ }),
43
+ $theme: theme
44
+ }), children);
45
+ }
46
+
47
+ Row.propTypes = {
48
+ children: _propTypes.default.any,
49
+ autoCenter: _propTypes.default.bool
50
+ };
51
+ Row.defaultProps = {
52
+ children: null,
53
+ autoCenter: false
54
+ };
55
+
56
+ const RowRoot = _styledComponents.default.div.withConfig({
57
+ displayName: "row__RowRoot",
58
+ componentId: "sc-11upj1u-0"
59
+ })(["display:flex;justify-content:space-between;& + &{margin-top:24px;}&.footer-row-auto-center > *:only-child{margin:0 auto;}", "{align-items:stretch;flex-direction:column;gap:16px;> *{flex:1 0 100%;}&.footer-row-auto-center > *{margin:0 auto;}}"], props => props.$theme.breakpoints.down('md'));
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = SocialMedia;
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 _useTheme = _interopRequireDefault(require("@mui/styles/useTheme"));
15
+
16
+ var _Icon = _interopRequireDefault(require("../Icon"));
17
+
18
+ const _excluded = ["items"];
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ 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; }
23
+
24
+ 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; }
25
+
26
+ function SocialMedia(_ref) {
27
+ let {
28
+ items
29
+ } = _ref,
30
+ rest = _objectWithoutProperties(_ref, _excluded);
31
+
32
+ const theme = (0, _useTheme.default)();
33
+
34
+ if (!(items !== null && items !== void 0 && items.length)) {
35
+ return null;
36
+ }
37
+
38
+ return /*#__PURE__*/_react.default.createElement(Root, rest, items.map((item, i) => {
39
+ return (
40
+ /*#__PURE__*/
41
+ // eslint-disable-next-line react/no-array-index-key
42
+ _react.default.createElement("a", {
43
+ key: i,
44
+ href: item.link,
45
+ target: "_blank",
46
+ rel: "noreferrer"
47
+ }, /*#__PURE__*/_react.default.createElement(_Icon.default, {
48
+ icon: item.icon,
49
+ sx: {
50
+ bgcolor: theme.palette.grey[600],
51
+ color: '#fff'
52
+ },
53
+ size: 44,
54
+ variant: "rounded",
55
+ component: "span"
56
+ }))
57
+ );
58
+ }));
59
+ }
60
+
61
+ SocialMedia.propTypes = {
62
+ items: _propTypes.default.arrayOf(_propTypes.default.shape({
63
+ // icon 对应 Icon#icon prop, 支持 iconify name 和 url 2 种形式:
64
+ icon: _propTypes.default.string,
65
+ link: _propTypes.default.string
66
+ }))
67
+ };
68
+ SocialMedia.defaultProps = {
69
+ items: null
70
+ };
71
+
72
+ const Root = _styledComponents.default.div.withConfig({
73
+ displayName: "social-media__Root",
74
+ componentId: "sc-w4ariy-0"
75
+ })(["display:inline-flex;align-items:center;a + a{margin-left:12px;}"]);
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = Icon;
7
+
8
+ var _react = _interopRequireDefault(require("react"));
9
+
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+
12
+ var _Avatar = _interopRequireDefault(require("@mui/material/Avatar"));
13
+
14
+ require("@iconify/iconify");
15
+
16
+ var _utils = require("../utils");
17
+
18
+ const _excluded = ["icon", "size"];
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ 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; }
23
+
24
+ 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; }
25
+
26
+ 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; }
27
+
28
+ 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; }
29
+
30
+ 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; }
31
+
32
+ /**
33
+ * Icon 组件, 基于 mui Avatar 组件扩展对 iconify 的支持
34
+ */
35
+ function Icon(_ref) {
36
+ let {
37
+ icon,
38
+ size
39
+ } = _ref,
40
+ rest = _objectWithoutProperties(_ref, _excluded);
41
+
42
+ const sx = _objectSpread({}, rest.sx);
43
+
44
+ if (size) {
45
+ sx.width = size;
46
+ sx.height = size;
47
+ }
48
+
49
+ if ((0, _utils.isUrl)(icon)) {
50
+ return /*#__PURE__*/_react.default.createElement(_Avatar.default, Object.assign({}, rest, {
51
+ src: icon,
52
+ sx: sx
53
+ }));
54
+ }
55
+
56
+ if (icon) {
57
+ // y = 0.6 * x + 4
58
+ const iconHeight = size ? 0.6 * size + 4 : 0;
59
+ return /*#__PURE__*/_react.default.createElement(_Avatar.default, Object.assign({}, rest, {
60
+ sx: sx
61
+ }), /*#__PURE__*/_react.default.createElement("span", Object.assign({
62
+ className: "iconify",
63
+ "data-icon": icon
64
+ }, iconHeight && {
65
+ 'data-height': iconHeight
66
+ })));
67
+ }
68
+
69
+ return null;
70
+ }
71
+
72
+ Icon.propTypes = {
73
+ // icon 支持 2 种形式:
74
+ // 1. iconify icon name: <prefix>:<name>
75
+ // 2. url
76
+ icon: _propTypes.default.string.isRequired,
77
+ size: _propTypes.default.number
78
+ };
79
+ Icon.defaultProps = {
80
+ size: null
81
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.mapRecursive = exports.isUrl = void 0;
7
+
8
+ 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; }
9
+
10
+ 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; }
11
+
12
+ 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; }
13
+
14
+ const mapRecursive = function mapRecursive(array, fn) {
15
+ let childrenKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'children';
16
+ return array.map(item => {
17
+ if (Array.isArray(item[childrenKey])) {
18
+ return fn(_objectSpread(_objectSpread({}, item), {}, {
19
+ [childrenKey]: mapRecursive(item[childrenKey], fn, childrenKey)
20
+ }));
21
+ }
22
+
23
+ return fn(item);
24
+ });
25
+ }; // "http://", "https://", "//" 开头的 3 种情况
26
+
27
+
28
+ exports.mapRecursive = mapRecursive;
29
+
30
+ const isUrl = str => {
31
+ return /^(https?:)?\/\//.test(str);
32
+ };
33
+
34
+ exports.isUrl = isUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -34,8 +34,8 @@
34
34
  "url": "https://github.com/ArcBlock/ux/issues"
35
35
  },
36
36
  "dependencies": {
37
- "@arcblock/did-connect": "^2.1.9",
38
- "@arcblock/ux": "^2.1.9",
37
+ "@arcblock/did-connect": "^2.1.10",
38
+ "@arcblock/ux": "^2.1.10",
39
39
  "@iconify/iconify": "^2.2.1",
40
40
  "@mui/material": "^5.6.4",
41
41
  "core-js": "^3.6.4",
@@ -56,5 +56,5 @@
56
56
  "eslint-plugin-react-hooks": "^4.2.0",
57
57
  "jest": "^24.1.0"
58
58
  },
59
- "gitHead": "7a3efb456e1dbe3318ab8757feb9e58a88c08d8e"
59
+ "gitHead": "f31615cda7024bde33cc8b4be093ab8e0baf46a1"
60
60
  }
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import useTheme from '@mui/styles/useTheme';
5
+
6
+ export default function Brand({ name, logo, description, ...rest }) {
7
+ const theme = useTheme();
8
+ if (!name && !logo && !description) {
9
+ return null;
10
+ }
11
+
12
+ const logoElement = React.isValidElement(logo) ? logo : <img src={logo} alt={name} />;
13
+
14
+ return (
15
+ <Root {...rest} $theme={theme}>
16
+ <div>
17
+ {logo && <div className="footer-brand-logo">{logoElement}</div>}
18
+ {name && <div className="footer-brand-name">{name}</div>}
19
+ </div>
20
+ {description && <div className="footer-brand-desc">{description}</div>}
21
+ </Root>
22
+ );
23
+ }
24
+
25
+ Brand.propTypes = {
26
+ name: PropTypes.node,
27
+ logo: PropTypes.node,
28
+ description: PropTypes.string,
29
+ };
30
+
31
+ Brand.defaultProps = {
32
+ name: '',
33
+ logo: '',
34
+ description: '',
35
+ };
36
+
37
+ const Root = styled.div`
38
+ display: flex;
39
+ flex-direction: column;
40
+ width: 240px;
41
+ font-size: 14px;
42
+ a {
43
+ text-decoration: none;
44
+ color: inherit;
45
+ }
46
+ > div:first-child {
47
+ display: flex;
48
+ align-items: center;
49
+ }
50
+ .footer-brand-logo {
51
+ height: 32px;
52
+ margin-right: 8px;
53
+ img,
54
+ svg {
55
+ max-width: 100%;
56
+ max-height: 100%;
57
+ }
58
+ }
59
+ .footer-brand-name {
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ }
63
+ .footer-brand-desc {
64
+ margin-top: 16px;
65
+ }
66
+
67
+ ${props => props.$theme.breakpoints.down('sm')} {
68
+ width: auto;
69
+ }
70
+ `;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+
5
+ export default function Copyright({ owner, year, ...rest }) {
6
+ return (
7
+ <Root {...rest}>
8
+ Copyright © {year} {owner}
9
+ </Root>
10
+ );
11
+ }
12
+
13
+ Copyright.propTypes = {
14
+ owner: PropTypes.string,
15
+ year: PropTypes.string,
16
+ };
17
+
18
+ Copyright.defaultProps = {
19
+ owner: 'ArcBlock, Inc.',
20
+ year: `${new Date().getFullYear()}`,
21
+ };
22
+
23
+ const Root = styled.p`
24
+ margin: 0;
25
+ font-size: 14px;
26
+ `;
@@ -1,8 +1,14 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import useTheme from '@mui/styles/useTheme';
4
+ import Box from '@mui/material/Box';
4
5
  import Container from '@mui/material/Container';
5
- import NavMenu from '@arcblock/ux/lib/NavMenu';
6
+ import Brand from './brand';
7
+ import Links from './links';
8
+ import SocialMedia from './social-media';
9
+ import Copyright from './copyright';
10
+ import Row from './row';
11
+ import { mapRecursive } from '../utils';
6
12
 
7
13
  import { blockletMetaProps } from '../types';
8
14
 
@@ -16,33 +22,45 @@ export default function Footer({ meta, ...rest }) {
16
22
  return null;
17
23
  }
18
24
 
19
- const { appLogo, appName, theme: blockletTheme, navigation = [] } = blocklet;
20
- // TODO: 支持分组的 links (#590)
21
- // 暂时只支持扁平的 links, 没有 link 属性的 navigation item 会被忽略
22
- const navMenuItems = navigation
23
- .filter(item => item.link)
24
- .map(item => ({
25
- label: <a href={item.link}>{item.title}</a>,
26
- }));
25
+ const {
26
+ appLogo,
27
+ appName,
28
+ theme: blockletTheme,
29
+ navigation = [],
30
+ copyrightOwner,
31
+ footerNavigation,
32
+ footerLinks,
33
+ socialMedia,
34
+ } = blocklet;
35
+ const navMenuItems = mapRecursive(
36
+ // TODO: 需要讨论 blocklet.yml 是否需要专门为 footer navigation 提供配置, 暂时与 header 共用 navigation 配置
37
+ footerNavigation || navigation,
38
+ item => ({
39
+ ...item,
40
+ label: item.title,
41
+ link: item.link,
42
+ }),
43
+ 'items'
44
+ );
27
45
 
28
46
  return (
29
47
  <Root {...rest} $bgcolor={blockletTheme?.background} $theme={theme}>
30
48
  <Container>
31
- <div className="footer_line">
32
- <div className="footer_brand">
33
- <img src={appLogo} alt="logo" />
34
- <span>{appName}</span>
35
- </div>
36
- <div className="footer_nav">
37
- {!!navMenuItems?.length && <NavMenu items={navMenuItems} />}
38
- </div>
39
- </div>
40
- <div className="footer_line">
41
- <div className="footer_copyright">
42
- <span>Powered by Blocklet Server</span>
43
- </div>
44
- <div className="footer_extra" />
45
- </div>
49
+ <Row>
50
+ <Box>
51
+ <Brand
52
+ name={appName}
53
+ logo={appLogo}
54
+ description="Official DID Wallet webapp implementation that makes it possible to manage your digital identities and assets from the browser."
55
+ />
56
+ <SocialMedia items={socialMedia} style={{ marginTop: 24 }} />
57
+ </Box>
58
+ <Links links={navMenuItems} />
59
+ </Row>
60
+ <Row autoCenter style={{ marginTop: 72 }}>
61
+ {<Copyright owner={copyrightOwner || appName} />}
62
+ {<Links flowLayout links={footerLinks} style={{ color: '#999' }} />}
63
+ </Row>
46
64
  </Container>
47
65
  </Root>
48
66
  );
@@ -57,52 +75,8 @@ Footer.defaultProps = {
57
75
  };
58
76
 
59
77
  const Root = styled.div`
60
- padding: 32px 40px;
78
+ padding: 48px 0;
61
79
  border-top: 1px solid #eee;
62
- color: #9397a1;
80
+ color: ${props => props.$theme.palette.grey[600]};
63
81
  ${({ $bgcolor }) => $bgcolor && `background-color: ${$bgcolor};`}
64
- .footer_line {
65
- display: flex;
66
- justify-content: space-between;
67
- }
68
- .footer_line + .footer_line {
69
- margin-top: 32px;
70
- }
71
- .footer_brand {
72
- display: flex;
73
- align-items: center;
74
- color: #777;
75
- img {
76
- height: 36px;
77
- }
78
- span {
79
- margin-left: 8px;
80
- font-size: 20px;
81
- }
82
- }
83
- .footer_nav {
84
- .navmenu {
85
- padding-left: 0;
86
- padding-right: 0;
87
- }
88
- > * {
89
- background: transparent;
90
- }
91
- }
92
- ${props => props.$theme.breakpoints.down('md')} {
93
- .footer_line {
94
- flex-direction: column;
95
- }
96
- .footer_line + .footer_line {
97
- margin-top: 0;
98
- }
99
- .footer_brand {
100
- img {
101
- height: 24px;
102
- }
103
- span {
104
- font-size: 16px;
105
- }
106
- }
107
- }
108
82
  `;
@@ -0,0 +1,202 @@
1
+ /* eslint-disable react/no-array-index-key */
2
+ import React, { useState } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import styled from 'styled-components';
5
+ import useTheme from '@mui/styles/useTheme';
6
+ import clsx from 'clsx';
7
+
8
+ /**
9
+ * footer 中的 links (支持分组, 最多支持 2 级)
10
+ * TODO: dark/light theme
11
+ */
12
+ export default function Links({ links, flowLayout, ...rest }) {
13
+ const theme = useTheme();
14
+ const [activeIndex, setActiveIndex] = useState(-1);
15
+ if (!links?.length) {
16
+ return null;
17
+ }
18
+ // 只要发现一项元素有子元素, 就认为是分组 (大字号突出 group title)
19
+ const isGroupMode = links.some(item => item.items?.length);
20
+ const renderItem = ({ label, link, render, props }) => {
21
+ let result = label;
22
+ if (render) {
23
+ result = render({ label, link, props });
24
+ } else if (link) {
25
+ result = (
26
+ <a href={link} {...props}>
27
+ {label}
28
+ </a>
29
+ );
30
+ }
31
+ return result;
32
+ };
33
+
34
+ return (
35
+ <Root
36
+ {...rest}
37
+ className={clsx(rest.className, {
38
+ 'footer-links--grouped': isGroupMode,
39
+ 'footer-links--flow': flowLayout,
40
+ })}
41
+ $theme={theme}>
42
+ <div className="footer-links-inner">
43
+ {flowLayout &&
44
+ links.map((item, i) => (
45
+ <span key={i} className="footer-links-item">
46
+ {renderItem(item)}
47
+ </span>
48
+ ))}
49
+ {!flowLayout &&
50
+ links.map((item, i) => {
51
+ const { items } = item;
52
+ return (
53
+ <div
54
+ key={i}
55
+ className={clsx('footer-links-group', {
56
+ 'footer-links-group--active': i === activeIndex,
57
+ })}
58
+ onClick={() => setActiveIndex(activeIndex === i ? -1 : i)}>
59
+ <span className="footer-links-item">{renderItem(item)}</span>
60
+ {!!items?.length && (
61
+ <div className="footer-links-sub">
62
+ {items.map((child, j) => (
63
+ <span key={j} className="footer-links-item">
64
+ {renderItem(child)}
65
+ </span>
66
+ ))}
67
+ </div>
68
+ )}
69
+ </div>
70
+ );
71
+ })}
72
+ </div>
73
+ </Root>
74
+ );
75
+ }
76
+
77
+ Links.propTypes = {
78
+ links: PropTypes.arrayOf(
79
+ PropTypes.shape({
80
+ label: PropTypes.string,
81
+ link: PropTypes.string,
82
+ render: PropTypes.func,
83
+ props: PropTypes.object,
84
+ })
85
+ ),
86
+ // 流动布局, 简单的从左到右排列
87
+ flowLayout: PropTypes.bool,
88
+ };
89
+
90
+ Links.defaultProps = {
91
+ links: [],
92
+ flowLayout: false,
93
+ };
94
+
95
+ const Root = styled.div`
96
+ overflow: hidden;
97
+ color: ${props => props.$theme.palette.grey[600]};
98
+ .footer-links-inner {
99
+ display: flex;
100
+ justify-content: space-between;
101
+ margin: 0 -8px;
102
+ }
103
+ .footer-links-group,
104
+ .footer-links-sub {
105
+ display: flex;
106
+ flex-direction: column;
107
+ }
108
+ .footer-links-item {
109
+ display: inline-block;
110
+ padding: 0 8px;
111
+ font-size: 14px;
112
+ line-height: 1.6;
113
+ }
114
+ .footer-links-group + .footer-links-group {
115
+ margin-left: 128px;
116
+ }
117
+ &.footer-links--grouped {
118
+ .footer-links-group {
119
+ > .footer-links-item {
120
+ font-weight: bold;
121
+ }
122
+ .footer-links-sub {
123
+ margin-top: 8px;
124
+ }
125
+ }
126
+ }
127
+ a {
128
+ color: inherit;
129
+ text-decoration: none;
130
+ &:hover {
131
+ text-decoration: underline;
132
+ }
133
+ }
134
+
135
+ &.footer-links--flow {
136
+ display: inline-flex;
137
+ .footer-links-inner {
138
+ justify-content: center;
139
+ flex-wrap: wrap;
140
+ margin: 0 -8px;
141
+ .footer-links-item {
142
+ padding: 0 8px;
143
+ }
144
+ }
145
+ }
146
+
147
+ ${props => props.$theme.breakpoints.down('lg')} {
148
+ .footer-links-group + .footer-links-group {
149
+ margin-left: 56px;
150
+ }
151
+ }
152
+
153
+ ${props => props.$theme.breakpoints.down('lg')} {
154
+ .footer-links-group + .footer-links-group {
155
+ margin-left: 40px;
156
+ }
157
+ }
158
+
159
+ ${props => props.$theme.breakpoints.down('sm')} {
160
+ .footer-links-inner {
161
+ flex-direction: column;
162
+ margin: 0;
163
+ }
164
+ .footer-links-sub {
165
+ display: none;
166
+ }
167
+ .footer-links-group {
168
+ padding: 12px 0;
169
+ border-top: 1px solid ${props => props.$theme.palette.grey[200]};
170
+ }
171
+ .footer-links-group + .footer-links-group {
172
+ margin-left: 0;
173
+ }
174
+ .footer-links-group--active {
175
+ .footer-links-sub {
176
+ display: flex;
177
+ flex-direction: row;
178
+ flex-wrap: wrap;
179
+ .footer-links-item {
180
+ flex: 0 0 50%;
181
+ }
182
+ }
183
+ }
184
+ .footer-links-item {
185
+ padding: 0;
186
+ font-size: 13px;
187
+ }
188
+ &.footer-links--grouped {
189
+ .footer-links-group {
190
+ > .footer-links-item {
191
+ font-size: 14px;
192
+ }
193
+ }
194
+ }
195
+
196
+ &.footer-links--flow {
197
+ .footer-links-inner {
198
+ flex-direction: row;
199
+ }
200
+ }
201
+ }
202
+ `;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import useTheme from '@mui/styles/useTheme';
5
+ import clsx from 'clsx';
6
+
7
+ export default function Row({ children, autoCenter, ...rest }) {
8
+ const theme = useTheme();
9
+ if (!children) {
10
+ return null;
11
+ }
12
+ return (
13
+ <RowRoot
14
+ {...rest}
15
+ className={clsx(rest.className, { 'footer-row-auto-center': autoCenter })}
16
+ $theme={theme}>
17
+ {children}
18
+ </RowRoot>
19
+ );
20
+ }
21
+
22
+ Row.propTypes = {
23
+ children: PropTypes.any,
24
+ autoCenter: PropTypes.bool,
25
+ };
26
+
27
+ Row.defaultProps = {
28
+ children: null,
29
+ autoCenter: false,
30
+ };
31
+
32
+ const RowRoot = styled.div`
33
+ display: flex;
34
+ justify-content: space-between;
35
+ & + & {
36
+ margin-top: 24px;
37
+ }
38
+ &.footer-row-auto-center > *:only-child {
39
+ margin: 0 auto;
40
+ }
41
+
42
+ ${props => props.$theme.breakpoints.down('md')} {
43
+ align-items: stretch;
44
+ flex-direction: column;
45
+ gap: 16px;
46
+ > * {
47
+ flex: 1 0 100%;
48
+ }
49
+ &.footer-row-auto-center > * {
50
+ margin: 0 auto;
51
+ }
52
+ }
53
+ `;
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import useTheme from '@mui/styles/useTheme';
5
+ import Icon from '../Icon';
6
+
7
+ export default function SocialMedia({ items, ...rest }) {
8
+ const theme = useTheme();
9
+ if (!items?.length) {
10
+ return null;
11
+ }
12
+ return (
13
+ <Root {...rest}>
14
+ {items.map((item, i) => {
15
+ return (
16
+ // eslint-disable-next-line react/no-array-index-key
17
+ <a key={i} href={item.link} target="_blank" rel="noreferrer">
18
+ <Icon
19
+ icon={item.icon}
20
+ sx={{ bgcolor: theme.palette.grey[600], color: '#fff' }}
21
+ size={44}
22
+ variant="rounded"
23
+ component="span"
24
+ />
25
+ </a>
26
+ );
27
+ })}
28
+ </Root>
29
+ );
30
+ }
31
+
32
+ SocialMedia.propTypes = {
33
+ items: PropTypes.arrayOf(
34
+ PropTypes.shape({
35
+ // icon 对应 Icon#icon prop, 支持 iconify name 和 url 2 种形式:
36
+ icon: PropTypes.string,
37
+ link: PropTypes.string,
38
+ })
39
+ ),
40
+ };
41
+
42
+ SocialMedia.defaultProps = {
43
+ items: null,
44
+ };
45
+
46
+ const Root = styled.div`
47
+ display: inline-flex;
48
+ align-items: center;
49
+ a + a {
50
+ margin-left: 12px;
51
+ }
52
+ `;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Avatar from '@mui/material/Avatar';
4
+ import '@iconify/iconify';
5
+ import { isUrl } from '../utils';
6
+
7
+ /**
8
+ * Icon 组件, 基于 mui Avatar 组件扩展对 iconify 的支持
9
+ */
10
+ export default function Icon({ icon, size, ...rest }) {
11
+ const sx = { ...rest.sx };
12
+ if (size) {
13
+ sx.width = size;
14
+ sx.height = size;
15
+ }
16
+ if (isUrl(icon)) {
17
+ return <Avatar {...rest} src={icon} sx={sx} />;
18
+ }
19
+ if (icon) {
20
+ // y = 0.6 * x + 4
21
+ const iconHeight = size ? 0.6 * size + 4 : 0;
22
+ return (
23
+ <Avatar {...rest} sx={sx}>
24
+ <span
25
+ className="iconify"
26
+ data-icon={icon}
27
+ {...(iconHeight && { 'data-height': iconHeight })}
28
+ />
29
+ </Avatar>
30
+ );
31
+ }
32
+ return null;
33
+ }
34
+
35
+ Icon.propTypes = {
36
+ // icon 支持 2 种形式:
37
+ // 1. iconify icon name: <prefix>:<name>
38
+ // 2. url
39
+ icon: PropTypes.string.isRequired,
40
+ size: PropTypes.number,
41
+ };
42
+
43
+ Icon.defaultProps = {
44
+ size: null,
45
+ };
package/src/utils.js ADDED
@@ -0,0 +1,16 @@
1
+ export const mapRecursive = (array, fn, childrenKey = 'children') => {
2
+ return array.map(item => {
3
+ if (Array.isArray(item[childrenKey])) {
4
+ return fn({
5
+ ...item,
6
+ [childrenKey]: mapRecursive(item[childrenKey], fn, childrenKey),
7
+ });
8
+ }
9
+ return fn(item);
10
+ });
11
+ };
12
+
13
+ // "http://", "https://", "//" 开头的 3 种情况
14
+ export const isUrl = str => {
15
+ return /^(https?:)?\/\//.test(str);
16
+ };