Merge pull request #121 from unix/card

feat(card): add footer component
This commit is contained in:
witt
2020-04-22 09:18:00 +08:00
committed by GitHub
8 changed files with 1851 additions and 497 deletions

View 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

View 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()
})
})

View 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)

View File

@@ -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>

View File

@@ -1,3 +1,7 @@
import Card from './card'
import CardFooter from './card-footer'
Card.Footer = CardFooter
Card.Actions = CardFooter
export default Card

View File

@@ -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>

View File

@@ -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>