diff --git a/package-lock.json b/package-lock.json index 06453f057..33edd3906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@helpscout/hsds-react", - "version": "2.5.7", + "version": "2.5.8-page-beta-0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10550,7 +10550,7 @@ "dependencies": { "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true } @@ -12527,7 +12527,7 @@ "dependencies": { "regexpu-core": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { @@ -14013,7 +14013,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -16598,7 +16598,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -16657,7 +16657,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -20848,7 +20848,7 @@ }, "globby": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -20861,7 +20861,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -22456,7 +22456,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -24408,7 +24408,7 @@ }, "raw-loader": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", "dev": true }, @@ -25793,11 +25793,6 @@ "integrity": "sha512-M2AelyJDVR/oLnToJLtuDJRBBWUGUvvGigj1411hXhAdyFWqMaqHp7TixW3FpiLuVaikIcR1QL+zqoJoZlOgpg==", "dev": true }, - "resizey": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/resizey/-/resizey-0.0.14.tgz", - "integrity": "sha512-VR26wbJFSoeBNewfD/7eaolJ05RxxiTuRwvRygt91u8NZCh2pqgviN/R8dO9QLL8ZUMSfzR8g5bNuzbnujc/rw==" - }, "resolve": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", @@ -27492,7 +27487,7 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, @@ -27651,7 +27646,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -28041,7 +28036,7 @@ }, "temp": { "version": "0.8.3", - "resolved": "http://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", "dev": true, "requires": { @@ -28390,7 +28385,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -29127,7 +29122,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { diff --git a/package.json b/package.json index f160a905e..6c4961508 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@helpscout/hsds-react", - "version": "2.5.7", + "version": "2.5.8-page-beta-0", "private": false, "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/src/components/Page/Card.tsx b/src/components/Page/Card.tsx index 4dff4c0e1..687aaea87 100644 --- a/src/components/Page/Card.tsx +++ b/src/components/Page/Card.tsx @@ -8,22 +8,13 @@ import { COMPONENT_KEY } from './utils' export interface Props { children?: any className?: string - isResponsive: boolean } class Card extends React.PureComponent { - static defaultProps = { - isResponsive: false, - } - render() { - const { children, className, isResponsive, ...rest } = this.props + const { children, className, ...rest } = this.props - const componentClassName = classNames( - 'c-PageCard', - isResponsive && 'is-responsive', - className - ) + const componentClassName = classNames('c-PageCard', className) return ( diff --git a/src/components/Page/Header.tsx b/src/components/Page/Header.tsx index ea93b36b0..6cf730521 100644 --- a/src/components/Page/Header.tsx +++ b/src/components/Page/Header.tsx @@ -3,20 +3,44 @@ import getValidProps from '@helpscout/react-utils/dist/getValidProps' import Text from '../Text' import { classNames } from '../../utilities/classNames' import { namespaceComponent } from '../../utilities/component' -import { HeaderUI, TitleUI, SubTitleUI } from './styles/Header.css' -import Heading from './Heading' +import { HeaderUI, TitleUI, SubTitleUI, HeadingUI } from './styles/Header.css' import { COMPONENT_KEY } from './utils' export interface Props { children?: any className?: string isResponsive: boolean - title: string + render?: any + title?: string subtitle?: string withBorder: boolean withBottomMargin: boolean } +const Title = props => { + const { headingLevel, isSecondary, className, children, ...rest } = props + const componentClassName = classNames('c-PageHeading', className) + + return ( + + + {children} + + + ) +} + +const Subtitle = ({ children }) => ( + + {children} + +) + class Header extends React.PureComponent { static defaultProps = { isResponsive: false, @@ -27,9 +51,9 @@ class Header extends React.PureComponent { render() { const { - children, className, isResponsive, + render, title, subtitle, withBorder, @@ -45,23 +69,16 @@ class Header extends React.PureComponent { className ) - const titleMarkup = title && ( - - {title} - - ) - const subtitleMarkup = subtitle && ( - - - {subtitle} - - - ) - return ( - {titleMarkup} - {subtitleMarkup} + {render ? ( + render({ Title, Subtitle }) + ) : ( +
+ {title} + {subtitle && {subtitle}} +
+ )}
) } diff --git a/src/components/Page/Heading.tsx b/src/components/Page/Heading.tsx deleted file mode 100644 index 43a3f04df..000000000 --- a/src/components/Page/Heading.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from 'react' -import getValidProps from '@helpscout/react-utils/dist/getValidProps' -import { COMPONENT_KEY } from './utils' -import { classNames } from '../../utilities/classNames' -import { namespaceComponent } from '../../utilities/component' -import { HeadingUI, SecondaryHeadingUI } from './styles/Heading.css' - -export interface Props { - children?: any - className?: string - secondary?: boolean -} - -class Heading extends React.PureComponent { - static defaultProps = { - secondary: false, - } - - render() { - const { children, secondary, className, ...rest } = this.props - - const componentClassName = classNames( - 'c-PageHeader__titleHeading', - secondary && 'c-PageHeader__titleHeading--secondary', - className - ) - - return !secondary ? ( - - {children} - - ) : ( - - {children} - - ) - } -} -namespaceComponent(COMPONENT_KEY.Heading)(Heading) - -export default Heading diff --git a/src/components/Page/Page.tsx b/src/components/Page/Page.tsx index 5eadfe72a..a088e16ac 100644 --- a/src/components/Page/Page.tsx +++ b/src/components/Page/Page.tsx @@ -4,7 +4,7 @@ import Actions from './Actions' import Card from './Card' import Content from './Content' import Header from './Header' -import Heading from './Heading' +import Section from './Section' import PropProvider from '../PropProvider' import { classNames } from '../../utilities/classNames' import { namespaceComponent } from '../../utilities/component' @@ -25,19 +25,22 @@ class Page extends React.PureComponent { static Card = Card static Content = Content static Header = Header - static Heading = Heading + static Section = Section getPropProviderProps = () => { const { isResponsive } = this.props return { Accordion: { isPage: true, isSeamless: true }, - [COMPONENT_KEY.Card]: { + [COMPONENT_KEY.Section]: { isResponsive, }, [COMPONENT_KEY.Header]: { isResponsive, }, + [COMPONENT_KEY.Content]: { + isResponsive, + }, } } diff --git a/src/components/Page/Section.tsx b/src/components/Page/Section.tsx new file mode 100644 index 000000000..03860df77 --- /dev/null +++ b/src/components/Page/Section.tsx @@ -0,0 +1,37 @@ +import * as React from 'react' +import { COMPONENT_KEY } from './utils' +import { classNames } from '../../utilities/classNames' +import { namespaceComponent } from '../../utilities/component' +import { SectionUI } from './styles/Section.css' + +export interface Props { + children?: any + className?: string + isResponsive?: boolean +} + +class Section extends React.PureComponent { + static defaultProps = { + isResponsive: false, + } + + render() { + const { children, className, isResponsive, ...rest } = this.props + + const componentClassName = classNames( + 'c-PageSection', + isResponsive && 'is-responsive', + className + ) + + return ( + + {this.props.children} + + ) + } +} + +namespaceComponent(COMPONENT_KEY.Section)(Section) + +export default Section diff --git a/src/components/Page/__tests__/Card.test.js b/src/components/Page/__tests__/Card.test.js index c9da9de5b..24d1e5245 100644 --- a/src/components/Page/__tests__/Card.test.js +++ b/src/components/Page/__tests__/Card.test.js @@ -34,17 +34,3 @@ describe('Content', () => { expect(wrapper.find('div.ron').length).toBe(1) }) }) - -describe('Responsive', () => { - test('Renders responsive styles, if specified', () => { - const wrapper = mount() - - expect(wrapper.getDOMNode().classList.contains('is-responsive')).toBe(true) - }) - - test('Does not render responsive styles, if specified', () => { - const wrapper = mount() - - expect(wrapper.getDOMNode().classList.contains('is-responsive')).toBe(false) - }) -}) diff --git a/src/components/Page/__tests__/Header.test.js b/src/components/Page/__tests__/Header.test.js index d503502c1..21f9c0603 100644 --- a/src/components/Page/__tests__/Header.test.js +++ b/src/components/Page/__tests__/Header.test.js @@ -25,36 +25,86 @@ describe('Content', () => { }) }) -describe('Title', () => { +describe('Title and Subtitle', () => { test('Can render a title', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} /> + ) - expect(wrapper.text()).toContain('Channel 4') + expect(wrapper.find('h1').text()).toContain('Channel 4') }) - test('Can render a subtitle', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} + /> + ) + + expect(wrapper.find('span').text()).toContain('Channel 4') + }) + + test('Title is primary heading (h1) by default', () => { + const wrapper = mount( +
Channel 4} /> + ) + + expect(wrapper.find('h1').length).toBe(1) + }) + + test('Title can render h2', () => { + const wrapper = mount( +
Channel 4} + /> + ) + + expect(wrapper.find('h2').length).toBe(1) + }) + + test('Title can render smaller (secondary)', () => { + const wrapper = mount( +
( + + Channel 4 + + )} + /> + ) + + expect(wrapper.find('h2').hasClass('is-h4')).toBe(true) + }) + + test('Can render the title with "title" prop', () => { + const wrapper = mount(
) expect(wrapper.text()).toContain('Channel 4') - expect(wrapper.text()).toContain('News team') }) - test('Title is primary heading (h1)', () => { + test('Can render the subtitle with "subtitle" prop', () => { const wrapper = mount(
) - expect(wrapper.find('h1').length).toBe(1) + expect(wrapper.text()).toContain('Channel 4') + expect(wrapper.text()).toContain('News team') }) }) describe('Border', () => { test('Renders a border', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} /> + ) expect(wrapper.getDOMNode().classList.contains('is-withBorder')).toBe(true) }) test('Can not render a border, if specified', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} + withBorder={false} + /> + ) expect(wrapper.getDOMNode().classList.contains('is-withBorder')).toBe(false) }) @@ -76,7 +126,12 @@ describe('Responsive', () => { describe('withBottomMargin', () => { test('Renders withBottomMargin styles, if specified', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} + withBottomMargin + /> + ) expect(wrapper.getDOMNode().classList.contains('is-withBottomMargin')).toBe( true @@ -84,7 +139,12 @@ describe('withBottomMargin', () => { }) test('Does not render withBottomMargin styles, if specified', () => { - const wrapper = mount(
) + const wrapper = mount( +
Channel 4} + withBottomMargin={false} + /> + ) expect(wrapper.getDOMNode().classList.contains('is-withBottomMargin')).toBe( false diff --git a/src/components/Page/__tests__/Heading.test.js b/src/components/Page/__tests__/Heading.test.js deleted file mode 100644 index 6124df1ad..000000000 --- a/src/components/Page/__tests__/Heading.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import { mount } from 'enzyme' -import Heading from '../Heading' - -describe('ClassName', () => { - test('Has default className', () => { - const wrapper = mount(Heisenberg shop) - - expect( - wrapper.getDOMNode().classList.contains('c-PageHeader__titleHeading') - ).toBe(true) - }) - - test('Has Secondary className', () => { - const wrapper = mount(Beakers) - - expect( - wrapper - .getDOMNode() - .classList.contains('c-PageHeader__titleHeading--secondary') - ).toBe(true) - }) - - test('Applies custom className if specified', () => { - const className = 'baby-blue' - const wrapper = mount( - Heisenberg shop - ) - - expect(wrapper.hasClass(className)).toBe(true) - }) -}) - -describe('Primary: h1', () => { - test('Default is primary heading (h1)', () => { - const wrapper = mount(Heisenberg Shop) - - expect(wrapper.find('h1').length).toBe(1) - }) -}) - -describe('Secondary: h2', () => { - test('Secondary renders an h2', () => { - const wrapper = mount(Beakers) - - expect(wrapper.find('h2').length).toBe(1) - }) -}) diff --git a/src/components/Page/__tests__/Page.test.js b/src/components/Page/__tests__/Page.test.js index db1b33b35..753373aad 100644 --- a/src/components/Page/__tests__/Page.test.js +++ b/src/components/Page/__tests__/Page.test.js @@ -19,9 +19,9 @@ describe('ClassName', () => { describe('Content', () => { test('Can render child content', () => { - const wrapper = mount(Channel 4) + const wrapper = mount(Channel 5) - expect(wrapper.html()).toContain('Channel 4') + expect(wrapper.html()).toContain('Channel 5') }) test('Can render child component', () => { @@ -52,7 +52,9 @@ describe('Responsive', () => { const wrapper = mount( - + + + @@ -60,7 +62,7 @@ describe('Responsive', () => { expect( wrapper - .find(Page.Card) + .find(Page.Section) .getDOMNode() .classList.contains('is-responsive') ).toBe(true) diff --git a/src/components/Page/__tests__/Section.test.js b/src/components/Page/__tests__/Section.test.js new file mode 100644 index 000000000..9c87bd33c --- /dev/null +++ b/src/components/Page/__tests__/Section.test.js @@ -0,0 +1,50 @@ +import React from 'react' +import { mount } from 'enzyme' +import Section from '../Section' + +describe('ClassName', () => { + test('Has default className', () => { + const wrapper = mount(
) + + expect(wrapper.getDOMNode().classList.contains('c-PageSection')).toBe(true) + }) + + test('Applies custom className if specified', () => { + const className = 'channel-4' + const wrapper = mount(
) + + expect(wrapper.getDOMNode().classList.contains(className)).toBe(true) + }) +}) + +describe('Content', () => { + test('Can render child content', () => { + const wrapper = mount(
Channel 4
) + + expect(wrapper.text()).toBe('Channel 4') + }) + + test('Can render child component', () => { + const wrapper = mount( +
+
Channel 4
+
+ ) + + expect(wrapper.find('div.ron').length).toBe(1) + }) +}) + +describe('Responsive', () => { + test('Renders responsive styles, if specified', () => { + const wrapper = mount(
) + + expect(wrapper.getDOMNode().classList.contains('is-responsive')).toBe(true) + }) + + test('Does not render responsive styles, if specified', () => { + const wrapper = mount(
) + + expect(wrapper.getDOMNode().classList.contains('is-responsive')).toBe(false) + }) +}) diff --git a/src/components/Page/docs/Card.md b/src/components/Page/docs/Card.md index f4f33eeb0..d7a8b37a8 100644 --- a/src/components/Page/docs/Card.md +++ b/src/components/Page/docs/Card.md @@ -12,8 +12,7 @@ This component is a presentational wrapper used to render content within a [`Pag ## Props -| Prop | Type | Description | -| ------------ | --------- | ------------------------------------------------ | -| children | `any` | Content to render. | -| className | `string` | Custom class names to be added to the component. | -| isResponsive | `boolean` | Enables responsive styles. Default `false`. | +| Prop | Type | Description | +| --------- | -------- | ------------------------------------------------ | +| children | `any` | Content to render. | +| className | `string` | Custom class names to be added to the component. | diff --git a/src/components/Page/docs/Header.md b/src/components/Page/docs/Header.md index 8efc6263a..9e832b366 100644 --- a/src/components/Page/docs/Header.md +++ b/src/components/Page/docs/Header.md @@ -1,28 +1,55 @@ # Header -This component is a presentational wrapper used to a title/subtitle within a [`Page`](./Page.md). +This component is a presentational wrapper used to display a title/subtitle within a [`Page`](./Page.md). + +The `render` prop gives us access to a `Title` and a `Subtitle` component. + +Aside from those 2, you are free to add whatever markup you need: icons, links, tooltips, etc + +`Title` takes two props: `headingLevel`, with values of `"h1"` or `"h2"` and `isSecondary` (boolean) + +* `headingLevel`: The html tag to use "h1" or "h2", has no effect on looks, just semantics +* `isSecondary`: This determines if it should look slightly smaller when true. Use when you need a secondary heading in the same Card. ## Example ```jsx - + + ( +
+ Headers! + Oh so nice +
+ )} + /> +
...
``` +For backwards compatibility you can use the header using the "title" and "subtitle" props: + +```jsx + +``` + +However, this only renders big H1 tags, so avoid the use of it. + ## Props -| Prop | Type | Description | -| ---------------- | --------- | --------------------------------------------------- | -| className | `string` | Custom class names to be added to the component. | -| isResponsive | `boolean` | Enables responsive styles. Default `false`. | -| title | `string` | The [`Heading`](../../Heading) title to render. | -| subtitle | `string` | The [`Text`](../../Text) subtitle to render. | -| withBorder | `boolean` | Renders an [`Hr`](../../Hr) border. Default `true`. | -| withBottomMargin | `boolean` | Renders bottom margin styles. Default `true`. | +| Prop | Type | Description | +| ---------------- | ---------- | --------------------------------------------------------------------------------------- | +| className | `string` | Custom class names to be added to the component. | +| isResponsive | `boolean` | Enables responsive styles. Default `false`. | +| render | `function` | function with 2 arguments: `Title` and `Subtitle`, 2 React Components with styles ready | +| withBorder | `boolean` | Renders a border under the header. Default `true`. | +| withBottomMargin | `boolean` | Renders bottom margin styles. Default `true`. | +| title | `string` | The [`Heading`](../../Heading) title to render. | +| subtitle | `string` | The [`Text`](../../Text) subtitle to render. | diff --git a/src/components/Page/docs/Heading.md b/src/components/Page/docs/Heading.md deleted file mode 100644 index 426b65da6..000000000 --- a/src/components/Page/docs/Heading.md +++ /dev/null @@ -1,33 +0,0 @@ -# Heading - -This component leverages `` for use inside [`Page`](./Page.md) components. - -Pages usually have one [`Header`](./Header.md), whose contents are always a title and sometimes a subtitle, for the title `` uses this component in _primary_ mode, which means the heading will be an `

` element. - -_Secondary_ mode is for additional headings within a page and will be rendered as `

` elements. - -Styles (spacing and font sizes) are handled specifically for use within Pages. - -## Example - -```jsx - - - - Contact Details - ... - Address Details - ... - - -``` - -## Props - -| Prop | Type | Description | -| --------- | --------- | -------------------------------------------------- | -| className | `string` | Custom class names to be added to the component. | -| secondary | `boolean` | Signals a secondary (h2) heading. Default `false`. | diff --git a/src/components/Page/docs/Page.md b/src/components/Page/docs/Page.md index e5c654fb8..9abed08b2 100644 --- a/src/components/Page/docs/Page.md +++ b/src/components/Page/docs/Page.md @@ -7,9 +7,21 @@ This component is a presentational wrapper used to render a variety of content. ```jsx - + + ( +
+ My page + Very important stuff +
+ )} + /> + +

Here, I will explain the importance of this

+
+
- Save} /> + Got it} />
``` diff --git a/src/components/Page/docs/Section.md b/src/components/Page/docs/Section.md new file mode 100644 index 000000000..7fea5e6d0 --- /dev/null +++ b/src/components/Page/docs/Section.md @@ -0,0 +1,33 @@ +# Section + +Sections live inside `Card` components and their purpose is to divide content inside them. + +A clear indication of the need of more sections is when there are extra headings in the content. + +The use of sections, besides of being good for semantic purposes, allows the headings of each section to behave just like the headers in Responsive mode. + +`isResponsive` can be set on the `Page.Section`, but normally is better to set it on the parent `Page` component, so it doesn't have to be done individually on each section. + +## Example + +```jsx + + + +

Section 1

+

Content under section 1

+
+ +

Section 2

+

Content under section 2

+
+
+
+``` + +## Props + +| Prop | Type | Description | +| ------------ | --------- | ------------------------------------------------ | +| className | `string` | Custom class names to be added to the component. | +| isResponsive | `boolean` | Enables responsive styles. Default `false`. | diff --git a/src/components/Page/index.ts b/src/components/Page/index.ts index 073183c91..a98fc0abe 100644 --- a/src/components/Page/index.ts +++ b/src/components/Page/index.ts @@ -6,6 +6,6 @@ Page.Actions = propConnect(COMPONENT_KEY.Actions)(Page.Actions) Page.Card = propConnect(COMPONENT_KEY.Card)(Page.Card) Page.Content = propConnect(COMPONENT_KEY.Content)(Page.Content) Page.Header = propConnect(COMPONENT_KEY.Header)(Page.Header) -Page.Heading = propConnect(COMPONENT_KEY.Heading)(Page.Heading) +Page.Section = propConnect(COMPONENT_KEY.Section)(Page.Section) export default propConnect(COMPONENT_KEY.Page)(Page) diff --git a/src/components/Page/styles/Card.css.ts b/src/components/Page/styles/Card.css.ts index 920002335..62659dc8f 100644 --- a/src/components/Page/styles/Card.css.ts +++ b/src/components/Page/styles/Card.css.ts @@ -28,7 +28,7 @@ export const config = { transition: 'all 300ms ease', } -export const CardUI = styled('section')` +export const CardUI = styled('div')` ${baseStyles} background-color: white; border-radius: ${config.borderRadius}; box-shadow: ${config.boxShadow}; diff --git a/src/components/Page/styles/Header.css.ts b/src/components/Page/styles/Header.css.ts index f89d6a0de..d87049192 100644 --- a/src/components/Page/styles/Header.css.ts +++ b/src/components/Page/styles/Header.css.ts @@ -2,10 +2,14 @@ import baseStyles from '../../../styles/resets/baseStyles.css.js' import { breakpoint } from '../../../styles/mixins/breakpoints.css.js' import styled from '../../styled' import PageConfig from './Page.config.css' +import Heading from '../../Heading' export const config = { paddingBottom: '11px', - marginBottom: '25px', + marginBottom: '27px', + marginRight: { + superWidescreen: '60px', + }, width: { default: '100%', superWidescreen: '250px', @@ -14,11 +18,11 @@ export const config = { export const HeaderUI = styled('header')` ${baseStyles} margin-bottom: 0; - padding-bottom: ${config.paddingBottom}; width: ${config.width.default}; &.is-withBorder { border-bottom: 1px solid #e3e8eb; + padding-bottom: ${config.paddingBottom}; } &.is-withBottomMargin { @@ -31,6 +35,7 @@ export const HeaderUI = styled('header')` ` border-bottom: none; width: ${config.width.superWidescreen}; + margin-right: ${config.marginRight.superWidescreen} ` )}; } @@ -42,3 +47,8 @@ export const TitleUI = styled('div')` export const SubTitleUI = styled('div')` ${baseStyles} margin-top: 5px; ` + +export const HeadingUI = styled(Heading)` + margin: 0; + padding: 0; +` diff --git a/src/components/Page/styles/Heading.css.ts b/src/components/Page/styles/Heading.css.ts deleted file mode 100644 index 308f4345a..000000000 --- a/src/components/Page/styles/Heading.css.ts +++ /dev/null @@ -1,17 +0,0 @@ -import styled from '../../styled' -import Heading from '../../Heading' - -export const config = { - marginTop: '35px', - marginBottom: '27px', -} - -export const HeadingUI = styled(Heading)` - margin: 0; - padding: 0; -` - -export const SecondaryHeadingUI = styled(Heading)` - margin: ${config.marginTop} 0 ${config.marginBottom}; - padding: 0; -` diff --git a/src/components/Page/styles/Section.css.ts b/src/components/Page/styles/Section.css.ts new file mode 100644 index 000000000..1072698a4 --- /dev/null +++ b/src/components/Page/styles/Section.css.ts @@ -0,0 +1,38 @@ +import styled from '../../styled' +import baseStyles from '../../../styles/resets/baseStyles.css.js' +import { breakpoint } from '../../../styles/mixins/breakpoints.css.js' +import PageConfig from './Page.config.css' + +export const config = { + flexDirection: { + default: 'column', + superWidescreen: 'row', + }, + flexItemsAlign: { + superWidescreen: 'flex-start', + }, + marginBottom: '35px', +} + +export const SectionUI = styled('section')` + ${baseStyles} display: flex; + flex-direction: ${config.flexDirection.default}; + width: 100%; + margin-bottom: ${config.marginBottom}; + + &:last-child { + margin-bottom: 0; + } + + &.is-responsive { + ${breakpoint( + PageConfig.breakpoint.superWidescreen, + ` + flex-direction: ${config.flexDirection.superWidescreen}; + align-items: ${config.flexItemsAlign.superWidescreen}; + ` + )}; + } +` + +export default SectionUI diff --git a/src/components/Page/utils.ts b/src/components/Page/utils.ts index eab60c604..e48c69e05 100644 --- a/src/components/Page/utils.ts +++ b/src/components/Page/utils.ts @@ -5,6 +5,7 @@ export const COMPONENT_KEY: { Content: string Header: string Heading: string + Section: string } = { Page: 'Page', Actions: 'PageActions', @@ -12,4 +13,5 @@ export const COMPONENT_KEY: { Content: 'PageContent', Header: 'PageHeader', Heading: 'PageHeading', + Section: 'PageSection', } diff --git a/stories/Page/Page.Card.stories.js b/stories/Page/Page.Card.stories.js index e2dc8dc3e..b4bc0433e 100644 --- a/stories/Page/Page.Card.stories.js +++ b/stories/Page/Page.Card.stories.js @@ -1,15 +1,6 @@ import React from 'react' -// import * as React from 'react' import { storiesOf } from '@storybook/react' -import { - Button, - ControlGroup, - FormGroup, - FormLabel, - Input, - Page, - Switch, -} from '../../src/index.js' +import { Page } from '../../src/index.js' import { App } from './decorators' const stories = storiesOf('Page/Card', module).addDecorator(App) @@ -17,124 +8,13 @@ const stories = storiesOf('Page/Card', module).addDecorator(App) stories.add('default', () => ( - - - - - - +

Cards are just presentational wrappers, nothing special.

-
-)) - -stories.add('Sections', () => ( - - - - - - - - - -)) - -stories.add('Responsive', () => ( - - - +

There can be more than 1 per Page.

- - - - - - - - Save - - } - secondary={ - - } - serious={ - - } - /> -
-)) - -stories.add('Example', () => ( - - - +

Sorry Cards, I didn't mean that. You are so pretty.

- - Save} - secondary={} - />
)) - -function ExampleContent() { - return ( - - - - - - - - - - - - - Default Settings - - - - - - - - .helpscoutdocs.com - - - - - - - - - - https:// - - - - - - - - - ) -} diff --git a/stories/Page/Page.Example.stories.js b/stories/Page/Page.Example.stories.js new file mode 100644 index 000000000..30e968d7e --- /dev/null +++ b/stories/Page/Page.Example.stories.js @@ -0,0 +1,134 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { + Button, + ControlGroup, + FormGroup, + FormLabel, + Input, + Page, + Switch, +} from '../../src/index.js' +import { App } from './decorators' + +const stories = storiesOf('Page/Example', module).addDecorator(App) + +stories.add('Responsive', () => ( + + + + ( +
+ Edit your account + Welcome to the Dharma Initiative. +
+ )} + /> + + + + + + + +
+ + + ( +
+ + Default Settings + +
+ )} + /> + +
+
+ + + + ( +
+ More settings this way + + Heading looks the same, but it's an H2! There should only be one + h1 per page ;) + +
+ )} + /> + +
+
+ + + Save + + } + secondary={ + + } + serious={ + + } + /> +
+)) + +function ExampleContent() { + return ( + + + + + + + + + + + + + + + + + + + + .helpscoutdocs.com + + + + + + + + + + https:// + + + + + + + + + ) +} diff --git a/stories/Page/Page.Header.stories.js b/stories/Page/Page.Header.stories.js new file mode 100644 index 000000000..cced5a534 --- /dev/null +++ b/stories/Page/Page.Header.stories.js @@ -0,0 +1,264 @@ +import React from 'react' +import styled from '../../src/components/styled' +import { storiesOf } from '@storybook/react' +import { Page } from '../../src/index.js' +import { App } from './decorators' + +const stories = storiesOf('Page/Header', module).addDecorator(App) + +const renderPropCode = ` + ( +
+ Headers! + Oh so nice +
+ ) +} />` + +const titleSubtitlePropCode = ` +` + +const renderPropCodeH2 = ` + ( +
+ Headers! + Oh so nice +
+ ) +} />` + +const renderPropCodeSecondary = ` + ( +
+ Headers! + Oh so nice +
+ ) +} />` + +const P = styled('p')` + margin: 0 0 1.5em 0; + padding: 0; + font-size: 14px; + + &:last-child { + margin-bottom: 0; + } +` + +stories.add('default', () => ( + + + + ( +
+ Headers! + Oh so nice +
+ )} + /> + +

Each Card can have one or more Headers.

+

+ The Header gives us one special prop:{' '} + render +

+

+ render takes a function + with 2 arguments: Title{' '} + and Subtitle +

+

+ Those 2 are React Components with all the pretty styles ready, use + like this: +

+
+            {renderPropCode}
+          
+

+ Inside render aside + from those 2, you are free to add whatever markup you need: icons, + links, tooltips, etc +

+

+ Title has 2 props: +

    +
  • + headingLevel: The + html tag to use "h1" or "h2", has no effect on looks, just + semantics +
  • +
  • + Reason being: there should only be one "h1"{' '} + per Page +
  • +
  • + So, if you have a 2nd Card, use: +
    +                  
    +                    {renderPropCodeH2}
    +                  
    +                
    +
  • +
  • + isSecondary: This + determines if it should look slightly smaller when true. Use + when you need a secondary heading in the same Card like: +
    +                  
    +                    {renderPropCodeSecondary}
    +                  
    +                
    +
  • +
+

+
+
+ + ( +
+ + Second Header + + This one has a subtitle too +
+ )} + /> + Something cool +
+ + ( +
+ + Third Header, no subtitle here + +
+ )} + /> + Something cool +
+ + + +

+ For backwards compatibility you can use the header using the "title" + and "subtitle" props: +

+
+            {titleSubtitlePropCode}
+          
+

However, this only renders big H1 tags, so avoid the use of it.

+
+
+
+
+)) + +stories.add('Subsequent Cards', () => ( + + + + ( +
+ Headers! + Oh so nice +
+ )} + /> +

+ For accesibility compliance, each page should only have one{' '} + H1 heading +

+

Normally the 1st Card would hold it

+
+
+ + + ( +
+ This is an h2 + Not that you can tell +
+ )} + /> +

+ So make sure that on the other Cards the prop{' '} + headingLevel on the{' '} + Title is set to "h2" +

+

+ The heading would look the same, but screen readers would not get + confused what is the main purpose of this page +

+
+
+
+)) + +stories.add('Responsiveness', () => ( + + + + ( +
+ Responsive? + You bet +
+ )} + /> +

+ Just set isResponsive on + the Page +

+
+ + ( +
+ + Second Header + + This one has a subtitle too +
+ )} + /> + Something cool +
+ + ( +
+ + Third Header, no subtitle here + +
+ )} + /> + Something cool +
+
+
+)) diff --git a/stories/Page/Page.Section.stories.js b/stories/Page/Page.Section.stories.js new file mode 100644 index 000000000..94288b084 --- /dev/null +++ b/stories/Page/Page.Section.stories.js @@ -0,0 +1,27 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { Page } from '../../src/index.js' +import { App } from './decorators' + +const stories = storiesOf('Page/Section', module).addDecorator(App) + +stories.add('default', () => ( + + + +

Section 1

+

A Page.Section is where content should live.

+

Sections are wrappers too, not pretty, but smart

+

They handle responsiveness if enabled on the Page

+
+ +

Section 2

+

When to use?

+

+ If the content inside a Card has multiple Headings, it's a sure signal + that it has multiple sections +

+
+
+
+))