@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.
- package/lib/components/Catalog/index.d.ts +3 -0
- package/lib/components/Catalog/index.js +20 -0
- package/lib/components/Catalog/useCatalog.js +43 -9
- package/lib/components/Filter/Filter.d.ts +3 -1
- package/lib/components/Filter/Filter.js +85 -4
- package/lib/components/OpenAPIDocs/DevOnboardingTryItSecurity.js +1 -1
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/types/portal/src/shared/types/catalog.d.ts +6 -2
- package/lib/ui/index.d.ts +1 -0
- package/lib/ui/index.js +1 -0
- package/package.json +4 -2
- package/src/components/Catalog/index.tsx +3 -0
- package/src/components/Catalog/useCatalog.ts +48 -12
- package/src/components/Filter/Filter.tsx +121 -3
- package/src/components/OpenAPIDocs/DevOnboardingTryItSecurity.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/types/portal/src/shared/types/catalog.ts +8 -2
- package/src/ui/index.tsx +1 -0
|
@@ -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 =
|
|
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
|
|
76
|
-
|
|
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,
|
|
21
|
-
|
|
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
|
-
}))) :
|
|
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);
|
package/lib/components/index.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "Shared UI components
|
|
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
|
},
|
|
@@ -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 =
|
|
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
|
|
71
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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>
|
|
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
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -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:
|
|
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;
|