@redocly/theme 0.6.5 → 0.7.0

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 (61) hide show
  1. package/lib/Feedback/Comment.d.ts +3 -0
  2. package/lib/Feedback/Comment.js +80 -0
  3. package/lib/Feedback/Rating.js +23 -10
  4. package/lib/Feedback/Reasons.d.ts +3 -0
  5. package/lib/Feedback/Reasons.js +85 -0
  6. package/lib/Feedback/Sentiment.js +28 -4
  7. package/lib/Feedback/index.d.ts +4 -2
  8. package/lib/Feedback/index.js +7 -3
  9. package/lib/Feedback/types.d.ts +48 -3
  10. package/lib/Markdown/Tabs/Tab.js +11 -5
  11. package/lib/Markdown/Tabs/Tabs.js +14 -5
  12. package/lib/Navbar/Navbar.js +3 -1
  13. package/lib/Pages/Forbidden.d.ts +2 -0
  14. package/lib/Pages/Forbidden.js +39 -0
  15. package/lib/Pages/NotFound.d.ts +2 -0
  16. package/lib/Pages/NotFound.js +39 -0
  17. package/lib/Pages/index.d.ts +1 -0
  18. package/lib/Pages/index.js +18 -0
  19. package/lib/Search/Autocomplete.d.ts +4 -1
  20. package/lib/Search/Autocomplete.js +19 -3
  21. package/lib/Search/ClearIcon.js +1 -1
  22. package/lib/Search/Input.js +1 -1
  23. package/lib/Search/Search.js +6 -1
  24. package/lib/Search/SearchIcon.js +1 -1
  25. package/lib/Search/ShortcutKey.d.ts +7 -0
  26. package/lib/Search/ShortcutKey.js +35 -0
  27. package/lib/config.d.ts +8 -0
  28. package/lib/config.js +1 -0
  29. package/lib/globalStyle.js +59 -1
  30. package/lib/index.d.ts +1 -0
  31. package/lib/index.js +1 -0
  32. package/lib/mocks/Link.js +1 -1
  33. package/lib/mocks/hooks/index.js +5 -1
  34. package/lib/mocks/search.js +18 -5
  35. package/lib/ui/darkColors.js +5 -0
  36. package/package.json +8 -4
  37. package/src/Feedback/Comment.tsx +64 -0
  38. package/src/Feedback/Rating.tsx +45 -17
  39. package/src/Feedback/Reasons.tsx +81 -0
  40. package/src/Feedback/Sentiment.tsx +44 -5
  41. package/src/Feedback/index.ts +4 -2
  42. package/src/Feedback/types.ts +37 -3
  43. package/src/Markdown/Tabs/Tab.tsx +11 -5
  44. package/src/Markdown/Tabs/Tabs.tsx +14 -5
  45. package/src/Navbar/Navbar.tsx +5 -1
  46. package/src/Pages/Forbidden.tsx +42 -0
  47. package/src/Pages/NotFound.tsx +42 -0
  48. package/src/Pages/index.ts +1 -0
  49. package/src/Search/Autocomplete.tsx +26 -2
  50. package/src/Search/ClearIcon.tsx +1 -1
  51. package/src/Search/Input.tsx +1 -1
  52. package/src/Search/Search.tsx +3 -0
  53. package/src/Search/SearchIcon.tsx +1 -1
  54. package/src/Search/ShortcutKey.tsx +35 -0
  55. package/src/config.ts +4 -0
  56. package/src/globalStyle.ts +60 -1
  57. package/src/index.ts +1 -0
  58. package/src/mocks/Link.tsx +2 -1
  59. package/src/mocks/hooks/index.ts +5 -1
  60. package/src/mocks/search.ts +20 -5
  61. package/src/ui/darkColors.tsx +5 -0
@@ -35,12 +35,17 @@ const Autocomplete_1 = require("../Search/Autocomplete");
35
35
  const ClearIcon_1 = require("../Search/ClearIcon");
