diff --git a/.storybook-dist/main.js b/.storybook-dist/main.js index 256fc83..a332065 100644 --- a/.storybook-dist/main.js +++ b/.storybook-dist/main.js @@ -1,14 +1,35 @@ +const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin') + module.exports = { addons: ['@storybook/addon-knobs/register', './dist/register'], - stories: ['../src/examples/*.stories.tsx'], + stories: ['../src/examples/*.stories.mdx', '../src/examples/*.stories.tsx'], webpackFinal: async (config) => { - config.module.rules.push({ - test: /\.(ts|tsx)$/, - loader: require.resolve('babel-loader'), - options: { - presets: [['react-app', { flow: false, typescript: true }]], + config.module.rules.push( + { + test: /\.(stories|story)\.mdx$/, + use: [ + { + loader: 'babel-loader', + options: { + plugins: ['@babel/plugin-transform-react-jsx'], + }, + }, + { + loader: '@mdx-js/loader', + options: { + compilers: [createCompiler({})], + }, + }, + ], + }, + { + test: /\.(ts|tsx)$/, + loader: require.resolve('babel-loader'), + options: { + presets: [['react-app', { flow: false, typescript: true }]], + }, }, - }) + ) config.resolve.extensions.push('.ts', '.tsx') diff --git a/README.md b/README.md index 7421514..7bacf75 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Storybook Addon Headless allows you to preview data from a headless CMS inside s Check out examples and detailed documentation: -- [https://storybook-addon-headless.netlify.com/](https://storybook-addon-headless.netlify.com/) +- [https://storybook-addon-headless.netlify.com/?path=/story/examples](https://storybook-addon-headless.netlify.com/?path=/story/examples) - [https://github.com/ArrayKnight/storybook-addon-headless/tree/master/src/examples](https://github.com/ArrayKnight/storybook-addon-headless/tree/master/src/examples) ## Getting Started @@ -52,9 +52,9 @@ export default { } ``` -You can find options documented as [HeadlessOptions](https://github.com/ArrayKnight/storybook-addon-headless/blob/master/src/types/options.ts) +You can find options documented as [HeadlessOptions](https://github.com/ArrayKnight/storybook-addon-headless/blob/master/src/types/options.ts) and on the [documentation site](https://storybook-addon-headless.netlify.com/?path=/story/options--page). -##### Options +##### [Options](https://storybook-addon-headless.netlify.com/?path=/story/options--page) ```js { @@ -67,7 +67,7 @@ You can find options documented as [HeadlessOptions](https://github.com/ArrayKni Under the covers, this addon uses Axios for Restful queries and Apollo Client for GraphQL queries. These configs are optional, though you'll likely want to use one or both. The configs will also be merged with the optional configs being passed through the parameters. -#### Parameterize +#### [Parameters](https://storybook-addon-headless.netlify.com/?path=/story/parameters--page) Parameters are added locally via: @@ -85,7 +85,7 @@ export default { } ``` -You can find parameters document as [HeadlessParameters](https://github.com/ArrayKnight/storybook-addon-headless/blob/master/src/types/parameters.ts) +You can find parameters document as [HeadlessParameters](https://github.com/ArrayKnight/storybook-addon-headless/blob/master/src/types/parameters.ts) and on the [documentation site](https://storybook-addon-headless.netlify.com/?path=/story/parameters--page). ```js { diff --git a/package.json b/package.json index dc630d0..773ed08 100644 --- a/package.json +++ b/package.json @@ -48,20 +48,22 @@ "@commitlint/config-conventional": "8.3.4", "@material-ui/core": "4.9.0", "@storybook/addon-actions": "5.3.9", + "@storybook/addon-docs": "5.3.9", "@storybook/addon-knobs": "5.3.9", "@storybook/react": "5.3.9", "@types/ajv-keywords": "3.4.0", "@types/react": "16.9.19", "babel-loader": "8.0.6", - "babel-preset-react-app": "9.1.0", + "babel-preset-react-app": "9.1.1", "husky": "4.2.1", - "lint-staged": "10.0.2", + "lint-staged": "10.0.7", "npm-check": "5.9.0", "npm-run-all": "4.1.5", "prettier": "1.19.1", - "rollup": "1.29.1", + "react-is": "16.12.0", + "rollup": "1.31.0", "rollup-plugin-typescript2": "0.25.3", - "semantic-release": "16.0.3", + "semantic-release": "17.0.2", "tslint": "6.0.0", "tslint-config-prettier": "1.18.0", "tslint-react": "4.2.0", diff --git a/src/components/Variable/Date.tsx b/src/components/Variable/Date.tsx index 10f5a08..8790035 100644 --- a/src/components/Variable/Date.tsx +++ b/src/components/Variable/Date.tsx @@ -7,7 +7,7 @@ import { Error, Row } from './styled' export interface Props { schema: DateTimeSchema - value: string + value: string | undefined error: string | null isValid: boolean onChange: (value: string) => void @@ -43,9 +43,9 @@ export const DateTimeInput = memo( return parseISO(val) } - function getter(val: string): string { + function getter(val: string | undefined): string { if (!val) { - return val + return '' } const date = getDate(val, true) diff --git a/src/components/Variable/Number.tsx b/src/components/Variable/Number.tsx index d7cec10..b530dd1 100644 --- a/src/components/Variable/Number.tsx +++ b/src/components/Variable/Number.tsx @@ -2,11 +2,12 @@ import { Form } from '@storybook/components' import React, { ChangeEvent, memo } from 'react' import { NumberSchema } from '../../types' +import { isUndefined } from '../../utilities' import { Error, Row } from './styled' export interface Props { schema: NumberSchema - value: number + value: number | undefined error: string | null isValid: boolean onChange: (value: number) => void @@ -23,7 +24,7 @@ export const NumberInput = memo( {!isValid && {error}} diff --git a/src/components/Variable/String.tsx b/src/components/Variable/String.tsx index 53bdc67..7a50e18 100644 --- a/src/components/Variable/String.tsx +++ b/src/components/Variable/String.tsx @@ -6,7 +6,7 @@ import { Error, Row } from './styled' export interface Props { schema: StringSchema - value: string + value: string | undefined error: string | null isValid: boolean onChange: (value: string) => void diff --git a/src/examples/intro.stories.mdx b/src/examples/intro.stories.mdx new file mode 100644 index 0000000..3505e16 --- /dev/null +++ b/src/examples/intro.stories.mdx @@ -0,0 +1,20 @@ +import { + Description, + DocsContainer, + DocsPage, + Meta, +} from '@storybook/addon-docs/blocks' + +import readme from '../../README.md' + + + +export default () => diff --git a/src/examples/options.stories.mdx b/src/examples/options.stories.mdx new file mode 100644 index 0000000..1a7d8ef --- /dev/null +++ b/src/examples/options.stories.mdx @@ -0,0 +1,27 @@ +import { DocsContainer, DocsPage, Meta } from '@storybook/addon-docs/blocks' + + + +# Options + +By utilizing options when setting up the Headless decorator, its possible to establish a base config that will keep individual story parameters simple. Global setup pieces like authentication headers should be done here too. + +## Restful + +A partial [Axios config](https://github.com/axios/axios#request-config) can be passed in as a base config. The most common use case would be to establish a `baseURL` so that all stories can have a simpler relative query. + +## GraphQL + +A partial [Apollo Boost config](https://www.apollographql.com/docs/react/get-started/#configuration-options) can be passed in as a base config. The most common use case would be to establish a `uri` so that all stories can simply define a query without the need for an additional config. + +## Theming + +If you're customizing the theme of your Storybook to match your company's branding, you may wish to switch up the JSON editor theme as well. The `jsonDark` and `jsonLight` options allow you to [pick a theme](https://github.com/mac-s-g/react-json-view#customizing-style) of your choosing. All other component styles should inherit theme from Storybook. diff --git a/src/examples/parameters.stories.mdx b/src/examples/parameters.stories.mdx new file mode 100644 index 0000000..9e9ccce --- /dev/null +++ b/src/examples/parameters.stories.mdx @@ -0,0 +1,207 @@ +import { DocsContainer, DocsPage, Meta } from '@storybook/addon-docs/blocks' + + + +# Parameters + +For each endpoint you need to establish add an entry to the parameters object. It's possible to do a mix of Restful and GraphQL queries as well as simple and advanced configs. + +Example: + +```js +import { gql } from 'apollo-boost' +import { pack } from 'storybook-addon-headless' + +{ + Foos: '/foos' // Simple Restful + Foo: { // Advanced Restful + query: '/foos/{id}', + // Variables, etc + }, + Bars: pack(gql``), // Simple GraphQL + Bar: { // Advanced GraphQL + query: pack(gql``), + // Variables, etc + } +} +``` + +## Config + +If a specific query needs to augment the base config, you can optionally pass a partial config to be merged with the config that may have been supplied in the options. See options about what configs are accepted. + +## Query [required] + +For Restful requests the query should be a string. Depending on the options passed, this might be an absolute or relative path. + +For GraphQL requests the query must be a `pack`ed GraphQL Tag DocumentNode. + +Example: + +```js +import { gql } from 'apollo-boost' +import { pack } from 'storybook-addon-headless' + +pack(gql` + { + entities { + id + } + } +`) +``` + +## Variable Types + +If your query requires variables/parameters, pass an object of variable schemas where the key is the variable name and the value is the schema. In order to define a variable type, a matching [Ajv](https://ajv.js.org/#validation-keywords) schema. + +Example: + +```js +{ + variables: { + foo: { + type: 'integer', + multipleOf: 3, + }, + }, +} +``` + +### Boolean + +```js +{ + type: 'boolean' +} +``` + +This schema will render a checkbox input. + +### Date + +```js +{ + type: 'string', + format: 'date', + // Optional additional rules +} +``` + +This schema will render a date input. There are optional [keywords](https://ajv.js.org/keywords.html#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum-proposed) for additional validation. + +### DateTime + +```js +{ + type: 'string', + format: 'date-time', + // Optional additional rules +} +``` + +This schema will render a date time input. Time entered by the user is in local timezone, but the value is converted to UTC. There are optional [keywords](https://ajv.js.org/keywords.html#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum-proposed) for additional validation. + +### Time + +```js +{ + type: 'string', + format: 'time', + // Optional additional rules +} +``` + +This schema will render a time input. Time entered by the user is in local timezone, but the value is converted to UTC. There are optional [keywords](https://ajv.js.org/keywords.html#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum-proposed) for additional validation. + +### Number + +```js +{ + type: 'number' | 'integer', + // Optional additional rules +} +``` + +This schema will render a number input. There are optional [keywords](https://ajv.js.org/keywords.html#keywords-for-numbers) for additional validation. + +### Select + +```js +{ + type: any, // Type should match the type of the values in enum + enum: any[], +} +``` + +This schema will render a select dropdown. If values are not basic types or if you want to differentiate the label from the value, you can use an object with `label` and `value` keys. + +Example: + +```js +{ + type: ['integer', 'null'], + enum: [ + 42, + { label: 'Seven', value: 7 }, + { label: 'None of the above', value: null }, + ] +} +``` + +### String + +```js +{ + type: 'string', + // Optional additional rules +} +``` + +This schema will render a text input. There are optional [keywords](https://ajv.js.org/keywords.html#keywords-for-strings) for additional validation. + +## Default Values + +To provide default values for any/all of your variables, pass an object of values where the key is the variable name and the value is the default. + +Example: + +```js +{ + defaults: { + foo: 3 + } +} +``` + +## Transforms + +To transform values before the query is sent, pass an object of values where the key is the variable name and the value is a function that accepts and returns a value. The output value will not be validated against the schema and can therefore be any value, it's up to you to pass something valid to the query. + +Example: + +```js +{ + transforms: { + foo: (value) => value * 10000 + } +} +``` + +## Other Features + +### autoFetchOnInit + +If you would like data to be fetched on story load, pass `autoFetchOnInit: true`. This also requires that the query variables (if present) have default values that are valid. + +### convertToFormData + +For Restful queries, if you're setting up a POST request, it might be necessary to pass data through as Multipart Form-data. In that case, pass `convertToFormData: true`.