From 1b8a15aa126eb50957f05a10fb005167a4fa55ea Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 17 Feb 2022 12:30:49 +0100 Subject: [PATCH 01/62] docs: Add storybook --- website/.storybook/main.js | 12 ++ website/.storybook/preview.js | 11 + website/package.json | 14 +- website/src/stories/Button.jsx | 50 +++++ website/src/stories/Button.stories.jsx | 40 ++++ website/src/stories/Header.jsx | 57 +++++ website/src/stories/Header.stories.jsx | 24 +++ website/src/stories/Introduction.stories.mdx | 211 +++++++++++++++++++ website/src/stories/Page.jsx | 69 ++++++ website/src/stories/Page.stories.jsx | 25 +++ website/src/stories/assets/code-brackets.svg | 1 + website/src/stories/assets/colors.svg | 1 + website/src/stories/assets/comments.svg | 1 + website/src/stories/assets/direction.svg | 1 + website/src/stories/assets/flow.svg | 1 + website/src/stories/assets/plugin.svg | 1 + website/src/stories/assets/repo.svg | 1 + website/src/stories/assets/stackalt.svg | 1 + website/src/stories/button.css | 30 +++ website/src/stories/header.css | 32 +++ website/src/stories/page.css | 69 ++++++ 21 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 website/.storybook/main.js create mode 100644 website/.storybook/preview.js create mode 100644 website/src/stories/Button.jsx create mode 100644 website/src/stories/Button.stories.jsx create mode 100644 website/src/stories/Header.jsx create mode 100644 website/src/stories/Header.stories.jsx create mode 100644 website/src/stories/Introduction.stories.mdx create mode 100644 website/src/stories/Page.jsx create mode 100644 website/src/stories/Page.stories.jsx create mode 100644 website/src/stories/assets/code-brackets.svg create mode 100644 website/src/stories/assets/colors.svg create mode 100644 website/src/stories/assets/comments.svg create mode 100644 website/src/stories/assets/direction.svg create mode 100644 website/src/stories/assets/flow.svg create mode 100644 website/src/stories/assets/plugin.svg create mode 100644 website/src/stories/assets/repo.svg create mode 100644 website/src/stories/assets/stackalt.svg create mode 100644 website/src/stories/button.css create mode 100644 website/src/stories/header.css create mode 100644 website/src/stories/page.css diff --git a/website/.storybook/main.js b/website/.storybook/main.js new file mode 100644 index 0000000000..c96c218255 --- /dev/null +++ b/website/.storybook/main.js @@ -0,0 +1,12 @@ +module.exports = { + "stories": [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(js|jsx|ts|tsx)" + ], + "addons": [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions" + ], + "framework": "@storybook/react" +} \ No newline at end of file diff --git a/website/.storybook/preview.js b/website/.storybook/preview.js new file mode 100644 index 0000000000..798e1cd558 --- /dev/null +++ b/website/.storybook/preview.js @@ -0,0 +1,11 @@ +import '../src/css/custom.css'; + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; diff --git a/website/package.json b/website/package.json index a67a482584..b984119878 100644 --- a/website/package.json +++ b/website/package.json @@ -11,7 +11,9 @@ "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" + "write-heading-ids": "docusaurus write-heading-ids", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" }, "dependencies": { "@docusaurus/core": "2.0.0-beta.15", @@ -47,5 +49,15 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@babel/core": "^7.17.4", + "@storybook/addon-actions": "^6.4.19", + "@storybook/addon-essentials": "^6.4.19", + "@storybook/addon-interactions": "^6.4.19", + "@storybook/addon-links": "^6.4.19", + "@storybook/react": "^6.4.19", + "@storybook/testing-library": "^0.0.9", + "babel-loader": "^8.2.3" } } diff --git a/website/src/stories/Button.jsx b/website/src/stories/Button.jsx new file mode 100644 index 0000000000..15dde39209 --- /dev/null +++ b/website/src/stories/Button.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import './button.css'; + +/** + * Primary UI component for user interaction + */ +export const Button = ({ primary, backgroundColor, size, label, ...props }) => { + const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; + return ( + + ); +}; + +Button.propTypes = { + /** + * Is this the principal call to action on the page? + */ + primary: PropTypes.bool, + /** + * What background color to use + */ + backgroundColor: PropTypes.string, + /** + * How large should the button be? + */ + size: PropTypes.oneOf(['small', 'medium', 'large']), + /** + * Button contents + */ + label: PropTypes.string.isRequired, + /** + * Optional click handler + */ + onClick: PropTypes.func, +}; + +Button.defaultProps = { + backgroundColor: null, + primary: false, + size: 'medium', + onClick: undefined, +}; diff --git a/website/src/stories/Button.stories.jsx b/website/src/stories/Button.stories.jsx new file mode 100644 index 0000000000..61f6e19e14 --- /dev/null +++ b/website/src/stories/Button.stories.jsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { Button } from './Button'; + +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: 'Example/Button', + component: Button, + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + }, +}; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Template = (args) => + + ); diff --git a/website/src/components/UserFeedback/styles.css b/website/src/components/UserFeedback/styles.css index e6f72b65db..6a82119a13 100644 --- a/website/src/components/UserFeedback/styles.css +++ b/website/src/components/UserFeedback/styles.css @@ -3,6 +3,46 @@ position: absolute; bottom: 0; border: var(--ifm-global-border-width) solid var(--unleash-color-gray); - border-radius: var(--ifm-global-radius); + border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0; box-shadow: var(--ifm-global-shadow-lw); + padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); + text-align: center; +} + +.satisfaction-input-container { + display: flex; + place-content: center; + align-items: center; + gap: 1em; +} + + +.user-satisfaction-score-label { + display: grid; + place-content: center; + height: 3em; + width: 3em; + border: var(--ifm-global-border-width) solid currentColor; + border-radius: 50%; + outline-offset: 4px; +} + +.user-satisfaction-score-input:focus-visible + .user-satisfaction-score-label { + outline: 2px solid var(--ifm-color-primary); +} + +.user-satisfaction-score-label:hover { + color: var(--ifm-color-primary) +} + +.user-satisfaction-score-input:checked + label { + color: var(--ifm-color-primary); + background: var(--ifm-color-primary); + color: var(--ifm-color-primary-contrast-background); + border-color: var(--ifm-color-primary); +} + +.button-container { + display: flex; + justify-content: end; } diff --git a/website/src/css/custom.css b/website/src/css/custom.css index dd92dbc69f..1097fa604c 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -62,6 +62,18 @@ html[data-theme='dark'] { --docsearch-primary-color: var(--ifm-color-primary-darkest); } +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: auto; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; +} + main img { background: var(--unleash-img-background-color); display: block; From aa3fb6c4e230f5adfd6acde51be347d8912b3d8d Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 24 Feb 2022 18:00:03 +0100 Subject: [PATCH 07/62] chore: rename styles to module, add more focus styles --- website/src/components/UserFeedback/index.jsx | 4 ++-- .../{styles.css => styles.module.css} | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) rename website/src/components/UserFeedback/{styles.css => styles.module.css} (80%) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index 5f718adb53..16679e3e40 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import './styles.css'; +import styles from './styles.module.css'; const Component = ({ text }) => (
-
+

On a scale from 1 to 5 where 1 is very unsatisfied and 5 is diff --git a/website/src/components/UserFeedback/styles.css b/website/src/components/UserFeedback/styles.module.css similarity index 80% rename from website/src/components/UserFeedback/styles.css rename to website/src/components/UserFeedback/styles.module.css index 6a82119a13..ee5dc6d4af 100644 --- a/website/src/components/UserFeedback/styles.css +++ b/website/src/components/UserFeedback/styles.module.css @@ -7,6 +7,20 @@ box-shadow: var(--ifm-global-shadow-lw); padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); text-align: center; + --outline-style: 2px solid var(--ifm-color-primary); +} + +.user-feedback > form { + max-width: 850px; + margin: auto; +} + +.user-feedback * { + outline-offset: 4px; +} + +.user-feedback *:focus { + outline: var(--outline-style); } .satisfaction-input-container { @@ -24,11 +38,10 @@ width: 3em; border: var(--ifm-global-border-width) solid currentColor; border-radius: 50%; - outline-offset: 4px; } .user-satisfaction-score-input:focus-visible + .user-satisfaction-score-label { - outline: 2px solid var(--ifm-color-primary); + outline: var(--outline-style); } .user-satisfaction-score-label:hover { @@ -46,3 +59,5 @@ display: flex; justify-content: end; } + +.bah {} From a6191f5ab8a934b7e9476f75d7e9f0f10ed75e8d Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 12:57:13 +0100 Subject: [PATCH 08/62] feat: rough style first question page of feedback component. --- website/.storybook/main.js | 6 ++ website/src/components/UserFeedback/index.jsx | 8 +++ .../components/UserFeedback/styles.module.css | 63 +++++++++++++++++-- website/src/css/custom.css | 2 +- website/src/icons/close.jsx | 15 +++++ website/src/icons/styles.css | 5 ++ 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 website/src/icons/close.jsx create mode 100644 website/src/icons/styles.css diff --git a/website/.storybook/main.js b/website/.storybook/main.js index 57f59bbb12..fce48b819b 100644 --- a/website/.storybook/main.js +++ b/website/.storybook/main.js @@ -1,3 +1,5 @@ +const path = require('path'); + module.exports = { stories: [ '../src/**/*.stories.mdx', @@ -16,4 +18,8 @@ module.exports = { to: '/', }, ], + webpackFinal: async (config) => { + config.resolve.alias['@site'] = path.resolve(__dirname, '../'); + return config; + }, }; diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index 16679e3e40..6245a5c2cd 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -1,8 +1,15 @@ import React from 'react'; import styles from './styles.module.css'; +import CloseIcon from '@site/src/icons/close' const Component = ({ text }) => (

+
+ +

@@ -36,6 +43,7 @@ const Component = ({ text }) => (

+
diff --git a/website/src/components/UserFeedback/styles.module.css b/website/src/components/UserFeedback/styles.module.css index ee5dc6d4af..7157a54691 100644 --- a/website/src/components/UserFeedback/styles.module.css +++ b/website/src/components/UserFeedback/styles.module.css @@ -8,18 +8,31 @@ padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); text-align: center; --outline-style: 2px solid var(--ifm-color-primary); + --row-gap: 1rem; + --element-horizontal-gap: 1rem; +} + +.user-feedback > * + * { + margin-top: var(--row-gap); +} + +.user-feedback button { + border: none; + border-radius: var(--ifm-global-radius); + padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); } .user-feedback > form { max-width: 850px; - margin: auto; + margin-left: auto; + margin-right: auto; } .user-feedback * { outline-offset: 4px; } -.user-feedback *:focus { +.user-feedback *:focus-visible { outline: var(--outline-style); } @@ -27,7 +40,7 @@ display: flex; place-content: center; align-items: center; - gap: 1em; + gap: var(--element-horizontal-gap); } @@ -56,8 +69,50 @@ } .button-container { + margin-top: var(--row-gap); + display: flex; + justify-content: end; + gap: var(--element-horizontal-gap); +} + +button.close-button { + background: none; + border: none; + border-radius: 50%; + padding: 0; + aspect-ratio: 1; + height: 1em; +} + +.close-button:hover{ + color: var(--ifm-color-primary); +} + +.close-button:active{ + color: var(--ifm-color-primary-darker); +} + +.close-button-row { display: flex; justify-content: end; } -.bah {} +.user-feedback button[type=submit] { + background-color: var(--ifm-color-primary); + color: var(--ifm-color-primary-contrast-background); + padding-inline: calc(var(--ifm-spacing-horizontal) * 4); +} + +.user-feedback button[type=submit]:active { + background-color: var(--ifm-color-primary-dark); +} + +.button-secondary { + color: var(--ifm-color-primary); + background: none; +} + + +.button-secondary:active { + color: var(--ifm-color-primary-darker); +} diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 1097fa604c..3649cb82aa 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -20,7 +20,7 @@ footer { } html[data-theme='light'] { - --ifm-color-primary: #39535b; + --ifm-color-primary: var(--unleash-color-purple); --ifm-color-primary-dark: #334b52; --ifm-color-primary-darker: #30474d; --ifm-color-primary-darkest: #283a40; diff --git a/website/src/icons/close.jsx b/website/src/icons/close.jsx new file mode 100644 index 0000000000..ce59a0fd6c --- /dev/null +++ b/website/src/icons/close.jsx @@ -0,0 +1,15 @@ +import './styles.css'; + +const svg = () => ( + + + +); + +const Icon = svg; + +export default Icon; diff --git a/website/src/icons/styles.css b/website/src/icons/styles.css new file mode 100644 index 0000000000..6bcf15829d --- /dev/null +++ b/website/src/icons/styles.css @@ -0,0 +1,5 @@ +.icon { + fill: currentColor; + border-radius: 50%; + aspect-ratio: 1; +} From 4a795966d10784cd9004b9cdf0bdb3c1d3695fca Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 13:32:52 +0100 Subject: [PATCH 09/62] docs: change primary color for light theme to unleash purple --- website/src/css/custom.css | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 3649cb82aa..afb5decc62 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -20,15 +20,14 @@ footer { } html[data-theme='light'] { + --ifm-color-primary-lightest: #8783d2; + --ifm-color-primary-lighter: #7b76ce; + --ifm-color-primary-light: #6f6ac9; --ifm-color-primary: var(--unleash-color-purple); - --ifm-color-primary-dark: #334b52; - --ifm-color-primary-darker: #30474d; - --ifm-color-primary-darkest: #283a40; - --ifm-color-primary-light: #3f5b64; - --ifm-color-primary-lighter: #425f69; - --ifm-color-primary-lightest: #4a6c76; + --ifm-color-primary-dark: #5953be; + --ifm-color-primary-darker: #4f4ab7; + --ifm-color-primary-darkest: #4540b0; - --ifm-link-color: var(--unleash-color-purple); --ifm-menu-color-background-active: var(--unleash-color-gray); --ifm-menu-color-background-hover: var(--unleash-color-gray); @@ -63,15 +62,15 @@ html[data-theme='dark'] { } .visually-hidden { - border: 0; - clip: rect(0 0 0 0); - height: auto; - margin: 0; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - white-space: nowrap; + border: 0; + clip: rect(0 0 0 0); + height: auto; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; } main img { From 790bd4da0f46b51f68bd4515444a13091dc84a3d Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 15:37:04 +0100 Subject: [PATCH 10/62] chore: support css modules in storybook --- website/.storybook/main.js | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/website/.storybook/main.js b/website/.storybook/main.js index fce48b819b..0646e98d97 100644 --- a/website/.storybook/main.js +++ b/website/.storybook/main.js @@ -1,5 +1,3 @@ -const path = require('path'); - module.exports = { stories: [ '../src/**/*.stories.mdx', @@ -19,7 +17,48 @@ module.exports = { }, ], webpackFinal: async (config) => { - config.resolve.alias['@site'] = path.resolve(__dirname, '../'); - return config; + const path = require('path'); + + config.resolve.alias = { + ...config.resolve.alias, + '@site': path.resolve(__dirname, '../'), + }; + + const rules = config.module.rules.map((rule) => { + if (rule.test.toString() !== '/\\.css$/') { + return rule; + } + + const use = rule.use.map((u) => { + const { loader } = u; + + if (!loader || !loader.includes('/css-loader/')) { + return u; + } + + const options = { + ...u.options, + modules: true, + }; + + return { + ...u, + options, + }; + }); + + return { + ...rule, + use, + }; + }); + + return { + ...config, + module: { + ...config.module, + rules, + }, + }; }, }; From 43597687df9dfe1e5fe48c275949f763ba5731a9 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 16:38:32 +0100 Subject: [PATCH 11/62] fix: make css module loading work as expected. --- website/.storybook/main.js | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/website/.storybook/main.js b/website/.storybook/main.js index 0646e98d97..45f573c2d5 100644 --- a/website/.storybook/main.js +++ b/website/.storybook/main.js @@ -24,33 +24,36 @@ module.exports = { '@site': path.resolve(__dirname, '../'), }; + let cssRules = []; const rules = config.module.rules.map((rule) => { - if (rule.test.toString() !== '/\\.css$/') { - return rule; - } - - const use = rule.use.map((u) => { - const { loader } = u; - - if (!loader || !loader.includes('/css-loader/')) { - return u; - } - - const options = { - ...u.options, - modules: true, - }; + if (rule.test.toString() === '/\\.css$/') { + cssRules.push(JSON.parse(JSON.stringify(rule))); return { - ...u, - options, + ...rule, + exclude: /\.module\.css$/, }; - }); + } else return rule; + }); - return { - ...rule, - use, + cssRules.forEach((r) => { + const moduleRule = { + ...r, + test: /\.module\.css$/, + use: r.use.map((use) => { + if ( + typeof use === 'object' && + use.loader.includes('/css-loader/') + ) { + use.options = { + ...use.options, + modules: true, + }; + } + return use; + }), }; + rules.push(moduleRule); }); return { From 532f0cb6344c8918830316ea9cecde892d070b7c Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 16:39:25 +0100 Subject: [PATCH 12/62] fix: explicitly set background for the light theme the default is for it to be transparent, which causes issues when you're overlaying components with the default background color. --- website/src/css/custom.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index afb5decc62..e0fcf975db 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -34,6 +34,8 @@ html[data-theme='light'] { --unleash-color-admonition-background: var(--unleash-color-gray); --unleash-color-admonition-border: #999; --unleash-color-admonition-text: #2b2b2b; + + --ifm-background-color: #fff; } html[data-theme='dark'] { From 6e2072bca477608468e0f1857f8f5db9ee436675 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 25 Feb 2022 17:10:29 +0100 Subject: [PATCH 13/62] chore: first feedback page --- website/src/components/UserFeedback/index.jsx | 20 ++++++++++--------- .../components/UserFeedback/styles.module.css | 11 +++++++++- website/src/icons/close.jsx | 5 +++-- .../icons/{styles.css => styles.module.css} | 0 website/src/theme/Root.js | 3 ++- 5 files changed, 26 insertions(+), 13 deletions(-) rename website/src/icons/{styles.css => styles.module.css} (100%) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index 6245a5c2cd..fd4a1af39d 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -2,15 +2,17 @@ import React from 'react'; import styles from './styles.module.css'; import CloseIcon from '@site/src/icons/close' +const join = (...cs) => cs.join(" ") + const Component = ({ text }) => ( -
-
-
-
+

On a scale from 1 to 5 where 1 is very unsatisfied and 5 is @@ -20,12 +22,12 @@ const Component = ({ text }) => ( documentation?

-
+
{[1, 2, 3, 4, 5].map((n) => ( ( value={n} />
-
- +
+
diff --git a/website/src/components/UserFeedback/styles.module.css b/website/src/components/UserFeedback/styles.module.css index 7157a54691..fea0099b37 100644 --- a/website/src/components/UserFeedback/styles.module.css +++ b/website/src/components/UserFeedback/styles.module.css @@ -1,6 +1,7 @@ .user-feedback { width: 100%; position: absolute; + background: var(--ifm-background-color); bottom: 0; border: var(--ifm-global-border-width) solid var(--unleash-color-gray); border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0; @@ -99,10 +100,14 @@ button.close-button { .user-feedback button[type=submit] { background-color: var(--ifm-color-primary); - color: var(--ifm-color-primary-contrast-background); + color: var(--ifm-background-color); padding-inline: calc(var(--ifm-spacing-horizontal) * 4); } +.user-feedback button[type=submit]:hover { + background-color: var(--ifm-color-primary-lighter); +} + .user-feedback button[type=submit]:active { background-color: var(--ifm-color-primary-dark); } @@ -116,3 +121,7 @@ button.close-button { .button-secondary:active { color: var(--ifm-color-primary-darker); } + +.button-secondary:hover { + color: var(--ifm-color-primary-lightest); +} diff --git a/website/src/icons/close.jsx b/website/src/icons/close.jsx index ce59a0fd6c..4bb6d4de54 100644 --- a/website/src/icons/close.jsx +++ b/website/src/icons/close.jsx @@ -1,8 +1,9 @@ -import './styles.css'; +import React from 'react'; +import styles from './styles.module.css'; const svg = () => ( diff --git a/website/src/icons/styles.css b/website/src/icons/styles.module.css similarity index 100% rename from website/src/icons/styles.css rename to website/src/icons/styles.module.css diff --git a/website/src/theme/Root.js b/website/src/theme/Root.js index aa2ffad302..aa8e2a42b1 100644 --- a/website/src/theme/Root.js +++ b/website/src/theme/Root.js @@ -5,7 +5,8 @@ import UF from '@site/src/components/UserFeedback'; function Root({ children }) { return ( <> - {children} + + {children} ); } From 211b18c4a3443713cbcc862fcb2429030d3a1f26 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Sat, 26 Feb 2022 21:38:07 +0100 Subject: [PATCH 14/62] feat: start adding state logic to component. --- website/src/components/UserFeedback/index.jsx | 175 +++++++++++++----- 1 file changed, 129 insertions(+), 46 deletions(-) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index fd4a1af39d..61affb3e81 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -1,55 +1,138 @@ import React from 'react'; import styles from './styles.module.css'; -import CloseIcon from '@site/src/icons/close' +import CloseIcon from '@site/src/icons/close'; -const join = (...cs) => cs.join(" ") +const join = (...cs) => cs.join(' '); -const Component = ({ text }) => ( -
-
- -
-
-

- - On a scale from 1 to 5 where 1 is very unsatisfied and 5 is - very satisfied, - {' '} - How would you rate your overall satisfaction with the Unleash - documentation? -

+const Step1 = () => <>; +const Step2 = () => <>; +const Step3 = () => <>; -
- - {[1, 2, 3, 4, 5].map((n) => ( - - - +const initialData = () => ({ + initialized: Date.now(), + closedOrCompleted: false, + data: { + score: undefined, + comment: undefined, + customerType: undefined, + }, +}); + +const fetchData = (initialData) => { + const localstorageKey = 'user-feedback'; + + return initialData; + // check localstorage + // populate if there is +}; + +const FeedbackWrapper = () => { + const [feedbackIsOpen, setFeedbackIsOpen] = React.useState(false); + const stateReducer = (state, message) => { + switch (message.kind) { + case 'clear': + return initialData(); + case 'set score': + return { + ...state, + data: { ...state.data, score: message.data }, + }; + case 'set comment': + return { + ...state, + data: { ...state.data, comment: message.data }, + }; + case 'set customer type': + return { + ...state, + data: { ...state.data, customerType: message.data }, + }; + } + return state; + }; + + const [state, dispatch] = React.useReducer( + stateReducer, + initialData(), + fetchData, + ); + + const clear = () => dispatch({ kind: 'clear' }); + const setScore = (score) => dispatch({ kind: 'set score', data: score }); + const setComment = (comment) => + dispatch({ kind: 'set comment', data: comment }); + const setCustomerType = (customerType) => + dispatch({ kind: 'set customer type', data: customerType }); + + return feedbackIsOpen ? ( + setFeedbackIsOpen(false)} /> + ) : ( + setFeedbackIsOpen(true)} /> + ); +}; + +const OpenFeedbackButton = ({ openFeedback }) => { + return ; +}; + +const Component = ({ closeFeedback = () => undefined }) => { + return ( +
+
+
-
- - -
- -
-); +
+

+ + On a scale from 1 to 5 where 1 is very unsatisfied and 5 + is very satisfied, + {' '} + How would you rate your overall satisfaction with the + Unleash documentation? +

+ +
+ + {[1, 2, 3, 4, 5].map((n) => ( + + + + + ))} + +
+
+ + +
+
+
+ ); +}; export default Component; From 35a939b895f32a1eacfd99b705a9283d121eeb2e Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Sun, 27 Feb 2022 14:37:44 +0100 Subject: [PATCH 15/62] feat: start splitting component up into multiple pieces. --- .../UserFeedback/UserFeedback.stories.jsx | 23 ++- website/src/components/UserFeedback/index.jsx | 163 ++++++++++++++---- 2 files changed, 142 insertions(+), 44 deletions(-) diff --git a/website/src/components/UserFeedback/UserFeedback.stories.jsx b/website/src/components/UserFeedback/UserFeedback.stories.jsx index c96807162f..5f4ad9eec3 100644 --- a/website/src/components/UserFeedback/UserFeedback.stories.jsx +++ b/website/src/components/UserFeedback/UserFeedback.stories.jsx @@ -1,16 +1,25 @@ import React from 'react'; -import Component from './index'; +import Component, { initialData, FeedbackWrapper } from './index'; export default { title: 'User feedback component', component: Component, }; -const Template = (args) => ; +const Template = (args) => ; -export const A = Template.bind({}); -A.args = { - x: true, - y: 45, - text: 'blah blah blah', +export const FullComponent = Template.bind({}); +FullComponent.args = { + initialData, }; + +export const Step2 = Template.bind({}); +Step2.args = { + initialData: { + ...initialData, + currentStep: 2, + }, +}; + +export const Step3 = Template.bind({}); +export const Closed = Template.bind({}); diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index 61affb3e81..63213c5d56 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -4,29 +4,28 @@ import CloseIcon from '@site/src/icons/close'; const join = (...cs) => cs.join(' '); -const Step1 = () => <>; -const Step2 = () => <>; -const Step3 = () => <>; - -const initialData = () => ({ - initialized: Date.now(), - closedOrCompleted: false, +export const initialData = { + currentStep: 1, data: { score: undefined, comment: undefined, customerType: undefined, }, -}); +}; const fetchData = (initialData) => { const localstorageKey = 'user-feedback'; - return initialData; + return { + ...initialData, + initialized: Date.now(), + closedOrCompleted: false, + }; // check localstorage // populate if there is }; -const FeedbackWrapper = () => { +export const FeedbackWrapper = ({ initialData }) => { const [feedbackIsOpen, setFeedbackIsOpen] = React.useState(false); const stateReducer = (state, message) => { switch (message.kind) { @@ -47,48 +46,41 @@ const FeedbackWrapper = () => { ...state, data: { ...state.data, customerType: message.data }, }; + case 'step forward': + return { + ...state, + currentStep: min(state.currentStep + 1, 3), + }; + case 'step back': + return { + ...state, + currentStep: max(state.currentStep - 1, 1), + }; } return state; }; const [state, dispatch] = React.useReducer( stateReducer, - initialData(), + initialData, fetchData, ); const clear = () => dispatch({ kind: 'clear' }); + const stepForward = (e) => { + e.preventDefault(); + console.log('stepping forward!'); + dispatch({ kind: 'step forward' }); + }; + const stepBack = () => dispatch({ kind: 'step back' }); const setScore = (score) => dispatch({ kind: 'set score', data: score }); const setComment = (comment) => dispatch({ kind: 'set comment', data: comment }); const setCustomerType = (customerType) => dispatch({ kind: 'set customer type', data: customerType }); - return feedbackIsOpen ? ( - setFeedbackIsOpen(false)} /> - ) : ( - setFeedbackIsOpen(true)} /> - ); -}; - -const OpenFeedbackButton = ({ openFeedback }) => { - return ; -}; - -const Component = ({ closeFeedback = () => undefined }) => { - return ( -
-
- -
+ const Step1 = () => { + return (

@@ -113,6 +105,12 @@ const Component = ({ closeFeedback = () => undefined }) => { name="satisfaction-level" type="radio" value={n} + defaultChecked={n === state.data.score} + onChange={(e) => { + const value = parseInt(e.target.value); + console.log('the value is', value); + setScore(value); + }} />

- +
+ ); + }; + + return ( + + } + /> + ); +}; + +const OpenFeedbackButton = ({ openFeedback }) => { + return ; +}; + +export const Step1 = ({ score, stepForward, setScore }) => { + return ( +
+

+ + On a scale from 1 to 5 where 1 is very unsatisfied and 5 is + very satisfied, + {' '} + How would you rate your overall satisfaction with the Unleash + documentation? +

+ +
+ + {[1, 2, 3, 4, 5].map((n) => ( + + { + const value = parseInt(e.target.value); + console.log('the value is', value); + setScore(value); + }} + /> + + + ))} + +
+
+ + +
+
+ ); +}; + +const Component = ({ closeFeedback = () => undefined, CurrentStep }) => { + return ( +
+
+ +
+ + {CurrentStep}
); }; From 5340ca28fc19cbd1698f71777d9c9c4b7d9ffbdd Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Sun, 27 Feb 2022 14:59:33 +0100 Subject: [PATCH 16/62] move subcomponent into wrapper --- .../UserFeedback/UserFeedback.stories.jsx | 8 +- website/src/components/UserFeedback/index.jsx | 157 +++++------------- 2 files changed, 49 insertions(+), 116 deletions(-) diff --git a/website/src/components/UserFeedback/UserFeedback.stories.jsx b/website/src/components/UserFeedback/UserFeedback.stories.jsx index 5f4ad9eec3..3414ff0457 100644 --- a/website/src/components/UserFeedback/UserFeedback.stories.jsx +++ b/website/src/components/UserFeedback/UserFeedback.stories.jsx @@ -1,21 +1,21 @@ import React from 'react'; -import Component, { initialData, FeedbackWrapper } from './index'; +import { initialData, FeedbackWrapper } from './index'; export default { title: 'User feedback component', - component: Component, + component: FeedbackWrapper, }; const Template = (args) => ; export const FullComponent = Template.bind({}); FullComponent.args = { - initialData, + // initialData, }; export const Step2 = Template.bind({}); Step2.args = { - initialData: { + seedData: { ...initialData, currentStep: 2, }, diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index 63213c5d56..1ccb50e8b8 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -17,6 +17,9 @@ const fetchData = (initialData) => { const localstorageKey = 'user-feedback'; return { + data: { + score: undefined, + }, ...initialData, initialized: Date.now(), closedOrCompleted: false, @@ -25,44 +28,45 @@ const fetchData = (initialData) => { // populate if there is }; -export const FeedbackWrapper = ({ initialData }) => { +const stateReducer = (state, message) => { + switch (message.kind) { + case 'clear': + return fetchData(seedData); + case 'set score': + return { + ...state, + // data: { ...state.data, score: message.data }, + }; + case 'set comment': + return { + ...state, + data: { ...state.data, comment: message.data }, + }; + case 'set customer type': + return { + ...state, + data: { ...state.data, customerType: message.data }, + }; + case 'step forward': + return { + ...state, + currentStep: min(state.currentStep + 1, 3), + }; + case 'step back': + return { + ...state, + currentStep: max(state.currentStep - 1, 1), + }; + } + return state; +}; + +export const FeedbackWrapper = ({ seedData }) => { const [feedbackIsOpen, setFeedbackIsOpen] = React.useState(false); - const stateReducer = (state, message) => { - switch (message.kind) { - case 'clear': - return initialData(); - case 'set score': - return { - ...state, - data: { ...state.data, score: message.data }, - }; - case 'set comment': - return { - ...state, - data: { ...state.data, comment: message.data }, - }; - case 'set customer type': - return { - ...state, - data: { ...state.data, customerType: message.data }, - }; - case 'step forward': - return { - ...state, - currentStep: min(state.currentStep + 1, 3), - }; - case 'step back': - return { - ...state, - currentStep: max(state.currentStep - 1, 1), - }; - } - return state; - }; const [state, dispatch] = React.useReducer( stateReducer, - initialData, + seedData, fetchData, ); @@ -109,7 +113,7 @@ export const FeedbackWrapper = ({ initialData }) => { onChange={(e) => { const value = parseInt(e.target.value); console.log('the value is', value); - setScore(value); + /* setScore(value); */ }} />
); diff --git a/website/src/components/UserFeedback/styles.module.css b/website/src/components/UserFeedback/styles.module.css index 7646179f6b..b3cef1410e 100644 --- a/website/src/components/UserFeedback/styles.module.css +++ b/website/src/components/UserFeedback/styles.module.css @@ -43,15 +43,15 @@ padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); } -.user-feedback > form { +.user-feedback form { max-width: 850px; margin-left: auto; margin-right: auto; transition: var(--fade-in-transition); - opacity: 1; + /* opacity: 1; */ } -.user-feedback > form > * + * { +.user-feedback form > * + * { margin-top: var(--row-gap); } @@ -225,3 +225,13 @@ button.close-button { opacity: 0; transition: var(--fade-out-transition); } + +.form-section-container { + display: grid; + align-items: center; +} + +.form-section-container > * { + grid-column: 1; + grid-row: 1; +} From 05d8cfe10c08f4cd1a5360fc7c5b1f49005b27b0 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 28 Feb 2022 22:09:18 +0100 Subject: [PATCH 30/62] feat: fix keyboard focus between screens --- website/src/components/UserFeedback/index.jsx | 38 +++++++++++-------- .../components/UserFeedback/styles.module.css | 9 +++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index b2f60cc992..fe8bdcc5e0 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -67,6 +67,7 @@ const stateReducer = (state, message) => { export const FeedbackWrapper = ({ seedData, open }) => { const [feedbackIsOpen, setFeedbackIsOpen] = React.useState(open); + const [manuallyOpened, setManuallyOpened] = React.useState(false); const [state, dispatch] = React.useReducer( stateReducer, @@ -92,8 +93,6 @@ export const FeedbackWrapper = ({ seedData, open }) => { console.log('send feedback here '); }; - const step1ref = React.useRef(null); - const visuallyHidden = (stepNumber) => state.currentStep !== stepNumber; const isHidden = (stepNumber) => !feedbackIsOpen || visuallyHidden(stepNumber); @@ -131,10 +130,9 @@ export const FeedbackWrapper = ({ seedData, open }) => { Very unsatisfied - {[1, 2, 3, 4, 5].map((n) => ( + {[1, 2, 3, 4, 5].map((n, i) => ( { console.log('the value is', value); setNewValue(value); }} + autoFocus={ + manuallyOpened + ? state.data.score + ? state.data.score === n + : i === 0 + : false + } /> - @@ -259,16 +259,21 @@ export const FeedbackWrapper = ({ seedData, open }) => { >
- Finally, would you mind telling us a little about - yourself? What kind of customer are you? + Finally, are you a paying customer or an open source + customer of Unleash?
{[ ['a', 'paying', 'paying'], ['an', 'open source', 'opensource'], - ].map(([article, customerType, key]) => ( + ].map(([article, customerType, key], i) => ( { return (
-

feedback is {feedbackIsOpen ? 'open' : 'closed'}

+

+ feedback is {feedbackIsOpen ? 'open' : 'closed'}, manually?{' '} + {manuallyOpened} +

@@ -245,7 +249,7 @@ export const FeedbackWrapper = ({ seedData, open }) => { const Step3 = () => { const hidden = isHidden(3); - const [value, setValue] = React.useState(); + const [value, setValue] = React.useState(state.data.customerType); return ( Date: Mon, 28 Feb 2022 22:29:27 +0100 Subject: [PATCH 33/62] step forward after submitting response --- .../components/UserFeedback/UserFeedback.stories.jsx | 8 ++++++++ website/src/components/UserFeedback/index.jsx | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/website/src/components/UserFeedback/UserFeedback.stories.jsx b/website/src/components/UserFeedback/UserFeedback.stories.jsx index b61dcf5038..9d3dde2103 100644 --- a/website/src/components/UserFeedback/UserFeedback.stories.jsx +++ b/website/src/components/UserFeedback/UserFeedback.stories.jsx @@ -29,4 +29,12 @@ Step3.args = { open: true, }; +export const Step4 = Template.bind({}); +Step4.args = { + seedData: { + currentStep: 4, + }, + open: true, +}; + export const Closed = Template.bind({}); diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index ae828e0948..d939525ca2 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -14,7 +14,7 @@ export const initialData = { }; const fetchData = (initialData) => { - const localstorageKey = 'user-feedback'; + const localstorageKey = 'user-feedback-v1'; return { currentStep: 1, @@ -54,7 +54,7 @@ const stateReducer = (state, message) => { case 'step forward': return { ...state, - currentStep: Math.min(state.currentStep + 1, 3), + currentStep: Math.min(state.currentStep + 1, 4), }; case 'step back': return { @@ -91,6 +91,7 @@ export const FeedbackWrapper = ({ seedData, open }) => { const submitFeedback = () => { console.log('send feedback here '); + stepForward(); }; const visuallyHidden = (stepNumber) => state.currentStep !== stepNumber; @@ -320,12 +321,15 @@ export const FeedbackWrapper = ({ seedData, open }) => { }; const Step4 = () => { + const hidden = isHidden(4); return ( -
+

Thank you! 🙌

@@ -373,7 +377,7 @@ export const FeedbackWrapper = ({ seedData, open }) => { - {/* */} +
From b2a1628ff5bde3ba2d607a507acbb48376ca2577 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 28 Feb 2022 23:13:07 +0100 Subject: [PATCH 34/62] feat: set up request execution on form submission --- website/src/components/UserFeedback/index.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index d939525ca2..ab003f016c 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -90,7 +90,20 @@ export const FeedbackWrapper = ({ seedData, open }) => { dispatch({ kind: 'set customer type', data: customerType }); const submitFeedback = () => { - console.log('send feedback here '); + fetch(process.env.UNLEASH_FEEDBACK_TARGET_URL, { + method: 'post', + body: JSON.stringify({ data: state.data }), + }) + .then(async (res) => + res.ok + ? console.log('Success! Feedback was registered.') + : console.warn( + `Oh, no! The feedback registration failed: ${await res.text()}`, + ), + ) + .catch((e) => + console.error('Oh, no! The feedback registration failed:', e), + ); stepForward(); }; From 0e96a39ecf0f46fd94a74bef88ddec96626a42a6 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 28 Feb 2022 23:18:36 +0100 Subject: [PATCH 35/62] fix: remove pointer events from hidden content. --- website/src/components/UserFeedback/styles.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/components/UserFeedback/styles.module.css b/website/src/components/UserFeedback/styles.module.css index 4316306acc..cbd0325323 100644 --- a/website/src/components/UserFeedback/styles.module.css +++ b/website/src/components/UserFeedback/styles.module.css @@ -225,6 +225,7 @@ button.close-button { .open-feedback-button[disabled] { opacity: 0; transition: var(--fade-out-transition); + pointer-events: none; } .form-section-container { From 74a3c27b064abe100f564ba86f3a2532bbf227ff Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 28 Feb 2022 23:18:53 +0100 Subject: [PATCH 36/62] clear form after submission and re-opening --- website/src/components/UserFeedback/index.jsx | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/website/src/components/UserFeedback/index.jsx b/website/src/components/UserFeedback/index.jsx index ab003f016c..bb7e2091cd 100644 --- a/website/src/components/UserFeedback/index.jsx +++ b/website/src/components/UserFeedback/index.jsx @@ -4,38 +4,70 @@ import CloseIcon from '@site/src/icons/close'; const join = (...cs) => cs.join(' '); -export const initialData = { +const clearedData = { currentStep: 1, data: { score: undefined, comment: undefined, customerType: undefined, }, + userClosed: false, }; -const fetchData = (initialData) => { - const localstorageKey = 'user-feedback-v1'; +const localstorageKey = 'user-feedback-v1'; +const populateData = (initialData) => { + // if we get seed data, use that. Otherwise, check if the last entry in + // localstorage was completed. If not, use that as base. + + const getSeedData = () => { + if (initialData) { + return initialData; + } + + const userFeedbackLog = localStorage.getItem(localstorageKey); + + if (userFeedbackLog) { + const mostRecent = Math.max( + Object.keys(userFeedbackLog).map(parseInt), + ); + if (!mostRecent.closedOrCompleted) { + return mostRecent; + } + } + + return {}; + }; + + const seedData = getSeedData(); return { currentStep: 1, - ...initialData, + ...seedData, data: { score: undefined, comment: undefined, customerType: undefined, - ...initialData?.data, + ...seedData?.data, }, initialized: Date.now(), closedOrCompleted: false, }; - // check localstorage - // populate if there is +}; + +const storeData = (data) => { + const existingData = localStorage.getItem(localstorageKey); + localStorage.setItem(localstorageKey, { + ...existingData, + [data.initialized]: data, + }); }; const stateReducer = (state, message) => { switch (message.kind) { - case 'clear': - return fetchData(seedData); + case 'close': + return { ...state, userClosed: true }; + case 'reset': + return { ...populateData(clearedData), userClosed: false }; case 'set score': return { ...state, @@ -72,12 +104,13 @@ export const FeedbackWrapper = ({ seedData, open }) => { const [state, dispatch] = React.useReducer( stateReducer, seedData, - fetchData, + populateData, ); console.log(state, state.data); - const clear = () => dispatch({ kind: 'clear' }); + const close = () => dispatch({ kind: 'close' }); + const stepForward = () => { console.log('stepping forward!'); dispatch({ kind: 'step forward' }); @@ -298,11 +331,11 @@ export const FeedbackWrapper = ({ seedData, open }) => { type="radio" value={key} defaultChecked={ - key === state.data.customerType + customerType === state.data.customerType } onChange={(e) => { const value = e.target.value; - setValue(value); + setValue(customerType); }} />