@redocly/theme 0.30.8 → 0.31.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 (30) hide show
  1. package/lib/components/Catalog/CatalogCard.d.ts +0 -1
  2. package/lib/components/Catalog/CatalogCard.js +27 -16
  3. package/lib/components/Catalog/useCatalog.js +14 -7
  4. package/lib/components/Filter/Filter.js +1 -5
  5. package/lib/components/LastUpdated/LastUpdated.js +4 -1
  6. package/lib/components/OpenApiDocs/ScorecardBadges.d.ts +2 -1
  7. package/lib/components/OpenApiDocs/ScorecardBadges.js +29 -5
  8. package/lib/components/Scorecard/Card.d.ts +4 -1
  9. package/lib/components/Scorecard/Card.js +14 -3
  10. package/lib/components/Scorecard/Gauge.d.ts +5 -2
  11. package/lib/components/Scorecard/Gauge.js +6 -3
  12. package/lib/components/Scorecard/StatusByLevelWidget.d.ts +1 -0
  13. package/lib/components/Scorecard/StatusByLevelWidget.js +5 -2
  14. package/lib/config.d.ts +24 -5
  15. package/lib/config.js +3 -7
  16. package/lib/types/portal/src/shared/constants.d.ts +1 -0
  17. package/lib/types/portal/src/shared/constants.js +2 -1
  18. package/lib/types/portal/src/shared/types/catalog.d.ts +6 -2
  19. package/package.json +1 -1
  20. package/src/components/Catalog/CatalogCard.tsx +34 -19
  21. package/src/components/Catalog/useCatalog.ts +23 -9
  22. package/src/components/Filter/Filter.tsx +1 -6
  23. package/src/components/LastUpdated/LastUpdated.tsx +6 -1
  24. package/src/components/OpenApiDocs/ScorecardBadges.tsx +35 -7
  25. package/src/components/Scorecard/Card.tsx +14 -2
  26. package/src/components/Scorecard/Gauge.tsx +16 -8
  27. package/src/components/Scorecard/StatusByLevelWidget.tsx +6 -2
  28. package/src/config.ts +2 -6
  29. package/src/types/portal/src/shared/constants.ts +1 -0
  30. package/src/types/portal/src/shared/types/catalog.ts +3 -2
@@ -3,4 +3,3 @@ import type { CatalogItem } from '../../types/portal/src/shared/types/catalog';
3
3
  export declare function CatalogCard({ item }: {
4
4
  item: CatalogItem;
5
5
  }): JSX.Element;
6
- export declare function statusToColor(status: string): "error" | "" | "warning" | "success";
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.statusToColor = exports.CatalogCard = void 0;
29
+ exports.CatalogCard = void 0;
30
30
  const React = __importStar(require("react"));
31
31
  const styled_components_1 = __importDefault(require("styled-components"));
32
32
  const Link_1 = require("../../mocks/Link");
@@ -35,9 +35,10 @@ const Tag_1 = require("../../components/Tag");
35
35
  const icons_1 = require("../../icons");
36
36
  const telemetry_1 = require("../../mocks/telemetry");
37
37
  const utils_1 = require("../../utils");
