@atlaskit/contextual-survey 2.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/LICENSE +13 -0
  3. package/README.md +13 -0
  4. package/build/tsconfig.json +17 -0
  5. package/constants/package.json +7 -0
  6. package/dist/cjs/components/ContextualSurvey.js +274 -0
  7. package/dist/cjs/components/FeedbackAcknowledgement.js +26 -0
  8. package/dist/cjs/components/FeedbackScoreButtons.js +60 -0
  9. package/dist/cjs/components/SignUpPrompt.js +84 -0
  10. package/dist/cjs/components/SignUpSuccess.js +29 -0
  11. package/dist/cjs/components/SuccessContainer.js +35 -0
  12. package/dist/cjs/components/SurveyContainer.js +48 -0
  13. package/dist/cjs/components/SurveyForm.js +159 -0
  14. package/dist/cjs/components/SurveyMarshal.js +67 -0
  15. package/dist/cjs/components/useEscapeToDismiss.js +78 -0
  16. package/dist/cjs/constants.js +15 -0
  17. package/dist/cjs/index.js +35 -0
  18. package/dist/cjs/types.js +5 -0
  19. package/dist/cjs/version.json +5 -0
  20. package/dist/es2019/components/ContextualSurvey.js +178 -0
  21. package/dist/es2019/components/FeedbackAcknowledgement.js +12 -0
  22. package/dist/es2019/components/FeedbackScoreButtons.js +69 -0
  23. package/dist/es2019/components/SignUpPrompt.js +46 -0
  24. package/dist/es2019/components/SignUpSuccess.js +12 -0
  25. package/dist/es2019/components/SuccessContainer.js +22 -0
  26. package/dist/es2019/components/SurveyContainer.js +37 -0
  27. package/dist/es2019/components/SurveyForm.js +121 -0
  28. package/dist/es2019/components/SurveyMarshal.js +62 -0
  29. package/dist/es2019/components/useEscapeToDismiss.js +69 -0
  30. package/dist/es2019/constants.js +4 -0
  31. package/dist/es2019/index.js +2 -0
  32. package/dist/es2019/types.js +1 -0
  33. package/dist/es2019/version.json +5 -0
  34. package/dist/esm/components/ContextualSurvey.js +244 -0
  35. package/dist/esm/components/FeedbackAcknowledgement.js +13 -0
  36. package/dist/esm/components/FeedbackScoreButtons.js +44 -0
  37. package/dist/esm/components/SignUpPrompt.js +66 -0
  38. package/dist/esm/components/SignUpSuccess.js +16 -0
  39. package/dist/esm/components/SuccessContainer.js +21 -0
  40. package/dist/esm/components/SurveyContainer.js +30 -0
  41. package/dist/esm/components/SurveyForm.js +132 -0
  42. package/dist/esm/components/SurveyMarshal.js +55 -0
  43. package/dist/esm/components/useEscapeToDismiss.js +68 -0
  44. package/dist/esm/constants.js +4 -0
  45. package/dist/esm/index.js +2 -0
  46. package/dist/esm/types.js +1 -0
  47. package/dist/esm/version.json +5 -0
  48. package/dist/types/components/ContextualSurvey.d.ts +39 -0
  49. package/dist/types/components/FeedbackAcknowledgement.d.ts +2 -0
  50. package/dist/types/components/FeedbackScoreButtons.d.ts +6 -0
  51. package/dist/types/components/SignUpPrompt.d.ts +5 -0
  52. package/dist/types/components/SignUpSuccess.d.ts +4 -0
  53. package/dist/types/components/SuccessContainer.d.ts +6 -0
  54. package/dist/types/components/SurveyContainer.d.ts +7 -0
  55. package/dist/types/components/SurveyForm.d.ts +11 -0
  56. package/dist/types/components/SurveyMarshal.d.ts +13 -0
  57. package/dist/types/components/useEscapeToDismiss.d.ts +6 -0
  58. package/dist/types/constants.d.ts +2 -0
  59. package/dist/types/index.d.ts +3 -0
  60. package/dist/types/types.d.ts +5 -0
  61. package/docs/0-intro.tsx +115 -0
  62. package/package.json +58 -0
  63. package/tsconfig.json +16 -0
  64. package/types/package.json +7 -0
