@redocly/theme 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/lib/components/DatePicker/variables.js +1 -1
  2. package/lib/core/contexts/CodeWalkthrough/CodeWalkthroughControlsContext.js +2 -6
  3. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +6 -9
  4. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +57 -96
  5. package/lib/core/styles/global.js +18 -0
  6. package/lib/core/types/search.d.ts +1 -0
  7. package/lib/core/utils/download-code-walkthrough.d.ts +4 -2
  8. package/lib/core/utils/get-code-walkthrough-file-text.d.ts +4 -2
  9. package/lib/ext/configure.d.ts +24 -0
  10. package/lib/ext/configure.js +18 -0
  11. package/lib/index.d.ts +1 -0
  12. package/lib/index.js +1 -0
  13. package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +24 -3
  14. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.d.ts +5 -4
  15. package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +1 -1
  16. package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +4 -4
  17. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +2 -2
  18. package/lib/markdoc/components/CodeWalkthrough/Input.js +3 -3
  19. package/package.json +3 -2
  20. package/src/components/DatePicker/variables.ts +1 -1
  21. package/src/core/contexts/CodeWalkthrough/CodeWalkthroughControlsContext.tsx +2 -8
  22. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +81 -139
  23. package/src/core/styles/global.ts +18 -0
  24. package/src/core/types/search.ts +1 -0
  25. package/src/core/utils/download-code-walkthrough.ts +2 -2
  26. package/src/core/utils/get-code-walkthrough-file-text.ts +3 -3
  27. package/src/ext/configure.ts +39 -0
  28. package/src/index.ts +1 -0
  29. package/src/markdoc/components/CodeWalkthrough/CodeContainer.tsx +27 -3
  30. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +2 -1
  31. package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +1 -1
  32. package/src/markdoc/components/CodeWalkthrough/CodeToggle.tsx +4 -4
  33. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +7 -4
  34. package/src/markdoc/components/CodeWalkthrough/Input.tsx +4 -4