38
+ const Card_1 = require("../../components/Scorecard/Card");
38
39
  function CatalogCard({ item }) {
39
40
  var _a, _b;
40
- const hasTags = item.scorecardStatus || ((_a = item.tags) === null || _a === void 0 ? void 0 : _a.length);
41
+ const hasTags = item.scorecardLevel || ((_a = item.tags) === null || _a === void 0 ? void 0 : _a.length);
41
42
  return (React.createElement(Link_1.Link, { key: item.docsLink || item.link, to: item.docsLink || item.link },
42
43
  React.createElement(StyledCard, { onClick: () => telemetry_1.telemetry.send('catalog_item_clicked', {}) },
43
44
  React.createElement(CardTitle, null,
@@ -49,26 +50,13 @@ function CatalogCard({ item }) {
49
50
  hasTags && (React.createElement(CardTags, null,
50
51
  (item.tags || []).map((tag, index) => (React.createElement(CardTag, { key: tag + index, color: (0, utils_1.slug)(tag) },
51
52
  React.createElement(Highlight_1.Highlight, null, (0, utils_1.capitalize)(tag))))),
52
- (item.scorecardLevel && item.scorecardStatus && (React.createElement(CardTag, { color: statusToColor(item.scorecardStatus), className: "tag-variant-scorecard-level" },
53
+ (item.scorecardLevel && (React.createElement(StatusDot, { color: (0, Card_1.getLevelColor)(item.scorecardLevelIdx || -1, Object.keys(item.scorecardLevels || {}).length) },
53
54
  React.createElement(Highlight_1.Highlight, null, item.scorecardLevel)))) ||
54
55
  null)),
55
56
  React.createElement(SelectButton, null,
56
57
  React.createElement(icons_1.PointingArrowIcon, null)))))));
57
58
  }
58
59
  exports.CatalogCard = CatalogCard;
59
- function statusToColor(status) {
60
- switch (status) {
61
- case 'Below minimum':
62
- return 'error';
63
- case 'Minimum':
64
- return 'warning';
65
- case 'Highest':
66
- return 'success';
67
- default:
68
- return '';
69
- }
70
- }
71
- exports.statusToColor = statusToColor;
72
60
  const SelectButton = styled_components_1.default.div `
73
61
  border: 1px solid var(--catalog-card-button-border-color);
74
62
  border-radius: 100%;
@@ -153,4 +141,27 @@ const CardTag = (0, styled_components_1.default)(Tag_1.Tag) `
153
141
  text-transform: inherit;
154
142
  margin: 0;
155
143
  `;
144
+ const StatusDot = styled_components_1.default.div `
145
+ font-size: var(--font-size-base);
146
+ font-style: normal;
147
+ font-weight: 400;
148
+ line-height: 22px;
149
+ display: flex;
150
+ align-items: center;
151
+ &:not(:only-child) {
152
+ margin-left: 4px;
153
+ }
154
+
155
+ ${({ color }) => color &&
156
+ `
157
+ &::before {
158
+ content: '';
159
+ display: inline-block;
160
+ width: 10px;
161
+ height: 10px;
162
+ border-radius: 50%;
163
+ background-color: ${color};
164
+ margin-right: 8px;
165
+ }`};
166
+ `;
156
167
  //# sourceMappingURL=CatalogCard.js.map
@@ -117,7 +117,6 @@ function useCatalog(items, config) {
117
117
  // filterParents[i] is a Set with indexes of parents of this filter
118
118
  const filterParents = React.useMemo(() => collectFilterParents(config.filters), [config.filters]);
119
119
  return React.useMemo(() => {
120
- var _a;
121
120
  const filters = filtersWithOptions.map((filter, idx) => {
122
121
  var _a, _b, _c, _d;
123
122
  return (Object.assign(Object.assign({}, filter), { toggleOption: (value) => toggleOption(idx, value), selectOption: (value) => selectOption(idx, value), selectedOptions: (_a = filtersState[idx]) !== null && _a !== void 0 ? _a : new Set(), isFilterUsed: ((_c = (_b = filtersState[idx]) === null || _b === void 0 ? void 0 : _b.size) !== null && _c !== void 0 ? _c : 0) > 0 || !!((_d = filtersState[idx]) === null || _d === void 0 ? void 0 : _d.from) }));
@@ -140,7 +139,7 @@ function useCatalog(items, config) {
140
139
  const adjustedFilterOptions = collectFilterOptions(filteredWithOtherFilters.map((m) => ({ metadata: m })), config.filters);
141
140
  return Object.assign(Object.assign({}, filter), { parentUsed, filteredOptions: adjustedFilterOptions[idx].options });
142
141
  });
143
- const groups = config.groupByFirstFilter && ((_a = filters[0].selectedOptions) === null || _a === void 0 ? void 0 : _a.size) > 0
142
+ const groups = config.groupByFirstFilter
144
143
  ? groupByFirstFilter(resolvedFilters, filteredItems)
145
144
  : [{ title: 'APIs', items: filteredItems }];
146
145
  return {
@@ -241,7 +240,7 @@ function collectFilterOptions(items, filters) {
241
240
  ? (_b = item.metadata) === null || _b === void 0 ? void 0 : _b[filter.property]
242
241
  : [(_c = item.metadata) === null || _c === void 0 ? void 0 : _c[filter.property]];
243
242
  for (const value of values) {
244
- const str = String(value);
243
+ const str = mapFilterValues(String(value), filter.valuesMapping);
245
244
  if (staticOptions) {
246
245
  if (str in staticOptions) {
247
246
  usedOptions[str] = usedOptions[str] + 1;
@@ -256,9 +255,10 @@ function collectFilterOptions(items, filters) {
256
255
  }
257
256
  }
258
257
  }
259
- const options = Object.entries(usedOptions)
260
- .map(([value, count]) => ({ value, count }))
261
- .sort((a, b) => b.value.localeCompare(a.value));
258
+ const options = Object.entries(usedOptions).map(([value, count]) => ({ value, count }));
259
+ if (!staticOptions) {
260
+ options.sort((a, b) => b.value.localeCompare(a.value));
261
+ }
262
262
  if (othersCount) {
263
263
  options.push({
264
264
  value: filter.missingCategoryNameTranslationKey || filter.missingCategoryName || 'Others',
@@ -286,7 +286,7 @@ function filterItems(normalizedItems, filters, term) {
286
286
  if (filter.selectedOptions.size === 0) {
287
287
  return true;
288
288
  }
289
- const itemValue = (item === null || item === void 0 ? void 0 : item[filter.property]) || filter.missingCategoryName || 'Others';
289
+ const itemValue = mapFilterValues((item === null || item === void 0 ? void 0 : item[filter.property]) || filter.missingCategoryName || 'Others', filter.valuesMapping);
290
290
  if (Array.isArray(itemValue)) {
291
291
  return itemValue.some((value) => filter.selectedOptions.has(value));
292
292
  }
@@ -307,4 +307,11 @@ function filterItems(normalizedItems, filters, term) {
307
307
  return filteredByFilters;
308
308
  }
309
309
  }
310
+ function mapFilterValues(value, mapping) {
311
+ if (!mapping)
312
+ return value;
313
+ return Array.isArray(value)
314
+ ? value.map((v) => mapping[String(v)] || v)
315
+ : mapping[String(value)] || value;
316
+ }
310
317
  //# sourceMappingURL=useCatalog.js.map
@@ -52,7 +52,7 @@ function Filter({ filter, filterValuesCasing, }) {
52
52
  return;
53
53
  filter.selectOption(Object.assign(Object.assign({}, filter.selectedOptions), { to: formatDateWithNoTimeZone(to) }));
54
54
  } })))) : (filter.filteredOptions.map((value) => {
55
- const id = 'filter--' + filter.property + '--' + slug(value.value);
55
+ const id = 'filter--' + filter.property + '--' + value.value;
56
56
  return (react_1.default.createElement(FilterOption, { key: id, role: "link", onClick: () => filter.toggleOption(value.value) },
57
57
  react_1.default.createElement(icons_1.CheckboxIcon, { checked: filter.selectedOptions.has(value.value) }),
58
58
  react_1.default.createElement(FilterOptionLabel, null, changeCasing(translate(value.value), filterValuesCasing)),
@@ -133,10 +133,6 @@ const StyledSelect = (0, styled_components_1.default)(Select_1.Select) `
133
133
  margin: var(--filter-select-option-margin);
134
134
  }
135
135
  `;
136
- // TODO: import from portal
137
- function slug(str) {
138
- return str.replace(/\s/g, '-').toLowerCase();
139
- }
140
136
  const DatePickerWrapper = styled_components_1.default.div `
141
137
  color: var(--filter-date-picker-color);
142
138
  display: flex;
@@ -32,6 +32,7 @@ const styled_components_1 = __importDefault(require("styled-components"));
32
32
  const timeago_js_1 = require("timeago.js");
33
33
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
34
34
  const hooks_1 = require("../../mocks/hooks");
35
+ const constants_1 = require("../../types/portal/src/shared/constants");
35
36
  const FORMATS = {
36
37
  timeago: (date, locale) => (0, timeago_js_1.format)(date, locale),
37
38
  iso: (date) => date.toISOString().split('T')[0],
@@ -47,7 +48,9 @@ function LastUpdated(props) {
47
48
  }
48
49
  const lastModified = props.lastModified;
49
50
  const format = props.format || lastUpdatedBlock.format || 'timeago';
50
- const locale = props.locale || lastUpdatedBlock.locale || currentLocale || 'en-US';
51
+ const locale = props.locale ||
52
+ lastUpdatedBlock.locale ||
53
+ (currentLocale !== constants_1.DEFAULT_LOCALE_PLACEHOLDER ? currentLocale || 'en-US' : 'en-US');
51
54
  const isoDate = lastModified.toISOString().split('T')[0];
52
55
  const lastUpdatedString = FORMATS[format](lastModified, locale);
53
56
  const translationKey = format === 'timeago' ? 'theme.page.lastUpdated.timeago' : 'theme.page.lastUpdated.on';
@@ -2,8 +2,9 @@ import React from 'react';
2
2
  interface ScorecardBadgesProps {
3
3
  metadata?: {
4
4
  scorecardLevel?: string;
5
- scorecardStatus?: string;
6
5
  scoreCardSlug?: string;
6
+ scorecardLevelIdx?: number;
7
+ scorecardLevels?: Record<string, any>;
7
8
  };
8
9
  }
9
10
  export declare function ScorecardBadges(props: ScorecardBadgesProps): React.JSX.Element | null;
@@ -9,11 +9,11 @@ const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Tag_1 = require("../../components/Tag");
10
10
  const Link_1 = require("../../mocks/Link");
11
11
  const telemetry_1 = require("../../mocks/telemetry");
12
- const CatalogCard_1 = require("../../components/Catalog/CatalogCard");
12
+ const Card_1 = require("../../components/Scorecard/Card");
13
13
  function ScorecardBadges(props) {
14
- var _a, _b, _c;
14
+ var _a, _b, _c, _d;
15
15
  return ((((_a = props.metadata) === null || _a === void 0 ? void 0 : _a.scorecardLevel) && (react_1.default.createElement(ScorecardBadgesWrapper, { "data-component-name": "OpenApiDocs/ScorecardBadges" },
16
- react_1.default.createElement(ComplianceTag, { level: props.metadata.scorecardLevel, status: (_b = props.metadata) === null || _b === void 0 ? void 0 : _b.scorecardStatus, slug: (_c = props.metadata) === null || _c === void 0 ? void 0 : _c.scoreCardSlug })))) ||
16
+ react_1.default.createElement(ComplianceTag, { level: props.metadata.scorecardLevel, slug: (_b = props.metadata) === null || _b === void 0 ? void 0 : _b.scoreCardSlug, color: (0, Card_1.getLevelColor)((_c = props.metadata) === null || _c === void 0 ? void 0 : _c.scorecardLevelIdx, Object.keys(((_d = props.metadata) === null || _d === void 0 ? void 0 : _d.scorecardLevels) || {}).length) })))) ||
17
17
  null);
18
18
  }
