Skip to content

Commit

Permalink
feat: Feedback dialog (#772)
Browse files Browse the repository at this point in the history
* feat: initial commit and experience selector

* feat: add data submission

* feat: add text fadeout

* fix: thicken outline when focused

* feat: better focus handling

* fix: focus stealing

* fix: button text alignment, fieldset spacing, pointer events

* fix: focus and style tweaks, use Carbon textbox

* feat: add  entrance animation

* feat:  fade on scroll

* fix:  animation choreography

* feat: add livve url

* fix: safari zamboni and text zoom

* fix: safari zamboni and resize

* fix: animation performance

* docs: add Feedback docs

* fix: homepage rendering and console errors

* fix: desktop transition speed

* fix:  focus return and icon flexibility

* cchore: remove unused dep

* fix: list overflow weirdness

* feat: add submission indicators and mobile styles

* chore: checkmark tweaks

* fix: launch button active and hover styles

* fix: use proper focus token

* fix: icon visibility animation

* fix: slow down stroke, add thanks and adjust reset

* fix: add background hover change

* fix: inverse 01

Co-authored-by: Vince Picone <Vincent.Patrick.Picone@ibm.com>
  • Loading branch information
vpicone and vpicone authored Mar 16, 2020
1 parent 8755c25 commit cb12399
Show file tree
Hide file tree
Showing 28 changed files with 839 additions and 169 deletions.
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"react/prop-types": 0,
"react/destructuring-assignment": 0,
"react/no-access-state-in-setstate": 0,
"no-useless-escape": 0
"react/jsx-props-no-spreading": 0,
"no-useless-escape": 0,
"arrow-body-style": 0
}
}
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@
"**/public/"
],
"devDependencies": {
"babel-eslint": "^9.0.0",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^4.2.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0",
"eslint-config-react-app": "^5.0.2",
"eslint-config-wesbos": "0.0.19",
"eslint-plugin-flowtype": "^4.3.0",
"eslint-plugin-html": "^5.0.3",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^1.3.0",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^2.5.0",
"husky": "^3.0.0",
"lerna": "^3.16.2",
"lint-staged": "^9.2.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/example/src/data/nav-items.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
path: /components/DoDontRow
- title: FeatureCard
path: /components/FeatureCard
- title: FeedbackDialog
path: /components/FeedbackDialog
- title: GifPlayer
path: /components/GifPlayer
- title: Grid
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import ThemeFeedbackDialog from 'gatsby-theme-carbon/src/components/FeedbackDialog/FeedbackDialog';

const FeedbackDialog = ({ props }) => {
const onSubmit = data => {
console.log({ ...data });
};

return <ThemeFeedbackDialog {...props} onSubmit={onSubmit} />;
};

export default FeedbackDialog;
60 changes: 60 additions & 0 deletions packages/example/src/pages/components/FeedbackDialog.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: FeatureCard
description: Allow visitors to your website to provide feedback through a dialog
---

<PageDescription>

The `<FeatureDialog>` component is a modeless dialog that allows your users to provide low-friction, anonymous feedback for a specific page.

</PageDescription>

## Activating the dialog

The Feedback button only becomes visisble once you've supplied an `onSubmit` handler. To do that, we'll need to shadow the `FeedbackDialog` component.

1. Create a new javascript file under `src/gatsby-theme-carbon/components/FeedbackDialog/FeedbackDialog.js`. Matching the filepath exactly is important here.

2. Copy the following snippet into your new file

```jsx
import React from 'react';
import ThemeFeedbackDialog from 'gatsby-theme-carbon/src/components/FeedbackDialog/FeedbackDialog';

const FeedbackDialog = ({ props }) => {
const onSubmit = data => {
console.log({ ...data });
};

return <ThemeFeedbackDialog {...props} onSubmit={onSubmit} />;
};

export default FeedbackDialog;

```


## Creating a handler

Next, you'll need a place to send the data. For the Carbon website, we use a serverless function that forwards the data to a [SurveyGizmo](https://www.surveygizmo.com/) quiz.
You can see that function [here](https://github.com/carbon-design-system/carbon-website/blob/master/functions/survey.js).

The handler can send a fetch request off to the endpoint you create. Update the `onSubmit` handler to send the data wherever you want. This function receives the following arguments:
- `experience`: "Negative", "Positive" or "Neutral"
- `comment`: An optional comment
- `path`: the window location when the survey was submitted

```jsx
const FeedbackDialog = ({ props }) => {
const onSubmit = data => {
fetch(process.env.API_ENDPOINT, {
method: 'POST',
body: JSON.stringify(data),
});

return <ThemeFeedbackDialog {...props} onSubmit={onSubmit} />;
};
```
3 changes: 2 additions & 1 deletion packages/gatsby-theme-carbon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@mdx-js/react": "^1.5.5",
"@reach/router": "^1.2.1",
"@vimeo/player": "^2.9.1",
"beautiful-react-hooks": "^0.22.2",
"carbon-components": "^10.10.1",
"carbon-components-react": "^7.10.1",
"classnames": "^2.2.6",
Expand Down Expand Up @@ -57,7 +58,7 @@
"prism-react-renderer": "^0.1.7",
"prop-types": "^15.7.2",
"react-helmet": "^6.0.0-beta",
"react-scroll-up": "^1.3.3",
"react-transition-group": "^4.3.0",
"remark-slug": "^5.1.2",
"slugify": "^1.3.4",
"smooth-scroll": "^16.0.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from 'react';
import { UpToTop20 } from '@carbon/icons-react';
import ScrollToTop from 'react-scroll-up';

import { button } from './BackToTopBtn.module.scss';

const BackToTopBtn = () => (
<ScrollToTop showUnder={300} style={{ zIndex: 9999 }}>
<button className={button} type="button" aria-label="Back to Top">
<UpToTop20 />
</button>
</ScrollToTop>
<button
onClick={() => window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })}
className={button}
type="button"
aria-label="Back to Top"
>
<UpToTop20 />
</button>
);

