diff --git a/package-lock.json b/package-lock.json index f9f0d7e8b..0666271a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@helpscout/blue", - "version": "0.0.17", + "version": "0.0.19", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/components/Card/Block.js b/src/components/Card/Block.js index 6bdf67527..3d644621c 100644 --- a/src/components/Card/Block.js +++ b/src/components/Card/Block.js @@ -1,27 +1,57 @@ import React from 'react' import PropTypes from 'prop-types' import classNames from '../../utilities/classNames' +import Scrollable from '../Scrollable' import { standardSizeTypes } from '../../constants/propTypes' export const propTypes = { + bgMuted: PropTypes.bool, className: PropTypes.string, + scrollable: PropTypes.bool, + flex: PropTypes.bool, size: standardSizeTypes } const Block = props => { - const { className, children, size, ...rest } = props + const { + bgMuted, + className, + children, + scrollable, + flex, + size, + ...rest + } = props const componentClassName = classNames( 'c-card__block', + bgMuted && 'is-bg-muted', + flex && 'is-flex', + scrollable && 'is-scrollable', size && `c-card__block--${size}`, className ) - return ( + const scrollableClassName = classNames( + 'c-card__block', + 'c-card__block--scrollable', + flex && 'is-flex', + scrollable && 'is-scrollable' + ) + + const blockMarkup = scrollable ? ( + +
+ {children} +
+
+ ) : (
{children}
) + + return blockMarkup } Block.propTypes = propTypes diff --git a/src/components/Card/README.md b/src/components/Card/README.md index 5940e0188..1a99b2e7d 100644 --- a/src/components/Card/README.md +++ b/src/components/Card/README.md @@ -15,10 +15,12 @@ A Card component is used to encapsulate pieces of UI that share a common concept | Prop | Type | Description | | --- | --- | --- | +| borderless | boolean | Removes the border from the component. | | onBlur | function | Callback when the component is blurred. | | onClick | boolean or function | Callback when the component is clicked. | | onFocus | function | Callback when the component is focused. | | className | string | Custom class names to be added to the component. | +| flex | boolean | Adds flexbox styles to the component. | | hover | boolean | Adds a hover style to the component. | | href | string | Adds an `href` to the component. Transforms it into an `` tag. | | seamless | boolean | Removes the padding within the component. | @@ -51,5 +53,8 @@ Note: It is highly recommended the `seamless` prop is used for the container ` { const { + borderless, className, children, + flex, hover, href, onClick, @@ -41,6 +45,8 @@ const Card = props => { 'c-card', (onClick || href) && 'is-clickable', (onClick || hover || href) && 'is-hoverable', + borderless && 'is-borderless', + flex && 'is-flex', seamless && 'is-seamless', className ) diff --git a/src/components/Card/tests/Card.Block.test.js b/src/components/Card/tests/Card.Block.test.js index c4f1bf3e8..2efb213c5 100644 --- a/src/components/Card/tests/Card.Block.test.js +++ b/src/components/Card/tests/Card.Block.test.js @@ -1,6 +1,7 @@ import React from 'react' import { mount, shallow } from 'enzyme' import CardBlock from '../Block' +import Scrollable from '../../Scrollable' describe('ClassName', () => { test('Has default className', () => { @@ -52,6 +53,37 @@ describe('Click', () => { }) }) +describe('Scrollable', () => { + test('Does not render Scrollable by default', () => { + const wrapper = shallow() + const o = wrapper.find(Scrollable) + + expect(o.length).toBe(0) + }) + + test('Renders Scrollable if specified', () => { + const wrapper = mount() + const o = wrapper.find(Scrollable) + const n = wrapper.find('.c-card__block') + + expect(o.length).toBe(1) + expect(o.hasClass('c-card__block')).toBeTruthy() + expect(o.hasClass('is-scrollable')).toBeTruthy() + expect(n.length).toBe(2) + + wrapper.unmount() + }) + + test('Renders Scrollable with flex if specified', () => { + const wrapper = shallow() + const o = wrapper.find(Scrollable) + + expect(o.length).toBe(1) + expect(o.hasClass('is-scrollable')).toBeTruthy() + expect(o.hasClass('is-flex')).toBeTruthy() + }) +}) + describe('Styles', () => { test('Does not have a size modifier style by default', () => { const wrapper = shallow() @@ -67,4 +99,16 @@ describe('Styles', () => { expect(wrapper.prop('className')).toContain('c-card__block--sm') }) + + test('Renders bgMuted styles, if specified', () => { + const wrapper = shallow() + + expect(wrapper.hasClass('is-bg-muted')).toBeTruthy() + }) + + test('Renders flex styles, if specified', () => { + const wrapper = shallow() + + expect(wrapper.hasClass('is-flex')).toBeTruthy() + }) }) diff --git a/src/components/Card/tests/Card.test.js b/src/components/Card/tests/Card.test.js index b5177003b..ed3ca747e 100644 --- a/src/components/Card/tests/Card.test.js +++ b/src/components/Card/tests/Card.test.js @@ -92,9 +92,21 @@ describe('Selector', () => { }) describe('Styles', () => { + test('Renders borderless styles, if specified', () => { + const wrapper = shallow() + + expect(wrapper.hasClass('is-borderless')).toBeTruthy() + }) + + test('Renders flex styles, if specified', () => { + const wrapper = shallow() + + expect(wrapper.hasClass('is-flex')).toBeTruthy() + }) + test('Renders seamless styles, if specified', () => { const wrapper = shallow() - expect(wrapper.prop('className')).toContain('is-seamless') + expect(wrapper.hasClass('is-seamless')).toBeTruthy() }) }) diff --git a/src/components/index.js b/src/components/index.js index 5d0072858..d1445b8b6 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -7,18 +7,21 @@ export { default as Card } from './Card' export { default as Checkbox } from './Checkbox' export { default as Choice } from './Choice' export { default as ChoiceGroup } from './ChoiceGroup' +export { default as CloseButton } from './CloseButton' +export { default as EventListener } from './EventListener' +export { default as Flexy } from './Flexy' export { default as FormGroup } from './FormGroup' export { default as Grid } from './Grid' export { default as Heading } from './Heading' export { default as HelpText } from './HelpText' -export { default as Flexy } from './Flexy' export { default as Icon } from './Icon' export { default as Image } from './Image' -export { default as Modal } from './Modal' export { default as Input } from './Input' +export { default as KeypressListener } from './KeypressListener' export { default as Label } from './Label' export { default as Link } from './Link' export { default as LoadingDots } from './LoadingDots' +export { default as Modal } from './Modal' export { default as Overlay } from './Overlay' export { default as Portal } from './Portal' export { default as PortalWrapper } from './PortalWrapper' diff --git a/src/styles/components/Card.scss b/src/styles/components/Card/Card.scss similarity index 74% rename from src/styles/components/Card.scss rename to src/styles/components/Card/Card.scss index 13645177d..c75aefaa5 100644 --- a/src/styles/components/Card.scss +++ b/src/styles/components/Card/Card.scss @@ -1,11 +1,10 @@ -@import "../configs/_color"; -$seed-card-namespace: "c-card"; +@import "../../configs/_color"; $seed-card-border: 1px solid rgba(_color(border, ui, dark), 0.7); // Import Seed Pack @import "pack/seed-card/_index"; .c-card { - @import "../resets/base"; + @import "../../resets/base"; box-shadow: #{ 0 0 0 0 rgba(black, 0) }; @@ -15,10 +14,21 @@ $seed-card-border: 1px solid rgba(_color(border, ui, dark), 0.7); text-decoration: none; // Modifiers + &.is-borderless { + border: none; + } + &.is-clickable { cursor: pointer; } + &.is-flex { + display: flex; + flex-direction: column; + min-height: 0; + width: 100%; + } + &.is-hoverable { box-shadow: #{ 0 1px 3px 0 rgba(black, 0.1), @@ -35,5 +45,5 @@ $seed-card-border: 1px solid rgba(_color(border, ui, dark), 0.7); &.is-seamless { padding: 0; - } + } } diff --git a/src/styles/components/Card/CardBlock.scss b/src/styles/components/Card/CardBlock.scss new file mode 100644 index 000000000..d5cf7a1ee --- /dev/null +++ b/src/styles/components/Card/CardBlock.scss @@ -0,0 +1,20 @@ +@import "../../configs/color"; + +.c-card__block { + @import "../../resets/base"; + + // Modifiers + &--scrollable { + padding: 0; + min-height: 0; + max-height: 100%; + } + + &.is-bg-muted { + background-color: _color(grey, 200); + } + + &.is-flex { + flex: 1; + } +} diff --git a/src/styles/components/Card/__index.scss b/src/styles/components/Card/__index.scss new file mode 100644 index 000000000..db784df4b --- /dev/null +++ b/src/styles/components/Card/__index.scss @@ -0,0 +1,2 @@ +@import "./Card"; +@import "./CardBlock"; diff --git a/src/styles/components/__index.scss b/src/styles/components/__index.scss index ab197096b..92bc59cfc 100644 --- a/src/styles/components/__index.scss +++ b/src/styles/components/__index.scss @@ -3,7 +3,7 @@ @import "./AvatarStack"; @import "./Badge"; @import "./Button"; -@import "./Card"; +@import "./Card/_index"; @import "./Checkbox"; @import "./Choice/_index"; @import "./CloseButton"; diff --git a/src/styles/components/__slim.scss b/src/styles/components/__slim.scss index de69127bc..fb9caf6cb 100644 --- a/src/styles/components/__slim.scss +++ b/src/styles/components/__slim.scss @@ -5,7 +5,7 @@ @import "./AvatarStack"; @import "./Badge"; // @import "./Button"; -@import "./Card"; +@import "./Card/_index"; @import "./Checkbox"; @import "./Choice/_index"; @import "./CloseButton"; diff --git a/src/tests/index.test.js b/src/tests/index.test.js index c4d1d8173..1c5ac996d 100644 --- a/src/tests/index.test.js +++ b/src/tests/index.test.js @@ -8,6 +8,8 @@ import { Checkbox, Choice, ChoiceGroup, + CloseButton, + EventListener, Flexy, FormGroup, Grid, @@ -15,6 +17,7 @@ import { Icon, Image, Input, + KeypressListener, Label, Link, LoadingDots, @@ -40,6 +43,8 @@ const components = [ Checkbox, Choice, ChoiceGroup, + CloseButton, + EventListener, Flexy, FormGroup, Grid, @@ -47,6 +52,7 @@ const components = [ Icon, Image, Input, + KeypressListener, Label, Link, LoadingDots, diff --git a/stories/Card.js b/stories/Card.js index 0aff6bfb1..658884019 100644 --- a/stories/Card.js +++ b/stories/Card.js @@ -1,6 +1,6 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import { Card } from '../src/index.js' +import { Card, Heading } from '../src/index.js' storiesOf('Card', module) .add('default', () => Hello) @@ -12,3 +12,33 @@ storiesOf('Card', module) Block Three )) + .add('scrollable', () => ( + + + Elf: Synopsis + + +

+ On Christmas Eve in 1973, an infant boy stows away in Santa Claus' sack. When discovered back at the North Pole, he is adopted by Papa Elf. Papa Elf names his son Buddy.

+ +

+ Buddy grows up at the North Pole believing he is an elf, but due to his human size he is unable to perform elf tasks. When Buddy accidentally learns that he is human, Papa Elf explains that he was born to Walter Hobbs and Susan Wells, and was given up for adoption without Walter knowing. Susan died and Walter works at a children's book publisher in New York City at the Empire State Building. Santa notes that Walter is on the naughty list due to his greed and selfishness, but suggests Buddy could help redeem him, and so Buddy travels alone to New York.

+ +

+ Buddy has trouble acclimating to the customs of the human world. Buddy finds his father's office, but Walter has him ejected after Buddy mentions Susan Wells. After following a security guard's sarcastic suggestion to go "back to Gimbels" due to his elf outfit, the Gimbels' manager mistakes him for an employee at Santa Land. He meets Jovie, an unenthused employee to whom he is attracted. Knowing that Santa will arrive the next day, Buddy stays behind and spends the night decorating Santa Land, and buys a nightie for Walter.

+ +

+ The next day, Buddy is appalled that the store's Santa is not real and rips off the man's fake beard, causing them to fight, with the manager having to subdue the fake Santa. Walter bails Buddy out of prison and takes him to Dr. Leonardo for a DNA test, which confirms that Buddy is Walter's son. The doctor convinces him to take Buddy home to meet his step-mother Emily and 11-year-old half-brother Michael. Walter and Michael are annoyed by Buddy's childlike behavior, but Emily insists that they take care of him until he "recovers".

+ +

+ Buddy wins Michael over by helping him defeat a gang of bullies in a snowball fight and Michael encourages Buddy to ask Jovie out. Walter learns from his boss Fulton Greenway that his company is in financial trouble after publishing a failed children's book, and organizes a book pitch for Christmas Eve, for which Walter and his associates Eugene and Morris arrange a meeting with best-selling children's author Miles Finch to hire him.

+ +

+ One night, Buddy goes on a date with Jovie and wins her over. On Christmas Eve, Buddy bursts into Walter's office during a meeting with Finch to tell Walter about his love, and mistakes Finch, who has dwarfism for an elf. Finch loses his temper and attacks Buddy before storming out, causing Walter to harshly disown Buddy.

+ +

+ Eugene and Morris find a notebook Finch left that is filled with ideas for children's books. Walter pitches these ideas to Greenway, but Michael bursts in to tell Walter that Buddy ran away. Greenway refuses to reschedule; Walter quits his job and leaves to find Buddy.

+
+ Block Three +
+ ))