19
19
  exports.ScorecardBadges = ScorecardBadges;
@@ -27,8 +27,32 @@ const ScorecardBadgesWrapper = styled_components_1.default.div `
27
27
  right: var(--panel-gap-horizontal);
28
28
  `;
29
29
  function ComplianceTag(props) {
30
- const { level, status, slug } = props;
30
+ const { level, slug, color } = props;
31
31
  return (react_1.default.createElement(Link_1.Link, { to: slug },
32
- react_1.default.createElement(Tag_1.Tag, { color: (0, CatalogCard_1.statusToColor)(status), size: "large", onClick: () => telemetry_1.telemetry.send('scorecard_link_clicked', { action: 'click' }) }, level)));
32
+ react_1.default.createElement(Tag_1.Tag, { size: "large", onClick: () => telemetry_1.telemetry.send('scorecard_link_clicked', { action: 'click' }) },
33
+ react_1.default.createElement(StatusDot, { color: color }, level))));
33
34
  }
35
+ const StatusDot = styled_components_1.default.div `
36
+ font-size: var(--font-size-base);
37
+ font-style: normal;
38
+ font-weight: 400;
39
+ line-height: 22px;
40
+ display: flex;
41
+ align-items: center;
42
+ &:not(:only-child) {
43
+ margin-left: 4px;
44
+ }
45
+
46
+ ${({ color }) => color &&
47
+ `
48
+ &::before {
49
+ content: '';
50
+ display: inline-block;
51
+ width: 10px;
52
+ height: 10px;
53
+ border-radius: 50%;
54
+ background-color: ${color};
55
+ margin-right: 8px;
56
+ }`};
57
+ `;
34
58
  //# sourceMappingURL=ScorecardBadges.js.map
@@ -1,2 +1,5 @@
1
- export declare const ScorecardCard: import("styled-components").StyledComponent<"div", any, {}, never>;
1
+ export declare const ScorecardCard: import("styled-components").StyledComponent<"div", any, {
2
+ 'data-component-name': string;
3
+ }, "data-component-name">;
2
4
  export declare const ScorecardCardTitle: import("styled-components").StyledComponent<"h3", any, {}, never>;
5
+ export declare function getLevelColor(idx: number, numberOfLevels: number): string;
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ScorecardCardTitle = exports.ScorecardCard = void 0;
6
+ exports.getLevelColor = exports.ScorecardCardTitle = exports.ScorecardCard = void 0;
7
7
  const styled_components_1 = __importDefault(require("styled-components"));
8
- exports.ScorecardCard = styled_components_1.default.div `
8
+ exports.ScorecardCard = styled_components_1.default.div.attrs({
9
+ 'data-component-name': 'Scorecard/ScorecardCard',
10
+ }) `
9
11
  color: var(--text-primary);
