@redocly/theme 0.46.2 → 0.46.4

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.
@@ -5,6 +5,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.markdocExample = void 0;
7
7
  const markdoc_1 = __importDefault(require("@markdoc/markdoc"));
8
+ // Walk through AST and look for raw markdown to parse
9
+ function parseInlineMarkdown(ast, config) {
10
+ if (Array.isArray(ast)) {
11
+ return ast.flatMap((child) => parseInlineMarkdown(child, config));
12
+ }
13
+ if (typeof ast === 'string') {
14
+ const parsed = markdoc_1.default.parse(ast);
15
+ return markdoc_1.default.transform(parsed, config);
16
+ }
17
+ if (ast && typeof ast === 'object' && ast.$$mdtype === 'Tag') {
18
+ const children = parseInlineMarkdown(ast.children, config);
19
+ return Object.assign(Object.assign({}, ast), { children });
20
+ }
21
+ return ast;
22
+ }
8
23
  // This custom tag prevents evaluating any children markdoc tags (no children) so we can
9
24
  // have markdoc examples in code fences
10
25
  // approach copied from: https://github.com/markdoc/docs/blob/main/markdoc/tags/markdoc-example.markdoc.js
@@ -30,8 +45,10 @@ exports.markdocExample = {
30
45
  const fenceWithTitle = annotations.find((annotation) => annotation.name === 'title');
31
46
  title = fenceWithTitle === null || fenceWithTitle === void 0 ? void 0 : fenceWithTitle.value;
32
47
  }
48
+ const demoContent = node.children[0].transformChildren(config);
49
+ const parsedDemoContent = parseInlineMarkdown(demoContent, config);
33
50
  return new markdoc_1.default.Tag('MarkdocExample', Object.assign(Object.assign({}, attributes), { title,
34
- language, demoContent: node.children[0].transformChildren(config), rawContent: content }), []);
51
+ language, demoContent: parsedDemoContent, rawContent: content }), []);
35
52
  },
36
53
  },
37
54
  tagName: 'markdoc-example',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.46.2",
3
+ "version": "0.46.4",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -79,7 +79,7 @@
79
79
  "timeago.js": "4.0.2",
80
80
  "i18next": "22.4.15",
81
81
  "nprogress": "0.2.0",
82
- "@redocly/config": "0.19.1"
82
+ "@redocly/config": "0.19.2"
83
83
  },
84
84
  "scripts": {
85
85
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -1,14 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect } from 'react';
3
3
  import styled from 'styled-components';
4
- import { ReasonsSettingsSchema } from '@redocly/config';
5
4
 
5
+ import type { AnonymousUserEmailSettings, ReasonsSettingsSchema } from '@redocly/config';
6
6
  import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
7
7
 
8
+ import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
8
9
  import { useThemeHooks } from '@redocly/theme/core/hooks';
9
10
  import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
10
11
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
11
- import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
12
12
  import { Button } from '@redocly/theme/components/Button/Button';
13
13
  import { FaceDissatisfiedIcon } from '@redocly/theme/icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon';
14
14
  import { FaceSatisfiedIcon } from '@redocly/theme/icons/FaceSatisfiedIcon/FaceSatisfiedIcon';
@@ -21,7 +21,12 @@ export enum MOOD_STATES {
21
21
  }
22
22
 
23
23
  export type MoodProps = {
24
- onSubmit: (value: { score: number; comment?: string; reasons?: string[] }) => void;
24
+ onSubmit: (value: {
25
+ score: number;
26
+ comment?: string;
27
+ reasons?: string[];
28
+ email?: string;
29
+ }) => void;
25
30
  settings?: {
26
31
  label?: string;
27
32
  submitText?: string;
@@ -36,19 +41,33 @@ export type MoodProps = {
36
41
  neutral?: ReasonsSettingsSchema;
37
42
  dissatisfied?: ReasonsSettingsSchema;
38
43
  };
44
+ anonymousUserEmail?: AnonymousUserEmailSettings;
39
45
  };
40
46
  className?: string;
41
47
  };
