UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

314 lines (300 loc) 8.29 kB
/* 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; } } } `;