10
- background-color: var(--thin-tile-background-color);
12
+ background-color: var(--bg-raised);
11
13
  border-radius: 4px;
12
14
 
13
15
  border: 1px solid var(--border-primary);
@@ -23,4 +25,13 @@ exports.ScorecardCardTitle = styled_components_1.default.h3 `
23
25
  margin-bottom: 10px;
24
26
  margin-top: 0;
25
27
  `;
28
+ function getLevelColor(idx, numberOfLevels) {
29
+ if (numberOfLevels === 0) {
30
+ return 'hsl(0, 0%, 50%)';
31
+ }
32
+ const hue = ((idx + 1) / numberOfLevels) * 120;
33
+ const lum = 50 - ((idx + 1) / numberOfLevels) * 25;
34
+ return `hsl(${hue}, 100%, ${lum}%)`;
35
+ }
36
+ exports.getLevelColor = getLevelColor;
26
37
  //# sourceMappingURL=Card.js.map
@@ -1,8 +1,11 @@
1
1
  import * as React from 'react';
2
- export declare function Gauge({ chunks, }: {
2
+ export interface GaugeProps {
3
3
  chunks: {
4
4
  share: number;
5
5
  color: string;
6
+ title?: string;
6
7
  }[];
7
- }): React.JSX.Element;
8
+ className?: string;
9
+ }
10
+ export declare function Gauge({ chunks, className }: GaugeProps): React.JSX.Element;
8
11
  export declare const GaugeValue: import("styled-components").StyledComponent<"span", any, {}, never>;
@@ -29,15 +29,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.GaugeValue = exports.Gauge = void 0;
30
30
  const React = __importStar(require("react"));
31
31
  const styled_components_1 = __importDefault(require("styled-components"));
