diff --git a/.happo.js b/.happo.js index 799656a8b8..a77a949d78 100644 --- a/.happo.js +++ b/.happo.js @@ -1,6 +1,35 @@ const { RemoteBrowserTarget } = require('happo.io') const happoStorybookPlugin = require('happo-plugin-storybook') +const breakpoints = { + xs: 0, + sm: 480, + md: 768, + lg: 1024, + xl: 1440, +} + +const sortBy = field => (a, b) => a[field] - b[field] + +const checkpoints = Object.entries(breakpoints) + .map(([name, minWidth]) => ({ name, minWidth })) + .sort(sortBy('minWidth')) + .map((breakpoint, idx, breakpoints) => { + const nextBreakpoint = breakpoints[idx + 1] + + const viewportWidth = nextBreakpoint + ? nextBreakpoint.minWidth - 1 // Either next 1px lower than next breakpoint minimum + : breakpoint.minWidth + 1 // Or 1px higher than current breakpoint, when the last one + + return { + [`chrome-desktop-${breakpoint.name}`]: new RemoteBrowserTarget('chrome', { + viewport: `${viewportWidth}x1024`, + applyPseudoClasses: true, + }), + } + }) + .reduce((a, b) => Object.assign(a, b)) + module.exports = { project: process.env.HAPPO_PROJECT, apiKey: process.env.HAPPO_API_KEY, @@ -9,12 +38,13 @@ module.exports = { targets: { 'chrome-desktop': new RemoteBrowserTarget('chrome', { viewport: '1280x1024', - applyPseudoClasses: true - }) + applyPseudoClasses: true, + }), + ...process.env.SCREENSHOT_BREAKPOINTS && checkpoints, }, plugins: [ happoStorybookPlugin({ - outputDir: '.happo' - }) - ] + outputDir: '.happo', + }), + ], } diff --git a/.storybook/components/PicassoBook/Chapter.tsx b/.storybook/components/PicassoBook/Chapter.tsx index 7b42b291af..06d4c5c743 100644 --- a/.storybook/components/PicassoBook/Chapter.tsx +++ b/.storybook/components/PicassoBook/Chapter.tsx @@ -5,12 +5,12 @@ import React, { Fragment, ReactNode } from 'react' import DocumentationGenerator, { PropDocumentation, PropDocumentationMap, - Documentable + Documentable, } from '~/.storybook/utils/documentation-generator' import { generateUrl, getHost, - normalize + normalize, } from '../../../src/utils/url-generator' import { Typography } from '@toptal/picasso' @@ -44,6 +44,7 @@ type Options = { extra?: string description?: string takeScreenshot?: boolean + screenshotBreakpoints?: boolean } & Record class Chapter extends Base { @@ -76,8 +77,8 @@ class Chapter extends Base { options: { decorator: (story: () => ReactNode) => (
{story()}
- ) - } + ), + }, }) return this @@ -91,7 +92,7 @@ class Chapter extends Base { const render = () => this.createSection({ - sectionFn: render + sectionFn: render, }) return this @@ -136,7 +137,7 @@ class Chapter extends Base { this.createSection({ sectionFn: render, title: name, - subtitle: description + subtitle: description, }) return this @@ -146,7 +147,7 @@ class Chapter extends Base { const finalOptions: Options = typeof options === 'string' ? { - title: options + title: options, } : options @@ -167,7 +168,7 @@ class Chapter extends Base { host: getHost(), kind: this.page.section, type: this.page.title, - section: anchor + section: anchor, }) const render = () => ( @@ -191,7 +192,7 @@ class Chapter extends Base { sectionFn: render, ...finalOptions, subtitle: description, - info: extra + info: extra, }) return this @@ -200,7 +201,7 @@ class Chapter extends Base { toStoryBook() { return { ...this.options, - sections: this.collection.map(section => section.toStoryBook()) + sections: this.collection.map(section => section.toStoryBook()), } } } diff --git a/.storybook/components/PicassoBook/Page.js b/.storybook/components/PicassoBook/Page.js index 72e7358a5d..5e3d1b9500 100644 --- a/.storybook/components/PicassoBook/Page.js +++ b/.storybook/components/PicassoBook/Page.js @@ -7,6 +7,8 @@ import TabChapter from './TabChapter' const COMPONENTS_SECTION = 'Components' +const DEFAULT_HAPPO_TARGET = 'chrome-desktop' + class Page extends Base { type = 'Page' title = '' @@ -20,14 +22,14 @@ class Page extends Base { info = null, sectionFn = null, section = COMPONENTS_SECTION, - alwaysOnTop = false + alwaysOnTop = false, }) { super({ title, subtitle, info, section, - sectionFn + sectionFn, }) this.title = title @@ -59,7 +61,7 @@ class Page extends Base { toStoryBook() { return { ...this.options, - chapters: this.collection.map(chapter => chapter.toStoryBook()) + chapters: this.collection.map(chapter => chapter.toStoryBook()), } } @@ -92,7 +94,16 @@ class Page extends Base { const stories = storiesOf(storyName, module) chapter.sections.forEach(section => { - const parameters = { happo: section.takeScreenshot } + const happoConfig = {} + + if (!section.screenshotBreakpoints) { + happoConfig.targets = [DEFAULT_HAPPO_TARGET] + } + + const parameters = { + happo: section.takeScreenshot && happoConfig, + } + stories.add(section.title || section.id, section.sectionFn, parameters) }) }) diff --git a/package.json b/package.json index cdbaf939d2..c31ba726a0 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "generate:svg-components": "yarn generate:icons && yarn generate:pictograms", "generate:icons": "./bin/generate-components-from-svg icon", "generate:pictograms": "./bin/generate-components-from-svg pictogram", - "happo": "cross-env TEST_ENV=visual HAPPO_PROJECT=Picasso/Storybook happo", - "happo:storybook": "cross-env TEST_ENV=visual HAPPO_IS_ASYNC=false happo-ci-github-actions", + "happo": "cross-env SCREENSHOT_BREAKPOINTS=true TEST_ENV=visual HAPPO_PROJECT=Picasso/Storybook happo", + "happo:storybook": "cross-env SCREENSHOT_BREAKPOINTS=true TEST_ENV=visual HAPPO_IS_ASYNC=false happo-ci-github-actions", "lint": "davinci-syntax lint code --check .", "lint:fix": "davinci-syntax lint code .", "release": "yarn build:package && changeset publish", diff --git a/packages/picasso/src/Grid/story/index.jsx b/packages/picasso/src/Grid/story/index.jsx index 6c681f5342..122a9a6300 100644 --- a/packages/picasso/src/Grid/story/index.jsx +++ b/packages/picasso/src/Grid/story/index.jsx @@ -25,13 +25,23 @@ page page .createChapter() - .addExample('Grid/story/Alignment.example.tsx', 'Alignment') - .addExample('Grid/story/Direction.example.tsx', 'Direction') - .addExample('Grid/story/Wrapping.example.tsx', 'Wrapping') + .addExample('Grid/story/Alignment.example.tsx', { + title: 'Alignment', + screenshotBreakpoints: true, + }) + .addExample('Grid/story/Direction.example.tsx', { + title: 'Direction', + screenshotBreakpoints: true, + }) + .addExample('Grid/story/Wrapping.example.tsx', { + title: 'Wrapping', + screenshotBreakpoints: true, + }) .addExample('Grid/story/ResponsiveSpacing.example.tsx', { title: 'Responsive spacing', description: 'When `spacing` is not explicitly specified by consumer, grid adjusts it according to the screen size (please see the property description for details). You can try to resize screen, to see how different spacing is applied.', + screenshotBreakpoints: true, }) page.connect(gridItemStory.chapter) diff --git a/packages/picasso/src/PageArticle/story/index.jsx b/packages/picasso/src/PageArticle/story/index.jsx index abf2523697..b4c93a5f85 100644 --- a/packages/picasso/src/PageArticle/story/index.jsx +++ b/packages/picasso/src/PageArticle/story/index.jsx @@ -4,7 +4,10 @@ import PicassoBook from '~/.storybook/components/PicassoBook' const chapter = PicassoBook.connectToPage(page => page .createChapter('Page.Article', 'Use as a page content container') - .addExample('PageArticle/story/Default.example.tsx', 'Default') + .addExample('PageArticle/story/Default.example.tsx', { + title: 'Default', + screenshotBreakpoints: true, + }) ) const componentDocs = PicassoBook.createComponentDocs( diff --git a/packages/picasso/src/PageFooter/story/index.jsx b/packages/picasso/src/PageFooter/story/index.jsx index ab7b997b1f..d7a2f9fb61 100644 --- a/packages/picasso/src/PageFooter/story/index.jsx +++ b/packages/picasso/src/PageFooter/story/index.jsx @@ -6,12 +6,18 @@ const componentDocs = PicassoBook.createComponentDocs(PageFooter, 'Page.Footer') const chapter = PicassoBook.connectToPage(page => page .createChapter('Page.Footer', 'A Footer component') - .addExample('PageFooter/story/Default.example.tsx', 'Default') - .addExample('PageFooter/story/RightContent.example.tsx', 'Right content') - .addExample( - 'PageFooter/story/CopyrightContent.example.tsx', - 'Copyright content' - ) + .addExample('PageFooter/story/Default.example.tsx', { + title: 'Default', + screenshotBreakpoints: true, + }) + .addExample('PageFooter/story/RightContent.example.tsx', { + title: 'Right content', + screenshotBreakpoints: true, + }) + .addExample('PageFooter/story/CopyrightContent.example.tsx', { + title: 'Copyright content', + screenshotBreakpoints: true, + }) ) export default { diff --git a/packages/picasso/src/PageSidebar/story/index.jsx b/packages/picasso/src/PageSidebar/story/index.jsx index 6baba75723..877b28f7df 100644 --- a/packages/picasso/src/PageSidebar/story/index.jsx +++ b/packages/picasso/src/PageSidebar/story/index.jsx @@ -35,6 +35,7 @@ page }) .addExample('PageSidebar/story/Size.example.tsx', { title: 'Sizes', + screenshotBreakpoints: true, }) page.connect(sidebarItemStory.chapter) diff --git a/packages/picasso/src/PageTopBar/story/index.jsx b/packages/picasso/src/PageTopBar/story/index.jsx index c23e7b301c..ab1c1f102d 100644 --- a/packages/picasso/src/PageTopBar/story/index.jsx +++ b/packages/picasso/src/PageTopBar/story/index.jsx @@ -21,17 +21,41 @@ page page .createChapter() - .addExample('PageTopBar/story/Default.example.tsx', 'Default') - .addExample('PageTopBar/story/Variants.example.tsx', 'Variants') - .addExample('PageTopBar/story/LeftContent.example.tsx', 'Left content') - .addExample('PageTopBar/story/RightContent.example.tsx', 'Right content') - .addExample('PageTopBar/story/CenterContent.example.tsx', 'Center content') - .addExample( - 'PageTopBar/story/ExtraMenuContent.example.tsx', - 'Extra header menu content' - ) - .addExample('PageTopBar/story/Link.example.tsx', 'With link') - .addExample('PageTopBar/story/WithoutTitle.example.tsx', 'Without title') - .addExample('PageTopBar/story/Logo.example.tsx', 'With custom logo') + .addExample('PageTopBar/story/Default.example.tsx', { + title: 'Default', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/Variants.example.tsx', { + title: 'Variants', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/LeftContent.example.tsx', { + title: 'Left content', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/RightContent.example.tsx', { + title: 'Right content', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/CenterContent.example.tsx', { + title: 'Center content', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/ExtraMenuContent.example.tsx', { + title: 'Extra header menu content', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/Link.example.tsx', { + title: 'With link', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/WithoutTitle.example.tsx', { + title: 'Without title', + screenshotBreakpoints: true, + }) + .addExample('PageTopBar/story/Logo.example.tsx', { + title: 'With custom logo', + screenshotBreakpoints: true, + }) page.connect(topBarMenuStory.chapter) diff --git a/packages/picasso/src/Tabs/story/index.jsx b/packages/picasso/src/Tabs/story/index.jsx index 1db00e6c34..8044018f1a 100644 --- a/packages/picasso/src/Tabs/story/index.jsx +++ b/packages/picasso/src/Tabs/story/index.jsx @@ -37,5 +37,6 @@ page }) .addExample('Tabs/story/FullWidth.example.tsx', { title: 'Full Width', + screenshotBreakpoints: true, }) page.connect(tabStory.chapter)