@atlaskit/link-datasource 7.12.2 → 7.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/dist/cjs/ui/assets-modal/search-container/loading-state.js +12 -1
- package/dist/cjs/ui/common/modal/basic-search-input/index.js +8 -3
- package/dist/cjs/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
- package/dist/cjs/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
- package/dist/cjs/ui/common/modal/popup-select/index.js +34 -5
- package/dist/cjs/ui/common/modal/popup-select/useInlinePopupLayout.js +123 -0
- package/dist/cjs/ui/datasource-table-view/datasourceTableView.js +9 -3
- package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +4 -1
- package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/index.js +5 -1
- package/dist/cjs/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +19 -0
- package/dist/cjs/ui/jira-issues-modal/jira-search-container/buildJQL.js +12 -2
- package/dist/cjs/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
- package/dist/cjs/ui/jira-issues-modal/jira-search-container/embedded.js +321 -0
- package/dist/cjs/ui/jira-issues-modal/jira-search-container/index.js +12 -0
- package/dist/cjs/ui/jira-issues-modal/jql-editor/index.js +9 -1
- package/dist/cjs/ui/jira-search-state/index.compiled.css +12 -0
- package/dist/cjs/ui/jira-search-state/index.js +90 -0
- package/dist/es2019/ui/assets-modal/search-container/loading-state.js +12 -1
- package/dist/es2019/ui/common/modal/basic-search-input/index.js +5 -2
- package/dist/es2019/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
- package/dist/es2019/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
- package/dist/es2019/ui/common/modal/popup-select/index.js +34 -4
- package/dist/es2019/ui/common/modal/popup-select/useInlinePopupLayout.js +103 -0
- package/dist/es2019/ui/datasource-table-view/datasourceTableView.js +8 -3
- package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +3 -1
- package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/index.js +4 -1
- package/dist/es2019/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +11 -0
- package/dist/es2019/ui/jira-issues-modal/jira-search-container/buildJQL.js +10 -2
- package/dist/es2019/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
- package/dist/es2019/ui/jira-issues-modal/jira-search-container/embedded.js +283 -0
- package/dist/es2019/ui/jira-issues-modal/jira-search-container/index.js +12 -0
- package/dist/es2019/ui/jira-issues-modal/jql-editor/index.js +7 -1
- package/dist/es2019/ui/jira-search-state/index.compiled.css +12 -0
- package/dist/es2019/ui/jira-search-state/index.js +82 -0
- package/dist/esm/ui/assets-modal/search-container/loading-state.js +12 -1
- package/dist/esm/ui/common/modal/basic-search-input/index.js +8 -3
- package/dist/esm/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
- package/dist/esm/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
- package/dist/esm/ui/common/modal/popup-select/index.js +35 -5
- package/dist/esm/ui/common/modal/popup-select/useInlinePopupLayout.js +116 -0
- package/dist/esm/ui/datasource-table-view/datasourceTableView.js +9 -3
- package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +4 -1
- package/dist/esm/ui/jira-issues-modal/basic-filters/ui/index.js +5 -1
- package/dist/esm/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +13 -0
- package/dist/esm/ui/jira-issues-modal/jira-search-container/buildJQL.js +12 -2
- package/dist/esm/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
- package/dist/esm/ui/jira-issues-modal/jira-search-container/embedded.js +312 -0
- package/dist/esm/ui/jira-issues-modal/jira-search-container/index.js +12 -0
- package/dist/esm/ui/jira-issues-modal/jql-editor/index.js +9 -1
- package/dist/esm/ui/jira-search-state/index.compiled.css +12 -0
- package/dist/esm/ui/jira-search-state/index.js +83 -0
- package/dist/types/ui/common/modal/basic-search-input/index.d.ts +5 -1
- package/dist/types/ui/common/modal/mode-switcher/ModeSwitcher.d.ts +1 -0
- package/dist/types/ui/common/modal/popup-select/index.d.ts +2 -1
- package/dist/types/ui/common/modal/popup-select/useInlinePopupLayout.d.ts +9 -0
- package/dist/types/ui/datasource-table-view/datasourceTableView.d.ts +5 -1
- package/dist/types/ui/datasource-table-view/types.d.ts +5 -0
- package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.d.ts +2 -1
- package/dist/types/ui/jira-issues-modal/basic-filters/ui/index.d.ts +2 -1
- package/dist/types/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.d.ts +4 -0
- package/dist/types/ui/jira-issues-modal/jira-search-container/buildJQL.d.ts +1 -0
- package/dist/types/ui/jira-issues-modal/jira-search-container/embedded.d.ts +9 -0
- package/dist/types/ui/jira-issues-modal/jira-search-container/index.d.ts +11 -0
- package/dist/types/ui/jira-issues-modal/jql-editor/index.d.ts +4 -1
- package/dist/types/ui/jira-search-state/index.d.ts +9 -0
- package/jira-search-container/package.json +10 -0
- package/jira-search-state/package.json +10 -0
- package/package.json +4 -4
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
const VIEWPORT_PADDING = 8;
|
|
3
|
+
const TRIGGER_GAP = 8;
|
|
4
|
+
const MAX_MENU_HEIGHT = 300;
|
|
5
|
+
const MIN_PREFERRED_SPACE = 180;
|
|
6
|
+
// Reserve the footer and container padding until the open popup can be measured.
|
|
7
|
+
const INITIAL_EXTRA_HEIGHT = 72;
|
|
8
|
+
const INITIAL_CONTROL_HEIGHT = 48;
|
|
9
|
+
const viewportModifiers = [{
|
|
10
|
+
name: 'offset',
|
|
11
|
+
options: {
|
|
12
|
+
offset: [0, TRIGGER_GAP]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
// Placement and height are calculated together; flipping independently can clip the search box.
|
|
16
|
+
{
|
|
17
|
+
name: 'flip',
|
|
18
|
+
enabled: false
|
|
19
|
+
}, {
|
|
20
|
+
name: 'preventOverflow',
|
|
21
|
+
enabled: true,
|
|
22
|
+
options: {
|
|
23
|
+
rootBoundary: 'viewport',
|
|
24
|
+
padding: VIEWPORT_PADDING,
|
|
25
|
+
altAxis: true,
|
|
26
|
+
tether: false
|
|
27
|
+
}
|
|
28
|
+
}];
|
|
29
|
+
export function useInlinePopupLayout(isInline, popupRef) {
|
|
30
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
31
|
+
const [layout, setLayout] = useState({
|
|
32
|
+
maxMenuHeight: MAX_MENU_HEIGHT,
|
|
33
|
+
placement: 'bottom-start'
|
|
34
|
+
});
|
|
35
|
+
const updateLayout = useCallback(() => {
|
|
36
|
+
var _viewport$offsetTop, _viewport$height, _popup$selectRef$sele, _popup$selectRef, _popup$selectRef$sele2, _popup$selectRef$sele3;
|
|
37
|
+
const popup = popupRef.current;
|
|
38
|
+
const trigger = popup === null || popup === void 0 ? void 0 : popup.targetRef;
|
|
39
|
+
if (!isInline || !trigger) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const viewport = window.visualViewport;
|
|
43
|
+
const viewportTop = (_viewport$offsetTop = viewport === null || viewport === void 0 ? void 0 : viewport.offsetTop) !== null && _viewport$offsetTop !== void 0 ? _viewport$offsetTop : 0;
|
|
44
|
+
const viewportBottom = viewportTop + ((_viewport$height = viewport === null || viewport === void 0 ? void 0 : viewport.height) !== null && _viewport$height !== void 0 ? _viewport$height : window.innerHeight);
|
|
45
|
+
const rect = trigger.getBoundingClientRect();
|
|
46
|
+
const below = Math.max(0, viewportBottom - rect.bottom - TRIGGER_GAP - VIEWPORT_PADDING);
|
|
47
|
+
const above = Math.max(0, rect.top - viewportTop - TRIGGER_GAP - VIEWPORT_PADDING);
|
|
48
|
+
const placement = below >= MIN_PREFERRED_SPACE || below >= above ? 'bottom-start' : 'top-start';
|
|
49
|
+
const available = placement === 'bottom-start' ? below : above;
|
|
50
|
+
const menu = popup.menuRef;
|
|
51
|
+
const list = menu === null || menu === void 0 ? void 0 : menu.querySelector('[role="listbox"]');
|
|
52
|
+
const controlHeight = (_popup$selectRef$sele = (_popup$selectRef = popup.selectRef) === null || _popup$selectRef === void 0 ? void 0 : (_popup$selectRef$sele2 = _popup$selectRef.select) === null || _popup$selectRef$sele2 === void 0 ? void 0 : (_popup$selectRef$sele3 = _popup$selectRef$sele2.controlRef) === null || _popup$selectRef$sele3 === void 0 ? void 0 : _popup$selectRef$sele3.offsetHeight) !== null && _popup$selectRef$sele !== void 0 ? _popup$selectRef$sele : INITIAL_CONTROL_HEIGHT;
|
|
53
|
+
// PopupSelect already subtracts the search control from maxMenuHeight. Reserve the
|
|
54
|
+
// remaining chrome separately, including the pagination footer outside its scroll area.
|
|
55
|
+
const extraHeight = menu && list ? Math.max(0, menu.offsetHeight - list.offsetHeight - controlHeight) : INITIAL_EXTRA_HEIGHT;
|
|
56
|
+
const maxMenuHeight = Math.max(controlHeight, Math.min(MAX_MENU_HEIGHT, available - extraHeight));
|
|
57
|
+
setLayout(previous => previous.placement === placement && previous.maxMenuHeight === maxMenuHeight ? previous : {
|
|
58
|
+
placement,
|
|
59
|
+
maxMenuHeight
|
|
60
|
+
});
|
|
61
|
+
}, [isInline, popupRef]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
var _popupRef$current;
|
|
64
|
+
if (!isInline || !isOpen) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
updateLayout();
|
|
68
|
+
const viewport = window.visualViewport;
|
|
69
|
+
const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(updateLayout);
|
|
70
|
+
const menu = (_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.menuRef;
|
|
71
|
+
if (menu) {
|
|
72
|
+
observer === null || observer === void 0 ? void 0 : observer.observe(menu);
|
|
73
|
+
}
|
|
74
|
+
window.addEventListener('resize', updateLayout);
|
|
75
|
+
window.addEventListener('scroll', updateLayout, true);
|
|
76
|
+
viewport === null || viewport === void 0 ? void 0 : viewport.addEventListener('resize', updateLayout);
|
|
77
|
+
viewport === null || viewport === void 0 ? void 0 : viewport.addEventListener('scroll', updateLayout);
|
|
78
|
+
return () => {
|
|
79
|
+
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
80
|
+
window.removeEventListener('resize', updateLayout);
|
|
81
|
+
window.removeEventListener('scroll', updateLayout, true);
|
|
82
|
+
viewport === null || viewport === void 0 ? void 0 : viewport.removeEventListener('resize', updateLayout);
|
|
83
|
+
viewport === null || viewport === void 0 ? void 0 : viewport.removeEventListener('scroll', updateLayout);
|
|
84
|
+
};
|
|
85
|
+
}, [isInline, isOpen, popupRef, updateLayout]);
|
|
86
|
+
const onOpen = useCallback(() => {
|
|
87
|
+
if (isInline) {
|
|
88
|
+
updateLayout();
|
|
89
|
+
setIsOpen(true);
|
|
90
|
+
}
|
|
91
|
+
}, [isInline, updateLayout]);
|
|
92
|
+
const onClose = useCallback(() => setIsOpen(false), []);
|
|
93
|
+
const popperProps = useMemo(() => ({
|
|
94
|
+
placement: layout.placement,
|
|
95
|
+
modifiers: viewportModifiers
|
|
96
|
+
}), [layout.placement]);
|
|
97
|
+
return {
|
|
98
|
+
onOpen,
|
|
99
|
+
onClose,
|
|
100
|
+
maxMenuHeight: isInline ? layout.maxMenuHeight : undefined,
|
|
101
|
+
popperProps: isInline ? popperProps : undefined
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -44,7 +44,9 @@ const DatasourceTableViewWithoutAnalytics = ({
|
|
|
44
44
|
onWrappedColumnChange,
|
|
45
45
|
onWrappedColumnsChange,
|
|
46
46
|
scrollableContainerHeight = DefaultScrollableContainerHeight,
|
|
47
|
-
onRequestSettled
|
|
47
|
+
onRequestSettled,
|
|
48
|
+
enableRequestSettled = false,
|
|
49
|
+
renderEmptyState
|
|
48
50
|
}) => {
|
|
49
51
|
// Local copy lets us apply in-session sort mutations without mutating external parameters.
|
|
50
52
|
const [sessionParameters, setSessionParameters] = useState(parameters);
|
|
@@ -135,7 +137,7 @@ const DatasourceTableViewWithoutAnalytics = ({
|
|
|
135
137
|
// settled request is reported exactly once.
|
|
136
138
|
const lastSettledResultRef = useRef(null);
|
|
137
139
|
useEffect(() => {
|
|
138
|
-
if (!fg('platform_lp_sllv_on_request_settled')) {
|
|
140
|
+
if (!fg('platform_lp_sllv_on_request_settled') && !enableRequestSettled) {
|
|
139
141
|
return;
|
|
140
142
|
}
|
|
141
143
|
if (status !== 'resolved' && status !== 'rejected' && status !== 'unauthorized' && status !== 'forbidden') {
|
|
@@ -152,7 +154,7 @@ const DatasourceTableViewWithoutAnalytics = ({
|
|
|
152
154
|
}
|
|
153
155
|
lastSettledResultRef.current = result;
|
|
154
156
|
onRequestSettled === null || onRequestSettled === void 0 ? void 0 : onRequestSettled(result);
|
|
155
|
-
}, [status, responseItems.length, totalCount, onRequestSettled]);
|
|
157
|
+
}, [status, responseItems.length, totalCount, onRequestSettled, enableRequestSettled]);
|
|
156
158
|
useEffect(() => {
|
|
157
159
|
const shouldStartUfoExperience = datasourceId && parameters && visibleColumnKeys && status === 'loading';
|
|
158
160
|
if (shouldStartUfoExperience) {
|
|
@@ -200,6 +202,9 @@ const DatasourceTableViewWithoutAnalytics = ({
|
|
|
200
202
|
});
|
|
201
203
|
}, [reset]);
|
|
202
204
|
const isResolvedWithNoResults = status === 'resolved' && !responseItems.length;
|
|
205
|
+
if (isResolvedWithNoResults && renderEmptyState) {
|
|
206
|
+
return renderEmptyState();
|
|
207
|
+
}
|
|
203
208
|
// With columns available the table can keep its headers and footer and show the empty state
|
|
204
209
|
// in place of the rows, instead of replacing the whole view with it.
|
|
205
210
|
const shouldRenderTableWithNoResults = isResolvedWithNoResults && hasColumns;
|
|
@@ -12,7 +12,8 @@ const AsyncPopupSelect = ({
|
|
|
12
12
|
selection,
|
|
13
13
|
isJQLHydrating,
|
|
14
14
|
onSelectionChange = () => {},
|
|
15
|
-
isDisabled = false
|
|
15
|
+
isDisabled = false,
|
|
16
|
+
isInline = false
|
|
16
17
|
}) => {
|
|
17
18
|
const {
|
|
18
19
|
formatMessage
|
|
@@ -87,6 +88,7 @@ const AsyncPopupSelect = ({
|
|
|
87
88
|
const shouldDisplayShowMoreButton = status === 'resolved' && !!pageCursor && !areAllResultsLoaded;
|
|
88
89
|
const triggerButtonLabel = formatMessage(asyncPopupSelectMessages[`${filterType}Label`]);
|
|
89
90
|
return /*#__PURE__*/React.createElement(FilterPopupSelect, {
|
|
91
|
+
isInline: isInline,
|
|
90
92
|
filterName: filterName,
|
|
91
93
|
status: status,
|
|
92
94
|
showLoading: isLoading,
|
|
@@ -14,7 +14,8 @@ const BasicFilterContainer = ({
|
|
|
14
14
|
site,
|
|
15
15
|
onChange,
|
|
16
16
|
selections,
|
|
17
|
-
isJQLHydrating
|
|
17
|
+
isJQLHydrating,
|
|
18
|
+
isInline = false
|
|
18
19
|
}) => {
|
|
19
20
|
const extractedFilterValues = useMemo(() => isJQLHydrating ? extractValuesFromNonComplexJQL(jql) : {}, [isJQLHydrating, jql]);
|
|
20
21
|
const {
|
|
@@ -23,11 +24,13 @@ const BasicFilterContainer = ({
|
|
|
23
24
|
return /*#__PURE__*/React.createElement(Flex, {
|
|
24
25
|
xcss: styles.basicFilterContainerStyles,
|
|
25
26
|
gap: "space.100",
|
|
27
|
+
wrap: isInline ? 'wrap' : 'nowrap',
|
|
26
28
|
testId: "jlol-basic-filter-container"
|
|
27
29
|
}, availableBasicFilterTypes.map(filter => {
|
|
28
30
|
var _extractedFilterValue;
|
|
29
31
|
const shouldShowHydrationLoader = isJQLHydrating && ((_extractedFilterValue = extractedFilterValues[filter]) === null || _extractedFilterValue === void 0 ? void 0 : _extractedFilterValue.length) > 0;
|
|
30
32
|
return /*#__PURE__*/React.createElement(AsyncPopupSelect, {
|
|
33
|
+
isInline: isInline,
|
|
31
34
|
site: site,
|
|
32
35
|
filterType: filter,
|
|
33
36
|
key: filter,
|
package/dist/es2019/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import IntlMessagesProvider from '@atlaskit/intl-messages-provider/main';
|
|
3
|
+
import { fetchMessagesForLocale } from '../../../common/utils/locale/fetch-messages-for-locale';
|
|
4
|
+
import i18nEN from '../../../i18n/en';
|
|
5
|
+
import { JiraSearchContainer as SearchContainer } from './index';
|
|
6
|
+
|
|
7
|
+
/** Embedded consumers need the same translated search controls as the configuration modal. */
|
|
8
|
+
export const JiraSearchContainer = props => /*#__PURE__*/React.createElement(IntlMessagesProvider, {
|
|
9
|
+
defaultMessages: i18nEN,
|
|
10
|
+
loaderFn: fetchMessagesForLocale
|
|
11
|
+
}, /*#__PURE__*/React.createElement(SearchContainer, props));
|
|
@@ -9,6 +9,10 @@ const fuzzySearchRegExp = /^"(.+)"$/;
|
|
|
9
9
|
const jiraIssueKeyRegExp = /[A-Z]+-\d+/;
|
|
10
10
|
export const fuzzyCharacter = '*';
|
|
11
11
|
const constructTerminalClause = (field, operator, value) => creators.terminalClause(creators.field(field), creators.operator(operator), creators.valueOperand(value));
|
|
12
|
+
|
|
13
|
+
// The generic AST string sanitizer quotes values but does not escape backslashes.
|
|
14
|
+
// Embedded keyword search accepts literal user text, so quote both delimiters explicitly.
|
|
15
|
+
const escapedValueOperand = value => creators.byText.valueOperand(`"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`);
|
|
12
16
|
export const buildJQL = input => {
|
|
13
17
|
/**
|
|
14
18
|
* Jql ast - Transforming the ast
|
|
@@ -20,6 +24,7 @@ export const buildJQL = input => {
|
|
|
20
24
|
} = jast;
|
|
21
25
|
const {
|
|
22
26
|
rawSearch,
|
|
27
|
+
textSearchField,
|
|
23
28
|
orderDirection = ORDER_BY_DIRECTION_DESC,
|
|
24
29
|
orderKey = 'created',
|
|
25
30
|
filterValues
|
|
@@ -33,7 +38,10 @@ export const buildJQL = input => {
|
|
|
33
38
|
if (!query) {
|
|
34
39
|
return '';
|
|
35
40
|
}
|
|
36
|
-
if (trimmedRawSearch) {
|
|
41
|
+
if (trimmedRawSearch && textSearchField === 'textfields') {
|
|
42
|
+
const suffix = /["*?]$/.test(trimmedRawSearch) ? '' : fuzzyCharacter;
|
|
43
|
+
query.appendClause(creators.terminalClause(creators.field('textfields'), creators.operator(OPERATOR_LIKE), escapedValueOperand(`${trimmedRawSearch}${suffix}`)), COMPOUND_OPERATOR_AND);
|
|
44
|
+
} else if (trimmedRawSearch) {
|
|
37
45
|
const fuzzy = !trimmedRawSearch.match(fuzzySearchRegExp) ? fuzzyCharacter : '';
|
|
38
46
|
const basicSearch = trimmedRawSearch.replace(/['"?*]+/g, '');
|
|
39
47
|
const text = constructTerminalClause('text', OPERATOR_LIKE, `${basicSearch}${fuzzy}`);
|
|
@@ -51,7 +59,7 @@ export const buildJQL = input => {
|
|
|
51
59
|
if (filterFieldValues.length === 0) {
|
|
52
60
|
return;
|
|
53
61
|
}
|
|
54
|
-
const filterInClause = creators.terminalClause(creators.field(key), creators.operator(OPERATOR_IN), creators.listOperand(filterFieldValues.map(filterFieldValue => creators.valueOperand(filterFieldValue.value))));
|
|
62
|
+
const filterInClause = creators.terminalClause(creators.field(key), creators.operator(OPERATOR_IN), creators.listOperand(filterFieldValues.map(filterFieldValue => textSearchField ? escapedValueOperand(filterFieldValue.value) : creators.valueOperand(filterFieldValue.value))));
|
|
55
63
|
query.appendClause(filterInClause, COMPOUND_OPERATOR_AND);
|
|
56
64
|
});
|
|
57
65
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
._16jlkb7n{flex-grow:1}
|
|
2
|
+
._19pk1b66{margin-top:var(--ds-space-050,4px)}
|
|
3
|
+
._1bsb1osq{width:100%}
|
|
4
|
+
._1e0c1txw{display:flex}
|
|
5
|
+
._1n261g80{flex-wrap:wrap}
|
|
6
|
+
._1o9zidpf{flex-shrink:0}
|
|
7
|
+
._1p57u2gc{row-gap:var(--ds-space-100,8px)}
|
|
8
|
+
._1tket9kd{min-height:72px}
|
|
9
|
+
._1ul9idpf{min-width:0}
|
|
10
|
+
._4cvr1y6m{align-items:flex-start}
|
|
11
|
+
._i0dlghg7{flex-basis:240px}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/* embedded.tsx generated by @compiled/babel-plugin v3.0.2 */
|
|
2
|
+
import "./embedded.compiled.css";
|
|
3
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
4
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
6
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
7
|
+
import { Box } from '@atlaskit/primitives/compiled/box';
|
|
8
|
+
import { Flex } from '@atlaskit/primitives/compiled/flex';
|
|
9
|
+
import { Text } from '@atlaskit/primitives/compiled/text';
|
|
10
|
+
import { useDatasourceAnalyticsEvents } from '../../../analytics';
|
|
11
|
+
import { BasicSearchInput } from '../../common/modal/basic-search-input';
|
|
12
|
+
import { basicSearchInputMessages } from '../../common/modal/basic-search-input/messages';
|
|
13
|
+
import { ModeSwitcher } from '../../common/modal/mode-switcher/ModeSwitcher';
|
|
14
|
+
import { FILTER_SELECTION_DEBOUNCE_MS } from '../../common/modal/popup-select/constants';
|
|
15
|
+
import { useHydrateJqlQuery } from '../basic-filters/hooks/useHydrateJqlQuery';
|
|
16
|
+
import { default as BasicFilters } from '../basic-filters/ui/index';
|
|
17
|
+
import { isQueryTooComplex } from '../basic-filters/utils/isQueryTooComplex';
|
|
18
|
+
import { JiraJQLEditor } from '../jql-editor';
|
|
19
|
+
import { buildJQL } from './buildJQL';
|
|
20
|
+
import { modeSwitcherMessages } from './messages';
|
|
21
|
+
const styles = {
|
|
22
|
+
inlineSearch: "_16jlkb7n _i0dlghg7 _1ul9idpf",
|
|
23
|
+
inlineSwitcher: "_1o9zidpf",
|
|
24
|
+
basicSearchInputBoxStyles: "_1bsb1osq",
|
|
25
|
+
basicSearchInputContainerStyles: "_16jlkb7n _1n261g80 _1p57u2gc",
|
|
26
|
+
inputContainerStyles: "_4cvr1y6m _1e0c1txw _1tket9kd",
|
|
27
|
+
modeSwitcherContainerStyles: "_19pk1b66"
|
|
28
|
+
};
|
|
29
|
+
export const EmbeddedJiraSearchContainer = props => {
|
|
30
|
+
const {
|
|
31
|
+
defaultJql: DEFAULT_JQL_QUERY,
|
|
32
|
+
allowedOrderByKeys: ALLOWED_ORDER_BY_KEYS,
|
|
33
|
+
isSearching,
|
|
34
|
+
parameters,
|
|
35
|
+
onSearch,
|
|
36
|
+
onSearchMethodChange: onSearchMethodChangeCallback,
|
|
37
|
+
initialSearchMethod,
|
|
38
|
+
setSearchBarJql,
|
|
39
|
+
searchBarJql = DEFAULT_JQL_QUERY,
|
|
40
|
+
site,
|
|
41
|
+
setHasJqlSyntaxErrors,
|
|
42
|
+
layout = 'modal',
|
|
43
|
+
initialBasicSearchTerm = '',
|
|
44
|
+
allowBasicModeReset = false,
|
|
45
|
+
showBasicSearchButton = true,
|
|
46
|
+
textSearchField
|
|
47
|
+
} = props;
|
|
48
|
+
const {
|
|
49
|
+
cloudId: currentCloudId
|
|
50
|
+
} = parameters || {};
|
|
51
|
+
const {
|
|
52
|
+
formatMessage
|
|
53
|
+
} = useIntl();
|
|
54
|
+
const {
|
|
55
|
+
fireEvent
|
|
56
|
+
} = useDatasourceAnalyticsEvents();
|
|
57
|
+
const [basicSearchTerm, setBasicSearchTerm] = useState(initialBasicSearchTerm);
|
|
58
|
+
const [currentSearchMethod, setCurrentSearchMethod] = useState(initialSearchMethod);
|
|
59
|
+
const [cloudId, setCloudId] = useState(currentCloudId);
|
|
60
|
+
const [isComplexQuery, setIsComplexQuery] = useState(false);
|
|
61
|
+
const [orderKey, setOrderKey] = useState();
|
|
62
|
+
const [orderDirection, setOrderDirection] = useState();
|
|
63
|
+
const [filterSelections, setFilterSelections] = useState({});
|
|
64
|
+
const basicDraft = useRef({
|
|
65
|
+
jql: initialSearchMethod === 'basic' ? searchBarJql : buildJQL({
|
|
66
|
+
rawSearch: initialBasicSearchTerm,
|
|
67
|
+
textSearchField
|
|
68
|
+
}),
|
|
69
|
+
term: initialBasicSearchTerm,
|
|
70
|
+
selections: {}
|
|
71
|
+
});
|
|
72
|
+
const advancedDraft = useRef();
|
|
73
|
+
const modeSwitcherOptionsMap = useMemo(() => ({
|
|
74
|
+
jql: {
|
|
75
|
+
label: 'JQL',
|
|
76
|
+
value: 'jql'
|
|
77
|
+
},
|
|
78
|
+
basic: {
|
|
79
|
+
label: formatMessage(modeSwitcherMessages.basicTextSearchLabel),
|
|
80
|
+
value: 'basic',
|
|
81
|
+
disabled: isComplexQuery && !allowBasicModeReset,
|
|
82
|
+
tooltipText: isComplexQuery && !allowBasicModeReset ? formatMessage(modeSwitcherMessages.basicModeSwitchDisabledTooltipText) : ''
|
|
83
|
+
}
|
|
84
|
+
}), [formatMessage, isComplexQuery, allowBasicModeReset]);
|
|
85
|
+
const modeSwitcherOptions = useMemo(() => [modeSwitcherOptionsMap.basic, modeSwitcherOptionsMap.jql], [modeSwitcherOptionsMap]);
|
|
86
|
+
const {
|
|
87
|
+
hydratedOptions,
|
|
88
|
+
fetchHydratedJqlOptions,
|
|
89
|
+
status: basicFilterHydrationStatus
|
|
90
|
+
} = useHydrateJqlQuery(cloudId || '', searchBarJql);
|
|
91
|
+
const onSearchMethodChange = useCallback(searchMethod => {
|
|
92
|
+
if (allowBasicModeReset) {
|
|
93
|
+
if (searchMethod === 'jql') {
|
|
94
|
+
var _advancedDraft$curren;
|
|
95
|
+
basicDraft.current = {
|
|
96
|
+
jql: searchBarJql,
|
|
97
|
+
term: basicSearchTerm,
|
|
98
|
+
selections: filterSelections
|
|
99
|
+
};
|
|
100
|
+
setSearchBarJql((_advancedDraft$curren = advancedDraft.current) !== null && _advancedDraft$curren !== void 0 ? _advancedDraft$curren : searchBarJql);
|
|
101
|
+
} else if (isQueryTooComplex(searchBarJql)) {
|
|
102
|
+
advancedDraft.current = searchBarJql;
|
|
103
|
+
setSearchBarJql(basicDraft.current.jql);
|
|
104
|
+
setBasicSearchTerm(basicDraft.current.term);
|
|
105
|
+
setFilterSelections(basicDraft.current.selections);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
onSearchMethodChangeCallback(searchMethod);
|
|
109
|
+
setCurrentSearchMethod(searchMethod);
|
|
110
|
+
}, [onSearchMethodChangeCallback, allowBasicModeReset, searchBarJql, basicSearchTerm, filterSelections, setSearchBarJql]);
|
|
111
|
+
const handleBasicSearchChange = useCallback(e => {
|
|
112
|
+
const rawSearch = e.currentTarget.value;
|
|
113
|
+
setBasicSearchTerm(rawSearch);
|
|
114
|
+
setSearchBarJql(buildJQL({
|
|
115
|
+
rawSearch,
|
|
116
|
+
textSearchField,
|
|
117
|
+
filterValues: filterSelections,
|
|
118
|
+
orderDirection,
|
|
119
|
+
orderKey
|
|
120
|
+
}));
|
|
121
|
+
}, [setSearchBarJql, filterSelections, orderDirection, orderKey, textSearchField]);
|
|
122
|
+
const onQueryChange = useCallback((query, jast) => {
|
|
123
|
+
var _query$split$map$filt, _fragments$at, _fragments$at2, _fragments$at3;
|
|
124
|
+
setHasJqlSyntaxErrors === null || setHasJqlSyntaxErrors === void 0 ? void 0 : setHasJqlSyntaxErrors(jast.errors.length > 0);
|
|
125
|
+
// determine if order keys have been set so they can be saved and persisted when changes occur in basic search
|
|
126
|
+
const fragments = (_query$split$map$filt = query === null || query === void 0 ? void 0 : query.split(/(^| )(order by)( |$)/i).map(item => item.trim()).filter(Boolean)) !== null && _query$split$map$filt !== void 0 ? _query$split$map$filt : [];
|
|
127
|
+
const hasOrder = ((_fragments$at = fragments.at(-2)) === null || _fragments$at === void 0 ? void 0 : _fragments$at.toLowerCase()) === 'order by';
|
|
128
|
+
const key = hasOrder ? (_fragments$at2 = fragments.at(-1)) === null || _fragments$at2 === void 0 ? void 0 : _fragments$at2.split(' ').at(-2) : undefined;
|
|
129
|
+
const order = hasOrder ? (_fragments$at3 = fragments.at(-1)) === null || _fragments$at3 === void 0 ? void 0 : _fragments$at3.split(' ').at(-1) : undefined;
|
|
130
|
+
|
|
131
|
+
// TODO: confirm if these are the only order keys we want to preserve - existing whiteboard logic
|
|
132
|
+
if (key && ALLOWED_ORDER_BY_KEYS.includes(key)) {
|
|
133
|
+
setOrderKey(key);
|
|
134
|
+
setOrderDirection(order);
|
|
135
|
+
}
|
|
136
|
+
setSearchBarJql(query);
|
|
137
|
+
}, [setSearchBarJql, setHasJqlSyntaxErrors]);
|
|
138
|
+
const handleSearch = useCallback(() => {
|
|
139
|
+
const isCurrentQueryComplex = isQueryTooComplex(searchBarJql);
|
|
140
|
+
if (currentSearchMethod === 'basic') {
|
|
141
|
+
advancedDraft.current = undefined;
|
|
142
|
+
}
|
|
143
|
+
onSearch({
|
|
144
|
+
jql: searchBarJql
|
|
145
|
+
}, {
|
|
146
|
+
searchMethod: currentSearchMethod,
|
|
147
|
+
basicFilterSelections: filterSelections,
|
|
148
|
+
isQueryComplex: isCurrentQueryComplex
|
|
149
|
+
});
|
|
150
|
+
if (currentSearchMethod === 'jql') {
|
|
151
|
+
fireEvent('ui.jqlEditor.searched', {
|
|
152
|
+
isQueryComplex: isCurrentQueryComplex
|
|
153
|
+
});
|
|
154
|
+
setIsComplexQuery(isCurrentQueryComplex);
|
|
155
|
+
if (!isCurrentQueryComplex) {
|
|
156
|
+
fetchHydratedJqlOptions();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}, [currentSearchMethod, fetchHydratedJqlOptions, filterSelections, fireEvent, searchBarJql, onSearch]);
|
|
160
|
+
const [debouncedBasicFilterSelectionChange] = useDebouncedCallback(filterValues => {
|
|
161
|
+
const jqlWithFilterValues = buildJQL({
|
|
162
|
+
rawSearch: basicSearchTerm,
|
|
163
|
+
textSearchField,
|
|
164
|
+
filterValues,
|
|
165
|
+
orderDirection,
|
|
166
|
+
orderKey
|
|
167
|
+
});
|
|
168
|
+
advancedDraft.current = undefined;
|
|
169
|
+
setSearchBarJql(jqlWithFilterValues);
|
|
170
|
+
const isCurrentQueryComplex = isQueryTooComplex(jqlWithFilterValues);
|
|
171
|
+
onSearch({
|
|
172
|
+
jql: jqlWithFilterValues
|
|
173
|
+
}, {
|
|
174
|
+
searchMethod: currentSearchMethod,
|
|
175
|
+
basicFilterSelections: filterValues,
|
|
176
|
+
isQueryComplex: isCurrentQueryComplex
|
|
177
|
+
});
|
|
178
|
+
}, FILTER_SELECTION_DEBOUNCE_MS);
|
|
179
|
+
const handleBasicFilterSelectionChange = useCallback((filterType, options) => {
|
|
180
|
+
const updatedSelection = {
|
|
181
|
+
...filterSelections,
|
|
182
|
+
[filterType]: options
|
|
183
|
+
};
|
|
184
|
+
setFilterSelections(updatedSelection);
|
|
185
|
+
debouncedBasicFilterSelectionChange(updatedSelection);
|
|
186
|
+
}, [debouncedBasicFilterSelectionChange, filterSelections]);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
const isCurrentQueryComplex = isQueryTooComplex(searchBarJql);
|
|
189
|
+
setIsComplexQuery(isCurrentQueryComplex);
|
|
190
|
+
if (!isCurrentQueryComplex && searchBarJql !== DEFAULT_JQL_QUERY) {
|
|
191
|
+
fetchHydratedJqlOptions();
|
|
192
|
+
}
|
|
193
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
|
+
}, []);
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (basicFilterHydrationStatus === 'resolved') {
|
|
197
|
+
const {
|
|
198
|
+
basicInputTextValue,
|
|
199
|
+
...hydratedFilterOptions
|
|
200
|
+
} = hydratedOptions;
|
|
201
|
+
setFilterSelections(hydratedFilterOptions);
|
|
202
|
+
if (basicInputTextValue) {
|
|
203
|
+
setBasicSearchTerm(basicInputTextValue);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, [hydratedOptions, basicFilterHydrationStatus]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (currentCloudId !== cloudId) {
|
|
209
|
+
basicDraft.current = {
|
|
210
|
+
jql: DEFAULT_JQL_QUERY,
|
|
211
|
+
term: '',
|
|
212
|
+
selections: {}
|
|
213
|
+
};
|
|
214
|
+
advancedDraft.current = undefined;
|
|
215
|
+
setBasicSearchTerm('');
|
|
216
|
+
setSearchBarJql(DEFAULT_JQL_QUERY);
|
|
217
|
+
setIsComplexQuery(false);
|
|
218
|
+
setOrderKey(undefined);
|
|
219
|
+
setOrderDirection(undefined);
|
|
220
|
+
setFilterSelections({});
|
|
221
|
+
setCloudId(currentCloudId);
|
|
222
|
+
}
|
|
223
|
+
}, [currentCloudId, cloudId, setSearchBarJql]);
|
|
224
|
+
const searchInput = currentSearchMethod === 'basic' ? /*#__PURE__*/React.createElement(Box, {
|
|
225
|
+
xcss: layout === 'inline' ? styles.inlineSearch : styles.basicSearchInputBoxStyles
|
|
226
|
+
}, /*#__PURE__*/React.createElement(Flex, {
|
|
227
|
+
alignItems: "center",
|
|
228
|
+
xcss: styles.basicSearchInputContainerStyles
|
|
229
|
+
}, /*#__PURE__*/React.createElement(BasicSearchInput, {
|
|
230
|
+
isCompact: layout === 'inline',
|
|
231
|
+
isSearching: isSearching,
|
|
232
|
+
onChange: handleBasicSearchChange,
|
|
233
|
+
onSearch: handleSearch,
|
|
234
|
+
searchTerm: basicSearchTerm,
|
|
235
|
+
showSearchButton: showBasicSearchButton,
|
|
236
|
+
ariaLabel: basicSearchInputMessages.basicTextSearchLabel,
|
|
237
|
+
testId: "jira-datasource-modal",
|
|
238
|
+
fullWidth: false
|
|
239
|
+
}), /*#__PURE__*/React.createElement(BasicFilters, {
|
|
240
|
+
jql: searchBarJql,
|
|
241
|
+
site: site,
|
|
242
|
+
onChange: handleBasicFilterSelectionChange,
|
|
243
|
+
selections: filterSelections,
|
|
244
|
+
isJQLHydrating: basicFilterHydrationStatus === 'loading',
|
|
245
|
+
isInline: layout === 'inline'
|
|
246
|
+
})), layout !== 'inline' && /*#__PURE__*/React.createElement(Text, {
|
|
247
|
+
size: "small",
|
|
248
|
+
color: "color.text.subtlest",
|
|
249
|
+
testId: "jira-search-placeholder"
|
|
250
|
+
}, formatMessage(basicSearchInputMessages.basicTextSearchLabel))) : /*#__PURE__*/React.createElement(JiraJQLEditor, {
|
|
251
|
+
isCompact: layout === 'inline',
|
|
252
|
+
cloudId: cloudId || '',
|
|
253
|
+
autoFocus: layout !== 'inline',
|
|
254
|
+
isSearching: isSearching,
|
|
255
|
+
onChange: onQueryChange,
|
|
256
|
+
onSearch: handleSearch,
|
|
257
|
+
query: searchBarJql
|
|
258
|
+
});
|
|
259
|
+
const modeSwitcher = /*#__PURE__*/React.createElement(ModeSwitcher, {
|
|
260
|
+
isInline: layout === 'inline',
|
|
261
|
+
onOptionValueChange: onSearchMethodChange,
|
|
262
|
+
selectedOptionValue: currentSearchMethod,
|
|
263
|
+
options: modeSwitcherOptions
|
|
264
|
+
});
|
|
265
|
+
if (layout === 'inline') {
|
|
266
|
+
return /*#__PURE__*/React.createElement(Flex, {
|
|
267
|
+
alignItems: "start",
|
|
268
|
+
gap: "space.100",
|
|
269
|
+
wrap: "wrap",
|
|
270
|
+
testId: "jira-search-container"
|
|
271
|
+
}, /*#__PURE__*/React.createElement(Box, {
|
|
272
|
+
xcss: styles.inlineSwitcher
|
|
273
|
+
}, modeSwitcher), currentSearchMethod === 'basic' ? searchInput : /*#__PURE__*/React.createElement(Box, {
|
|
274
|
+
xcss: styles.inlineSearch
|
|
275
|
+
}, searchInput));
|
|
276
|
+
}
|
|
277
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
278
|
+
"data-testid": "jira-search-container",
|
|
279
|
+
className: ax([styles.inputContainerStyles])
|
|
280
|
+
}, searchInput, /*#__PURE__*/React.createElement(Box, {
|
|
281
|
+
xcss: styles.modeSwitcherContainerStyles
|
|
282
|
+
}, modeSwitcher));
|
|
283
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* index.tsx generated by @compiled/babel-plugin v3.0.2 */
|
|
2
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
3
|
import "./index.compiled.css";
|
|
3
4
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
5
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
@@ -17,6 +18,7 @@ import { default as BasicFilters } from '../basic-filters/ui/index';
|
|
|
17
18
|
import { isQueryTooComplex } from '../basic-filters/utils/isQueryTooComplex';
|
|
18
19
|
import { JiraJQLEditor } from '../jql-editor';
|
|
19
20
|
import { buildJQL } from './buildJQL';
|
|
21
|
+
import { EmbeddedJiraSearchContainer } from './embedded';
|
|
20
22
|
import { modeSwitcherMessages } from './messages';
|
|
21
23
|
const styles = {
|
|
22
24
|
basicSearchInputBoxStyles: "_1bsb1osq",
|
|
@@ -26,7 +28,17 @@ const styles = {
|
|
|
26
28
|
};
|
|
27
29
|
export const DEFAULT_JQL_QUERY = 'ORDER BY created DESC';
|
|
28
30
|
export const ALLOWED_ORDER_BY_KEYS = ['key', 'summary', 'assignee', 'status', 'created'];
|
|
31
|
+
/** Opt-in consumers use a separate component; the configuration modal retains its existing path. */
|
|
29
32
|
export const JiraSearchContainer = props => {
|
|
33
|
+
if (props.layout === 'inline' || props.allowBasicModeReset || props.initialBasicSearchTerm !== undefined || props.showBasicSearchButton === false || props.textSearchField !== undefined) {
|
|
34
|
+
return /*#__PURE__*/React.createElement(EmbeddedJiraSearchContainer, _extends({}, props, {
|
|
35
|
+
defaultJql: DEFAULT_JQL_QUERY,
|
|
36
|
+
allowedOrderByKeys: ALLOWED_ORDER_BY_KEYS
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return /*#__PURE__*/React.createElement(ModalJiraSearchContainer, props);
|
|
40
|
+
};
|
|
41
|
+
const ModalJiraSearchContainer = props => {
|
|
30
42
|
const {
|
|
31
43
|
isSearching,
|
|
32
44
|
parameters,
|
|
@@ -6,6 +6,8 @@ import { makeGetJqlAutocompleteData } from '../../../services/makeGetJqlAutocomp
|
|
|
6
6
|
import { makeGetJqlSuggestionsData } from '../../../services/makeGetJqlSuggestionsData';
|
|
7
7
|
export const JiraJQLEditor = ({
|
|
8
8
|
cloudId,
|
|
9
|
+
autoFocus = true,
|
|
10
|
+
isCompact = false,
|
|
9
11
|
isSearching,
|
|
10
12
|
onChange,
|
|
11
13
|
onSearch,
|
|
@@ -18,11 +20,14 @@ export const JiraJQLEditor = ({
|
|
|
18
20
|
focus: () => {}
|
|
19
21
|
});
|
|
20
22
|
useEffect(() => {
|
|
23
|
+
if (!autoFocus) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
21
26
|
requestAnimationFrame(() => {
|
|
22
27
|
var _inputRef$current;
|
|
23
28
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
24
29
|
});
|
|
25
|
-
}, []);
|
|
30
|
+
}, [autoFocus]);
|
|
26
31
|
const searchIfValidJql = (_, jast) => {
|
|
27
32
|
if (jast.errors.length === 0) {
|
|
28
33
|
onSearch();
|
|
@@ -34,6 +39,7 @@ export const JiraJQLEditor = ({
|
|
|
34
39
|
onSearch: searchIfValidJql,
|
|
35
40
|
onUpdate: onChange,
|
|
36
41
|
isSearching: isSearching,
|
|
42
|
+
isCompact: isCompact,
|
|
37
43
|
inputRef: inputRef,
|
|
38
44
|
query: query,
|
|
39
45
|
"aria-label": /*#__PURE__*/React.createElement(FormattedMessage, {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
._18zrpxbi{padding-inline:var(--ds-space-200,1pc)}
|
|
2
|
+
._1rjcpxbi{padding-block:var(--ds-space-200,1pc)}._18m915vq{overflow-y:hidden}
|
|
3
|
+
._1pglindk{animation-timing-function:var(--ds-easing-out-practical,cubic-bezier(.4,1,.6,1))}
|
|
4
|
+
._1reo15vq{overflow-x:hidden}
|
|
5
|
+
._1tke194s{min-height:14pc}
|
|
6
|
+
._1tkeva7c{min-height:281px}
|
|
7
|
+
._4t3i1osq{height:100%}
|
|
8
|
+
._5sag1ign{animation-duration:var(--ds-duration-short,.15s)}
|
|
9
|
+
._j7hq1bym{animation-name:var(--ds-keyframe-fade-in,FadeIn0to100)}
|
|
10
|
+
._p12f1gwv{max-width:360px}
|
|
11
|
+
._y3gn1h6o{text-align:center}
|
|
12
|
+
@media (prefers-reduced-motion:reduce){._bl8aglyw{animation-name:none}}
|