@drodil/backstage-plugin-qeta-react 3.28.2 → 3.29.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 (113) hide show
  1. package/dist/components/AnswerCard/AnswerCard.esm.js +77 -41
  2. package/dist/components/AnswerCard/AnswerCard.esm.js.map +1 -1
  3. package/dist/components/AnswersContainer/AnswersContainer.esm.js +16 -8
  4. package/dist/components/AnswersContainer/AnswersContainer.esm.js.map +1 -1
  5. package/dist/components/ArticleContent/ArticleContent.esm.js +2 -3
  6. package/dist/components/ArticleContent/ArticleContent.esm.js.map +1 -1
  7. package/dist/components/AuthorBox/AuthorBox.esm.js +74 -70
  8. package/dist/components/AuthorBox/AuthorBox.esm.js.map +1 -1
  9. package/dist/components/Buttons/AddToCollectionButton.esm.js +17 -2
  10. package/dist/components/Buttons/AddToCollectionButton.esm.js.map +1 -1
  11. package/dist/components/Buttons/ButtonContainer.esm.js +1 -0
  12. package/dist/components/Buttons/ButtonContainer.esm.js.map +1 -1
  13. package/dist/components/CollectionForm/CollectionForm.esm.js +78 -14
  14. package/dist/components/CollectionForm/CollectionForm.esm.js.map +1 -1
  15. package/dist/components/CollectionsGrid/CollectionsGrid.esm.js +19 -21
  16. package/dist/components/CollectionsGrid/CollectionsGrid.esm.js.map +1 -1
  17. package/dist/components/CommentSection/CommentListItem.esm.js +20 -3
  18. package/dist/components/CommentSection/CommentListItem.esm.js.map +1 -1
  19. package/dist/components/CommentSection/CommentSection.esm.js +57 -12
  20. package/dist/components/CommentSection/CommentSection.esm.js.map +1 -1
  21. package/dist/components/EntitiesGrid/EntitiesGrid.esm.js +26 -24
  22. package/dist/components/EntitiesGrid/EntitiesGrid.esm.js.map +1 -1
  23. package/dist/components/EntitiesGrid/EntitiesGridItem.esm.js +48 -50
  24. package/dist/components/EntitiesGrid/EntitiesGridItem.esm.js.map +1 -1
  25. package/dist/components/FilterPanel/DateRangeFilter.esm.js +28 -20
  26. package/dist/components/FilterPanel/DateRangeFilter.esm.js.map +1 -1
  27. package/dist/components/FilterPanel/FilterPanel.esm.js +34 -1
  28. package/dist/components/FilterPanel/FilterPanel.esm.js.map +1 -1
  29. package/dist/components/FollowedLists/FollowedCollectionsList.esm.js +1 -1
  30. package/dist/components/FollowedLists/FollowedCollectionsList.esm.js.map +1 -1
  31. package/dist/components/FollowedLists/FollowedEntitiesList.esm.js +1 -1
  32. package/dist/components/FollowedLists/FollowedEntitiesList.esm.js.map +1 -1
  33. package/dist/components/FollowedLists/FollowedTagsList.esm.js +1 -1
  34. package/dist/components/FollowedLists/FollowedTagsList.esm.js.map +1 -1
  35. package/dist/components/FollowedLists/FollowedUsersList.esm.js +1 -1
  36. package/dist/components/FollowedLists/FollowedUsersList.esm.js.map +1 -1
  37. package/dist/components/GridItemStyles/useGridItemStyles.esm.js +68 -0
  38. package/dist/components/GridItemStyles/useGridItemStyles.esm.js.map +1 -0
  39. package/dist/components/HeaderImageInput/HeaderImageInput.esm.js +171 -36
  40. package/dist/components/HeaderImageInput/HeaderImageInput.esm.js.map +1 -1
  41. package/dist/components/HomePageCards/ImpactCard.esm.js +52 -15
  42. package/dist/components/HomePageCards/ImpactCard.esm.js.map +1 -1
  43. package/dist/components/HomePageCards/PostsCard.esm.js +2 -0
  44. package/dist/components/HomePageCards/PostsCard.esm.js.map +1 -1
  45. package/dist/components/LeftMenu/LeftMenu.esm.js +45 -13
  46. package/dist/components/LeftMenu/LeftMenu.esm.js.map +1 -1
  47. package/dist/components/Links/Links.esm.js +6 -0
  48. package/dist/components/Links/Links.esm.js.map +1 -1
  49. package/dist/components/MarkdownRenderer/MarkdownRenderer.esm.js +87 -31
  50. package/dist/components/MarkdownRenderer/MarkdownRenderer.esm.js.map +1 -1
  51. package/dist/components/PostForm/EntitiesInput.esm.js +4 -6
  52. package/dist/components/PostForm/EntitiesInput.esm.js.map +1 -1
  53. package/dist/components/PostForm/PostForm.esm.js +98 -26
  54. package/dist/components/PostForm/PostForm.esm.js.map +1 -1
  55. package/dist/components/PostForm/TagInput.esm.js.map +1 -1
  56. package/dist/components/PostHighlightList/PostHighlightList.esm.js +97 -19
  57. package/dist/components/PostHighlightList/PostHighlightList.esm.js.map +1 -1
  58. package/dist/components/PostsContainer/PostList.esm.js +11 -2
  59. package/dist/components/PostsContainer/PostList.esm.js.map +1 -1
  60. package/dist/components/PostsContainer/PostListItem.esm.js +238 -139
  61. package/dist/components/PostsContainer/PostListItem.esm.js.map +1 -1
  62. package/dist/components/PostsContainer/PostsContainer.esm.js +68 -57
  63. package/dist/components/PostsContainer/PostsContainer.esm.js.map +1 -1
  64. package/dist/components/PostsGrid/PostsGrid.esm.js +20 -13
  65. package/dist/components/PostsGrid/PostsGrid.esm.js.map +1 -1
  66. package/dist/components/PostsGrid/PostsGridContent.esm.js +2 -1
  67. package/dist/components/PostsGrid/PostsGridContent.esm.js.map +1 -1
  68. package/dist/components/PostsGrid/PostsGridItem.esm.js +210 -51
  69. package/dist/components/PostsGrid/PostsGridItem.esm.js.map +1 -1
  70. package/dist/components/QetaPagination/QetaPagination.esm.js +4 -2
  71. package/dist/components/QetaPagination/QetaPagination.esm.js.map +1 -1
  72. package/dist/components/QuestionCard/QuestionCard.esm.js +69 -40
  73. package/dist/components/QuestionCard/QuestionCard.esm.js.map +1 -1
  74. package/dist/components/SearchBar/SearchBar.esm.js +72 -18
  75. package/dist/components/SearchBar/SearchBar.esm.js.map +1 -1
  76. package/dist/components/StatsChart/StatsChart.esm.js +176 -64
  77. package/dist/components/StatsChart/StatsChart.esm.js.map +1 -1
  78. package/dist/components/SuggestionsCard/SuggestionsCard.esm.js +93 -16
  79. package/dist/components/SuggestionsCard/SuggestionsCard.esm.js.map +1 -1
  80. package/dist/components/SummaryStatsGrid/SummaryStatsGrid.esm.js +80 -27
  81. package/dist/components/SummaryStatsGrid/SummaryStatsGrid.esm.js.map +1 -1
  82. package/dist/components/TagsAndEntities/EntityChip.esm.js +7 -0
  83. package/dist/components/TagsAndEntities/EntityChip.esm.js.map +1 -1
  84. package/dist/components/TagsAndEntities/TagChip.esm.js +6 -0
  85. package/dist/components/TagsAndEntities/TagChip.esm.js.map +1 -1
  86. package/dist/components/TagsGrid/TagGridItem.esm.js +84 -83
  87. package/dist/components/TagsGrid/TagGridItem.esm.js.map +1 -1
  88. package/dist/components/TagsGrid/TagsGrid.esm.js +39 -36
  89. package/dist/components/TagsGrid/TagsGrid.esm.js.map +1 -1
  90. package/dist/components/TopRankingUsersCard/TopRankingUsersCard.esm.js +95 -44
  91. package/dist/components/TopRankingUsersCard/TopRankingUsersCard.esm.js.map +1 -1
  92. package/dist/components/UsersGrid/UsersGrid.esm.js +10 -10
  93. package/dist/components/UsersGrid/UsersGrid.esm.js.map +1 -1
  94. package/dist/components/UsersGrid/UsersGridItem.esm.js +80 -77
  95. package/dist/components/UsersGrid/UsersGridItem.esm.js.map +1 -1
  96. package/dist/components/Utility/QetaGridHeader.esm.js +36 -0
  97. package/dist/components/Utility/QetaGridHeader.esm.js.map +1 -0
  98. package/dist/components/Utility/RightList.esm.js +19 -8
  99. package/dist/components/Utility/RightList.esm.js.map +1 -1
  100. package/dist/components/Utility/SmallAvatar.esm.js +0 -1
  101. package/dist/components/Utility/SmallAvatar.esm.js.map +1 -1
  102. package/dist/components/ViewToggle/ViewToggle.esm.js +33 -0
  103. package/dist/components/ViewToggle/ViewToggle.esm.js.map +1 -0
  104. package/dist/hooks/useTooltipStyles.esm.js +23 -0
  105. package/dist/hooks/useTooltipStyles.esm.js.map +1 -0
  106. package/dist/index.d.ts +105 -24
  107. package/dist/index.esm.js +1 -0
  108. package/dist/index.esm.js.map +1 -1
  109. package/dist/translation.esm.js +96 -27
  110. package/dist/translation.esm.js.map +1 -1
  111. package/dist/utils/utils.esm.js +18 -3
  112. package/dist/utils/utils.esm.js.map +1 -1
  113. package/package.json +3 -2