@@ -6,7 +6,7 @@ exports.datePicker = (0, styled_components_1.css) `
6
6
  --date-picker-nav-color: #000000;
7
7
  --date-picker-tile-bg-color: var(--color-blue-2);
8
8
  --date-picker-tile-color: var(--text-color-primary);
9
- --date-picker-tile-bg-color-hover: var(--color-blue-3)
9
+ --date-picker-tile-bg-color-hover: var(--color-blue-3);
10
10
  --date-picker-tile-color-hover: var(--text-color-primary);
11
11
  --date-picker-input-width: var(--spacing-md);
12
12
  --date-picker-invalid-input-bg-color: var(--bg-color-raised);
@@ -4,12 +4,8 @@ exports.CodeWalkthroughControlsStateContext = void 0;
4
4
  const react_1 = require("react");
5
5
  exports.CodeWalkthroughControlsStateContext = (0, react_1.createContext)({
6
6
  activeFilters: [],
7
- getToggleState: () => null,
8
- changeToggleState: () => { },
9
- getInputState: () => null,
10
- changeInputState: () => { },
11
- getFilterState: () => null,
12
- changeFilterState: () => { },
7
+ getControlState: () => null,
8
+ changeControlState: () => { },
13
9
  getFileText: () => '',
14
10
  areConditionsMet: () => false,
15
11
  handleDownloadCode: () => Promise.resolve(),
@@ -1,6 +1,4 @@
1
- import type { CodeWalkthroughFile, CodeWalkthroughFilter, InputsMarkdocAttr, TogglesMarkdocAttr, CodeWalkthroughFilterItem, ControlState, ControlTypeValue, ControlType, CodeWalkthroughConditionsObject } from '@redocly/config';
2
- export type getState<T extends ControlType> = (id: string) => Omit<ControlState<T>, 'type'> | null;
3
- export type changeState<T extends ControlType> = (id: string, value: ControlTypeValue<T>) => void;
1
+ import type { CodeWalkthroughFile, CodeWalkthroughFilter, InputsMarkdocAttr, TogglesMarkdocAttr, CodeWalkthroughFilterItem, CodeWalkthroughConditionsObject } from '@redocly/config';
4
2
  export type ActiveFilter = {
5
3
  id: string;
6
4
  label?: string;
@@ -8,12 +6,11 @@ export type ActiveFilter = {
8
6
  };
9
7
  export type WalkthroughControlsState = {
10
8
  activeFilters: ActiveFilter[];
11
- getToggleState: getState<'toggle'>;
12
- changeToggleState: changeState<'toggle'>;
13
- getInputState: getState<'input'>;
14
- changeInputState: changeState<'input'>;
15
- getFilterState: getState<'filter'>;
16
- changeFilterState: changeState<'filter'>;
9
+ getControlState: (id: string) => {
10
+ value: string | boolean;
11
+ render: boolean;
12
+ } | null;
13
+ changeControlState: (id: string, value: string | boolean) => void;
17
14
  areConditionsMet: (conditions: CodeWalkthroughConditionsObject) => boolean;
18
15
  handleDownloadCode: (files: CodeWalkthroughFile[]) => Promise<void>;
19
16
  getFileText: (file: CodeWalkthroughFile) => string;
@@ -13,128 +13,94 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
13
13
  const location = (0, react_router_dom_1.useLocation)();
14
14
  const navigate = (0, react_router_dom_1.useNavigate)();
15
15
  const searchParams = (0, react_1.useMemo)(() => new URLSearchParams(location.search), [location.search]);
16
- const [togglesState, setTogglesState] = (0, react_1.useState)(() => {
16
+ const [controlsState, setControlsState] = (0, react_1.useState)(() => {
17
+ var _a, _b, _c, _d;
17
18
  const initialState = {};
18
19
  for (const [id, toggle] of Object.entries(toggles)) {
19
20
  initialState[id] = Object.assign(Object.assign({}, toggle), { render: true, type: 'toggle', value: enableDeepLink ? searchParams.get(id) === 'true' : false });
20
21
  }
21
- return initialState;
22
- });
23
- const changeToggleState = (toggleId, checked) => {
24
- setTogglesState((prev) => {
25
- const toggle = prev[toggleId];
26
- if (toggle) {
27
- return Object.assign(Object.assign({}, prev), { [toggleId]: Object.assign(Object.assign({}, toggle), { value: checked }) });
28
- }
29
- else {
30
- return prev;
31
- }
32
- });
33
- };
34
- const getToggleState = (toggleId) => {
35
- const toggleState = togglesState[toggleId];
36
- if (toggleState) {
37
- return {
38
- render: toggleState.render,
39
- value: toggleState.value,
40
- };
41
- }
42
- else {
43
- return null;
44
- }
45
- };
46
- const [inputsState, setInputsState] = (0, react_1.useState)(() => {
47
- var _a;
48
- const initialState = {};
49
22
  for (const [id, input] of Object.entries(inputs)) {
50
23
  initialState[id] = Object.assign(Object.assign({}, input), { render: true, type: 'input', value: enableDeepLink ? ((_a = searchParams.get(id)) !== null && _a !== void 0 ? _a : input.value) : input.value });
51
24
  }
52
- return initialState;
53
- });
54
- const changeInputState = (inputId, value) => {
55
- setInputsState((prev) => {
56
- const input = prev[inputId];
57
- if (input) {
58
- return Object.assign(Object.assign({}, prev), { [inputId]: Object.assign(Object.assign({}, input), { value }) });
59
- }
60
- else {
61
- return prev;
62
- }
63
- });
64
- };
65
- const getInputState = (inputId) => {
66
- const inputState = inputsState[inputId];
67
- if (inputState) {
68
- return {
69
- render: inputState.render,
70
- value: inputState.value,
71
- };
72
- }
73
- else {
74
- return null;
75
- }
76
- };
77
- const [filtersState, setFiltersState] = (0, react_1.useState)(() => {
78
- var _a, _b, _c;
79
- const initialState = {};
80
25
  for (const [id, filter] of Object.entries(filters)) {
81
- const defaultValue = ((_b = (_a = filter === null || filter === void 0 ? void 0 : filter.items) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) || '';
82
- initialState[id] = Object.assign(Object.assign({}, filter), { render: true, type: 'filter', value: enableDeepLink ? ((_c = searchParams.get(id)) !== null && _c !== void 0 ? _c : defaultValue) : defaultValue });
26
+ const defaultValue = ((_c = (_b = filter === null || filter === void 0 ? void 0 : filter.items) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value) || '';
27
+ initialState[id] = Object.assign(Object.assign({}, filter), { render: true, type: 'filter', value: enableDeepLink ? ((_d = searchParams.get(id)) !== null && _d !== void 0 ? _d : defaultValue) : defaultValue });
83
28
  }
84
29
  return initialState;
85
30
  });
86
- const changeFilterState = (filterId, value) => {
87
- setFiltersState((prev) => {
88
- const filter = prev[filterId];
89
- if (filter) {
90
- return Object.assign(Object.assign({}, prev), { [filterId]: Object.assign(Object.assign({}, filter), { value }) });
91
- }
92
- else {
31
+ const changeControlState = (id, value) => {
32
+ setControlsState((prev) => {
33
+ const control = prev[id];
34
+ if (!control) {
35
+ console.error(`Control with id "${id}" not found.`);
93
36
  return prev;
94
37
  }
38
+ switch (control.type) {
39
+ case 'input':
40
+ if (typeof value !== 'string') {
41
+ console.error(`Invalid value type for input "${id}". Input control type requires a string value.`);
42
+ return prev;
43
+ }
44
+ break;
45
+ case 'toggle':
46
+ if (typeof value !== 'boolean') {
47
+ console.error(`Invalid value type for toggle "${id}". Toggle control type requires a boolean value.`);
48
+ return prev;
49
+ }
50
+ break;
51
+ case 'filter':
52
+ if (typeof value !== 'string') {
53
+ console.error(`Invalid value type for filter "${id}". Filter control type requires a string value.`);
54
+ return prev;
55
+ }
56
+ break;
57
+ default:
58
+ console.error(`Invalid control type "${control === null || control === void 0 ? void 0 : control.type}" for control "${id}". Allowed types are "toggle", "input", or "filter".`);
59
+ return prev;
60
+ }
61
+ return Object.assign(Object.assign({}, prev), { [id]: Object.assign(Object.assign({}, control), { value }) });
95
62
  });
96
63
  };
97
- const getFilterState = (filterId) => {
98
- const filterState = filtersState[filterId];
99
- if (filterState) {
64
+ const getControlState = (id) => {
65
+ const controlState = controlsState[id];
66
+ if (controlState) {
100
67
  return {
101
- render: filterState.render,
102
- value: filterState.value,
68
+ render: controlState.render,
69
+ value: controlState.value,
103
70
  };
104
71
  }
105
72
  else {
106
73
  return null;
107
74
  }
108
75
  };
109
- const state = Object.assign(Object.assign(Object.assign({}, filtersState), togglesState), inputsState);
110
76
  const walkthroughContext = (0, react_1.useMemo)(() => {
111
- const areConditionsMet = (conditions) => (0, utils_1.matchCodeWalkthroughConditions)(conditions, state);
112
- for (const [id, element] of Object.entries(state)) {
113
- if (element && !areConditionsMet(element)) {
114
- state[id].render = false;
115
- state[id].value = defaultControlsValues[element.type];
77
+ const areConditionsMet = (conditions) => (0, utils_1.matchCodeWalkthroughConditions)(conditions, controlsState);
78
+ for (const [id, control] of Object.entries(controlsState)) {
79
+ if (control && !areConditionsMet(control)) {
80
+ controlsState[id].render = false;
81
+ controlsState[id].value = defaultControlsValues[control.type];
116
82
  }
117
83
  }
118
84
  const activeFilters = [];
119
85
  for (const [id, filter] of Object.entries(filters)) {
120
- if (!filtersState[id].render) {
86
+ if (!controlsState[id].render) {
121
87
  continue;
122
88
  }
123
89
  // code-walk-todo: need to check if we have a default fallback
124
90
  const items = Array.isArray(filter === null || filter === void 0 ? void 0 : filter.items) ? filter.items : [];
125
91
  const activeItems = items.filter((item) => areConditionsMet(item));
126
92
  if (activeItems.length === 0) {
127
- filtersState[id].render = false;
128
- filtersState[id].value = defaultControlsValues['filter'];
93
+ controlsState[id].render = false;
94
+ controlsState[id].value = defaultControlsValues['filter'];
129
95
  continue;
130
96
  }
131
- const currentValue = filtersState[id].value;
97
+ const currentValue = controlsState[id].value;
132
98
  if (currentValue) {
133
99
  const isValueInActiveItems = activeItems.findIndex(({ value }) => value === currentValue) !== -1;
134
- filtersState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
100
+ controlsState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
135
101
  }
136
102
  else {
137
- filtersState[id].value = activeItems[0].value;
103
+ controlsState[id].value = activeItems[0].value;
138
104
  }
139
105
  activeFilters.push({
140
106
  id,
@@ -142,8 +108,9 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
142
108
  items: activeItems,
143
109
  });
144
110
  }
145
- const handleDownloadCode = (files) => (0, utils_1.downloadCodeWalkthrough)(files, state, inputsState);
146
- const getFileText = (file) => (0, utils_1.getCodeWalkthroughFileText)(file, state, inputsState);
111
+ const inputsState = Object.fromEntries(Object.entries(controlsState).filter(([_, controlState]) => controlState.type === 'input'));
112
+ const handleDownloadCode = (files) => (0, utils_1.downloadCodeWalkthrough)(files, controlsState, inputsState);
113
+ const getFileText = (file) => (0, utils_1.getCodeWalkthroughFileText)(file, controlsState, inputsState);
147
114
  const populateInputsWithValue = (node) => (0, utils_1.replaceInputsWithValue)(node, inputsState);
148
115
  return {
149
116
  activeFilters,
@@ -152,9 +119,7 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
152
119
  getFileText,
153
120
  populateInputsWithValue,
154
121
  };
155
- // Ignore state in dependency array as it's simply a combination of toggles, filters and inputs.
156
- // eslint-disable-next-line react-hooks/exhaustive-deps
157
- }, [filters, filtersState, togglesState, inputsState]);
122
+ }, [filters, controlsState]);
158
123
  /**
159
124
  * Update the URL search params with the current state of the filters and inputs
160
125
  */
@@ -163,7 +128,7 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
163
128
  return;
164
129
  }
165
130
  const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
166
- for (const [id, { value }] of Object.entries(state)) {
131
+ for (const [id, { value }] of Object.entries(controlsState)) {
167
132
  if (value) {
168
133
  newSearchParams.set(id, value.toString());
169
134
  }
@@ -177,12 +142,8 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
177
142
  navigate({ search: newSearch });
178
143
  // Ignore searchParams in dependency array to avoid infinite re-renders
179
144
  // eslint-disable-next-line react-hooks/exhaustive-deps
180
- }, [filters, filtersState, togglesState, inputsState, navigate, location, state]);
181
- return Object.assign({ getInputState,
182
- changeInputState,
183
- getToggleState,
184
- changeToggleState,
185
- getFilterState,
186
- changeFilterState }, walkthroughContext);
145
+ }, [filters, controlsState, navigate, location]);
146
+ return Object.assign({ changeControlState,
147
+ getControlState }, walkthroughContext);
187
148
  }
188
149
  //# sourceMappingURL=use-code-walkthrough-controls.js.map
@@ -1007,6 +1007,24 @@ const error = (0, styled_components_1.css) `
1007
1007
  --detailed-error-message-width: 100%;