export default BackToTopBtn;
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
position: fixed;
bottom: $spacing-09;
right: $spacing-07;
transition: all $transition--base;
transition: all $duration--fast-02;

cursor: pointer;
z-index: 9999;
z-index: z('modal');
padding-top: 0.325rem;
}

.button:hover {
background: $carbon--gray-90;
background: var(--cds-inverse-01);
border-color: transparent;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import styles from './Checkmark.module.scss';

const Checkmark = () => {
return (
<svg className={styles.svg} width="20" viewBox="0 0 28 28">
<circle r="14" cx="14" cy="14" fill="#fff" />
<polyline
className={styles.path}
points="7.8 13.75 12 17.95 20.2 9.8"
fill="white"
stroke="#0f62fe"
strokeMiterlimit="10"
strokeWidth="2.5"
/>
</svg>
);
};

export default Checkmark;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.svg {
position: absolute;
right: 10px;
top: 10px;
padding: 2px;
}

.path {
stroke-dasharray: 30px;
stroke-dashoffset: 30px;
animation: drawStroke 1200ms forwards;
}

@keyframes drawStroke {
to {
stroke-dashoffset: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
// eslint doesn't know about TextArea labeling
import React, { useState } from 'react';
import { TextArea } from 'carbon-components-react';
import cx from 'classnames';
import styles from './Comment.module.scss';

const Comment = () => {
const [focused, setFocused] = useState(false);
const handleBlur = e => {
e.target.scrollTop = 0;
setFocused(false);
};

return (
<div className={styles.container}>
<TextArea
className={styles.textarea}
labelText="Comments (optional):"
onBlur={handleBlur}
onFocus={() => setFocused(true)}
rows={5}
name="feedback-form-comment"
id="feedback-form-comment"
/>
<div
className={cx(styles.fadeout, {
[styles.focused]: focused,
})}
/>
</div>
);
};

export default Comment;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.container {
position: relative;
}

.textarea {
resize: none;
@include carbon--type-style('body-short-02');
@include carbon--breakpoint('md') {
@include carbon--type-style('body-short-01');
}
}

.fadeout {
pointer-events: none;
position: absolute;
bottom: 1px;
left: $spacing-03;
right: $spacing-05;
transition: opacity $duration--fast-02 motion(standard, productive);
opacity: 1;
background: linear-gradient(rgba(57, 57, 57, 0), rgba(57, 57, 57, 1));
height: 3rem;
}

.focused {
opacity: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState } from 'react';
import {
FaceDissatisfied32,
FaceNeutral32,
FaceSatisfied32,
FaceDissatisfiedFilled32,
FaceNeutralFilled32,
FaceSatisfiedFilled32,
} from '@carbon/icons-react';
import cx from 'classnames';

import styles from './Experience.module.scss';

const NEGATIVE = 'feedback-form-negative';
const NEUTRAL = 'feedback-form-neutral';
const POSITIVE = 'feedback-form-positive';

const Experience = React.forwardRef(function Experience(props, ref) {
const [selected, setSelected] = useState(NEUTRAL);

const onExperienceChange = e => {
setSelected(e.target.id);
};

return (
<fieldset onChange={onExperienceChange}>
<legend>Rate your experience:</legend>
<div className={styles.experienceContainer}>
<label
className={cx(styles.experience, {
[styles.selected]: selected === NEGATIVE,
})}
htmlFor={NEGATIVE}
>
<input
ref={selected === NEGATIVE ? ref : undefined}
type="radio"
id={NEGATIVE}
defaultChecked={selected === NEGATIVE}
name="feedback-form-experience"
value="Negative"
/>
<span>Negative</span>
{selected === NEGATIVE ? (
<FaceDissatisfiedFilled32 />
) : (
<FaceDissatisfied32 />
)}
</label>

<label
className={cx(styles.experience, {
[styles.selected]: selected === NEUTRAL,
})}
htmlFor={NEUTRAL}
>
<input
ref={selected === NEUTRAL ? ref : undefined}
type="radio"
id={NEUTRAL}
defaultChecked={selected === NEUTRAL}
name="feedback-form-experience"
value="Neutral"
/>
<span>Neutral</span>
{selected === NEUTRAL ? <FaceNeutralFilled32 /> : <FaceNeutral32 />}
</label>

<label
className={cx(styles.experience, {
[styles.selected]: selected === POSITIVE,
})}
htmlFor={POSITIVE}
>
<input
ref={selected === POSITIVE ? ref : undefined}
type="radio"
id={POSITIVE}
defaultChecked={selected === POSITIVE}
name="feedback-form-experience"
value="Positive"
/>
<span>Positive</span>
{selected === POSITIVE ? (
<FaceSatisfiedFilled32 />
) : (
<FaceSatisfied32 />
)}
</label>
</div>
</fieldset>
);
});

export default Experience;
Loading

1 comment on commit cb12399

@vercel
Copy link

@vercel vercel bot commented on cb12399 Mar 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.