@blocklet/ui-react
Version:
Some useful front-end web components that can be used in Blocklets.
314 lines (300 loc) • 8.29 kB
JSX
/* eslint-disable react/no-array-index-key */
import { useState } from 'react';
import PropTypes from 'prop-types';
import { useCreation } from 'ahooks';
import isInteger from 'lodash/isInteger';
import isString from 'lodash/isString';
import { styled } from '@arcblock/ux/lib/Theme';
import clsx from 'clsx';
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
import Icon from '../Icon';
import useMobile from '../hooks/use-mobile';
import { splitNavColumns } from '../utils';
/**
* footer 中的 links (支持分组, 最多支持 2 级)
*/
export default function Links({ links = [], flowLayout = false, columns, ...rest }) {
const [activeIndex, setActiveIndex] = useState(-1);
const isMobile = useMobile({ key: 'md' });
// 只要发现一项元素有子元素, 就认为是分组 (大字号突出 group title)
const isGroupMode = links.some((item) => item.items?.length);
// 是否启用 columns 布局
const columnsLayout = !isMobile && isGroupMode && isInteger(columns) && columns > 1;
const renderItem = ({ label, link, icon, render, props }) => {
let result = label;
if (render) {
result = render({ label, link, props });
} else if (link && isString(link)) {
const isExternal = link.startsWith('http') || link.startsWith('//');
result = (
<a
href={link}
aria-label={`Footer link for ${label}`}
target={isExternal ? '_blank' : '_self'}
rel={isExternal ? 'noreferrer noopener' : undefined}
{...props}>
{label}
</a>
);
}
return (
<>
{icon && <Icon icon={icon} size={20} sx={{ mr: 0.5 }} />}
{result}
</>
);
};
const content = useCreation(() => {
if (!links?.length) {
return null;
}
// 流布局
if (flowLayout) {
return links.map((item, i) => (
<span key={i} className="footer-links-item">
{renderItem(item)}
</span>
));
}
// 列布局
if (columnsLayout) {
return splitNavColumns(links, { columns }).map((cols, i) => {
return (
<div key={i} className="footer-links-column">
{cols
.filter((v) => v.group)
.map((item, j) => {
const { items } = item;
return (
<div key={j} className="footer-links-group">
<span className="footer-links-item">{renderItem(item)}</span>
{!!items?.length && (
<div className="footer-links-sub">
{items.map((child, k) => (
<span
key={k}
className={clsx('footer-links-item', { 'footer-links-item--new': child.isNew })}>
{renderItem(child)}
</span>
))}
</div>
)}
</div>
);
})}
</div>
);
});
}
// 纯 flex 布局
return links.map((item, i) => {
const { items } = item;
// 用于移动端展开
const isActive = i === activeIndex;
return (
<div
key={i}
className={clsx('footer-links-group', {
'footer-links-group--active': isActive,
})}
onClick={() => setActiveIndex(activeIndex === i ? -1 : i)}>
<span className="footer-links-item">
{renderItem(item)}
{!!items?.length && (
<span className="footer-links-group-expand-icon">
<ExpandMoreIcon
style={{
transform: `rotate(${isActive ? 180 : 0}deg)`,
}}
/>
</span>
)}
</span>
{!!items?.length && (
<div className="footer-links-sub">
{items.map((child, j) => (
<span key={j} className={clsx('footer-links-item', { 'footer-links-item--new': child.isNew })}>
{renderItem(child)}
</span>
))}
</div>
)}
</div>
);
});
}, [links, flowLayout, columnsLayout, activeIndex]);
if (!links?.length) {
return null;
}
return (
<Root
{...rest}
className={clsx(rest.className, {
'footer-links--grouped': isGroupMode,
'footer-links--flow': flowLayout,
'footer-links--columns': columnsLayout,
})}>
<div className="footer-links-inner">{content}</div>
</Root>
);
}
Links.propTypes = {
links: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
link: PropTypes.string,
render: PropTypes.func,
props: PropTypes.object,
})
),
// 流动布局, 简单的从左到右排列
flowLayout: PropTypes.bool,
// 列布局
columns: PropTypes.number.isRequired,
};
const Root = styled('div')`
overflow: hidden;
color: ${({ theme }) => theme.palette.text.secondary};
.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-group-expand-icon {
display: none;
position: absolute;
right: 16px;
top: 50%;
transform: translate(0, -50%);
line-height: 1;
svg {
width: auto;
height: 0.75em;
}
}
.footer-links-item {
display: inline-flex;
align-items: center;
position: relative;
padding: 6px 8px;
font-size: 14px;
&--new::after {
content: 'New';
color: ${({ theme }) => theme.palette.info.main};
background-color: ${({ theme }) => theme.palette.info.light};
padding: 1px 8px;
border-radius: 10px/50%;
margin-left: 8px;
}
}
&.footer-links--grouped {
.footer-links-group {
> .footer-links-item {
font-weight: 600;
color: ${({ theme }) => theme.palette.text.primary};
}
.footer-links-sub {
margin-top: 8px;
}
}
}
a {
display: inline-block;
max-width: 150px;
color: inherit;
text-decoration: none;
transition: color 0.2s ease-in-out;
&:hover {
color: ${({ theme }) => theme.palette.text.primary};
}
}
/* columns 布局 */
&.footer-links--columns {
.footer-links-inner {
gap: 96px;
}
.footer-links-column {
display: flex;
flex-direction: column;
}
.footer-links-group {
.footer-links-sub {
margin-top: 2px;
margin-bottom: 12px;
}
}
}
/* flow 布局 */
&.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-item + .footer-links-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translate(0, -50%);
height: 1em;
border-left: 1px solid ${(props) => props.theme.palette.text.secondary};
}
}
}
/* 移动端样式 */
${(props) => props.theme.breakpoints.down('md')} {
.footer-links-inner {
flex-direction: column;
margin: 0;
}
.footer-links-sub {
display: none;
}
.footer-links-group {
position: relative;
padding: 12px 0;
.footer-links-item .footer-links-group-expand-icon {
display: inline-block;
}
}
.footer-links-group + .footer-links-group {
border-top: 1px solid ${(props) => props.theme.palette.grey[200]};
}
.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-left: 0;
padding-right: 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;
}
}
}
`;