1008
1008
  --detailed-error-message-font-size: var(--font-size-base);
1009
1009
  --detailed-error-message-font-family: var(--code-block-controls-font-family);
1010
+
1011
+ --compilation-error-description-padding: 0 0 var(--spacing-base);
1012
+ --compilation-error-codeframe-margin: var(--spacing-xs) 0 0 0;
1013
+ --compilation-error-codeframe-padding: var(--spacing-xs);
1014
+ --compilation-error-block-padding: var(--spacing-base);
1015
+ --compilation-error-block-margin: var(--spacing-xl) 0;
1016
+ --compilation-error-title-font-size: var(--font-size-xl);
1017
+ --compilation-error-header-font-size: var(--font-size-lg);
1018
+ --compilation-error-text-font-size: var(--font-size-base);
1019
+ --compilation-error-header-padding: var(--spacing-sm);
1020
+ --compilation-error-container-margin: var(--spacing-xxl) auto;
1021
+ --compilation-error-container-padding: var(--spacing-lg);
1022
+ --compilation-error-page-max-width: 800px;
1023
+ --compilation-error-font-family: var(--font-family-base);
1024
+ --compilation-error-fix-instruction-margin: var(--spacing-sm) 0 0 0;
1025
+ --compilation-error-highlighted-text-padding: 0 var(--spacing-xxs);
1026
+ --compilation-error-divider-margin: var(--spacing-base) 0;
1027
+ --compilation-error-file-header-margin: 0 0 var(--spacing-xs) 0;
1010
1028
  `;
1011
1029
  const modal = (0, styled_components_1.css) `
1012
1030
  body:has(.scroll-lock) {
@@ -5,6 +5,7 @@ export type OperationParameter = {
5
5
  description: string | string[];
6
6
  place: string;
7
7
  path?: string[];
8
+ deepLink?: string;
8
9
  };
9
10
  export type SearchDocument = {
10
11
  id: string;
@@ -1,4 +1,6 @@
1
- import type { CodeWalkthroughFile, InputsState } from '@redocly/config';
1
+ import type { CodeWalkthroughFile } from '@redocly/config';
2
2
  export declare function downloadCodeWalkthrough(files: CodeWalkthroughFile[], state: Record<string, {
3
3
  value: string | boolean;
4
- }>, inputsState: InputsState): Promise<void>;
4
+ }>, inputsState: Record<string, {
5
+ value: string;
6
+ }>): Promise<void>;
@@ -1,4 +1,6 @@
1
- import type { CodeWalkthroughFile, InputsState } from '@redocly/config';
1
+ import type { CodeWalkthroughFile } from '@redocly/config';
2
2
  export declare function getCodeWalkthroughFileText(file: CodeWalkthroughFile, state: Record<string, {
3
3
  value: string | boolean;
4
- }>, inputsState: InputsState): string;
4
+ }>, inputsState: Record<string, {
5
+ value: string;
6
+ }>): string;
@@ -0,0 +1,24 @@
1
+ export type SecurityDetails = {
2
+ password?: string;
3
+ username?: string;
4
+ token?: {
5
+ token_type?: string;
6
+ access_token: string;
7
+ };
8
+ client_id?: string;
9
+ client_secret?: string;
10
+ scopes?: string[];
11
+ };
12
+ export type ConfigureRequestValues = {
13
+ headers?: Record<string, string>;
14
+ body?: Record<string, any>;
15
+ query?: Record<string, string>;
16
+ path?: Record<string, string>;
17
+ cookie?: Record<string, string>;
18
+ security?: SecurityDetails;
19
+ };
20
+ type Configure = {
21
+ requestValues?: ConfigureRequestValues;
22
+ };
23
+ export declare function configure(): Configure;
24
+ export {};
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configure = configure;
4
+ function configure() {
5
+ // const exampleRequestValues: ConfigureRequestValues = {
6
+ // body: {
7
+ // name: 'John Doe',
8
+ // },
9
+ // security: {
10
+ // token: {
11
+ // access_token: 'John Doe token',
12
+ // },
13
+ // },
14
+ // };
15
+ // return { requestValues: exampleRequestValues } satisfies Configure;
16
+ return {};
17
+ }
18
+ //# sourceMappingURL=configure.js.map
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './core';
2
2
  export * from './config';
3
+ export * from './ext/configure';
3
4
  export * from './components/Button/Button';
4
5
  export * from './components/JsonViewer/JsonViewer';
5
6
  export * from './components/Tag/Tag';
package/lib/index.js CHANGED
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.markdoc = void 0;
30
30
  __exportStar(require("./core"), exports);
31
31
  __exportStar(require("./config"), exports);
