@redocly/theme 0.12.0 → 0.12.2

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.
@@ -0,0 +1,3 @@
1
+ export * from './Catalog';
2
+ export * from './CatalogCard';
3
+ export * from './useCatalog';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Catalog"), exports);
18
+ __exportStar(require("./CatalogCard"), exports);
19
+ __exportStar(require("./useCatalog"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -34,6 +34,13 @@ function useCatalog(items, config) {
34
34
  const [filtersState, setFiltersState] = React.useState(() => {
35
35
  var _a;
36
36
  return ((_a = config.filters) !== null && _a !== void 0 ? _a : []).map((f) => {
37
+ var _a, _b;
38
+ if (f.type === 'date-range') {
39
+ const [from, to] = (_b = (_a = searchParams.get(f.property)) === null || _a === void 0 ? void 0 : _a.split('--')) !== null && _b !== void 0 ? _b : [];
40
+ if (!from && !to)
41
+ return {};
42
+ return { from, to };
43
+ }
37
44
  return new Set(searchParams.getAll(f.property));
38
45
  });
39
46
  });
@@ -43,6 +50,8 @@ function useCatalog(items, config) {
43
50
  const toggleOption = React.useCallback((filterIdx, option) => {
44
51
  setFiltersState((prev) => {
45
52
  const newFilterOptions = prev[filterIdx] ? prev[filterIdx] : new Set();
53
+ if (!(newFilterOptions instanceof Set))
54
+ return prev;
46
55
  if (newFilterOptions.has(option)) {
47
56
  newFilterOptions.delete(option);
48
57
  }
@@ -55,7 +64,12 @@ function useCatalog(items, config) {
55
64
  }, []);
56
65
  const selectOption = React.useCallback((filterIdx, option) => {
57
66
  setFiltersState((prev) => {
58
- const newFilterOptions = new Set(option ? [option] : []);
67
+ const newFilterOptions = prev[filterIdx] instanceof Set
68
+ ? new Set(option ? [option] : [])
69
+ : {
70
+ from: option === null || option === void 0 ? void 0 : option.from,
71
+ to: option === null || option === void 0 ? void 0 : option.to,
72
+ };
59
73
  const filter = filtersWithOptions[filterIdx];
60
74
  return prev.map((f, idx) => idx === filterIdx
61
75
  ? newFilterOptions
@@ -72,11 +86,16 @@ function useCatalog(items, config) {
72
86
  if (!filter)
73
87
  return;
74
88
  searchParams.delete(filter.property);
75
- filterValues.forEach((value) => {
76
- searchParams.append(filter.property, value);
77
- });
89
+ if (filterValues instanceof Set) {
90
+ filterValues.forEach((value) => {
91
+ searchParams.append(filter.property, value);
92
+ });
93
+ }
94
+ else if (filterValues.from || filterValues.to) {
95
+ searchParams.append(filter.property, `${filterValues.from || ''}--${filterValues.to || ''}`);
96
+ return;
97
+ }
78
98
  });
79
- searchParams;
80
99
  if (filterTerm) {
81
100
  searchParams.set('filter', filterTerm);
82
101
  }
@@ -84,14 +103,17 @@ function useCatalog(items, config) {
84
103
  searchParams.delete('filter');
85
104
  }
86
105
  const newSearch = searchParams.toString();
106
+ if (newSearch === location.search.substring(1))
107
+ return;
87
108
  navigate({ search: newSearch });
88
- }, [config.filters, searchParams, filtersState, filterTerm, navigate]);
109
+ }, [config.filters, searchParams, filtersState, filterTerm, navigate, location]);
89
110
  // filterParents[i] is a Set with indexes of parents of this filter
90
111
  const filterParents = React.useMemo(() => collectFilterParents(config.filters), [config.filters]);
91
112
  return React.useMemo(() => {
113
+ var _a;
92
114
  const filters = filtersWithOptions.map((filter, idx) => {
93
115
  var _a, _b, _c;
94
- 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 }));
116
+ 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 || !!filtersState[idx].from }));
95
117
  });
96
118
  const filteredItems = filterItems(normalizedItems, filters, filterTerm);
97
119
  // add more information to filters state which is known only after filtering items
@@ -99,7 +121,8 @@ function useCatalog(items, config) {
99
121
  var _a, _b;
100
122
  const parentFilterIdx = filters.findIndex((f) => f.property === filter.parentFilter);
101
123
  const parentUsed = filter.parentFilter
102
- ? ((_b = (_a = filtersState[parentFilterIdx]) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0
124
+ ? ((_b = (_a = filtersState[parentFilterIdx]) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0 ||
125
+ !!filtersState[parentFilterIdx].from
103
126
  : true;
104
127
  // we filter items with all the other independent filters current state
105
128
  // then we can calculate the options for this filter after other filters are applied
@@ -110,7 +133,7 @@ function useCatalog(items, config) {
110
133
  const adjustedFilterOptions = collectFilterOptions(filteredWithOtherFilters.map((m) => ({ metadata: m })), config.filters);
111
134
  return Object.assign(Object.assign({}, filter), { parentUsed, filteredOptions: adjustedFilterOptions[idx].options });
112
135
  });
113
- const groups = config.groupByFirstFilter && filters[0].selectedOptions.size > 0
136
+ const groups = config.groupByFirstFilter && ((_a = filters[0].selectedOptions) === null || _a === void 0 ? void 0 : _a.size) > 0
114
137
  ? groupByFirstFilter(resolvedFilters, filteredItems)
115
138
  : [{ title: 'APIs', items: filteredItems }];
116
139
  return { groups, filters: resolvedFilters, setFilterTerm, filterTerm };
@@ -233,6 +256,17 @@ function filterItems(normalizedItems, filters, term) {
233
256
  // filter by filters first, and then by search term
234
257
  const filteredByFilters = normalizedItems.filter((item) => {
235
258
  return filters.every((filter) => {
259
+ var _a, _b;
260
+ if (filter.selectedOptions && !(filter.selectedOptions instanceof Set)) {
261
+ try {
262
+ const date = new Date(item[filter.property]).toISOString().split('T')[0];
263
+ return (date >= ((_a = filter.selectedOptions.from) !== null && _a !== void 0 ? _a : '') &&
264
+ date <= ((_b = filter.selectedOptions.to) !== null && _b !== void 0 ? _b : 'Z'));
265
+ }
266
+ catch (e) {
267
+ return true;
268
+ }
269
+ }
236
270
  if (filter.selectedOptions.size === 0) {
237
271
  return true;
238
272
  }
@@ -1,5 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import type { ResolvedFilter } from '../../types/portal/src/shared/types/catalog';
3
3
  export declare function Filter({ filter }: {
4
- filter: ResolvedFilter;
4
+ filter: ResolvedFilter & {
5
+ selectedOptions: any;
6
+ };
5
7
  }): JSX.Element | null;
@@ -6,19 +6,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Filter = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
+ const react_date_picker_1 = require("react-date-picker");
9
10
  const Checkbox_1 = require("../../ui/Checkbox");
10
11
  const hooks_1 = require("../../mocks/hooks");
11
12
  function Filter({ filter }) {
12
- var _a;
13
+ var _a, _b;
13
14
  const { translate } = (0, hooks_1.useTranslate)();
14
15
  const translationKeys = {
15
16
  selectAll: 'theme.catalog.filters.select.all',
17
+ clear: 'theme.catalog.filters.clear',
16
18
  };
17
19
  if (!filter.parentUsed)
18
20
  return null;
19
21
  return (react_1.default.createElement(FilterGroup, { key: filter.property + filter.title },
20
- react_1.default.createElement(FilterTitle, null, translate(filter.titleTranslationKey, filter.title)),
21
- filter.type === 'select' ? (react_1.default.createElement(StyledSelect, { onChange: (e) => filter.selectOption(e.target.value), value: ((_a = filter.selectedOptions.values().next()) === null || _a === void 0 ? void 0 : _a.value) || '' },
22
+ react_1.default.createElement(FilterTitle, null,
23
+ translate(filter.titleTranslationKey, filter.title),
24
+ ' ',
25
+ ((_a = filter.selectedOptions) === null || _a === void 0 ? void 0 : _a.size) ? (react_1.default.createElement("a", { "data-translation-key": translationKeys.clear, onClick: () => filter.selectOption('') }, translate(translationKeys.clear, 'Clear'))) : null),
26
+ filter.type === 'select' ? (react_1.default.createElement(StyledSelect, { onChange: (e) => filter.selectOption(e.target.value), value: ((_b = filter.selectedOptions.values().next()) === null || _b === void 0 ? void 0 : _b.value) || '' },
22
27
  react_1.default.createElement("option", { key: "none", value: "", "data-translation-key": translationKeys.selectAll }, translate(translationKeys.selectAll, 'All')),
23
28
  filter.filteredOptions.map((value) => {
24
29
  return (react_1.default.createElement("option", { key: value.value, value: value.value },
@@ -26,7 +31,21 @@ function Filter({ filter }) {
26
31
  " (",
27
32
  value.count,
28
33
  ")"));
29
- }))) : (filter.filteredOptions.map((value) => {
34
+ }))) : filter.type === 'date-range' ? (react_1.default.createElement(react_1.default.Fragment, null,
35
+ react_1.default.createElement(DatePickerWrapper, null,
36
+ react_1.default.createElement("span", null, "From:"),
37
+ react_1.default.createElement(react_date_picker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : null, minDetail: "decade", maxDate: new Date(), onChange: (from) => {
38
+ if (Array.isArray(from))
39
+ return;
40
+ filter.selectOption(Object.assign(Object.assign({}, filter.selectedOptions), { from: formatDateWithNoTimeZone(from) }));
41
+ } })),
42
+ react_1.default.createElement(DatePickerWrapper, null,
43
+ react_1.default.createElement("span", null, "To:"),
44
+ react_1.default.createElement(react_date_picker_1.DatePicker, { closeCalendar: true, dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", format: "y-MM-dd", minDate: filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : undefined, value: filter.selectedOptions.to ? new Date(filter.selectedOptions.to) : null, minDetail: "decade", onChange: (to) => {
45
+ if (Array.isArray(to))
46
+ return;
47
+ filter.selectOption(Object.assign(Object.assign({}, filter.selectedOptions), { to: formatDateWithNoTimeZone(to) }));
48
+ } })))) : (filter.filteredOptions.map((value) => {
30
49
  const id = 'filter--' + filter.property + '--' + slug(value.value);
31
50
  return (react_1.default.createElement(FilterValue, { key: id },
32
51
  react_1.default.createElement(Checkbox_1.Checkbox, { type: "checkbox", id: id, checked: filter.selectedOptions.has(value.value), onChange: () => filter.toggleOption(value.value) }),
@@ -51,6 +70,11 @@ const FilterTitle = styled_components_1.default.h4 `
51
70
  font-weight: var(--font-weight-bold);
52
71
  margin: 0;
53
72
  margin-bottom: 16px;
73
+
74
+ > a {
75
+ font-size: 14px;
76
+ cursor: pointer;
77
+ }
54
78
  `;
55
79
  const FilterValue = styled_components_1.default.label `
56
80
  display: block;
@@ -85,9 +109,66 @@ const StyledSelect = styled_components_1.default.select `
85
109
  background-position: right 10px center;
86
110
  background-size: 1em;
87
111
  width: 100%;
112
+ padding-right: 25px;
88
113
  `;
89
114
  // TODO: import from portal
90
115
  function slug(str) {
91
116
  return str.replace(/\s/g, '-').toLowerCase();
92
117
  }
118
+ const DatePickerWrapper = styled_components_1.default.div `
119
+ display: flex;
120
+ flex-direction: row;
121
+ margin-bottom: 5px;
122
+
123
+ align-items: center;
124
+ gap: 10px;
125
+
126
+ > span {
127
+ width: 50px;
128
+ }
129
+
130
+ .react-date-picker {
131
+ flex: 1;
132
+ }
133
+
134
+ .react-calendar__tile--now {
135
+ background: #cbf7f1;
136
+ color: black;
137
+
138
+ &:enabled:hover,
139
+ &:enabled:focus {
140
+ background: #b1efe7;
141
+ color: black;
142
+ }
143
+ }
144
+
145
+ .react-date-picker__inputGroup__input:invalid {
146
+ background: rgb(255 125 0 / 10%);
147
+ }
148
+
149
+ .react-date-picker__button {
150
+ padding: 4px 4px;
151
+ svg {
152
+ width: 12px;
153
+ }
154
+ }
155
+
156
+ .react-date-picker__wrapper {
157
+ border: 1px solid rgba(0, 0, 0, 0.23);
158
+ border-radius: var(--border-radius);
159
+ padding: var(--input-padding);
160
+ }
161
+
162
+ .react-date-picker__inputGroup__input {
163
+ width: 20px;
164
+ }
165
+ `;
166
+ function padZero(num) {
167
+ return num < 10 ? '0' + num : num;
168
+ }
169
+ function formatDateWithNoTimeZone(date) {
170
+ if (!date)
171
+ return date;
172
+ return `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())}`;
173
+ }
93
174
  //# sourceMappingURL=Filter.js.map
@@ -103,7 +103,7 @@ function DevOnboardingTryItSecurity(props) {
103
103
  if (res.ok) {
104
104
  const cred = data.credentials.find((cred) => cred.status === 'approved');
105
105
  setAcceptValueUpdate(true);
106
- setSelectedAppSecret(cred.clientSecret);
106
+ setSelectedAppSecret(cred[cred.valueToUseInHeader] || cred.clientSecret);
107
107
  }
108
108
  else {
109
109
  setError(data.message);
@@ -26,3 +26,4 @@ export * from './Menu';
26
26
  export * from './Separator';
27
27
  export * from './Cards';
28
28
  export * from './Tiles';
29
+ export * from './Catalog';
@@ -42,4 +42,5 @@ __exportStar(require("./Menu"), exports);
42
42
  __exportStar(require("./Separator"), exports);
43
43
  __exportStar(require("./Cards"), exports);
44
44
  __exportStar(require("./Tiles"), exports);
45
+ __exportStar(require("./Catalog"), exports);
45
46
  //# sourceMappingURL=index.js.map
@@ -9,7 +9,7 @@ export type FilteredCatalog = {
9
9
  setFilterTerm: (term: string) => void;
10
10
  };
11
11
  export type Filter = {
12
- type?: 'select' | 'checkboxes';
12
+ type?: 'select' | 'checkboxes' | 'date-range';
13
13
  title: string;
14
14
  titleTranslationKey?: string;
15
15
  property: string;
@@ -42,12 +42,16 @@ export type ResolvedFilter = Omit<Filter, 'options'> & {
42
42
  toggleOption: (option: string) => void;
43
43
  selectOption: (option: string) => void;
44
44
  parentUsed: boolean;
45
- selectedOptions: Set<string>;
45
+ selectedOptions: Set<string> | {
46
+ from?: string;
47
+ to?: string;
48
+ };
46
49
  };
47
50
  export type CatalogItem = {
48
51
  title: string;
49
52
  link: string;
50
53
  description?: string;
51
54
  image?: string;
55
+ docsLink?: string;
52
56
  [k: string]: unknown;
53
57
  };
package/lib/ui/index.d.ts CHANGED
@@ -5,5 +5,6 @@ export * from '../ui/Dropdown';
5
5
  export * from '../ui/Flex';
6
6
  export * from '../ui/Jumbotron';
7
7
  export * from '../ui/ArrowBack';
8
+ export * from '../ui/Highlight';
8
9
  export declare const LandingLayout: ({ children }: React.PropsWithChildren<object>) => React.ReactNode;
9
10
  export declare const EmptyLayout: ({ children }: React.PropsWithChildren<object>) => React.ReactNode;
package/lib/ui/index.js CHANGED
@@ -21,6 +21,7 @@ __exportStar(require("../ui/Dropdown"), exports);
21
21
  __exportStar(require("../ui/Flex"), exports);
22
22
  __exportStar(require("../ui/Jumbotron"), exports);
23
23
  __exportStar(require("../ui/ArrowBack"), exports);
24
+ __exportStar(require("../ui/Highlight"), exports);
24
25
  const LandingLayout = ({ children }) => children;
25
26
  exports.LandingLayout = LandingLayout;
26
27
  exports.EmptyLayout = exports.LandingLayout;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.12.0",
4
- "description": "Shared UI components lib",
3
+ "version": "0.12.2",
4
+ "description": "Shared UI components library",
5
5
  "keywords": [],
6
6
  "author": "team@redocly.com",
7
7
  "license": "SEE LICENSE IN LICENSE",
@@ -82,6 +82,8 @@
82
82
  "@redocly/ajv": "^8.11.0",
83
83
  "copy-to-clipboard": "^3.3.3",
84
84
  "highlight-words-core": "^1.2.2",
85
+ "react-date-picker": "10.0.3",
86
+ "react-calendar": "4.2.1",
85
87
  "hotkeys-js": "^3.10.1",
86
88
  "timeago.js": "^4.0.2"
87
89
  },
@@ -0,0 +1,3 @@
1
+ export * from './Catalog';
2
+ export * from './CatalogCard';
3
+ export * from './useCatalog';
@@ -6,10 +6,10 @@ import type { Location } from 'react-router-dom';
6
6
  import type { ResolvedNavItem } from '@theme/types/portal';
7
7
  import type {
8
8
  CatalogConfig,
9
- FilteredCatalog,
10
- ResolvedFilter,
11
9
  CatalogItem,
12
10
  Filter,
11
+ FilteredCatalog,
12
+ ResolvedFilter,
13
13
  } from '@theme/types/portal/src/shared/types/catalog';
14
14
  import { findDeepFirst, toStringIfDefined, withoutHash } from '@theme/utils';
15
15
 
@@ -19,6 +19,11 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
19
19
  const searchParams = useSearchParams(location);
20
20
  const [filtersState, setFiltersState] = React.useState(() =>
21
21
  (config.filters ?? []).map((f) => {
22
+ if (f.type === 'date-range') {
23
+ const [from, to] = searchParams.get(f.property)?.split('--') ?? [];
24
+ if (!from && !to) return {};
25
+ return { from, to };
26
+ }
22
27
  return new Set(searchParams.getAll(f.property));
23
28
  }),
24
29
  );
@@ -33,6 +38,7 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
33
38
  const toggleOption = React.useCallback((filterIdx, option) => {
34
39
  setFiltersState((prev) => {
35
40
  const newFilterOptions = prev[filterIdx] ? prev[filterIdx] : new Set<string>();
41
+ if (!(newFilterOptions instanceof Set)) return prev;
36
42
  if (newFilterOptions.has(option)) {
37
43
  newFilterOptions.delete(option);
38
44
  } else {
@@ -46,7 +52,13 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
46
52
  const selectOption = React.useCallback(
47
53
  (filterIdx, option) => {
48
54
  setFiltersState((prev) => {
49
- const newFilterOptions = new Set<string>(option ? [option] : []);
55
+ const newFilterOptions =
56
+ prev[filterIdx] instanceof Set
57
+ ? new Set<string>(option ? [option] : [])
58
+ : {
59
+ from: option?.from,
60
+ to: option?.to,
61
+ };
50
62
  const filter = filtersWithOptions[filterIdx];
51
63
 
52
64
  return prev.map((f, idx) =>
@@ -67,19 +79,28 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
67
79
  const filter = config.filters?.[filterIdx];
68
80
  if (!filter) return;
69
81
  searchParams.delete(filter.property);
70
- filterValues.forEach((value) => {
71
- searchParams.append(filter.property, value);
72
- });
82
+ if (filterValues instanceof Set) {
83
+ filterValues.forEach((value) => {
84
+ searchParams.append(filter.property, value);
85
+ });
86
+ } else if (filterValues.from || filterValues.to) {
87
+ searchParams.append(
88
+ filter.property,
89
+ `${filterValues.from || ''}--${filterValues.to || ''}`,
90
+ );
91
+ return;
92
+ }
73
93
  });
74
- searchParams;
94
+
75
95
  if (filterTerm) {
76
96
  searchParams.set('filter', filterTerm);
77
97
  } else {
78
98
  searchParams.delete('filter');
79
99
  }
80
100
  const newSearch = searchParams.toString();
101
+ if (newSearch === location.search.substring(1)) return;
81
102
  navigate({ search: newSearch });
82
- }, [config.filters, searchParams, filtersState, filterTerm, navigate]);
103
+ }, [config.filters, searchParams, filtersState, filterTerm, navigate, location]);
83
104
 
84
105
  // filterParents[i] is a Set with indexes of parents of this filter
85
106
  const filterParents = React.useMemo(() => collectFilterParents(config.filters), [config.filters]);
@@ -90,7 +111,8 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
90
111
  toggleOption: (value: string) => toggleOption(idx, value),
91
112
  selectOption: (value: string) => selectOption(idx, value),
92
113
  selectedOptions: filtersState[idx] ?? new Set<string>(),
93
- isFilterUsed: (filtersState[idx]?.size ?? 0) > 0, // TODO: rename to isUsed
114
+ isFilterUsed:
115
+ ((filtersState[idx] as any)?.size ?? 0) > 0 || !!(filtersState[idx] as any).from,
94
116
  }));
95
117
 
96
118
  const filteredItems = filterItems(normalizedItems, filters, filterTerm);
@@ -99,7 +121,8 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
99
121
  const resolvedFilters = filters.map((filter, idx) => {
100
122
  const parentFilterIdx = filters.findIndex((f) => f.property === filter.parentFilter);
101
123
  const parentUsed = filter.parentFilter
102
- ? (filtersState[parentFilterIdx]?.size ?? 0) > 0
124
+ ? ((filtersState[parentFilterIdx] as any)?.size ?? 0) > 0 ||
125
+ !!(filtersState[parentFilterIdx] as any).from
103
126
  : true;
104
127
 
105
128
  // we filter items with all the other independent filters current state
@@ -127,7 +150,7 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
127
150
  });
128
151
 
129
152
  const groups =
130
- config.groupByFirstFilter && filters[0].selectedOptions.size > 0
153
+ config.groupByFirstFilter && (filters[0].selectedOptions as any)?.size > 0
131
154
  ? groupByFirstFilter(resolvedFilters, filteredItems)
132
155
  : [{ title: 'APIs', items: filteredItems }];
133
156
 
@@ -272,12 +295,25 @@ function filterItems(
272
295
  // filter by filters first, and then by search term
273
296
  const filteredByFilters = normalizedItems.filter((item) => {
274
297
  return filters.every((filter) => {
298
+ if (filter.selectedOptions && !(filter.selectedOptions instanceof Set)) {
299
+ try {
300
+ const date = new Date(item[filter.property] as string).toISOString().split('T')[0];
301
+ return (
302
+ date >= (filter.selectedOptions.from ?? '') &&
303
+ date <= (filter.selectedOptions.to ?? 'Z')
304
+ );
305
+ } catch (e) {
306
+ return true;
307
+ }
308
+ }
309
+
275
310
  if (filter.selectedOptions.size === 0) {
276
311
  return true;
277
312
  }
313
+
278
314
  const itemValue = item?.[filter.property] || filter.missingCategoryName || 'Others';
279
315
  if (Array.isArray(itemValue)) {
280
- return itemValue.some((value) => filter.selectedOptions.has(value));
316
+ return itemValue.some((value) => (filter.selectedOptions as Set<any>).has(value));
281
317
  }
282
318
  return filter.selectedOptions.has(itemValue as string);
283
319
  });
@@ -1,24 +1,33 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
+ import { DatePicker } from 'react-date-picker';
3
4
 
4
5
  import type { ResolvedFilter } from '@theme/types/portal/src/shared/types/catalog';
5
6
  import { Checkbox } from '@theme/ui/Checkbox';
6
7
  import { useTranslate } from '@portal/hooks';
7
8
 
8
- export function Filter({ filter }: { filter: ResolvedFilter }) {
9
+ export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions: any } }) {
9
10
  const { translate } = useTranslate();
10
11
  const translationKeys = {
11
12
  selectAll: 'theme.catalog.filters.select.all',
13
+ clear: 'theme.catalog.filters.clear',
12
14
  };
13
15
 
14
16
  if (!filter.parentUsed) return null;
15
17
  return (
16
18
  <FilterGroup key={filter.property + filter.title}>
17
- <FilterTitle>{translate(filter.titleTranslationKey, filter.title)}</FilterTitle>
19
+ <FilterTitle>
20
+ {translate(filter.titleTranslationKey, filter.title)}{' '}
21
+ {filter.selectedOptions?.size ? (
22
+ <a data-translation-key={translationKeys.clear} onClick={() => filter.selectOption('')}>
23
+ {translate(translationKeys.clear, 'Clear')}
24
+ </a>
25
+ ) : null}
26
+ </FilterTitle>
18
27
  {filter.type === 'select' ? (
19
28
  <StyledSelect
20
29
  onChange={(e) => filter.selectOption(e.target.value)}
21
- value={filter.selectedOptions.values().next()?.value || ''}
30
+ value={(filter.selectedOptions as Set<any>).values().next()?.value || ''}
22
31
  >
23
32
  <option key="none" value="" data-translation-key={translationKeys.selectAll}>
24
33
  {translate(translationKeys.selectAll, 'All')}
@@ -31,6 +40,51 @@ export function Filter({ filter }: { filter: ResolvedFilter }) {
31
40
  );
32
41
  })}
33
42
  </StyledSelect>
43
+ ) : filter.type === 'date-range' ? (
44
+ <>
45
+ <DatePickerWrapper>
46
+ <span>From:</span>
47
+ <DatePicker
48
+ closeCalendar={true}
49
+ format="y-MM-dd"
50
+ dayPlaceholder="DD"
51
+ monthPlaceholder="MM"
52
+ yearPlaceholder="YYYY"
53
+ value={filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : null}
54
+ minDetail="decade"
55
+ maxDate={new Date()}
56
+ onChange={(from) => {
57
+ if (Array.isArray(from)) return;
58
+ filter.selectOption({
59
+ ...(filter.selectedOptions as any),
60
+ from: formatDateWithNoTimeZone(from),
61
+ });
62
+ }}
63
+ />
64
+ </DatePickerWrapper>
65
+ <DatePickerWrapper>
66
+ <span>To:</span>
67
+ <DatePicker
68
+ closeCalendar={true}
69
+ dayPlaceholder="DD"
70
+ monthPlaceholder="MM"
71
+ yearPlaceholder="YYYY"
72
+ format="y-MM-dd"
73
+ minDate={
74
+ filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : undefined
75
+ }
76
+ value={filter.selectedOptions.to ? new Date(filter.selectedOptions.to) : null}
77
+ minDetail="decade"
78
+ onChange={(to) => {
79
+ if (Array.isArray(to)) return;
80
+ filter.selectOption({
81
+ ...filter.selectedOptions,
82
+ to: formatDateWithNoTimeZone(to),
83
+ });
84
+ }}
85
+ />
86
+ </DatePickerWrapper>
87
+ </>
34
88
  ) : (
35
89
  filter.filteredOptions.map((value: any) => {
36
90
  const id = 'filter--' + filter.property + '--' + slug(value.value);
@@ -67,6 +121,11 @@ const FilterTitle = styled.h4`
67
121
  font-weight: var(--font-weight-bold);
68
122
  margin: 0;
69
123
  margin-bottom: 16px;
124
+
125
+ > a {
126
+ font-size: 14px;
127
+ cursor: pointer;
128
+ }
70
129
  `;
71
130
 
72
131
  const FilterValue = styled.label`
@@ -103,9 +162,68 @@ const StyledSelect = styled.select`
103
162
  background-position: right 10px center;
104
163
  background-size: 1em;
105
164
  width: 100%;
165
+ padding-right: 25px;
106
166
  `;
107
167
 
108
168
  // TODO: import from portal
109
169
  function slug(str: string): string {
110
170
  return str.replace(/\s/g, '-').toLowerCase();
111
171
  }
172
+
173
+ const DatePickerWrapper = styled.div`
174
+ display: flex;
175
+ flex-direction: row;
176
+ margin-bottom: 5px;
177
+
178
+ align-items: center;
179
+ gap: 10px;
180
+
181
+ > span {
182
+ width: 50px;
183
+ }
184
+
185
+ .react-date-picker {
186
+ flex: 1;
187
+ }
188
+
189
+ .react-calendar__tile--now {
190
+ background: #cbf7f1;
191
+ color: black;
192
+
193
+ &:enabled:hover,
194
+ &:enabled:focus {
195
+ background: #b1efe7;
196
+ color: black;
197
+ }
198
+ }
199
+
200
+ .react-date-picker__inputGroup__input:invalid {
201
+ background: rgb(255 125 0 / 10%);
202
+ }
203
+
204
+ .react-date-picker__button {
205
+ padding: 4px 4px;
206
+ svg {
207
+ width: 12px;
208
+ }
209
+ }
210
+
211
+ .react-date-picker__wrapper {
212
+ border: 1px solid rgba(0, 0, 0, 0.23);
213
+ border-radius: var(--border-radius);
214
+ padding: var(--input-padding);
215
+ }
216
+
217
+ .react-date-picker__inputGroup__input {
218
+ width: 20px;
219
+ }
220
+ `;
221
+
222
+ function padZero(num: number) {
223
+ return num < 10 ? '0' + num : num;
224
+ }
225
+
226
+ function formatDateWithNoTimeZone(date?: Date | null) {
227
+ if (!date) return date;
228
+ return `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())}`;
229
+ }
@@ -85,7 +85,7 @@ export function DevOnboardingTryItSecurity(props: TryItSecurityAppsProps) {
85
85
  if (res.ok) {
86
86
  const cred = data.credentials.find((cred: any) => cred.status === 'approved');
87
87
  setAcceptValueUpdate(true);
88
- setSelectedAppSecret(cred.clientSecret);
88
+ setSelectedAppSecret(cred[cred.valueToUseInHeader] || cred.clientSecret);
89
89
  } else {
90
90
  setError(data.message);
91
91
  }
@@ -26,3 +26,4 @@ export * from './Menu';
26
26
  export * from './Separator';
27
27
  export * from './Cards';
28
28
  export * from './Tiles';
29
+ export * from './Catalog';
@@ -8,7 +8,7 @@ export type FilteredCatalog = {
8
8
  };
9
9
 
10
10
  export type Filter = {
11
- type?: 'select' | 'checkboxes';
11
+ type?: 'select' | 'checkboxes' | 'date-range';
12
12
  title: string;
13
13
  titleTranslationKey?: string;
14
14
  property: string;
@@ -43,7 +43,12 @@ export type ResolvedFilter = Omit<Filter, 'options'> & {
43
43
  toggleOption: (option: string) => void;
44
44
  selectOption: (option: string) => void;
45
45
  parentUsed: boolean;
46
- selectedOptions: Set<string>;
46
+ selectedOptions:
47
+ | Set<string>
48
+ | {
49
+ from?: string;
50
+ to?: string;
51
+ };
47
52
  };
48
53
 
49
54
  export type CatalogItem = {
@@ -51,5 +56,6 @@ export type CatalogItem = {
51
56
  link: string;
52
57
  description?: string;
53
58
  image?: string;
59
+ docsLink?: string;
54
60
  [k: string]: unknown;
55
61
  };
package/src/ui/index.tsx CHANGED
@@ -6,6 +6,7 @@ export * from '@theme/ui/Dropdown';
6
6
  export * from '@theme/ui/Flex';
7
7
  export * from '@theme/ui/Jumbotron';
8
8
  export * from '@theme/ui/ArrowBack';
9
+ export * from '@theme/ui/Highlight';
9
10
 
10
11
  export const LandingLayout = ({ children }: React.PropsWithChildren<object>): React.ReactNode =>
11
12
  children;