@redocly/theme 0.6.5 → 0.7.1

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 +11 -0
  28. package/lib/config.js +2 -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 +5 -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>;
@@ -429,6 +432,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
429
432
  }>>>;
430
433
  openapi: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, {}, {}>>;
431
434
  graphql: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, {}, {}>>;
435
+ analytics: z.ZodOptional<z.ZodAny>;
432
436
  userProfile: z.ZodDefault<z.ZodOptional<z.ZodObject<z.extendShape<{
433
437
  loginLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
434
438
  logoutLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
@@ -519,6 +523,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
519
523
  search?: {
520
524
  hide?: boolean | undefined;
521
525
  placement?: string | undefined;
526
+ shortcuts?: string[] | undefined;
522
527
  } | undefined;
523
528
  markdown?: {
524
529
  frontMatterKeysToResolve?: string[] | undefined;
@@ -548,6 +553,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
548
553
  } | undefined;
549
554
  openapi?: {} | undefined;
550
555
  graphql?: {} | undefined;
556
+ analytics?: any;
551
557
  colorMode: {
552
558
  hide?: boolean | undefined;
553
559
  ignoreDetection?: boolean | undefined;
@@ -642,6 +648,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
642
648
  search?: {
643
649
  hide?: boolean | undefined;
644
650
  placement?: string | undefined;
651
+ shortcuts?: string[] | undefined;
645
652
  } | undefined;
646
653
  colorMode?: {
647
654
  hide?: boolean | undefined;
@@ -686,6 +693,7 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
686
693
  } | undefined;
687
694
  openapi?: {} | undefined;
688
695
  graphql?: {} | undefined;
696
+ analytics?: any;
689
697
  userProfile?: {
690
698
  hide?: boolean | undefined;
691
699
  loginLabel?: string | undefined;
@@ -705,4 +713,7 @@ export type ThemeUIConfig = Omit<ThemeConfig, 'navbar' | 'footer' | 'links' | 's
705
713
  }[];
706
714
  devLogin?: boolean;
707
715
  };
716
+ search?: {
717
+ shortcuts?: string[];
718
+ };
708
719
  };
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()
@@ -160,6 +161,7 @@ exports.ThemeConfig = zod_1.z
160
161
  .optional(),
161
162
  openapi: zod_1.z.object({}).passthrough().optional(),
162
163
  graphql: zod_1.z.object({}).passthrough().optional(),
164
+ analytics: zod_1.z.any().optional(),
163
165
  userProfile: zod_1.z
164
166
  .object({
165
167
  loginLabel: zod_1.z.string().default('Login').optional(),
@@ -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.1",
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>