42
48
 
43
49
  export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element {
44
- const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
50
+ const {
51
+ label,
52
+ submitText,
53
+ comment: commentSettings,
54
+ reasons: reasonsSettings,
55
+ anonymousUserEmail: anonymousUserEmailSettings,
56
+ } = settings || {};
45
57
  const [score, setScore] = React.useState('');
46
58
  const [isSubmitted, setIsSubmitted] = React.useState(false);
47
59
  const [comment, setComment] = React.useState('');
48
60
  const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
49
- const { useTranslate } = useThemeHooks();
61
+ const [email, setEmail] = React.useState<string>();
62
+ const { useTranslate, useUserMenu } = useThemeHooks();
63
+ const { userData } = useUserMenu();
50
64
  const { translate } = useTranslate();
51
65
 
66
+ const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
67
+ const value = e.target.value;
68
+ setEmail(value || undefined);
69
+ };
70
+
52
71
  const checkIfShouldDisplayReasons = (score: string) => {
53
72
  if (!score || !reasonsSettings) {
54
73
  return false;
@@ -112,26 +131,32 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
112
131
  const displayReasons = checkIfShouldDisplayReasons(score);
113
132
  const displayComment = !!(score && !commentSettings?.hide);
114
133
  const displaySubmitBnt = !!(score && (displayReasons || displayComment));
134
+ const displayFeedbackEmail =
135
+ !!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
115
136
 
116
137
  const onSubmitMoodForm = () => {
117
138
  onSubmit({
118
139
  score: remapScore(score),
119
140
  comment,
120
141
  reasons,
142
+ email,
121
143
  });
122
144
  setIsSubmitted(true);
123
145
  };
124
146
 
125
147
  const onCancelMoodForm = () => {
126
148
  setScore('');
149
+ setComment('');
150
+ setReasons([]);
151
+ setEmail(undefined);
127
152
  };
128
153
 
129
154
  useEffect(() => {
130
- if (score && !displayComment && !displayReasons) {
155
+ if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
131
156
  onSubmitMoodForm();
132
157
  }
133
158
  // eslint-disable-next-line react-hooks/exhaustive-deps
134
- }, [score, displayComment, displayReasons]);
159
+ }, [score, displayComment, displayReasons, displayFeedbackEmail]);
135
160
 
136
161
  if (isSubmitted) {
137
162
  return (
@@ -152,7 +177,7 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
152
177
 
153
178
  return (
154
179
  <MoodWrapper data-component-name="Feedback/Mood" className={className}>
155
- <StyledForm>
180
+ <StyledForm onSubmit={onSubmitMoodForm}>
156
181
  <StyledFormMandatoryFields>
157
182
  <Label data-translation-key="feedback.settings.label">
158
183
  {label || translate('feedback.settings.label', 'Was this helpful?')}
@@ -213,12 +238,34 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
213
238
  )}
214
239
  </StyledFormOptionalFields>
215
240
  )}