@@ -1,8 +1,11 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useState } from 'react';
3
- import { makeStyles, Paper, IconButton, CircularProgress, InputBase } from '@material-ui/core';
2
+ import { useState, useCallback, useMemo, useEffect } from 'react';
3
+ import { makeStyles, Paper, Tooltip, IconButton, CircularProgress, InputBase } from '@material-ui/core';
4
4
  import SearchIcon from '@material-ui/icons/Search';
5
5
  import CloseIcon from '@material-ui/icons/Close';
6
+ import debounce from 'lodash/debounce';
7
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
8
+ import { qetaTranslationRef } from '../../translation.esm.js';
6
9
 
7
10
  const useStyles = makeStyles(
8
11
  (theme) => ({
@@ -11,26 +14,82 @@ const useStyles = makeStyles(
11
14
  display: "flex",
12
15
  alignItems: "center",
13
16
  minWidth: 300,
14
- boxShadow: "none"
17
+ boxShadow: "none",
18
+ border: `1px solid ${theme.palette.divider}`,
19
+ borderRadius: theme.shape.borderRadius,
20
+ transition: "all 0.2s ease-in-out",
21
+ "&:hover": {
22
+ borderColor: theme.palette.primary.main
23
+ },
24
+ "&:focus-within": {
25
+ borderColor: theme.palette.primary.main,
26
+ boxShadow: `0 0 0 2px ${theme.palette.primary.light}`
27
+ }
15
28
  },
16
29
  input: {
17
30
  marginLeft: theme.spacing(1),
18
31
  flex: 1
19
32
  },
20
33
  iconButton: {
21
- padding: 5
34
+ padding: 5,
35
+ "&:hover": {
36
+ backgroundColor: theme.palette.action.hover
37
+ }
22
38
  },
23
39
  divider: {
24
40
  height: 28,
25
41
  margin: 4
42
+ },
43
+ searchIcon: {
44
+ color: theme.palette.text.secondary
45
+ },
46
+ loadingIcon: {
47
+ color: theme.palette.primary.main
26
48
  }
27
49
  }),
28
50
  { name: "QetaSearchBar" }
29
51
  );