32
- function Gauge({ chunks, }) {
33
- return (React.createElement(GaugeWrapper, null, chunks.map((chunk, i) => (React.createElement(GaugeChunk, Object.assign({ key: i }, chunk))))));
32
+ function Gauge({ chunks, className }) {
33
+ const title = chunks
34
+ .map((chunk) => chunk.title)
35
+ .filter(Boolean)
36
+ .join(', ');
37
+ return (React.createElement(GaugeWrapper, { "data-component-name": "Scorecard/StatusByLevelWidget", className: className, title: title }, chunks.map((chunk, i) => (React.createElement(GaugeChunk, { key: i, share: chunk.share, color: chunk.color })))));
34
38
  }
35
39
  exports.Gauge = Gauge;
36
40
  exports.GaugeValue = styled_components_1.default.span `
37
41
  font-size: var(--font-size-lg);
38
42
  line-height: var(line-height-regular);
39
43
 
40
- margin-right: 5px;
41
44
  vertical-align: middle;
42
45
  display: inline-block;
43
46
  margin-right: 0;
@@ -7,5 +7,6 @@ export interface StatusByLevelWidgetProps {
7
7
  warnings: number;
8
8
  total: number;
9
9
  }[];
10
+ className?: string;
10
11
  }
11
12
  export declare function StatusByLevelWidget(props: StatusByLevelWidgetProps): React.JSX.Element;
@@ -32,8 +32,8 @@ const styled_components_1 = __importDefault(require("styled-components"));
32
32
  const Gauge_1 = require("../../components/Scorecard/Gauge");
33
33
  const Card_1 = require("../../components/Scorecard/Card");
34
34
  function StatusByLevelWidget(props) {
35
- const { levels, title } = props;
36
- return (React.createElement(Card_1.ScorecardCard, null,
35
+ const { levels, title, className } = props;
36
+ return (React.createElement(Card_1.ScorecardCard, { "data-component-name": "Scorecard/StatusByLevelWidget", className: className },
37
37
  React.createElement(Card_1.ScorecardCardTitle, null, title),
38
38
  React.createElement(CardBody, null, levels.map((level) => {
39
39
  const success = level.total - level.errors - level.warnings;
@@ -43,14 +43,17 @@ function StatusByLevelWidget(props) {
43
43
  {
44
44
  share: (success / level.total) * 100,
45
45
  color: 'var(--scorecard-color-success)',
46
+ title: `${success} passed`,
46
47
  },
47
48
  {
48
49
  share: (level.warnings / level.total) * 100,
49
50
  color: 'var(--scorecard-color-warning)',
51
+ title: `${level.warnings} ${level.warnings === 1 ? 'warning' : 'warnings'}`,
50
52
  },
51
53
  {
52
54
  share: (level.errors / level.total) * 100,
53
55
  color: 'var(--scorecard-color-error)',
56
+ title: `${level.errors} ${level.errors === 1 ? 'error' : 'errors'}`,
54
57
  },
55
58
  ] }),
56
59
  React.createElement(Gauge_1.GaugeValue, null,
package/lib/config.d.ts CHANGED
@@ -269,6 +269,12 @@ declare const catalogFilterSchema: {
269
269
  readonly parentFilter: {
270
270
  readonly type: "string";
271
271
  };
272
+ readonly valuesMapping: {
273
+ readonly type: "object";
274
+ readonly additionalProperties: {
275
+ readonly type: "string";
276
+ };
277
+ };
272
278
  readonly missingCategoryName: {
273
279
  readonly type: "string";
274
280
  };
@@ -315,6 +321,9 @@ declare const scorecardConfigSchema: {
315
321
  readonly name: {
316
322
  readonly type: "string";
317
323
  };
324
+ readonly color: {
325
+ readonly type: "string";
326
+ };
318
327
  readonly extends: {
319
328
  readonly type: "array";
320
329
  readonly items: {
@@ -390,6 +399,12 @@ declare const catalogSchema: {
390
399
  readonly parentFilter: {
391
400
  readonly type: "string";
392
401
  };
402
+ readonly valuesMapping: {
403
+ readonly type: "object";
404
+ readonly additionalProperties: {
405
+ readonly type: "string";
406
+ };
407
+ };
393
408
  readonly missingCategoryName: {
394
409
  readonly type: "string";
395
410
  };
@@ -1690,6 +1705,12 @@ export declare const themeConfigSchema: {
1690
1705
  readonly parentFilter: {
1691
1706
  readonly type: "string";
1692
1707
  };
1708
+ readonly valuesMapping: {
1709
+ readonly type: "object";
1710
+ readonly additionalProperties: {
1711
+ readonly type: "string";
1712
+ };
1713
+ };
1693
1714
  readonly missingCategoryName: {
1694
1715
  readonly type: "string";
1695
1716
  };
@@ -1882,6 +1903,9 @@ export declare const themeConfigSchema: {
1882
1903
  readonly name: {
1883
1904
  readonly type: "string";
1884
1905
  };
1906
+ readonly color: {
1907
+ readonly type: "string";
1908
+ };
1885
1909
  readonly extends: {
1886
1910
  readonly type: "array";
1887
1911
  readonly items: {
@@ -2414,9 +2438,4 @@ export type GoogleAnalyticsConfig = FromSchema<typeof googleAnalyticsConfigSchem
2414
2438
  export type CatalogConfig = FromSchema<typeof catalogSchema>;
2415
2439
  export type CatalogFilterConfig = FromSchema<typeof catalogFilterSchema>;
2416
2440
  export type ScorecardConfig = FromSchema<typeof scorecardConfigSchema>;
2417
- export declare enum ScorecardStatus {
2418
- BelowMinimum = "Below minimum",
2419
- Highest = "Highest",
2420
- Minimum = "Minimum"
2421
- }
2422
2441
  export {};
package/lib/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ScorecardStatus = exports.productThemeOverrideSchema = exports.themeConfigSchema = void 0;
3
+ exports.productThemeOverrideSchema = exports.themeConfigSchema = void 0;
4
4
  const logoConfigSchema = {
5
5
  type: 'object',
6
6
  properties: {
@@ -259,6 +259,7 @@ const catalogFilterSchema = {
259
259
  titleTranslationKey: { type: 'string' },
260
260
  property: { type: 'string' },
261
261
  parentFilter: { type: 'string' },
262
+ valuesMapping: { type: 'object', additionalProperties: { type: 'string' } },
262
263
  missingCategoryName: { type: 'string' },
263
264
  missingCategoryNameTranslationKey: { type: 'string' },
264
265
  options: { type: 'array', items: { type: 'string' } },
@@ -285,6 +286,7 @@ const scorecardConfigSchema = {
285
286
  required: ['name'],
286
287
  properties: {
287
288
  name: { type: 'string' },
289
+ color: { type: 'string' },
288
290
  extends: { type: 'array', items: { type: 'string' } },
289
291
  rules: {
290
292
  type: 'object',
@@ -609,10 +611,4 @@ exports.productThemeOverrideSchema = {
609
611
  additionalProperties: true,
610
612
  default: {},
611
613
  };
612
- var ScorecardStatus;
613
- (function (ScorecardStatus) {
614
- ScorecardStatus["BelowMinimum"] = "Below minimum";
615
- ScorecardStatus["Highest"] = "Highest";
616
- ScorecardStatus["Minimum"] = "Minimum";
617
- })(ScorecardStatus || (exports.ScorecardStatus = ScorecardStatus = {}));
618
614
  //# sourceMappingURL=config.js.map
@@ -1,6 +1,7 @@
1
1
  export declare const DEFAULT_THEME_NAME = "@redocly/theme";
2
2
  export declare const USER_THEME_ALIAS = "@theme";
3
3
  export declare const REDOCLY_TEAMS_RBAC = "redocly::teams-rbac";
4
+ export declare const DEFAULT_LOCALE_PLACEHOLDER = "default_locale";
4
5
  export type REQUIRED_OIDC_SCOPES = string[];
5
6
  export type DEFAULT_COOKIE_EXPIRATION = number;
6
7
  export declare enum FEEDBACK_TYPES {
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FsErrors = exports.ExternalRoutes = exports.DEV_LOGIN_SLUG = exports.FEEDBACK_TYPES = exports.REDOCLY_TEAMS_RBAC = exports.USER_THEME_ALIAS = exports.DEFAULT_THEME_NAME = void 0;
3
+ exports.FsErrors = exports.ExternalRoutes = exports.DEV_LOGIN_SLUG = exports.FEEDBACK_TYPES = exports.DEFAULT_LOCALE_PLACEHOLDER = exports.REDOCLY_TEAMS_RBAC = exports.USER_THEME_ALIAS = exports.DEFAULT_THEME_NAME = void 0;
4
4
  exports.DEFAULT_THEME_NAME = '@redocly/theme';
5
5
  exports.USER_THEME_ALIAS = '@theme';
6
6
  exports.REDOCLY_TEAMS_RBAC = 'redocly::teams-rbac';
7
+ exports.DEFAULT_LOCALE_PLACEHOLDER = 'default_locale';
7
8
  var FEEDBACK_TYPES;
8
9
  (function (FEEDBACK_TYPES) {
9
10
  FEEDBACK_TYPES["RATING"] = "rating";
@@ -1,4 +1,4 @@
1
- import { CatalogFilterConfig, ScorecardStatus } from '../../../../../index.js';
1
+ import { CatalogFilterConfig } from '../../../../../index.js';
2
2
  export type FilteredCatalog = {
3
3
  groups: {
4
4
  title: string;
@@ -31,8 +31,12 @@ export type CatalogItem = {
31
31
  description?: string;
32
32
  image?: string;
33
33
  docsLink?: string;
34
- scorecardStatus?: ScorecardStatus;
35
34
  scorecardLevel?: string;
35
+ scorecardLevelIdx?: number;
36
+ scorecardLevels?: Record<string, {
37
+ uniqueErrors: number;
38
+ uniqueWarnings: number;
39
+ }>;
36
40
  scoreCardSlug?: string;
37
41
  tags?: unknown[];
38
42
  [k: string]: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.30.8",
3
+ "version": "0.31.0",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -8,9 +8,10 @@ import { Tag } from '@theme/components/Tag';
8
8
  import { PointingArrowIcon } from '@theme/icons';
9
9
  import { telemetry } from '@portal/telemetry';
10
10
  import { slug, capitalize } from '@theme/utils';
11
+ import { getLevelColor } from '@theme/components/Scorecard/Card';
11
12
 
12
13
  export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
13
- const hasTags = item.scorecardStatus || item.tags?.length;
14
+ const hasTags = item.scorecardLevel || item.tags?.length;
14
15
  return (
15
16
  <Link key={item.docsLink || item.link} to={item.docsLink || item.link}>
16
17
  <StyledCard onClick={() => telemetry.send('catalog_item_clicked', {})}>
@@ -29,13 +30,15 @@ export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
29
30
  <Highlight>{capitalize(tag)}</Highlight>
30
31
  </CardTag>
31
32
  ))}
32
- {(item.scorecardLevel && item.scorecardStatus && (
33
- <CardTag
34
- color={statusToColor(item.scorecardStatus)}
35
- className="tag-variant-scorecard-level"
33
+ {(item.scorecardLevel && (
34
+ <StatusDot
35
+ color={getLevelColor(
36
+ item.scorecardLevelIdx || -1,
37
+ Object.keys(item.scorecardLevels || {}).length,
38
+ )}
36
39
  >
37
40
  <Highlight>{item.scorecardLevel}</Highlight>
38
- </CardTag>
41
+ </StatusDot>
39
42
  )) ||
40
43
  null}
41
44
  </CardTags>
@@ -50,19 +53,6 @@ export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
50
53
  );
51
54
  }
52
55
 
53
- export function statusToColor(status: string) {
54
- switch (status) {
55
- case 'Below minimum':
56
- return 'error';
57
- case 'Minimum':
58
- return 'warning';
59
- case 'Highest':
60
- return 'success';
61
- default:
62
- return '';
63
- }
64
- }
65
-
66
56
  const SelectButton = styled.div`
67
57
  border: 1px solid var(--catalog-card-button-border-color);
68
58
  border-radius: 100%;
@@ -154,3 +144,28 @@ const CardTag = styled(Tag)`
154
144
  text-transform: inherit;
155
145
  margin: 0;
156
146
  `;
147
+
148
+ const StatusDot = styled.div<{ color: string }>`
149
+ font-size: var(--font-size-base);
150
+ font-style: normal;
151
+ font-weight: 400;
152
+ line-height: 22px;
153
+ display: flex;
154
+ align-items: center;
155
+ &:not(:only-child) {
156
+ margin-left: 4px;
157
+ }
158
+
159
+ ${({ color }) =>
160
+ color &&
161
+ `
162
+ &::before {
163
+ content: '';
164
+ display: inline-block;
165
+ width: 10px;
166
+ height: 10px;
167
+ border-radius: 50%;
168
+ background-color: ${color};
169
+ margin-right: 8px;
170
+ }`};
171
+ `;
@@ -158,10 +158,9 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
158
158
  };