241
+
242
+ {displayFeedbackEmail && (
243
+ <StyledFormOptionalFields>
244
+ <Label data-translation-key="feedback.settings.anonymousUserEmail.label">
245
+ {anonymousUserEmailSettings?.label ||
246
+ translate(
247
+ 'feedback.settings.anonymousUserEmail.label',
248
+ 'Your email (optional, for follow-up)',
249
+ )}
250
+ </Label>
251
+ <EmailInput
252
+ onChange={onEmailChange}
253
+ placeholder={
254
+ anonymousUserEmailSettings?.placeholder ||
255
+ translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
256
+ }
257
+ type="email"
258
+ required={!!email}
259
+ />
260
+ </StyledFormOptionalFields>
261
+ )}
262
+
216
263
  {displaySubmitBnt && (
217
264
  <ButtonsContainer>
218
265
  <Button onClick={onCancelMoodForm} variant="text" size="small">
219
266
  Cancel
220
267
  </Button>
221
- <Button onClick={onSubmitMoodForm} variant="secondary" size="small">
268
+ <Button type="submit" variant="secondary" size="small">
222
269
  Submit
223
270
  </Button>
224
271
  </ButtonsContainer>
@@ -291,3 +338,13 @@ const StyledMandatoryFieldContainer = styled.div`
291
338
  align-items: center;
292
339
  gap: var(--spacing-xxs);
293
340
  `;
341
+
342
+ const EmailInput = styled.input`
343
+ background-color: var(--bg-color);
344
+ border-radius: var(--border-radius-lg);
345
+ border: var(--input-border);
346
+ outline: none;
347
+ color: var(--feedback-text-color);
348
+ font-family: var(--feedback-font-family);
349
+ padding: 10px;
350
+ `;
@@ -1,20 +1,27 @@
1
1
  import * as React from 'react';
2
- import styled from 'styled-components';
3
2
  import { useEffect } from 'react';
3
+ import styled from 'styled-components';
4
4
 
5
+ import type { AnonymousUserEmailSettings } from '@redocly/config';
5
6
  import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
6
7
 
8
+ import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
7
9
  import { useThemeHooks } from '@redocly/theme/core/hooks';
8
10
  import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
9
11
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
10
- import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
11
12
  import { Stars } from '@redocly/theme/components/Feedback/Stars';
12
13
  import { Button } from '@redocly/theme/components/Button/Button';
13
14
 
14
15
  export const FEEDBACK_MAX_RATING = 5;
15
16
 
16
17
  export type RatingProps = {
17
- onSubmit: (value: { score: number; comment?: string; reasons?: string[]; max: number }) => void;
18
+ onSubmit: (value: {
19
+ score: number;
20
+ comment?: string;
21
+ reasons?: string[];
22
+ max: number;
23
+ email?: string;
24
+ }) => void;
18
25
  settings?: {
19
26
  label?: string;
20
27
  submitText?: string;
@@ -28,43 +35,63 @@ export type RatingProps = {
28
35
  component?: string;
29
36
  items: string[];
30
37
  };
38
+ anonymousUserEmail?: AnonymousUserEmailSettings;
31
39
  };
32
40
  className?: string;
33
41
  };
34
42
 
35
43
  export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Element {
36
- const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
44
+ const {
45
+ label,
46
+ submitText,
47
+ comment: commentSettings,
48
+ reasons: reasonsSettings,
49
+ anonymousUserEmail: anonymousUserEmailSettings,
50
+ } = settings || {};
37
51
  const [isSubmitted, setIsSubmitted] = React.useState(false);
38
52
  const [score, setScore] = React.useState(0);
39
53
  const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
40
54
  const [comment, setComment] = React.useState('');
41
- const { useTranslate } = useThemeHooks();
55
+ const [email, setEmail] = React.useState<string>();
56
+ const { useTranslate, useUserMenu } = useThemeHooks();
57
+ const { userData } = useUserMenu();
42
58
  const { translate } = useTranslate();
43
59
 
60
+ const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
61
+ const value = e.target.value;
62
+ setEmail(value || undefined);
63
+ };
64
+
44
65
  const onSubmitRatingForm = () => {
45
66
  onSubmit({
46
67
  score,
47
68
  comment,
48
69
  reasons,
49
70
  max: FEEDBACK_MAX_RATING,
71
+ email,
50
72
  });
51
73
  setIsSubmitted(true);
52
74
  };
53
75
 
54
76
  const onCancelRatingForm = () => {
55
77
  setScore(0);
78
+ setComment('');
79
+ setReasons([]);
80
+ setEmail(undefined);
56
81
  };
57
82
 
58
83
  const displayReasons = !!(score && reasonsSettings && !reasonsSettings.hide);
59
84
  const displayComment = !!(score && !commentSettings?.hide);
60
85
  const displaySubmitBnt = !!(score && (displayReasons || displayComment));
86
+ const displayFeedbackEmail =
87
+ !!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
61
88
 
62
89
  useEffect(() => {
63
- if (score && !displayComment && !displayReasons) {
90
+ if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
64
91
  onSubmitRatingForm();
65
92
  }
66
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
- }, [score, displayComment, displayReasons]);
94
+ }, [score, displayComment, displayReasons, displayFeedbackEmail]);
68
95
 
