diff --git a/.vscode/story-template.code-snippets b/.vscode/story-template.code-snippets index 19736c0479..3be39e8179 100644 --- a/.vscode/story-template.code-snippets +++ b/.vscode/story-template.code-snippets @@ -7,71 +7,70 @@ "// import { StoryTemplateName } from './OtherStoryFile.stories' // import stories for component compositions", "", "export default {", - " title: 'Components/ComponentName',", - " excludeStories: ['ComponentTemplateName'],", - " argTypes: {", - " booleanExample: {", - " control: { type: 'boolean' },", - " description: 'true/false toggle to controls',", - " table: {", - " category: 'Pick one: CSS, HTML, Interactive'", - " }", - " },", - " selectExample: {", - " options: [0, 1, 2, 3], // iterator", - " mapping: ['string1', 'string2', 'string3', 'string4'], // values", - " control: {", - " type: 'select',", - " labels: ['string1-label', 'string2-label', 'string3-label', 'string4-label'] // public labels", - " },", - " description: 'select menu mapping to strings (example: use for variant class names)',", - " table: {", - " category: 'Pick one: CSS, HTML, Interactive'", - " }", - " },", - " stringExample: {", - " name: 'stringExample',", - " type: 'string',", - " description: 'text box control',", - " table: {", - " category: 'Pick one: CSS, HTML, Interactive'", - " }", - " },", - " children: {", - " description: 'creates a slot for children',", - " table: {", - " category: 'HTML'", - " }", - " },", + " title: 'Components/ComponentName',", + " excludeStories: ['ComponentTemplateName'],", + " layout: 'padded',", + " argTypes: {", + " booleanExample: {", + " control: {type: 'boolean'},", + " description: 'true/false toggle to controls',", + " table: {", + " category: 'Pick one: CSS, HTML, Interactive'", + " }", + " },", + " radioExample: {", + " options: ['string1', 'string2', 'string3', 'string4'],", + " control: {", + " type: 'inline-radio',", + " },", + " description: 'radio buttons mapping to strings (example: use for variant class names)',", + " table: {", + " category: 'Pick one: CSS, HTML, Interactive'", + " }", + " },", + " stringExample: {", + " name: 'stringExample',", + " type: 'string',", + " description: 'text box control',", + " table: {", + " category: 'Pick one: CSS, HTML, Interactive'", + " }", + " },", + " children: {", + " description: 'creates a slot for children',", + " table: {", + " category: 'HTML'", + " }", " }", + " }", "}", "", "// build every component case here in the template (private api)", - "export const ComponentTemplateName = ({ booleanExample, selectExample, stringExample, children }) => (", - " ", - " {/* use {children} for wrapper component templates */}", - " <>", - " {stringExample}", - " {children}", - " ", - " ", + "export const ComponentTemplateName = ({booleanExample, radioExample, stringExample, children}) => (", + " ", + " {/* use {children} for wrapper component templates */}", + " <>", + " {stringExample}", + " {children}", + " ", + " ", ")", "", "// create a \"playground\" demo page that may set some defaults and allow story to access component controls", "export const Playground = ComponentTemplateName.bind({})", "Playground.args = {", - " stringExample: 'Default text',", - " booleanExample: false,", - " children: (", - " <>", - " ", - " ", - " )", + " stringExample: 'Default text',", + " booleanExample: false", + " // children: (", + " // <>", + " // ", + " // ", + " // )", "}", "" ], diff --git a/docs/.storybook/main.js b/docs/.storybook/main.js index f5f15878ed..06417d4184 100644 --- a/docs/.storybook/main.js +++ b/docs/.storybook/main.js @@ -6,6 +6,7 @@ module.exports = { '@storybook/addon-essentials', '@storybook/preset-scss', '@whitespace/storybook-addon-html', - 'storybook-addon-designs' + 'storybook-addon-designs', + 'storybook-color-picker' ] } diff --git a/docs/.storybook/manager-head.html b/docs/.storybook/manager-head.html new file mode 100644 index 0000000000..37254b8687 --- /dev/null +++ b/docs/.storybook/manager-head.html @@ -0,0 +1,10 @@ + diff --git a/docs/.storybook/preview-head.html b/docs/.storybook/preview-head.html index 16b173065b..fe487fd774 100644 --- a/docs/.storybook/preview-head.html +++ b/docs/.storybook/preview-head.html @@ -4,7 +4,7 @@ Helvetica Neue, sans-serif; color: var(--color-fg-default); background-color: var(--color-canvas-default); - padding: 0; + height: 100vh; } .theme-wrap { @@ -12,4 +12,18 @@ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); height: 100vh; } + + .theme-wrap .story-wrap { + padding: 1rem; + height: unset; + } + + .sb-main-padded .theme-wrap { + margin: -1rem; + } + + .sb-main-padded div:not(.theme-wrap) > [data-dark-theme] { + margin: -1rem; + padding: 1rem; + } diff --git a/docs/.storybook/preview.js b/docs/.storybook/preview.js index 171eca3175..20b0dbc5f7 100644 --- a/docs/.storybook/preview.js +++ b/docs/.storybook/preview.js @@ -1,6 +1,7 @@ import '../../src/docs.scss' import '../../src/index.scss' import '../../src/base/index.scss' +import '../src/stories/helpers/storybook-styles.scss' import renderToHTML from '../src/stories/helpers/code-snippet-html-helper' const customViewports = { diff --git a/docs/package.json b/docs/package.json index b701660f77..6c2f74664d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,7 +7,7 @@ "develop": "ln -sf ../dist . && gatsby develop -H 0.0.0.0", "build-content": "gatsby build --prefix-paths", "build": "./script/now-build.sh && ./script/build-storybook.sh", - "storybook": "start-storybook -p 6006", + "storybook": "NODE_ENV=test start-storybook -p 6006", "build-storybook": "./script/build-storybook.sh" }, "dependencies": { @@ -58,6 +58,7 @@ "@storybook/react": "6.3.12", "@whitespace/storybook-addon-html": "^5.0.0", "babel-loader": "^8.2.3", - "storybook-addon-designs": "6.2.0" + "storybook-addon-designs": "6.2.0", + "storybook-color-picker": "2.1.4" } } diff --git a/docs/src/stories/components/ActionList/ActionListItem.stories.jsx b/docs/src/stories/components/ActionList/ActionListItem.stories.jsx index 01cb8eb0cb..d49cfa71b5 100644 --- a/docs/src/stories/components/ActionList/ActionListItem.stories.jsx +++ b/docs/src/stories/components/ActionList/ActionListItem.stories.jsx @@ -16,7 +16,7 @@ export default { options: [0, 1, 2], // iterator mapping: ['', 'ActionList-content--sizeMedium', 'ActionList-content--sizeLarge'], // values control: { - type: 'select', + type: 'inline-radio', labels: ['default', 'medium', 'large'] }, description: 'small (default), medium, large', diff --git a/docs/src/stories/components/Button/Button.stories.jsx b/docs/src/stories/components/Button/Button.stories.jsx index 5b763f0254..365a477df9 100644 --- a/docs/src/stories/components/Button/Button.stories.jsx +++ b/docs/src/stories/components/Button/Button.stories.jsx @@ -7,31 +7,173 @@ export default { design: { type: 'figma', url: 'https://www.figma.com/file/GCvY3Qv8czRgZgvl1dG6lp/Primer-Web?node-id=4371%3A7128' - } + }, + layout: 'padded' }, + + excludeStories: ['ButtonTemplate'], argTypes: { variant: { - options: [0, 1, 2, 3], // iterator - mapping: [null, 'btn-primary', 'btn-outline', 'btn-danger'], // values + options: [0, 1, 2, 3, 4, 5, 6, 7], // iterator + mapping: [ + null, + 'btn-primary', + 'btn-outline', + 'btn-danger', + 'btn-link', + 'btn-invisible', + 'btn-with-count', + 'btn-octicon' + ], // values control: { type: 'select', - labels: ['default', 'primary', 'outline', 'danger'] + labels: ['default', 'primary', 'outline', 'danger', 'link', 'invisible', 'close', 'with-count', 'icon-only'] }, - defaultValue: '' + table: { + category: 'CSS' + } + }, + size: { + options: [0, 1, 2], // iterator + mapping: [null, 'btn-sm', 'btn-large'], // values + control: { + type: 'select', + labels: ['default', 'small', 'large'] + }, + table: { + category: 'CSS' + } }, label: { defaultValue: 'Button', type: 'string', name: 'label', - description: 'string' + description: 'string', + table: { + category: 'HTML' + } + }, + disabled: { + defaultValue: false, + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + fullWidth: { + defaultValue: false, + control: {type: 'boolean'}, + table: { + category: 'CSS' + } + }, + closeBtn: { + control: {type: 'boolean'}, + table: { + category: 'CSS' + } + }, + leadingVisual: { + name: 'leadingVisual', + type: 'string', + description: 'Paste [Octicon](https://primer.style/octicons/) in control field', + table: { + category: 'HTML' + } + }, + trailingVisual: { + name: 'trailingVisual', + type: 'string', + description: 'Paste [Octicon](https://primer.style/octicons/) in control field', + table: { + category: 'HTML' + } + }, + trailingAction: { + defaultValue: false, + control: {type: 'boolean'}, + table: { + category: 'CSS' + } + }, + selected: { + defaultValue: false, + control: {type: 'boolean'}, + table: { + category: 'CSS' + } + }, + focusElement: { + control: {type: 'boolean'}, + description: 'set focus on one element', + table: { + category: 'Interactive' + } + }, + focusAllElements: { + control: {type: 'boolean'}, + description: 'set focus on all elements for viewing in all themes', + table: { + category: 'Interactive' + } } } } -const Template = ({label, variant}) => ( +const focusMethod = function getFocus() { + // find the focusable element + var button = document.getElementsByTagName('button')[0] + // set focus on element + button.focus() +} + +export const ButtonTemplate = ({ + label, + variant, + disabled, + size, + fullWidth, + leadingVisual, + trailingVisual, + trailingAction, + selected, + closeBtn, + focusElement, + focusAllElements +}) => ( <> - + + {focusElement && focusMethod()} ) -export const Button = Template.bind({}) +export const Playground = ButtonTemplate.bind({}) +Playground.args = { + closeBtn: false, + focusElement: false, + focusAllElements: false +} diff --git a/docs/src/stories/components/Button/ButtonFeatures.stories.jsx b/docs/src/stories/components/Button/ButtonFeatures.stories.jsx new file mode 100644 index 0000000000..853ee49e81 --- /dev/null +++ b/docs/src/stories/components/Button/ButtonFeatures.stories.jsx @@ -0,0 +1,55 @@ +import React from 'react' +import clsx from 'clsx' +import {ButtonTemplate} from './Button.stories' + +export default { + title: 'Components/Button/Features' +} + +export const LeadingVisual = ButtonTemplate.bind({}) +LeadingVisual.storyName = 'Leading icon' +LeadingVisual.args = { + label: 'Open in Desktop', + leadingVisual: + '' +} + +export const TrailingVisual = ButtonTemplate.bind({}) +TrailingVisual.storyName = 'Trailing icon' +TrailingVisual.args = { + label: 'Open in Desktop', + trailingVisual: + '' +} + +export const Disabled = ButtonTemplate.bind({}) +Disabled.storyName = 'Disabled' +Disabled.args = { + label: 'Disabled', + disabled: true +} + +export const DisabledWithLeadingVisual = ButtonTemplate.bind({}) +DisabledWithLeadingVisual.storyName = 'Disabled with leading visual' +DisabledWithLeadingVisual.args = { + label: 'Disabled', + disabled: true, + leadingVisual: + '' +} + +export const Selected = ButtonTemplate.bind({}) +Selected.storyName = 'Selected' +Selected.args = { + label: 'Selected', + selected: true +} + +export const SelectedWithLeadingVisual = ButtonTemplate.bind({}) +SelectedWithLeadingVisual.storyName = 'Selected with leading visual' +SelectedWithLeadingVisual.args = { + label: 'Selected', + selected: true, + leadingVisual: + '' +} diff --git a/docs/src/stories/components/Forms/Checkbox.stories.jsx b/docs/src/stories/components/Forms/Checkbox.stories.jsx new file mode 100644 index 0000000000..f4a28347b9 --- /dev/null +++ b/docs/src/stories/components/Forms/Checkbox.stories.jsx @@ -0,0 +1,121 @@ +import React from 'react' +import clsx from 'clsx' + +export default { + title: 'Components/Forms/Checkbox', + parameters: { + layout: 'padded' + }, + decorators: [ + Story => ( +
+ + + ) + ], + excludeStories: ['CheckboxTemplate'], + argTypes: { + disabled: { + description: 'disabled field', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + note: { + type: 'string', + name: 'note', + description: 'string', + table: { + category: 'HTML' + } + }, + label: { + type: 'string', + name: 'label', + description: 'string', + table: { + category: 'HTML' + } + }, + highlight: { + control: {type: 'boolean'}, + description: 'highlight label', + table: { + category: 'CSS' + } + }, + checked: { + control: {type: 'boolean'}, + description: 'checkbox state', + table: { + category: 'CSS' + } + }, + focusElement: { + control: {type: 'boolean'}, + description: 'set focus on element', + table: { + category: 'Interactive' + } + }, + focusAllElements: { + control: {type: 'boolean'}, + description: 'set focus on all elements for viewing in all themes', + table: { + category: 'Interactive' + } + }, + type: { + options: [0, 1], // iterator + mapping: ['checkbox', 'radio'], // values + control: { + type: 'inline-radio', + labels: ['checkbox', 'radio'] // public labels + }, + description: 'input type' + } + } +} + +const focusMethod = function getFocus() { + // find the focusable element + var checkbox = document.getElementsByTagName('input')[0] + // set focus on element + checkbox.focus() +} + +export const CheckboxTemplate = ({label, note, highlight, disabled, checked, focusElement, focusAllElements, type}) => ( + <> +
+ + {note && ( +

+ {note} +

+ )} +
+ {focusElement && focusMethod()} + +) + +export const Playground = CheckboxTemplate.bind({}) +Playground.args = { + label: 'Label', + focusElement: false, + disabled: false, + checked: false, + highlight: false, + note: '', + type: 'checkbox', + focusAllElements: false +} diff --git a/docs/src/stories/components/Forms/Input.stories.jsx b/docs/src/stories/components/Forms/Input.stories.jsx new file mode 100644 index 0000000000..c2aac751f5 --- /dev/null +++ b/docs/src/stories/components/Forms/Input.stories.jsx @@ -0,0 +1,168 @@ +import React from 'react' +import clsx from 'clsx' + +export default { + title: 'Components/Forms/Input', + parameters: { + layout: 'padded' + }, + decorators: [ + Story => ( +
+ + + ) + ], + excludeStories: ['InputTemplate'], + argTypes: { + size: { + options: [0, 1, 2], // iterator + mapping: [null, 'input-sm', 'input-lg'], // values + control: { + type: 'select', + labels: ['default', 'small', 'large'] + }, + table: { + category: 'CSS' + } + }, + block: { + description: 'full width', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + monospace: { + description: 'monospace text', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + contrast: { + description: 'change input background to light gray', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + disabled: { + description: 'disabled field', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + hideWebKit: { + description: 'hide WebKit autofill icon', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + placeholder: { + type: 'string', + name: 'placeholder', + description: 'string', + table: { + category: 'HTML' + } + }, + label: { + type: 'string', + name: 'label', + description: 'string', + table: { + category: 'HTML' + } + }, + type: { + name: 'type', + type: 'string', + description: 'type', + table: { + category: 'HTML' + } + }, + id: { + name: 'id', + type: 'string', + description: 'id', + table: { + category: 'HTML' + } + }, + focusElement: { + control: {type: 'boolean'}, + description: 'set focus on element', + table: { + category: 'Interactive' + } + }, + focusAllElements: { + control: {type: 'boolean'}, + description: 'set focus on all elements for viewing in all themes', + table: { + category: 'Interactive' + } + } + } +} + +const focusMethod = function getFocus() { + // find the focusable element + var input = document.getElementsByTagName('input')[0] + // set focus on element + input.focus() +} + +export const InputTemplate = ({ + label, + type, + id, + size, + block, + placeholder, + contrast, + disabled, + hideWebKit, + monospace, + focusElement, + focusAllElements +}) => ( + <> + + + {focusElement && focusMethod()} + +) + +export const Playground = InputTemplate.bind({}) +Playground.args = { + type: 'email', + id: 'some-id', + placeholder: 'Email address', + label: 'Enter email address', + block: false, + hideWebKit: false, + monospace: false, + contrast: false, + disabled: false, + focusElement: false, + focusAllElements: false +} diff --git a/docs/src/stories/components/Forms/Radio.stories.jsx b/docs/src/stories/components/Forms/Radio.stories.jsx new file mode 100644 index 0000000000..33835a50cb --- /dev/null +++ b/docs/src/stories/components/Forms/Radio.stories.jsx @@ -0,0 +1,29 @@ +import React from 'react' +import clsx from 'clsx' +import {CheckboxTemplate} from './Checkbox.stories' + +export default { + title: 'Components/Forms/Radio', + parameters: { + layout: 'padded' + }, + decorators: [ + Story => ( +
+ + + ) + ] +} + +export const Playground = CheckboxTemplate.bind({}) +Playground.args = { + label: 'Label', + focusElement: false, + disabled: false, + checked: false, + highlight: false, + note: '', + type: 'radio', + focusAllElements: false +} diff --git a/docs/src/stories/components/Forms/Select.stories.jsx b/docs/src/stories/components/Forms/Select.stories.jsx new file mode 100644 index 0000000000..f8303e7efc --- /dev/null +++ b/docs/src/stories/components/Forms/Select.stories.jsx @@ -0,0 +1,112 @@ +import React from 'react' +import clsx from 'clsx' + +export default { + title: 'Components/Forms/Select', + parameters: { + layout: 'padded' + }, + decorators: [ + Story => ( +
+ + + ) + ], + excludeStories: ['SelectTemplate'], + argTypes: { + size: { + options: [0, 1], // iterator + mapping: [null, 'select-sm'], // values + control: { + type: 'select', + labels: ['default', 'small'] + }, + table: { + category: 'CSS' + } + }, + disabled: { + description: 'disabled field', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + placeholder: { + type: 'string', + name: 'placeholder', + description: 'string', + table: { + category: 'HTML' + } + }, + label: { + type: 'string', + name: 'label', + description: 'string', + table: { + category: 'HTML' + } + }, + id: { + name: 'id', + type: 'string', + description: 'id', + table: { + category: 'HTML' + } + }, + focusElement: { + control: {type: 'boolean'}, + description: 'set focus on element', + table: { + category: 'Interactive' + } + }, + focusAllElements: { + control: {type: 'boolean'}, + description: 'set focus on all elements for viewing in all themes', + table: { + category: 'Interactive' + } + } + } +} + +const focusMethod = function getFocus() { + // find the focusable element + var select = document.getElementsByTagName('select')[0] + // set focus on element + select.focus() +} + +export const SelectTemplate = ({label, id, size, placeholder, disabled, focusElement, focusAllElements}) => ( + <> + + + {focusElement && focusMethod()} + +) + +export const Playground = SelectTemplate.bind({}) +Playground.args = { + id: 'some-id', + placeholder: 'Select', + label: 'Select one', + focusElement: false, + focusAllElements: false +} diff --git a/docs/src/stories/components/Forms/Textarea.stories.jsx b/docs/src/stories/components/Forms/Textarea.stories.jsx new file mode 100644 index 0000000000..4bc81d4ac7 --- /dev/null +++ b/docs/src/stories/components/Forms/Textarea.stories.jsx @@ -0,0 +1,154 @@ +import React from 'react' +import clsx from 'clsx' + +export default { + title: 'Components/Forms/Textarea', + parameters: { + layout: 'padded' + }, + decorators: [ + Story => ( +
+ + + ) + ], + excludeStories: ['TextareaTemplate'], + argTypes: { + size: { + options: [0, 1, 2], // iterator + mapping: [null, 'input-sm', 'input-lg'], // values + control: { + type: 'select', + labels: ['default', 'small', 'large'] + }, + table: { + category: 'CSS' + } + }, + block: { + description: 'full width', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + contrast: { + description: 'change input background to light gray', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + disabled: { + description: 'disabled field', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + hideWebKit: { + description: 'hude WebKit autofill icon', + control: {type: 'boolean'}, + table: { + category: 'Interactive' + } + }, + placeholder: { + type: 'string', + name: 'placeholder', + description: 'string', + table: { + category: 'HTML' + } + }, + label: { + type: 'string', + name: 'label', + description: 'string', + table: { + category: 'HTML' + } + }, + type: { + name: 'type', + type: 'string', + description: 'type', + table: { + category: 'HTML' + } + }, + id: { + name: 'id', + type: 'string', + description: 'id', + table: { + category: 'HTML' + } + }, + focusElement: { + control: {type: 'boolean'}, + description: 'set focus on element', + table: { + category: 'Interactive' + } + }, + focusAllElements: { + control: {type: 'boolean'}, + description: 'set focus on all elements for viewing in all themes', + table: { + category: 'Interactive' + } + } + } +} + +const focusMethod = function getFocus() { + // find the focusable element + var textarea = document.getElementsByTagName('textarea')[0] + // set focus on element + textarea.focus() +} + +export const TextareaTemplate = ({ + label, + type, + id, + size, + block, + placeholder, + contrast, + disabled, + hideWebKit, + focusElement, + focusAllElements +}) => ( + <> + +