@@ -0,0 +1,13 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+
3
+ var _templateObject;
4
+
5
+ /** @jsx jsx */
6
+ import { css, jsx } from '@emotion/core';
7
+ import { fontSize, gridSize } from '@atlaskit/theme/constants';
8
+ import SuccessContainer from './SuccessContainer';
9
+ export default (function () {
10
+ return jsx(SuccessContainer, null, jsx("h1", {
11
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n font-size: ", "px;\n font-weight: 600;\n margin-top: 0;\n line-height: ", "px;\n "])), fontSize(), gridSize() * 3)
12
+ }, "Thanks for your feedback"));
13
+ });
@@ -0,0 +1,44 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+
3
+ var _templateObject, _templateObject2, _templateObject3, _templateObject4;
4
+
5
+ /** @jsx jsx */
6
+ import { css, jsx } from '@emotion/core';
7
+ import Button from '@atlaskit/button/custom-theme-button';
8
+ import { N200 } from '@atlaskit/theme/colors';
9
+ import { gridSize } from '@atlaskit/theme/constants';
10
+ import Tooltip from '@atlaskit/tooltip';
11
+ var tooltipMessage = ['Strongly disagree', 'Disagree', 'Slightly disagree', 'Neutral', 'Slightly agree', 'Agree', 'Strongly agree'];
12
+ export default (function (_ref) {
13
+ var onChange = _ref.onChange,
14
+ value = _ref.value;
15
+ return jsx("div", null, jsx("div", {
16
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n display: flex;\n justify-content: space-between;\n\n & > * + * {\n margin-left: ", "px;\n }\n\n & > * {\n flex: 1;\n\n & > button {\n justify-content: center;\n }\n }\n "])), gridSize())
17
+ }, Array.from({
18
+ length: 7
19
+ }, function (_, i) {
20
+ var score = i + 1;
21
+ var isSelected = value === score;
22
+ return jsx(Tooltip, {
23
+ content: tooltipMessage[i],
24
+ key: score,
25
+ hideTooltipOnClick: true
26
+ }, jsx(Button, {
27
+ onClick: function onClick() {
28
+ return onChange(score);
29
+ },
30
+ isSelected: isSelected,
31
+ "aria-pressed": isSelected,
32
+ "aria-describedby": "contextualSurveyStatement",
33
+ "aria-label": tooltipMessage[i],
34
+ shouldFitContainer: true
35
+ }, score));
36
+ })), jsx("div", {
37
+ css: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n font-size: 12px;\n font-weight: 600;\n color: ", ";\n display: flex;\n margin-top: ", "px;\n margin-bottom: ", "px;\n\n & > span {\n width: ", "px;\n }\n "])), N200, gridSize(), gridSize() * 3, gridSize() * 10),
38
+ "aria-hidden": true
39
+ }, jsx("span", null, "Strongly disagree"), jsx("span", {
40
+ css: css(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["\n text-align: center;\n margin: 0 auto;\n padding: 0 ", "px;\n "])), gridSize() * 6)
41
+ }, "Neutral"), jsx("span", {
42
+ css: css(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["\n text-align: right;\n "])))
43
+ }, "Strongly agree")));
44
+ });
@@ -0,0 +1,66 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
4
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
+
6
+ var _templateObject, _templateObject2;
7
+
8
+ /** @jsx jsx */
9
+ import { useCallback, useState } from 'react';
10
+ import { css, jsx } from '@emotion/core';
11
+ import Button from '@atlaskit/button/custom-theme-button';
12
+ import { fontSize, gridSize } from '@atlaskit/theme/constants';
13
+ import SuccessContainer from './SuccessContainer';
14
+ export default (function (_ref) {
15
+ var onAnswer = _ref.onAnswer;
16
+
17
+ var _useState = useState(null),
18
+ _useState2 = _slicedToArray(_useState, 2),
19
+ pending = _useState2[0],
20
+ setPending = _useState2[1];
21
+
22
+ var answeredWith = useCallback( /*#__PURE__*/function () {
23
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(answer) {
24
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
25
+ while (1) {
26
+ switch (_context.prev = _context.next) {
27
+ case 0:
28
+ setPending(answer ? 'yes' : 'no');
29
+ _context.next = 3;
30
+ return onAnswer(answer);
31
+
32
+ case 3:
33
+ case "end":
34
+ return _context.stop();
35
+ }
36
+ }
37
+ }, _callee);
38
+ }));
39
+
40
+ return function (_x) {
41
+ return _ref2.apply(this, arguments);
42
+ };
43
+ }(), [setPending, onAnswer]);
44
+ var isDisabled = Boolean(pending);
45
+ return jsx(SuccessContainer, null, jsx("h1", {
46
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n font-size: ", "px;\n font-weight: 600;\n margin: 0;\n line-height: ", "px;\n "])), fontSize(), gridSize() * 3)
47
+ }, "Thanks for your feedback"), jsx("p", null, "Are you interested in participating in our research?"), jsx("p", null, "Sign up for the", ' ', jsx("a", {
48
+ href: "https://www.atlassian.com/research-group"
49
+ }, "Atlassian Research Group"), ' ', "and we may contact you in the future with research opportunities."), jsx("div", {
50
+ css: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n margin-top: ", "px;\n display: flex;\n justify-content: flex-end;\n\n & > * + * {\n margin-left: ", "px;\n }\n "])), gridSize() * 4, gridSize())
51
+ }, jsx(Button, {
52
+ appearance: "subtle",
53
+ onClick: function onClick() {
54
+ return answeredWith(false);
55
+ },
56
+ isDisabled: isDisabled,
57
+ isLoading: pending === 'no'
58
+ }, "No, thanks"), jsx(Button, {
59
+ appearance: "primary",
60
+ onClick: function onClick() {
61
+ return answeredWith(true);
62
+ },
63
+ isDisabled: isDisabled,
64
+ isLoading: pending === 'yes'
65
+ }, "Yes, sign me up")));
66
+ });
@@ -0,0 +1,16 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+ import _objectDestructuringEmpty from "@babel/runtime/helpers/objectDestructuringEmpty";
3
+
4
+ var _templateObject;
5
+
6
+ /** @jsx jsx */
7
+ import { css, jsx } from '@emotion/core';
8
+ import { fontSize, gridSize } from '@atlaskit/theme/constants';
9
+ import SuccessContainer from './SuccessContainer';
10
+ export default (function (_ref) {
11
+ _objectDestructuringEmpty(_ref);
12
+
13
+ return jsx(SuccessContainer, null, jsx("h1", {
14
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n font-size: ", "px;\n font-weight: 600;\n line-height: ", "px;\n margin: 0;\n "])), fontSize(), gridSize() * 3)
15
+ }, "Thanks for signing up"), jsx("p", null, "We may reach out to you in the future to participate in additional research."));
16
+ });
@@ -0,0 +1,21 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+
3
+ var _templateObject, _templateObject2;
4
+
5
+ /** @jsx jsx */
6
+ import { css, jsx } from '@emotion/core';
7
+ import CheckCircleIcon from '@atlaskit/icon/glyph/check-circle';
8
+ import { G300 } from '@atlaskit/theme/colors';
9
+ import { gridSize } from '@atlaskit/theme/constants';
10
+ export default (function (_ref) {
11
+ var children = _ref.children;
12
+ return jsx("section", {
13
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n margin-left: ", "px;\n "])), gridSize() * 5)
14
+ }, jsx("div", {
15
+ css: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n position: absolute;\n top: ", "px;\n left: ", "px;\n "])), gridSize() * 3, gridSize() * 3)
16
+ }, jsx(CheckCircleIcon, {
17
+ label: "",
18
+ "aria-hidden": true,
19
+ primaryColor: G300
20
+ })), children);
21
+ });
@@ -0,0 +1,30 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+
3
+ var _templateObject, _templateObject2;
4
+
5
+ /** @jsx jsx */
6
+ import { css, jsx } from '@emotion/core';
7
+ import Button from '@atlaskit/button/custom-theme-button';
8
+ import CrossIcon from '@atlaskit/icon/glyph/cross';
9
+ import { N0, N50 } from '@atlaskit/theme/colors';
10
+ import { borderRadius, gridSize } from '@atlaskit/theme/constants';
11
+ import { e500 } from '@atlaskit/theme/elevation';
12
+ import { surveyInnerWidth } from '../constants';
13
+ var padding = gridSize() * 3;
14
+ export default (function (_ref) {
15
+ var children = _ref.children,
16
+ onDismiss = _ref.onDismiss;
17
+ return jsx("div", {
18
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n background-color: ", ";\n border-radius: ", "px;\n padding: ", "px;\n ", "\n width: ", "px;\n "])), N0, borderRadius(), padding, e500(), surveyInnerWidth)
19
+ }, jsx("div", {
20
+ css: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n position: absolute;\n top: ", "px;\n right: ", "px;\n "])), padding - gridSize(), padding - gridSize())
21
+ }, jsx(Button, {
22
+ iconBefore: jsx(CrossIcon, {
23
+ label: "",
24
+ primaryColor: N50
25
+ }),
26
+ "aria-label": "Dismiss",
27
+ appearance: "subtle",
28
+ onClick: onDismiss
29
+ })), children);
30
+ });
@@ -0,0 +1,132 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
3
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
+
5
+ var _templateObject, _templateObject2;
6
+
7
+ /** @jsx jsx */
8
+ import { useCallback, useRef, useState } from 'react';
9
+ import { css, jsx } from '@emotion/core';
10
+ import { Transition } from 'react-transition-group';
11
+ import Button from '@atlaskit/button/custom-theme-button';
12
+ import { Checkbox } from '@atlaskit/checkbox';
13
+ import Form, { CheckboxField, Field, FormFooter } from '@atlaskit/form';
14
+ import Textarea from '@atlaskit/textarea';
15
+ import { fontSize } from '@atlaskit/theme/constants';
16
+ import FeedbackScoreButtons from './FeedbackScoreButtons';
17
+
18
+ var getExpandedHeight = function getExpandedHeight(ref, state) {
19
+ if (!ref.current) {
20
+ return '0';
21
+ }
22
+
23
+ switch (state) {
24
+ case 'entering':
25
+ return "".concat(ref.current.scrollHeight, "px");
26
+
27
+ case 'entered':
28
+ // needed for TextField auto height expand
29
+ return "none";
30
+
31
+ default:
32
+ return '0';
33
+ }
34
+ };
35
+
36
+ var transitionDuration = 200;
37
+ export default (function (_ref) {
38
+ var question = _ref.question,
39
+ statement = _ref.statement,
40
+ textPlaceholder = _ref.textPlaceholder,
41
+ textLabel = _ref.textLabel,
42
+ onSubmit = _ref.onSubmit;
43
+
44
+ var _useState = useState(false),
45
+ _useState2 = _slicedToArray(_useState, 2),
46
+ expanded = _useState2[0],
47
+ setExpanded = _useState2[1];
48
+
49
+ var _useState3 = useState(false),
50
+ _useState4 = _slicedToArray(_useState3, 2),
51
+ canContactDefault = _useState4[0],
52
+ setCanContactDefault = _useState4[1];
53
+
54
+ var hasAutoFilledCanContactRef = useRef(false);
55
+ var expandedAreaRef = useRef(null);
56
+ var onScoreSelect = useCallback(function () {
57
+ setExpanded(true);
58
+ }, [setExpanded]); // On the first type the user types some feedback we auto select
59
+ // the option for allowing feedback. This automatic selection only
60
+ // happens once. After that it is up to the user to control
61
+
62
+ var onFeedbackChange = useCallback(function () {
63
+ if (hasAutoFilledCanContactRef.current) {
64
+ return;
65
+ }
66
+
67
+ hasAutoFilledCanContactRef.current = true;
68
+ setCanContactDefault(true);
69
+ }, []);
70
+ return jsx("section", {
71
+ "aria-labelledby": "contextualSurveyQuestion"
72
+ }, jsx("h1", {
73
+ id: "contextualSurveyQuestion",
74
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n font-size: ", "px;\n font-weight: 600;\n "])), fontSize())
75
+ }, question), statement && jsx("p", {
76
+ id: "contextualSurveyStatement"
77
+ }, statement), jsx(Form, {
78
+ onSubmit: onSubmit
79
+ }, function (_ref2) {
80
+ var formProps = _ref2.formProps,
81
+ submitting = _ref2.submitting;
82
+ return jsx("form", formProps, jsx(Field, {
83
+ name: "feedbackScore",
84
+ isDisabled: submitting,
85
+ isRequired: true
86
+ }, function (_ref3) {
87
+ var fieldProps = _ref3.fieldProps;
88
+ return jsx(FeedbackScoreButtons, _extends({}, fieldProps, {
89
+ onChange: function onChange(score) {
90
+ fieldProps.onChange(score);
91
+ onScoreSelect();
92
+ }
93
+ }));
94
+ }), jsx(Transition, {
95
+ in: expanded,
96
+ timeout: transitionDuration,
97
+ mountOnEnter: true
98
+ }, function (state) {
99
+ return jsx("div", {
100
+ css: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n transition: max-height ", "ms ease-in-out;\n overflow: hidden;\n max-height: ", ";\n "])), transitionDuration, getExpandedHeight(expandedAreaRef, state)),
101
+ ref: expandedAreaRef
102
+ }, jsx(Field, {
103
+ name: "writtenFeedback",
104
+ defaultValue: "",
105
+ isDisabled: submitting
106
+ }, function (_ref4) {
107
+ var fieldProps = _ref4.fieldProps;
108
+ return jsx(Textarea, _extends({}, fieldProps, {
109
+ "aria-label": textLabel,
110
+ placeholder: textPlaceholder,
111
+ onChange: function onChange(event) {
112
+ fieldProps.onChange(event);
113
+ onFeedbackChange();
114
+ }
115
+ }));
116
+ }), jsx(CheckboxField, {
117
+ name: "canContact",
118
+ isDisabled: submitting,
119
+ defaultIsChecked: canContactDefault
120
+ }, function (_ref5) {
121
+ var fieldProps = _ref5.fieldProps;
122
+ return jsx(Checkbox, _extends({}, fieldProps, {
123
+ label: "Atlassian can contact me about this feedback"
124
+ }));
125
+ }), jsx(FormFooter, null, jsx(Button, {
126
+ type: "submit",
127
+ appearance: "primary",
128
+ isLoading: submitting
129
+ }, "Submit")));
130
+ }));
131
+ }));
132
+ });
@@ -0,0 +1,55 @@
1
+ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral";
2
+
3
+ var _templateObject;
4
+
5
+ /** @jsx jsx */
6
+ import { css, jsx } from '@emotion/core';
7
+ import { Transition } from 'react-transition-group';
8
+ import { layers } from '@atlaskit/theme/constants';
9
+ import { surveyInnerWidth, surveyOffset } from '../constants';
10
+ var animationDuration = 300;
11
+ var offscreen = {
12
+ translateX: "".concat(surveyInnerWidth + surveyOffset, "px"),
13
+ opacity: '0'
14
+ };
15
+
16
+ var getAnimationProps = function getAnimationProps(state) {
17
+ switch (state) {
18
+ case 'entering':
19
+ {
20
+ return offscreen;
21
+ }
22
+
23
+ case 'entered':
24
+ {
25
+ return {
26
+ translateX: '0',
27
+ opacity: '1'
28
+ };
29
+ }
30
+
31
+ case 'exited':
32
+ case 'exiting':
33
+ {
34
+ return offscreen;
35
+ }
36
+ }
37
+ };
38
+
39
+ export default function SurveyMarshal(props) {
40
+ var children = props.children,
41
+ shouldShow = props.shouldShow;
42
+ return jsx(Transition, {
43
+ in: shouldShow,
44
+ timeout: animationDuration,
45
+ unmountOnExit: true
46
+ }, function (state) {
47
+ var _getAnimationProps = getAnimationProps(state),
48
+ translateX = _getAnimationProps.translateX,
49
+ opacity = _getAnimationProps.opacity;
50
+
51
+ return jsx("div", {
52
+ css: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n position: fixed;\n right: ", "px;\n bottom: ", "px;\n z-index: ", ";\n transform: translateX(", ");\n opacity: ", ";\n transition: all ", "ms ease-in-out;\n transition-property: transform, opacity;\n "])), surveyOffset, surveyOffset, layers.flag(), translateX, opacity, animationDuration)
53
+ }, children());
54
+ });
55
+ }
@@ -0,0 +1,68 @@
1
+ import { useEffect, useRef } from 'react';
2
+ export var escape = 27;
3
+
4
+ function bind(target, eventName, handler, options) {
5
+ target.addEventListener(eventName, handler, options);
6
+ return function unbind() {
7
+ target.removeEventListener(eventName, handler, options);
8
+ };
9
+ }
10
+
11
+ function shouldDismiss(target) {
12
+ if (!target) {
13
+ return true;
14
+ }
15
+
16
+ if (!(target instanceof HTMLElement)) {
17
+ return true;
18
+ } // Closest doesn't exist for ie11
19
+ // Because we cannot be sure if in a text area - just don't allow dismissing
20
+
21
+
22
+ if (!target.closest) {
23
+ return false;
24
+ }
25
+
26
+ var inTextArea = Boolean(target.closest('textarea')); // Allow dismissing if not in a textarea
27
+
28
+ return !inTextArea;
29
+ }
30
+
31
+ export default function useEscapeToDismiss(_ref) {
32
+ var onDismiss = _ref.onDismiss;
33
+ var onDismissRef = useRef(onDismiss); // Defensively accounting for consumer passing in a new function
34
+ // each time. We just want to call the latest one
35
+
36
+ useEffect(function () {
37
+ onDismissRef.current = onDismiss;
38
+ }, [onDismiss]);
39
+ useEffect(function () {
40
+ var unbind;
41
+
42
+ function onKeyDown(event) {
43
+ if (event.keyCode !== escape) {
44
+ return;
45
+ } // Escape pressed
46
+ // We don't want to close if the user is typing in the text area
47
+
48
+
49
+ if (!shouldDismiss(event.target)) {
50
+ return;
51
+ }
52
+
53
+ if (unbind) {
54
+ // only want to call dismiss once
55
+ unbind();
56
+ }
57
+
58
+ onDismissRef.current();
59
+ }
60
+
61
+ unbind = bind(window, 'keydown', // @ts-ignore: the typescript for this is lame
62
+ onKeyDown, {
63
+ passive: true
64
+ }); // double calls to unbind is fine
65
+
66
+ return unbind;
67
+ }, []);
68
+ }