mirror of
https://github.com/zhigang1992/react.git
synced 2026-03-26 22:42:51 +08:00
feat(breadcrumbs): add component
docs(breadcrumbs): add docs fix(breadcrumbs): fix item types error
This commit is contained in:
12
components/breadcrumbs/breadcrumbs-context.ts
Normal file
12
components/breadcrumbs/breadcrumbs-context.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface BreadcrumbsConfig {
|
||||
separator?: string
|
||||
}
|
||||
|
||||
const defaultContext = {}
|
||||
|
||||
export const BreadcrumbsContext = React.createContext<BreadcrumbsConfig>(defaultContext)
|
||||
|
||||
export const useBreadcrumbsContext = (): BreadcrumbsConfig =>
|
||||
React.useContext<BreadcrumbsConfig>(BreadcrumbsContext)
|
||||
74
components/breadcrumbs/breadcrumbs-item.tsx
Normal file
74
components/breadcrumbs/breadcrumbs-item.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import Link from '../link'
|
||||
import { Props as LinkBasicProps } from '../link/link'
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import { pickChild } from '../utils/collections'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
import { useBreadcrumbsContext } from './breadcrumbs-context'
|
||||
|
||||
interface Props {
|
||||
href?: string
|
||||
nextLink?: boolean
|
||||
onClick?: (event: React.MouseEvent) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
nextLink: false,
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.AnchorHTMLAttributes<any>, keyof Props>
|
||||
type NativeLinkAttrs = Omit<NativeAttrs, keyof LinkBasicProps>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeLinkAttrs
|
||||
|
||||
const BreadcrumbsItem = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.PropsWithChildren<BreadcrumbsProps>
|
||||
>(
|
||||
(
|
||||
{ href, nextLink, onClick, children, className, ...props },
|
||||
ref: React.Ref<HTMLAnchorElement>,
|
||||
) => {
|
||||
const { separator } = useBreadcrumbsContext()
|
||||
const isLink = useMemo(() => href !== undefined || nextLink, [href, nextLink])
|
||||
const [withoutSepChildren, sepChildren] = pickChild(children, BreadcrumbsSeparator)
|
||||
const composeSeparator = useMemo(() => {
|
||||
if (React.Children.count(sepChildren) > 0) return sepChildren
|
||||
return <BreadcrumbsSeparator>{separator}</BreadcrumbsSeparator>
|
||||
}, [separator])
|
||||
|
||||
const clickHandler = (event: React.MouseEvent) => {
|
||||
onClick && onClick(event)
|
||||
}
|
||||
|
||||
if (!isLink) {
|
||||
return (
|
||||
<>
|
||||
<span className="breadcrums-item" onClick={clickHandler}>
|
||||
{withoutSepChildren}
|
||||
</span>
|
||||
{composeSeparator}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
className={`breadcrums-item ${className}`}
|
||||
href={href}
|
||||
onClick={clickHandler}
|
||||
ref={ref}
|
||||
{...props}>
|
||||
{withoutSepChildren}
|
||||
</Link>
|
||||
{composeSeparator}
|
||||
</>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const MemoBreadcrumbsItem = React.memo(BreadcrumbsItem)
|
||||
|
||||
export default withDefaults(MemoBreadcrumbsItem, defaultProps)
|
||||
40
components/breadcrumbs/breadcrumbs-separator.tsx
Normal file
40
components/breadcrumbs/breadcrumbs-separator.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
// import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const BreadcrumbsSeparator: React.FC<React.PropsWithChildren<BreadcrumbsProps>> = ({
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
// const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className={`separator ${className}`}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.separator {
|
||||
display: inline-flex;
|
||||
margin: 0 8px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoBreadcrumbsSeparator = React.memo(BreadcrumbsSeparator)
|
||||
|
||||
export default withDefaults(MemoBreadcrumbsSeparator, defaultProps)
|
||||
88
components/breadcrumbs/breadcrumbs.tsx
Normal file
88
components/breadcrumbs/breadcrumbs.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import BreadcrumbsItem from './breadcrumbs-item'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
import { addColorAlpha } from '../utils/color'
|
||||
import { BreadcrumbsContext } from './breadcrumbs-context'
|
||||
|
||||
interface Props {
|
||||
separator?: string | ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
separator: '/',
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const Breadcrumbs: React.FC<React.PropsWithChildren<BreadcrumbsProps>> = ({
|
||||
separator,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const initialValue = useMemo(
|
||||
() => ({
|
||||
separator,
|
||||
}),
|
||||
[separator],
|
||||
)
|
||||
|
||||
return (
|
||||
<BreadcrumbsContext.Provider value={initialValue}>
|
||||
<nav className={className}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: ${theme.palette.accents_4};
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav :global(.link:hover) {
|
||||
color: ${addColorAlpha(theme.palette.link, 0.85)};
|
||||
}
|
||||
|
||||
nav > :global(span:last-of-type) {
|
||||
color: ${theme.palette.accents_6};
|
||||
}
|
||||
|
||||
nav > :global(.separator:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav :global(svg) {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
nav :global(.breadcrums-item) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}</style>
|
||||
</nav>
|
||||
</BreadcrumbsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
type MemoBreadcrumbsComponent<P = {}> = React.NamedExoticComponent<P> & {
|
||||
Item: typeof BreadcrumbsItem
|
||||
Separator: typeof BreadcrumbsSeparator
|
||||
}
|
||||
type ComponentProps = Partial<typeof defaultProps> &
|
||||
Omit<Props, keyof typeof defaultProps> &
|
||||
NativeAttrs
|
||||
|
||||
Breadcrumbs.defaultProps = defaultProps
|
||||
|
||||
export default React.memo(Breadcrumbs) as MemoBreadcrumbsComponent<ComponentProps>
|
||||
8
components/breadcrumbs/index.ts
Normal file
8
components/breadcrumbs/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Breadcrumbs from './breadcrumbs'
|
||||
import BreadcrumbsItem from './breadcrumbs-item'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
|
||||
Breadcrumbs.Item = BreadcrumbsItem
|
||||
Breadcrumbs.Separator = BreadcrumbsSeparator
|
||||
|
||||
export default Breadcrumbs
|
||||
@@ -58,3 +58,4 @@ export { default as User } from './user'
|
||||
export { default as Page } from './page'
|
||||
export { default as Grid } from './grid'
|
||||
export { default as ButtonGroup } from './button-group'
|
||||
export { default as Breadcrumbs } from './breadcrumbs'
|
||||
|
||||
@@ -4,7 +4,7 @@ import useTheme from '../styles/use-theme'
|
||||
import useWarning from '../utils/use-warning'
|
||||
import LinkIcon from './icon'
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
href?: string
|
||||
color?: boolean
|
||||
pure?: boolean
|
||||
|
||||
File diff suppressed because one or more lines are too long
104
pages/en-us/components/breadcrumbs.mdx
Normal file
104
pages/en-us/components/breadcrumbs.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Breadcrumbs, Spacer } from 'components'
|
||||
import NextLink from 'next/link'
|
||||
import Home from '@zeit-ui/react-icons/home'
|
||||
import Inbox from '@zeit-ui/react-icons/inbox'
|
||||
|
||||
export const meta = {
|
||||
title: 'breadcrumbs',
|
||||
group: 'Navigation',
|
||||
}
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
Show where users are in the application.
|
||||
|
||||
<Playground
|
||||
scope={{ Breadcrumbs }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Separator"
|
||||
desc="Custom separator in characters."
|
||||
scope={{ Breadcrumbs, Spacer }}
|
||||
code={`
|
||||
<>
|
||||
<Breadcrumbs separator="-">
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Spacer y={.5} />
|
||||
<Breadcrumbs separator=">">
|
||||
<Breadcrumbs.Item>
|
||||
Home
|
||||
<Breadcrumbs.Separator>:</Breadcrumbs.Separator>
|
||||
</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Components</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Basic</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Button</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Icons"
|
||||
desc="Show more information with icons."
|
||||
scope={{ Breadcrumbs, Home, Inbox }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item><Home /></Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href=""><Inbox /> Inbox</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="With NextJS"
|
||||
desc="Example for use with `next.js`."
|
||||
scope={{ Breadcrumbs, NextLink }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<NextLink href="/">
|
||||
<Breadcrumbs.Item nextLink>Home</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<NextLink href="/en-us/components">
|
||||
<Breadcrumbs.Item nextLink>Components</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<Breadcrumbs.Item>Breadcrumbs</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/breadcrumbs.mdx">
|
||||
<Attributes.Title>Breadcrumbs.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **separator** | separator string | `string` | - | `/` |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Item.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **href** | link address | `string` | - | - |
|
||||
| **nextLink** | in `next.js` route | `boolean` | - | `false` |
|
||||
| **onClick** | click event | `(event: MouseEvent) => void` | - | - |
|
||||
| **onClick** | click event | `(event: MouseEvent) => void` | - | - |
|
||||
| ... | native props | `AnchorHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Separator.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user