@redocly/theme 0.65.0-next.4 → 0.65.0-next.5

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.
@@ -14,6 +14,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
14
14
  extraClass?: string;
15
15
  to?: string;
16
16
  external?: boolean;
17
+ languageInsensitive?: boolean;
17
18
  icon?: JSX.Element;
18
19
  iconPosition?: 'left' | 'right';
19
20
  title?: string;
@@ -32,6 +32,17 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __rest = (this && this.__rest) || function (s, e) {
36
+ var t = {};
37
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
38
+ t[p] = s[p];
39
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
40
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
41
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
42
+ t[p[i]] = s[p[i]];
43
+ }
44
+ return t;
45
+ };
35
46
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
47
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
48
  };
@@ -166,13 +177,14 @@ const StyledButton = styled_components_1.default.button.attrs((props) => ({
166
177
  `}
167
178
  `;
168
179
  const ButtonComponent = (props) => {
180
+ const { languageInsensitive } = props, buttonProps = __rest(props, ["languageInsensitive"]);
169
181
  const tabIndex = 'tabIndex' in props ? props.tabIndex : props.to ? -1 : undefined;
170
- const button = (react_1.default.createElement(StyledButton, Object.assign({ "data-component-name": "Button/Button" }, props, { iconOnly: !props.children && props.icon !== null, tabIndex: tabIndex }),
182
+ const button = (react_1.default.createElement(StyledButton, Object.assign({ "data-component-name": "Button/Button" }, buttonProps, { iconOnly: !props.children && props.icon !== null, tabIndex: tabIndex }),
171
183
  props.icon && props.iconPosition !== 'right' && props.icon,
172
184
  props.children,
173
185
  props.icon && props.iconPosition === 'right' && props.icon));
174
186
  if (props.to) {
175
- return (react_1.default.createElement(StyledButtonLink, { to: props.to, external: props.external, onClick: props.onClick }, button));
187
+ return (react_1.default.createElement(StyledButtonLink, { to: props.to, external: props.external, languageInsensitive: languageInsensitive, onClick: props.onClick }, button));
176
188
  }
177
189
  else {
178
190
  return button;
@@ -62,13 +62,25 @@ function DropdownMenuItem(_a) {
62
62
  }
63
63
  };
64
64
  className = className || '' + (active ? ' active' : '');
65
+ const sharedProps = {
66
+ $active: active,
67
+ $dangerous: dangerous,
68
+ $disabled: disabled,
69
+ $separator: separator,
70
+ $separatorLine: separatorLine,
71
+ 'data-component-name': 'Dropdown/DropdownMenuItem',
72
+ className,
73
+ onClick: handleClick,
74
+ role,
75
+ style,
76
+ };
65
77
  if (to) {
66
- return (react_1.default.createElement(DropdownMenuItemWrapper, Object.assign({ as: Link_1.Link, "data-component-name": "Dropdown/DropdownMenuItem", className: className, "$separatorLine": separatorLine, to: to, style: style, role: role }, dataAttributes, otherProps),
78
+ return (react_1.default.createElement(DropdownMenuItemWrapper, Object.assign({ as: Link_1.Link, to: to }, sharedProps, dataAttributes, otherProps),
67
79
  prefix,
68
80
  children,
69
81
  suffix));
70
82
  }
71
- return (react_1.default.createElement(DropdownMenuItemWrapper, Object.assign({ "data-component-name": "Dropdown/DropdownMenuItem", className: className, role: role, style: style }, dataAttributes, { onClick: handleClick, onKeyDown: handleKeyDown, tabIndex: onAction ? 0 : -1, active: active, disabled: disabled, separator: separator, dangerous: dangerous, "$separatorLine": separatorLine }),
83
+ return (react_1.default.createElement(DropdownMenuItemWrapper, Object.assign({}, sharedProps, dataAttributes, { onKeyDown: handleKeyDown, tabIndex: onAction ? 0 : -1 }),
72
84
  prefix,
73
85
  children || content,
74
86
  suffix));
@@ -109,7 +121,7 @@ const DropdownMenuItemWrapper = styled_components_1.default.li `
109
121
  color: var(--dropdown-menu-item-color-hover);
110
122
  }
111
123
 
112
- ${({ separator }) => separator &&
124
+ ${({ $separator }) => $separator &&
113
125
  (0, styled_components_1.css) `
114
126
  cursor: default;
115
127
  pointer-events: none;
@@ -119,7 +131,7 @@ const DropdownMenuItemWrapper = styled_components_1.default.li `
119
131
  --dropdown-menu-item-bg-color-hover: var(--dropdown-menu-item-bg-color);
120
132
  `}
121
133
 
122
- ${({ active }) => active &&
134
+ ${({ $active }) => $active &&
123
135
  (0, styled_components_1.css) `
124
136
  background-color: var(--dropdown-menu-item-bg-color-active);
125
137
  color: var(--dropdown-menu-item-color-active);
@@ -128,7 +140,7 @@ const DropdownMenuItemWrapper = styled_components_1.default.li `
128
140
  }
129
141
  `}
130
142
 
131
- ${({ disabled }) => disabled &&
143
+ ${({ $disabled }) => $disabled &&
132
144
  (0, styled_components_1.css) `
133
145
  cursor: default;
134
146
  pointer-events: none;
@@ -152,7 +164,7 @@ const DropdownMenuItemWrapper = styled_components_1.default.li `
152
164
  }
153
165
  `}
154
166
 
155
- ${({ dangerous }) => dangerous &&
167
+ ${({ $dangerous }) => $dangerous &&
156
168
  (0, styled_components_1.css) `
157
169
  &:hover,
158
170
  & {
@@ -14,7 +14,7 @@ const Button_1 = require("../../components/Button/Button");
14
14
  const Dropdown_1 = require("../../components/Dropdown/Dropdown");
15
15
  const CheckmarkIcon_1 = require("../../icons/CheckmarkIcon/CheckmarkIcon");
16
16
  function LanguagePicker(props) {
17
- const { currentLocale, locales, setLocale } = (0, hooks_1.useLanguagePicker)();
17
+ const { currentLocale, locales, getLocaleUrl } = (0, hooks_1.useLanguagePicker)();
18
18
  const { useTelemetry } = (0, hooks_1.useThemeHooks)();
19
19
  const telemetry = useTelemetry();
20
20
  if (locales.length < 2 || !currentLocale) {
@@ -23,8 +23,9 @@ function LanguagePicker(props) {
23
23
  const languagePickerButton = (react_1.default.createElement(Button_1.Button, { icon: react_1.default.createElement(GlobalOutlinedIcon_1.GlobalOutlinedIcon, { color: "--button-content-color" }), variant: "secondary", size: "medium" }));
24
24
  const languageItems = locales.map((locale) => ({
25
25
  content: locale.name || locale.code || '',
26
+ to: getLocaleUrl(locale.code),
27
+ languageInsensitive: true,
26
28
  onAction: () => {
27
- setLocale(locale.code);
28
29
  props.onChangeLanguage(locale.code);
29
30
  telemetry.sendLanguagePickerLocaleChangedMessage([{ object: 'locale', locale: locale.code }]);
30
31
  },
@@ -13,6 +13,6 @@ function LoginButton({ href, className, variant = 'primary', size = 'medium', la
13
13
  const telemetry = useTelemetry();
14
14
  const buttonLabel = label || translate(labelTranslationKey, 'Login');
15
15
  return (react_1.default.createElement("span", { "data-component-name": componentName, className: className },
16
- react_1.default.createElement(Button_1.Button, { "data-translation-key": label ? undefined : labelTranslationKey, to: href, onClick: () => telemetry.sendLoginButtonClickedMessage(), "data-testid": "login-btn", extraClass: className, variant: variant, size: size }, buttonLabel)));
16
+ react_1.default.createElement(Button_1.Button, { "data-translation-key": label ? undefined : labelTranslationKey, to: href, languageInsensitive: true, onClick: () => telemetry.sendLoginButtonClickedMessage(), "data-testid": "login-btn", extraClass: className, variant: variant, size: size }, buttonLabel)));
17
17
  }
18
18
  //# sourceMappingURL=LoginButton.js.map
@@ -10,15 +10,4 @@ type Params = {
10
10
  root: React.RefObject<HTMLDivElement | null>;
11
11
  };
12
12
  export declare function useCodeWalkthroughSteps({ steps, enableDeepLink, root, }: Params): WalkthroughStepsState;
13
- type StepsGroup = {
14
- freeSpace: number;
15
- usedSpace: number;
16
- offset: number;
17
- steps: {
18
- offset: number;
19
- height: number;
20
- ref?: HTMLElement;
21
- }[];
22
- };
23
- export declare function getGroupMarkers(group: StepsGroup): number[];
24
13
  export {};
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useCodeWalkthroughSteps = useCodeWalkthroughSteps;
4
- exports.getGroupMarkers = getGroupMarkers;
5
4
  const react_1 = require("react");
6
5
  const react_router_dom_1 = require("react-router-dom");
7
6
  const js_utils_1 = require("../../utils/js-utils");
@@ -97,7 +96,7 @@ function useCodeWalkthroughSteps({ steps, enableDeepLink, root, }) {
97
96
  return;
98
97
  }
99
98
  step.compRef = element;
100
- setVisibleSteps((prevSteps) => (0, js_utils_1.insertAt)(prevSteps, step.index, step));
99
+ setVisibleSteps((prevSteps) => insertVisibleStepInOrder(prevSteps, step));
101
100
  }, [stepsMap]);
102
101
  const removeStep = (0, react_1.useCallback)((stepId) => {
103
102
  const step = stepsMap.get(stepId);
@@ -315,4 +314,10 @@ function getNormalizedNumber(options) {
315
314
  const { min, max, value } = options;
316
315
  return (value - min) / (max - min);
317
316
  }
317
+ function insertVisibleStepInOrder(visible, step) {
318
+ const others = visible.filter((s) => s.id !== step.id);
319
+ const laterAt = others.findIndex((s) => s.index > step.index);
320
+ const at = laterAt === -1 ? others.length : laterAt;
321
+ return (0, js_utils_1.insertAt)(others, at, step);
322
+ }
318
323
  //# sourceMappingURL=use-code-walkthrough-steps.js.map
@@ -1,6 +1,11 @@
1
1
  import type { L10nConfig } from '../types/hooks';
2
- export declare function useLanguagePicker(): {
2
+ export type UseLanguagePickerResult = {
3
3
  currentLocale: L10nConfig['locales'][number] | undefined;
4
4
  locales: L10nConfig['locales'];
5
+ getLocaleUrl: (value: string) => string;
6
+ /**
7
+ * @deprecated Use `getLocaleUrl` to build the URL for the target locale, then pass it to `<Link>`.
8
+ */
5
9
  setLocale: (value: string) => void;
6
10
  };
11
+ export declare function useLanguagePicker(): UseLanguagePickerResult;
@@ -9,15 +9,23 @@ function useLanguagePicker() {
9
9
  const navigate = (0, react_router_dom_1.useNavigate)();
10
10
  const loadAndNavigate = useLoadAndNavigate();
11
11
  const { currentLocale, locales, defaultLocale } = useL10nConfig();
12
+ const location = (0, react_router_dom_1.useLocation)();
12
13
  const locale = locales.find((l) => l.code === currentLocale);
14
+ function getLocaleUrl(value) {
15
+ let newLangPathname = (0, urls_1.getPathnameForLocale)((0, urls_1.withoutPathPrefix)(location.pathname), defaultLocale, value, locales);
16
+ if (location.search) {
17
+ newLangPathname += location.search;
18
+ }
19
+ return (0, urls_1.addLeadingSlash)(newLangPathname);
20
+ }
13
21
  function setLocale(value) {
14
- const newLangPathname = (0, urls_1.withPathPrefix)((0, urls_1.getPathnameForLocale)((0, urls_1.withoutPathPrefix)(location.pathname), defaultLocale, value, locales));
15
- const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
22
+ const newUrlWithLanguage = (0, urls_1.withPathPrefix)(getLocaleUrl(value));
16
23
  loadAndNavigate({ navigate, to: newUrlWithLanguage });
17
24
  }
18
25
  return {
19
26
  currentLocale: locale,
20
27
  locales,
28
+ getLocaleUrl,
21
29
  setLocale,
22
30
  };
23
31
  }
@@ -40,9 +40,11 @@ exports.StepWrapper = void 0;
40
40
  exports.CodeStep = CodeStep;
41
41
  const react_1 = __importStar(require("react"));
42
42
  const styled_components_1 = __importDefault(require("styled-components"));
43
+ const react_router_dom_1 = require("react-router-dom");
43
44
  const Marker_1 = require("../../../components/Marker/Marker");
44
45
  const contexts_1 = require("../../../core/contexts");
45
46
  function CodeStep({ id, heading, when, unless, children, }) {
47
+ const location = (0, react_router_dom_1.useLocation)();
46
48
  const compRef = (0, react_1.useRef)(null);
47
49
  const markerRef = (0, react_1.useRef)(null);
48
50
  const { areConditionsMet } = (0, react_1.useContext)(contexts_1.CodeWalkthroughControlsStateContext);
@@ -66,15 +68,14 @@ function CodeStep({ id, heading, when, unless, children, }) {
66
68
  const handleRegisterMarker = (0, react_1.useCallback)((element) => registerMarker(id, element), [registerMarker, id]);
67
69
  const handleRemoveMarker = (0, react_1.useCallback)((element) => removeMarker(id, element), [removeMarker, id]);
68
70
  (0, react_1.useEffect)(() => {
69
- // If the step is active during first render, scroll to it
70
- // This is to ensure that the step is visible when the page is loaded
71
- if (isActive) {
72
- // Ensure scrollMarginTop calculated before step being scrolled.
73
- setTimeout(handleActivateStep, 5);
74
- }
71
+ // If the step is active during navigation or first render, scroll to it
72
+ if (!isActive)
73
+ return;
74
+ const timer = setTimeout(handleActivateStep, 5);
75
+ return () => clearTimeout(timer);
75
76
  // Ignore dependency array because we only need to run this once
76
77
  // eslint-disable-next-line react-hooks/exhaustive-deps
77
- }, []);
78
+ }, [location.pathname]);
78
79
  (0, react_1.useEffect)(() => {
79
80
  var _a, _b;
80
81
  const currentCompRef = compRef.current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.65.0-next.4",
3
+ "version": "0.65.0-next.5",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -63,7 +63,7 @@
63
63
  "vitest": "4.0.10",
64
64
  "vitest-when": "0.6.2",
65
65
  "webpack": "5.105.2",
66
- "@redocly/realm-asyncapi-sdk": "0.11.0-next.2"
66
+ "@redocly/realm-asyncapi-sdk": "0.11.0-next.3"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -28,6 +28,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
28
28
  extraClass?: string;
29
29
  to?: string;
30
30
  external?: boolean;
31
+ languageInsensitive?: boolean;
31
32
  icon?: JSX.Element;
32
33
  iconPosition?: 'left' | 'right';
33
34
  title?: string;
@@ -175,12 +176,13 @@ const StyledButton = styled.button.attrs((props: ButtonProps) => ({
175
176
  `;
176
177
 
177
178
  const ButtonComponent: React.FC<ButtonProps> = (props) => {
179
+ const { languageInsensitive, ...buttonProps } = props;
178
180
  const tabIndex = 'tabIndex' in props ? props.tabIndex : props.to ? -1 : undefined;
179
181
 
180
182
  const button = (
181
183
  <StyledButton
182
184
  data-component-name="Button/Button"
183
- {...props}
185
+ {...buttonProps}
184
186
  iconOnly={!props.children && props.icon !== null}
185
187
  tabIndex={tabIndex}
186
188
  >
@@ -192,7 +194,12 @@ const ButtonComponent: React.FC<ButtonProps> = (props) => {
192
194
 
193
195
  if (props.to) {
194
196
  return (
195
- <StyledButtonLink to={props.to} external={props.external} onClick={props.onClick}>
197
+ <StyledButtonLink
198
+ to={props.to}
199
+ external={props.external}
200
+ languageInsensitive={languageInsensitive}
201
+ onClick={props.onClick}
202
+ >
196
203
  {button}
197
204
  </StyledButtonLink>
198
205
  );
@@ -55,16 +55,25 @@ export function DropdownMenuItem({
55
55
 
56
56
  className = className || '' + (active ? ' active' : '');
57
57
 
58
+ const sharedProps = {
59
+ $active: active,
60
+ $dangerous: dangerous,
61
+ $disabled: disabled,
62
+ $separator: separator,
63
+ $separatorLine: separatorLine,
64
+ 'data-component-name': 'Dropdown/DropdownMenuItem',
65
+ className,
66
+ onClick: handleClick,
67
+ role,
68
+ style,
69
+ };
70
+
58
71
  if (to) {
59
72
  return (
60
73
  <DropdownMenuItemWrapper
61
74
  as={Link}
62
- data-component-name="Dropdown/DropdownMenuItem"
63
- className={className}
64
- $separatorLine={separatorLine}
65
75
  to={to}
66
- style={style}
67
- role={role}
76
+ {...sharedProps}
68
77
  {...dataAttributes}
69
78
  {...otherProps}
70
79
  >
@@ -77,19 +86,10 @@ export function DropdownMenuItem({
77
86
 
78
87
  return (
79
88
  <DropdownMenuItemWrapper
80
- data-component-name="Dropdown/DropdownMenuItem"
81
- className={className}
82
- role={role}
83
- style={style}
89
+ {...sharedProps}
84
90
  {...dataAttributes}
85
- onClick={handleClick}
86
91
  onKeyDown={handleKeyDown}
87
92
  tabIndex={onAction ? 0 : -1}
88
- active={active}
89
- disabled={disabled}
90
- separator={separator}
91
- dangerous={dangerous}
92
- $separatorLine={separatorLine}
93
93
  >
94
94
  {prefix}
95
95
  {children || content}
@@ -100,10 +100,10 @@ export function DropdownMenuItem({
100
100
 
101
101
  const DropdownMenuItemWrapper = styled.li<{
102
102
  $separatorLine?: boolean;
103
- active?: boolean;
104
- disabled?: boolean;
105
- separator?: boolean;
106
- dangerous?: boolean;
103
+ $separator?: boolean;
104
+ $active?: boolean;
105
+ $disabled?: boolean;
106
+ $dangerous?: boolean;
107
107
  }>`
108
108
  display: flex;
109
109
  flex-direction: row;
@@ -140,8 +140,8 @@ const DropdownMenuItemWrapper = styled.li<{
140
140
  color: var(--dropdown-menu-item-color-hover);
141
141
  }
142
142
 
143
- ${({ separator }) =>
144
- separator &&
143
+ ${({ $separator }) =>
144
+ $separator &&
145
145
  css`
146
146
  cursor: default;
147
147
  pointer-events: none;
@@ -151,8 +151,8 @@ const DropdownMenuItemWrapper = styled.li<{
151
151
  --dropdown-menu-item-bg-color-hover: var(--dropdown-menu-item-bg-color);
152
152
  `}
153
153
 
154
- ${({ active }) =>
155
- active &&
154
+ ${({ $active }) =>
155
+ $active &&
156
156
  css`
157
157
  background-color: var(--dropdown-menu-item-bg-color-active);
158
158
  color: var(--dropdown-menu-item-color-active);
@@ -161,8 +161,8 @@ const DropdownMenuItemWrapper = styled.li<{
161
161
  }
162
162
  `}
163
163
 
164
- ${({ disabled }) =>
165
- disabled &&
164
+ ${({ $disabled }) =>
165
+ $disabled &&
166
166
  css`
167
167
  cursor: default;
168
168
  pointer-events: none;
@@ -187,8 +187,8 @@ const DropdownMenuItemWrapper = styled.li<{
187
187
  }
188
188
  `}
189
189
 
190
- ${({ dangerous }) =>
191
- dangerous &&
190
+ ${({ $dangerous }) =>
191
+ $dangerous &&
192
192
  css`
193
193
  &:hover,
194
194
  & {
@@ -19,7 +19,7 @@ export type LanguagePickerProps = {
19
19
  };
20
20
 
21
21
  export function LanguagePicker(props: LanguagePickerProps): JSX.Element | null {
22
- const { currentLocale, locales, setLocale } = useLanguagePicker();
22
+ const { currentLocale, locales, getLocaleUrl } = useLanguagePicker();
23
23
  const { useTelemetry } = useThemeHooks();
24
24
  const telemetry = useTelemetry();
25
25
 
@@ -37,8 +37,9 @@ export function LanguagePicker(props: LanguagePickerProps): JSX.Element | null {
37
37
 
38
38
  const languageItems = locales.map((locale) => ({
39
39
  content: locale.name || locale.code || '',
40
+ to: getLocaleUrl(locale.code),
41
+ languageInsensitive: true,
40
42
  onAction: () => {
41
- setLocale(locale.code);
42
43
  props.onChangeLanguage(locale.code);
43
44
  telemetry.sendLanguagePickerLocaleChangedMessage([{ object: 'locale', locale: locale.code }]);
44
45
  },
@@ -35,6 +35,7 @@ export function LoginButton({
35
35
  <Button
36
36
  data-translation-key={label ? undefined : labelTranslationKey}
37
37
  to={href}
38
+ languageInsensitive
38
39
  onClick={() => telemetry.sendLoginButtonClickedMessage()}
39
40
  data-testid="login-btn"
40
41
  extraClass={className}
@@ -153,7 +153,7 @@ export function useCodeWalkthroughSteps({
153
153
  }
154
154
 
155
155
  step.compRef = element;
156
- setVisibleSteps((prevSteps) => insertAt(prevSteps, step.index, step));
156
+ setVisibleSteps((prevSteps) => insertVisibleStepInOrder(prevSteps, step));
157
157
  },
158
158
  [stepsMap],
159
159
  );
@@ -365,7 +365,7 @@ function getGroups(steps: CodeWalkthroughStep[]): StepsGroup[] {
365
365
  return groups;
366
366
  }
367
367
 
368
- export function getGroupMarkers(group: StepsGroup) {
368
+ function getGroupMarkers(group: StepsGroup) {
369
369
  if (!group.steps.length) {
370
370
  return [];
371
371
  }
@@ -440,3 +440,11 @@ function getNormalizedNumber(options: { min: number; max: number; value: number
440
440
 
441
441
  return (value - min) / (max - min);
442
442
  }
443
+
444
+ function insertVisibleStepInOrder(visible: StepWithIndex[], step: StepWithIndex): StepWithIndex[] {
445
+ const others = visible.filter((s) => s.id !== step.id);
446
+ const laterAt = others.findIndex((s) => s.index > step.index);
447
+ const at = laterAt === -1 ? others.length : laterAt;
448
+
449
+ return insertAt(others, at, step);
450
+ }
@@ -1,34 +1,59 @@
1
- import { useNavigate } from 'react-router-dom';
1
+ import { useLocation, useNavigate } from 'react-router-dom';
2
2
 
3
3
  import type { L10nConfig } from '../types/hooks';
4
4
 
5
5
  import { useThemeHooks } from './use-theme-hooks';
6
- import { getPathnameForLocale, withPathPrefix, withoutPathPrefix } from '../utils/urls';
6
+ import {
7
+ addLeadingSlash,
8
+ getPathnameForLocale,
9
+ withoutPathPrefix,
10
+ withPathPrefix,
11
+ } from '../utils/urls';
7
12
 
8
- export function useLanguagePicker(): {
13
+ export type UseLanguagePickerResult = {
9
14
  currentLocale: L10nConfig['locales'][number] | undefined;
10
15
  locales: L10nConfig['locales'];
16
+ getLocaleUrl: (value: string) => string;
17
+ /**
18
+ * @deprecated Use `getLocaleUrl` to build the URL for the target locale, then pass it to `<Link>`.
19
+ */
11
20
  setLocale: (value: string) => void;
12
- } {
21
+ };
22
+
23
+ export function useLanguagePicker(): UseLanguagePickerResult {
13
24
  const { useL10nConfig, useLoadAndNavigate } = useThemeHooks();
14
25
  const navigate = useNavigate();
15
26
  const loadAndNavigate = useLoadAndNavigate();
27
+
16
28
  const { currentLocale, locales, defaultLocale } = useL10nConfig();
29
+ const location = useLocation();
17
30
 
18
31
  const locale = locales.find((l) => l.code === currentLocale);
19
32
 
20
- function setLocale(value: string) {
21
- const newLangPathname = withPathPrefix(
22
- getPathnameForLocale(withoutPathPrefix(location.pathname), defaultLocale, value, locales),
33
+ function getLocaleUrl(value: string): string {
34
+ let newLangPathname = getPathnameForLocale(
35
+ withoutPathPrefix(location.pathname),
36
+ defaultLocale,
37
+ value,
38
+ locales,
23
39
  );
24
40
 
25
- const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
41
+ if (location.search) {
42
+ newLangPathname += location.search;
43
+ }
44
+
45
+ return addLeadingSlash(newLangPathname);
46
+ }
47
+
48
+ function setLocale(value: string): void {
49
+ const newUrlWithLanguage = withPathPrefix(getLocaleUrl(value));
26
50
  loadAndNavigate({ navigate, to: newUrlWithLanguage });
27
51
  }
28
52
 
29
53
  return {
30
54
  currentLocale: locale,
31
55
  locales,
56
+ getLocaleUrl,
32
57
  setLocale,
33
58
  };
34
59
  }
@@ -8,6 +8,7 @@ import React, {
8
8
  useCallback,
9
9
  } from 'react';
10
10
  import styled from 'styled-components';
11
+ import { useLocation } from 'react-router-dom';
11
12
 
12
13
  import type { WithConditions } from '@redocly/config';
13
14
 
@@ -29,6 +30,7 @@ export function CodeStep({
29
30
  unless,
30
31
  children,
31
32
  }: PropsWithChildren<CodeStepProps>) {
33
+ const location = useLocation();
32
34
  const compRef = useRef<HTMLDivElement | null>(null);
33
35
  const markerRef = useRef<HTMLDivElement | null>(null);
34
36
 
@@ -79,16 +81,13 @@ export function CodeStep({
79
81
  );
80
82
 
81
83
  useEffect(() => {
82
- // If the step is active during first render, scroll to it
83
- // This is to ensure that the step is visible when the page is loaded
84
- if (isActive) {
85
- // Ensure scrollMarginTop calculated before step being scrolled.
86
- setTimeout(handleActivateStep, 5);
87
- }
88
-
84
+ // If the step is active during navigation or first render, scroll to it
85
+ if (!isActive) return;
86
+ const timer = setTimeout(handleActivateStep, 5);
87
+ return () => clearTimeout(timer);
89
88
  // Ignore dependency array because we only need to run this once
90
89
  // eslint-disable-next-line react-hooks/exhaustive-deps
91
- }, []);
90
+ }, [location.pathname]);
92
91
 
93
92
  useEffect(() => {
94
93
  const currentCompRef = compRef.current;