mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-02 09:08:52 +08:00
Merge pull request #121 from unix/card
feat(card): add footer component
This commit is contained in:
89
components/card/__tests__/__snapshots__/footer.test.tsx.snap
Normal file
89
components/card/__tests__/__snapshots__/footer.test.tsx.snap
Normal file
@@ -0,0 +1,89 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Card Footer should render correctly 1`] = `
|
||||
"<div class=\\"card \\"><div class=\\"content\\"><p>card</p></div><footer class=\\"auto-margin \\">footer<style>
|
||||
footer {
|
||||
padding: 8pt 16pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
font-size: .875rem;
|
||||
border-top: 1px solid #eaeaea;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
min-height: calc(2.5 * 16pt);
|
||||
}
|
||||
|
||||
.auto-margin :global(*) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
</style></footer><style>
|
||||
.card {
|
||||
background: #fff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
transition: all .2s ease;
|
||||
border-radius: 5px;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
padding: 16pt 16pt;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card :global(*:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.card :global(*:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card :global(img) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card :global(.image) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
</style></div>"
|
||||
`;
|
||||
|
||||
exports[`Card Footer should work with disable-auto-margin 1`] = `
|
||||
"<footer class=\\" \\">footer<style>
|
||||
footer {
|
||||
padding: 8pt 16pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
font-size: .875rem;
|
||||
border-top: 1px solid #eaeaea;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
min-height: calc(2.5 * 16pt);
|
||||
}
|
||||
|
||||
.auto-margin :global(*) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
</style></footer>"
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
27
components/card/__tests__/footer.test.tsx
Normal file
27
components/card/__tests__/footer.test.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Card } from 'components'
|
||||
|
||||
describe('Card Footer', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = mount(
|
||||
<Card>
|
||||
<p>card</p>
|
||||
<Card.Footer>footer</Card.Footer>
|
||||
</Card>
|
||||
)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should work properly when use alone', () => {
|
||||
const wrapper = mount(<Card.Footer>footer</Card.Footer>)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should work with disable-auto-margin', () => {
|
||||
const wrapper = mount(<Card.Footer disableAutoMargin>footer</Card.Footer>)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
})
|
||||
52
components/card/card-footer.tsx
Normal file
52
components/card/card-footer.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
disableAutoMargin?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disableAutoMargin: false,
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type CardFooterProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const CardFooter: React.FC<React.PropsWithChildren<CardFooterProps>> = ({
|
||||
children, className, disableAutoMargin, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<footer className={`${disableAutoMargin ? '' : 'auto-margin'} ${className}`}
|
||||
{...props}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
footer {
|
||||
padding: ${theme.layout.gapHalf} ${theme.layout.gap};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
font-size: .875rem;
|
||||
border-top: 1px solid ${theme.palette.border};
|
||||
border-bottom-left-radius: ${theme.layout.radius};
|
||||
border-bottom-right-radius: ${theme.layout.radius};
|
||||
min-height: calc(2.5 * ${theme.layout.gap});
|
||||
}
|
||||
|
||||
.auto-margin :global(*) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: ${theme.layout.gapQuarter};
|
||||
}
|
||||
`}</style>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(CardFooter, defaultProps)
|
||||
@@ -1,13 +1,16 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { CardTypes } from '../utils/prop-types'
|
||||
import { getStyles } from './styles'
|
||||
import CardFooter from './card-footer'
|
||||
import Image from '../image'
|
||||
import { pickChild } from '../utils/collections'
|
||||
|
||||
interface Props {
|
||||
hoverable?: boolean
|
||||
shadow?: boolean
|
||||
className?: string
|
||||
width?: string
|
||||
type?: CardTypes
|
||||
}
|
||||
|
||||
@@ -15,6 +18,7 @@ const defaultProps = {
|
||||
type: 'default' as CardTypes,
|
||||
hoverable: false,
|
||||
shadow: false,
|
||||
width: '100%',
|
||||
className: '',
|
||||
}
|
||||
|
||||
@@ -22,7 +26,7 @@ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type CardProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const Card: React.FC<React.PropsWithChildren<CardProps>> = React.memo(({
|
||||
children, hoverable, className, shadow, type, ...props
|
||||
children, hoverable, className, shadow, type, width, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const hoverShadow = useMemo(() => {
|
||||
@@ -34,16 +38,25 @@ const Card: React.FC<React.PropsWithChildren<CardProps>> = React.memo(({
|
||||
[type, theme.palette, shadow],
|
||||
)
|
||||
|
||||
const [withoutFooterChildren, footerChildren] = pickChild(children, CardFooter)
|
||||
const [withoutImageChildren, imageChildren] = pickChild(withoutFooterChildren, Image)
|
||||
|
||||
|
||||
return (
|
||||
<div className={`card ${className}`} {...props}>
|
||||
{children}
|
||||
{imageChildren}
|
||||
<div className="content">
|
||||
{withoutImageChildren}
|
||||
</div>
|
||||
{footerChildren}
|
||||
|
||||
<style jsx>{`
|
||||
.card {
|
||||
background: ${theme.palette.background};
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
width: ${width};
|
||||
transition: all .2s ease;
|
||||
padding: ${theme.layout.gap} ${theme.layout.gap};
|
||||
border-radius: ${theme.layout.radius};
|
||||
box-shadow: ${shadow ? theme.expressiveness.shadowSmall : 'none'};
|
||||
box-sizing: border-box;
|
||||
@@ -52,6 +65,11 @@ const Card: React.FC<React.PropsWithChildren<CardProps>> = React.memo(({
|
||||
border: 1px solid ${borderColor};
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
padding: ${theme.layout.gap} ${theme.layout.gap};
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: ${hoverShadow};
|
||||
}
|
||||
@@ -63,9 +81,27 @@ const Card: React.FC<React.PropsWithChildren<CardProps>> = React.memo(({
|
||||
.card :global(*:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card :global(img) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card :global(.image) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default withDefaults(Card, defaultProps)
|
||||
type CardComponent<P = {}> = React.FC<P> & {
|
||||
Footer: typeof CardFooter
|
||||
Actions: typeof CardFooter
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps> & NativeAttrs
|
||||
|
||||
(Card as CardComponent<ComponentProps>).defaultProps = defaultProps
|
||||
|
||||
export default Card as CardComponent<ComponentProps>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import Card from './card'
|
||||
import CardFooter from './card-footer'
|
||||
|
||||
Card.Footer = CardFooter
|
||||
Card.Actions = CardFooter
|
||||
|
||||
export default Card
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Card, Spacer, Row, Col } from 'components'
|
||||
import { Card, Spacer, Row, Col, Link, Image, Text } from 'components'
|
||||
|
||||
export const meta = {
|
||||
title: 'card',
|
||||
@@ -10,7 +10,6 @@ export const meta = {
|
||||
|
||||
A common container component.
|
||||
|
||||
|
||||
<Playground
|
||||
title="Basic"
|
||||
scope={{ Card }}
|
||||
@@ -70,6 +69,43 @@ A common container component.
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="With Footer"
|
||||
scope={{ Card, Link, Spacer, Row }}
|
||||
code={`
|
||||
<Row style={{ flexWrap: 'wrap' }} justify="space-around">
|
||||
<Card width="330px">
|
||||
<h4>ZEIT UI React</h4>
|
||||
<p>Modern and minimalist React UI library.</p>
|
||||
<Card.Footer>
|
||||
<Link pure color target="_blank" href="https://github.com/zeit-ui/react">Visit source code on GitHub.</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
<Card width="330px" type="dark">
|
||||
<h4>ZEIT UI React</h4>
|
||||
<p>Modern and minimalist React UI library.</p>
|
||||
<Card.Footer>
|
||||
<Link pure target="_blank" href="https://github.com/zeit-ui/react">Visit source code on GitHub.</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
</Row>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="With Image"
|
||||
scope={{ Card, Link, Spacer, Text, Image }}
|
||||
code={`
|
||||
<Card width="400px">
|
||||
<Image src="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png"
|
||||
height="200" width="400" style={{ objectFit: 'cover' }} />
|
||||
<Text h4 style={{ marginBottom: '0' }}>ZEIT UI React</Text>
|
||||
<Text type="secondary" small>Modern and minimalist React UI library.</Text>
|
||||
<Card.Footer>
|
||||
<Link pure block target="_blank" href="https://github.com/zeit-ui/react">Visit source code on GitHub.</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/card.mdx">
|
||||
<Attributes.Title>Card.Props</Attributes.Title>
|
||||
|
||||
@@ -78,7 +114,15 @@ A common container component.
|
||||
| **hoverable** | add effect on hover | `boolean` | - | `false` |
|
||||
| **shadow** | show shadow | `boolean` | - | `false` |
|
||||
| **type** | card type | [CardType](#cardtype) | - | `default` |
|
||||
| ... | native props | `HTMLAttributes` | `'className', ...` | - |
|
||||
| **width** | CSS width value | `string` | - | `100%` |
|
||||
| ... | native props | `HTMLAttributes` | `'id', className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="Card.Actions">Card.Footer.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **disableAutoMargin** | cancel automatic margin value | `boolean` | - | `false` |
|
||||
| ... | native props | `HTMLAttributes` | `'id', className', ...` | - |
|
||||
|
||||
<Attributes.Title>CardType</Attributes.Title>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Card, Row, Col } from 'components'
|
||||
import { Card, Row, Col, Link, Spacer, Text, Image } from 'components'
|
||||
|
||||
export const meta = {
|
||||
title: '卡片 Card',
|
||||
@@ -72,6 +72,44 @@ export const meta = {
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="卡片页脚"
|
||||
scope={{ Card, Link, Spacer, Row }}
|
||||
code={`
|
||||
<Row style={{ flexWrap: 'wrap' }} justify="space-around">
|
||||
<Card width="330px">
|
||||
<h4>ZEIT UI React</h4>
|
||||
<p>现代化、极简风格的 UI 库。</p>
|
||||
<Card.Footer>
|
||||
<Link pure color target="_blank" href="https://github.com/zeit-ui/react">在 GitHub 上查看源码</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
<Card width="330px" type="dark">
|
||||
<h4>ZEIT UI React</h4>
|
||||
<p>现代化、极简风格的 UI 库。</p>
|
||||
<Card.Footer>
|
||||
<Link pure target="_blank" href="https://github.com/zeit-ui/react">在 GitHub 上查看源码</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
</Row>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="组合图片"
|
||||
scope={{ Card, Link, Spacer, Text, Image }}
|
||||
code={`
|
||||
<Card width="400px">
|
||||
<Image src="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png"
|
||||
height="200" width="400" style={{ objectFit: 'cover' }} />
|
||||
<Text h4 style={{ marginBottom: '0' }}>ZEIT UI React</Text>
|
||||
<Text type="secondary" small>现代化、极简风格的 UI 库。</Text>
|
||||
<Card.Footer>
|
||||
<Link pure block target="_blank" href="https://github.com/zeit-ui/react">在 GitHub 上查看源码</Link>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/zh-cn/components/card.mdx">
|
||||
<Attributes.Title>Card.Props</Attributes.Title>
|
||||
|
||||
@@ -80,7 +118,15 @@ export const meta = {
|
||||
| **hoverable** | 是否在悬停时增加阴影 | `boolean` | - | `false` |
|
||||
| **shadow** | 是否总是显示阴影 | `boolean` | - | `false` |
|
||||
| **type** | 卡片的类型 | [CardType](#cardtype) | - | `default` |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'className', ...` | - |
|
||||
| **width** | CSS 宽度属性 | `string` | - | `100%` |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="Card.Actions">Card.Footer.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **disableAutoMargin** | 取消自动设置的 Margin 值 | `boolean` | - | `false` |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', className', ...` | - |
|
||||
|
||||
<Attributes.Title>CardType</Attributes.Title>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user