30
52
  const SearchBar = (props) => {
31
- const { label, onSearch, loading } = props;
53
+ const {
54
+ label,
55
+ onSearch,
56
+ loading = false,
57
+ minSearchLength = 1,
58
+ debounceTime = 150
59
+ } = props;
32
60
  const [searchQuery, setSearchQuery] = useState("");
33
61
  const classes = useStyles();
62
+ const { t } = useTranslationRef(qetaTranslationRef);
63
+ const debouncedSearch = useCallback(
64
+ (query) => {
65
+ if (query.length >= minSearchLength || query.length === 0) {
66
+ onSearch(query);
67
+ }
68
+ },
69
+ [onSearch, minSearchLength]
70
+ );
71
+ const debouncedSearchCallback = useMemo(
72
+ () => debounce(debouncedSearch, debounceTime),
73
+ [debouncedSearch, debounceTime]
74
+ );
75
+ useEffect(() => {
76
+ return () => {
77
+ debouncedSearchCallback.cancel();
78
+ };
79
+ }, [debouncedSearchCallback]);
80
+ const handleSearch = (query) => {
81
+ setSearchQuery(query);
82
+ debouncedSearchCallback(query);
83
+ };
84
+ const handleClear = () => {
85
+ setSearchQuery("");
86
+ onSearch("");
87
+ };
88
+ const handleKeyDown = (event) => {
89
+ if (event.key === "Escape") {
90
+ handleClear();
91
+ }
92
+ };
34
93
  return /* @__PURE__ */ jsxs(
35
94
  Paper,
36
95
  {
@@ -38,43 +97,38 @@ const SearchBar = (props) => {
38
97
  className: classes.root,
39
98
  onSubmit: (e) => e.preventDefault(),
40
99
  children: [
41
- /* @__PURE__ */ jsx(
100
+ /* @__PURE__ */ jsx(Tooltip, { title: t("common.search"), children: /* @__PURE__ */ jsx(
42
101
  IconButton,
43
102
  {
44
103
  type: "button",
45
104
  "aria-label": "search",
46
105
  className: classes.iconButton,
47
- children: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "1em" }) : /* @__PURE__ */ jsx(SearchIcon, { color: "disabled" })
106
+ children: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: 24, className: classes.loadingIcon }) : /* @__PURE__ */ jsx(SearchIcon, { className: classes.searchIcon })
48
107
  }
49
- ),
108
+ ) }),
50
109
  /* @__PURE__ */ jsx(
51
110
  InputBase,
52
111
  {
53
112
  className: classes.input,
54
113
  placeholder: label,
55
114
  value: searchQuery,
115
+ onKeyDown: handleKeyDown,
56
116
  inputProps: {
57
117
  "aria-label": label,
58
- onChange: (event) => {
59
- onSearch(event.target.value);
60
- setSearchQuery(event.target.value);
61
- }
118
+ onChange: (event) => handleSearch(event.target.value)
62
119
  }
63
120
  }
64
121
  ),
65
- searchQuery && /* @__PURE__ */ jsx(
122
+ searchQuery && /* @__PURE__ */ jsx(Tooltip, { title: t("common.clear"), children: /* @__PURE__ */ jsx(
66
123
  IconButton,
67
124
  {
68
125
  type: "button",
69
126
  "aria-label": "clear",
70
127
  className: classes.iconButton,
71
- onClick: () => {
72
- onSearch("");
73
- setSearchQuery("");
74
- },
128
+ onClick: handleClear,
75
129
  children: /* @__PURE__ */ jsx(CloseIcon, {})
76
130
  }
77
- )
131
+ ) })
78
132
  ]
79
133
  }
80
134
  );