69
96
  if (isSubmitted) {
70
97
  return (
@@ -95,7 +122,6 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
95
122
  <Stars max={FEEDBACK_MAX_RATING} onChange={setScore} value={score} />
96
123
  </StyledMandatoryFieldContainer>
97
124
  </StyledFormMandatoryFields>
98
-
99
125
  {(displayReasons || displayComment) && (
100
126
  <StyledFormOptionalFields>
101
127
  {displayReasons && (
@@ -125,6 +151,28 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
125
151
  )}
126
152
  </StyledFormOptionalFields>
127
153
  )}
154
+
155
+ {displayFeedbackEmail && (
156
+ <StyledFormOptionalFields>
157
+ <Label data-translation-key="feedback.settings.anonymousUserEmail.label">
158
+ {anonymousUserEmailSettings?.label ||
159
+ translate(
160
+ 'feedback.settings.anonymousUserEmail.label',
161
+ 'Your email (optional, for follow-up)',
162
+ )}
163
+ </Label>
164
+ <EmailInput
165
+ onChange={onEmailChange}
166
+ placeholder={
167
+ anonymousUserEmailSettings?.placeholder ||
168
+ translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
169
+ }
170
+ type="email"
171
+ required={!!email}
172
+ />
173
+ </StyledFormOptionalFields>
174
+ )}
175
+
128
176
  {displaySubmitBnt && (
129
177
  <ButtonsContainer>
130
178
  <Button onClick={onCancelRatingForm} variant="text" size="small">
@@ -185,3 +233,13 @@ const ButtonsContainer = styled.div`
185
233
  margin-bottom: var(--spacing-xxs);
186
234
  gap: var(--spacing-xxs);
187
235
  `;
236
+
237
+ const EmailInput = styled.input`
238
+ background-color: var(--bg-color);
239
+ border-radius: var(--border-radius-lg);
240
+ border: var(--input-border);
241
+ outline: none;
242
+ color: var(--feedback-text-color);
243
+ font-family: var(--feedback-font-family);
244
+ padding: 10px;
245
+ `;
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import { useEffect } from 'react';
3
3
  import styled from 'styled-components';
4
4
 
5
+ import type { AnonymousUserEmailSettings } from '@redocly/config';
5
6
  import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
6
7
 
7
8
  import { breakpoints } from '@redocly/theme/core/utils';
@@ -14,7 +15,13 @@ import { Button } from '@redocly/theme/components/Button/Button';
14
15
  export const MAX_SCALE = 10;
15
16
 
16
17
  export type ScaleProps = {
17
- onSubmit: (value: { score: number; comment?: string; reasons?: string[]; max?: number }) => void;
18
+ onSubmit: (value: {
19
+ score: number;
20
+ comment?: string;
21
+ reasons?: string[];
22
+ max?: number;
23
+ email?: string;
24
+ }) => void;
18
25
  settings?: {
19
26
  label?: string;
20
27
  submitText?: string;
@@ -30,6 +37,7 @@ export type ScaleProps = {
30
37
  component?: string;
31
38
  items: string[];
32
39
  };
40
+ anonymousUserEmail?: AnonymousUserEmailSettings;
33
41
  };
34
42
  className?: string;
35
43
  };
@@ -42,14 +50,22 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
42
50
  rightScaleLabel,
43
51
  comment: commentSettings,
44
52
  reasons: reasonsSettings,
53
+ anonymousUserEmail: anonymousUserEmailSettings,
45
54
  } = settings || {};
46
55
  const [score, setScore] = React.useState(0);
47
56
  const [isSubmitted, setIsSubmitted] = React.useState(false);
48
57
  const [comment, setComment] = React.useState('');
49
58
  const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
50
- const { useTranslate } = useThemeHooks();
59
+ const [email, setEmail] = React.useState<string>();
60
+ const { useTranslate, useUserMenu } = useThemeHooks();
61
+ const { userData } = useUserMenu();
51
62
  const { translate } = useTranslate();
52
63
 
64
+ const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
65
+ const value = e.target.value;
66
+ setEmail(value || undefined);
67
+ };
68
+
53
69
  const scaleOptions = [];
54
70
 
55
71
  for (let i = 1; i <= MAX_SCALE; i++) {
@@ -69,11 +85,14 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
69
85
  const displayReasons = !!score && reasonsSettings && !reasonsSettings.hide;
70
86
  const displayComment = !!(score && !commentSettings?.hide);
71
87
  const displaySubmitBnt = !!score && (displayReasons || displayComment);
88
+ const displayFeedbackEmail =
89
+ !!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
72
90
 
73
91
  const handleCancel = () => {
74
92
  setScore(0);
75
93
  setComment('');
76
94
  setReasons([]);
95
+ setEmail(undefined);
77
96
  };
78
97
 
79
98
  const onSubmitScaleForm = () => {
@@ -82,16 +101,17 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
82
101
  comment,
83
102
  reasons,
84
103
  max: MAX_SCALE,
104
+ email,
85
105
  });
86
106
  setIsSubmitted(true);
87
107
  };
88
108
 
89
109
  useEffect(() => {
90
- if (score && !displayComment && !displayReasons) {
110
+ if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
91
111
  onSubmitScaleForm();
92
112
  }
93
113
  // eslint-disable-next-line react-hooks/exhaustive-deps
94
- }, [score, displayComment, displayReasons]);
114
+ }, [score, displayComment, displayReasons, displayFeedbackEmail]);
95
115
 