159
159
  });
160
160
 
161
- const groups =
162
- config.groupByFirstFilter && (filters[0].selectedOptions as any)?.size > 0
163
- ? groupByFirstFilter(resolvedFilters, filteredItems)
164
- : [{ title: 'APIs', items: filteredItems }];
161
+ const groups = config.groupByFirstFilter
162
+ ? groupByFirstFilter(resolvedFilters, filteredItems)
163
+ : [{ title: 'APIs', items: filteredItems }];
165
164
 
166
165
  return {
167
166
  groups,
@@ -283,7 +282,7 @@ function collectFilterOptions(
283
282
  : [item.metadata?.[filter.property]];
284
283
 
285
284
  for (const value of values) {
286
- const str = String(value);
285
+ const str = mapFilterValues(String(value), filter.valuesMapping) as string;
287
286
  if (staticOptions) {
288
287
  if (str in staticOptions) {
289
288
  usedOptions[str] = usedOptions[str] + 1;
@@ -299,15 +298,19 @@ function collectFilterOptions(
299
298
  }
300
299
  }
301
300
 
302
- const options = Object.entries(usedOptions)
303
- .map(([value, count]) => ({ value, count }))
304
- .sort((a, b) => b.value.localeCompare(a.value));
301
+ const options = Object.entries(usedOptions).map(([value, count]) => ({ value, count }));
302
+
303
+ if (!staticOptions) {
304
+ options.sort((a, b) => b.value.localeCompare(a.value));
305
+ }
306
+
305
307
  if (othersCount) {
306
308
  options.push({
307
309
  value: filter.missingCategoryNameTranslationKey || filter.missingCategoryName || 'Others',
308
310
  count: othersCount,
309
311
  });
310
312
  }
313
+
311
314
  return { ...filter, options };
312
315
  });
313
316
  }
@@ -336,7 +339,11 @@ function filterItems(
336
339
  return true;
337
340
  }
338
341
 
339
- const itemValue = item?.[filter.property] || filter.missingCategoryName || 'Others';
342
+ const itemValue = mapFilterValues(
343
+ item?.[filter.property] || filter.missingCategoryName || 'Others',
344
+ filter.valuesMapping,
345
+ );
346
+
340
347
  if (Array.isArray(itemValue)) {
341
348
  return itemValue.some((value) => (filter.selectedOptions as Set<any>).has(value));
342
349
  }
@@ -357,3 +364,10 @@ function filterItems(
357
364
  return filteredByFilters;
358
365
  }
359
366
  }