@@ -1 +1 @@
1
- {"version":3,"file":"SearchBar.esm.js","sources":["../../../src/components/SearchBar/SearchBar.tsx"],"sourcesContent":["import { ChangeEvent, useState } from 'react';\nimport {\n CircularProgress,\n IconButton,\n InputBase,\n makeStyles,\n Paper,\n Theme,\n} from '@material-ui/core';\nimport SearchIcon from '@material-ui/icons/Search';\nimport CloseIcon from '@material-ui/icons/Close';\n\nexport type QetaSearchBarClassKeys =\n | 'root'\n | 'input'\n | 'iconButton'\n | 'divider';\n\nconst useStyles = makeStyles(\n (theme: Theme) => ({\n root: {\n padding: '2px 4px',\n display: 'flex',\n alignItems: 'center',\n minWidth: 300,\n boxShadow: 'none',\n },\n input: {\n marginLeft: theme.spacing(1),\n flex: 1,\n },\n iconButton: {\n padding: 5,\n },\n divider: {\n height: 28,\n margin: 4,\n },\n }),\n { name: 'QetaSearchBar' },\n);\n\nexport const SearchBar = (props: {\n label: string;\n onSearch: (query: string) => void;\n loading?: boolean;\n}) => {\n const { label, onSearch, loading } = props;\n const [searchQuery, setSearchQuery] = useState('');\n const classes = useStyles();\n\n return (\n <Paper\n component=\"form\"\n className={classes.root}\n onSubmit={e => e.preventDefault()}\n >\n <IconButton\n type=\"button\"\n aria-label=\"search\"\n className={classes.iconButton}\n >\n {loading ? (\n <CircularProgress size=\"1em\" />\n ) : (\n <SearchIcon color=\"disabled\" />\n )}\n </IconButton>\n <InputBase\n className={classes.input}\n placeholder={label}\n value={searchQuery}\n inputProps={{\n 'aria-label': label,\n onChange: (\n event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,\n ) => {\n onSearch(event.target.value);\n setSearchQuery(event.target.value);\n },\n }}\n />\n {searchQuery && (\n <IconButton\n type=\"button\"\n aria-label=\"clear\"\n className={classes.iconButton}\n onClick={() => {\n onSearch('');\n setSearchQuery('');\n }}\n >\n <CloseIcon />\n </IconButton>\n )}\n </Paper>\n );\n};\n"],"names":[],"mappings":";;;;;;AAkBA,MAAM,SAAY,GAAA,UAAA;AAAA,EAChB,CAAC,KAAkB,MAAA;AAAA,IACjB,IAAM,EAAA;AAAA,MACJ,OAAS,EAAA,SAAA;AAAA,MACT,OAAS,EAAA,MAAA;AAAA,MACT,UAAY,EAAA,QAAA;AAAA,MACZ,QAAU,EAAA,GAAA;AAAA,MACV,SAAW,EAAA;AAAA,KACb;AAAA,IACA,KAAO,EAAA;AAAA,MACL,UAAA,EAAY,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,MAC3B,IAAM,EAAA;AAAA,KACR;AAAA,IACA,UAAY,EAAA;AAAA,MACV,OAAS,EAAA;AAAA,KACX;AAAA,IACA,OAAS,EAAA;AAAA,MACP,MAAQ,EAAA,EAAA;AAAA,MACR,MAAQ,EAAA;AAAA;AACV,GACF,CAAA;AAAA,EACA,EAAE,MAAM,eAAgB;AAC1B,CAAA;AAEa,MAAA,SAAA,GAAY,CAAC,KAIpB,KAAA;AACJ,EAAA,MAAM,EAAE,KAAA,EAAO,QAAU,EAAA,OAAA,EAAY,GAAA,KAAA;AACrC,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,EAAE,CAAA;AACjD,EAAA,MAAM,UAAU,SAAU,EAAA;AAE1B,EACE,uBAAA,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAU,EAAA,MAAA;AAAA,MACV,WAAW,OAAQ,CAAA,IAAA;AAAA,MACnB,QAAA,EAAU,CAAK,CAAA,KAAA,CAAA,CAAE,cAAe,EAAA;AAAA,MAEhC,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,IAAK,EAAA,QAAA;AAAA,YACL,YAAW,EAAA,QAAA;AAAA,YACX,WAAW,OAAQ,CAAA,UAAA;AAAA,YAElB,QAAA,EAAA,OAAA,uBACE,gBAAiB,EAAA,EAAA,IAAA,EAAK,OAAM,CAE7B,mBAAA,GAAA,CAAC,UAAW,EAAA,EAAA,KAAA,EAAM,UAAW,EAAA;AAAA;AAAA,SAEjC;AAAA,wBACA,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,WAAW,OAAQ,CAAA,KAAA;AAAA,YACnB,WAAa,EAAA,KAAA;AAAA,YACb,KAAO,EAAA,WAAA;AAAA,YACP,UAAY,EAAA;AAAA,cACV,YAAc,EAAA,KAAA;AAAA,cACd,QAAA,EAAU,CACR,KACG,KAAA;AACH,gBAAS,QAAA,CAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAC3B,gBAAe,cAAA,CAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA;AACnC;AACF;AAAA,SACF;AAAA,QACC,WACC,oBAAA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,IAAK,EAAA,QAAA;AAAA,YACL,YAAW,EAAA,OAAA;AAAA,YACX,WAAW,OAAQ,CAAA,UAAA;AAAA,YACnB,SAAS,MAAM;AACb,cAAA,QAAA,CAAS,EAAE,CAAA;AACX,cAAA,cAAA,CAAe,EAAE,CAAA;AAAA,aACnB;AAAA,YAEA,8BAAC,SAAU,EAAA,EAAA;AAAA;AAAA;AACb;AAAA;AAAA,GAEJ;AAEJ;;;;"}
1
+ {"version":3,"file":"SearchBar.esm.js","sources":["../../../src/components/SearchBar/SearchBar.tsx"],"sourcesContent":["import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n CircularProgress,\n IconButton,\n InputBase,\n makeStyles,\n Paper,\n Theme,\n Tooltip,\n} from '@material-ui/core';\nimport SearchIcon from '@material-ui/icons/Search';\nimport CloseIcon from '@material-ui/icons/Close';\nimport debounce from 'lodash/debounce';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { qetaTranslationRef } from '../../translation';\n\nexport type QetaSearchBarClassKeys =\n | 'root'\n | 'input'\n | 'iconButton'\n | 'divider'\n | 'searchIcon'\n | 'loadingIcon';\n\nconst useStyles = makeStyles(\n (theme: Theme) => ({\n root: {\n padding: '2px 4px',\n display: 'flex',\n alignItems: 'center',\n minWidth: 300,\n boxShadow: 'none',\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n transition: 'all 0.2s ease-in-out',\n '&:hover': {\n borderColor: theme.palette.primary.main,\n },\n '&:focus-within': {\n borderColor: theme.palette.primary.main,\n boxShadow: `0 0 0 2px ${theme.palette.primary.light}`,\n },\n },\n input: {\n marginLeft: theme.spacing(1),\n flex: 1,\n },\n iconButton: {\n padding: 5,\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n },\n divider: {\n height: 28,\n margin: 4,\n },\n searchIcon: {\n color: theme.palette.text.secondary,\n },\n loadingIcon: {\n color: theme.palette.primary.main,\n },\n }),\n { name: 'QetaSearchBar' },\n);\n\nexport const SearchBar = (props: {\n label: string;\n onSearch: (query: string) => void;\n loading?: boolean;\n minSearchLength?: number;\n debounceTime?: number;\n}) => {\n const {\n label,\n onSearch,\n loading = false,\n minSearchLength = 1,\n debounceTime = 150,\n } = props;\n const [searchQuery, setSearchQuery] = useState('');\n const classes = useStyles();\n const { t } = useTranslationRef(qetaTranslationRef);\n\n const debouncedSearch = useCallback(\n (query: string) => {\n if (query.length >= minSearchLength || query.length === 0) {\n onSearch(query);\n }\n },\n [onSearch, minSearchLength],\n );\n\n const debouncedSearchCallback = useMemo(\n () => debounce(debouncedSearch, debounceTime),\n [debouncedSearch, debounceTime],\n );\n\n useEffect(() => {\n return () => {\n debouncedSearchCallback.cancel();\n };\n }, [debouncedSearchCallback]);\n\n const handleSearch = (query: string) => {\n setSearchQuery(query);\n debouncedSearchCallback(query);\n };\n\n const handleClear = () => {\n setSearchQuery('');\n onSearch('');\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n handleClear();\n }\n };\n\n return (\n <Paper\n component=\"form\"\n className={classes.root}\n onSubmit={e => e.preventDefault()}\n >\n <Tooltip title={t('common.search')}>\n <IconButton\n type=\"button\"\n aria-label=\"search\"\n className={classes.iconButton}\n >\n {loading ? (\n <CircularProgress size={24} className={classes.loadingIcon} />\n ) : (\n <SearchIcon className={classes.searchIcon} />\n )}\n </IconButton>\n </Tooltip>\n <InputBase\n className={classes.input}\n placeholder={label}\n value={searchQuery}\n onKeyDown={handleKeyDown}\n inputProps={{\n 'aria-label': label,\n onChange: (\n event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,\n ) => handleSearch(event.target.value),\n }}\n />\n {searchQuery && (\n <Tooltip title={t('common.clear')}>\n <IconButton\n type=\"button\"\n aria-label=\"clear\"\n className={classes.iconButton}\n onClick={handleClear}\n >\n <CloseIcon />\n </IconButton>\n </Tooltip>\n )}\n </Paper>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { useState, useCallback } from 'react';
3
- import { ResponsiveContainer, BarChart, Tooltip, Bar, CartesianGrid, XAxis, YAxis, Legend, LineChart, Line } from 'recharts';
4
- import { makeStyles, createStyles, Typography, ButtonGroup, IconButton } from '@material-ui/core';
3
+ import { ResponsiveContainer, BarChart, Tooltip as Tooltip$1, Bar, CartesianGrid, XAxis, YAxis, Legend, LineChart, Line } from 'recharts';
4
+ import { makeStyles, createStyles, Typography, Box, CircularProgress, ButtonGroup, Tooltip, IconButton } from '@material-ui/core';
5
5
  import ShowChartIcon from '@material-ui/icons/ShowChart';
6
6
  import BarChartIcon from '@material-ui/icons/BarChart';
7
7
  import { useIsDarkTheme } from '../../hooks/useIsDarkTheme.esm.js';
@@ -16,85 +16,131 @@ const useStyles = makeStyles(
16
16
  },
17
17
  tooltipWrapper: {
18
18
  backgroundColor: `${theme.palette.background.default} !important`,
19
- border: "none !important"
19
+ border: "none !important",
20
+ padding: theme.spacing(1),
21
+ borderRadius: theme.shape.borderRadius,
22
+ boxShadow: theme.shadows[2]
20
23
  },
21
24
  xAxis: {
22
25
  color: `${theme.palette.text.primary} !important`
23
26
  },
24
27
  lineChart: {},
25
- barChart: {}
28
+ barChart: {},
29
+ chartContainer: {
30
+ position: "relative",
31
+ width: "100%",
32
+ height: 500
33
+ },
34
+ loadingOverlay: {
35
+ position: "absolute",
36
+ top: 0,
37
+ left: 0,
38
+ right: 0,
39
+ bottom: 0,
40
+ display: "flex",
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
44
+ zIndex: 1
45
+ },
46
+ chartTypeButton: {
47
+ "&.Mui-selected": {
48
+ backgroundColor: theme.palette.action.selected
49
+ }
50
+ },
51
+ errorMessage: {
52
+ color: theme.palette.error.main,
53
+ textAlign: "center",
54
+ padding: theme.spacing(2)
55
+ },
56
+ legend: {
57
+ "& .recharts-legend-item-text": {
58
+ fontFamily: theme.typography.fontFamily,
59
+ fontSize: theme.typography.body2.fontSize,
60
+ color: theme.palette.text.primary
61
+ }
62
+ }
26
63
  }),
