@redocly/theme 0.47.1 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Feedback/Mood.d.ts +2 -2
- package/lib/components/Feedback/Mood.js +20 -15
- package/lib/components/Feedback/Rating.d.ts +2 -2
- package/lib/components/Feedback/Rating.js +6 -6
- package/lib/components/Feedback/Scale.d.ts +2 -2
- package/lib/components/Feedback/Scale.js +6 -6
- package/lib/components/Feedback/Sentiment.d.ts +2 -2
- package/lib/components/Feedback/Sentiment.js +6 -6
- package/lib/components/Search/SearchDialog.js +17 -11
- package/lib/components/Search/SearchFilter.d.ts +3 -2
- package/lib/components/Search/SearchFilter.js +2 -2
- package/lib/components/Search/SearchFilterField.d.ts +3 -2
- package/lib/components/Search/SearchFilterField.js +2 -2
- package/lib/components/Search/SearchGroups.d.ts +5 -4
- package/lib/components/Search/SearchGroups.js +3 -3
- package/lib/components/Search/variables.js +4 -0
- package/lib/components/Segmented/Segmented.d.ts +4 -4
- package/lib/components/Segmented/Segmented.js +4 -7
- package/lib/components/Tag/Tag.d.ts +1 -0
- package/lib/components/Tag/Tag.js +3 -2
- package/lib/core/hooks/__mocks__/search/use-search-filter.d.ts +1 -1
- package/lib/core/hooks/__mocks__/search/use-search-filter.js +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +10 -5
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.d.ts +1 -2
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +20 -15
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough.d.ts +2 -7
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough.js +10 -3
- package/lib/core/hooks/code-walkthrough/use-renderable-files.d.ts +9 -0
- package/lib/core/hooks/code-walkthrough/use-renderable-files.js +28 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/search/use-search-filter.d.ts +2 -2
- package/lib/core/hooks/search/use-search-filter.js +5 -5
- package/lib/core/types/hooks.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/search.d.ts +1 -2
- package/lib/core/utils/download-code-walkthrough.js +9 -1
- package/lib/core/utils/find-closest-common-directory.d.ts +6 -0
- package/lib/core/utils/find-closest-common-directory.js +51 -0
- package/lib/core/utils/get-file-icon.js +6 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/replace-inputs-with-value.d.ts +1 -1
- package/lib/core/utils/replace-inputs-with-value.js +9 -10
- package/lib/icons/DocumentJavaIcon/DocumentJavaIcon.d.ts +9 -0
- package/lib/icons/DocumentJavaIcon/DocumentJavaIcon.js +22 -0
- package/lib/icons/DocumentJavaIcon/index.d.ts +1 -0
- package/lib/icons/DocumentJavaIcon/index.js +6 -0
- package/lib/icons/DocumentPythonIcon/DocumentPythonIcon.d.ts +9 -0
- package/lib/icons/DocumentPythonIcon/DocumentPythonIcon.js +23 -0
- package/lib/icons/DocumentPythonIcon/index.d.ts +1 -0
- package/lib/icons/DocumentPythonIcon/index.js +6 -0
- package/lib/icons/DocumentShellIcon/DocumentShellIcon.d.ts +9 -0
- package/lib/icons/DocumentShellIcon/DocumentShellIcon.js +22 -0
- package/lib/icons/DocumentShellIcon/index.d.ts +1 -0
- package/lib/icons/DocumentShellIcon/index.js +6 -0
- package/lib/icons/__tests__/IconTestUtils.d.ts +7 -0
- package/lib/icons/__tests__/IconTestUtils.js +33 -0
- package/lib/layouts/CodeWalkthroughLayout.js +4 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +15 -2
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.js +29 -23
- package/lib/markdoc/components/CodeWalkthrough/CodePanelPreview.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +6 -3
- package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/Input.js +4 -2
- package/lib/markdoc/tags/code-walkthrough.js +5 -0
- package/package.json +3 -3
- package/src/components/Feedback/Mood.tsx +25 -17
- package/src/components/Feedback/Rating.tsx +9 -10
- package/src/components/Feedback/Scale.tsx +9 -10
- package/src/components/Feedback/Sentiment.tsx +9 -10
- package/src/components/Search/SearchDialog.tsx +63 -42
- package/src/components/Search/SearchFilter.tsx +6 -3
- package/src/components/Search/SearchFilterField.tsx +4 -2
- package/src/components/Search/SearchGroups.tsx +13 -8
- package/src/components/Search/variables.ts +4 -0
- package/src/components/Segmented/Segmented.tsx +10 -10
- package/src/components/Tag/Tag.tsx +1 -1
- package/src/core/hooks/__mocks__/search/use-search-filter.ts +1 -1
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +9 -3
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +30 -18
- package/src/core/hooks/code-walkthrough/use-code-walkthrough.ts +13 -13
- package/src/core/hooks/code-walkthrough/use-renderable-files.ts +51 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/search/use-search-filter.ts +9 -5
- package/src/core/types/hooks.ts +1 -0
- package/src/core/types/l10n.ts +5 -3
- package/src/core/types/search.ts +1 -2
- package/src/core/utils/download-code-walkthrough.ts +14 -2
- package/src/core/utils/find-closest-common-directory.ts +51 -0
- package/src/core/utils/get-file-icon.ts +7 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/replace-inputs-with-value.ts +12 -9
- package/src/icons/DocumentJavaIcon/DocumentJavaIcon.tsx +33 -0
- package/src/icons/DocumentJavaIcon/index.ts +1 -0
- package/src/icons/DocumentPythonIcon/DocumentPythonIcon.tsx +37 -0
- package/src/icons/DocumentPythonIcon/index.ts +1 -0
- package/src/icons/DocumentShellIcon/DocumentShellIcon.tsx +33 -0
- package/src/icons/DocumentShellIcon/index.ts +1 -0
- package/src/icons/__tests__/IconTestUtils.tsx +31 -0
- package/src/layouts/CodeWalkthroughLayout.tsx +5 -1
- package/src/markdoc/components/CodeWalkthrough/CodeContainer.tsx +1 -0
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +19 -3
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelHeader.tsx +64 -47
- package/src/markdoc/components/CodeWalkthrough/CodePanelPreview.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelToolbar.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +6 -2
- package/src/markdoc/components/CodeWalkthrough/CodeToggle.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +4 -1
- package/src/markdoc/components/CodeWalkthrough/Input.tsx +4 -2
- package/src/markdoc/tags/code-walkthrough.ts +5 -0
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { OptionalEmailSettings, ReasonsSettingsSchema } from '@redocly/config';
|
|
6
6
|
import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
|
|
7
7
|
|
|
8
8
|
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
@@ -32,7 +32,7 @@ export type SentimentProps = {
|
|
|
32
32
|
like?: ReasonsSettingsSchema;
|
|
33
33
|
dislike?: ReasonsSettingsSchema;
|
|
34
34
|
};
|
|
35
|
-
|
|
35
|
+
optionalEmail?: OptionalEmailSettings;
|
|
36
36
|
};
|
|
37
37
|
className?: string;
|
|
38
38
|
};
|
|
@@ -43,7 +43,7 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
43
43
|
submitText,
|
|
44
44
|
comment: commentSettings,
|
|
45
45
|
reasons: reasonsSettings,
|
|
46
|
-
|
|
46
|
+
optionalEmail: optionalEmailSettings,
|
|
47
47
|
} = settings || {};
|
|
48
48
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
49
49
|
const [score, setScore] = React.useState(0);
|
|
@@ -87,8 +87,7 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
87
87
|
const displayReasons = checkIfShouldDisplayReasons(score);
|
|
88
88
|
const displayComment = !!(score && !commentSettings?.hide);
|
|
89
89
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
90
|
-
const displayFeedbackEmail =
|
|
91
|
-
!!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
|
|
90
|
+
const displayFeedbackEmail = !!score && !optionalEmailSettings?.hide && !userData.isAuthenticated;
|
|
92
91
|
|
|
93
92
|
const commentLabel =
|
|
94
93
|
score === 1
|
|
@@ -214,18 +213,18 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
214
213
|
|
|
215
214
|
{displayFeedbackEmail && (
|
|
216
215
|
<StyledFormOptionalFields>
|
|
217
|
-
<Label data-translation-key="feedback.settings.
|
|
218
|
-
{
|
|
216
|
+
<Label data-translation-key="feedback.settings.optionalEmail.label">
|
|
217
|
+
{optionalEmailSettings?.label ||
|
|
219
218
|
translate(
|
|
220
|
-
'feedback.settings.
|
|
219
|
+
'feedback.settings.optionalEmail.label',
|
|
221
220
|
'Your email (optional, for follow-up)',
|
|
222
221
|
)}
|
|
223
222
|
</Label>
|
|
224
223
|
<EmailInput
|
|
225
224
|
onChange={onEmailChange}
|
|
226
225
|
placeholder={
|
|
227
|
-
|
|
228
|
-
translate('feedback.settings.
|
|
226
|
+
optionalEmailSettings?.placeholder ||
|
|
227
|
+
translate('feedback.settings.optionalEmail.placeholder', 'yourname@example.com')
|
|
229
228
|
}
|
|
230
229
|
type="email"
|
|
231
230
|
required={!!email}
|
|
@@ -44,6 +44,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
44
44
|
setLoadMore,
|
|
45
45
|
advancedSearch,
|
|
46
46
|
askAi,
|
|
47
|
+
groupField,
|
|
47
48
|
} = useSearch(product?.name, autoSearchDisabled);
|
|
48
49
|
const {
|
|
49
50
|
isFilterOpen,
|
|
@@ -51,7 +52,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
51
52
|
onFilterChange,
|
|
52
53
|
onFilterReset,
|
|
53
54
|
onFacetReset,
|
|
54
|
-
|
|
55
|
+
onQuickFilterReset,
|
|
55
56
|
} = useSearchFilter(filter, setFilter);
|
|
56
57
|
const aiSearch = useAiSearch({ filter });
|
|
57
58
|
|
|
@@ -83,10 +84,10 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
const showLoadMore = (groupKey: string, currentCount: number = 0) => {
|
|
86
|
-
const
|
|
87
|
+
const groupFacet = facets.find((facet) => facet.field === groupField);
|
|
87
88
|
let needLoadMore = false;
|
|
88
|
-
if (
|
|
89
|
-
const groupValue =
|
|
89
|
+
if (groupFacet) {
|
|
90
|
+
const groupValue = groupFacet.values.find((value) => {
|
|
90
91
|
if (typeof value === 'object') {
|
|
91
92
|
return value.value === groupKey;
|
|
92
93
|
} else return false;
|
|
@@ -152,7 +153,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
152
153
|
}
|
|
153
154
|
}}
|
|
154
155
|
>
|
|
155
|
-
{translate('search.
|
|
156
|
+
{translate('search.ai.button', 'Search with AI')}
|
|
156
157
|
</SearchAiButton>
|
|
157
158
|
) : null}
|
|
158
159
|
{showSearchFilterButton && (
|
|
@@ -170,14 +171,17 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
170
171
|
facets={facets}
|
|
171
172
|
searchFilter={filter}
|
|
172
173
|
onFilterChange={onFilterChange}
|
|
173
|
-
|
|
174
|
+
onQuickFilterReset={onQuickFilterReset}
|
|
175
|
+
groupField={groupField}
|
|
174
176
|
/>
|
|
175
177
|
{showResults ? (
|
|
176
178
|
items && Object.keys(items).some((key) => items[key]?.length) ? (
|
|
177
179
|
Object.keys(items).map((key) =>
|
|
178
180
|
items[key]?.length ? (
|
|
179
181
|
<Fragment key={key}>
|
|
180
|
-
<SearchGroupTitle>
|
|
182
|
+
<SearchGroupTitle data-testid="search-group-title">
|
|
183
|
+
{key}
|
|
184
|
+
</SearchGroupTitle>
|
|
181
185
|
{items[key]?.map(mapItem)}
|
|
182
186
|
{showLoadMore(key, items[key]?.length || 0) && (
|
|
183
187
|
<SearchGroupFooter
|
|
@@ -215,6 +219,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
215
219
|
facets={facets}
|
|
216
220
|
filter={filter}
|
|
217
221
|
query={query}
|
|
222
|
+
quickFilterFields={[groupField]}
|
|
218
223
|
onFilterChange={onFilterChange}
|
|
219
224
|
onFilterReset={onFilterReset}
|
|
220
225
|
onFacetReset={onFacetReset}
|
|
@@ -231,41 +236,50 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
231
236
|
/>
|
|
232
237
|
)}
|
|
233
238
|
</SearchDialogBody>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
239
|
+
<SearchDialogFooter>
|
|
240
|
+
{mode === 'ai-dialog' ? (
|
|
241
|
+
<AiDisclaimer>
|
|
242
|
+
{translate(
|
|
243
|
+
'search.ai.disclaimer',
|
|
244
|
+
'AI search might provide incomplete or incorrect results. Verify important information.',
|
|
245
|
+
)}
|
|
246
|
+
</AiDisclaimer>
|
|
247
|
+
) : (
|
|
248
|
+
<>
|
|
249
|
+
<SearchShortcuts>
|
|
250
|
+
<SearchShortcut
|
|
251
|
+
data-translation-key="search.keys.navigate"
|
|
252
|
+
combination="Tab"
|
|
253
|
+
text={translate('search.keys.navigate', 'to navigate')}
|
|
254
|
+
/>
|
|
255
|
+
<SearchShortcut
|
|
256
|
+
data-translation-key="search.keys.select"
|
|
257
|
+
combination="⏎"
|
|
258
|
+
text={translate('search.keys.select', 'to select')}
|
|
259
|
+
/>
|
|
260
|
+
<SearchShortcut
|
|
261
|
+
data-translation-key="search.keys.exit"
|
|
262
|
+
combination="Esc"
|
|
263
|
+
text={translate('search.keys.exit', 'to exit')}
|
|
264
|
+
/>
|
|
265
|
+
</SearchShortcuts>
|
|
266
|
+
{isSearchLoading && (
|
|
267
|
+
<SearchLoading>
|
|
268
|
+
<SpinnerLoader size="16px" color="var(--search-input-icon-color)" />
|
|
269
|
+
{translate('search.loading', 'Loading...')}
|
|
270
|
+
</SearchLoading>
|
|
271
|
+
)}
|
|
272
|
+
<SearchCancelButton
|
|
273
|
+
data-translation-key="search.cancel"
|
|
274
|
+
variant="secondary"
|
|
275
|
+
size="small"
|
|
276
|
+
onClick={onClose}
|
|
277
|
+
>
|
|
278
|
+
{translate('search.cancel', 'Cancel')}
|
|
279
|
+
</SearchCancelButton>
|
|
280
|
+
</>
|
|
281
|
+
)}
|
|
282
|
+
</SearchDialogFooter>
|
|
269
283
|
</SearchDialogWrapper>
|
|
270
284
|
</SearchOverlay>
|
|
271
285
|
);
|
|
@@ -434,3 +448,10 @@ const SearchHeaderButtons = styled.div`
|
|
|
434
448
|
padding-left: var(--search-header-buttons-padding-left);
|
|
435
449
|
border-left: var(--search-header-buttons-border-left);
|
|
436
450
|
`;
|
|
451
|
+
|
|
452
|
+
const AiDisclaimer = styled.div`
|
|
453
|
+
font-size: var(--search-ai-disclaimer-font-size);
|
|
454
|
+
line-height: var(--search-ai-disclaimer-line-height);
|
|
455
|
+
color: var(--search-ai-disclaimer-text-color);
|
|
456
|
+
margin: 0 auto;
|
|
457
|
+
`;
|
|
@@ -13,7 +13,8 @@ export type SearchFilterProps = {
|
|
|
13
13
|
facets: SearchFacet[];
|
|
14
14
|
filter: SearchFilterItem[];
|
|
15
15
|
query: string;
|
|
16
|
-
|
|
16
|
+
quickFilterFields: string[];
|
|
17
|
+
onFilterChange: (field: string, value: string | string[], isQuickFilter?: boolean) => void;
|
|
17
18
|
onFilterReset: () => void;
|
|
18
19
|
onFacetReset: (field: string) => void;
|
|
19
20
|
};
|
|
@@ -23,6 +24,7 @@ export function SearchFilter({
|
|
|
23
24
|
facets,
|
|
24
25
|
filter,
|
|
25
26
|
query,
|
|
27
|
+
quickFilterFields,
|
|
26
28
|
onFilterChange,
|
|
27
29
|
onFilterReset,
|
|
28
30
|
onFacetReset,
|
|
@@ -50,10 +52,11 @@ export function SearchFilter({
|
|
|
50
52
|
<SearchFilterField
|
|
51
53
|
key={`${facet.field}-${index}`}
|
|
52
54
|
facet={facet}
|
|
53
|
-
onFilterChange={onFilterChange}
|
|
54
|
-
onFacetReset={onFacetReset}
|
|
55
55
|
filter={filter}
|
|
56
56
|
query={query}
|
|
57
|
+
quickFilterFields={quickFilterFields}
|
|
58
|
+
onFilterChange={onFilterChange}
|
|
59
|
+
onFacetReset={onFacetReset}
|
|
57
60
|
/>
|
|
58
61
|
))}
|
|
59
62
|
</SearchFilterFields>
|
|
@@ -14,7 +14,8 @@ type SearchFilterFieldProps = {
|
|
|
14
14
|
facet: SearchFacet;
|
|
15
15
|
filter: SearchFilterItem[];
|
|
16
16
|
query: string;
|
|
17
|
-
|
|
17
|
+
quickFilterFields: string[];
|
|
18
|
+
onFilterChange: (field: string, value: string | string[], isQuickFilter?: boolean) => void;
|
|
18
19
|
onFacetReset: (filed: string) => void;
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -23,6 +24,7 @@ export function SearchFilterField({
|
|
|
23
24
|
facet,
|
|
24
25
|
filter,
|
|
25
26
|
query,
|
|
27
|
+
quickFilterFields,
|
|
26
28
|
onFilterChange,
|
|
27
29
|
onFacetReset,
|
|
28
30
|
}: SearchFilterFieldProps): JSX.Element {
|
|
@@ -31,7 +33,7 @@ export function SearchFilterField({
|
|
|
31
33
|
const selectedValues = filter.find((item) => item.field === facet.field)?.values || [];
|
|
32
34
|
|
|
33
35
|
const onChange = (value: string | string[]) => {
|
|
34
|
-
onFilterChange(facet.field, value, facet.
|
|
36
|
+
onFilterChange(facet.field, value, facet.field in quickFilterFields);
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
const onReset = () => {
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
type SearchFacet,
|
|
6
|
+
type SearchFacetCount,
|
|
7
|
+
type SearchFilterItem,
|
|
8
|
+
} from '@redocly/theme/core/types';
|
|
6
9
|
import { Tag } from '@redocly/theme/components/Tag/Tag';
|
|
7
10
|
|
|
8
11
|
type SearchGroupsProps = {
|
|
9
12
|
facets: SearchFacet[];
|
|
10
13
|
searchFilter: SearchFilterItem[];
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
groupField: string;
|
|
15
|
+
onFilterChange: (field: string, value: string[], isQuickFilter?: boolean) => void;
|
|
16
|
+
onQuickFilterReset: () => void;
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
export function SearchGroups({
|
|
16
20
|
facets,
|
|
17
21
|
searchFilter,
|
|
22
|
+
groupField,
|
|
18
23
|
onFilterChange,
|
|
19
|
-
|
|
24
|
+
onQuickFilterReset,
|
|
20
25
|
}: SearchGroupsProps): JSX.Element {
|
|
21
|
-
const groupFacets = facets.filter((facet) => facet.
|
|
26
|
+
const groupFacets = facets.filter((facet) => facet.field === groupField);
|
|
22
27
|
|
|
23
28
|
const handleGroupTagClick = (
|
|
24
29
|
value: string,
|
|
@@ -36,8 +41,8 @@ export function SearchGroups({
|
|
|
36
41
|
<SearchGroupsWrapper>
|
|
37
42
|
<GroupTag
|
|
38
43
|
borderless
|
|
39
|
-
active={!searchFilter.some((item) => item.
|
|
40
|
-
onClick={() => searchFilter.some((item) => item.
|
|
44
|
+
active={!searchFilter.some((item) => item.isQuickFilter)}
|
|
45
|
+
onClick={() => searchFilter.some((item) => item.isQuickFilter) && onQuickFilterReset()}
|
|
41
46
|
>
|
|
42
47
|
All
|
|
43
48
|
</GroupTag>
|
|
@@ -176,5 +176,9 @@ export const search = css`
|
|
|
176
176
|
--search-ai-resource-tag-text-color: var(--text-color-secondary);
|
|
177
177
|
--search-ai-resource-tag-icon-color: var(--text-color-secondary);
|
|
178
178
|
|
|
179
|
+
--search-ai-disclaimer-font-size: var(--font-size-sm);
|
|
180
|
+
--search-ai-disclaimer-line-height: var(--line-height-sm);
|
|
181
|
+
--search-ai-disclaimer-text-color: var(--text-color-secondary);
|
|
182
|
+
|
|
179
183
|
// @tokens End
|
|
180
184
|
`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import type { ReactElement } from 'react';
|
|
4
|
+
import type { ForwardedRef, ReactElement } from 'react';
|
|
5
5
|
import type { SelectOption } from '@redocly/theme/core/types/select';
|
|
6
6
|
|
|
7
7
|
import { typedMemo } from '@redocly/theme/core/hoc/typedMemo';
|
|
@@ -14,15 +14,13 @@ export type SegmentedProps<T> = {
|
|
|
14
14
|
size?: 'regular' | 'small';
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
function SegmentedComponent<T>(
|
|
18
|
-
options,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
className = '',
|
|
22
|
-
size = 'regular',
|
|
23
|
-
}: SegmentedProps<T>): ReactElement {
|
|
17
|
+
function SegmentedComponent<T>(
|
|
18
|
+
{ options, onChange, value, className = '', size = 'regular' }: SegmentedProps<T>,
|
|
19
|
+
ref?: ForwardedRef<HTMLDivElement>,
|
|
20
|
+
): ReactElement {
|
|
24
21
|
return (
|
|
25
22
|
<SegmentedGroup
|
|
23
|
+
ref={ref}
|
|
26
24
|
data-component-name="Segmented/Segmented"
|
|
27
25
|
className={`tag-grey ${size} ${className}`}
|
|
28
26
|
role="tablist"
|
|
@@ -43,7 +41,9 @@ function SegmentedComponent<T>({
|
|
|
43
41
|
);
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
export const Segmented = typedMemo(SegmentedComponent)
|
|
44
|
+
export const Segmented = typedMemo(forwardRef(SegmentedComponent)) as <T>(
|
|
45
|
+
props: SegmentedProps<T> & { ref?: ForwardedRef<HTMLDivElement> },
|
|
46
|
+
) => ReactElement;
|
|
47
47
|
|
|
48
48
|
const SegmentedGroup = styled.div`
|
|
49
49
|
display: flex;
|
|
@@ -60,6 +60,7 @@ export function useCodeWalkthroughControls(
|
|
|
60
60
|
filters: Record<string, CodeWalkthroughFilter>,
|
|
61
61
|
inputs: InputsMarkdocAttr,
|
|
62
62
|
toggles: TogglesMarkdocAttr,
|
|
63
|
+
enableDeepLink: boolean,
|
|
63
64
|
): WalkthroughControlsState {
|
|
64
65
|
const location = useLocation();
|
|
65
66
|
const navigate = useNavigate();
|
|
@@ -72,7 +73,7 @@ export function useCodeWalkthroughControls(
|
|
|
72
73
|
...toggle,
|
|
73
74
|
render: true,
|
|
74
75
|
type: 'toggle',
|
|
75
|
-
value: searchParams.get(id) === 'true',
|
|
76
|
+
value: enableDeepLink ? searchParams.get(id) === 'true' : false,
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
return initialState;
|
|
@@ -116,7 +117,7 @@ export function useCodeWalkthroughControls(
|
|
|
116
117
|
...input,
|
|
117
118
|
render: true,
|
|
118
119
|
type: 'input',
|
|
119
|
-
value: searchParams.get(id)
|
|
120
|
+
value: enableDeepLink ? (searchParams.get(id) ?? input.value) : input.value,
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
return initialState;
|
|
@@ -156,11 +157,12 @@ export function useCodeWalkthroughControls(
|
|
|
156
157
|
const initialState: FiltersState = {};
|
|
157
158
|
|
|
158
159
|
for (const [id, filter] of Object.entries(filters)) {
|
|
160
|
+
const defaultValue = filter?.items?.[0]?.value || '';
|
|
159
161
|
initialState[id] = {
|
|
160
162
|
...filter,
|
|
161
163
|
render: true,
|
|
162
164
|
type: 'filter',
|
|
163
|
-
value: searchParams.get(id)
|
|
165
|
+
value: enableDeepLink ? (searchParams.get(id) ?? defaultValue) : defaultValue,
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -270,6 +272,10 @@ export function useCodeWalkthroughControls(
|
|
|
270
272
|
* Update the URL search params with the current state of the filters and inputs
|
|
271
273
|
*/
|
|
272
274
|
useEffect(() => {
|
|
275
|
+
if (!enableDeepLink) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
273
279
|
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
|
|
274
280
|
|
|
275
281
|
for (const [id, { value }] of Object.entries(state)) {
|
|
@@ -8,9 +8,9 @@ import { ACTIVE_STEP_QUERY_PARAM } from '@redocly/theme/core/constants';
|
|
|
8
8
|
|
|
9
9
|
type ActiveStep = string | null;
|
|
10
10
|
type CodeWalkthroughStep = CodeWalkthroughStepAttr & {
|
|
11
|
-
active?: boolean;
|
|
12
11
|
compRef?: HTMLElement;
|
|
13
12
|
};
|
|
13
|
+
|
|
14
14
|
export type WalkthroughStepsState = {
|
|
15
15
|
activeStep: ActiveStep;
|
|
16
16
|
setActiveStep: (stepId: ActiveStep) => void;
|
|
@@ -20,7 +20,10 @@ export type WalkthroughStepsState = {
|
|
|
20
20
|
filtersElementRef?: React.RefObject<HTMLDivElement>;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export function useCodeWalkthroughSteps(
|
|
23
|
+
export function useCodeWalkthroughSteps(
|
|
24
|
+
steps: CodeWalkthroughStep[],
|
|
25
|
+
enableDeepLink: boolean,
|
|
26
|
+
): WalkthroughStepsState {
|
|
24
27
|
const location = useLocation();
|
|
25
28
|
const navigate = useNavigate();
|
|
26
29
|
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
|
@@ -32,7 +35,7 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
32
35
|
const observedElementsRef = useRef(new Set<HTMLElement>());
|
|
33
36
|
|
|
34
37
|
const [activeStep, setActiveStep] = useState<ActiveStep>(
|
|
35
|
-
searchParams.get(ACTIVE_STEP_QUERY_PARAM),
|
|
38
|
+
enableDeepLink ? searchParams.get(ACTIVE_STEP_QUERY_PARAM) : null,
|
|
36
39
|
);
|
|
37
40
|
|
|
38
41
|
const register = useCallback(
|
|
@@ -74,28 +77,23 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const renderedSteps = steps.filter((step) => Boolean(step.compRef));
|
|
81
|
+
|
|
82
|
+
if (renderedSteps.length < 2) {
|
|
83
|
+
setActiveStep(renderedSteps[0]?.id || null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
for (const entry of entries) {
|
|
79
|
-
const
|
|
80
|
-
const stepKey = Number((target as HTMLElement)?.dataset?.stepKey);
|
|
81
|
-
const stepActive = (target as HTMLElement)?.dataset?.stepActive === 'true';
|
|
88
|
+
const stepKey = Number((entry.target as HTMLElement)?.dataset?.stepKey);
|
|
82
89
|
|
|
83
90
|
if (!Number.isInteger(stepKey) || stepKey < 0) {
|
|
84
91
|
continue;
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
const { intersectionRatio, boundingClientRect, rootBounds, isIntersecting } = entry;
|
|
87
95
|
const step = steps[stepKey];
|
|
88
|
-
step.active = stepActive;
|
|
89
|
-
stepsEntries.push(entry);
|
|
90
|
-
}
|
|
91
96
|
|
|
92
|
-
for (const stepEntry of stepsEntries) {
|
|
93
|
-
const { target, intersectionRatio, boundingClientRect, rootBounds, isIntersecting } =
|
|
94
|
-
stepEntry;
|
|
95
|
-
const stepKey = Number((target as HTMLElement)?.dataset?.stepKey);
|
|
96
|
-
const step = steps[stepKey];
|
|
97
|
-
|
|
98
|
-
const renderedSteps = steps.filter((step) => Boolean(step.compRef));
|
|
99
97
|
const stepIndex = renderedSteps.findIndex(
|
|
100
98
|
(renderedStep) => renderedStep.stepKey === step.stepKey,
|
|
101
99
|
);
|
|
@@ -105,6 +103,16 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
105
103
|
rootBounds?.bottom !== undefined && boundingClientRect.top < rootBounds.top;
|
|
106
104
|
const stepGoesIn = isIntersecting;
|
|
107
105
|
|
|
106
|
+
if (
|
|
107
|
+
intersectionRatio > 0.8 &&
|
|
108
|
+
intersectionRatio < 1 &&
|
|
109
|
+
intersectionAtTop &&
|
|
110
|
+
activeStep === null
|
|
111
|
+
) {
|
|
112
|
+
setActiveStep(step.id);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
if (intersectionRatio < 1 && intersectionRatio !== 0 && intersectionAtTop) {
|
|
109
117
|
let newStep: string | null = null;
|
|
110
118
|
|
|
@@ -128,7 +136,7 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
128
136
|
const filtersElementHeight = filtersElementRef.current?.clientHeight || 0;
|
|
129
137
|
const navbarHeight = document.querySelector('nav')?.clientHeight || 0;
|
|
130
138
|
const newObserver = new IntersectionObserver(observerCallback, {
|
|
131
|
-
threshold: [0.8],
|
|
139
|
+
threshold: [0.8, 0.85, 0.9, 0.95],
|
|
132
140
|
rootMargin: `-${filtersElementHeight + navbarHeight}px 0px 0px 0px`,
|
|
133
141
|
});
|
|
134
142
|
|
|
@@ -145,6 +153,10 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
145
153
|
* Update the URL search params with the current state of the filters and inputs
|
|
146
154
|
*/
|
|
147
155
|
useEffect(() => {
|
|
156
|
+
if (!enableDeepLink) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
|
|
149
161
|
|
|
150
162
|
if (activeStep) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
CodeWalkthroughFileset,
|
|
3
2
|
CodeWalkthroughFile,
|
|
4
3
|
CodeWalkthroughStepAttr,
|
|
5
|
-
|
|
6
|
-
InputsMarkdocAttr,
|
|
7
|
-
TogglesMarkdocAttr,
|
|
4
|
+
CodeWalkthroughAttr,
|
|
8
5
|
} from '@redocly/config';
|
|
9
6
|
|
|
10
7
|
import {
|
|
@@ -23,16 +20,19 @@ export type WalkthroughState = {
|
|
|
23
20
|
|
|
24
21
|
export function useCodeWalkthrough(
|
|
25
22
|
steps: CodeWalkthroughStepAttr[],
|
|
26
|
-
attributes:
|
|
27
|
-
filters: Record<string, CodeWalkthroughFilter>;
|
|
28
|
-
filesets: CodeWalkthroughFileset[];
|
|
29
|
-
inputs: InputsMarkdocAttr;
|
|
30
|
-
toggles: TogglesMarkdocAttr;
|
|
31
|
-
},
|
|
23
|
+
attributes: Omit<CodeWalkthroughAttr, 'steps' | 'preview'>,
|
|
32
24
|
): WalkthroughState {
|
|
33
|
-
const { filters, filesets, inputs, toggles } = attributes;
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
const { filters, filesets, inputs, toggles, __idx } = attributes;
|
|
26
|
+
/*
|
|
27
|
+
We only enable deep linking for the first CodeWalkthrough,
|
|
28
|
+
because we don't expect more than one on the same page.
|
|
29
|
+
Any subsequent walkthroughs have it disabled to avoid
|
|
30
|
+
collisions/conflicts in the URL.
|
|
31
|
+
*/
|
|
32
|
+
const enableDeepLink = __idx === 1;
|
|
33
|
+
|
|
34
|
+
const stepsState = useCodeWalkthroughSteps(steps, enableDeepLink);
|
|
35
|
+
const controlsState = useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink);
|
|
36
36
|
|
|
37
37
|
const files: CodeWalkthroughFile[] = filesets
|
|
38
38
|
.filter((fileset) => controlsState.areConditionsMet(fileset))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { CodeWalkthroughFile } from '@redocly/config';
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getFileIconByExt,
|
|
8
|
+
removeLeadingSlash,
|
|
9
|
+
findClosestCommonDirectory,
|
|
10
|
+
} from '@redocly/theme/core/utils';
|
|
11
|
+
|
|
12
|
+
export type RenderableFile = CodeWalkthroughFile & {
|
|
13
|
+
FileIcon: React.FunctionComponent<IconProps>;
|
|
14
|
+
parentFolder: string;
|
|
15
|
+
isNameDuplicate: boolean;
|
|
16
|
+
inRootDir: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function useRenderableFiles(files: CodeWalkthroughFile[]): RenderableFile[] {
|
|
20
|
+
return useMemo(
|
|
21
|
+
function () {
|
|
22
|
+
const filePaths = files.map(({ path }) => path);
|
|
23
|
+
const rootDir = findClosestCommonDirectory(filePaths);
|
|
24
|
+
|
|
25
|
+
const renderableFiles = files.map((file) => {
|
|
26
|
+
const FileIcon = getFileTypeIcon(file.basename);
|
|
27
|
+
const parentFolder = file.path.split('/').slice(-2, -1)[0];
|
|
28
|
+
const isNameDuplicate = files.some(
|
|
29
|
+
(_file) => file.basename === _file.basename && file.path !== _file.path,
|
|
30
|
+
);
|
|
31
|
+
const inRootDir = file.path === `${removeLeadingSlash(rootDir)}/${file.basename}`;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
...file,
|
|
35
|
+
FileIcon,
|
|
36
|
+
inRootDir,
|
|
37
|
+
parentFolder,
|
|
38
|
+
isNameDuplicate,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return renderableFiles;
|
|
43
|
+
},
|
|
44
|
+
[files],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getFileTypeIcon(basename: string) {
|
|
49
|
+
const extension = basename.split('.').pop()?.toLowerCase() || '';
|
|
50
|
+
return getFileIconByExt(extension);
|
|
51
|
+
}
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -32,3 +32,4 @@ export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough';
|
|
|
32
32
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-steps';
|
|
33
33
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-controls';
|
|
34
34
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-panel';
|
|
35
|
+
export * from '@redocly/theme/core/hooks/code-walkthrough/use-renderable-files';
|