96
116
  if (isSubmitted) {
97
117
  return (
@@ -110,7 +130,7 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
110
130
 
111
131
  return (
112
132
  <ScaleWrapper data-component-name="Feedback/Scale" className={className}>
113
- <StyledForm>
133
+ <StyledForm onSubmit={onSubmitScaleForm}>
114
134
  <StyledFormMandatoryFields>
115
135
  <Label data-translation-key="feedback.settings.label">
116
136
  {label || translate('feedback.settings.label', 'Was this helpful?')}
@@ -156,6 +176,28 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
156
176
  />
157
177
  )}
158
178
  </StyledFormOptionalFields>
179
+
180
+ {displayFeedbackEmail && (
181
+ <StyledFormOptionalFields>
182
+ <Label data-translation-key="feedback.settings.anonymousUserEmail.label">
183
+ {anonymousUserEmailSettings?.label ||
184
+ translate(
185
+ 'feedback.settings.anonymousUserEmail.label',
186
+ 'Your email (optional, for follow-up)',
187
+ )}
188
+ </Label>
189
+ <EmailInput
190
+ onChange={onEmailChange}
191
+ placeholder={
192
+ anonymousUserEmailSettings?.placeholder ||
193
+ translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
194
+ }
195
+ type="email"
196
+ required={!!email}
197
+ />
198
+ </StyledFormOptionalFields>
199
+ )}
200
+
159
201
  {displaySubmitBnt && (
160
202
  <ButtonsContainer>
161
203
  <Button
@@ -169,7 +211,7 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
169
211
 
170
212
  <Button
171
213
  data-translation-key="feedback.settings.scale.send"
172
- onClick={onSubmitScaleForm}
214
+ type="submit"
173
215
  variant="secondary"
174
216
  size="small"
175
217
  >
@@ -259,3 +301,13 @@ const ScaleOptionsWrapper = styled.div`
259
301
  gap: 2px;
260
302
  }
261
303
  `;
304
+
305
+ const EmailInput = styled.input`
306
+ background-color: var(--bg-color);
307
+ border-radius: var(--border-radius-lg);
308
+ border: var(--input-border);
309
+ outline: none;
310
+ color: var(--feedback-text-color);
311
+ font-family: var(--feedback-font-family);
312
+ padding: 10px;
313
+ `;
@@ -1,20 +1,25 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect } from 'react';
3
3
  import styled from 'styled-components';
4
- import { ReasonsSettingsSchema } from '@redocly/config';
5
4
 
5
+ import type { ReasonsSettingsSchema, AnonymousUserEmailSettings } from '@redocly/config';
6
6
  import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
7
7
 
8
+ import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
8
9
  import { useThemeHooks } from '@redocly/theme/core/hooks';
9
10
  import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
10
11
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
11
- import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
12
12
  import { Button } from '@redocly/theme/components/Button/Button';
13
13
  import { ThumbDownIcon } from '@redocly/theme/icons/ThumbDownIcon/ThumbDownIcon';
14
14
  import { ThumbUpIcon } from '@redocly/theme/icons/ThumbUpIcon/ThumbUpIcon';
15
15
 
16
16
  export type SentimentProps = {
17
- onSubmit: (value: { score: number; comment?: string; reasons?: string[] }) => void;
17
+ onSubmit: (value: {
18
+ score: number;
19
+ comment?: string;
20
+ reasons?: string[];
21
+ email?: string;
22
+ }) => void;
18
23
  settings?: {
19
24
  label?: string;
20
25
  submitText?: string;
@@ -27,19 +32,33 @@ export type SentimentProps = {
27
32
  like?: ReasonsSettingsSchema;
28
33
  dislike?: ReasonsSettingsSchema;
29
34
  };
35
+ anonymousUserEmail?: AnonymousUserEmailSettings;
30
36
  };
31
37
  className?: string;
32
38
  };
33
39
 
34
40
  export function Sentiment({ settings, onSubmit, className }: SentimentProps): JSX.Element {
35
- const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
41
+ const {
42
+ label,
43
+ submitText,
44
+ comment: commentSettings,
45
+ reasons: reasonsSettings,
46
+ anonymousUserEmail: anonymousUserEmailSettings,
47
+ } = settings || {};
36
48
  const [isSubmitted, setIsSubmitted] = React.useState(false);
37
49
  const [score, setScore] = React.useState(0);
38
50
  const [comment, setComment] = React.useState('');
39
51
  const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
40
- const { useTranslate } = useThemeHooks();
52
+ const [email, setEmail] = React.useState<string>();
53
+ const { useTranslate, useUserMenu } = useThemeHooks();
54
+ const { userData } = useUserMenu();
41
55
  const { translate } = useTranslate();
42
56
 
57
+ const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
58
+ const value = e.target.value;
59
+ setEmail(value || undefined);
60
+ };
61
+
43
62
  const getScoreSpecificReasonSettings = (score: number) => {
44
63
  switch (score) {
45
64
  case 1:
@@ -68,6 +87,8 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
68
87
  const displayReasons = checkIfShouldDisplayReasons(score);
69
88
  const displayComment = !!(score && !commentSettings?.hide);
70
89
  const displaySubmitBnt = !!(score && (displayReasons || displayComment));
90
+ const displayFeedbackEmail =
91
+ !!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
71
92
 
72
93
  const commentLabel =
73
94
  score === 1
@@ -100,20 +121,24 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
100
121
  score,
101
122
  comment,
102
123
  reasons,
124
+ email,
103
125
  });
104
126
  setIsSubmitted(true);
105
127
  };
106
128
 
107
129
  const onCancelSentimentForm = () => {
108
130
  setScore(0);
131
+ setComment('');
132
+ setReasons([]);
133
+ setEmail(undefined);
109
134
  };
110
135
 
111
136
  useEffect(() => {
112
- if (score && !displayComment && !displayReasons) {
137
+ if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
113
138
  onSubmitSentimentForm();
114
139
  }
115
140
  // eslint-disable-next-line react-hooks/exhaustive-deps
116
- }, [score, displayComment, displayReasons]);
141
+ }, [score, displayComment, displayReasons, displayFeedbackEmail]);
117
142
 
