@redocly/theme 0.55.0-next.4 → 0.55.0-next.6

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.
@@ -18,6 +18,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
18
18
  title?: string;
19
19
  tabIndex?: number;
20
20
  onClick?: (e?: any) => void;
21
+ ref?: React.Ref<HTMLButtonElement>;
21
22
  type?: 'button' | 'submit' | 'reset';
22
23
  }
23
24
  export declare function generateClassName({ variant, tone, size, extraClass, }: ButtonProps): string;
@@ -42,7 +42,7 @@ exports.dropdown = (0, styled_components_1.css) `
42
42
 
43
43
  --dropdown-menu-item-color-dangerous: var(--color-error-base);
44
44
 
45
- --dropdown-menu-item-border-color-focused: var(--color-blue-4);
45
+ --dropdown-menu-item-border-color-focused: var(--button-border-color-focused);
46
46
  // @tokens End
47
47
  `;
48
48
  //# sourceMappingURL=variables.js.map
@@ -58,8 +58,8 @@ function Image(props) {
58
58
  (_a = lightboxContainerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
59
59
  }
60
60
  }, [lightboxImage]);
61
- const combinedStyles = Object.assign(Object.assign(Object.assign({}, (withLightbox && { cursor: 'pointer' })), (border && { border })), (typeof style === 'string' ? { cssText: style } : style));
62
- const lightboxOverlayStyles = typeof lightboxStyle === 'string' ? { cssText: lightboxStyle } : lightboxStyle;
61
+ const combinedStyles = Object.assign(Object.assign(Object.assign({}, (withLightbox && { cursor: 'pointer' })), (border && { border })), (typeof style === 'string' ? (0, utils_1.parseStyleString)(style) : style));
62
+ const lightboxOverlayStyles = typeof lightboxStyle === 'string' ? (0, utils_1.parseStyleString)(lightboxStyle) : lightboxStyle;
63
63
  return (react_1.default.createElement(react_1.default.Fragment, null,
64
64
  lightboxImage ? (react_1.default.createElement(LightboxContainer, { ref: lightboxContainerRef, onClick: handleCloseLightbox, onKeyDown: handleLightboxKeyDown, tabIndex: 0 },
65
65
  react_1.default.createElement(Overlay, { style: lightboxOverlayStyles }),
@@ -151,11 +151,23 @@ function Select(props) {
151
151
  return !!selectedOptions.find((selectOption) => selectOption.value === option.value || selectOption.value === option.label);
152
152
  };
153
153
  const renderDefaultInput = (inputRef) => {
154
- return (react_1.default.createElement(SelectInput_1.SelectInput, { id: inputId, selectedOptions: selectedOptions, searchValue: searchValue, placeholder: placeholder, stickyValue: stickyInputValue, multiple: multiple, searchable: searchable, clearable: clearable, customIcon: icon, inputRef: inputRef, onlyIcon: onlyIcon, clearHandler: clearHandler, searchHandler: searchHandler, inputBlurHandler: inputBlurHandler, inputFocusHandler: inputFocusHandler, clickHandler: inputClickHandler }));
154
+ return (react_1.default.createElement(SelectInput_1.SelectInput, { id: inputId, listboxId: inputId, selectedOptions: selectedOptions, searchValue: searchValue, placeholder: placeholder, stickyValue: stickyInputValue, multiple: multiple, searchable: searchable, clearable: clearable, customIcon: icon, inputRef: inputRef, onlyIcon: onlyIcon, clearHandler: clearHandler, searchHandler: searchHandler, inputBlurHandler: inputBlurHandler, inputFocusHandler: inputFocusHandler, clickHandler: inputClickHandler }));
155
155
  };
156
- return (react_1.default.createElement(exports.SelectWrapper, Object.assign({ "data-component-name": "Select/Select", ref: selectRef }, dataAttributes, { disabled: disabled, "data-testid": dataTestId, className: className }),
156
+ return (react_1.default.createElement(exports.SelectWrapper, Object.assign({ "data-component-name": "Select/Select", ref: selectRef }, dataAttributes, { disabled: disabled, "data-testid": dataTestId, className: className, onKeyDown: (e) => {
157
+ if (e.key === 'Enter' && document.activeElement === selectRef.current) {
158
+ e.preventDefault();
159
+ setDropdownActive(!dropdownActive);
160
+ }
161
+ }, tabIndex: 0, "aria-haspopup": "listbox", "aria-expanded": dropdownActive, "aria-label": "Select option", onFocus: (e) => {
162
+ var _a;
163
+ if (e.target === selectRef.current) {
164
+ (_a = selectInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
165
+ }
166
+ }, onMouseDown: (e) => {
167
+ e.preventDefault();
168
+ } }),
157
169
  react_1.default.createElement(SelectDropdown, { closeOnClick: !multiple, withArrow: withArrow, trigger: renderInput ? renderInput() : renderDefaultInput(selectInputRef), triggerEvent: triggerEvent, placement: placement, alignment: alignment, active: !renderInput ? dropdownActive : undefined },
158
- react_1.default.createElement(SelectDropdownMenu, { footer: footer }, filteredOptions.length === 0 ? (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { disabled: true }, "No results")) : (filteredOptions.map((option, index) => {
170
+ react_1.default.createElement(SelectDropdownMenu, { role: "listbox", footer: footer }, filteredOptions.length === 0 ? (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { disabled: true }, "No results")) : (filteredOptions.map((option, index) => {
159
171
  return (react_1.default.createElement(react_1.Fragment, { key: index },
160
172
  react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { onAction: () => selectHandler(option), prefix: !hideCheckmarkIcon &&
161
173
  (checkmarkIconPosition ? checkmarkIconPosition === 'start' : false) &&
@@ -176,6 +188,10 @@ exports.SelectWrapper = styled_components_1.default.div `
176
188
  color: var(--select-text-color);
177
189
  min-width: 0;
178
190
 
191
+ &:focus-visible {
192
+ outline: 1px solid var(--button-border-color-focused);
193
+ }
194
+
179
195
  ${({ disabled }) => disabled &&
180
196
  `
181
197
  opacity: 0.59;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import type { SelectOption } from '../../core/types/select';
3
3
  type SelectInputProps<T> = {
4
4
  id?: string;
5
+ listboxId?: string;
5
6
  selectedOptions: SelectOption<T>[];
6
7
  searchValue: any;
7
8
  stickyValue: any;
@@ -34,7 +34,7 @@ const Tag_1 = require("../../components/Tag/Tag");
34
34
  const CloseIcon_1 = require("../../icons/CloseIcon/CloseIcon");
35
35
  const Button_1 = require("../../components/Button/Button");
36
36
  function SelectInput(props) {
37
- const { id, onlyIcon, icon, customIcon, selectedOptions, placeholder, stickyValue, multiple, searchable, clearable, clearHandler, searchHandler, clickHandler, searchValue, inputBlurHandler, inputFocusHandler, } = props;
37
+ const { id, listboxId, onlyIcon, icon, customIcon, selectedOptions, placeholder, stickyValue, multiple, searchable, clearable, clearHandler, searchHandler, clickHandler, searchValue, inputBlurHandler, inputFocusHandler, } = props;
38
38
  const inputRef = (0, react_1.useRef)(null);
39
39
  const onChangeHandler = (e) => {
40
40
  var _a;
@@ -83,10 +83,14 @@ function SelectInput(props) {
83
83
  else {
84
84
  props.inputRef.current = input;
85
85
  }
86
- }, width: multiple ? (!searchValue && selectedOptions.length ? '10px' : 'auto') : '100%' }));
86
+ }, width: multiple ? (!searchValue && selectedOptions.length ? '10px' : 'auto') : '100%', tabIndex: 0, onFocus: (e) => {
87
+ e.stopPropagation();
88
+ }, onMouseDown: (e) => {
89
+ e.preventDefault();
90
+ } }));
87
91
  const simpleValue = selectedOptions.length ? (selectedOptions[0].label || selectedOptions[0].element || selectedOptions[0].value) : (react_1.default.createElement(SelectInternalInputPlaceholder, null, placeholder));
88
92
  const multipleValues = selectedOptions.length ? (selectTags) : (react_1.default.createElement(SelectInternalInputPlaceholder, null, placeholder));
89
- return (react_1.default.createElement(exports.SelectInputWrapper, Object.assign({}, props, { id: id, onFocus: onFocusHandler, onClick: onClickHandler }),
93
+ return (react_1.default.createElement(exports.SelectInputWrapper, Object.assign({}, props, { id: id, onFocus: onFocusHandler, onClick: onClickHandler, role: "button", "aria-haspopup": "listbox", "aria-controls": listboxId, "aria-owns": listboxId, "aria-label": "Select option" }),
90
94
  !onlyIcon && (react_1.default.createElement(react_1.default.Fragment, null,
91
95
  react_1.default.createElement(SelectInputValue, null, multiple ? (searchable ? (react_1.default.createElement(react_1.default.Fragment, null,
92
96
  selectTags,
@@ -54,9 +54,14 @@ function VersionPicker(props) {
54
54
  };
55
55
  if (!options.length && !(versionPicker === null || versionPicker === void 0 ? void 0 : versionPicker.showForUnversioned))
56
56
  return null;
57
- return (React.createElement(VersionsPickerWrapper, { "data-component-name": "VersionPicker/VersionPicker" },
58
- React.createElement(VersionPickerLabel, { "data-translation-key": "versionPicker.label" }, translate('versionPicker.label', 'Version:')),
59
- React.createElement(exports.VersionPickerSelect, { placeholder: translate('versionPicker.unversioned', 'All versions'), disabled: !options.length, options: options, value: value, onChange: handleOnChange })));
57
+ return (React.createElement(VersionsPickerWrapper, { "data-component-name": "VersionPicker/VersionPicker", role: "region", "aria-label": translate('versionPicker.label', 'Version selector') },
58
+ React.createElement(VersionPickerLabel, { "data-translation-key": "versionPicker.label", htmlFor: "version-picker-select" }, translate('versionPicker.label', 'Version:')),
59
+ React.createElement(exports.VersionPickerSelect, { placeholder: translate('versionPicker.unversioned', 'All versions'), disabled: !options.length, options: options, value: value, onChange: handleOnChange, dataAttributes: {
60
+ id: 'version-picker-select',
61
+ 'aria-describedby': 'version-picker-description',
62
+ 'aria-label': translate('versionPicker.label', 'Select version'),
63
+ } }),
64
+ React.createElement(SrOnly, { id: "version-picker-description" }, "This is version picker select, using it you can select a version of the API.")));
60
65
  }
61
66
  const VersionPickerLabel = styled_components_1.default.label `
62
67
  font-size: var(--version-picker-label-font-size);
@@ -79,6 +84,10 @@ exports.VersionPickerSelect = (0, styled_components_1.default)(Select_1.Select)
79
84
  border-radius: var(--version-picker-input-border-radius);
80
85
  padding: var(--version-picker-input-padding-vertical)
81
86
  var(--version-picker-input-padding-horizontal);
87
+
88
+ &:focus-within {
89
+ outline: 1px solid var(--version-picker-focus-outline-color);
90
+ }
82
91
  }
83
92
  `;
84
93
  const VersionsPickerWrapper = styled_components_1.default.div `
@@ -89,5 +98,15 @@ const VersionsPickerWrapper = styled_components_1.default.div `
89
98
  justify-content: space-between;
90
99
  padding: var(--version-picker-padding-vertical) var(--version-picker-padding-horizontal);
91
100
  border-bottom: var(--version-picker-border-bottom);
101
+
102
+ &:focus-visible {
103
+ outline: 1px solid var(--version-picker-focus-outline-color);
104
+ }
105
+ `;
106
+ const SrOnly = styled_components_1.default.span `
107
+ position: absolute;
108
+ width: 0;
109
+ height: 0;
110
+ overflow: hidden;
92
111
  `;
93
112
  //# sourceMappingURL=VersionPicker.js.map
@@ -37,6 +37,7 @@ exports.versionPicker = (0, styled_components_1.css) `
37
37
  --version-picker-list-item-bg-color-active: var(--select-list-item-bg-color-active); // @presenter Color
38
38
  --version-picker-list-item-bg-color-hover: var(--select-list-item-bg-color-hover); // @presenter Color
39
39
 
40
+ --version-picker-focus-outline-color: var(--button-border-color-focused);
40
41
  // @tokens End
41
42
  `;
42
43
  //# sourceMappingURL=variables.js.map
@@ -32,3 +32,4 @@ export * from '../../core/utils/match-code-walkthrough-conditions';
32
32
  export * from '../../core/utils/replace-inputs-with-value';
33
33
  export * from '../../core/utils/find-closest-common-directory';
34
34
  export * from '../../core/utils/get-user-agent';
35
+ export * from '../../core/utils/parse-style-string';
@@ -48,4 +48,5 @@ __exportStar(require("../../core/utils/match-code-walkthrough-conditions"), expo
48
48
  __exportStar(require("../../core/utils/replace-inputs-with-value"), exports);
49
49
  __exportStar(require("../../core/utils/find-closest-common-directory"), exports);
50
50
  __exportStar(require("../../core/utils/get-user-agent"), exports);
51
+ __exportStar(require("../../core/utils/parse-style-string"), exports);
51
52
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export declare function parseStyleString(styleString: string): React.CSSProperties;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseStyleString = parseStyleString;
4
+ function parseStyleString(styleString) {
5
+ return styleString
6
+ .split(';')
7
+ .filter((style) => style.trim().length)
8
+ .reduce((styleObj, style) => {
9
+ const colonIndex = style.indexOf(':');
10
+ if (colonIndex === -1)
11
+ return styleObj;
12
+ const key = style.substring(0, colonIndex).trim();
13
+ const value = style.substring(colonIndex + 1).trim();
14
+ if (key && value) {
15
+ const camelCaseKey = key.replace(/-[a-z]/g, (match) => {
16
+ return match[1].toUpperCase();
17
+ });
18
+ styleObj[camelCaseKey] = value;
19
+ }
20
+ return styleObj;
21
+ }, {});
22
+ }
23
+ //# sourceMappingURL=parse-style-string.js.map
@@ -26,8 +26,5 @@ export declare const TabItem: import("styled-components").StyledComponent<"li",
26
26
  size: TabsSize;
27
27
  tabIndex?: number;
28
28
  }, never>;
29
- export declare const TabButtonLink: import("styled-components").StyledComponent<"button", any, {
30
- size: TabsSize;
31
- disabled?: boolean;
32
- }, never>;
29
+ export declare const TabButtonLink: import("styled-components").StyledComponent<React.FC<import("../../../components/Button/Button").ButtonProps>, any, import("../../../components/Button/Button").ButtonProps, never>;
33
30
  export {};
@@ -22,25 +22,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.TabButtonLink = exports.TabItem = exports.TabListContainer = void 0;
27
30
  exports.TabList = TabList;
28
- const react_1 = __importStar(require("react"));
31
+ const react_1 = __importDefault(require("react"));
29
32
  const styled_components_1 = __importStar(require("styled-components"));
30
33
  const Tab_1 = require("../../../markdoc/components/Tabs/Tab");
31
34
  const Dropdown_1 = require("../../../components/Dropdown/Dropdown");
32
35
  const DropdownMenu_1 = require("../../../components/Dropdown/DropdownMenu");
33
36
  const DropdownMenuItem_1 = require("../../../components/Dropdown/DropdownMenuItem");
34
- const ChevronDownIcon_1 = require("../../../icons/ChevronDownIcon/ChevronDownIcon");
35
- const ChevronUpIcon_1 = require("../../../icons/ChevronUpIcon/ChevronUpIcon");
37
+ const Button_1 = require("../../../components/Button/Button");
36
38
  function TabList({ childrenArray, size, overflowTabs, visibleTabs, setTabRef, onTabClick, handleKeyboard, getTabId, activeTab, isAnimating, highlightStyle, allTabsHidden, tabsContainerRef, }) {
37
- const [isDropdownOpen, setIsDropdownOpen] = (0, react_1.useState)(false);
38
- const handleDropdownOpen = (0, react_1.useCallback)(() => {
39
- setIsDropdownOpen(true);
40
- }, []);
41
- const handleDropdownClose = (0, react_1.useCallback)(() => {
42
- setIsDropdownOpen(false);
43
- }, []);
44
39
  return (react_1.default.createElement(exports.TabListContainer, { role: "tablist", ref: tabsContainerRef, "data-animating": isAnimating },
45
40
  react_1.default.createElement(HighlightBar, { size: size, style: { left: highlightStyle.left, width: highlightStyle.width } },
46
41
  react_1.default.createElement("div", null)),
@@ -59,20 +54,16 @@ function TabList({ childrenArray, size, overflowTabs, visibleTabs, setTabRef, on
59
54
  onTabClick(label);
60
55
  } }));
61
56
  }),
62
- react_1.default.createElement(exports.TabItem, { size: size, active: overflowTabs.some((index) => activeTab === childrenArray[index].props.label), tabIndex: 0 }, overflowTabs.length > 0 && (react_1.default.createElement("div", { onPointerEnter: handleDropdownOpen, onPointerLeave: handleDropdownClose },
63
- react_1.default.createElement(Dropdown_1.Dropdown, { trigger: react_1.default.createElement(exports.TabButtonLink, { size: size, className: overflowTabs.some((index) => activeTab === childrenArray[index].props.label)
64
- ? 'active'
65
- : undefined },
66
- allTabsHidden ? activeTab : 'More',
67
- isDropdownOpen ? react_1.default.createElement(ChevronUpIcon_1.ChevronUpIcon, null) : react_1.default.createElement(ChevronDownIcon_1.ChevronDownIcon, null)), alignment: "start", withArrow: false, active: isDropdownOpen, triggerEvent: "click" },
68
- react_1.default.createElement(DropdownMenu_1.DropdownMenu, null, overflowTabs.map((index) => {
69
- const { label } = childrenArray[index].props;
70
- const tabId = getTabId(label, index);
71
- return (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { key: `more-${tabId}`, active: activeTab === label, onAction: () => {
72
- onTabClick(index);
73
- handleDropdownClose();
74
- }, disabled: childrenArray[index].props.disable }, label));
75
- }))))))));
57
+ react_1.default.createElement(exports.TabItem, { size: size, active: overflowTabs.some((index) => activeTab === childrenArray[index].props.label), tabIndex: 0 }, overflowTabs.length > 0 && (react_1.default.createElement(Dropdown_1.Dropdown, { trigger: react_1.default.createElement(exports.TabButtonLink, { size: size, className: overflowTabs.some((index) => activeTab === childrenArray[index].props.label)
58
+ ? 'active'
59
+ : undefined }, allTabsHidden ? activeTab : 'More'), alignment: "start", withArrow: true },
60
+ react_1.default.createElement(DropdownMenu_1.DropdownMenu, null, overflowTabs.map((index) => {
61
+ const { label } = childrenArray[index].props;
62
+ const tabId = getTabId(label, index);
63
+ return (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { key: `more-${tabId}`, active: activeTab === label, onAction: () => {
64
+ onTabClick(index);
65
+ }, disabled: childrenArray[index].props.disable }, label));
66
+ })))))));
76
67
  }
77
68
  exports.TabListContainer = styled_components_1.default.ul `
78
69
  position: relative;
@@ -166,22 +157,11 @@ const HighlightBar = styled_components_1.default.div `
166
157
  border-radius: var(--md-tabs-${({ size }) => size}-active-tab-border-radius);
167
158
  }
168
159
  `;
169
- exports.TabButtonLink = styled_components_1.default.button `
170
- all: unset;
171
- flex-grow: 1;
172
- cursor: pointer;
173
- width: 100%;
174
- text-align: center;
160
+ exports.TabButtonLink = (0, styled_components_1.default)(Button_1.Button) `
175
161
  color: var(--md-tabs-tab-text-color);
176
162
  font-family: var(--md-tabs-tab-font-family);
177
163
  font-style: var(--md-tabs-tab-font-style);
178
164
  background-color: var(--md-tabs-tab-bg-color);
179
- white-space: nowrap;
180
- display: inline-flex;
181
- align-items: center;
182
- justify-content: center;
183
- gap: var(--spacing-xxs);
184
- z-index: 2;
185
165
 
186
166
  transition:
187
167
  background-color 300ms ease-in-out,
@@ -92,14 +92,7 @@ function Tabs({ children, className, size }) {
92
92
  else {
93
93
  setHighlightStyle({ left: offsetLeft, width: offsetWidth });
94
94
  }
95
- const timeoutId = setTimeout(() => {
96
- if (visibleTabs.includes(activeIndex)) {
97
- activeTabElement === null || activeTabElement === void 0 ? void 0 : activeTabElement.classList.add('active');
98
- }
99
- setIsAnimating(false);
100
- }, 300);
101
95
  return () => {
102
- clearTimeout(timeoutId);
103
96
  container.querySelectorAll('[data-label]').forEach((el) => {
104
97
  el.classList.remove('active');
105
98
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.55.0-next.4",
3
+ "version": "0.55.0-next.6",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -88,7 +88,7 @@
88
88
  "nprogress": "0.2.0",
89
89
  "react-calendar": "5.1.0",
90
90
  "react-date-picker": "11.0.0",
91
- "@redocly/config": "0.26.1",
91
+ "@redocly/config": "0.26.2",
92
92
  "@redocly/realm-asyncapi-sdk": "0.1.0-next.0"
93
93
  },
94
94
  "scripts": {
@@ -32,7 +32,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
32
32
  title?: string;
33
33
  tabIndex?: number;
34
34
  onClick?: (e?: any) => void;
35
-
35
+ ref?: React.Ref<HTMLButtonElement>;
36
36
  type?: 'button' | 'submit' | 'reset';
37
37
  }
38
38
 
@@ -40,6 +40,6 @@ export const dropdown = css`
40
40
 
41
41
  --dropdown-menu-item-color-dangerous: var(--color-error-base);
42
42
 
43
- --dropdown-menu-item-border-color-focused: var(--color-blue-4);
43
+ --dropdown-menu-item-border-color-focused: var(--button-border-color-focused);
44
44
  // @tokens End
45
45
  `;
@@ -3,11 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import type { KeyboardEvent, JSX } from 'react';
5
5
 
6
- import { parseSrcSet } from '@redocly/theme/core/utils';
7
-
8
- interface CSSProperties extends React.CSSProperties {
9
- cssText?: string;
10
- }
6
+ import { parseSrcSet, parseStyleString } from '@redocly/theme/core/utils';
11
7
 
12
8
  export type ImageProps = {
13
9
  src?: string;
@@ -59,14 +55,14 @@ export function Image(props: ImageProps): JSX.Element {
59
55
  }
60
56
  }, [lightboxImage]);
61
57
 
62
- const combinedStyles: CSSProperties = {
58
+ const combinedStyles: React.CSSProperties = {
63
59
  ...(withLightbox && { cursor: 'pointer' }),
64
60
  ...(border && { border }),
65
- ...(typeof style === 'string' ? { cssText: style } : style),
61
+ ...(typeof style === 'string' ? parseStyleString(style) : style),
66
62
  };
67
63
 
68
- const lightboxOverlayStyles: CSSProperties | undefined =
69
- typeof lightboxStyle === 'string' ? { cssText: lightboxStyle } : lightboxStyle;
64
+ const lightboxOverlayStyles: React.CSSProperties | undefined =
65
+ typeof lightboxStyle === 'string' ? parseStyleString(lightboxStyle) : lightboxStyle;
70
66
 
71
67
  return (
72
68
  <>
@@ -172,6 +172,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
172
172
  return (
173
173
  <SelectInput
174
174
  id={inputId}
175
+ listboxId={inputId}
175
176
  selectedOptions={selectedOptions}
176
177
  searchValue={searchValue}
177
178
  placeholder={placeholder}
@@ -199,6 +200,24 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
199
200
  disabled={disabled}
200
201
  data-testid={dataTestId}
201
202
  className={className}
203
+ onKeyDown={(e) => {
204
+ if (e.key === 'Enter' && document.activeElement === selectRef.current) {
205
+ e.preventDefault();
206
+ setDropdownActive(!dropdownActive);
207
+ }
208
+ }}
209
+ tabIndex={0}
210
+ aria-haspopup="listbox"
211
+ aria-expanded={dropdownActive}
212
+ aria-label="Select option"
213
+ onFocus={(e) => {
214
+ if (e.target === selectRef.current) {
215
+ selectInputRef.current?.focus();
216
+ }
217
+ }}
218
+ onMouseDown={(e) => {
219
+ e.preventDefault();
220
+ }}
202
221
  >
203
222
  <SelectDropdown
204
223
  closeOnClick={!multiple}
@@ -209,7 +228,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
209
228
  alignment={alignment}
210
229
  active={!renderInput ? dropdownActive : undefined}
211
230
  >
212
- <SelectDropdownMenu footer={footer}>
231
+ <SelectDropdownMenu role="listbox" footer={footer}>
213
232
  {filteredOptions.length === 0 ? (
214
233
  <DropdownMenuItem disabled>No results</DropdownMenuItem>
215
234
  ) : (
@@ -257,6 +276,10 @@ export const SelectWrapper = styled.div<{ disabled?: boolean }>`
257
276
  color: var(--select-text-color);
258
277
  min-width: 0;
259
278
 
279
+ &:focus-visible {
280
+ outline: 1px solid var(--button-border-color-focused);
281
+ }
282
+
260
283
  ${({ disabled }) =>
261
284
  disabled &&
262
285
  `
@@ -9,6 +9,7 @@ import { Button } from '@redocly/theme/components/Button/Button';
9
9
 
10
10
  type SelectInputProps<T> = {
11
11
  id?: string;
12
+ listboxId?: string;
12
13
  selectedOptions: SelectOption<T>[];
13
14
  searchValue: any;
14
15
  stickyValue: any;
@@ -30,6 +31,7 @@ type SelectInputProps<T> = {
30
31
  export function SelectInput<T>(props: SelectInputProps<T>): React.ReactNode {
31
32
  const {
32
33
  id,
34
+ listboxId,
33
35
  onlyIcon,
34
36
  icon,
35
37
  customIcon,
@@ -119,6 +121,13 @@ export function SelectInput<T>(props: SelectInputProps<T>): React.ReactNode {
119
121
  }
120
122
  }}
121
123
  width={multiple ? (!searchValue && selectedOptions.length ? '10px' : 'auto') : '100%'}
124
+ tabIndex={0}
125
+ onFocus={(e) => {
126
+ e.stopPropagation();
127
+ }}
128
+ onMouseDown={(e) => {
129
+ e.preventDefault();
130
+ }}
122
131
  />
123
132
  );
124
133
 
@@ -135,7 +144,17 @@ export function SelectInput<T>(props: SelectInputProps<T>): React.ReactNode {
135
144
  );
136
145
 
137
146
  return (
138
- <SelectInputWrapper {...props} id={id} onFocus={onFocusHandler} onClick={onClickHandler}>
147
+ <SelectInputWrapper
148
+ {...props}
149
+ id={id}
150
+ onFocus={onFocusHandler}
151
+ onClick={onClickHandler}
152
+ role="button"
153
+ aria-haspopup="listbox"
154
+ aria-controls={listboxId}
155
+ aria-owns={listboxId}
156
+ aria-label="Select option"
157
+ >
139
158
  {!onlyIcon && (
140
159
  <>
141
160
  <SelectInputValue>
@@ -35,8 +35,15 @@ export function VersionPicker(props: { versions?: Version[]; onChange: (v: Versi
35
35
  if (!options.length && !versionPicker?.showForUnversioned) return null;
36
36
 
37
37
  return (
38
- <VersionsPickerWrapper data-component-name="VersionPicker/VersionPicker">
39
- <VersionPickerLabel data-translation-key="versionPicker.label">
38
+ <VersionsPickerWrapper
39
+ data-component-name="VersionPicker/VersionPicker"
40
+ role="region"
41
+ aria-label={translate('versionPicker.label', 'Version selector')}
42
+ >
43
+ <VersionPickerLabel
44
+ data-translation-key="versionPicker.label"
45
+ htmlFor="version-picker-select"
46
+ >
40
47
  {translate('versionPicker.label', 'Version:')}
41
48
  </VersionPickerLabel>
42
49
  <VersionPickerSelect
@@ -45,7 +52,15 @@ export function VersionPicker(props: { versions?: Version[]; onChange: (v: Versi
45
52
  options={options}
46
53
  value={value}
47
54
  onChange={handleOnChange}
55
+ dataAttributes={{
56
+ id: 'version-picker-select',
57
+ 'aria-describedby': 'version-picker-description',
58
+ 'aria-label': translate('versionPicker.label', 'Select version'),
59
+ }}
48
60
  />
61
+ <SrOnly id="version-picker-description">
62
+ This is version picker select, using it you can select a version of the API.
63
+ </SrOnly>
49
64
  </VersionsPickerWrapper>
50
65
  );
51
66
  }
@@ -72,6 +87,10 @@ export const VersionPickerSelect = styled(Select)<SelectProps>`
72
87
  border-radius: var(--version-picker-input-border-radius);
73
88
  padding: var(--version-picker-input-padding-vertical)
74
89
  var(--version-picker-input-padding-horizontal);
90
+
91
+ &:focus-within {
92
+ outline: 1px solid var(--version-picker-focus-outline-color);
93
+ }
75
94
  }
76
95
  `;
77
96
 
@@ -83,4 +102,15 @@ const VersionsPickerWrapper = styled.div`
83
102
  justify-content: space-between;
84
103
  padding: var(--version-picker-padding-vertical) var(--version-picker-padding-horizontal);
85
104
  border-bottom: var(--version-picker-border-bottom);
105
+
106
+ &:focus-visible {
107
+ outline: 1px solid var(--version-picker-focus-outline-color);
108
+ }
109
+ `;
110
+
111
+ const SrOnly = styled.span`
112
+ position: absolute;
113
+ width: 0;
114
+ height: 0;
115
+ overflow: hidden;
86
116
  `;
@@ -35,5 +35,6 @@ export const versionPicker = css`
35
35
  --version-picker-list-item-bg-color-active: var(--select-list-item-bg-color-active); // @presenter Color
36
36
  --version-picker-list-item-bg-color-hover: var(--select-list-item-bg-color-hover); // @presenter Color
37
37
 
38
+ --version-picker-focus-outline-color: var(--button-border-color-focused);
38
39
  // @tokens End
39
40
  `;
@@ -32,3 +32,4 @@ export * from '@redocly/theme/core/utils/match-code-walkthrough-conditions';
32
32
  export * from '@redocly/theme/core/utils/replace-inputs-with-value';
33
33
  export * from '@redocly/theme/core/utils/find-closest-common-directory';
34
34
  export * from '@redocly/theme/core/utils/get-user-agent';
35
+ export * from '@redocly/theme/core/utils/parse-style-string';
@@ -0,0 +1,21 @@
1
+ export function parseStyleString(styleString: string): React.CSSProperties {
2
+ return styleString
3
+ .split(';')
4
+ .filter((style) => style.trim().length)
5
+ .reduce((styleObj: React.CSSProperties, style) => {
6
+ const colonIndex = style.indexOf(':');
7
+ if (colonIndex === -1) return styleObj;
8
+
9
+ const key = style.substring(0, colonIndex).trim();
10
+ const value = style.substring(colonIndex + 1).trim();
11
+
12
+ if (key && value) {
13
+ const camelCaseKey = key.replace(/-[a-z]/g, (match) => {
14
+ return match[1].toUpperCase();
15
+ });
16
+ (styleObj as any)[camelCaseKey] = value;
17
+ }
18
+
19
+ return styleObj;
20
+ }, {});
21
+ }
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from 'react';
1
+ import React from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -8,8 +8,7 @@ import { TabItemProps, TabsSize } from '@redocly/theme/markdoc/components/Tabs/T
8
8
  import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
9
9
  import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
10
10
  import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
11
- import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
12
- import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
11
+ import { Button } from '@redocly/theme/components/Button/Button';
13
12
 
14
13
  type TabListProps = {
15
14
  childrenArray: React.ReactElement<TabItemProps>[];
@@ -42,16 +41,6 @@ export function TabList({
42
41
  allTabsHidden,
43
42
  tabsContainerRef,
44
43
  }: TabListProps): JSX.Element {
45
- const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false);
46
-
47
- const handleDropdownOpen = useCallback(() => {
48
- setIsDropdownOpen(true);
49
- }, []);
50
-
51
- const handleDropdownClose = useCallback(() => {
52
- setIsDropdownOpen(false);
53
- }, []);
54
-
55
44
  return (
56
45
  <TabListContainer role="tablist" ref={tabsContainerRef} data-animating={isAnimating}>
57
46
  <HighlightBar size={size} style={{ left: highlightStyle.left, width: highlightStyle.width }}>
@@ -88,47 +77,41 @@ export function TabList({
88
77
  tabIndex={0}
89
78
  >
90
79
  {overflowTabs.length > 0 && (
91
- <div onPointerEnter={handleDropdownOpen} onPointerLeave={handleDropdownClose}>
92
- <Dropdown
93
- trigger={
94
- <TabButtonLink
95
- size={size}
96
- className={
97
- overflowTabs.some((index) => activeTab === childrenArray[index].props.label)
98
- ? 'active'
99
- : undefined
100
- }
101
- >
102
- {allTabsHidden ? activeTab : 'More'}
103
- {isDropdownOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
104
- </TabButtonLink>
105
- }
106
- alignment="start"
107
- withArrow={false}
108
- active={isDropdownOpen}
109
- triggerEvent="click"
110
- >
111
- <DropdownMenu>
112
- {overflowTabs.map((index) => {
113
- const { label } = childrenArray[index].props;
114
- const tabId = getTabId(label, index);
115
- return (
116
- <DropdownMenuItem
117
- key={`more-${tabId}`}
118
- active={activeTab === label}
119
- onAction={() => {
120
- onTabClick(index);
121
- handleDropdownClose();
122
- }}
123
- disabled={childrenArray[index].props.disable}
124
- >
125
- {label}
126
- </DropdownMenuItem>
127
- );
128
- })}
129
- </DropdownMenu>
130
- </Dropdown>
131
- </div>
80
+ <Dropdown
81
+ trigger={
82
+ <TabButtonLink
83
+ size={size}
84
+ className={
85
+ overflowTabs.some((index) => activeTab === childrenArray[index].props.label)
86
+ ? 'active'
87
+ : undefined
88
+ }
89
+ >
90
+ {allTabsHidden ? activeTab : 'More'}
91
+ </TabButtonLink>
92
+ }
93
+ alignment="start"
94
+ withArrow={true}
95
+ >
96
+ <DropdownMenu>
97
+ {overflowTabs.map((index) => {
98
+ const { label } = childrenArray[index].props;
99
+ const tabId = getTabId(label, index);
100
+ return (
101
+ <DropdownMenuItem
102
+ key={`more-${tabId}`}
103
+ active={activeTab === label}
104
+ onAction={() => {
105
+ onTabClick(index);
106
+ }}
107
+ disabled={childrenArray[index].props.disable}
108
+ >
109
+ {label}
110
+ </DropdownMenuItem>
111
+ );
112
+ })}
113
+ </DropdownMenu>
114
+ </Dropdown>
132
115
  )}
133
116
  </TabItem>
134
117
  </TabListContainer>
@@ -231,25 +214,11 @@ const HighlightBar = styled.div<{ size: TabsSize }>`
231
214
  }
232
215
  `;
233
216
 
234
- export const TabButtonLink = styled.button<{
235
- size: TabsSize;
236
- disabled?: boolean;
237
- }>`
238
- all: unset;
239
- flex-grow: 1;
240
- cursor: pointer;
241
- width: 100%;
242
- text-align: center;
217
+ export const TabButtonLink = styled(Button)`
243
218
  color: var(--md-tabs-tab-text-color);
244
219
  font-family: var(--md-tabs-tab-font-family);
245
220
  font-style: var(--md-tabs-tab-font-style);
246
221
  background-color: var(--md-tabs-tab-bg-color);
247
- white-space: nowrap;
248
- display: inline-flex;
249
- align-items: center;
250
- justify-content: center;
251
- gap: var(--spacing-xxs);
252
- z-index: 2;
253
222
 
254
223
  transition:
255
224
  background-color 300ms ease-in-out,
@@ -105,15 +105,7 @@ export function Tabs({ children, className, size }: TabsProps): JSX.Element {
105
105
  setHighlightStyle({ left: offsetLeft, width: offsetWidth });
106
106
  }
107
107
 
108
- const timeoutId = setTimeout(() => {
109
- if (visibleTabs.includes(activeIndex)) {
110
- activeTabElement?.classList.add('active');
111
- }
112
- setIsAnimating(false);
113
- }, 300);
114
-
115
108
  return () => {
116
- clearTimeout(timeoutId);
117
109
  container.querySelectorAll('[data-label]').forEach((el) => {
118
110
  el.classList.remove('active');
119
111
  });