@redocly/theme 0.70.0-next.0 → 0.70.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/lib/components/Button/Button.js +5 -2
  2. package/lib/components/Button/variables.js +14 -5
  3. package/lib/components/Dropdown/Dropdown.js +0 -7
  4. package/lib/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.d.ts +11 -0
  5. package/lib/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.js +7 -0
  6. package/lib/components/Search/SearchAiConversationInput.js +10 -6
  7. package/lib/components/Search/SearchAiDialog.js +8 -1
  8. package/lib/components/Search/SearchItem.js +9 -1
  9. package/lib/components/SplitButton/SplitButton.d.ts +1 -0
  10. package/lib/components/SplitButton/SplitButton.js +2 -2
  11. package/lib/components/Tooltip/AnchorTooltip.js +17 -27
  12. package/lib/components/Tooltip/JsTooltip.js +34 -46
  13. package/lib/components/Tooltip/Tooltip.js +1 -2
  14. package/lib/core/constants/search.d.ts +1 -1
  15. package/lib/core/constants/search.js +1 -1
  16. package/lib/core/hooks/catalog/use-catalog-entity-schema.js +2 -2
  17. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +1 -1
  18. package/lib/core/hooks/index.d.ts +1 -0
  19. package/lib/core/hooks/index.js +1 -0
  20. package/lib/core/hooks/menu/use-nested-menu.js +2 -2
  21. package/lib/core/hooks/use-anchor-positioning.d.ts +3 -0
  22. package/lib/core/hooks/use-anchor-positioning.js +21 -0
  23. package/lib/core/hooks/use-dialog-hotkeys.js +4 -2
  24. package/lib/core/hooks/use-theme-hooks.js +0 -1
  25. package/lib/core/types/hooks.d.ts +0 -3
  26. package/lib/core/types/l10n.d.ts +1 -1
  27. package/lib/core/types/search.d.ts +2 -0
  28. package/lib/core/utils/load-and-navigate.js +3 -3
  29. package/package.json +3 -3
  30. package/src/components/Button/Button.tsx +5 -2
  31. package/src/components/Button/variables.ts +14 -5
  32. package/src/components/Dropdown/Dropdown.tsx +0 -7
  33. package/src/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.tsx +19 -0
  34. package/src/components/Search/SearchAiConversationInput.tsx +13 -7
  35. package/src/components/Search/SearchAiDialog.tsx +16 -1
  36. package/src/components/Search/SearchItem.tsx +14 -0
  37. package/src/components/SplitButton/SplitButton.tsx +3 -0
  38. package/src/components/Tooltip/AnchorTooltip.tsx +17 -27
  39. package/src/components/Tooltip/JsTooltip.tsx +55 -62
  40. package/src/components/Tooltip/Tooltip.tsx +1 -2
  41. package/src/core/constants/search.ts +1 -1
  42. package/src/core/hooks/__mocks__/index.ts +1 -0
  43. package/src/core/hooks/__mocks__/use-anchor-positioning.ts +3 -0
  44. package/src/core/hooks/__mocks__/use-theme-hooks.ts +0 -3
  45. package/src/core/hooks/catalog/use-catalog-entity-schema.ts +2 -2
  46. package/src/core/hooks/code-walkthrough/__mocks__/MockIntersectionObserver.ts +1 -2
  47. package/src/core/hooks/code-walkthrough/__mocks__/mock-intersection-observer.ts +1 -2
  48. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +1 -1
  49. package/src/core/hooks/index.ts +1 -0
  50. package/src/core/hooks/menu/use-nested-menu.ts +2 -2
  51. package/src/core/hooks/use-anchor-positioning.ts +22 -0
  52. package/src/core/hooks/use-dialog-hotkeys.ts +11 -5
  53. package/src/core/hooks/use-theme-hooks.ts +0 -1
  54. package/src/core/types/hooks.ts +0 -1
  55. package/src/core/types/l10n.ts +3 -0
  56. package/src/core/types/search.ts +2 -0
  57. package/src/core/utils/load-and-navigate.ts +3 -3