118
143
  if (isSubmitted) {
119
144
  return (
@@ -186,6 +211,28 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
186
211
  )}
187
212
  </StyledFormOptionalFields>
188
213
  )}
214
+
215
+ {displayFeedbackEmail && (
216
+ <StyledFormOptionalFields>
217
+ <Label data-translation-key="feedback.settings.anonymousUserEmail.label">
218
+ {anonymousUserEmailSettings?.label ||
219
+ translate(
220
+ 'feedback.settings.anonymousUserEmail.label',
221
+ 'Your email (optional, for follow-up)',
222
+ )}
223
+ </Label>
224
+ <EmailInput
225
+ onChange={onEmailChange}
226
+ placeholder={
227
+ anonymousUserEmailSettings?.placeholder ||
228
+ translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
229
+ }
230
+ type="email"
231
+ required={!!email}
232
+ />
233
+ </StyledFormOptionalFields>
234
+ )}
235
+
189
236
  {displaySubmitBnt && (
190
237
  <ButtonsContainer>
191
238
  <Button onClick={onCancelSentimentForm} variant="text" size="small">
@@ -256,3 +303,13 @@ const ButtonsContainer = styled.div`
256
303
  margin-bottom: var(--spacing-xxs);
257
304
  gap: var(--spacing-xxs);
258
305
  `;
306
+
307
+ const EmailInput = styled.input`
308
+ background-color: var(--bg-color);
309
+ border-radius: var(--border-radius-lg);
310
+ border: var(--input-border);
311
+ outline: none;
312
+ color: var(--feedback-text-color);
313
+ font-family: var(--feedback-font-family);
314
+ padding: 10px;
315
+ `;
@@ -280,6 +280,13 @@ const SearchOverlay = styled.div`
280
280
  height: 100vh;
281
281
  background: var(--bg-color-modal-overlay);
282
282
  z-index: var(--z-index-overlay);
283
+
284
+ @media screen and (max-width: ${breakpoints.small}) {
285
+ align-items: start;
286
+ position: fixed;
287
+ overflow: hidden;
288
+ overscroll-behavior: none;
289
+ }
283
290
  `;
284
291
 
285
292
  const SearchDialogWrapper = styled.div`
@@ -293,9 +300,10 @@ const SearchDialogWrapper = styled.div`
293
300
  border-radius: 0;
294
301
 
295
302
  @media screen and (max-width: ${breakpoints.small}) {
296
- /* Ignore resize on mobile */
303
+ min-height: -webkit-fill-available !important;
304
+ min-height: 100dvh !important;
305
+ height: auto !important;
297
306
  width: 100vw !important;
298
- height: 100vh !important;
299
307
  }
300
308
 
301
309
  @media screen and (min-width: ${breakpoints.small}) {
@@ -323,6 +331,10 @@ const SearchDialogBody = styled.div`
323
331
  flex-direction: row;
324
332
  flex-grow: 1;
325
333
  overflow: hidden;
334
+
335
+ @media screen and (max-width: ${breakpoints.small}) {
336
+ min-height: 0;
337
+ }
326
338
  `;
327
339
 
328
340
  const SearchDialogBodyMainView = styled.div`