27
64
  { name: "QetaStatsChart" }
28
65
  );
29
- const DEFAULT_STATS = [
66
+ const getDefaultStats = (isDark, t) => [
30
67
  {
31
68
  dataKey: "totalViews",
32
- name: "Total views",
33
- color: "#8884d8",
69
+ name: t("stats.totalViews"),
70
+ color: isDark ? "#a3bffa" : "#1e40af",
71
+ // blue-400 / blue-900
34
72
  enabled: false,
35
73
  globalStat: true,
36
74
  userStat: true
37
75
  },
38
76
  {
39
77
  dataKey: "totalQuestions",
40
- name: "Total questions",
41
- color: "#82ca9d",
78
+ name: t("stats.totalQuestions"),
79
+ color: isDark ? "#6ee7b7" : "#047857",
80
+ // green-300 / green-800
42
81
  enabled: true,
43
82
  globalStat: true,
44
83
  userStat: true
45
84
  },
46
85
  {
47
86
  dataKey: "totalAnswers",
48
- name: "Total answers",
49
- color: "#ff7300",
87
+ name: t("stats.totalAnswers"),
88
+ color: isDark ? "#fbbf24" : "#b45309",
89
+ // yellow-400 / yellow-800
50
90
  enabled: true,
51
91
  globalStat: true,
52
92
  userStat: true
53
93
  },
54
94
  {
55
95
  dataKey: "totalComments",
56
- name: "Total comments",
57
- color: "#739973",
96
+ name: t("stats.totalComments"),
97
+ color: isDark ? "#2dd4bf" : "#0e7490",
98
+ // teal-400 / cyan-800
58
99
  enabled: true,
59
100
  globalStat: true,
60
101
  userStat: true
61
102
  },
62
103
  {
63
104
  dataKey: "totalVotes",
64
- name: "Total votes",
65
- color: "#d88884",
105
+ name: t("stats.totalVotes"),
106
+ color: isDark ? "#f87171" : "#991b1b",
107
+ // red-400 / red-800
66
108
  enabled: true,
67
109
  globalStat: true,
68
110
  userStat: true
69
111
  },
70
112
  {
71
113
  dataKey: "totalUsers",
72
- name: "Total users",
73
- color: "#ff0000",
114
+ name: t("stats.totalUsers"),
115
+ color: isDark ? "#a78bfa" : "#6d28d9",
116
+ // purple-400 / purple-800
74
117
  enabled: true,
75
118
  globalStat: true,
76
119
  userStat: false
77
120
  },
78
121
  {
79
122
  dataKey: "totalTags",
80
- name: "Total tags",
81
- color: "#ff00ff",
123
+ name: t("stats.totalTags"),
124
+ color: isDark ? "#34d399" : "#065f46",
125
+ // emerald-400 / emerald-900
82
126
  enabled: true,
83
127
  globalStat: true,
84
128
  userStat: false
85
129
  },
86
130
  {
87
131
  dataKey: "totalArticles",
88
- name: "Total articles",
89
- color: "#00ff00",
132
+ name: t("stats.totalArticles"),
133
+ color: isDark ? "#60a5fa" : "#1d4ed8",
134
+ // blue-400 / blue-800
90
135
  enabled: true,
91
136
  globalStat: true,
92
137
  userStat: true
93
138
  },
94
139
  {
95
140
  dataKey: "totalFollowers",
96
- name: "Total followers",
97
- color: "#ff0000",
141
+ name: t("stats.totalFollowers"),
142
+ color: isDark ? "#f472b6" : "#be185d",
143
+ // pink-400 / pink-800
98
144
  enabled: true,
99
145
  globalStat: false,
100
146
  userStat: true
@@ -103,10 +149,11 @@ const DEFAULT_STATS = [
103
149
  const useChartState = (data) => {
104
150
  const styles = useStyles();
105
151
  const isDark = useIsDarkTheme();
152
+ const { t } = useTranslationRef(qetaTranslationRef);
106
153
  const globalStats = isGlobalStat(data[0]);
107
154
  const isUserStats = isUserStat(data[0]);
108
155
  const [stats, setStats] = useState(
109
- DEFAULT_STATS.filter((stat) => {
156
+ getDefaultStats(isDark, t).filter((stat) => {
110
157
  if (globalStats && !stat.globalStat) {
111
158
  return false;
112
159
  }
@@ -131,25 +178,63 @@ const useChartState = (data) => {
131
178
  );
132
179
  return { styles, isDark, toggleStat, stats, isDisabled };
133
180
  };
181
+ const legendWrapperStyle = {
182
+ cursor: "pointer",
183
+ display: "flex",
184
+ gap: 16,
185
+ marginLeft: 48
186
+ };
187
+ const XAxisTick = (props) => {
188
+ const { x, y, payload, fill } = props;
189
+ return /* @__PURE__ */ jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsx(
190
+ "text",
191
+ {
192
+ x: -6,
193
+ y: -12,
194
+ dy: 16,
195
+ textAnchor: "end",
196
+ fill,
197
+ transform: "rotate(-90)",
198
+ fontSize: "12",
199
+ children: new Date(payload.value).toDateString()
200
+ }
201
+ ) });
202
+ };
134
203
  const StatsBarChart = (props) => {
135
204
  const { styles, isDark, stats, toggleStat, isDisabled } = useChartState(
136
205
  props.data
137
206
  );
138
207
  const localStyles = useStyles();
139
- return /* @__PURE__ */ jsx(ResponsiveContainer, { height: 400, width: "100%", children: /* @__PURE__ */ jsxs(
208
+ const { t } = useTranslationRef(qetaTranslationRef);
209
+ return /* @__PURE__ */ jsx(ResponsiveContainer, { height: 500, width: "100%", children: /* @__PURE__ */ jsxs(
140
210
  BarChart,
141
211
  {
142
212
  data: props.data,
143
- width: 900,
144
- height: 300,
213
+ width: 1e3,
214
+ height: 400,
215
+ margin: { bottom: 80, right: 30 },
145
216
  className: localStyles.barChart,
217
+ "aria-label": t("stats.barChart"),
146
218
  children: [
147
219
  /* @__PURE__ */ jsx(
148
- Tooltip,
220
+ Tooltip$1,
149
221
  {
150
222
  labelClassName: styles.tooltipLabel,
151
223
  wrapperClassName: styles.tooltipWrapper,
152
- cursor: { fill: isDark ? "#4f4f4f" : "#f5f5f5" }
224
+ cursor: { fill: isDark ? "#4f4f4f" : "#f5f5f5" },
225
+ content: ({ active, payload, label }) => {
226
+ if (active && payload && payload.length) {
227
+ return /* @__PURE__ */ jsxs("div", { role: "tooltip", "aria-label": t("stats.tooltip"), children: [
228
+ /* @__PURE__ */ jsx("p", { children: new Date(label).toLocaleDateString() }),
229
+ payload.map((entry) => /* @__PURE__ */ jsxs("p", { style: { color: entry.color }, children: [
230
+ entry.name,
231
+ ": ",
232
+ entry.value
233
+ ] }, entry.name))
234
+ ] });
235
+ }
236
+ return null;
237
+ }
153
238
  }
154
239
  ),
155
240
  stats.map((stat) => /* @__PURE__ */ jsx(
@@ -157,11 +242,12 @@ const StatsBarChart = (props) => {
157
242
  {
158
243
  dataKey: stat.enabled ? stat.dataKey : "hidden",
159
244
  name: stat.name,
160
- fill: stat.color
245
+ fill: stat.color,
246
+ "aria-label": stat.name
161
247
  },
162
248
  stat.dataKey
163
249
  )),
164
- /* @__PURE__ */ jsx(CartesianGrid, { stroke: "#ccc" }),
250
+ /* @__PURE__ */ jsx(CartesianGrid, { stroke: isDark ? "#4f4f4f" : "#e0e0e0" }),
165
251
  /* @__PURE__ */ jsx(
166
252
  XAxis,
167
253
  {
@@ -169,7 +255,10 @@ const StatsBarChart = (props) => {
169
255
  tickFormatter: (tick) => new Date(tick).toDateString(),
170
256
  axisLine: { stroke: isDark ? "white" : "black" },
171
257
  tickLine: { stroke: isDark ? "white" : "black" },
172
- tick: { fill: isDark ? "white" : "black" }
258
+ tick: /* @__PURE__ */ jsx(XAxisTick, { fill: isDark ? "white" : "black" }),
259
+ angle: -90,
260
+ dy: 10,
261
+ "aria-label": t("stats.dateAxis")
173
262
  }
174
263
  ),
175
264
  /* @__PURE__ */ jsx(
@@ -178,7 +267,8 @@ const StatsBarChart = (props) => {
178
267
  allowDecimals: false,
179
268
  axisLine: { stroke: isDark ? "white" : "black" },
180
269
  tickLine: { stroke: isDark ? "white" : "black" },
181
- tick: { fill: isDark ? "white" : "black" }
270
+ tick: { fill: isDark ? "white" : "black" },
271
+ "aria-label": t("stats.valueAxis")
182
272
  }
183
273
  ),
184
274
  /* @__PURE__ */ jsx(
@@ -186,7 +276,8 @@ const StatsBarChart = (props) => {
186
276
  {
187
277
  verticalAlign: "top",
188
278
  height: 36,
189
- wrapperStyle: { cursor: "pointer" },
279
+ wrapperStyle: legendWrapperStyle,
280
+ className: localStyles.legend,
190
281
  formatter: (data) => {
191
282
  return isDisabled(data) ? `[ ] ${data}` : `[x] ${data}`;
192
283
  },
@@ -206,16 +297,18 @@ const StatsLineChart = (props) => {
206
297
  props.data
207
298
  );
208
299
  const localStyles = useStyles();
209
- return /* @__PURE__ */ jsx(ResponsiveContainer, { height: 400, width: "100%", children: /* @__PURE__ */ jsxs(
300
+ const { t } = useTranslationRef(qetaTranslationRef);
301
+ return /* @__PURE__ */ jsx(ResponsiveContainer, { height: 500, width: "100%", children: /* @__PURE__ */ jsxs(
210
302
  LineChart,
211
303
  {
212
304
  data: props.data,
213
- width: 900,
214
- height: 300,
305
+ width: 1e3,
306
+ height: 400,
307
+ margin: { bottom: 80, right: 30 },
215
308
  className: localStyles.lineChart,
216
309
  children: [
217
310
  /* @__PURE__ */ jsx(
218
- Tooltip,
311
+ Tooltip$1,
219
312
  {
220
313
  labelClassName: styles.tooltipLabel,
221
314
  wrapperClassName: styles.tooltipWrapper,
@@ -239,7 +332,10 @@ const StatsLineChart = (props) => {
239
332
  tickFormatter: (tick) => new Date(tick).toDateString(),
240
333
  axisLine: { stroke: isDark ? "white" : "black" },
241
334
  tickLine: { stroke: isDark ? "white" : "black" },
242
- tick: { fill: isDark ? "white" : "black" }
335
+ tick: /* @__PURE__ */ jsx(XAxisTick, { fill: isDark ? "white" : "black" }),
336
+ angle: -45,
337
+ dy: 10,
338
+ "aria-label": t("stats.dateAxis")
243
339
  }
244
340
  ),
245
341
  /* @__PURE__ */ jsx(
@@ -256,7 +352,8 @@ const StatsLineChart = (props) => {
256
352
  {
257
353
  verticalAlign: "top",
258
354
  height: 36,
259
- wrapperStyle: { cursor: "pointer" },
355
+ wrapperStyle: legendWrapperStyle,
356
+ className: localStyles.legend,
260
357
  formatter: (data) => {
261
358
  return isDisabled(data) ? `[ ] ${data}` : `[x] ${data}`;
262
359
  },
@@ -274,34 +371,49 @@ const StatsLineChart = (props) => {
274
371
  const StatsChart = (props) => {
275
372
  const { t } = useTranslationRef(qetaTranslationRef);
276
373
  const [chart, setChart] = useState("line");
374
+ const classes = useStyles();
375
+ if (props.error) {
376
+ return /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", className: classes.errorMessage, children: props.error });
377
+ }
277
378
  if (!props.data || props.data.length === 0) {
278
379
  return /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: t("stats.noStats") });
279
380
  }
280
381
  const data = props.data.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()).map((d) => ({ ...d, hidden: 0 }));
281
- return /* @__PURE__ */ jsxs(Fragment, { children: [
382
+ return /* @__PURE__ */ jsxs(Box, { className: classes.chartContainer, children: [
383
+ props.loading && /* @__PURE__ */ jsx("div", { className: classes.loadingOverlay, children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
282
384
  chart === "line" ? /* @__PURE__ */ jsx(StatsLineChart, { data }) : /* @__PURE__ */ jsx(StatsBarChart, { data }),
283
- /* @__PURE__ */ jsxs(ButtonGroup, { "aria-label": "Chart type", style: { float: "right" }, children: [
284
- /* @__PURE__ */ jsx(
285
- IconButton,
286
- {
287
- "aria-label": "Line chart",
288
- onClick: () => {
289
- setChart("line");
290
- },
291
- children: /* @__PURE__ */ jsx(ShowChartIcon, {})
292
- }
293
- ),
294
- /* @__PURE__ */ jsx(
295
- IconButton,
296
- {
297
- "aria-label": "Bar chart",
298
- onClick: () => {
299
- setChart("bar");
300
- },
301
- children: /* @__PURE__ */ jsx(BarChartIcon, {})
302
- }
303
- )
304
- ] })
385
+ /* @__PURE__ */ jsxs(
386
+ ButtonGroup,
387
+ {
388
+ "aria-label": t("stats.chartType"),
389
+ style: { float: "right" },
390
+ role: "radiogroup",
391
+ children: [
392
+ /* @__PURE__ */ jsx(Tooltip, { title: t("stats.lineChart"), children: /* @__PURE__ */ jsx(
393
+ IconButton,
394
+ {
395
+ "aria-label": t("stats.lineChart"),
396
+ onClick: () => setChart("line"),
397
+ className: classes.chartTypeButton,
398
+ "aria-checked": chart === "line",
399
+ role: "radio",
400
+ children: /* @__PURE__ */ jsx(ShowChartIcon, {})
401
+ }
402
+ ) }),
403
+ /* @__PURE__ */ jsx(Tooltip, { title: t("stats.barChart"), children: /* @__PURE__ */ jsx(
404
+ IconButton,
405
+ {
406
+ "aria-label": t("stats.barChart"),
407
+ onClick: () => setChart("bar"),
408
+ className: classes.chartTypeButton,
409
+ "aria-checked": chart === "bar",
410
+ role: "radio",
411
+ children: /* @__PURE__ */ jsx(BarChartIcon, {})
412
+ }
413
+ ) })
414
+ ]
415
+ }
416
+ )
305
417
  ] });
306
418
  };
307
419