@@ -125,15 +125,20 @@ function TooltipComponent({
125
125
 
126
126
  export const Tooltip = memo<PropsWithChildren<TooltipProps>>(TooltipComponent);
127
127
 
128
+ type TooltipArrowProps = {
129
+ $withArrow: boolean;
130
+ $arrowPosition: NonNullable<TooltipProps['arrowPosition']>;
131
+ };
132
+
128
133
  const PLACEMENTS = {
129
- top: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
130
- ${({ withArrow, arrowPosition }) =>
131
- withArrow && arrowPosition === 'left'
134
+ top: css<TooltipArrowProps>`
135
+ ${({ $withArrow, $arrowPosition }) =>
136
+ $withArrow && $arrowPosition === 'left'
132
137
  ? css`
133
138
  transform: translate(0, -100%);
134
139
  margin-top: -10px;
135
140
  `
136
- : arrowPosition === 'right'
141
+ : $arrowPosition === 'right'
137
142
  ? css`
138
143
  transform: translate(-100%, -100%);
139
144
  margin-top: -10px;
@@ -143,30 +148,28 @@ const PLACEMENTS = {
143
148
  margin-top: -10px;
144
149
  `}
145
150
 
146
- ${({ withArrow, arrowPosition }) =>
147
- withArrow &&
151
+ ${({ $withArrow, $arrowPosition }) =>
152
+ $withArrow &&
148
153
  css`
149
154
  &::after {
150
- border-left: 14px solid transparent;
151
- border-right: 14px solid transparent;
152
- border-top-width: 8px;
153
- border-top-style: solid;
154
- border-radius: 2px;
155
+ width: 16px;
156
+ height: 8px;
157
+ clip-path: path('M0 0H16L9.414 6.586A2 2 0 0 1 6.586 6.586Z');
155
158
  bottom: 0;
156
- ${arrowPosition === 'left' && 'left: 16px; transform: translateY(99%);'}
157
- ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 99%);'}
158
- ${arrowPosition === 'right' && 'right: 16px; transform: translateY(99%);'}
159
+ ${$arrowPosition === 'left' && 'left: 22px; transform: translateY(99%);'}
160
+ ${$arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 99%);'}
161
+ ${$arrowPosition === 'right' && 'right: 22px; transform: translateY(99%);'}
159
162
  }
160
163
  `}
161
164
  `,
162
- bottom: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
163
- ${({ withArrow, arrowPosition }) =>
164
- withArrow && arrowPosition === 'left'
165
+ bottom: css<TooltipArrowProps>`
166
+ ${({ $withArrow, $arrowPosition }) =>
167
+ $withArrow && $arrowPosition === 'left'
165
168
  ? css`
166
169
  transform: translate(0, 10px);
167
170
  margin-top: 0;
168
171
  `
169
- : arrowPosition === 'right'
172
+ : $arrowPosition === 'right'
170
173
  ? css`
171
174
  transform: translate(-100%, 10px);
172
175
  margin-top: 0;
@@ -176,61 +179,55 @@ const PLACEMENTS = {
176
179
  margin-top: 0;
177
180
  `}
178
181
 
179
- ${({ withArrow, arrowPosition }) =>
180
- withArrow &&
182
+ ${({ $withArrow, $arrowPosition }) =>
183
+ $withArrow &&
181
184
  css`
182
185
  &::after {
183
- border-left: 14px solid transparent;
184
- border-right: 14px solid transparent;
185
- border-bottom-width: 8px;
186
- border-bottom-style: solid;
187
- border-radius: 0 0 2px 2px;
186
+ width: 16px;
187
+ height: 8px;
188
+ clip-path: path('M0 8H16L9.414 1.414A2 2 0 0 0 6.586 1.414Z');
188
189
  top: 0;
189
- ${arrowPosition === 'left' && 'left: 16px; transform: translateY(-99%);'}
190
- ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -99%);'}
191
- ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-99%);'}
190
+ ${$arrowPosition === 'left' && 'left: 22px; transform: translateY(-99%);'}
191
+ ${$arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -99%);'}
192
+ ${$arrowPosition === 'right' && 'right: 22px; transform: translateY(-99%);'}
192
193
  }
193
194
  `}
194
195
  `,
195
- left: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
196
+ left: css<TooltipArrowProps>`
196
197
  transform: translate(-100%, -50%);
197
198
  margin-left: -10px;
198
199
 
199
- ${({ withArrow, arrowPosition }) =>
200
- withArrow &&
200
+ ${({ $withArrow, $arrowPosition }) =>
201
+ $withArrow &&
201
202
  css`
202
203
  &::after {
203
- border-top: 14px solid transparent;
204
- border-bottom: 14px solid transparent;
205
- border-left-width: 8px;
206
- border-left-style: solid;
207
- border-radius: 2px 0 0 2px;
204
+ width: 8px;
205
+ height: 16px;
206
+ clip-path: path('M0 0V16L6.586 9.414A2 2 0 0 0 6.586 6.586Z');
208
207
  top: 50%;
209
208
  right: 0;
210
- ${arrowPosition === 'top' && 'top: 16px; transform: translateX(99%);'}
211
- ${arrowPosition === 'center' && 'top: 50%; transform: translate(99%, -50%);'}
212
- ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(99%);'}
209
+ ${$arrowPosition === 'top' && 'top: 22px; transform: translateX(99%);'}
210
+ ${$arrowPosition === 'center' && 'top: 50%; transform: translate(99%, -50%);'}
211
+ ${$arrowPosition === 'bottom' && 'bottom: 22px; transform: translateX(99%);'}
213
212
  }
214
213
  `}
215
214
  `,
216
- right: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
215
+ right: css<TooltipArrowProps>`
217
216
  transform: translate(0, -50%);
218
217
  margin-left: 10px;
219
218
 
220
- ${({ withArrow, arrowPosition }) =>
221
- withArrow &&
219
+ ${({ $withArrow, $arrowPosition }) =>
220
+ $withArrow &&
222
221
  css`
223
222
  &::after {
224
- border-top: 14px solid transparent;
225
- border-bottom: 14px solid transparent;
226
- border-right-width: 8px;
227
- border-right-style: solid;
228
- border-radius: 0 2px 2px 0;
223
+ width: 8px;
224
+ height: 16px;
225
+ clip-path: path('M8 0V16L1.414 9.414A2 2 0 0 1 1.414 6.586Z');
229
226
  top: 50%;
230
227
  left: 0;
231
- ${arrowPosition === 'top' && 'top: 16px; transform: translateX(-99%);'}
232
- ${arrowPosition === 'center' && 'top: 50%; transform: translate(-99%, -50%);'}
233
- ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(-99%);'}
228
+ ${$arrowPosition === 'top' && 'top: 22px; transform: translateX(-99%);'}
229
+ ${$arrowPosition === 'center' && 'top: 50%; transform: translate(-99%, -50%);'}
230
+ ${$arrowPosition === 'bottom' && 'bottom: 22px; transform: translateX(-99%);'}
234
231
  }
235
232
  `}
236
233
  `,
@@ -240,12 +237,12 @@ const TooltipWrapper = styled.div`
240
237
  position: relative;
241
238
  display: flex;
242
239
  `;
243
- const TooltipBody = styled.span<{
244
- $placement: NonNullable<TooltipProps['placement']>;
245
- $withArrow: boolean;
246
- $arrowPosition: NonNullable<TooltipProps['arrowPosition']>;
247
- $width?: string;
248
- }>`
240
+ const TooltipBody = styled.span<
241
+ TooltipArrowProps & {
242
+ $placement: NonNullable<TooltipProps['placement']>;
243
+ $width?: string;
244
+ }
245
+ >`
249
246
  display: inline-block;
250
247
 
251
248
  padding: var(--tooltip-padding);
@@ -267,9 +264,7 @@ const TooltipBody = styled.span<{
267
264
 
268
265
  content: ' ';
269
266
  display: inline-block;
270
- width: 0;
271
- height: 0;
272
- border-color: var(--tooltip-arrow-color, var(--tooltip-bg-color));
267
+ background: var(--tooltip-arrow-color, var(--tooltip-bg-color));
273
268
  }
274
269
 
275
270
  background: var(--tooltip-bg-color);
@@ -279,7 +274,5 @@ const TooltipBody = styled.span<{
279
274
  box-shadow: var(--bg-raised-shadow);
280
275
 
281
276
  width: ${({ $width }) => $width || 'max-content'};
282
- ${({ $placement }) => css`
283
- ${PLACEMENTS[$placement]};
284
- `}
277
+ ${({ $placement }) => PLACEMENTS[$placement]}
285
278
  `;
@@ -4,11 +4,10 @@ import type { TooltipProps } from '@redocly/theme/core/types';
4
4
 
5
5
  import { Tooltip as AnchorTooltip } from '@redocly/theme/components/Tooltip/AnchorTooltip';
6
6
  import { Tooltip as JsTooltip } from '@redocly/theme/components/Tooltip/JsTooltip';
7
- import { useThemeHooks } from '@redocly/theme/core/hooks';
7
+ import { useAnchorPositioning } from '@redocly/theme/core/hooks';
8
8
  import { getDefaultFallbackPlacements } from '@redocly/theme/core/utils';
9
9
 
10
10
  function TooltipComponent(props: TooltipProps): React.ReactElement {
11
- const { useAnchorPositioning } = useThemeHooks();
12
11
  const { isSupported } = useAnchorPositioning();
13
12
 
14
13
  const fallbackPlacements =
@@ -44,7 +44,7 @@ export const AI_SEARCH_ERROR_CONFIG: Record<AiSearchError, AiSearchErrorConfig>
44
44
 
45
45
  export const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
46
46
 
47
- export const SEARCH_DEBOUNCE_TIME_MS = 300;
47
+ export const SEARCH_DEBOUNCE_TIME_MS = 500;
48
48
 
49
49
  export const DEFAULT_KEYBOARD_SHORTCUTS = '⌘+K,CTRL+K';
50
50
 
@@ -1,5 +1,6 @@
1
1
  export * from './use-theme-config';
2
2
  export * from './use-theme-hooks';
3
+ export * from './use-anchor-positioning';
3
4
  export * from './use-mobile-menu';
4
5
  export * from './use-controlled-state';
5
6
  export * from './search/use-recent-searches';
@@ -0,0 +1,3 @@
1
+ import { vi } from 'vitest';
2
+
3
+ export const useAnchorPositioning = vi.fn(() => ({ isSupported: true }));
@@ -3,9 +3,6 @@ import { vi, type MockedFunction } from 'vitest';
3
3
  import type { ThemeHooks } from '../../types/hooks';
4
4
 
5
5
  export const useThemeHooks = vi.fn(() => ({
6
- useAnchorPositioning: vi.fn(() => ({
7
- isSupported: true,
8
- })),
9
6
  useTranslate: vi.fn(() => ({
10
7
  translate: vi.fn((key: string, defaultValue: string | { defaultValue: string }) =>
11
8
  defaultValue
@@ -12,7 +12,7 @@ export function useCatalogEntitySchema({ entity, relatedEntity }: CatalogEntityS
12
12
 
13
13
  try {
14
14
  parsedSchema = JSON.parse(schema);
15
- } catch (error) {
15
+ } catch {
16
16
  parsedSchema = undefined;
17
17
  }
18
18
 
@@ -22,7 +22,7 @@ export function useCatalogEntitySchema({ entity, relatedEntity }: CatalogEntityS
22
22
  parsedApiDefinition = relatedEntity?.metadata?.schema
23
23
  ? (JSON.parse(relatedEntity.metadata.schema) as Record<string, unknown>)
24
24
  : undefined;
25
- } catch (err) {
25
+ } catch {
26
26
  parsedApiDefinition = undefined;
27
27
  }
28
28
 
@@ -8,8 +8,7 @@ export class MockIntersectionObserver {
8
8
  unobserve: (target: HTMLElement) => void;
9
9
  disconnect: () => void;
10
10
 
11
- // @ts-ignore
12
- constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
11
+ constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {
13
12
  this.root = null;
14
13
  this.rootMargin = '0px';
15
14
  this.thresholds = [1];
@@ -9,8 +9,7 @@ export class MockIntersectionObserver {
9
9
  unobserve: (target: HTMLElement) => void;
10
10
  disconnect: () => void;
11
11
 
12
- // @ts-ignore
13
- constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
12
+ constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {
14
13
  this.root = null;
15
14
  this.rootMargin = '0px';
16
15
  this.scrollMargin = '';
@@ -98,7 +98,7 @@ export function useCodeWalkthroughControls(
98
98
  Object.entries(newState).forEach(([id, control]) => {
99
99
  const existingControl = controlsState[id];
100
100
  if (existingControl && existingControl.type === control.type) {
101
- // @ts-ignore
101
+ // @ts-expect-error
102
102
  newState[id] = {
103
103
  ...control,
104
104
  value: existingControl.value,
@@ -55,3 +55,4 @@ export * from './use-toast';
55
55
  export * from './use-toast-logic';
56
56
  export * from './use-banner-telemetry';
57
57
  export * from './use-tooltip-fallback-placement';
58
+ export * from './use-anchor-positioning';
@@ -47,9 +47,9 @@ export function useNestedMenu({ item, labelRef, nestedMenuRef }: NestedMenuProps
47
47
  rect.right <= (window.innerWidth || document.documentElement.clientWidth);
48
48
 
49
49
  // Only scroll if element is in viewport to prevent page jumping
50
- // @ts-ignore
50
+ // @ts-expect-error
51
51
  if (isInViewport && typeof el.scrollIntoViewIfNeeded === 'function') {
52
- // @ts-ignore
52
+ // @ts-expect-error
53
53
  el.scrollIntoViewIfNeeded(centerIfNeeded);
54
54
  }
55
55
  }
@@ -0,0 +1,22 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export const useAnchorPositioning = (): { isSupported: boolean } => {
4
+ const [isSupported, setIsSupported] = useState(false);
5
+
6
+ useEffect(() => {
7
+ setIsSupported(isAnchorPositioningSupported());
8
+ }, []);
9
+
10
+ return { isSupported };
11
+ };
12
+
13
+ function isAnchorPositioningSupported(): boolean {
14
+ if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') {
15
+ return false;
16
+ }
17
+ return (
18
+ CSS.supports('anchor-name: --a') &&
19
+ CSS.supports('position-anchor: --a') &&
20
+ CSS.supports('top: anchor(top)')
21
+ );
22
+ }
@@ -21,12 +21,18 @@ export function useDialogHotKeys(ref: RefObject<HTMLElement | null>, onClose: ()
21
21
  };
22
22
 
23
23
  const onFocusableElementsHandler = () => {
24
- const focusableElements = ref.current?.querySelectorAll(
25
- 'input, [href], [tabindex]:not([tabindex="-1"])',
24
+ const focusableElements = Array.from(
25
+ ref.current?.querySelectorAll<HTMLElement>(
26
+ 'input, textarea, select, button, [href], [tabindex]:not([tabindex="-1"])',
27
+ ) ?? [],
28
+ ).filter(
29
+ (element) =>
30
+ !element.hasAttribute('disabled') &&
31
+ (typeof element.checkVisibility !== 'function' || element.checkVisibility()),
26
32
  );
27
- if (focusableElements && focusableElements.length > 0) {
28
- firstFocusableRef.current = focusableElements[0] as HTMLElement;
29
- lastFocusableRef.current = focusableElements[focusableElements.length - 1] as HTMLElement;
33
+ if (focusableElements.length > 0) {
34
+ firstFocusableRef.current = focusableElements[0];
35
+ lastFocusableRef.current = focusableElements[focusableElements.length - 1];
30
36
  }
31
37
  };
32
38
 
@@ -6,7 +6,6 @@ import { ThemeDataContext } from '../contexts/ThemeDataContext';
6
6
  import { useTelemetryFallback } from './use-telemetry-fallback';
7
7
 
8
8
  const fallbacks = {
9
- useAnchorPositioning: () => ({ isSupported: false }),
10
9
  useTranslate: () => ({
11
10
  translate: (value?: string, options?: { defaultValue: string } | string) =>
12
11
  (typeof options === 'string' ? options : options?.defaultValue) || value || '',
@@ -44,7 +44,6 @@ import type { BreadcrumbItem } from './breadcrumb';
44
44
  import type { AiSearchError } from '../constants/search';
45
45
 
46
46
  export type ThemeHooks = {
47
- useAnchorPositioning: () => { isSupported: boolean };
48
47
  useTranslate: () => { translate: TFunction };
49
48
  useTranslationKeys: () => string[];
50
49
  /**
@@ -102,6 +102,7 @@ export type TranslationKey =
102
102
  | 'search.ai.placeholder'
103
103
  | 'search.ai.generatingResponse'
104
104
  | 'search.ai.followUpQuestion'
105
+ | 'search.ai.send'
105
106
  | 'search.ai.suggestionsTitle'
106
107
  | 'search.ai.thinkingText'
107
108
  | 'search.ai.resourcesFound'
@@ -124,6 +125,8 @@ export type TranslationKey =
124
125
  | 'search.ai.error.header.unauthorized'
125
126
  | 'search.ai.feedback.more'
126
127
  | 'search.searchItem.deprecated'
128
+ | 'search.searchItem.enum'
129
+ | 'search.searchItem.example'
127
130
  | 'search.groups.all'
128
131
  | 'search.filter.field.footer'
129
132
  | 'aiAssistant.trigger'
@@ -45,6 +45,8 @@ export type ParameterHighlight = {
45
45
  description: string;
46
46
  place: string;
47
47
  path: string[];
48
+ enum?: string;
49
+ example?: string;
48
50
  };
49
51
 
50
52
  export type SearchItemData = {
@@ -25,9 +25,9 @@ export async function loadAndNavigate({
25
25
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
26
26
  const loader = (window as any)['__LOADER'];
27
27
  const result = await withLoadProgress(loader.tryLoad(pathname));
28
- // @ts-ignore
28
+ // @ts-expect-error
29
29
  if (result?.redirectTo) {
30
- // @ts-ignore
30
+ // @ts-expect-error
31
31
  return loadAndNavigate({ navigate, to: result.redirectTo, origin, options });
32
32
  }
33
33
 
@@ -40,7 +40,7 @@ export async function loadAndNavigate({
40
40
  unstable_flushSync: true,
41
41
  } as NavigateOptions & { unstable_flushSync: boolean });
42
42
  }
43
- // @ts-ignore
43
+ // @ts-expect-error
44
44
  if (result.props?.disableAutoScroll) return;
45
45
 
46
46
  if (hash) {