@redocly/theme 0.70.0-next.4 → 0.70.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.
- package/lib/components/Buttons/ConnectMCPButton.js +8 -13
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.d.ts +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +30 -11
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
- package/lib/components/Dropdown/Dropdown.d.ts +4 -2
- package/lib/components/Dropdown/Dropdown.js +28 -8
- package/lib/components/Dropdown/DropdownMenu.d.ts +1 -0
- package/lib/components/Dropdown/DropdownMenu.js +1 -1
- package/lib/components/Feedback/Reasons.js +3 -2
- package/lib/components/Filter/FilterCheckboxes.js +30 -11
- package/lib/components/Filter/FilterDateRange.js +36 -2
- package/lib/components/Image/Image.js +24 -4
- package/lib/components/Search/SearchAiActivity.d.ts +10 -0
- package/lib/components/Search/SearchAiActivity.js +278 -0
- package/lib/components/Search/SearchAiDialog.js +5 -1
- package/lib/components/Search/SearchAiMessage.d.ts +2 -1
- package/lib/components/Search/SearchAiMessage.js +20 -108
- package/lib/components/Search/SearchItem.js +1 -8
- package/lib/components/Search/variables.js +15 -2
- package/lib/components/Segmented/Segmented.js +23 -3
- package/lib/components/Select/Select.js +10 -3
- package/lib/components/Select/SelectInput.d.ts +1 -0
- package/lib/components/Select/SelectInput.js +5 -11
- package/lib/components/SplitButton/SplitButton.js +37 -3
- package/lib/core/constants/mcp.d.ts +2 -2
- package/lib/core/constants/mcp.js +3 -8
- package/lib/core/constants/search.js +5 -1
- package/lib/core/hooks/index.d.ts +2 -0
- package/lib/core/hooks/index.js +2 -0
- package/lib/core/hooks/menu/use-collapse.js +1 -1
- package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
- package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
- package/lib/core/hooks/use-connect-mcp-button.js +2 -1
- package/lib/core/hooks/use-full-height.d.ts +1 -1
- package/lib/core/hooks/use-full-height.js +8 -8
- package/lib/core/hooks/use-tab-list.d.ts +31 -0
- package/lib/core/hooks/use-tab-list.js +49 -0
- package/lib/core/styles/global.d.ts +2 -0
- package/lib/core/styles/global.js +80 -76
- package/lib/core/styles/index.d.ts +1 -0
- package/lib/core/styles/index.js +3 -1
- package/lib/core/styles/search-ai-appear.d.ts +1 -0
- package/lib/core/styles/search-ai-appear.js +18 -0
- package/lib/core/types/dropdown.d.ts +1 -0
- package/lib/core/types/dropdown.js +3 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +2 -1
- package/lib/core/types/search.d.ts +23 -0
- package/lib/core/utils/Dynamic.d.ts +8 -1
- package/lib/core/utils/Dynamic.js +34 -3
- package/lib/core/utils/assign-ref.d.ts +2 -0
- package/lib/core/utils/assign-ref.js +12 -0
- package/lib/core/utils/content-segments.d.ts +12 -1
- package/lib/core/utils/content-segments.js +90 -5
- package/lib/core/utils/dropdown.d.ts +2 -0
- package/lib/core/utils/dropdown.js +12 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.d.ts +2 -1
- package/lib/core/utils/mcp.js +5 -0
- package/lib/core/utils/search-ai-activity.d.ts +4 -0
- package/lib/core/utils/search-ai-activity.js +83 -0
- package/lib/markdoc/tags/connect-mcp.js +15 -0
- package/package.json +2 -2
- package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +37 -22
- package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
- package/src/components/Dropdown/Dropdown.tsx +28 -6
- package/src/components/Dropdown/DropdownMenu.tsx +7 -1
- package/src/components/Feedback/Reasons.tsx +3 -2
- package/src/components/Filter/FilterCheckboxes.tsx +33 -18
- package/src/components/Filter/FilterDateRange.tsx +3 -2
- package/src/components/Image/Image.tsx +26 -5
- package/src/components/Search/SearchAiActivity.tsx +318 -0
- package/src/components/Search/SearchAiDialog.tsx +4 -3
- package/src/components/Search/SearchAiMessage.tsx +27 -135
- package/src/components/Search/SearchItem.tsx +2 -9
- package/src/components/Search/variables.ts +15 -2
- package/src/components/Segmented/Segmented.tsx +25 -16
- package/src/components/Select/Select.tsx +12 -6
- package/src/components/Select/SelectInput.tsx +10 -18
- package/src/components/SplitButton/SplitButton.tsx +8 -2
- package/src/core/constants/mcp.ts +3 -9
- package/src/core/constants/search.ts +5 -1
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +1 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/menu/use-collapse.ts +1 -1
- package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
- package/src/core/hooks/use-connect-mcp-button.ts +7 -2
- package/src/core/hooks/use-full-height.ts +8 -8
- package/src/core/hooks/use-tab-list.ts +63 -0
- package/src/core/styles/global.ts +80 -75
- package/src/core/styles/index.ts +2 -1
- package/src/core/styles/search-ai-appear.ts +16 -0
- package/src/core/types/dropdown.ts +1 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +1 -1
- package/src/core/types/mcp.ts +3 -1
- package/src/core/types/search.ts +21 -0
- package/src/core/utils/Dynamic.tsx +67 -35
- package/src/core/utils/assign-ref.ts +9 -0
- package/src/core/utils/content-segments.ts +98 -6
- package/src/core/utils/dropdown.ts +14 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +7 -1
- package/src/core/utils/search-ai-activity.ts +110 -0
- package/src/icons/CheckboxIcon/CheckboxIcon.tsx +1 -0
- package/src/icons/ChevronSortIcon/ChevronSortIcon.tsx +1 -0
- package/src/markdoc/tags/connect-mcp.ts +16 -1
package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx
CHANGED
|
@@ -3,5 +3,7 @@ import type { CatalogEntityRelationsGraphProps } from '@redocly/theme/components
|
|
|
3
3
|
import { dynamic } from '@redocly/theme/core/utils';
|
|
4
4
|
|
|
5
5
|
export const CatalogEntityRelationsGraph = dynamic<CatalogEntityRelationsGraphProps>(() =>
|
|
6
|
-
import('
|
|
6
|
+
import('@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph').then(
|
|
7
|
+
(mod) => mod.CatalogEntityRelationsGraph,
|
|
8
|
+
),
|
|
7
9
|
);
|
|
@@ -96,30 +96,28 @@ export function CatalogFilterCheckboxes({
|
|
|
96
96
|
<FilterOptions>
|
|
97
97
|
{filteredOptions.map(({ value, count }) => {
|
|
98
98
|
const id = 'filter--' + filter.property + '--' + value;
|
|
99
|
+
const checked =
|
|
100
|
+
filter.selectedOptions instanceof Set
|
|
101
|
+
? filter.selectedOptions.has(value) ||
|
|
102
|
+
filter.selectedOptions.has(value?.toLowerCase())
|
|
103
|
+
: false;
|
|
99
104
|
return (
|
|
100
|
-
<FilterCheckboxOption
|
|
101
|
-
|
|
102
|
-
role="link"
|
|
103
|
-
onClick={() => {
|
|
104
|
-
filter.toggleOption(value);
|
|
105
|
-
telemetry.sendCatalogEntitiesFilterCheckboxToggledMessage([
|
|
106
|
-
{
|
|
107
|
-
...getBaseDataAttributes('catalogEntitiesFilterCheckboxId', 'checkbox'),
|
|
108
|
-
category: filter.property,
|
|
109
|
-
value: value,
|
|
110
|
-
},
|
|
111
|
-
]);
|
|
112
|
-
}}
|
|
113
|
-
>
|
|
114
|
-
<CheckboxIcon
|
|
115
|
-
checked={
|
|
116
|
-
filter.selectedOptions instanceof Set
|
|
117
|
-
? filter.selectedOptions.has(value) ||
|
|
118
|
-
filter.selectedOptions.has(value?.toLowerCase())
|
|
119
|
-
: false
|
|
120
|
-
}
|
|
121
|
-
/>
|
|
105
|
+
<FilterCheckboxOption key={id}>
|
|
106
|
+
<CheckboxIcon checked={checked} />
|
|
122
107
|
<FilterOptionLabel data-translation-key={value}>
|
|
108
|
+
<FilterCheckboxInput
|
|
109
|
+
checked={checked}
|
|
110
|
+
onChange={() => {
|
|
111
|
+
filter.toggleOption(value);
|
|
112
|
+
telemetry.sendCatalogEntitiesFilterCheckboxToggledMessage([
|
|
113
|
+
{
|
|
114
|
+
...getBaseDataAttributes('catalogEntitiesFilterCheckboxId', 'checkbox'),
|
|
115
|
+
category: filter.property,
|
|
116
|
+
value: value,
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
123
121
|
{highlightText(changeTextCasing(translate(value), filterValuesCasing) || '')}
|
|
124
122
|
</FilterOptionLabel>
|
|
125
123
|
{showCounter && <CounterTag borderless>{count}</CounterTag>}
|
|
@@ -168,6 +166,23 @@ const HighlightedText = styled.span`
|
|
|
168
166
|
|
|
169
167
|
const FilterCheckboxOption = styled(FilterOption)`
|
|
170
168
|
padding-left: var(--filter-option-checkbox-padding-left);
|
|
169
|
+
|
|
170
|
+
&:has(input:focus-visible) {
|
|
171
|
+
outline: 1px solid var(--button-border-color-focused);
|
|
172
|
+
outline-offset: -1px;
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
const FilterCheckboxInput = styled.input.attrs({ type: 'checkbox' })`
|
|
177
|
+
position: absolute;
|
|
178
|
+
width: 1px;
|
|
179
|
+
height: 1px;
|
|
180
|
+
margin: -1px;
|
|
181
|
+
padding: 0;
|
|
182
|
+
overflow: hidden;
|
|
183
|
+
clip: rect(0 0 0 0);
|
|
184
|
+
white-space: nowrap;
|
|
185
|
+
border: 0;
|
|
171
186
|
`;
|
|
172
187
|
|
|
173
188
|
const FilterSearchWrapper = styled.div`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX } from 'react';
|
|
@@ -19,6 +19,7 @@ export function CatalogFilterDateRange({
|
|
|
19
19
|
filter,
|
|
20
20
|
className,
|
|
21
21
|
}: CatalogFilterDateRangeProps): JSX.Element {
|
|
22
|
+
const [today] = useState(() => new Date());
|
|
22
23
|
const { useTranslate } = useThemeHooks();
|
|
23
24
|
const { translate } = useTranslate();
|
|
24
25
|
|
|
@@ -60,7 +61,7 @@ export function CatalogFilterDateRange({
|
|
|
60
61
|
monthPlaceholder="MM"
|
|
61
62
|
yearPlaceholder="YYYY"
|
|
62
63
|
value={from}
|
|
63
|
-
maxDate={
|
|
64
|
+
maxDate={today}
|
|
64
65
|
onChange={(from) => {
|
|
65
66
|
if (Array.isArray(from)) return;
|
|
66
67
|
filter.selectOption({
|
|
@@ -11,9 +11,10 @@ import { createPortal } from 'react-dom';
|
|
|
11
11
|
import styled, { css } from 'styled-components';
|
|
12
12
|
|
|
13
13
|
import type { CSSProperties, ReactElement } from 'react';
|
|
14
|
-
import type { RenderableChildren } from '@redocly/theme/core/types';
|
|
14
|
+
import type { DropdownAlignment, RenderableChildren } from '@redocly/theme/core/types';
|
|
15
15
|
|
|
16
16
|
import { useOutsideClick, useControlledState } from '@redocly/theme/core/hooks';
|
|
17
|
+
import { fitDropdownAlignment } from '@redocly/theme/core/utils';
|
|
17
18
|
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
|
|
18
19
|
import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
|
|
19
20
|
|
|
@@ -37,7 +38,7 @@ export type DropdownProps = {
|
|
|
37
38
|
children: RenderableChildren<DropdownMenuContext>;
|
|
38
39
|
triggerEvent?: 'click' | 'hover';
|
|
39
40
|
placement?: 'top' | 'bottom';
|
|
40
|
-
alignment?:
|
|
41
|
+
alignment?: DropdownAlignment;
|
|
41
42
|
|
|
42
43
|
active?: boolean;
|
|
43
44
|
closeOnClick?: boolean;
|
|
@@ -46,6 +47,8 @@ export type DropdownProps = {
|
|
|
46
47
|
className?: string;
|
|
47
48
|
withArrow?: boolean;
|
|
48
49
|
portalled?: boolean;
|
|
50
|
+
/** Element the menu lines up with when it is not the dropdown itself, e.g. a whole split button. */
|
|
51
|
+
alignmentAnchorRef?: React.RefObject<HTMLElement | null>;
|
|
49
52
|
|
|
50
53
|
onClick?: (event: React.UIEvent) => void;
|
|
51
54
|
onClose?: () => void;
|
|
@@ -66,6 +69,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
66
69
|
placement,
|
|
67
70
|
alignment,
|
|
68
71
|
portalled = false,
|
|
72
|
+
alignmentAnchorRef,
|
|
69
73
|
onClick,
|
|
70
74
|
onClose,
|
|
71
75
|
onOpen,
|
|
@@ -76,6 +80,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
76
80
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
77
81
|
const [isOpen, setIsOpen] = useControlledState<boolean>(false, active);
|
|
78
82
|
const [menuStyle, setMenuStyle] = useState<CSSProperties>({});
|
|
83
|
+
const [fittedAlignment, setFittedAlignment] = useState(alignment);
|
|
79
84
|
|
|
80
85
|
const wrapperRef = (ref as React.RefObject<HTMLDivElement | null>) || dropdownRef;
|
|
81
86
|
|
|
@@ -115,7 +120,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
115
120
|
const updateMenuPosition = useCallback(() => {
|
|
116
121
|
const wrapper = wrapperRef.current;
|
|
117
122
|
if (!wrapper) return;
|
|
118
|
-
const rect = wrapper.getBoundingClientRect();
|
|
123
|
+
const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
|
|
119
124
|
const menuHeight = menuRef.current?.offsetHeight ?? 0;
|
|
120
125
|
const fitsBelow = rect.bottom + menuHeight <= window.innerHeight;
|
|
121
126
|
const fitsAbove = rect.top - menuHeight >= 0;
|
|
@@ -126,7 +131,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
126
131
|
} else {
|
|
127
132
|
style.top = rect.bottom;
|
|
128
133
|
}
|
|
129
|
-
if (alignment === 'end') {
|
|
134
|
+
if (fitDropdownAlignment(rect, menuRef.current?.offsetWidth ?? 0, alignment) === 'end') {
|
|
130
135
|
style.right = window.innerWidth - rect.right;
|
|
131
136
|
} else {
|
|
132
137
|
style.left = rect.left;
|
|
@@ -136,7 +141,24 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
136
141
|
style.zIndex = zIndex;
|
|
137
142
|
}
|
|
138
143
|
setMenuStyle(style);
|
|
139
|
-
}, [placement, alignment, wrapperRef]);
|
|
144
|
+
}, [placement, alignment, wrapperRef, alignmentAnchorRef]);
|
|
145
|
+
|
|
146
|
+
/* The absolute-positioned menu is placed by CSS, so the side is measured once it is
|
|
147
|
+
shown and again on resize; a side that runs off the viewport flips to the other one. */
|
|
148
|
+
const updateFittedAlignment = useCallback(() => {
|
|
149
|
+
const wrapper = wrapperRef.current;
|
|
150
|
+
const menu = menuRef.current;
|
|
151
|
+
if (!wrapper || !menu) return;
|
|
152
|
+
const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
|
|
153
|
+
setFittedAlignment(fitDropdownAlignment(rect, menu.offsetWidth, alignment));
|
|
154
|
+
}, [alignment, wrapperRef, alignmentAnchorRef]);
|
|
155
|
+
|
|
156
|
+
useIsomorphicLayoutEffect(() => {
|
|
157
|
+
if (portalled || !isOpen) return;
|
|
158
|
+
updateFittedAlignment();
|
|
159
|
+
window.addEventListener('resize', updateFittedAlignment);
|
|
160
|
+
return () => window.removeEventListener('resize', updateFittedAlignment);
|
|
161
|
+
}, [portalled, isOpen, updateFittedAlignment]);
|
|
140
162
|
|
|
141
163
|
useIsomorphicLayoutEffect(() => {
|
|
142
164
|
if (!portalled || !isOpen) return;
|
|
@@ -172,7 +194,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
|
172
194
|
<ChildrenWrapper
|
|
173
195
|
ref={menuRef}
|
|
174
196
|
$placement={placement}
|
|
175
|
-
$alignment={
|
|
197
|
+
$alignment={fittedAlignment}
|
|
176
198
|
$isOpen={isOpen}
|
|
177
199
|
$portalled={portalled}
|
|
178
200
|
style={portalled ? menuStyle : undefined}
|
|
@@ -6,7 +6,12 @@ import type { DropdownMenuItemProps } from '@redocly/theme/components/Dropdown/D
|
|
|
6
6
|
|
|
7
7
|
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
8
8
|
|
|
9
|
-
export type DropdownMenuProps = {
|
|
9
|
+
export type DropdownMenuProps = {
|
|
10
|
+
id?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
role?: string;
|
|
13
|
+
footer?: React.ReactNode;
|
|
14
|
+
} & (
|
|
10
15
|
| {
|
|
11
16
|
items: DropdownMenuItemProps[];
|
|
12
17
|
}
|
|
@@ -32,6 +37,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
|
|
|
32
37
|
<DropdownMenuWrapper
|
|
33
38
|
data-component-name="Dropdown/DropdownMenu"
|
|
34
39
|
data-testid="dropdown-menu"
|
|
40
|
+
id={props.id}
|
|
35
41
|
className={props.className}
|
|
36
42
|
role={props.role || 'menu'}
|
|
37
43
|
>
|
|
@@ -18,6 +18,7 @@ export type ReasonsProps = {
|
|
|
18
18
|
export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.Element | null {
|
|
19
19
|
const { label, component = 'checkbox', items = [] } = settings;
|
|
20
20
|
const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
|
|
21
|
+
const idPrefix = React.useId();
|
|
21
22
|
const { useTranslate } = useThemeHooks();
|
|
22
23
|
const { translate } = useTranslate();
|
|
23
24
|
|
|
@@ -51,6 +52,7 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
|
|
|
51
52
|
{items.map((reason, idx) => (
|
|
52
53
|
<OptionWrapper key={reason}>
|
|
53
54
|
<input
|
|
55
|
+
id={`${idPrefix}-${idx}`}
|
|
54
56
|
type={component}
|
|
55
57
|
value={reason}
|
|
56
58
|
checked={checkedState[idx]}
|
|
@@ -59,8 +61,7 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
|
|
|
59
61
|
/>
|
|
60
62
|
<label
|
|
61
63
|
data-translation-key={`feedback.settings.reasons.items.${idx + 1}`}
|
|
62
|
-
|
|
63
|
-
onClick={() => handleOptionChange(idx)}
|
|
64
|
+
htmlFor={`${idPrefix}-${idx}`}
|
|
64
65
|
>
|
|
65
66
|
{translate(`feedback.settings.reasons.items.${idx + 1}`, reason)}
|
|
66
67
|
</label>
|
|
@@ -30,26 +30,24 @@ export function FilterCheckboxes({
|
|
|
30
30
|
<FilterOptions>
|
|
31
31
|
{(filter.filteredOptions || filter.options).map(({ value, count }) => {
|
|
32
32
|
const id = 'filter--' + filter.property + '--' + value;
|
|
33
|
+
const checked =
|
|
34
|
+
filter.selectedOptions instanceof Set
|
|
35
|
+
? filter.selectedOptions.has(value) ||
|
|
36
|
+
filter.selectedOptions.has(value?.toLowerCase())
|
|
37
|
+
: false;
|
|
33
38
|
return (
|
|
34
|
-
<FilterCheckboxOption
|
|
35
|
-
|
|
36
|
-
role="link"
|
|
37
|
-
onClick={() => {
|
|
38
|
-
filter.toggleOption(value);
|
|
39
|
-
telemetry.sendFilterCheckboxToggledMessage([
|
|
40
|
-
getBaseDataAttributes('filterId', 'checkbox'),
|
|
41
|
-
]);
|
|
42
|
-
}}
|
|
43
|
-
>
|
|
44
|
-
<CheckboxIcon
|
|
45
|
-
checked={
|
|
46
|
-
filter.selectedOptions instanceof Set
|
|
47
|
-
? filter.selectedOptions.has(value) ||
|
|
48
|
-
filter.selectedOptions.has(value?.toLowerCase())
|
|
49
|
-
: false
|
|
50
|
-
}
|
|
51
|
-
/>
|
|
39
|
+
<FilterCheckboxOption key={id}>
|
|
40
|
+
<CheckboxIcon checked={checked} />
|
|
52
41
|
<FilterOptionLabel data-translation-key={value}>
|
|
42
|
+
<FilterCheckboxInput
|
|
43
|
+
checked={checked}
|
|
44
|
+
onChange={() => {
|
|
45
|
+
filter.toggleOption(value);
|
|
46
|
+
telemetry.sendFilterCheckboxToggledMessage([
|
|
47
|
+
getBaseDataAttributes('filterId', 'checkbox'),
|
|
48
|
+
]);
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
53
51
|
{changeTextCasing(translate(value), filterValuesCasing)}
|
|
54
52
|
</FilterOptionLabel>
|
|
55
53
|
{showCounter && <CounterTag borderless>{count}</CounterTag>}
|
|
@@ -63,6 +61,23 @@ export function FilterCheckboxes({
|
|
|
63
61
|
|
|
64
62
|
const FilterCheckboxOption = styled(FilterOption)`
|
|
65
63
|
padding-left: var(--filter-option-checkbox-padding-left);
|
|
64
|
+
|
|
65
|
+
&:has(input:focus-visible) {
|
|
66
|
+
outline: 1px solid var(--button-border-color-focused);
|
|
67
|
+
outline-offset: -1px;
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const FilterCheckboxInput = styled.input.attrs({ type: 'checkbox' })`
|
|
72
|
+
position: absolute;
|
|
73
|
+
width: 1px;
|
|
74
|
+
height: 1px;
|
|
75
|
+
margin: -1px;
|
|
76
|
+
padding: 0;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
clip: rect(0 0 0 0);
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
border: 0;
|
|
66
81
|
`;
|
|
67
82
|
|
|
68
83
|
const FilterCheckboxesWrapper = styled.div`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX } from 'react';
|
|
@@ -11,6 +11,7 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
|
11
11
|
import { formatDateWithoutTimeZone } from '@redocly/theme/core/utils';
|
|
12
12
|
|
|
13
13
|
export function FilterDateRange({ filter }: FilterProps): JSX.Element {
|
|
14
|
+
const [today] = useState(() => new Date());
|
|
14
15
|
const { useTranslate } = useThemeHooks();
|
|
15
16
|
const { translate } = useTranslate();
|
|
16
17
|
|
|
@@ -33,7 +34,7 @@ export function FilterDateRange({ filter }: FilterProps): JSX.Element {
|
|
|
33
34
|
monthPlaceholder="MM"
|
|
34
35
|
yearPlaceholder="YYYY"
|
|
35
36
|
value={from}
|
|
36
|
-
maxDate={
|
|
37
|
+
maxDate={today}
|
|
37
38
|
onChange={(from) => {
|
|
38
39
|
if (Array.isArray(from)) return;
|
|
39
40
|
filter.selectOption({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import type { KeyboardEvent, JSX } from 'react';
|
|
4
|
+
import type { KeyboardEvent, JSX, MouseEvent } from 'react';
|
|
5
5
|
|
|
6
6
|
import { parseSrcSet, parseStyleString } from '@redocly/theme/core/utils';
|
|
7
7
|
import { useModalScrollLock } from '@redocly/theme/core/hooks';
|
|
@@ -42,6 +42,7 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
42
42
|
} = props;
|
|
43
43
|
|
|
44
44
|
const lightboxContainerRef = useRef<HTMLDivElement>(null);
|
|
45
|
+
const lightboxTriggerRef = useRef<HTMLElement | null>(null);
|
|
45
46
|
const [lightboxImage, setLightboxImage] = useState<string | undefined>(undefined);
|
|
46
47
|
const parsedSourceSetMap = useMemo(() => {
|
|
47
48
|
return srcSet ? parseSrcSet(srcSet) : new Map();
|
|
@@ -55,15 +56,32 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
55
56
|
}, []);
|
|
56
57
|
|
|
57
58
|
const handleImageClick = useCallback(
|
|
58
|
-
(src: string) => {
|
|
59
|
+
(src: string, trigger: HTMLElement) => {
|
|
59
60
|
if (!withLightbox || lightboxImage) {
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
63
|
+
lightboxTriggerRef.current = trigger;
|
|
62
64
|
setLightboxImage(src);
|
|
63
65
|
},
|
|
64
66
|
[withLightbox, lightboxImage],
|
|
65
67
|
);
|
|
66
68
|
|
|
69
|
+
const getLightboxTriggerProps = (imageSrc: string) =>
|
|
70
|
+
withLightbox
|
|
71
|
+
? {
|
|
72
|
+
role: 'button',
|
|
73
|
+
tabIndex: 0,
|
|
74
|
+
'aria-label': alt ? undefined : 'Open image',
|
|
75
|
+
onClick: (e: MouseEvent<HTMLElement>) => handleImageClick(imageSrc, e.currentTarget),
|
|
76
|
+
onKeyDown: (e: KeyboardEvent<HTMLElement>) => {
|
|
77
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
handleImageClick(imageSrc, e.currentTarget);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
: {};
|
|
84
|
+
|
|
67
85
|
const handleCloseLightbox = useCallback(() => {
|
|
68
86
|
setLightboxImage(undefined);
|
|
69
87
|
}, []);
|
|
@@ -72,6 +90,9 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
72
90
|
useEffect(() => {
|
|
73
91
|
if (lightboxImage) {
|
|
74
92
|
lightboxContainerRef.current?.focus();
|
|
93
|
+
} else {
|
|
94
|
+
lightboxTriggerRef.current?.focus();
|
|
95
|
+
lightboxTriggerRef.current = null;
|
|
75
96
|
}
|
|
76
97
|
}, [lightboxImage]);
|
|
77
98
|
|
|
@@ -92,7 +113,7 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
92
113
|
width={width}
|
|
93
114
|
height={height}
|
|
94
115
|
style={combinedStyles}
|
|
95
|
-
|
|
116
|
+
{...getLightboxTriggerProps(src)}
|
|
96
117
|
/>
|
|
97
118
|
) : (
|
|
98
119
|
Array.from(parsedSourceSetMap).map(([key, value]) => (
|
|
@@ -106,7 +127,7 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
106
127
|
height={height}
|
|
107
128
|
$withLightbox={withLightbox}
|
|
108
129
|
style={combinedStyles}
|
|
109
|
-
|
|
130
|
+
{...getLightboxTriggerProps(value)}
|
|
110
131
|
/>
|
|
111
132
|
))
|
|
112
133
|
);
|
|
@@ -122,7 +143,7 @@ export function Image(props: ImageProps): JSX.Element {
|
|
|
122
143
|
src={galleryImageSrc}
|
|
123
144
|
alt={alt}
|
|
124
145
|
style={withLightbox ? { cursor: 'pointer' } : undefined}
|
|
125
|
-
|
|
146
|
+
{...getLightboxTriggerProps(galleryImageSrc)}
|
|
126
147
|
/>
|
|
127
148
|
))}
|
|
128
149
|
</GalleryRow>
|