diff --git a/.babelrc b/.babelrc index a4d3bd81ff67..38d54990ce39 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,9 @@ { "presets": ["env", "stage-0", "react"], "env": { + "test": { + "plugins": ["require-context-hook"] + }, "plugins": [ "emotion", "babel-plugin-macros", diff --git a/.circleci/config.yml b/.circleci/config.yml index 906cc5061d4f..dd5a757f5786 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,7 +30,7 @@ jobs: yarn bootstrap --core - save_cache: name: "Cache core dependencies" - key: core-dependencies-{{ checksum "yarn.lock" }} + key: core-dependencies-v2-{{ checksum "yarn.lock" }} paths: - node_modules - examples/angular-cli/node_modules @@ -39,9 +39,10 @@ jobs: - examples/official-storybook/node_modules - examples/polymer-cli/node_modules - examples/vue-kitchen-sink/node_modules + - examples/marko-cli/node_modules - save_cache: name: "Cache core dist" - key: core-dist-{{ .Revision }} + key: core-dist-v2-{{ .Revision }} paths: - addons - app @@ -53,11 +54,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: Workaround for https://github.com/GoogleChrome/puppeteer/issues/290 command: sh ./scripts/workaround-puppeteer-issue-290.sh @@ -81,6 +82,11 @@ jobs: command: | cd examples/polymer-cli yarn build-storybook + - run: + name: "Build marko-cli" + command: | + cd examples/marko-cli + yarn build-storybook - run: name: "Build official-storybook" command: | @@ -104,11 +110,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Run react kitchen-sink (smoke test)" @@ -147,11 +153,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Bootstrap" command: | @@ -206,7 +212,7 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore docs dependencies cache" keys: @@ -214,7 +220,7 @@ jobs: - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Lint" command: | @@ -226,11 +232,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Run unit tests" command: | @@ -247,11 +253,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Test CLI" command: | @@ -267,11 +273,11 @@ jobs: - restore_cache: name: "Restore core dependencies cache" keys: - - core-dependencies-{{ checksum "yarn.lock" }} + - core-dependencies-v2-{{ checksum "yarn.lock" }} - restore_cache: name: "Restore core dist cache" keys: - - core-dist-{{ .Revision }} + - core-dist-v2-{{ .Revision }} - run: name: "Test CLI with latest CR(N)A" command: | diff --git a/.teamcity/OpenSourceProjects_Storybook/Project.kt b/.teamcity/OpenSourceProjects_Storybook/Project.kt index 033f1ddf5488..9238e6b3675b 100644 --- a/.teamcity/OpenSourceProjects_Storybook/Project.kt +++ b/.teamcity/OpenSourceProjects_Storybook/Project.kt @@ -157,9 +157,21 @@ feature { param("title", "New chart title") param("seriesTitle", "Serie") } + feature { + type = "Invitation" + id = "PROJECT_EXT_209" + param("createdByUserId", "1702") + param("invitationType", "joinProjectInvitation") + param("secure:token", "credentialsJSON:07400f1b-a51d-46ae-b056-2e24a653f4d1") + param("name", "Join Storybook project") + param("welcomeText", "Filipp Riabchun invites you to join the Storybook project") + param("disabled", "false") + param("groupKey", "STORYBOOK_DEVELO") + param("multi", "true") + } } params { - param("docker.node.version", "10.1") + param("docker.node.version", "latest") } }) diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Build_2.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Build_2.kt index dea33f57a015..a9565e677dfe 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Build_2.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Build_2.kt @@ -6,6 +6,7 @@ import jetbrains.buildServer.configs.kotlin.v2017_2.failureConditions.BuildFailu import jetbrains.buildServer.configs.kotlin.v2017_2.failureConditions.failOnMetricChange import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.VcsTrigger import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.vcs +import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.retryBuild import jetbrains.buildServer.configs.kotlin.v2017_2.buildFeatures.merge import jetbrains.buildServer.configs.kotlin.v2017_2.ui.* @@ -27,6 +28,15 @@ object OpenSourceProjects_Storybook_Build_2 : BuildType({ vcs { quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT triggerRules = "-:comment=^TeamCity change:**" + branchFilter = """ + +:pull/* + +:release/* + +:master + +:dependencies.io-* + """.trimIndent() + } + retryBuild { + delaySeconds = 60 } } diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Danger.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Danger.kt index b49a22c78b38..6b1e39bf0954 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Danger.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Danger.kt @@ -4,6 +4,7 @@ import jetbrains.buildServer.configs.kotlin.v2017_2.* import jetbrains.buildServer.configs.kotlin.v2017_2.buildFeatures.commitStatusPublisher import jetbrains.buildServer.configs.kotlin.v2017_2.buildSteps.script import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.vcs +import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.retryBuild import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.VcsTrigger object OpenSourceProjects_Storybook_Danger : BuildType({ @@ -44,6 +45,9 @@ object OpenSourceProjects_Storybook_Danger : BuildType({ -:master """.trimIndent() } + retryBuild { + delaySeconds = 3600 + } } features { diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt index 98a215252b4c..ba18d2624a21 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt @@ -61,14 +61,14 @@ examples/official-storybook/image-snapshots/__image_snapshots__ => image-snapsho ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget yarn test --image --teamcity """.trimIndent() - dockerImage = "node:8" + dockerImage = "node:%docker.node.version%" } } failureConditions { failOnMetricChange { metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE - threshold = 50 + threshold = 60 units = BuildFailureOnMetric.MetricUnit.PERCENTS comparison = BuildFailureOnMetric.MetricComparison.LESS compareTo = build { diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_ReactNative.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_ReactNative.kt index 38496ba8c65e..328de556ccb2 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_ReactNative.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_ReactNative.kt @@ -28,7 +28,7 @@ object OpenSourceProjects_Storybook_ReactNative : BuildType({ yarn yarn bootstrap --core --reactnative --reactnativeapp """.trimIndent() - dockerImage = "node:9" + dockerImage = "node:%docker.node.version%" } script { name = "react-native-vanilla" @@ -36,7 +36,7 @@ object OpenSourceProjects_Storybook_ReactNative : BuildType({ cd examples/react-native-vanilla yarn storybook --smoke-test """.trimIndent() - dockerImage = "node:9" + dockerImage = "node:%docker.node.version%" } script { name = "crna-kitchen-sink" @@ -44,7 +44,7 @@ object OpenSourceProjects_Storybook_ReactNative : BuildType({ cd examples/crna-kitchen-sink yarn storybook --smoke-test """.trimIndent() - dockerImage = "node:9" + dockerImage = "node:%docker.node.version%" } script { name = "Test" @@ -52,7 +52,7 @@ object OpenSourceProjects_Storybook_ReactNative : BuildType({ yarn test --reactnative --coverage --runInBand --teamcity yarn coverage """.trimIndent() - dockerImage = "node:9" + dockerImage = "node:%docker.node.version%" } } diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/StotybookApp.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/StotybookApp.kt index 7ad99193404c..98910244dc65 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/StotybookApp.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/StotybookApp.kt @@ -13,7 +13,9 @@ enum class StorybookApp(val appName: String, val exampleDir: String, val merged: POLYMER("Polymer", "polymer-cli"), MITHRIL("Mithril", "mithril-kitchen-sink"), HTML("HTML", "html-kitchen-sink"), - MARKO("Marko", "marko-cli"); + MARKO("Marko", "marko-cli"), + HYPERAPP("Hyperapp", "hyperapp-kitchen-sink", false), + SVELTE("Svelte", "svelte-kitchen-sink", false); val lowerName = appName.toLowerCase() diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts new file mode 100644 index 000000000000..b9386de80ba8 --- /dev/null +++ b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts @@ -0,0 +1,24 @@ +package OpenSourceProjects_Storybook.patches.buildTypes + +import jetbrains.buildServer.configs.kotlin.v2017_2.* +import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.RetryBuildTrigger +import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.retryBuild +import jetbrains.buildServer.configs.kotlin.v2017_2.ui.* + +/* +This patch script was generated by TeamCity on settings change in UI. +To apply the patch, change the buildType with uuid = '2b9c73e2-0a6e-47ca-95ae-729cac42be2b' (id = 'OpenSourceProjects_Storybook_Build_2') +accordingly, and delete the patch script. +*/ +changeBuildType("2b9c73e2-0a6e-47ca-95ae-729cac42be2b") { + triggers { + val trigger1 = find { + retryBuild { + delaySeconds = 60 + } + } + trigger1.apply { + enabled = false + } + } +} diff --git a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt b/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt index b20deec059e7..1ae984287082 100644 --- a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt +++ b/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt @@ -10,9 +10,7 @@ object OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMa url = "git@github.com:storybooks/storybook.git" branchSpec = """ +:refs/(pull/*)/head - +:refs/heads/(release/3.4) - +:refs/heads/(master) - +:refs/heads/(dependencies.io-*) + +:refs/heads/* """.trimIndent() authMethod = uploadedKey { userName = "git" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3547f11313be..a7a5161c257e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,193 @@ +# 4.0.0-alpha.12 + +2018-July-03 + +#### Bug Fixes + +- Fix non-polyfilled themed UI components [#3829](https://github.com/storybooks/storybook/pull/3829) + +# 4.0.0-alpha.11 + +2018-July-02 + +#### Features + +- Storybook UI theming [#3628](https://github.com/storybooks/storybook/pull/3628) +- Replaced 'dotenv-webpack' with 'universal-dotenv' to support multiple dot env files (like CRA) [#3744](https://github.com/storybooks/storybook/pull/3744) +- Support other type of webpack configs [#3785](https://github.com/storybooks/storybook/pull/3785) + +#### Bug Fixes + +- Marko: fix welcome component [#3796](https://github.com/storybooks/storybook/pull/3796) +- Addon-a11y: Run analysis on demand [#3690](https://github.com/storybooks/storybook/pull/3690) + +# 4.0.0-alpha.10 + +2018-June-21 + +#### Breaking Changes + +- Storyshots - Replace require_context.js with babel-plugin-require-context-hook [#3757](https://github.com/storybooks/storybook/pull/3757) +- Storyshots advanced config options [#3747](https://github.com/storybooks/storybook/pull/3747) +- Storyshots addon refactoring [#3745](https://github.com/storybooks/storybook/pull/3745) +- Extract imageSnapshots to a separate package [#3742](https://github.com/storybooks/storybook/pull/3742) + +#### Bug Fixes + +- Addon-knobs: Allow number knob to be empty [#3775](https://github.com/storybooks/storybook/pull/3775) +- Improvements to Info Docgen parsing [#3772](https://github.com/storybooks/storybook/pull/3772) +- Angular-cli 6 assets as a glob-object compatibility fix [#3751](https://github.com/storybooks/storybook/pull/3751) + +#### Maintenance + +- Try to fix linter issues [#3748](https://github.com/storybooks/storybook/pull/3748) + +# 3.4.8 + +2018-June-21 + +#### Bug Fixes + +- Fix centered addon for IE11 [#3735](https://github.com/storybooks/storybook/pull/3735) +- Display functions as variables not invocations [#3761](https://github.com/storybooks/storybook/pull/3761) + +# 4.0.0-alpha.9 + +2018-June-10 + +#### Breaking Changes + +- storyshots: Remove deprecated props from storyshots [#3717](https://github.com/storybooks/storybook/pull/3717) +- angular: angular-cli 6 (with webpack 4) compatibility [#3491](https://github.com/storybooks/storybook/pull/3491) + +#### Features + +- addon-info: Use parameters for info addon [#3697](https://github.com/storybooks/storybook/pull/3697) +- addon-backgounds: Use parameters for backgrounds addon [#3676](https://github.com/storybooks/storybook/pull/3676) +- storyshots: add renderWithOptions to @addons/storyshots [#3479](https://github.com/storybooks/storybook/pull/3479) +- addon-knobs: Make withKnobs accept story parameters [#3675](https://github.com/storybooks/storybook/pull/3675) + +#### Bug Fixes + +- storysource: Add default parser option. Support prettier v1.13.0 [#3660](https://github.com/storybooks/storybook/pull/3660) +- react-native: using disableWebsockets instead of useWebsockets [#3686](https://github.com/storybooks/storybook/pull/3686) +- Updates storybook-start.js to use child_process instead of shelljs [#3527](https://github.com/storybooks/storybook/pull/3527) +- Force render on knob button click and update example [#3650](https://github.com/storybooks/storybook/pull/3650) + +#### Maintenance + +- Refactor webpack and babel configs to core [#3655](https://github.com/storybooks/storybook/pull/3655) +- Remove status bar hiding [#3634](https://github.com/storybooks/storybook/pull/3634) +- React Native Refactored list view [#3635](https://github.com/storybooks/storybook/pull/3635) +- Using only single channel for react native [#3636](https://github.com/storybooks/storybook/pull/3636) + +#### Dependency Upgrades + +
+ +88 Upgrades + + +- Update gatsby-plugin-sharp in /docs from "1.6.46" to "1.6.47" +- Update gatsby-remark-images in /docs from "1.5.65" to "1.5.66" +- Update gatsby-transformer-remark in /docs from "1.7.41" to "1.7.42" +- Update react-router in /docs from "4.2.0" to "4.3.1" +- Update danger in / from "3.7.14" to "3.7.15" +- Update eslint-plugin-react in / from "7.8.2" to "7.9.1" +- Update eslint-teamcity in / from "2.0.0" to "2.0.1" +- Update lint-staged in / from "7.1.2" to "7.1.3" +- Update prettier in / from "1.13.2" to "1.13.4" +- Update remark-lint in / from "6.0.1" to "6.0.2" +- Update remark-preset-lint-recommended in / from "3.0.1" to "3.0.2" +- Update typescript in / from "2.8.3" to "2.9.1" +- Update airbnb-js-shims in lib/core from "1.5.2" to "1.6.0" +- Update autoprefixer in lib/core from "8.5.1" to "8.6.0" +- Update babel-plugin-macros in lib/core from "2.2.1" to "2.2.2" +- Update dotenv-webpack in lib/core from "1.5.5" to "1.5.6" +- Update webpack in lib/core from "4.10.1" to "4.10.2" +- Update sass-loader in app/angular from "7.0.1" to "7.0.2" +- Update ts-loader in app/angular from "4.3.0" to "4.3.1" +- Update axe-core in addons/a11y from "3.0.2" to "3.0.3" +- Update moment in addons/knobs from "2.22.1" to "2.22.2" +- Update prettier in addons/storysource from "1.13.3" to "1.13.4" +- Update react-modal in lib/ui from "3.4.4" to "3.4.5" +- Update babel-plugin-macros in app/react-native from "2.2.1" to "2.2.2" +- Update dotenv-webpack in app/react-native from "1.5.5" to "1.5.6" +- Update webpack in app/react-native from "4.10.1" to "4.10.2" +- Update prettier in examples/marko-cli from "1.13.2" to "1.13.4" +- Update webpack in examples/marko-cli from "4.10.1" to "4.10.2" +- Update webpack in examples/polymer-cli from "4.10.1" to "4.10.2" +- Update @types/jasmine in examples/angular-cli from "2.8.7" to "2.8.8" +- Update @types/node in examples/angular-cli from "9.6.18" to "9.6.20" +- Update ts-node in examples/angular-cli from "6.0.5" to "6.1.0" +- Update typescript in examples/angular-cli from "2.8.3" to "2.9.1" +- Update webpack in examples/cra-kitchen-sink from "4.10.1" to "4.10.2" +- Update webpack in examples/mithril-kitchen-sink from "4.10.1" to "4.10.2" +- Update webpack in examples/vue-kitchen-sink from "4.10.1" to "4.10.2" +- Update webpack in examgatsby-plugin-sharp in /docs from "1.6.44" to "1.6.46" +- Update gatsby-remark-copy-linked-files in /docs from "1.5.32" to "1.5.35" +- Update gatsby-remark-images in /docs from "1.5.63" to "1.5.65" +- Update gatsby-source-filesystem in /docs from "1.5.36" to "1.5.38" +- Update gatsby in /docs from "1.9.261" to "1.9.269" +- Update gh-pages in /docs from "1.1.0" to "1.2.0" +- Update @storybook/addon-actions in /docs from "3.4.5" to "3.4.6" +- Update @storybook/addon-links in /docs from "3.4.5" to "3.4.6" +- Update @storybook/addons in /docs from "3.4.5" to "3.4.6" +- Update @storybook/react in /docs from "3.4.5" to "3.4.6" +- Update cross-env in / from "5.1.5" to "5.1.6" +- Update eslint-plugin-jest in / from "21.15.1" to "21.17.0" +- Update jest-enzyme in / from "6.0.0" to "6.0.1" +- Update jest-image-snapshot in / from "2.4.1" to "2.4.2" +- Update prettier in / from "1.12.1" to "1.13.0" +- Update react in / from "16.3.2" to "16.4.0" +- Update react-dom in / from "16.3.2" to "16.4.0" +- Update react-test-renderer in / from "16.3.2" to "16.4.0" +- Update airbnb-js-shims in lib/core from "1.5.1" to "1.5.2" +- Update autoprefixer in lib/core from "8.5.0" to "8.5.1" +- Update webpack in lib/core from "4.8.3" to "4.9.2" +- Update core-js in app/angular from "2.5.6" to "2.5.7" +- Update common-tags in app/html from "1.7.2" to "1.8.0" +- Update common-tags in app/marko from "1.7.2" to "1.8.0" +- Update common-tags in app/mithril from "1.7.2" to "1.8.0" +- Update common-tags in app/polymer from "1.7.2" to "1.8.0" +- Update common-tags in app/react from "1.7.2" to "1.8.0" +- Update common-tags in app/vue from "1.7.2" to "1.8.0" +- Update vue-loader in app/vue from "14.2.2" to "14.2.3" +- Update core-js in addons/info from "2.5.6" to "2.5.7" +- Update react-test-renderer in addons/info from "16.3.2" to "16.4.0" +- Update prettier in addons/storysource from "1.12.1" to "1.13.0" +- Update events in lib/ui from "2.0.0" to "2.1.0" +- Update jest-image-snapshot in addons/storyshots from "2.4.1" to "2.4.2" +- Update react in addons/storyshots from "16.3.2" to "16.4.0" +- Update webpack in app/react-native from "4.8.3" to "4.9.2" +- Update marko in examples/marko-cli from "4.9.7" to "4.10.0" +- Update prettier in examples/marko-cli from "1.12.1" to "1.13.0" +- Update webpack in examples/marko-cli from "4.8.3" to "4.9.2" +- Update webpack in examples/polymer-cli from "4.8.3" to "4.9.2" +- Update core-js in examples/angular-cli from "2.5.6" to "2.5.7" +- Update rxjs in examples/angular-cli from "5.5.10" to "5.5.11" +- Update ts-node in examples/angular-cli from "6.0.3" to "6.0.5" +- Update react in examples/cra-kitchen-sink from "16.3.2" to "16.4.0" +- Update react-dom in examples/cra-kitchen-sink from "16.3.2" to "16.4.0" +- Update webpack in examples/cra-kitchen-sink from "4.8.3" to "4.9.2" +- Update webpack in examples/mithril-kitchen-sink from "4.8.3" to "4.9.2" +- Update cross-env in examples/vue-kitchen-sink from "5.1.5" to "5.1.6" +- Update vue-loader in examples/vue-kitchen-sink from "14.2.2" to "14.2.3" +- Update webpack in examples/vue-kitchen-sink from "4.8.3" to "4.9.2" +- Update react in examples/official-storybook from "16.3.2" to "16.4.0" +- Update react-dom in examples/official-storybook from "16.3.2" to "16.4.0" +- Update webpack in examples/official-storybook from "4.8.3" to "4.9.2" + +
+ +# 3.4.7 + +2018-June-10 + +#### Bug Fixes + +- Remove linebreaks in notes text when they are html elements [#3731](https://github.com/storybooks/storybook/pull/3731) + # 4.0.0-alpha.8 2018-May-26 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 975d3d506560..a23c120e2300 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -331,7 +331,7 @@ yarn bootstrap --reset --core ```sh # publish and tag the release -npm run publish -- --concurrency 1 --npm-tag=alpha --force-publish=* +npm run publish:alpha # update the release page open https://github.com/storybooks/storybook/releases @@ -355,7 +355,7 @@ git commit -m "Changelog for vX.Y" yarn bootstrap --reset --core # publish and tag the release -npm run publish -- --concurrency 1 --force-publish=* +npm run publish # update the release page open https://github.com/storybooks/storybook/releases diff --git a/MIGRATION.md b/MIGRATION.md index e514b5714e1b..b6e9dfe875cb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -6,6 +6,7 @@ - [Keyboard shortcuts moved](#keyboard-shortcuts-moved) - [Removed addWithInfo](#removed-add-with-info) - [Removed RN addons](#removed-rn-addons) + - [Storyshots changes](#storyshots-changes) - [From version 3.3.x to 3.4.x](#from-version-33x-to-34x) - [From version 3.2.x to 3.3.x](#from-version-32x-to-33x) - [Refactored Knobs](#refactored-knobs) @@ -40,6 +41,19 @@ With 4.0 as our first major release in over a year, we've collected a lot of cle The `@storybook/react-native` had built-in addons (`addon-actions` and `addon-links`) that have been marked as deprecated since 3.x. They have been fully removed in 4.x. If your project still uses the built-ins, you'll need to add explicit dependencies on `@storybook/addon-actions` and/or `@storybook/addon-links` and import directly from those packages. +### Storyshots Changes + +1. `imageSnapshot` test function was extracted from `addon-storyshots` +and moved to a new package - `addon-storyshots-puppeteer` that now will +be dependant on puppeteer. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-puppeteer) +2. `getSnapshotFileName` export was replaced with the `Stories2SnapsConverter` +class that now can be overridden for a custom implementation of the +snapshot-name generation. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#stories2snapsconverter) +3. Storybook that was configured with Webpack's `require.context()` feature +will need to add a babel plugin to polyfill this functionality. +A possible plugin might be [babel-plugin-require-context-hook](https://github.com/smrq/babel-plugin-require-context-hook). +[README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#configure-jest-to-work-with-webpacks-requirecontext) + ## From version 3.3.x to 3.4.x There are no expected breaking changes in the 3.4.x release, but 3.4 contains a major refactor to make it easier to support new frameworks, and we will document any breaking changes here if they arise. diff --git a/README.md b/README.md index 74de7bd6bba8..a09f3be30960 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook) [![Storybook Slack](https://now-examples-slackin-rrirkqohko.now.sh/badge.svg)](https://now-examples-slackin-rrirkqohko.now.sh/) [![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors) +[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=)](https://www.browserstack.com/automate/public-build/) * * * diff --git a/addons/a11y/package.json b/addons/a11y/package.json index 140b64113b7e..9a151d574c41 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "a11y addon for storybook", "keywords": [ "a11y", @@ -25,13 +25,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/client-logger": "4.0.0-alpha.8", - "@storybook/components": "4.0.0-alpha.8", - "@storybook/core-events": "4.0.0-alpha.8", - "axe-core": "^3.0.2", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/client-logger": "4.0.0-alpha.12", + "@storybook/components": "4.0.0-alpha.12", + "@storybook/core-events": "4.0.0-alpha.12", + "axe-core": "^3.0.3", "babel-runtime": "^6.26.0", - "emotion": "^9.1.3", "global": "^4.3.2", "prop-types": "^15.6.1", "react-emotion": "^9.1.3" diff --git a/addons/a11y/src/components/Panel.js b/addons/a11y/src/components/Panel.js index 8e3617c81b18..d371ebc10359 100644 --- a/addons/a11y/src/components/Panel.js +++ b/addons/a11y/src/components/Panel.js @@ -1,65 +1,93 @@ import React, { Component } from 'react'; -import addons from '@storybook/addons'; +import PropTypes from 'prop-types'; import styled from 'react-emotion'; -import { CHECK_EVENT_ID } from '../shared'; +import { STORY_RENDERED } from '@storybook/core-events'; +import { ActionBar, ActionButton } from '@storybook/components'; + +import { CHECK_EVENT_ID, RERUN_EVENT_ID, REQUEST_CHECK_EVENT_ID } from '../shared'; import Tabs from './Tabs'; import Report from './Report'; -const Passes = styled('span')({ - color: '#0D6731', -}); +const Passes = styled('span')(({ theme }) => ({ + color: theme.successColor, +})); -const Violations = styled('span')({ - color: '#AC2300', -}); +const Violations = styled('span')(({ theme }) => ({ + color: theme.failColor, +})); class Panel extends Component { - constructor(props, ...args) { - super(props, ...args); - this.state = { - passes: [], - violations: [], - }; - this.channel = addons.getChannel(); - - this.onUpdate = this.onUpdate.bind(this); - } + static propTypes = { + active: PropTypes.bool.isRequired, + channel: PropTypes.shape({ + on: PropTypes.func, + emit: PropTypes.func, + removeListener: PropTypes.func, + }).isRequired, + }; + + state = { + passes: [], + violations: [], + }; componentDidMount() { - this.channel.on(CHECK_EVENT_ID, this.onUpdate); + this.props.channel.on(CHECK_EVENT_ID, this.onUpdate); + this.props.channel.on(STORY_RENDERED, this.requestCheck); + this.props.channel.on(RERUN_EVENT_ID, this.requestCheck); + } + + componentDidUpdate(prevProps) { + if (!prevProps.active && this.props.active) { + this.requestCheck(); + } } componentWillUnmount() { - this.channel.removeListener(CHECK_EVENT_ID, this.onUpdate); + this.props.channel.removeListener(CHECK_EVENT_ID, this.onUpdate); + this.props.channel.removeListener(STORY_RENDERED, this.requestCheck); + this.props.channel.removeListener(RERUN_EVENT_ID, this.requestCheck); } - onUpdate({ passes, violations }) { + onUpdate = ({ passes, violations }) => { this.setState({ passes, violations, }); - } + }; + + requestCheck = () => { + if (this.props.active) { + this.props.channel.emit(REQUEST_CHECK_EVENT_ID); + } + }; render() { const { passes, violations } = this.state; + const { active } = this.props; - return ( - {violations.length} Violations, - panel: , - }, - { - label: {passes.length} Passes, - panel: , - }, - ]} - /> - ); + return active ? ( +
+ {violations.length} Violations, + panel: , + }, + { + label: {passes.length} Passes, + panel: , + }, + ]} + /> + + RERUN TEST + +
+ ) : null; } } diff --git a/addons/a11y/src/components/Report/Info.js b/addons/a11y/src/components/Report/Info.js index 8b2679424d7b..5c1a9b6cfaad 100644 --- a/addons/a11y/src/components/Report/Info.js +++ b/addons/a11y/src/components/Report/Info.js @@ -3,18 +3,18 @@ import PropTypes from 'prop-types'; import styled from 'react-emotion'; -const Wrapper = styled('div')({ - backgroundColor: 'rgb(234, 234, 234)', +const Wrapper = styled('div')(({ theme }) => ({ + backgroundColor: theme.barFill, padding: '12px', marginBottom: '10px', -}); +})); const Help = styled('p')({ margin: '0 0 12px', }); const Link = styled('a')({ marginTop: '12px', textDecoration: 'underline', - color: 'rgb(130, 130, 130)', + color: 'inherit', display: 'block', }); diff --git a/addons/a11y/src/components/Report/Item.js b/addons/a11y/src/components/Report/Item.js index cae1eeb03e53..9b2340ee916f 100644 --- a/addons/a11y/src/components/Report/Item.js +++ b/addons/a11y/src/components/Report/Item.js @@ -7,19 +7,28 @@ import Info from './Info'; import Tags from './Tags'; import Elements from './Elements'; -const Wrapper = styled('div')({ +const Wrapper = styled('div')(({ theme }) => ({ padding: '0 14px', cursor: 'pointer', - borderBottom: '1px solid rgb(234, 234, 234)', -}); + borderBottom: theme.mainBorder, +})); -const HeaderBar = styled('button')({ +const HeaderBar = styled('button')(({ theme }) => ({ padding: '12px 0px', display: 'block', width: '100%', border: 0, background: 'none', -}); + color: 'inherit', + + borderTop: '3px solid transparent', + borderBottom: '3px solid transparent', + + '&:focus': { + outline: '0 none', + borderBottom: `3px solid ${theme.highlightColor}`, + }, +})); class Item extends Component { static propTypes = { diff --git a/addons/a11y/src/components/Report/RerunButton.js b/addons/a11y/src/components/Report/RerunButton.js deleted file mode 100644 index f419e3b197bf..000000000000 --- a/addons/a11y/src/components/Report/RerunButton.js +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'react-emotion'; - -const RerunButton = styled('button')({ - position: 'absolute', - bottom: 0, - right: 0, - border: 'none', - borderTop: 'solid 1px rgba(0, 0, 0, 0.2)', - borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)', - background: 'rgba(255, 255, 255, 0.5)', - padding: '5px 10px', - borderRadius: '4px 0 0 0', - color: 'rgba(0, 0, 0, 0.5)', - textTransform: 'uppercase', -}); - -export default RerunButton; diff --git a/addons/a11y/src/components/Report/Tags.js b/addons/a11y/src/components/Report/Tags.js index 2ad147f64cf4..0800dbb6c299 100644 --- a/addons/a11y/src/components/Report/Tags.js +++ b/addons/a11y/src/components/Report/Tags.js @@ -9,14 +9,12 @@ const Wrapper = styled('div')({ margin: '12px 0', }); -const Item = styled('div')({ +const Item = styled('div')(({ theme }) => ({ margin: '0 6px', padding: '5px', - border: '1px solid rgb(234, 234, 234)', - borderRadius: '2px', - color: 'rgb(130, 130, 130)', - fontSize: '12px', -}); + border: theme.mainBorder, + borderRadius: theme.mainBorderRadius, +})); function Tags({ tags }) { return {tags.map(tag => {tag})}; diff --git a/addons/a11y/src/components/Report/index.js b/addons/a11y/src/components/Report/index.js index 7a6b3e75eaa2..d9897dfa6818 100644 --- a/addons/a11y/src/components/Report/index.js +++ b/addons/a11y/src/components/Report/index.js @@ -1,18 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import addons from '@storybook/addons'; import { Placeholder } from '@storybook/components'; -import { RERUN_EVENT_ID } from '../../shared'; - -import RerunButton from './RerunButton'; import Item from './Item'; -function onRerunClick() { - const channel = addons.getChannel(); - channel.emit(RERUN_EVENT_ID); -} - const Report = ({ items, empty, passes }) => (
{items.length ? ( @@ -20,7 +11,6 @@ const Report = ({ items, empty, passes }) => ( ) : ( {empty} )} - Re-run tests
); diff --git a/addons/a11y/src/components/Tabs.js b/addons/a11y/src/components/Tabs.js index 2bdd3efcfff2..970b287249c2 100644 --- a/addons/a11y/src/components/Tabs.js +++ b/addons/a11y/src/components/Tabs.js @@ -6,13 +6,14 @@ import styled from 'react-emotion'; const Container = styled('div')({ width: '100%', position: 'relative', + minHeight: '100%', }); -const List = styled('div')({ - borderBottom: '1px solid rgb(234, 234, 234)', +const List = styled('div')(({ theme }) => ({ + borderBottom: theme.mainBorder, flexWrap: 'wrap', display: 'flex', -}); +})); const Item = styled('button')( ({ active }) => @@ -22,9 +23,7 @@ const Item = styled('button')( fontWeight: 600, } : {}, - { - color: 'rgb(68, 68, 68)', - fontSize: '11px', + ({ theme }) => ({ textDecoration: 'none', textTransform: 'uppercase', padding: '10px 15px', @@ -33,9 +32,16 @@ const Item = styled('button')( fontWeight: 500, opacity: 0.7, border: 'none', + borderTop: '3px solid transparent', + borderBottom: '3px solid transparent', background: 'none', flex: 1, - } + + '&:focus': { + outline: '0 none', + borderBottom: `3px solid ${theme.highlightColor}`, + }, + }) ); class Tabs extends Component { diff --git a/addons/a11y/src/index.js b/addons/a11y/src/index.js index e330e75f4a95..7d579a9b66d6 100644 --- a/addons/a11y/src/index.js +++ b/addons/a11y/src/index.js @@ -4,7 +4,7 @@ import addons from '@storybook/addons'; import Events from '@storybook/core-events'; import { logger } from '@storybook/client-logger'; -import { CHECK_EVENT_ID, RERUN_EVENT_ID } from './shared'; +import { CHECK_EVENT_ID, REQUEST_CHECK_EVENT_ID } from './shared'; let axeOptions = {}; @@ -23,11 +23,10 @@ const runA11yCheck = () => { const a11ySubscription = () => { const channel = addons.getChannel(); - channel.on(Events.STORY_RENDERED, runA11yCheck); - channel.on(RERUN_EVENT_ID, runA11yCheck); + channel.on(REQUEST_CHECK_EVENT_ID, runA11yCheck); + return () => { - channel.removeListener(Events.STORY_RENDERED, runA11yCheck); - channel.removeListener(RERUN_EVENT_ID, runA11yCheck); + channel.removeListener(REQUEST_CHECK_EVENT_ID, runA11yCheck); }; }; diff --git a/addons/a11y/src/register.js b/addons/a11y/src/register.js index 043797800f68..7074287dc6fb 100644 --- a/addons/a11y/src/register.js +++ b/addons/a11y/src/register.js @@ -5,12 +5,12 @@ import Panel from './components/Panel'; import { ADDON_ID, PANEL_ID } from './shared'; function init() { - addons.register(ADDON_ID, () => { + addons.register(ADDON_ID, api => { + const channel = addons.getChannel(); addons.addPanel(PANEL_ID, { title: 'Accessibility', - render() { - return ; - }, + // eslint-disable-next-line react/prop-types + render: ({ active }) => , }); }); } diff --git a/addons/a11y/src/shared/index.js b/addons/a11y/src/shared/index.js index 2bcaf9d19683..5c810f2a67f6 100755 --- a/addons/a11y/src/shared/index.js +++ b/addons/a11y/src/shared/index.js @@ -3,5 +3,6 @@ const ADDON_ID = '@storybook/addon-a11y'; const PANEL_ID = `${ADDON_ID}/panel`; const CHECK_EVENT_ID = `${ADDON_ID}/check`; const RERUN_EVENT_ID = `${ADDON_ID}/rerun`; +const REQUEST_CHECK_EVENT_ID = `${ADDON_ID}/request-check`; -export { ADDON_ID, PANEL_ID, CHECK_EVENT_ID, RERUN_EVENT_ID }; +export { ADDON_ID, PANEL_ID, CHECK_EVENT_ID, RERUN_EVENT_ID, REQUEST_CHECK_EVENT_ID }; diff --git a/addons/actions/README.md b/addons/actions/README.md index 7b63d53cd156..650f1e031a03 100644 --- a/addons/actions/README.md +++ b/addons/actions/README.md @@ -134,11 +134,11 @@ import { storiesOf } from '@storybook/html'; import { withActions } from '@storybook/addon-actions'; storiesOf('button', module) - // Log mousovers on entire story and clicks on .btn + // Log mouseovers on entire story and clicks on .btn .addDecorator(withActions('mouseover', 'click .btn')) .add('with actions', () => `
- Clicks on this button will be logged: + Clicks on this button will be logged:
`); ``` diff --git a/addons/actions/package.json b/addons/actions/package.json index 480dd2956614..ddcddd771e9b 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "Action Logger addon for storybook", "keywords": [ "storybook" @@ -20,12 +20,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/components": "4.0.0-alpha.8", - "@storybook/core-events": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/components": "4.0.0-alpha.12", + "@storybook/core-events": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", "deep-equal": "^1.0.1", - "emotion": "^9.1.3", + "emotion-theming": "^9.1.2", "global": "^4.3.2", "lodash.isequal": "^4.5.0", "make-error": "^1.3.4", diff --git a/addons/actions/src/components/ActionLogger/index.js b/addons/actions/src/components/ActionLogger/index.js index d565ce463313..2a31e78831a8 100644 --- a/addons/actions/src/components/ActionLogger/index.js +++ b/addons/actions/src/components/ActionLogger/index.js @@ -1,48 +1,48 @@ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React from 'react'; import Inspector from 'react-inspector'; -import { Actions, Action, Button, Wrapper, InspectorContainer, Countwrap, Counter } from './style'; +import { withTheme } from 'emotion-theming'; -class ActionLogger extends Component { - getActionData() { - return this.props.actions.map(action => this.renderAction(action)); - } +import { ActionBar, ActionButton } from '@storybook/components'; - renderAction(action) { - const counter = {action.count}; - return ( - - {action.count > 1 && counter} - - - - - ); - } +import { Actions, Action, Wrapper, InspectorContainer, Countwrap, Counter } from './style'; - render() { - return ( - - {this.getActionData()} - - - ); - } -} +const ActionLogger = withTheme(({ actions, onClear, theme }) => ( + + + {actions.map(action => ( + + {action.count > 1 && {action.count}} + + + + + ))} + + + + CLEAR + + +)); ActionLogger.propTypes = { - onClear: PropTypes.func, - // eslint-disable-next-line react/forbid-prop-types - actions: PropTypes.array, -}; -ActionLogger.defaultProps = { - onClear: () => {}, - actions: [], + onClear: PropTypes.func.isRequired, + actions: PropTypes.arrayOf( + PropTypes.shape({ + count: PropTypes.node, + data: PropTypes.shape({ + name: PropTypes.node.isRequired, + args: PropTypes.any, + }), + }) + ).isRequired, }; export default ActionLogger; diff --git a/addons/actions/src/components/ActionLogger/style.js b/addons/actions/src/components/ActionLogger/style.js index e2d4b67081f6..202a547b82b8 100644 --- a/addons/actions/src/components/ActionLogger/style.js +++ b/addons/actions/src/components/ActionLogger/style.js @@ -1,5 +1,4 @@ import styled from 'react-emotion'; -import { Button as BaseButton } from '@storybook/components'; export const Actions = styled('pre')({ flex: 1, @@ -12,24 +11,12 @@ export const Actions = styled('pre')({ export const Action = styled('div')({ display: 'flex', padding: '3px 3px 3px 0', - borderLeft: '5px solid white', - borderBottom: '1px solid #fafafa', + borderLeft: '5px solid transparent', + borderBottom: '1px solid transparent', transition: 'all 0.1s', alignItems: 'start', }); -export const Button = styled(BaseButton)({ - position: 'absolute', - bottom: 0, - right: 0, - borderRadius: '4px 0 0 0', - textTransform: 'uppercase', - letterSpacing: 1, - paddingTop: 5, - paddingBottom: 5, - border: '0 none', -}); - export const Counter = styled('div')({ margin: '0 5px 0 5px', backgroundColor: '#777777', @@ -51,4 +38,5 @@ export const Wrapper = styled('div')({ flex: 1, display: 'flex', position: 'relative', + minHeight: '100%', }); diff --git a/addons/actions/src/containers/ActionLogger/index.js b/addons/actions/src/containers/ActionLogger/index.js index df814b21ad87..6d78106dfb9d 100644 --- a/addons/actions/src/containers/ActionLogger/index.js +++ b/addons/actions/src/containers/ActionLogger/index.js @@ -56,21 +56,25 @@ export default class ActionLogger extends React.Component { } render() { + const { active } = this.props; const props = { actions: this.state.actions, onClear: () => this.clearActions(), }; - return ; + return active ? : null; } } ActionLogger.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - channel: PropTypes.object, + active: PropTypes.bool.isRequired, + channel: PropTypes.shape({ + emit: PropTypes.func, + on: PropTypes.func, + removeListener: PropTypes.func, + }).isRequired, api: PropTypes.shape({ - onStory: PropTypes.func.isRequired, + onStory: PropTypes.func, + getQueryParam: PropTypes.func, + setQueryParams: PropTypes.func, }).isRequired, }; -ActionLogger.defaultProps = { - channel: {}, -}; diff --git a/addons/actions/src/manager.js b/addons/actions/src/manager.js index 0fc82a627c7b..43eea1709d91 100644 --- a/addons/actions/src/manager.js +++ b/addons/actions/src/manager.js @@ -8,7 +8,8 @@ export function register() { const channel = addons.getChannel(); addons.addPanel(PANEL_ID, { title: 'Action Logger', - render: () => , + // eslint-disable-next-line react/prop-types + render: ({ active }) => , }); }); } diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index c49335867b36..75860e0f4f78 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "A storybook addon to show different backgrounds for your preview", "keywords": [ "addon", @@ -24,10 +24,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/core-events": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/core-events": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", - "emotion": "^9.1.3", "global": "^4.3.2", "prop-types": "^15.6.1", "react-emotion": "^9.1.3", diff --git a/addons/backgrounds/src/BackgroundPanel.js b/addons/backgrounds/src/BackgroundPanel.js index 4752d3c5d29f..5d571fd1f088 100644 --- a/addons/backgrounds/src/BackgroundPanel.js +++ b/addons/backgrounds/src/BackgroundPanel.js @@ -16,14 +16,13 @@ const Title = styled('h5')({ fontSize: 16, }); -const Pre = styled('pre')({ +const Pre = styled('pre')(({ theme }) => ({ padding: '30px', display: 'block', - background: 'rgba(19,19,19,0.9)', - color: 'rgba(255,255,255,0.95)', + background: theme.fillColor, marginTop: '15px', lineHeight: '1.75em', -}); +})); const List = styled('div')({ display: 'inline-block', @@ -47,13 +46,13 @@ const defaultBackground = { }; const instructionsHtml = ` -import { storiesOf } from "@storybook/react"; -import { withBackgrounds } from "@storybook/addon-backgrounds"; +import { storiesOf } from '@storybook/react'; +import { withBackgrounds } from '@storybook/addon-backgrounds'; -storiesOf("First Component", module) +storiesOf('First Component', module) .addDecorator(withBackgrounds([ - { name: "twitter", value: "#00aced" }, - { name: "facebook", value: "#3b5998" }, + { name: 'twitter', value: '#00aced' }, + { name: 'facebook', value: '#3b5998" }, ])) .add("First Button", () => ); `.trim(); @@ -130,8 +129,11 @@ export default class BackgroundPanel extends Component { } render() { + const { active } = this.props; const backgrounds = [...this.state.backgrounds]; - + if (!active) { + return null; + } if (!backgrounds.length) return ; const hasDefault = backgrounds.filter(x => x.default).length; @@ -149,6 +151,7 @@ export default class BackgroundPanel extends Component { } } BackgroundPanel.propTypes = { + active: PropTypes.bool.isRequired, api: PropTypes.shape({ getQueryParam: PropTypes.func, setQueryParams: PropTypes.func, diff --git a/addons/backgrounds/src/Swatch.js b/addons/backgrounds/src/Swatch.js index 02a7a7c0b7e3..e518da10bfee 100644 --- a/addons/backgrounds/src/Swatch.js +++ b/addons/backgrounds/src/Swatch.js @@ -3,24 +3,26 @@ import PropTypes from 'prop-types'; import styled from 'react-emotion'; -const Button = styled('button')({ +const Button = styled('button')(({ theme }) => ({ listStyle: 'none', - backgroundColor: '#fff', + backgroundColor: theme.barFill, textAlign: 'center', - border: '1px solid rgba(0,0,0,0.1)', - borderRadius: 4, + border: theme.mainBorder, + borderRadius: theme.mainBorderRadius, + color: 'inherit', cursor: 'pointer', display: 'inline-block', width: 175, verticalAlign: 'top', wordWrap: 'break-word', padding: 0, -}); -const Block = styled('div')(({ bg }) => ({ + overflow: 'hidden', +})); + +const Block = styled('div')(({ bg, theme }) => ({ height: 80, - borderRadius: '4px 4px 0 0', transition: 'opacity 0.25s ease-in-out', - borderBottom: '1px solid rgba(0,0,0,0.1)', + borderBottom: theme.mainBorder, background: bg, backgroundSize: 'cover', backgroundPosition: 'center', diff --git a/addons/backgrounds/src/__tests__/BackgroundPanel.js b/addons/backgrounds/src/__tests__/BackgroundPanel.js index 59eb89db51ff..8c9f59aac76c 100644 --- a/addons/backgrounds/src/__tests__/BackgroundPanel.js +++ b/addons/backgrounds/src/__tests__/BackgroundPanel.js @@ -30,26 +30,26 @@ jest.mock('global', () => ({ describe('Background Panel', () => { it('should exist', () => { - const backgroundPanel = shallow(); + const backgroundPanel = shallow(); expect(backgroundPanel).toBeDefined(); }); it('should have a default background value of transparent', () => { - const backgroundPanel = shallow(); + const backgroundPanel = shallow(); expect(backgroundPanel.state().backgrounds).toHaveLength(0); }); it('should show setup instructions if no colors provided', () => { - const backgroundPanel = shallow(); + const backgroundPanel = shallow(); expect(backgroundPanel.html().match(/Setup Instructions/gim).length).toBeGreaterThan(0); }); it('should set the query string', () => { const SpiedChannel = new EventEmitter(); - mount(); + mount(); SpiedChannel.emit(Events.SET, backgrounds); expect(mockedApi.getQueryParam).toBeCalledWith('background'); @@ -57,7 +57,7 @@ describe('Background Panel', () => { it('should not unset the query string', () => { const SpiedChannel = new EventEmitter(); - mount(); + mount(); SpiedChannel.emit(Events.UNSET, []); expect(mockedApi.setQueryParams).not.toHaveBeenCalled(); @@ -65,7 +65,9 @@ describe('Background Panel', () => { it('should accept colors through channel and render the correct swatches with a default swatch', () => { const SpiedChannel = new EventEmitter(); - const backgroundPanel = mount(); + const backgroundPanel = mount( + + ); SpiedChannel.emit(Events.SET, backgrounds); expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds); @@ -73,7 +75,9 @@ describe('Background Panel', () => { it('should allow setting a default swatch', () => { const SpiedChannel = new EventEmitter(); - const backgroundPanel = mount(); + const backgroundPanel = mount( + + ); const [head, ...tail] = backgrounds; const localBgs = [{ ...head, default: true }, ...tail]; SpiedChannel.emit(Events.SET, localBgs); @@ -88,7 +92,9 @@ describe('Background Panel', () => { it('should allow the default swatch become the background color', () => { const SpiedChannel = new EventEmitter(); - const backgroundPanel = mount(); + const backgroundPanel = mount( + + ); const [head, second, ...tail] = backgrounds; const localBgs = [head, { ...second, default: true }, ...tail]; SpiedChannel.on('background', bg => { @@ -106,7 +112,9 @@ describe('Background Panel', () => { it('should unset all swatches on receiving the background-unset message', () => { const SpiedChannel = new EventEmitter(); - const backgroundPanel = mount(); + const backgroundPanel = mount( + + ); SpiedChannel.emit(Events.SET, backgrounds); expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds); @@ -118,7 +126,9 @@ describe('Background Panel', () => { it('should set iframe background', () => { const SpiedChannel = new EventEmitter(); - const backgroundPanel = mount(); + const backgroundPanel = mount( + + ); backgroundPanel.setState({ backgrounds }); // force re-render backgroundPanel diff --git a/addons/backgrounds/src/register.js b/addons/backgrounds/src/register.js index 50d22ddbba67..c1a380e25443 100644 --- a/addons/backgrounds/src/register.js +++ b/addons/backgrounds/src/register.js @@ -10,6 +10,7 @@ addons.register(ADDON_ID, api => { const channel = addons.getChannel(); addons.addPanel(PANEL_ID, { title: 'Backgrounds', - render: () => , + // eslint-disable-next-line react/prop-types + render: ({ active }) => , }); }); diff --git a/addons/centered/package.json b/addons/centered/package.json index 185e3d3c3a6d..c916ceac3e43 100644 --- a/addons/centered/package.json +++ b/addons/centered/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-centered", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "Storybook decorator to center components", "license": "MIT", "author": "Muhammed Thanish ", diff --git a/addons/centered/src/styles.js b/addons/centered/src/styles.js index 911b33e980a6..6fca71e173d7 100644 --- a/addons/centered/src/styles.js +++ b/addons/centered/src/styles.js @@ -7,11 +7,11 @@ const styles = { right: 0, display: 'flex', alignItems: 'center', - justifyContent: 'center', overflow: 'auto', }, innerStyle: { margin: 'auto', + maxHeight: '100%', // Hack for centering correctly in IE11 }, }; diff --git a/addons/events/package.json b/addons/events/package.json index 5bee4ecde0e4..d1593f53262f 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -19,10 +19,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/core-events": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/core-events": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", - "emotion": "^9.1.3", "format-json": "^1.0.3", "prop-types": "^15.6.1", "react-emotion": "^9.1.3", diff --git a/addons/events/src/components/Event.js b/addons/events/src/components/Event.js index f47100af5082..b81d178ee6a0 100644 --- a/addons/events/src/components/Event.js +++ b/addons/events/src/components/Event.js @@ -76,6 +76,14 @@ const Wrapper = styled('div')({ width: '100%', }); +function getJSONFromString(str) { + try { + return JSON.parse(str); + } catch (e) { + return str; + } +} + class Item extends Component { static propTypes = { name: PropTypes.string.isRequired, @@ -89,13 +97,9 @@ class Item extends Component { payload: {}, }; - static getJSONFromString(str) { - try { - return JSON.parse(str); - } catch (e) { - return str; - } - } + state = { + isTextAreaShowed: false, + }; static getDerivedStateFromProps = ({ payload }, { prevPayload }) => { if (payload !== prevPayload) { @@ -103,7 +107,7 @@ class Item extends Component { return { failed: false, - payload: Item.getJSONFromString(payloadString), + payload: getJSONFromString(payloadString), payloadString, prevPayload, }; @@ -111,10 +115,6 @@ class Item extends Component { return null; }; - state = { - isTextAreaShowed: false, - }; - onChange = ({ target: { value } }) => { const newState = { payloadString: value, diff --git a/addons/events/src/components/Panel.js b/addons/events/src/components/Panel.js index adcab42e4b0b..7061171ac8d8 100644 --- a/addons/events/src/components/Panel.js +++ b/addons/events/src/components/Panel.js @@ -10,10 +10,12 @@ const Wrapper = styled('div')({ width: '100%', boxSizing: 'border-box', padding: '10px', + minHeight: '100%', }); export default class Events extends Component { static propTypes = { + active: PropTypes.bool.isRequired, channel: PropTypes.shape({ on: PropTypes.func, emit: PropTypes.func, @@ -43,10 +45,11 @@ export default class Events extends Component { render() { const { events } = this.state; - return ( + const { active } = this.props; + return active ? ( {events.map(event => )} - ); + ) : null; } } diff --git a/addons/events/src/manager.js b/addons/events/src/manager.js index c5929d7d525e..842bf3177d1b 100644 --- a/addons/events/src/manager.js +++ b/addons/events/src/manager.js @@ -6,9 +6,11 @@ import { ADDON_ID, PANEL_ID } from './constants'; export function register() { addons.register(ADDON_ID, () => { + const channel = addons.getChannel(); addons.addPanel(PANEL_ID, { title: 'Events', - render: () => , + // eslint-disable-next-line react/prop-types + render: ({ active }) => , }); }); } diff --git a/addons/graphql/package.json b/addons/graphql/package.json index e6429566aecb..c3f3fa75360b 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "storybook" diff --git a/addons/info/README.md b/addons/info/README.md index 4e0054337555..1c7e29f7bccd 100644 --- a/addons/info/README.md +++ b/addons/info/README.md @@ -25,140 +25,234 @@ npm i -D @storybook/addon-info ``` ## Basic usage +Then, add `withInfo` as a decarator to your book of stories. +It is possible to add `info` by default to all or a subsection of stories by using a global or story decorator. -Then wrap your story with the `withInfo`, which is a function that takes either -documentation text or an options object: +It is important to declare this decorator as **the first decorator**, otherwise it won't work well. ```js -import { withInfo } from '@storybook/addon-info'; - +addDecorator(withInfo); // Globally in your .storybook/config.js. +``` +or +```js storiesOf('Component', module) - .add('simple info', - withInfo(` - description or documentation about my component, supports markdown + .addDecorator(withInfo) // At your stories directly. + .add(...); +``` - ~~~js - - ~~~ +Then, you can use the `info` parameter to either pass certain options or specific documentation text to your stories. +A complete list of possible configurations can be found at [in a later section](#setting-global-options). +This can be done per book of stories: - `)(() => - Click the "?" mark at top-right to view the info. - ) - ) -``` +```js +import { storiesOf } from '@storybook/react'; -## Usage with options +import Component from './Component'; -`withInfo` can also take an [options object](#global-options) in case you want to configure how -the info panel looks on a per-story basis: +storiesOf('Component', module) + .addParameters({ + info: { + // Your settings + } + }) + .add('with some emoji', () => ); +``` +...or for each story individually: ```js -import { withInfo } from '@storybook/addon-info'; +import { storiesOf } from '@storybook/react'; + +import Component from './Component'; storiesOf('Component', module) - .add('simple info', - withInfo({ - styles: { - header: { - h1: { - color: 'red' - } - } - }, - text: 'String or React Element with docs about my component', // Warning! This option's name will be likely renamed to "summary" in 3.3 release. Follow this PR #1501 for details - // other possible options see in Global options section below - })(() => - Click the "?" mark at top-right to view the info. - ) + .add( + 'with some emoji', + () => , + { info : { inline: false, header: false } } // Make your component render inline with the additional info ) + .add( + 'with no emoji', + () => , + { info: '☹️ no emojis' } // Add additional info text directly + ); ``` -The `styles` prop can also accept a function. The default stylesheet is passed as argument: +...or even together: ```js -import { withInfo } from '@storybook/addon-info'; +import { storiesOf } from '@storybook/react'; + +import Component from './Component'; storiesOf('Component', module) - .add('custom info styles using a function', - withInfo({ - styles: stylesheet => ({ - ...stylesheet, + .addParameters({ + info: { // Make a default for all stories in this book, + inline: true, // where the components are inlined + styles: { header: { - ...stylesheet.header, h1: { - ...stylesheet.header.h1, - color: 'red' + color: 'red' // and the headers of the sections are red. } } - }) - })(() => - Click the "?" mark at top-right to view the info. - ) - ) + }, + } + }) + .add( + 'green version', + () => , + { + info: { + styles: stylesheet => ({ // Setting the style with a function + ...stylesheet, + header: { + ...stylesheet.header, + h1: { + ...stylesheet.header.h1, + color: 'green' // Still inlined but with green headers! + } + } + }) + } + }) + .add( + 'something else', + () => , + { + info: "This story has additional text added to the info!" // Still inlined and with red headers! + } + ); ``` -## Usage as decorator +It is also possible to disable the `info` addon entirely. +Depending on the scope at which you want to disable the addon, pass the following parameters object either to an individual story or to an `addParameters` call. -It is possible to add infos by default to all components by using a global or story decorator. The drawback is you won't be able to display a distinct info message per story. +``` +{ + info: { + disable: true + } +} +``` + +## Markdown +The `info` addon also supports markdown. +To use markdown as additional textual documentation for your stories, either pass it directly as a String to the `info` parameters, or use the `text` option. -It is important to declare this decorator as **the first decorator**, otherwise it won't work well. ```js -addDecorator((story, context) => withInfo('common info')(story)(context)); +storiesOf('Button', module) + .add( + 'Button Component', + () => + ~~~ + ` + } + } + ); ``` -## Global options +## Setting Global Options -To configure default options for all usage of the info option, use `setDefaults` in `.storybook/config.js`: +To configure default options for all usage of the info addon, pass a option object along with the decorator in `.storybook/config.js`. ```js // config.js -import { setDefaults } from '@storybook/addon-info'; +import { withInfo } from '@storybook/addon-info'; -// addon-info -setDefaults({ - header: false, // Toggles display of header with component name and description -}); +addDecorator(withInfo({ + header: false, // Global configuration for the info addon across all of your stories. +})); ``` +Configuration parameters can be set at 3 different locations: passed as default options along the `addDecorator` call, passed as an object of parameters to a book of stories to the `addParameters` call, and passed as direct parameters to each individual story. +In order, all of them will be combined together, with a later call overriding the previous set configurations on a per-key basis. + ## Options and Defaults ```js { - header: false, // Toggles display of header with component name and description - inline: true, // Displays info inline vs click button to view - source: true, // Displays the source of story Component - propTables: [/* Components used in story */], // displays Prop Tables with these components - propTablesExclude: [], // Exclude Components from being shown in Prop Tables section. Accepts an array of component classes or functions. - styles: {}, // Overrides styles of addon. The object should follow this shape: https://github.com/storybooks/storybook/blob/master/addons/info/src/components/Story.js#L19. This prop can also accept a function which has the default stylesheet passed as an argument. - components: {}, // Overrides components used to display markdown - maxPropsIntoLine: 1, // Max props to display per line in source code - maxPropObjectKeys: 10, // Displays the first 10 characters of the prop name - maxPropArrayLength: 10, // Displays the first 10 items in the default prop array - maxPropStringLength: 100, // Displays the first 100 characters in the default prop string, - TableComponent: props => {}, // Override the component used to render the props table - excludedPropTypes: [], // Will exclude any respective properties whose name is included in array + /** + * Text to display with storybook component + */ + text?: string; + /** + * Displays info inline vs click button to view + * @default false + */ + inline: boolean, + /** + * Toggles display of header with component name and description + * @default true + */ + header: boolean, + /** + * Displays the source of story Component + * @default true + */ + source: boolean, + /** + * Components used in story + * Displays Prop Tables with these components + * @default [] + */ + propTables: Array, + /** + * Exclude Components from being shown in Prop Tables section + * Accepts an array of component classes or functions + * @default [] + */ + propTablesExclude: Array, + /** + * Overrides styles of addon. The object should follow this shape: + * https://github.com/storybooks/storybook/blob/master/addons/info/src/components/Story.js#L19. + * This prop can also accept a function which has the default stylesheet passed as an argument + */ + styles: Object | Function, + /** + * Overrides components used to display markdown + * @default {} + */ + components: { [key: string]: React.ComponentType }, + /** + * Max props to display per line in source code + * @default 3 + */ + maxPropsIntoLine: number, + /** + * Displays the first 10 characters of the prop name + * @default 3 + */ + maxPropObjectKeys: number, + /** + * Displays the first 10 items in the default prop array + * @default 3 + */ + maxPropArrayLength: number, + /** + * Displays the first 100 characters in the default prop string + * @default 50 + */ + maxPropStringLength: number, + /** + * Override the component used to render the props table + * @default PropTable + */ + TableComponent: React.ComponentType, + /** + * Will exclude any respective properties whose name is included in array + * @default [] + */ + excludedPropTypes: Array, } ``` -## Customizing defaults - -To customize your defaults: - -```js -// config.js -import { setDefaults } from '@storybook/addon-info'; - -// addon-info -setDefaults({ - inline: true, - maxPropsIntoLine: 1, - maxPropObjectKeys: 10, - maxPropArrayLength: 10, - maxPropStringLength: 100, -}); -``` - ### Rendering a Custom Table The `TableComponent` option allows you to define how the prop table should be rendered. Your component will be rendered with the following props. diff --git a/addons/info/package.json b/addons/info/package.json index b5cf32d00165..978839683462 100644 --- a/addons/info/package.json +++ b/addons/info/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-info", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "A Storybook addon to show additional information for your stories.", "repository": { "type": "git", @@ -13,18 +13,19 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/client-logger": "4.0.0-alpha.8", - "@storybook/components": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/client-logger": "4.0.0-alpha.12", + "@storybook/components": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", "core-js": "2.5.7", - "emotion": "^9.1.3", "global": "^4.3.2", "marksy": "^6.0.3", "nested-object-assign": "^1.0.1", "prop-types": "^15.6.1", "react-addons-create-fragment": "^15.5.3", "react-emotion": "^9.1.3", - "react-lifecycles-compat": "^3.0.4" + "react-lifecycles-compat": "^3.0.4", + "util-deprecate": "^1.0.2" }, "devDependencies": { "react-test-renderer": "^16.4.0" diff --git a/addons/info/src/__snapshots__/index.test.js.snap b/addons/info/src/__snapshots__/index.test.js.snap index b108af2c028d..e935cd48a7a3 100644 --- a/addons/info/src/__snapshots__/index.test.js.snap +++ b/addons/info/src/__snapshots__/index.test.js.snap @@ -55,7 +55,7 @@ exports[`addon Info should render and external markdown 1`] = ` transform: translateY(-100%) translateY(-6px); } - + and external markdown 1`] = ` } } > - func() + func } @@ -1299,7 +1299,7 @@ exports[`addon Info should render and external markdown 1`] = ` - + `; exports[`addon Info should render and markdown 1`] = ` @@ -1357,7 +1357,7 @@ exports[`addon Info should render and markdown 1`] = ` transform: translateY(-100%) translateY(-6px); } - + - func() + func } @@ -2694,5 +2694,5 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)" - + `; diff --git a/addons/info/src/components/PropVal.js b/addons/info/src/components/PropVal.js index 18eda91120ce..d95e4fdd581d 100644 --- a/addons/info/src/components/PropVal.js +++ b/addons/info/src/components/PropVal.js @@ -219,7 +219,7 @@ function PropVal(props) { /> ); } else if (typeof val === 'function') { - content = {val.name ? `${val.name}()` : 'anonymous()'}; + content = {val.name || 'anonymous'}; } else if (!val) { content = {`${val}`}; } else if (typeof val !== 'object') { diff --git a/addons/info/src/index.js b/addons/info/src/index.js index 9ef8a45814c3..3813949ab153 100644 --- a/addons/info/src/index.js +++ b/addons/info/src/index.js @@ -1,5 +1,7 @@ import React from 'react'; import nestedObjectAssign from 'nested-object-assign'; +import deprecate from 'util-deprecate'; +import { makeDecorator } from '@storybook/addons'; import { logger } from '@storybook/client-logger'; import Story from './components/Story'; import PropTable from './components/PropTable'; @@ -82,13 +84,23 @@ function addInfo(storyFn, context, infoOptions) { return {storyFn(context)}; } -export const withInfo = textOrOptions => { - const options = typeof textOrOptions === 'string' ? { text: textOrOptions } : textOrOptions; - return storyFn => context => addInfo(storyFn, context, options); -}; +export const withInfo = makeDecorator({ + name: 'withInfo', + parameterName: 'info', + wrapper: (getStory, context, { options, parameters }) => { + const storyOptions = parameters || options; + const infoOptions = typeof storyOptions === 'string' ? { text: storyOptions } : storyOptions; + const mergedOptions = + typeof infoOptions === 'string' ? infoOptions : { ...options, ...infoOptions }; + return addInfo(getStory, context, mergedOptions); + }, +}); export { Story }; export function setDefaults(newDefaults) { - return Object.assign(defaultOptions, newDefaults); + return deprecate( + () => Object.assign(defaultOptions, newDefaults), + 'setDefaults is deprecated. Instead, you can pass options into withInfo(options) directly, or use the info parameter.' + )(); } diff --git a/addons/jest/package.json b/addons/jest/package.json index cf92e0ac3b15..b1cea1fa17f0 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -25,10 +25,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/components": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/components": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", - "emotion": "^9.1.3", "global": "^4.3.2", "prop-types": "^15.6.1", "react-emotion": "^9.1.3", diff --git a/addons/jest/src/components/Panel.js b/addons/jest/src/components/Panel.js index f856bc54ad97..2512aefb450d 100644 --- a/addons/jest/src/components/Panel.js +++ b/addons/jest/src/components/Panel.js @@ -2,8 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled from 'react-emotion'; -import { baseFonts } from '@storybook/components'; - import Indicator from './Indicator'; import Result, { FailedResult } from './Result'; import provideJestResult from '../hoc/provideJestResult'; @@ -25,7 +23,6 @@ const Item = styled('li')({ const NoTests = styled('div')({ padding: '10px 20px', flex: 1, - ...baseFonts, }); const FileTitle = styled('h2')({ @@ -64,8 +61,8 @@ const SuiteTotals = styled(({ successNumber, failedNumber, result, className }) const SuiteProgress = styled(({ successNumber, result, className }) => (
- - {`${successNumber / result.assertionResults.length * 100}%`} + + {`${(successNumber / result.assertionResults.length) * 100}%`}
))(() => ({ @@ -139,7 +136,6 @@ const Content = styled(({ tests, className }) => ( ))({ padding: '10px 20px', flex: '1 1 0%', - ...baseFonts, }); const Panel = ({ tests }) => diff --git a/addons/jest/src/hoc/provideJestResult.js b/addons/jest/src/hoc/provideJestResult.js index aec752bc7e0a..a72169a21ee2 100644 --- a/addons/jest/src/hoc/provideJestResult.js +++ b/addons/jest/src/hoc/provideJestResult.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const provideTests = Component => { +const provideTests = Component => class TestProvider extends React.Component { static propTypes = { channel: PropTypes.shape({ @@ -11,21 +11,22 @@ const provideTests = Component => { api: PropTypes.shape({ onStory: PropTypes.func, }).isRequired, + active: PropTypes.bool, + }; + static defaultProps = { + active: true, }; - constructor(props) { - super(props); - - this.state = {}; - this.onAddTests = this.onAddTests.bind(this); - } + state = {}; componentDidMount() { - this.stopListeningOnStory = this.props.api.onStory(() => { + const { channel, api } = this.props; + + this.stopListeningOnStory = api.onStory(() => { this.onAddTests({}); }); - this.props.channel.on('storybook/tests/add_tests', this.onAddTests); + channel.on('storybook/tests/add_tests', this.onAddTests); } componentWillUnmount() { @@ -35,16 +36,14 @@ const provideTests = Component => { this.props.channel.removeListener('storybook/tests/add_tests', this.onAddTests); } - onAddTests({ kind, storyName, tests }) { + onAddTests = ({ kind, storyName, tests }) => { this.setState({ kind, storyName, tests }); - } + }; render() { - return ; + const { active } = this.props; + return active ? : null; } - } - - return TestProvider; -}; + }; export default provideTests; diff --git a/addons/jest/src/register.js b/addons/jest/src/register.js index 66639bb3b402..7300e64299b6 100644 --- a/addons/jest/src/register.js +++ b/addons/jest/src/register.js @@ -4,11 +4,11 @@ import addons from '@storybook/addons'; import PanelTitle from './components/PanelTitle'; import Panel from './components/Panel'; -// Register the addon with a unique name. addons.register('storybook/tests', api => { - // Also need to set a unique name to the panel. + const channel = addons.getChannel(); addons.addPanel('storybook/tests/panel', { title: , - render: () => , + // eslint-disable-next-line react/prop-types + render: ({ active }) => , }); }); diff --git a/addons/knobs/package.json b/addons/knobs/package.json index 7b0ccfaa473d..3ec35a4b9997 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-knobs", - "version": "4.0.0-alpha.8", + "version": "4.0.0-alpha.12", "description": "Storybook Addon Prop Editor Component", "repository": { "type": "git", @@ -13,22 +13,19 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "4.0.0-alpha.8", - "@storybook/components": "4.0.0-alpha.8", - "@storybook/core-events": "4.0.0-alpha.8", + "@storybook/addons": "4.0.0-alpha.12", + "@storybook/components": "4.0.0-alpha.12", + "@storybook/core-events": "4.0.0-alpha.12", "babel-runtime": "^6.26.0", - "deep-equal": "^1.0.1", + "copy-to-clipboard": "^3.0.8", "escape-html": "^1.0.3", + "fast-deep-equal": "^2.0.1", "global": "^4.3.2", - "insert-css": "^2.0.0", - "lodash.debounce": "^4.0.8", - "moment": "^2.22.1", "prop-types": "^15.6.1", + "qs": "^6.5.2", "react-color": "^2.14.1", "react-datetime": "^2.14.0", "react-emotion": "^9.1.3", - "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^6.1.0", "util-deprecate": "^1.0.2" }, "devDependencies": { diff --git a/addons/knobs/src/KnobManager.js b/addons/knobs/src/KnobManager.js index 9ae2ca937a76..bd183225a50f 100644 --- a/addons/knobs/src/KnobManager.js +++ b/addons/knobs/src/KnobManager.js @@ -1,5 +1,5 @@ /* eslint no-underscore-dangle: 0 */ -import deepEqual from 'deep-equal'; +import deepEqual from 'fast-deep-equal'; import escape from 'escape-html'; import KnobStore from './KnobStore'; diff --git a/addons/knobs/src/KnobStore.js b/addons/knobs/src/KnobStore.js index 6474abfb79ee..18b5125245a0 100644 --- a/addons/knobs/src/KnobStore.js +++ b/addons/knobs/src/KnobStore.js @@ -1,3 +1,6 @@ +const callArg = fn => fn(); +const callAll = fns => fns.forEach(callArg); + export default class KnobStore { constructor() { this.store = {}; @@ -12,7 +15,12 @@ export default class KnobStore { this.store[key] = value; this.store[key].used = true; this.store[key].groupId = value.groupId; - this.callbacks.forEach(cb => cb()); + + // debounce the execution of the callbacks for 50 milliseconds + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(callAll, 50, this.callbacks); } get(key) { diff --git a/addons/knobs/src/components/GroupTabs.js b/addons/knobs/src/components/GroupTabs.js deleted file mode 100644 index bed567cf6c99..000000000000 --- a/addons/knobs/src/components/GroupTabs.js +++ /dev/null @@ -1,110 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import styled from 'react-emotion'; - -import { Placeholder } from '@storybook/components'; - -const Wrapper = styled('div')({ - flex: '1 1 auto', - display: 'flex', - flexDirection: 'column', - background: 'white', - borderRadius: 4, - border: 'solid 1px rgb(236, 236, 236)', - width: '100%', -}); - -const Bar = styled('div')({ - display: 'flex', - flexWrap: 'wrap', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - borderBottom: 'solid 1px #eaeaea', -}); - -const Content = styled('div')({ - flex: '1 1 0', - display: 'flex', - overflow: 'auto', -}); - -const Tab = styled('button')(({ active }) => ({ - fontSize: 11, - letterSpacing: '1px', - padding: '10px 15px', - textTransform: 'uppercase', - transition: 'opacity 0.3s', - opacity: active ? 1 : 0.5, - maxHeight: 60, - overflow: 'hidden', - cursor: 'pointer', - background: 'transparent', - border: 'none', -})); - -class GroupTabs extends Component { - renderTab(name, group) { - const onClick = e => { - e.preventDefault(); - this.props.onGroupSelect(name); - }; - - let { title } = group; - if (typeof title === 'function') { - title = title(); - } - - return ( - - {title} - - ); - } - - render() { - const entries = this.props.groups ? Object.entries(this.props.groups) : null; - - return entries && entries.length ? ( - - {entries.map(([key, value]) => this.renderTab(key, value))} - - {entries.map(([key, value]) => { - const groupStyle = { display: 'none' }; - if (key === this.props.selectedGroup) { - Object.assign(groupStyle, { flex: 1, display: 'flex' }); - } - return ( -
- {value.render()} -
- ); - })} -
-
- ) : ( - no groups available - ); - } -} - -GroupTabs.defaultProps = { - groups: {}, - onGroupSelect: () => {}, - selectedGroup: null, -}; - -GroupTabs.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - groups: PropTypes.object, - onGroupSelect: PropTypes.func, - selectedGroup: PropTypes.string, -}; - -export default GroupTabs; diff --git a/addons/knobs/src/components/Panel.js b/addons/knobs/src/components/Panel.js index 509a250849f3..c74464553233 100644 --- a/addons/knobs/src/components/Panel.js +++ b/addons/knobs/src/components/Panel.js @@ -1,12 +1,14 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; - +import qs from 'qs'; +import { document } from 'global'; import styled from 'react-emotion'; +import copy from 'copy-to-clipboard'; + +import { Placeholder, TabWrapper, TabsState, ActionBar, ActionButton } from '@storybook/components'; -import { Placeholder } from '@storybook/components'; -import GroupTabs from './GroupTabs'; -import PropForm from './PropForm'; import Types from './types'; +import PropForm from './PropForm'; const getTimestamp = () => +new Date(); @@ -16,49 +18,21 @@ const PanelWrapper = styled('div')({ width: '100%', }); -const PanelInner = styled('div')({ - padding: '5px', - width: 'auto', - position: 'relative', -}); - -const ResetButton = styled('button')({ - position: 'absolute', - bottom: 11, - right: 10, - border: 'none', - borderTop: 'solid 1px rgba(0, 0, 0, 0.2)', - borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)', - background: 'rgba(255, 255, 255, 0.5)', - padding: '5px 10px', - borderRadius: '4px 0 0 0', - color: 'rgba(0, 0, 0, 0.5)', - outline: 'none', -}); - -export default class Panel extends React.Component { +export default class Panel extends PureComponent { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - this.setKnobs = this.setKnobs.bind(this); - this.reset = this.reset.bind(this); - this.setOptions = this.setOptions.bind(this); - this.onGroupSelect = this.onGroupSelect.bind(this); - - this.state = { knobs: {}, groupId: DEFAULT_GROUP_ID }; + this.state = { knobs: {} }; this.options = {}; this.lastEdit = getTimestamp(); this.loadedFromUrl = false; } - componentDidMount() { this.props.channel.on('addon:knobs:setKnobs', this.setKnobs); this.props.channel.on('addon:knobs:setOptions', this.setOptions); this.stopListeningOnStory = this.props.api.onStory(() => { - this.setState({ knobs: [], groupId: DEFAULT_GROUP_ID }); + this.setState({ knobs: {} }); this.props.channel.emit('addon:knobs:reset'); }); } @@ -68,15 +42,11 @@ export default class Panel extends React.Component { this.stopListeningOnStory(); } - onGroupSelect(name) { - this.setState({ groupId: name }); - } - - setOptions(options = { timestamps: false }) { + setOptions = (options = { timestamps: false }) => { this.options = options; - } + }; - setKnobs({ knobs, timestamp }) { + setKnobs = ({ knobs, timestamp }) => { const queryParams = {}; const { api, channel } = this.props; @@ -86,7 +56,6 @@ export default class Panel extends React.Component { // For the first time, get values from the URL and set them. if (!this.loadedFromUrl) { const urlValue = api.getQueryParam(`knob-${name}`); - if (urlValue !== undefined) { // If the knob value present in url knob.value = Types[knob.type].deserialize(urlValue); @@ -94,48 +63,63 @@ export default class Panel extends React.Component { } } - queryParams[`knob-${name}`] = Types[knob.type].serialize(knob.value); + // set all knobsquery params to be deleted from URL + queryParams[`knob-${name}`] = null; }); - this.loadedFromUrl = true; + api.setQueryParams(queryParams); this.setState({ knobs }); + + this.loadedFromUrl = true; } - } + }; - reset() { + reset = () => { this.props.channel.emit('addon:knobs:reset'); - } + }; + + copy = () => { + const { location } = document; + const query = qs.parse(location.search.replace('?', '')); + const { knobs } = this.state; - emitChange(changedKnob) { + Object.entries(knobs).forEach(([name, knob]) => { + query[`knob-${name}`] = Types[knob.type].serialize(knob.value); + }); + + copy(`${location.origin + location.pathname}?${qs.stringify(query)}`); + + // TODO: show some notification of this + }; + + emitChange = changedKnob => { this.props.channel.emit('addon:knobs:knobChange', changedKnob); - } + }; - handleChange(changedKnob) { + handleChange = changedKnob => { this.lastEdit = getTimestamp(); - const { api } = this.props; const { knobs } = this.state; - const { name, type, value } = changedKnob; + const { name } = changedKnob; const newKnobs = { ...knobs }; newKnobs[name] = { ...newKnobs[name], ...changedKnob, }; - this.setState({ knobs: newKnobs }); - - const queryParams = {}; - queryParams[`knob-${name}`] = Types[type].serialize(value); - - api.setQueryParams(queryParams); this.setState({ knobs: newKnobs }, this.emitChange(changedKnob)); - } + }; - handleClick(knob) { + handleClick = knob => { this.props.channel.emit('addon:knobs:knobClick', knob); - } + }; render() { - const { knobs, groupId } = this.state; + const { knobs } = this.state; + const { active } = this.props; + + if (!active) { + return null; + } const groups = {}; const groupIds = []; @@ -146,20 +130,23 @@ export default class Panel extends React.Component { const knobKeyGroupId = knobs[key].groupId; groupIds.push(knobKeyGroupId); groups[knobKeyGroupId] = { - render: () =>
{knobKeyGroupId}
, + render: ({ active: groupActive, selected }) => ( + + knob.groupId === knobKeyGroupId)} + onFieldChange={this.handleChange} + onFieldClick={this.handleClick} + /> + + ), title: knobKeyGroupId, }; }); - if (groupIds.length > 0) { - groups[DEFAULT_GROUP_ID] = { - render: () =>
{DEFAULT_GROUP_ID}
, - title: DEFAULT_GROUP_ID, - }; - if (groupId !== DEFAULT_GROUP_ID) { - knobsArray = knobsArray.filter(key => knobs[key].groupId === groupId); - } - } + groups[DEFAULT_GROUP_ID] = { + render: () => null, + title: DEFAULT_GROUP_ID, + }; knobsArray = knobsArray.map(key => knobs[key]); @@ -169,33 +156,38 @@ export default class Panel extends React.Component { return ( - {groupIds.length > 0 && ( - - )} - + {groupIds.length > 0 ? ( + + {Object.entries(groups).map(([k, v]) => ( +
+ {v.render} +
+ ))} +
+ ) : ( -
- RESET + )} + + COPY + RESET +
); } } Panel.propTypes = { + active: PropTypes.bool.isRequired, + onReset: PropTypes.object, // eslint-disable-line channel: PropTypes.shape({ emit: PropTypes.func, on: PropTypes.func, removeListener: PropTypes.func, }).isRequired, - onReset: PropTypes.object, // eslint-disable-line api: PropTypes.shape({ onStory: PropTypes.func, getQueryParam: PropTypes.func, diff --git a/addons/knobs/src/components/PropField.js b/addons/knobs/src/components/PropField.js deleted file mode 100644 index 888d5a9e75b4..000000000000 --- a/addons/knobs/src/components/PropField.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import styled from 'react-emotion'; - -import TypeMap from './types'; - -const InvalidType = () => Invalid Type; - -const Field = styled('div')({ - display: 'table-row', - padding: '5px', -}); -const Label = styled('label')({ - display: 'table-cell', - boxSizing: 'border-box', - verticalAlign: 'top', - paddingRight: 5, - paddingTop: 5, - textAlign: 'right', - width: 80, - fontSize: 12, - color: 'rgb(68, 68, 68)', - fontWeight: 600, -}); - -export default function PropField({ onChange, onClick, knob }) { - const InputType = TypeMap[knob.type] || InvalidType; - return ( - - - - - ); -} - -PropField.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.any, - }).isRequired, - onChange: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, -}; diff --git a/addons/knobs/src/components/PropForm.js b/addons/knobs/src/components/PropForm.js index 821b2410e332..0b07504a8296 100644 --- a/addons/knobs/src/components/PropForm.js +++ b/addons/knobs/src/components/PropForm.js @@ -1,19 +1,19 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import styled from 'react-emotion'; -import PropField from './PropField'; +import { Field } from '@storybook/components'; +import TypeMap from './types'; const Form = styled('form')({ - display: 'table', boxSizing: 'border-box', width: '100%', - borderCollapse: 'separate', - borderSpacing: '5px', }); -export default class propForm extends React.Component { +const InvalidType = () => Invalid Type; + +export default class PropForm extends Component { makeChangeHandler(name, type) { return value => { const change = { name, type, value }; @@ -28,16 +28,12 @@ export default class propForm extends React.Component {
{knobs.map(knob => { const changeHandler = this.makeChangeHandler(knob.name, knob.type); + const InputType = TypeMap[knob.type] || InvalidType; + return ( - + + + ); })} @@ -45,19 +41,15 @@ export default class propForm extends React.Component { } } -propForm.displayName = 'propForm'; - -propForm.defaultProps = { - knobs: [], -}; +PropForm.displayName = 'PropForm'; -propForm.propTypes = { +PropForm.propTypes = { knobs: PropTypes.arrayOf( PropTypes.shape({ name: PropTypes.string, value: PropTypes.any, }) - ), + ).isRequired, onFieldChange: PropTypes.func.isRequired, onFieldClick: PropTypes.func.isRequired, }; diff --git a/addons/knobs/src/components/__tests__/GroupTabs.js b/addons/knobs/src/components/__tests__/GroupTabs.js deleted file mode 100644 index aa978bedb964..000000000000 --- a/addons/knobs/src/components/__tests__/GroupTabs.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import GroupTabs from '../GroupTabs'; - -describe('GroupTabs', () => { - test('should render only the selected group with display set other than "none"', () => { - const groups = { - test1: { - render() { - return
TEST 1
; - }, - }, - test2: { - render() { - return
TEST 2
; - }, - }, - }; - - const onGroupSelect = () => 'onGroupSelect'; - - const wrapper = shallow( - - ); - - expect(wrapper.find('#test1').parent()).toHaveStyle('display', 'none'); - expect(wrapper.find('#test2').parent()).not.toHaveStyle('display', 'none'); - }); - - test('should set onGroupSelected as onClick handlers of tabs', () => { - const groups = { - test1: { - title: 'test 1', - render() { - return
TEST 1
; - }, - }, - }; - const onGroupSelect = jest.fn(); - const preventDefault = jest.fn(); - const wrapper = shallow( - - ); - wrapper - .find('Styled(button)') - .dive() - .simulate('click', { preventDefault }); - - expect(onGroupSelect).toHaveBeenCalled(); - expect(preventDefault).toHaveBeenCalled(); - }); - - describe('when no groups are given', () => { - test('should render "no groups available"', () => { - const groups = {}; - const onGroupSelect = () => 'onGroupSelect'; - const wrapper = shallow(); - - expect(wrapper.contains('no groups available')).toBe(true); - }); - }); -}); diff --git a/addons/knobs/src/components/__tests__/Panel.js b/addons/knobs/src/components/__tests__/Panel.js index ef0389705b6f..dc9b17d163f2 100644 --- a/addons/knobs/src/components/__tests__/Panel.js +++ b/addons/knobs/src/components/__tests__/Panel.js @@ -6,14 +6,14 @@ describe('Panel', () => { it('should subscribe to setKnobs event of channel', () => { const testChannel = { on: jest.fn() }; const testApi = { onStory: jest.fn() }; - shallow(); + shallow(); expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function)); }); it('should subscribe to onStory event', () => { const testChannel = { on: jest.fn() }; const testApi = { onStory: jest.fn() }; - shallow(); + shallow(); expect(testApi.onStory).toHaveBeenCalled(); expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function)); @@ -41,7 +41,7 @@ describe('Panel', () => { onStory: jest.fn(), }; - shallow(); + shallow(); const setKnobsHandler = handlers['addon:knobs:setKnobs']; const knobs = { @@ -67,7 +67,7 @@ describe('Panel', () => { expect(testChannel.emit).toHaveBeenCalledWith(e, knobFromUrl); }); - it('should set query params when url params are already read', () => { + it('should remove query params when url params are already read', () => { const handlers = {}; const testChannel = { @@ -88,7 +88,7 @@ describe('Panel', () => { onStory: jest.fn(), }; - const wrapper = shallow(); + const wrapper = shallow(); const setKnobsHandler = handlers['addon:knobs:setKnobs']; const knobs = { @@ -109,8 +109,8 @@ describe('Panel', () => { setKnobsHandler({ knobs, timestamp: +new Date() }); const knobFromStory = { - 'knob-foo': knobs.foo.value, - 'knob-baz': knobs.baz.value, + 'knob-foo': null, + 'knob-baz': null, }; expect(testApi.setQueryParams).toHaveBeenCalledWith(knobFromStory); @@ -130,7 +130,7 @@ describe('Panel', () => { onStory: jest.fn(), }; - const wrapper = shallow(); + const wrapper = shallow(); const testChangedKnob = { name: 'foo', @@ -140,8 +140,8 @@ describe('Panel', () => { wrapper.instance().handleChange(testChangedKnob); expect(testChannel.emit).toHaveBeenCalledWith('addon:knobs:knobChange', testChangedKnob); - const paramsChange = { 'knob-foo': 'changed text' }; - expect(testApi.setQueryParams).toHaveBeenCalledWith(paramsChange); + // const paramsChange = { 'knob-foo': 'changed text' }; + // expect(testApi.setQueryParams).toHaveBeenCalledWith(paramsChange); }); }); }); diff --git a/addons/knobs/src/components/types/Array.js b/addons/knobs/src/components/types/Array.js index 6ccfedb1ae20..4367df5e49a3 100644 --- a/addons/knobs/src/components/types/Array.js +++ b/addons/knobs/src/components/types/Array.js @@ -1,24 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -import styled from 'react-emotion'; -import Textarea from 'react-textarea-autosize'; -import debounce from 'lodash.debounce'; - -const StyledTextarea = styled(Textarea)({ - display: 'table-cell', - boxSizing: 'border-box', - verticalAlign: 'middle', - height: '26px', - width: '100%', - maxWidth: '100%', - outline: 'none', - border: '1px solid #f7f4f4', - borderRadius: 2, - fontSize: 11, - padding: '5px', - color: '#555', -}); +import { Textarea } from '@storybook/components'; function formatArray(value, separator) { if (value === '') { @@ -28,18 +11,11 @@ function formatArray(value, separator) { } class ArrayType extends React.Component { - constructor(props, context) { - super(props, context); - - this.state = { - value: props.knob.value.join(props.knob.separator), - }; - - this.onChange = debounce(this.props.onChange, 200); - } - - componentWillUnmount() { - this.onChange.cancel(); + static getDerivedStateFromProps(props, state) { + if (!state || props.knob.value !== state.value) { + return { value: props.knob.value }; + } + return null; } handleChange = e => { @@ -48,14 +24,14 @@ class ArrayType extends React.Component { const newVal = formatArray(value, knob.separator); this.setState({ value }); - this.onChange(newVal); + this.props.onChange(newVal); }; render() { const { knob } = this.props; const { value } = this.state; - return ; + return , + , + , +] +`; + +exports[`Storyshots Components|Form/Textarea sizes 1`] = ` +Array [ + , + , + , +] +`; + +exports[`Storyshots Components|Form/Textarea validations 1`] = ` +Array [ + , + , + , + , +] +`; + +exports[`Storyshots Components|Form/Textarea with knobs (controlled) 1`] = ` + +`; diff --git a/lib/components/src/form/button.js b/lib/components/src/form/button.js deleted file mode 100644 index 41812381b2c3..000000000000 --- a/lib/components/src/form/button.js +++ /dev/null @@ -1,26 +0,0 @@ -import styled from 'react-emotion'; -import { baseFonts } from '../theme'; - -export default styled('button')({ - ...baseFonts, - border: 'none', - boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.2)', - backgroundColor: 'rgb(255, 255, 255)', - padding: '4px 10px 7px', - borderRadius: 4, - cursor: 'pointer', - transition: 'box-shadow 0.15s ease-out', - ':hover': { - transition: 'background-color 0.15s ease-out', - boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.3)', - }, - ':focus': { - transition: 'background-color 0.15s ease-out', - outline: 'none', - boxShadow: '0 0 0 2px rgba(0, 0, 0, 0.3)', - }, - ':active': { - transition: 'none', - backgroundColor: 'rgb(247, 247, 247)', - }, -}); diff --git a/lib/components/src/form/button.stories.js b/lib/components/src/form/button.stories.js deleted file mode 100644 index caba82eab126..000000000000 --- a/lib/components/src/form/button.stories.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { withKnobs, text } from '@storybook/addon-knobs'; - -import Button from './button'; - -storiesOf('Components|Form/Button', module) - .addDecorator(withKnobs) - .add('with onclick', () => ); diff --git a/lib/components/src/form/field.js b/lib/components/src/form/field.js new file mode 100644 index 000000000000..c7730ff9a678 --- /dev/null +++ b/lib/components/src/form/field.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'react-emotion'; + +const Wrapper = styled('label')(({ theme }) => ({ + width: '100%', + boxSizing: 'border-box', + display: 'flex', + margin: '8px 0', + borderBottom: theme.mainBorder, + padding: '0 8px 8px 8px', + + '&:last-child': { + borderBottom: '0 none', + paddingBottom: 0, + }, +})); + +const Label = styled('span')({ + minWidth: 100, + minHeight: 32, + marginRight: 16, + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + lineHeight: '16px', +}); + +const Field = ({ label, children }) => ( + + {label ? ( + + ) : null} + {children} + +); +Field.propTypes = { + label: PropTypes.node, + children: PropTypes.node.isRequired, +}; +Field.defaultProps = { + label: undefined, +}; + +export default Field; diff --git a/lib/components/src/form/form.stories.js b/lib/components/src/form/form.stories.js new file mode 100644 index 000000000000..5c2e1bc9efb5 --- /dev/null +++ b/lib/components/src/form/form.stories.js @@ -0,0 +1,93 @@ +import React, { Fragment } from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { withKnobs, text } from '@storybook/addon-knobs'; + +import { Button, Select, Textarea } from './input'; + +storiesOf('Components|Form/Select', module) + .addDecorator(withKnobs) + .add('sizes', () => ( + + {['auto', 'flex', '100%'].map(size => ( + + ))} + + )) + .add('validations', () => ( + + {['error', 'warn', 'valid', null].map(valid => ( + + ))} + + )); +storiesOf('Components|Form/Button', module) + .addDecorator(withKnobs) + .add('with knobs', () => ( + + )) + .add('sizes', () => ( + + {['auto', 'flex', '100%'].map(size => ( + + ))} + + )) + .add('validations', () => ( + + {['error', 'warn', 'valid', null].map(valid => ( + + ))} + + )) + .add('alignment', () => ( + + {['end', 'center', 'start'].map(align => ( + + ))} + + )); + +storiesOf('Components|Form/Textarea', module) + .addDecorator(withKnobs) + .add('with knobs (controlled)', () => ( +