367
+
368
+ function mapFilterValues(value: unknown | unknown[], mapping?: Record<string, string>) {
369
+ if (!mapping) return value;
370
+ return Array.isArray(value)
371
+ ? value.map((v) => mapping[String(v)] || v)
372
+ : mapping[String(value)] || value;
373
+ }
@@ -109,7 +109,7 @@ export function Filter({
109
109
  </>
110
110
  ) : (
111
111
  filter.filteredOptions.map((value: any) => {
112
- const id = 'filter--' + filter.property + '--' + slug(value.value);
112
+ const id = 'filter--' + filter.property + '--' + value.value;
113
113
  return (
114
114
  <FilterOption key={id} role="link" onClick={() => filter.toggleOption(value.value)}>
115
115
  <CheckboxIcon checked={filter.selectedOptions.has(value.value)} />
@@ -210,11 +210,6 @@ const StyledSelect = styled(Select)`
210
210
  }
211
211
  `;
212
212
 
213
- // TODO: import from portal
214
- function slug(str: string): string {
215
- return str.replace(/\s/g, '-').toLowerCase();
216
- }
217
-
218
213
  const DatePickerWrapper = styled.div`
219
214
  color: var(--filter-date-picker-color);
220
215
  display: flex;
@@ -5,6 +5,8 @@ import { format } from 'timeago.js';
5
5
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
6
6
  import { useI18nConfig, useTranslate } from '@portal/hooks';
7
7
 
8
+ import { DEFAULT_LOCALE_PLACEHOLDER } from '../../types/portal/src/shared/constants';
9
+
8
10
  const FORMATS = {
9
11
  timeago: (date: Date, locale: string) => format(date, locale),
10
12
  iso: (date: Date) => date.toISOString().split('T')[0],
@@ -32,7 +34,10 @@ export function LastUpdated(props: LastUpdatedProps): JSX.Element | null {
32
34
 
33
35
  const lastModified = props.lastModified;
34
36
  const format = props.format || lastUpdatedBlock.format || 'timeago';
35
- const locale = props.locale || lastUpdatedBlock.locale || currentLocale || 'en-US';
37
+ const locale =
38
+ props.locale ||
39
+ lastUpdatedBlock.locale ||
40
+ (currentLocale !== DEFAULT_LOCALE_PLACEHOLDER ? currentLocale || 'en-US' : 'en-US');
36
41
 
37
42
  const isoDate = lastModified.toISOString().split('T')[0];
38
43
 
@@ -4,13 +4,14 @@ import styled from 'styled-components';
4
4
  import { Tag } from '@theme/components/Tag';
5
5
  import { Link } from '@portal/Link';
6
6
  import { telemetry } from '@portal/telemetry';
7
- import { statusToColor } from '@theme/components/Catalog/CatalogCard';
7
+ import { getLevelColor } from '@theme/components/Scorecard/Card';
8
8
 
9
9
  interface ScorecardBadgesProps {
10
10
  metadata?: {
11
11
  scorecardLevel?: string;
12
- scorecardStatus?: string;
13
12
  scoreCardSlug?: string;
13
+ scorecardLevelIdx?: number;
14
+ scorecardLevels?: Record<string, any>;
14
15
  };
15
16
  }
16
17
 
@@ -20,8 +21,11 @@ export function ScorecardBadges(props: ScorecardBadgesProps) {
20
21
  <ScorecardBadgesWrapper data-component-name="OpenApiDocs/ScorecardBadges">
21
22
  <ComplianceTag
22
23
  level={props.metadata.scorecardLevel as string}
23
- status={props.metadata?.scorecardStatus as string}
24
24
  slug={props.metadata?.scoreCardSlug as string}
25
+ color={getLevelColor(
26
+ props.metadata?.scorecardLevelIdx as number,
27
+ Object.keys(props.metadata?.scorecardLevels || {}).length,
28
+ )}
25
29
  />
26
30
  </ScorecardBadgesWrapper>
27
31
  )) ||
@@ -39,17 +43,41 @@ const ScorecardBadgesWrapper = styled.div`
39
43
  right: var(--panel-gap-horizontal);
40
44
  `;
41
45
 
42
- function ComplianceTag(props: { level: string; status: string; slug: string }) {
43
- const { level, status, slug } = props;
46
+ function ComplianceTag(props: { level: string; slug: string; color: string }) {
47
+ const { level, slug, color } = props;
44
48
  return (
45
49
  <Link to={slug}>
46
50
  <Tag
47
- color={statusToColor(status)}
48
51
  size="large"
49
52
  onClick={() => telemetry.send('scorecard_link_clicked', { action: 'click' })}
50
53
  >
51
- {level}
54
+ <StatusDot color={color}>{level}</StatusDot>
52
55
  </Tag>
53
56
  </Link>
54
57
  );
55
58
  }
59
+
60
+ const StatusDot = styled.div<{ color: string }>`
61
+ font-size: var(--font-size-base);
62
+ font-style: normal;
63
+ font-weight: 400;
64
+ line-height: 22px;
65
+ display: flex;
66
+ align-items: center;
67
+ &:not(:only-child) {
68
+ margin-left: 4px;
69
+ }
70
+
71
+ ${({ color }) =>
72
+ color &&
73
+ `
74
+ &::before {
75
+ content: '';
76
+ display: inline-block;
77
+ width: 10px;
78
+ height: 10px;
79
+ border-radius: 50%;
80
+ background-color: ${color};
81
+ margin-right: 8px;
82
+ }`};
83
+ `;
@@ -1,8 +1,10 @@
1
1
  import styled from 'styled-components';
2
2
 
3
- export const ScorecardCard = styled.div`
3
+ export const ScorecardCard = styled.div.attrs({
4
+ 'data-component-name': 'Scorecard/ScorecardCard',
5
+ })`
4
6
  color: var(--text-primary);
5
- background-color: var(--thin-tile-background-color);
7
+ background-color: var(--bg-raised);
6
8
  border-radius: 4px;
7
9
 
8
10
  border: 1px solid var(--border-primary);
@@ -19,3 +21,13 @@ export const ScorecardCardTitle = styled.h3`
19
21
  margin-bottom: 10px;
20
22
  margin-top: 0;
21
23
  `;
24
+
25
+ export function getLevelColor(idx: number, numberOfLevels: number) {
26
+ if (numberOfLevels === 0) {
27
+ return 'hsl(0, 0%, 50%)';
28
+ }
29
+
30
+ const hue = ((idx + 1) / numberOfLevels) * 120;
31
+ const lum = 50 - ((idx + 1) / numberOfLevels) * 25;
32
+ return `hsl(${hue}, 100%, ${lum}%)`;
33
+ }
@@ -1,18 +1,27 @@
1
1
  import * as React from 'react';
2
2
  import styled from 'styled-components';
3
-
4
- export function Gauge({
5
- chunks,
6
- }: {
3
+ export interface GaugeProps {
7
4
  chunks: {
8
5
  share: number;
9
6
  color: string;
7
+ title?: string;
10
8
  }[];
11
- }) {
9
+ className?: string;
10
+ }
11
+
12
+ export function Gauge({ chunks, className }: GaugeProps) {
13
+ const title = chunks
14
+ .map((chunk) => chunk.title)
15
+ .filter(Boolean)
16
+ .join(', ');
12
17
  return (
13
- <GaugeWrapper>
18
+ <GaugeWrapper
19
+ data-component-name="Scorecard/StatusByLevelWidget"
20
+ className={className}
21
+ title={title}
22
+ >
14
23
  {chunks.map((chunk, i) => (
15
- <GaugeChunk key={i} {...chunk} />
24
+ <GaugeChunk key={i} share={chunk.share} color={chunk.color} />
16
25
  ))}
17
26
  </GaugeWrapper>
18
27
  );
@@ -22,7 +31,6 @@ export const GaugeValue = styled.span`
22
31
  font-size: var(--font-size-lg);
23
32
  line-height: var(line-height-regular);
24
33
 
25
- margin-right: 5px;
26
34
  vertical-align: middle;
27
35
  display: inline-block;
28
36
  margin-right: 0;
@@ -7,12 +7,13 @@ import { ScorecardCard, ScorecardCardTitle } from '@theme/components/Scorecard/C
7
7
  export interface StatusByLevelWidgetProps {
8
8
  title: string;
9
9
  levels: { name: string; errors: number; warnings: number; total: number }[];
10
+ className?: string;
10
11
  }
11
12
 
12
13
  export function StatusByLevelWidget(props: StatusByLevelWidgetProps) {
13
- const { levels, title } = props;
14
+ const { levels, title, className } = props;
14
15
  return (
15
- <ScorecardCard>
16
+ <ScorecardCard data-component-name="Scorecard/StatusByLevelWidget" className={className}>
16
17
  <ScorecardCardTitle>{title}</ScorecardCardTitle>
17
18
  <CardBody>
18
19
  {levels.map((level) => {
@@ -25,14 +26,17 @@ export function StatusByLevelWidget(props: StatusByLevelWidgetProps) {
25
26
  {
26
27
  share: (success / level.total) * 100,
27
28
  color: 'var(--scorecard-color-success)',
29
+ title: `${success} passed`,
28
30
  },
29
31
  {
30
32
  share: (level.warnings / level.total) * 100,
31
33
  color: 'var(--scorecard-color-warning)',
34
+ title: `${level.warnings} ${level.warnings === 1 ? 'warning' : 'warnings'}`,
32
35
  },
33
36
  {
34
37
  share: (level.errors / level.total) * 100,
35
38
  color: 'var(--scorecard-color-error)',
39
+ title: `${level.errors} ${level.errors === 1 ? 'error' : 'errors'}`,
36
40
  },
37
41
  ]}
38
42
  />
package/src/config.ts CHANGED
@@ -297,6 +297,7 @@ const catalogFilterSchema = {
297
297
  titleTranslationKey: { type: 'string' },
298
298
  property: { type: 'string' },
299
299
  parentFilter: { type: 'string' },
300
+ valuesMapping: { type: 'object', additionalProperties: { type: 'string' } },
300
301
  missingCategoryName: { type: 'string' },
301
302
  missingCategoryNameTranslationKey: { type: 'string' },
302
303
  options: { type: 'array', items: { type: 'string' } },
@@ -324,6 +325,7 @@ const scorecardConfigSchema = {
324
325
  required: ['name'],
325
326
  properties: {
326
327
  name: { type: 'string' },
328
+ color: { type: 'string' },
327
329
  extends: { type: 'array', items: { type: 'string' } },
328
330
  rules: {
329
331
  type: 'object',
@@ -748,9 +750,3 @@ export type GoogleAnalyticsConfig = FromSchema<typeof googleAnalyticsConfigSchem
748
750
  export type CatalogConfig = FromSchema<typeof catalogSchema>;
749
751
  export type CatalogFilterConfig = FromSchema<typeof catalogFilterSchema>;
750
752
  export type ScorecardConfig = FromSchema<typeof scorecardConfigSchema>;
751
-
752
- export enum ScorecardStatus {
753
- BelowMinimum = 'Below minimum',
754
- Highest = 'Highest',
755
- Minimum = 'Minimum',
756
- }
@@ -1,6 +1,7 @@
1
1
  export const DEFAULT_THEME_NAME = '@redocly/theme';
2
2
  export const USER_THEME_ALIAS = '@theme';
3
3
  export const REDOCLY_TEAMS_RBAC = 'redocly::teams-rbac';
4
+ export const DEFAULT_LOCALE_PLACEHOLDER = 'default_locale'
4
5
  export type REQUIRED_OIDC_SCOPES = string[];
5
6
  export type DEFAULT_COOKIE_EXPIRATION = number;
6
7
  export enum FEEDBACK_TYPES {
@@ -1,4 +1,4 @@
1
- import { CatalogFilterConfig, ScorecardStatus } from '@theme';
1
+ import { CatalogFilterConfig } from '@theme';
2
2
 
3
3
  export type FilteredCatalog = {
4
4
  groups: { title: string; items: CatalogItem[] }[];
@@ -34,8 +34,9 @@ export type CatalogItem = {
34
34
  image?: string;
35
35
  docsLink?: string;
36
36
 
37
- scorecardStatus?: ScorecardStatus;
38
37
  scorecardLevel?: string;
38
+ scorecardLevelIdx?: number;
39
+ scorecardLevels?: Record<string, { uniqueErrors: number; uniqueWarnings: number }>;
39
40
  scoreCardSlug?: string;
40
41
  tags?: unknown[];
41
42