36
36
  const SearchIcon_1 = require("../Search/SearchIcon");
37
37
  const SearchItem_1 = require("../Search/SearchItem");
38
+ const hooks_1 = require("../hooks");
38
39
  function Search() {
39
40
  const history = (0, usePreloadHistory_1.usePreloadHistory)();
40
41
  const { query, setQuery, items } = (0, search_1.useFuseSearch)();
42
+ const themeSettings = (0, hooks_1.useThemeConfig)();
41
43
  // TODO: ask somebody about typings
42
44
  const navigate = (item) => history.push(item.url);
43
- const renderAutocomplete = () => (React.createElement(Autocomplete_1.Autocomplete, { items: items, value: query, change: setQuery, select: navigate, placeholder: "Search the docs", renderItem: (item) => React.createElement(SearchItem_1.SearchItem, { key: item.id, item: item }) }, (isOpen, reset) => (isOpen ? React.createElement(ClearIcon_1.ClearIcon, { onClick: reset }) : React.createElement(SearchIcon_1.SearchIcon, null))));
45
+ const renderAutocomplete = () => {
46
+ var _a, _b;
47
+ return (React.createElement(Autocomplete_1.Autocomplete, { items: items, value: query, change: setQuery, select: navigate, placeholder: "Search the docs", keyShortcuts: (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : ['/'], renderItem: (item) => React.createElement(SearchItem_1.SearchItem, { key: item.id, item: item }) }, (isOpen, reset) => (isOpen ? React.createElement(ClearIcon_1.ClearIcon, { onClick: reset }) : React.createElement(SearchIcon_1.SearchIcon, null))));
48
+ };
44
49
  return React.createElement(Wrapper, { "data-component-name": "Search/Search" }, renderAutocomplete());
45
50
  }
46
51
  exports.Search = Search;
@@ -15,7 +15,7 @@ exports.SearchIcon = (0, styled_components_1.default)(Icon).attrs(() => ({
15
15
  cursor: pointer;
16
16
  width: 1em;
17
17
  height: 1em;
18
- right: 1em;
18
+ left: 0.8em;
19
19
  fill: var(--search-input-text-color);
20
20
  z-index: -1;
21
21
 
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ interface ShortcutKeyProps {
3
+ keyShortcuts?: string | string[];
4
+ }
5
+ export declare function ShortcutKey(props: ShortcutKeyProps): JSX.Element;
6
+ export declare const Wrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export {};
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Wrapper = exports.ShortcutKey = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const styled_components_1 = __importDefault(require("styled-components"));
9
+ function ShortcutKey(props) {
10
+ let mainShortcutKey = null;
11
+ if (props.keyShortcuts) {
12
+ if (Array.isArray(props.keyShortcuts)) {
13
+ mainShortcutKey = props.keyShortcuts[0];
14
+ }
15
+ else {
16
+ mainShortcutKey = props.keyShortcuts;
17
+ }
18
+ }
19
+ mainShortcutKey = mainShortcutKey === null || mainShortcutKey === void 0 ? void 0 : mainShortcutKey.toUpperCase();
20
+ return react_1.default.createElement(exports.Wrapper, null, mainShortcutKey);
21
+ }
22
+ exports.ShortcutKey = ShortcutKey;
23
+ exports.Wrapper = styled_components_1.default.div `
24
+ position: absolute;
25
+ cursor: pointer;
26
+ font-size: 0.8em;
27
+ height: 2em;
28
+ line-height: 2em;
29
+ right: 1em;
30
+ fill: var(--search-input-text-color);
31
+ color: var(--search-input-placeholder-color);
32
+ opacity: 0.5;
33
+ z-index: -1;
34
+ `;
35
+ //# sourceMappingURL=ShortcutKey.js.map
package/lib/config.d.ts CHANGED
@@ -244,14 +244,17 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
244
244
  }>>>;
245
245
  search: z.ZodOptional<z.ZodDefault<z.ZodObject<z.extendShape<{
246
246
  placement: z.ZodOptional<z.ZodDefault<z.ZodString>>;
247
+ shortcuts: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString, "many">>>;
247
248
  }, {
248
249
  hide: z.ZodOptional<z.ZodBoolean>;
249
250
  }>, "strict", z.ZodTypeAny, {
250
251
  hide?: boolean | undefined;
251
252
  placement?: string | undefined;
253
+ shortcuts?: string[] | undefined;
252
254
  }, {
253
255
  hide?: boolean | undefined;
254
256
  placement?: string | undefined;
257
+ shortcuts?: string[] | undefined;
255
258
  }>>>;
256
259
  colorMode: z.ZodDefault<z.ZodOptional<z.ZodObject<z.extendShape<{
257
260
  ignoreDetection: z.ZodOptional<z.ZodBoolean>;
@@ -519,6 +522,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
519
522
  search?: {
520
523
  hide?: boolean | undefined;
521
524
  placement?: string | undefined;
525
+ shortcuts?: string[] | undefined;
522
526
  } | undefined;
523
527
  markdown?: {
524
528
  frontMatterKeysToResolve?: string[] | undefined;
@@ -642,6 +646,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
642
646
  search?: {
643
647
  hide?: boolean | undefined;
644
648
  placement?: string | undefined;
649
+ shortcuts?: string[] | undefined;
645
650
  } | undefined;
646
651
  colorMode?: {
647
652
  hide?: boolean | undefined;
@@ -705,4 +710,7 @@ export type ThemeUIConfig = Omit<ThemeConfig, 'navbar' | 'footer' | 'links' | 's
705
710
  }[];
706
711
  devLogin?: boolean;
707
712
  };
713
+ search?: {
714
+ shortcuts?: string[];
715
+ };
708
716
  };
package/lib/config.js CHANGED
@@ -86,6 +86,7 @@ exports.ThemeConfig = zod_1.z
86
86
  search: zod_1.z
87
87
  .object({
88
88
  placement: zod_1.z.string().default('navbar').optional(),
89
+ shortcuts: zod_1.z.array(zod_1.z.string()).default(['/']).optional(),
89
90
  })
90
91
  .extend(HideConfig.shape)
91
92
  .strict()
@@ -1784,6 +1784,16 @@ const markdown = (0, styled_components_1.css) `
1784
1784
  --md-numbered-list-number-padding: 0 5px; // @presenter Spacing
1785
1785
 
1786
1786
  // @tokens End
1787
+
1788
+ /**
1789
+ * @tokens Markdown Tabs
1790
+ */
1791
+
1792
+ --md-tabs-tab-text-color: var(--color-emphasis-600); // @presenter Color
1793
+ --md-tabs-active-tab-border-color: var(--text-color); // @presenter Color
1794
+ --md-tabs-hover-tab-border-color: var(--text-color-secondary); // @presenter Color
1795
+
1796
+ // @tokens End
1787
1797
  `;
1788
1798
  const search = (0, styled_components_1.css) `
1789
1799
  /**
@@ -1879,7 +1889,54 @@ const tile = (0, styled_components_1.css) `
1879
1889
  --thin-tile-background-color: var(--color-secondary-50);
1880
1890
  `;
1881
1891
  const apiLogsTable = (0, styled_components_1.css) `
1882
- --api-logs-row-hover-background-color: var(--color-secondary-300)
1892
+ --api-logs-row-hover-background-color: var(--color-secondary-300);
1893
+ `;
1894
+ const pages = (0, styled_components_1.css) `
1895
+ /**
1896
+ * @tokens 404 Page
1897
+ * @presenter Color
1898
+ */
1899
+
1900
+ --page-404-font-family: var(--font-family-base); // @presenter FontFamily
1901
+
1902
+ --page-404-header-text-color: #000;
1903
+ --page-404-header-font-size: 14em; // @presenter FontSize
1904
+ --page-404-header-font-weight: 600; // @presenter FontWeight
1905
+ --page-404-header-line-height: 1.2; // @presenter LineHeight
1906
+ --page-404-header-margin: 0; // @presenter Spacing
1907
+
1908
+ --page-404-description-text-color: #000;
1909
+ --page-404-description-font-size: 2em; // @presenter FontSize
1910
+ --page-404-description-font-weight: 400; // @presenter FontWeight
1911
+ --page-404-description-line-height: 1; // @presenter LineHeight
1912
+ --page-404-description-margin: 0; // @presenter Spacing
1913
+
1914
+ --page-404-button-margin: 4em; // @presenter Spacing
1915
+
1916
+ // @tokens End
1917
+
1918
+ /**
1919
+ * @tokens 403 Page
1920
+ * @presenter Color
1921
+ */
1922
+
1923
+ --page-403-font-family: var(--font-family-base); // @presenter FontFamily
1924
+
1925
+ --page-403-header-text-color: #000;
1926
+ --page-403-header-font-size: 14em; // @presenter FontSize
1927
+ --page-403-header-font-weight: 600; // @presenter FontWeight
1928
+ --page-403-header-line-height: 1.2; // @presenter LineHeight
1929
+ --page-403-header-margin: 0; // @presenter Spacing
1930
+
1931
+ --page-403-description-text-color: #000;
1932
+ --page-403-description-font-size: 2em; // @presenter FontSize
1933
+ --page-403-description-font-weight: 400; // @presenter FontWeight
1934
+ --page-403-description-line-height: 1; // @presenter LineHeight
1935
+ --page-403-description-margin: 0; // @presenter Spacing
1936
+
1937
+ --page-403-button-margin: 4em; // @presenter Spacing
1938
+
1939
+ // @tokens End
1883
1940
  `;
1884
1941
  exports.styles = (0, styled_components_1.css) `
1885
1942
  :root {
@@ -1910,6 +1967,7 @@ exports.styles = (0, styled_components_1.css) `
1910
1967
  ${tile}
1911
1968
  ${loadProgressBar}
1912
1969
  ${apiLogsTable}
1970
+ ${pages}
1913
1971
  }
1914
1972
 
1915
1973
  :root.dark {
package/lib/index.d.ts CHANGED
@@ -21,3 +21,4 @@ export * from './ColorModeSwitcher';
21
21
  export * from './Sidebar';
22
22
  export * from './types/config';
23
23
  export * from './config';
24
+ export * from './Pages';
package/lib/index.js CHANGED
@@ -37,4 +37,5 @@ __exportStar(require("./ColorModeSwitcher"), exports);
37
37
  __exportStar(require("./Sidebar"), exports);
38
38
  __exportStar(require("./types/config"), exports);
39
39
  __exportStar(require("./config"), exports);
40
+ __exportStar(require("./Pages"), exports);
40
41
  //# sourceMappingURL=index.js.map
package/lib/mocks/Link.js CHANGED
@@ -21,7 +21,7 @@ const react_1 = __importDefault(require("react"));
21
21
  function Link(props) {
22
22
  const { active: _, httpVerb: _1, hasActiveSubItem: _2, routeSlug: _3, external: _4 } = props, filteredProps = __rest(props, ["active", "httpVerb", "hasActiveSubItem", "routeSlug", "external"]);
23
23
  // We omit "active" property to avoid "Warning: Received `false` for a non-boolean attribute `active`."
24
- return react_1.default.createElement("a", Object.assign({ href: filteredProps.to }, filteredProps));
24
+ return react_1.default.createElement("a", Object.assign({ href: filteredProps.to, ref: filteredProps.innerRef }, filteredProps));
25
25
  }
26
26
  exports.Link = Link;
27
27
  //# sourceMappingURL=Link.js.map
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useSidebarSiblingsData = exports.useThemeConfig = void 0;
4
4
  function useThemeConfig() {
5
5
  return {
6
- search: { hide: false, placement: 'navbar' },
6
+ search: {
7
+ hide: false,
8
+ placement: 'navbar',
9
+ shortcuts: ['ctrl+f', 'cmd+k', '/'],
10
+ },
7
11
  markdown: {
8
12
  toc: { depth: 3, header: 'Table of contents', hide: false },
9
13
  lastUpdatedBlock: { hide: false, format: 'timeago', locale: 'en-US' },
@@ -1,13 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useFuseSearch = void 0;
4
+ const react_1 = require("react");
4
5
  function useFuseSearch() {
6
+ const [query, setQuery] = (0, react_1.useState)('');
5
7
  return {
6
- query: '',
7
- setQuery(val) {
8
- this.query = val;
9
- },
10
- items: [],
8
+ query,
9
+ setQuery,
10
+ items: [
11
+ {
12
+ id: '1',
13
+ url: '#someUrl1',
14
+ title: 'Some Dummy Result Item 1',
15
+ text: 'Some sample search text',
16
+ },
17
+ {
18
+ id: '2',
19
+ url: '#someUrl2',
20
+ title: 'Some Dummy Result Item 2',
21
+ text: 'Some sample search text 2',
22
+ },
23
+ ],
11
24
  };
12
25
  }
13
26
  exports.useFuseSearch = useFuseSearch;
@@ -61,8 +61,13 @@ exports.darkMode = (0, styled_components_1.css) `
61
61
  --copy-button-tooltip-background-color: var(--tooltip-background-color);
62
62
  --tooltip-text-color: #fff;
63
63
  --md-table-head-background-color: var(--color-secondary-300);
64
+ --md-tabs-hover-tab-border-color: var(--color-secondary-500);
64
65
  --wide-tile-background-color: #000000;
65
66
  --thin-tile-background-color: #000000;
67
+ --page-404-header-text-color: #fff;
68
+ --page-404-description-text-color: #fff;
69
+ --page-403-header-text-color: #fff;
70
+ --page-403-description-text-color: #fff;
66
71
 
67
72
  background-color: var(--background-color);
68
73
  color: var(--text-color);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.6.5",
3
+ "version": "0.7.0",
4
4
  "description": "Shared UI components lib",
5
5
  "author": "team@redocly.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -39,6 +39,7 @@
39
39
  "prismjs": "^1.28.0",
40
40
  "react": "^17.0.2",
41
41
  "react-dom": "^17.0.2",
42
+ "react-router": "^5.3.0",
42
43
  "react-router-dom": "^5.3.0",
43
44
  "styled-components": "^5.3.6",
44
45
  "styled-system": "^5.1.5",
@@ -65,16 +66,18 @@
65
66
  "@types/jest": "^29.2.1",
66
67
  "@types/jest-when": "^3.5.2",
67
68
  "@types/lodash.throttle": "^4.1.7",
68
- "@types/node": "^16.11.26",
69
+ "@types/node": "^18.11.18",
69
70
  "@types/prismjs": "^1.26.0",
70
71
  "@types/react": "^17.0.43",
71
72
  "@types/react-dom": "^17.0.14",
72
- "@types/react-router-dom": "^5.3.1",
73
+ "@types/react-router-dom": "^5.3.3",
74
+ "@types/react-router": "^5.1.20",
73
75
  "@types/styled-components": "^5.1.26",
74
76
  "@types/styled-system": "^5.1.13",
75
77
  "@typescript-eslint/eslint-plugin": "^5.23.0",
76
78
  "@typescript-eslint/parser": "^5.23.0",
77
79
  "chromatic": "^6.10.2",
80
+ "concurrently": "^7.4.0",
78
81
  "esbuild": "^0.15.11",
79
82
  "jest": "^29.2.2",
80
83
  "jest-environment-jsdom": "^29.2.2",
@@ -83,6 +86,7 @@
83
86
  "lodash.throttle": "^4.1.1",
84
87
  "npm-run-all": "^4.1.5",
85
88
  "react-refresh": "^0.14.0",
89
+ "react-router": "^5.3.0",
86
90
  "react-router-dom": "^5.3.0",
87
91
  "storybook-addon-pseudo-states": "^1.15.1",
88
92
  "storybook-design-token": "^2.7.1",
@@ -95,10 +99,10 @@
95
99
  "tsconfig-paths-webpack-plugin": "^3.5.2",
96
100
  "typescript": "^4.8.4",
97
101
  "webpack": "^5.72.0",
98
- "concurrently": "^7.4.0",
99
102
  "zod": ">=3.19.1"
100
103
  },
101
104
  "dependencies": {
105
+ "hotkeys-js": "^3.10.1",
102
106
  "timeago.js": "^4.0.2"
103
107
  },
104
108
  "nx": {
@@ -0,0 +1,64 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Button } from '@theme';
4
+
5
+ import type { CommentProps } from '@theme/Feedback';
6
+
7
+ export const Comment = ({ settings, onSubmit }: CommentProps): JSX.Element => {
8
+ const { label, submitText } = settings || {};
9
+ const [text, setText] = React.useState('');
10
+ const [submitValue, setSubmitValue] = React.useState('');
11
+
12
+ const send = () => {
13
+ if (!text) return;
14
+ setSubmitValue(text);
15
+ onSubmit({ comment: text });
16
+ };
17
+ const handleTextAreaChange = (e: any) => {
18
+ setText(e.target.value);
19
+ };
20
+
21
+ if (submitValue) {
22
+ return (
23
+ <Wrapper>
24
+ <Label>{submitText || 'Thank you for helping improve our documentation!'}</Label>
25
+ </Wrapper>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <Wrapper data-component-name="Feedback/Comment">
31
+ <Label>{label || 'Please share your feedback with us:'}</Label>
32
+ <TextArea rows={3} onChange={handleTextAreaChange} />
33
+ <SendButton onClick={send}>Send</SendButton>
34
+ </Wrapper>
35
+ );
36
+ };
37
+
38
+ const Wrapper = styled.div`
39
+ font-family: var(--font-family-base);
40
+ display: flex;
41
+ flex-direction: column;
42
+ width: 100%;
43
+ `;
44
+
45
+ const Label = styled.h3`
46
+ margin-right: 15px;
47
+ `;
48
+
49
+ const TextArea = styled.textarea`
50
+ font-family: var(--font-family-base);
51
+ border: 1px solid #ccc;
52
+ border-radius: 5px;
53
+ color: black;
54
+ margin: 0 0 10px 0;
55
+ padding: 10px;
56
+ `;
57
+
58
+ const SendButton = styled(Button).attrs(() => ({
59
+ color: 'primary',
60
+ }))`
61
+ width: 100px;
62
+ margin-left: 0;
63
+ margin-right: 0;
64
+ `;
@@ -1,36 +1,64 @@
1
1
  import * as React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import type { RatingProps } from '@theme/Feedback';
4
+ import type { RatingProps, ReasonsProps } from '@theme/Feedback';
5
+ import { Comment, Reasons } from '@theme/Feedback';
5
6
 
6
7
  type StarsProps = {
7
- onSubmit: RatingProps['onSubmit'];
8
+ onSubmit: (value: number) => void;
8
9
  max: number;
9
10
  };
10
11
 
11
12
  export const Rating = ({ settings, onSubmit }: RatingProps): JSX.Element => {
12
- const { label, max, submitText } = settings || {};
13
- const [submitValue, setSubmitValue] = React.useState(0);
13
+ const {
14
+ label,
15
+ max,
16
+ submitText,
17
+ comment: commentSettings,
18
+ reasons: reasonsSettings,
19
+ } = settings || {};
20
+ const [score, setScore] = React.useState(0);
21
+ const [comment, setComment] = React.useState('');
22
+ const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
14
23
 
15
- if (submitValue) {
24
+ if (score && reasonsSettings?.enable && !reasons.length) {
25
+ const { label: reasonsLabel, items, multi } = reasonsSettings;
26
+ const buttonText = commentSettings?.enable ? 'Next' : 'Send';
16
27
  return (
17
- <RatingWidget>
28
+ <Reasons
29
+ onSubmit={({ reasons }) => setReasons(reasons)}
30
+ settings={{ label: reasonsLabel, items, multi, buttonText }}
31
+ />
32
+ );
33
+ }
34
+
35
+ if (score && commentSettings?.enable && !comment) {
36
+ return (
37
+ <Comment
38
+ onSubmit={({ comment }) => setComment(comment)}
39
+ settings={{ label: commentSettings.label }}
40
+ />
41
+ );
42
+ }
43
+
44
+ if (score) {
45
+ onSubmit({
46
+ score,
47
+ comment,
48
+ reasons,
49
+ });
50
+ return (
51
+ <Wrapper>
18
52
  <Label>{submitText || 'Thank you for helping improve our documentation!'}</Label>
19
- </RatingWidget>
53
+ </Wrapper>
20
54
  );
21
55
  }
22
56
 
23
57
  return (
24
- <RatingWidget>
58
+ <Wrapper data-component-name="Feedback/Rating">
25
59
  <Label>{label || 'How helpful was this page?'}</Label>
26
- <Stars
27
- max={max || 5}
28
- onSubmit={(value: number) => {
29
- setSubmitValue(value);
30
- onSubmit(value);
31
- }}
32
- />
33
- </RatingWidget>
60
+ <Stars max={max || 5} onSubmit={setScore} />
61
+ </Wrapper>
34
62
  );
35
63
  };
36
64
 
@@ -64,7 +92,7 @@ const Stars = ({ max, onSubmit }: StarsProps): JSX.Element => {
64
92
  return <>{stars}</>;
65
93
  };
66
94
 
67
- const RatingWidget = styled.div`
95
+ const Wrapper = styled.div`
68
96
  display: flex;
69
97
  align-items: center;
70
98
  `;
@@ -0,0 +1,81 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Button } from '@theme';
4
+
5
+ import type { ReasonsProps } from '@theme/Feedback';
6
+
7
+ export const Reasons = ({ settings, onSubmit }: ReasonsProps): JSX.Element => {
8
+ const { label, multi, buttonText, items = [] } = settings;
9
+ const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
10
+
11
+ if (!items.length) {
12
+ return <></>;
13
+ }
14
+
15
+ const input_type = multi ? 'checkbox' : 'radio';
16
+
17
+ const handleOptionChange = (position: number) => {
18
+ const updatedCheckedState = multi
19
+ ? checkedState.map((item, index) => (index === position ? !item : item))
20
+ : items.map((_, idx) => position === idx);
21
+
22
+ setCheckedState(updatedCheckedState);
23
+ };
24
+
25
+ const submitForm = () => {
26
+ onSubmit({ reasons: items.filter((_, index) => !!checkedState[index]) });
27
+ };
28
+
29
+ return (
30
+ <Wrapper data-component-name="Feedback/Reasons">
31
+ <Label>{label || 'Which statement describes your thoughts about this page?'}</Label>
32
+ {items.map((reason, idx) => (
33
+ <OptionWrapper key={reason}>
34
+ <input
35
+ type={input_type}
36
+ value={reason}
37
+ checked={checkedState[idx]}
38
+ name="reasons"
39
+ onChange={() => handleOptionChange(idx)}
40
+ />
41
+ <label id={reason} onClick={() => handleOptionChange(idx)}>
42
+ {reason}
43
+ </label>
44
+ </OptionWrapper>
45
+ ))}
46
+ <SendButton onClick={submitForm}>{buttonText || 'Send'}</SendButton>
47
+ </Wrapper>
48
+ );
49
+ };
50
+
51
+ const Wrapper = styled.div`
52
+ font-family: var(--font-family-base);
53
+ display: flex;
54
+ flex-direction: column;
55
+ `;
56
+
57
+ const Label = styled.h3`
58
+ margin-right: 15px;
59
+ `;
60
+
61
+ const SendButton = styled(Button).attrs(() => ({
62
+ color: 'primary',
63
+ }))`
64
+ width: 100px;
65
+ margin-left: 0;
66
+ margin-right: 0;
67
+ margin-top: 15px;
68
+ `;
69
+
70
+ const OptionWrapper = styled.div`
71
+ margin: 5px 0;
72
+ display: flex;
73
+ input {
74
+ margin-right: 10px;
75
+ cursor: pointer;
76
+ }
77
+ label {
78
+ font-size: 14px;
79
+ cursor: pointer;
80
+ }
81
+ `;
@@ -1,19 +1,58 @@
1
1
  import * as React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import type { SentimentProps } from '@theme/Feedback';
4
+ import type { SentimentProps, ReasonsProps } from '@theme/Feedback';
5
+ import { Comment, Reasons } from '@theme/Feedback';
5
6
 
6
7
  import { ThumbUp, ThumbDown } from './Thumbs';
7
8
 
8
9
  export const Sentiment = ({ settings, onSubmit }: SentimentProps): JSX.Element => {
9
- const { label } = settings || {};
10
+ const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
11
+ const [score, setScore] = React.useState(0);
12
+ const [comment, setComment] = React.useState('');
13
+ const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
14
+
15
+ if (score && reasonsSettings?.enable && !reasons.length) {
16
+ const { label: reasonsLabel, items, multi } = reasonsSettings;
17
+ const buttonText = commentSettings?.enable ? 'Next' : 'Send';
18
+ return (
19
+ <Reasons
20
+ onSubmit={({ reasons }) => setReasons(reasons)}
21
+ settings={{ label: reasonsLabel, items, multi, buttonText }}
22
+ />
23
+ );
24
+ }
25
+
26
+ if (score && commentSettings?.enable && !comment) {
27
+ const commentLabel =
28
+ score === 1
29
+ ? commentSettings.likeLabel || 'What was most helpful?'
30
+ : commentSettings.dislikeLabel || 'What can we improve?';
31
+ return (
32
+ <Comment onSubmit={({ comment }) => setComment(comment)} settings={{ label: commentLabel }} />
33
+ );
34
+ }
35
+
36
+ if (score) {
37
+ onSubmit({
38
+ score,
39
+ comment,
40
+ reasons,
41
+ });
42
+ return (
43
+ <Wrapper>
44
+ <Label>{submitText || 'Thank you for helping improve our documentation!'}</Label>
45
+ </Wrapper>
46
+ );
47
+ }
48
+
10
49
  return (
11
- <Wrapper>
50
+ <Wrapper data-component-name="Feedback/Sentiment">
12
51
  <Label>{label || 'Was this page helpful?'}</Label>
13
- <Vote onClick={() => onSubmit(1)}>
52
+ <Vote onClick={() => setScore(1)}>
14
53
  <ThumbUp text="Yes" />
15
54
  </Vote>
16
- <Vote onClick={() => onSubmit(-1)}>
55
+ <Vote onClick={() => setScore(-1)}>
17
56
  <ThumbDown />
18
57
  </Vote>
19
58
  </Wrapper>
@@ -1,3 +1,5 @@
1
- export { Rating } from './Rating';
2
- export { Sentiment } from './Sentiment';
1
+ export { Rating } from '@theme/Feedback/Rating';
2
+ export { Sentiment } from '@theme/Feedback/Sentiment';
3
+ export { Comment } from '@theme/Feedback/Comment';
4
+ export { Reasons } from '@theme/Feedback/Reasons';
3
5
  export * from './types';