32
+ __exportStar(require("./ext/configure"), exports);
32
33
  /* Components */
33
34
  __exportStar(require("./components/Button/Button"), exports);
34
35
  __exportStar(require("./components/JsonViewer/JsonViewer"), exports);
@@ -36,9 +36,30 @@ function CodeContainer({ highlightedCode, toolbar, }) {
36
36
  // useEffect executed before DOM is updated due to re-render called before "painting" phase.
37
37
  setTimeout(() => {
38
38
  if (compRef.current) {
39
- const element = compRef.current.querySelector('span.line.highlighted');
40
- if (element) {
41
- element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
39
+ const highlightedLines = Array.from(compRef.current.querySelectorAll('span.line.highlighted'));
40
+ const { bottom: panelBottom, top: panelTop } = compRef.current.getBoundingClientRect();
41
+ const linesBelow = [];
42
+ const linesAbove = [];
43
+ for (const highlightedLine of highlightedLines) {
44
+ const { bottom: lineBottom, top: lineTop } = highlightedLine.getBoundingClientRect();
45
+ const below = lineBottom > panelBottom;
46
+ const above = lineTop < panelTop;
47
+ if (below) {
48
+ linesBelow.push(highlightedLine);
49
+ }
50
+ else if (above) {
51
+ linesAbove.push(highlightedLine);
52
+ }
53
+ }
54
+ if ((linesBelow.length && linesAbove.length) || linesAbove.length) {
55
+ linesAbove[0].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
56
+ }
57
+ else if (linesBelow.length) {
58
+ linesBelow[linesBelow.length - 1].scrollIntoView({
59
+ behavior: 'smooth',
60
+ block: 'nearest',
61
+ inline: 'start',
62
+ });
42
63
  }
43
64
  }
44
65
  }, 200);
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import type { CodeWalkthroughFilter } from '@redocly/config';
3
+ export type GetFilterState = (groupId: string) => {
4
+ value: string;
5
+ render: boolean;
6
+ } | null;
3
7
  export type CodeFilterProps = {
4
8
  filters: CodeWalkthroughFilter[];
5
- getFilterState: (groupId: string) => {
6
- value: string;
7
- render: boolean;
8
- } | null;
9
+ getFilterState: GetFilterState;
9
10
  handleFilterSelect: (groupId: string, id: string) => void;
10
11
  filtersElementRef?: React.RefObject<HTMLDivElement>;
11
12
  };
@@ -41,7 +41,7 @@ function CodeStep({ id, heading, stepKey, when, unless, children, }) {
41
41
  if (lockObserver) {
42
42
  lockObserver.current = true;
43
43
  if (compRef.current) {
44
- compRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
44
+ compRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
45
45
  }
46
46
  setActiveStep(id);
47
47
  setTimeout(() => {
@@ -35,16 +35,16 @@ const CodeStep_1 = require("../../../markdoc/components/CodeWalkthrough/CodeStep
35
35
  const Switch_1 = require("../../../components/Switch/Switch");
36
36
  function CodeToggle(props) {
37
37
  const { id, label, description, children } = props;
38
- const { getToggleState, changeToggleState } = (0, react_1.useContext)(contexts_1.CodeWalkthroughControlsStateContext);
39
- const toggleState = getToggleState(id);
40
- if (!(toggleState && toggleState.render)) {
38
+ const { getControlState, changeControlState } = (0, react_1.useContext)(contexts_1.CodeWalkthroughControlsStateContext);
39
+ const toggleState = getControlState(id);
40
+ if (!(toggleState && toggleState.render && typeof toggleState.value === 'boolean')) {
41
41
  return null;
42
42
  }
43
43
  const checked = toggleState.value;
44
44
  return (react_1.default.createElement(exports.ToggleWrapper, { "data-component-name": "Markdoc/CodeWalkthrough/CodeToggle" },
45
45
  react_1.default.createElement(ToggleContentWrapper, null,
46
46
  react_1.default.createElement(exports.ToggleSubtitle, null,
47
- react_1.default.createElement(Switch_1.Switch, { value: checked, onChange: (newValue) => changeToggleState(id, newValue) }),
47
+ react_1.default.createElement(Switch_1.Switch, { value: checked, onChange: (newValue) => changeControlState(id, newValue) }),
48
48
  react_1.default.createElement("div", null, label)),
49
49
  description ? (react_1.default.createElement("div", null, description.map((paragraph, idx) => (react_1.default.createElement(react_1.default.Fragment, { key: idx }, paragraph))))) : null),
50
50
  checked ? children : null));
@@ -51,13 +51,13 @@ function CodeWalkthrough(_a) {
51
51
  // This is a workaround to prevent the observer from being recreated
52
52
  const [initialSteps] = (0, react_1.useState)([...steps]);
53
53
  const { controlsState, stepsState, files, downloadAssociatedFiles } = (0, hooks_1.useCodeWalkthrough)(initialSteps, attributes);
54
- const { activeFilters, getFilterState, changeFilterState } = controlsState;
54
+ const { activeFilters, getControlState, changeControlState } = controlsState;
55
55
  const { filtersElementRef } = stepsState;
56
56
  return (react_1.default.createElement(contexts_1.CodeWalkthroughStepsContext.Provider, { value: stepsState },
57
57
  react_1.default.createElement(contexts_1.CodeWalkthroughControlsStateContext.Provider, { value: controlsState },
58
58
  react_1.default.createElement(CodeWalkthroughWrapper, { className: "code-walkthrough", "data-component-name": "Markdoc/CodeWalkthrough/CodeWalkthrough" },
59
59
  react_1.default.createElement(DocsPanel, null,
60
- react_1.default.createElement(CodeFilters_1.CodeFilters, { filters: activeFilters, getFilterState: getFilterState, handleFilterSelect: changeFilterState, filtersElementRef: filtersElementRef }),
60
+ react_1.default.createElement(CodeFilters_1.CodeFilters, { filters: activeFilters, getFilterState: getControlState, handleFilterSelect: changeControlState, filtersElementRef: filtersElementRef }),
61
61
  react_1.default.createElement(ContentWrapper, null, children)),
62
62
  react_1.default.createElement(CodePanel_1.CodePanel, { files: files, downloadAssociatedFiles: downloadAssociatedFiles, preview: preview })))));
63
63
  }
@@ -34,12 +34,12 @@ const contexts_1 = require("../../../core/contexts");
34
34
  const DEBOUNCE_TIME = 500;
35
35
  function Input(props) {
36
36
  const { id, label, placeholder } = props;
37
- const { getInputState, changeInputState } = (0, react_1.useContext)(contexts_1.CodeWalkthroughControlsStateContext);
38
- const inputState = getInputState(id);
37
+ const { getControlState, changeControlState } = (0, react_1.useContext)(contexts_1.CodeWalkthroughControlsStateContext);
38
+ const inputState = getControlState(id);
39
39
  const [value, setValue] = (0, react_1.useState)((inputState === null || inputState === void 0 ? void 0 : inputState.value) || '');
40
40
  // eslint-disable-next-line react-hooks/exhaustive-deps
41
41
  const debouncedSave = (0, react_1.useCallback)((0, lodash_debounce_1.default)((id, value) => {
42
- changeInputState(id, value);
42
+ changeControlState(id, value);
43
43
  }, DEBOUNCE_TIME), []);
44
44
  if (!(inputState === null || inputState === void 0 ? void 0 : inputState.render)) {
45
45
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.48.1",
3
+ "version": "0.49.0",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -19,6 +19,7 @@
19
19
  "./src/": "./src/",
20
20
  "./core/*": "./lib/core/*/index.js",
21
21
  "./core/templates/*": "./lib/core/templates/*.js",
22
+ "./ext/*": "./lib/ext/*.js",
22
23
  "./components/*": "./lib/components/*.js",
23
24
  "./layouts/*": "./lib/layouts/*.js",
24
25
  "./icons/*": "./lib/icons/*.js",
@@ -86,7 +87,7 @@
86
87
  "timeago.js": "4.0.2",
87
88
  "i18next": "22.4.15",
88
89
  "nprogress": "0.2.0",
89
- "@redocly/config": "0.20.1"
90
+ "@redocly/config": "0.20.2"
90
91
  },
91
92
  "scripts": {
92
93
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -4,7 +4,7 @@ export const datePicker = css`
4
4
  --date-picker-nav-color: #000000;
5
5
  --date-picker-tile-bg-color: var(--color-blue-2);
6
6
  --date-picker-tile-color: var(--text-color-primary);
7
- --date-picker-tile-bg-color-hover: var(--color-blue-3)
7
+ --date-picker-tile-bg-color-hover: var(--color-blue-3);
8
8
  --date-picker-tile-color-hover: var(--text-color-primary);
9
9
  --date-picker-input-width: var(--spacing-md);
10
10
  --date-picker-invalid-input-bg-color: var(--bg-color-raised);
@@ -5,14 +5,8 @@ import type { WalkthroughControlsState } from '@redocly/theme/core/hooks';
5
5
  export const CodeWalkthroughControlsStateContext = createContext<WalkthroughControlsState>({
6
6
  activeFilters: [],
7
7
 
8
- getToggleState: () => null,
9
- changeToggleState: () => {},
10
-
11
- getInputState: () => null,
12
- changeInputState: () => {},
13
-
14
- getFilterState: () => null,
15
- changeFilterState: () => {},
8
+ getControlState: () => null,
9
+ changeControlState: () => {},
16
10
 
17
11
  getFileText: () => '',
18
12
  areConditionsMet: () => false,
@@ -7,14 +7,9 @@ import type {
7
7
  InputsMarkdocAttr,
8
8
  TogglesMarkdocAttr,
9
9
  CodeWalkthroughControls,
10
- TogglesState,
11
- InputsState,
12
- FiltersState,
13
10
  CodeWalkthroughFilterItem,
14
- ControlState,
15
- ControlTypeValue,
16
- ControlType,
17
11
  CodeWalkthroughConditionsObject,
12
+ CodeWalkthroughControlsState,
18
13
  } from '@redocly/config';
19
14
 
20
15
  import {
@@ -24,8 +19,6 @@ import {
24
19
  replaceInputsWithValue,
25
20
  } from '@redocly/theme/core/utils';
26
21
 
27
- export type getState<T extends ControlType> = (id: string) => Omit<ControlState<T>, 'type'> | null;
28
- export type changeState<T extends ControlType> = (id: string, value: ControlTypeValue<T>) => void;
29
22
  export type ActiveFilter = {
30
23
  id: string;
31
24
  label?: string;
@@ -34,15 +27,8 @@ export type ActiveFilter = {
34
27
 
35
28
  export type WalkthroughControlsState = {
36
29
  activeFilters: ActiveFilter[];
37
- /* Toggle State Controls */
38
- getToggleState: getState<'toggle'>;
39
- changeToggleState: changeState<'toggle'>;
40
- /* Input State Controls */
41
- getInputState: getState<'input'>;
42
- changeInputState: changeState<'input'>;
43
- /* Filter State Controls */
44
- getFilterState: getState<'filter'>;
45
- changeFilterState: changeState<'filter'>;
30
+ getControlState: (id: string) => { value: string | boolean; render: boolean } | null;
31
+ changeControlState: (id: string, value: string | boolean) => void;
46
32
  /* Utility */
47
33
  areConditionsMet: (conditions: CodeWalkthroughConditionsObject) => boolean;
48
34
  handleDownloadCode: (files: CodeWalkthroughFile[]) => Promise<void>;
@@ -66,8 +52,9 @@ export function useCodeWalkthroughControls(
66
52
  const navigate = useNavigate();
67
53
  const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
68
54
 
69
- const [togglesState, setTogglesState] = useState(() => {
70
- const initialState: TogglesState = {};
55
+ const [controlsState, setControlsState] = useState(() => {
56
+ const initialState: CodeWalkthroughControlsState = {};
57
+
71
58
  for (const [id, toggle] of Object.entries(toggles)) {
72
59
  initialState[id] = {
73
60
  ...toggle,
@@ -76,41 +63,6 @@ export function useCodeWalkthroughControls(
76
63
  value: enableDeepLink ? searchParams.get(id) === 'true' : false,
77
64
  };
78
65
  }
79
- return initialState;
80
- });
81
-
82
- const changeToggleState = (toggleId: string, checked: boolean) => {
83
- setTogglesState((prev) => {
84
- const toggle = prev[toggleId];
85
- if (toggle) {
86
- return {
87
- ...prev,
88
- [toggleId]: {
89
- ...toggle,
90
- value: checked,
91
- },
92
- };
93
- } else {
94
- return prev;
95
- }
96
- });
97
- };
98
-
99
- const getToggleState = (toggleId: string) => {
100
- const toggleState = togglesState[toggleId];
101
-
102
- if (toggleState) {
103
- return {
104
- render: toggleState.render,
105
- value: toggleState.value,
106
- };
107
- } else {
108
- return null;
109
- }
110
- };
111
-
112
- const [inputsState, setInputsState] = useState(() => {
113
- const initialState: InputsState = {};
114
66
 
115
67
  for (const [id, input] of Object.entries(inputs)) {
116
68
  initialState[id] = {
@@ -120,41 +72,6 @@ export function useCodeWalkthroughControls(
120
72
  value: enableDeepLink ? (searchParams.get(id) ?? input.value) : input.value,
121
73
  };
122
74
  }
123
- return initialState;
124
- });
125
-
126
- const changeInputState = (inputId: string, value: string) => {
127
- setInputsState((prev) => {
128
- const input = prev[inputId];
129
- if (input) {
130
- return {
131
- ...prev,
132
- [inputId]: {
133
- ...input,
134
- value,
135
- },
136
- };
137
- } else {
138
- return prev;
139
- }
140
- });
141
- };
142
-
143
- const getInputState = (inputId: string) => {
144
- const inputState = inputsState[inputId];
145
-
146
- if (inputState) {
147
- return {
148
- render: inputState.render,
149
- value: inputState.value,
150
- };
151
- } else {
152
- return null;
153
- }
154
- };
155
-
156
- const [filtersState, setFiltersState] = useState(() => {
157
- const initialState: FiltersState = {};
158
75
 
159
76
  for (const [id, filter] of Object.entries(filters)) {
160
77
  const defaultValue = filter?.items?.[0]?.value || '';
@@ -169,56 +86,86 @@ export function useCodeWalkthroughControls(
169
86
  return initialState;
170
87
  });
171
88
 
172
- const changeFilterState = (filterId: string, value: string) => {
173
- setFiltersState((prev) => {
174
- const filter = prev[filterId];
175
- if (filter) {
176
- return {
177
- ...prev,
178
- [filterId]: {
179
- ...filter,
180
- value,
181
- },
182
- };
183
- } else {
89
+ const changeControlState = (id: string, value: string | boolean) => {
90
+ setControlsState((prev) => {
91
+ const control = prev[id];
92
+ if (!control) {
93
+ console.error(`Control with id "${id}" not found.`);
184
94
  return prev;
185
95
  }
96
+
97
+ switch (control.type) {
98
+ case 'input':
99
+ if (typeof value !== 'string') {
100
+ console.error(
101
+ `Invalid value type for input "${id}". Input control type requires a string value.`,
102
+ );
103
+ return prev;
104
+ }
105
+ break;
106
+
107
+ case 'toggle':
108
+ if (typeof value !== 'boolean') {
109
+ console.error(
110
+ `Invalid value type for toggle "${id}". Toggle control type requires a boolean value.`,
111
+ );
112
+ return prev;
113
+ }
114
+ break;
115
+
116
+ case 'filter':
117
+ if (typeof value !== 'string') {
118
+ console.error(
119
+ `Invalid value type for filter "${id}". Filter control type requires a string value.`,
120
+ );
121
+ return prev;
122
+ }
123
+ break;
124
+
125
+ default:
126
+ console.error(
127
+ `Invalid control type "${(control as { type: string })?.type}" for control "${id}". Allowed types are "toggle", "input", or "filter".`,
128
+ );
129
+ return prev;
130
+ }
131
+
132
+ return {
133
+ ...prev,
134
+ [id]: {
135
+ ...control,
136
+ value,
137
+ },
138
+ } as CodeWalkthroughControlsState;
186
139
  });
187
140
  };
188
141
 
189
- const getFilterState = (filterId: string) => {
190
- const filterState = filtersState[filterId];
142
+ const getControlState = (id: string) => {
143
+ const controlState = controlsState[id];
191
144
 
192
- if (filterState) {
145
+ if (controlState) {
193
146
  return {
194
- render: filterState.render,
195
- value: filterState.value,
147
+ render: controlState.render,
148
+ value: controlState.value,
196
149
  };
197
150
  } else {
198
151
  return null;
199
152
  }
200
153
  };
201
154
 
202
- const state = {
203
- ...filtersState,
204
- ...togglesState,
205
- ...inputsState,
206
- };
207
-
208
155
  const walkthroughContext = useMemo(() => {
209
156
  const areConditionsMet = (conditions: CodeWalkthroughConditionsObject) =>
210
- matchCodeWalkthroughConditions(conditions, state);
157
+ matchCodeWalkthroughConditions(conditions, controlsState);
211
158
 
212
- for (const [id, element] of Object.entries(state)) {
213
- if (element && !areConditionsMet(element)) {
214
- state[id].render = false;
215
- state[id].value = defaultControlsValues[element.type];
159
+ for (const [id, control] of Object.entries(controlsState)) {
160
+ if (control && !areConditionsMet(control)) {
161
+ controlsState[id].render = false;
162
+ controlsState[id].value = defaultControlsValues[control.type];
216
163
  }
217
164
  }
218
165
 
219
166
  const activeFilters = [];
220
167
  for (const [id, filter] of Object.entries(filters)) {
221
- if (!filtersState[id].render) {
168
+ if (!controlsState[id].render) {
222
169
  continue;
223
170
  }
224
171
 
@@ -228,18 +175,18 @@ export function useCodeWalkthroughControls(
228
175
  const activeItems = items.filter((item) => areConditionsMet(item));
229
176
 
230
177
  if (activeItems.length === 0) {
231
- filtersState[id].render = false;
232
- filtersState[id].value = defaultControlsValues['filter'];
178
+ controlsState[id].render = false;
179
+ controlsState[id].value = defaultControlsValues['filter'];
233
180
  continue;
234
181
  }
235
182
 
236
- const currentValue = filtersState[id].value;
183
+ const currentValue = controlsState[id].value;
237
184
  if (currentValue) {
238
185
  const isValueInActiveItems =
239
186
  activeItems.findIndex(({ value }) => value === currentValue) !== -1;
240
- filtersState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
187
+ controlsState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
241
188
  } else {
242
- filtersState[id].value = activeItems[0].value;
189
+ controlsState[id].value = activeItems[0].value;
243
190
  }
244
191
 
245
192
  activeFilters.push({
@@ -249,11 +196,15 @@ export function useCodeWalkthroughControls(
249
196
  });
250
197
  }
251
198
 
199
+ const inputsState = Object.fromEntries(
200
+ Object.entries(controlsState).filter(([_, controlState]) => controlState.type === 'input'),
201
+ ) as Record<string, { value: string }>;
202
+
252
203
  const handleDownloadCode = (files: CodeWalkthroughFile[]) =>
253
- downloadCodeWalkthrough(files, state, inputsState);
204
+ downloadCodeWalkthrough(files, controlsState, inputsState);
254
205
 
255
206
  const getFileText = (file: CodeWalkthroughFile) =>
256
- getCodeWalkthroughFileText(file, state, inputsState);
207
+ getCodeWalkthroughFileText(file, controlsState, inputsState);
257
208
 
258
209
  const populateInputsWithValue = (node: string) => replaceInputsWithValue(node, inputsState);
259
210
 
@@ -264,9 +215,7 @@ export function useCodeWalkthroughControls(
264
215
  getFileText,
265
216
  populateInputsWithValue,
266
217
  };
267
- // Ignore state in dependency array as it's simply a combination of toggles, filters and inputs.
268
- // eslint-disable-next-line react-hooks/exhaustive-deps
269
- }, [filters, filtersState, togglesState, inputsState]);
218
+ }, [filters, controlsState]);
270
219
 
271
220
  /**
272
221
  * Update the URL search params with the current state of the filters and inputs
@@ -278,7 +227,7 @@ export function useCodeWalkthroughControls(
278
227
 
279
228
  const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
280
229
 
281
- for (const [id, { value }] of Object.entries(state)) {
230
+ for (const [id, { value }] of Object.entries(controlsState)) {
282
231
  if (value) {
283
232
  newSearchParams.set(id, value.toString());
284
233
  } else {
@@ -291,18 +240,11 @@ export function useCodeWalkthroughControls(
291
240
  navigate({ search: newSearch });
292
241
  // Ignore searchParams in dependency array to avoid infinite re-renders
293
242
  // eslint-disable-next-line react-hooks/exhaustive-deps
294
- }, [filters, filtersState, togglesState, inputsState, navigate, location, state]);
243
+ }, [filters, controlsState, navigate, location]);
295
244
 
296
245
  return {
297
- getInputState,
298
- changeInputState,
299
-
300
- getToggleState,
301
- changeToggleState,
302
-
303
- getFilterState,
304
- changeFilterState,
305
-
246
+ changeControlState,
247
+ getControlState,
306
248
  ...walkthroughContext,
307
249
  };
308
250
  }
@@ -1019,6 +1019,24 @@ const error = css`
1019
1019
  --detailed-error-message-width: 100%;
1020
1020
  --detailed-error-message-font-size: var(--font-size-base);
1021
1021
  --detailed-error-message-font-family: var(--code-block-controls-font-family);
1022
+
1023
+ --compilation-error-description-padding: 0 0 var(--spacing-base);
1024
+ --compilation-error-codeframe-margin: var(--spacing-xs) 0 0 0;
1025
+ --compilation-error-codeframe-padding: var(--spacing-xs);
1026
+ --compilation-error-block-padding: var(--spacing-base);
1027
+ --compilation-error-block-margin: var(--spacing-xl) 0;
1028
+ --compilation-error-title-font-size: var(--font-size-xl);
1029
+ --compilation-error-header-font-size: var(--font-size-lg);
1030
+ --compilation-error-text-font-size: var(--font-size-base);
1031
+ --compilation-error-header-padding: var(--spacing-sm);
1032
+ --compilation-error-container-margin: var(--spacing-xxl) auto;
1033
+ --compilation-error-container-padding: var(--spacing-lg);
1034
+ --compilation-error-page-max-width: 800px;
1035
+ --compilation-error-font-family: var(--font-family-base);
1036
+ --compilation-error-fix-instruction-margin: var(--spacing-sm) 0 0 0;
1037
+ --compilation-error-highlighted-text-padding: 0 var(--spacing-xxs);
1038
+ --compilation-error-divider-margin: var(--spacing-base) 0;
1039
+ --compilation-error-file-header-margin: 0 0 var(--spacing-xs) 0;
1022
1040
  `;
1023
1041
 
1024
1042
  const modal = css`
@@ -6,6 +6,7 @@ export type OperationParameter = {
6
6
  description: string | string[];
7
7
  place: string;
8
8
  path?: string[];
9
+ deepLink?: string;
9
10
  };
10
11
 
11
12
  export type SearchDocument = {
@@ -1,7 +1,7 @@
1
1
  import { saveAs } from 'file-saver';
2
2
  import JSZip from 'jszip';
3
3
 
4
- import type { CodeWalkthroughFile, InputsState } from '@redocly/config';
4
+ import type { CodeWalkthroughFile } from '@redocly/config';
5
5
 
6
6
  import {
7
7
  findClosestCommonDirectory,
@@ -15,7 +15,7 @@ JSZip.support.nodebuffer = false;
15
15
  export async function downloadCodeWalkthrough(
16
16
  files: CodeWalkthroughFile[],
17
17
  state: Record<string, { value: string | boolean }>,
18
- inputsState: InputsState,
18
+ inputsState: Record<string, { value: string }>,
19
19
  ) {
20
20
  const zip = new JSZip();
21
21
 
@@ -1,11 +1,11 @@
1
- import type { CodeWalkthroughFile, CodeWalkthroughNode, InputsState } from '@redocly/config';
1
+ import type { CodeWalkthroughFile, CodeWalkthroughNode } from '@redocly/config';
2
2
 
3
3
  import { matchCodeWalkthroughConditions, replaceInputsWithValue } from '@redocly/theme/core/utils';
4
4
 
5
5
  export function getCodeWalkthroughFileText(
6
6
  file: CodeWalkthroughFile,
7
7
  state: Record<string, { value: string | boolean }>,
8
- inputsState: InputsState,
8
+ inputsState: Record<string, { value: string }>,
9
9
  ) {
10
10
  const contentChunks: string[] =
11
11
  file?.content.flatMap((node) => getChunkText(node, state, inputsState)) ?? [];
@@ -16,7 +16,7 @@ export function getCodeWalkthroughFileText(
16
16
  function getChunkText(
17
17
  node: CodeWalkthroughNode,
18
18
  state: Record<string, { value: string | boolean }>,
19
- inputsState: InputsState,
19
+ inputsState: Record<string, { value: string }>,
20
20
  ): string[] {
21
21
  if (typeof node === 'string') {
22
22
  const replacedNode = replaceInputsWithValue(node, inputsState);
@@ -0,0 +1,39 @@
1
+ export type SecurityDetails = {
2
+ password?: string;
3
+ username?: string;
4
+ token?: {
5
+ token_type?: string;
6
+ access_token: string;
7
+ };
8
+ client_id?: string;
9
+ client_secret?: string;
10
+ scopes?: string[];
11
+ };
12
+
13
+ export type ConfigureRequestValues = {
14
+ headers?: Record<string, string>;
15
+ body?: Record<string, any>;
16
+ query?: Record<string, string>;
17
+ path?: Record<string, string>;
18
+ cookie?: Record<string, string>;
19
+ security?: SecurityDetails;
20
+ };
21
+
22
+ type Configure = {
23
+ requestValues?: ConfigureRequestValues;
24
+ };
25
+
26
+ export function configure(): Configure {
27
+ // const exampleRequestValues: ConfigureRequestValues = {
28
+ // body: {
29
+ // name: 'John Doe',
30
+ // },
31
+ // security: {
32
+ // token: {
33
+ // access_token: 'John Doe token',
34
+ // },
35
+ // },
36
+ // };
37
+ // return { requestValues: exampleRequestValues } satisfies Configure;
38
+ return {} satisfies Configure;
39
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from '@redocly/theme/core';
2
2
  export * from '@redocly/theme/config';
3
+ export * from '@redocly/theme/ext/configure';
3
4
 
4
5
  /* Components */
5
6
  export * from '@redocly/theme/components/Button/Button';
@@ -21,9 +21,33 @@ export function CodeContainer({
21
21
  // useEffect executed before DOM is updated due to re-render called before "painting" phase.
22
22
  setTimeout(() => {
23
23
  if (compRef.current) {
24
- const element = compRef.current.querySelector('span.line.highlighted');
25
- if (element) {
26
- element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
24
+ const highlightedLines = Array.from(
25
+ compRef.current.querySelectorAll('span.line.highlighted'),
26
+ );
27
+ const { bottom: panelBottom, top: panelTop } = compRef.current.getBoundingClientRect();
28
+
29
+ const linesBelow: Element[] = [];
30
+ const linesAbove: Element[] = [];
31
+ for (const highlightedLine of highlightedLines) {
32
+ const { bottom: lineBottom, top: lineTop } = highlightedLine.getBoundingClientRect();
33
+ const below = lineBottom > panelBottom;
34
+ const above = lineTop < panelTop;
35
+
36
+ if (below) {
37
+ linesBelow.push(highlightedLine);
38
+ } else if (above) {
39
+ linesAbove.push(highlightedLine);
40
+ }
41
+ }
42
+
43
+ if ((linesBelow.length && linesAbove.length) || linesAbove.length) {
44
+ linesAbove[0].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
45
+ } else if (linesBelow.length) {
46
+ linesBelow[linesBelow.length - 1].scrollIntoView({
47
+ behavior: 'smooth',
48
+ block: 'nearest',
49
+ inline: 'start',
50
+ });
27
51
  }
28
52
  }
29
53
  }, 200);
@@ -5,9 +5,10 @@ import type { CodeWalkthroughFilter } from '@redocly/config';
5
5
 
6
6
  import { Tag, ContentWrapper as TagContentWrapper } from '@redocly/theme/components/Tag/Tag';
7
7
 
8
+ export type GetFilterState = (groupId: string) => { value: string; render: boolean } | null;
8
9
  export type CodeFilterProps = {
9
10
  filters: CodeWalkthroughFilter[];
10
- getFilterState: (groupId: string) => { value: string; render: boolean } | null;
11
+ getFilterState: GetFilterState;
11
12
  handleFilterSelect: (groupId: string, id: string) => void;
12
13
  filtersElementRef?: React.RefObject<HTMLDivElement>;
13
14
  };
@@ -35,7 +35,7 @@ export function CodeStep({
35
35
  lockObserver.current = true;
36
36
 
37
37
  if (compRef.current) {
38
- compRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
38
+ compRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
39
39
  }
40
40
 
41
41
  setActiveStep(id);
@@ -13,11 +13,11 @@ export type CodeToggleProps = React.PropsWithChildren<{
13
13
 
14
14
  export function CodeToggle(props: CodeToggleProps) {
15
15
  const { id, label, description, children } = props;
16
- const { getToggleState, changeToggleState } = useContext(CodeWalkthroughControlsStateContext);
16
+ const { getControlState, changeControlState } = useContext(CodeWalkthroughControlsStateContext);
17
17
 
18
- const toggleState = getToggleState(id);
18
+ const toggleState = getControlState(id);
19
19
 
20
- if (!(toggleState && toggleState.render)) {
20
+ if (!(toggleState && toggleState.render && typeof toggleState.value === 'boolean')) {
21
21
  return null;
22
22
  }
23
23
  const checked = toggleState.value;
@@ -26,7 +26,7 @@ export function CodeToggle(props: CodeToggleProps) {
26
26
  <ToggleWrapper data-component-name="Markdoc/CodeWalkthrough/CodeToggle">
27
27
  <ToggleContentWrapper>
28
28
  <ToggleSubtitle>
29
- <Switch value={checked} onChange={(newValue) => changeToggleState(id, newValue)} />
29
+ <Switch value={checked} onChange={(newValue) => changeControlState(id, newValue)} />
30
30
  <div>{label}</div>
31
31
  </ToggleSubtitle>
32
32
  {description ? (
@@ -9,7 +9,10 @@ import {
9
9
  } from '@redocly/theme/core/contexts';
10
10
  import { CodePanel } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodePanel';
11
11
  import { useCodeWalkthrough } from '@redocly/theme/core/hooks';
12
- import { CodeFilters } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodeFilters';
12
+ import {
13
+ CodeFilters,
14
+ GetFilterState,
15
+ } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodeFilters';
13
16
  import { StepWrapper } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodeStep';
14
17
 
15
18
  export type CodeWalkthroughProps = PropsWithChildren<CodeWalkthroughAttr>;
@@ -22,7 +25,7 @@ export function CodeWalkthrough({ children, steps, preview, ...attributes }: Cod
22
25
  initialSteps,
23
26
  attributes,
24
27
  );
25
- const { activeFilters, getFilterState, changeFilterState } = controlsState;
28
+ const { activeFilters, getControlState, changeControlState } = controlsState;
26
29
  const { filtersElementRef } = stepsState;
27
30
 
28
31
  return (
@@ -35,8 +38,8 @@ export function CodeWalkthrough({ children, steps, preview, ...attributes }: Cod
35
38
  <DocsPanel>
36
39
  <CodeFilters
37
40
  filters={activeFilters}
38
- getFilterState={getFilterState}
39
- handleFilterSelect={changeFilterState}
41
+ getFilterState={getControlState as GetFilterState}
42
+ handleFilterSelect={changeControlState}
40
43
  filtersElementRef={filtersElementRef}
41
44
  />
42
45
  <ContentWrapper>{children}</ContentWrapper>
@@ -15,16 +15,16 @@ const DEBOUNCE_TIME = 500;
15
15
 
16
16
  export function Input(props: InputProps) {
17
17
  const { id, label, placeholder } = props;
18
- const { getInputState, changeInputState } = useContext(CodeWalkthroughControlsStateContext);
18
+ const { getControlState, changeControlState } = useContext(CodeWalkthroughControlsStateContext);
19
19
 
20
- const inputState = getInputState(id);
20
+ const inputState = getControlState(id);
21
21
 
22
22
  const [value, setValue] = useState(inputState?.value || '');
23
23
 
24
24
  // eslint-disable-next-line react-hooks/exhaustive-deps
25
25
  const debouncedSave = useCallback(
26
26
  debounce((id: string, value: string) => {
27
- changeInputState(id, value);
27
+ changeControlState(id, value);
28
28
  }, DEBOUNCE_TIME),
29
29
  [],
30
30
  );
@@ -45,7 +45,7 @@ export function Input(props: InputProps) {
45
45
  {label && <Label>{label}</Label>}
46
46
  <StyledInput
47
47
  id={id}
48
- value={value}
48
+ value={value as string}
49
49
  onChange={handleChange}
50
50
  aria-label={label}
51
51
  placeholder={placeholder}