@atlaskit/link-picker 1.8.3 → 1.9.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/controllers/use-fix-height/index.js +27 -0
  3. package/dist/cjs/services/use-plugins/index.js +48 -90
  4. package/dist/cjs/services/use-plugins/reducer.js +64 -0
  5. package/dist/cjs/services/use-plugins/utils.js +134 -0
  6. package/dist/cjs/ui/index.js +2 -1
  7. package/dist/cjs/ui/link-picker/form-footer/index.js +76 -0
  8. package/dist/cjs/ui/link-picker/form-footer/styled.js +17 -0
  9. package/dist/cjs/ui/link-picker/index.js +35 -76
  10. package/dist/cjs/ui/link-picker/link-search-error/index.js +47 -6
  11. package/dist/cjs/ui/link-picker/link-search-list/index.js +89 -20
  12. package/dist/cjs/ui/link-picker/link-search-list/link-search-no-results/index.js +54 -0
  13. package/dist/cjs/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/index.js +0 -0
  14. package/dist/cjs/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/styled.js +0 -0
  15. package/dist/cjs/ui/link-picker/link-search-list/link-search-no-results/styled.js +17 -0
  16. package/dist/cjs/ui/link-picker/link-search-list/styled.js +13 -6
  17. package/dist/cjs/ui/link-picker/messages.js +0 -50
  18. package/dist/cjs/ui/link-picker/styled.js +16 -11
  19. package/dist/cjs/ui/link-picker/text-input/styled.js +1 -1
  20. package/dist/cjs/version.json +1 -1
  21. package/dist/es2019/controllers/use-fix-height/index.js +19 -0
  22. package/dist/es2019/services/use-plugins/index.js +35 -57
  23. package/dist/es2019/services/use-plugins/reducer.js +37 -0
  24. package/dist/es2019/services/use-plugins/utils.js +59 -0
  25. package/dist/es2019/ui/index.js +1 -1
  26. package/dist/es2019/ui/link-picker/form-footer/index.js +51 -0
  27. package/dist/es2019/ui/link-picker/form-footer/styled.js +5 -0
  28. package/dist/es2019/ui/link-picker/index.js +30 -68
  29. package/dist/es2019/ui/link-picker/link-search-error/index.js +40 -4
  30. package/dist/es2019/ui/link-picker/link-search-list/index.js +60 -14
  31. package/dist/es2019/ui/link-picker/link-search-list/link-search-no-results/index.js +35 -0
  32. package/dist/es2019/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/index.js +0 -0
  33. package/dist/es2019/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/styled.js +0 -0
  34. package/dist/es2019/ui/link-picker/link-search-list/link-search-no-results/styled.js +6 -0
  35. package/dist/es2019/ui/link-picker/link-search-list/styled.js +23 -2
  36. package/dist/es2019/ui/link-picker/messages.js +0 -50
  37. package/dist/es2019/ui/link-picker/styled.js +20 -15
  38. package/dist/es2019/ui/link-picker/text-input/styled.js +3 -1
  39. package/dist/es2019/version.json +1 -1
  40. package/dist/esm/controllers/use-fix-height/index.js +19 -0
  41. package/dist/esm/services/use-plugins/index.js +49 -94
  42. package/dist/esm/services/use-plugins/reducer.js +43 -0
  43. package/dist/esm/services/use-plugins/utils.js +111 -0
  44. package/dist/esm/ui/index.js +1 -1
  45. package/dist/esm/ui/link-picker/form-footer/index.js +53 -0
  46. package/dist/esm/ui/link-picker/form-footer/styled.js +6 -0
  47. package/dist/esm/ui/link-picker/index.js +31 -73
  48. package/dist/esm/ui/link-picker/link-search-error/index.js +41 -5
  49. package/dist/esm/ui/link-picker/link-search-list/index.js +67 -13
  50. package/dist/esm/ui/link-picker/link-search-list/link-search-no-results/index.js +37 -0
  51. package/dist/esm/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/index.js +0 -0
  52. package/dist/esm/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/styled.js +0 -0
  53. package/dist/esm/ui/link-picker/link-search-list/link-search-no-results/styled.js +6 -0
  54. package/dist/esm/ui/link-picker/link-search-list/styled.js +11 -5
  55. package/dist/esm/ui/link-picker/messages.js +0 -50
  56. package/dist/esm/ui/link-picker/styled.js +14 -11
  57. package/dist/esm/ui/link-picker/text-input/styled.js +1 -1
  58. package/dist/esm/version.json +1 -1
  59. package/dist/types/controllers/use-fix-height/index.d.ts +8 -0
  60. package/dist/types/services/use-plugins/reducer.d.ts +30 -0
  61. package/dist/types/services/use-plugins/utils.d.ts +18 -0
  62. package/dist/types/ui/index.d.ts +5 -0
  63. package/dist/types/ui/link-picker/form-footer/index.d.ts +13 -0
  64. package/dist/types/ui/link-picker/form-footer/styled.d.ts +1 -0
  65. package/dist/types/ui/link-picker/index.d.ts +21 -18
  66. package/dist/types/ui/link-picker/link-search-error/index.d.ts +19 -3
  67. package/dist/types/ui/link-picker/link-search-list/index.d.ts +30 -5
  68. package/dist/types/ui/link-picker/link-search-list/link-search-no-results/index.d.ts +19 -0
  69. package/dist/types/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/index.d.ts +0 -0
  70. package/dist/types/ui/link-picker/{link-search-no-results → link-search-list/link-search-no-results}/no-results-svg/styled.d.ts +0 -0
  71. package/dist/types/ui/link-picker/link-search-list/link-search-no-results/styled.d.ts +1 -0
  72. package/dist/types/ui/link-picker/link-search-list/styled.d.ts +1 -0
  73. package/dist/types/ui/link-picker/messages.d.ts +0 -50
  74. package/dist/types/ui/link-picker/styled.d.ts +5 -1
  75. package/package.json +15 -14
  76. package/report.api.md +8 -2
  77. package/dist/cjs/ui/link-picker/link-search-no-results/index.js +0 -27
  78. package/dist/es2019/ui/link-picker/link-search-no-results/index.js +0 -10
  79. package/dist/esm/ui/link-picker/link-search-no-results/index.js +0 -14
  80. package/dist/types/ui/link-picker/link-search-no-results/index.d.ts +0 -4
@@ -21,5 +21,5 @@ var _templateObject, _templateObject2;
21
21
 
22
22
  var clearTextButtonStyles = (0, _react.css)(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n padding: 0;\n margin-right: ", "px;\n color: ", ";\n background: transparent;\n border: none;\n cursor: pointer;\n"])), (0, _constants.gridSize)() / 2, (0, _tokens.token)('color.icon.subtle', _colors.N500));
23
23
  exports.clearTextButtonStyles = clearTextButtonStyles;
24
- var fieldStyles = (0, _react.css)(_templateObject2 || (_templateObject2 = (0, _taggedTemplateLiteral2.default)(["\n margin-bottom: ", "px;\n"])), (0, _constants.gridSize)() * 2);
24
+ var fieldStyles = (0, _react.css)(_templateObject2 || (_templateObject2 = (0, _taggedTemplateLiteral2.default)(["\n & + & {\n margin-top: ", "px;\n }\n"])), (0, _constants.gridSize)() * 2);
25
25
  exports.fieldStyles = fieldStyles;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.8.3",
3
+ "version": "1.9.0",
4
4
  "sideEffects": false
5
5
  }
@@ -0,0 +1,19 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+
3
+ const useFixHeight = shouldFixHeight => {
4
+ const ref = useRef(null);
5
+ const currentHeight = useRef(null);
6
+ useLayoutEffect(() => {
7
+ if (ref.current) {
8
+ currentHeight.current = ref.current.getBoundingClientRect().height;
9
+ }
10
+ });
11
+ return {
12
+ ref,
13
+ style: shouldFixHeight ? {
14
+ minHeight: currentHeight.current || 'auto'
15
+ } : undefined
16
+ };
17
+ };
18
+
19
+ export default useFixHeight;
@@ -1,25 +1,10 @@
1
- import { useEffect, useReducer, useMemo, useRef, useState, useCallback } from 'react';
1
+ import { useEffect, useMemo, useState, useCallback } from 'react';
2
2
  import { RECENT_SEARCH_LIST_SIZE } from '../../ui/link-picker';
3
-
4
- function reducer(state, payload) {
5
- return { ...state,
6
- ...payload
7
- };
8
- }
9
-
3
+ import { CancellationError, resolvePluginUpdates } from './utils';
4
+ import { usePluginReducer } from './reducer';
10
5
  export function usePlugins(state, activeTab, plugins) {
11
6
  const [retries, setRetries] = useState(0);
12
- const [pluginState, dispatch] = useReducer(reducer, {
13
- items: null,
14
- error: null,
15
- isLoading: false
16
- });
17
- const {
18
- items,
19
- isLoading,
20
- error
21
- } = pluginState;
22
- const queryVersion = useRef(0);
7
+ const [pluginState, dispatch] = usePluginReducer();
23
8
  const activePlugin = plugins === null || plugins === void 0 ? void 0 : plugins[activeTab];
24
9
  useEffect(() => {
25
10
  if (!activePlugin) {
@@ -28,62 +13,50 @@ export function usePlugins(state, activeTab, plugins) {
28
13
 
29
14
  if (!state) {
30
15
  dispatch({
31
- items: null,
32
- isLoading: false,
33
- error: null
16
+ type: 'CLEAR'
34
17
  });
35
18
  return;
36
19
  }
37
20
 
38
- const updates = activePlugin.resolve(state);
39
21
  dispatch({
40
- items: null,
41
- isLoading: true,
42
- error: null
22
+ type: 'LOADING'
43
23
  });
44
- const newQuery = ++queryVersion.current;
24
+ const {
25
+ next,
26
+ cancel
27
+ } = resolvePluginUpdates(activePlugin, state);
45
28
 
46
29
  const updateResults = async () => {
47
30
  try {
48
- while (newQuery === queryVersion.current) {
49
- let done = false;
50
- let value;
51
-
52
- if (updates instanceof Promise) {
53
- done = true;
54
- value = await updates;
55
- } else {
56
- var _result$done;
57
-
58
- const result = await updates.next();
59
- done = (_result$done = result.done) !== null && _result$done !== void 0 ? _result$done : false;
60
- value = result.value;
61
- }
62
-
63
- if (newQuery !== queryVersion.current) {
64
- return;
65
- }
31
+ let isLoading = true;
66
32
 
33
+ while (isLoading) {
34
+ const {
35
+ value,
36
+ done
37
+ } = await next();
38
+ isLoading = !done;
67
39
  dispatch({
68
- items: limit(value.data),
69
- isLoading: !done
40
+ type: 'SUCCESS',
41
+ payload: {
42
+ items: limit(value.data),
43
+ isLoading: !done
44
+ }
70
45
  });
71
-
72
- if (done) {
73
- return;
74
- }
75
46
  }
76
47
  } catch (error) {
77
- dispatch({
78
- error,
79
- items: null,
80
- isLoading: false
81
- });
48
+ if (!(error instanceof CancellationError)) {
49
+ dispatch({
50
+ type: 'ERROR',
51
+ payload: error
52
+ });
53
+ }
82
54
  }
83
55
  };
84
56
 
85
57
  updateResults();
86
- }, [activePlugin, queryVersion, state, retries]);
58
+ return cancel;
59
+ }, [activePlugin, state, retries, dispatch]);
87
60
  const tabs = useMemo(() => {
88
61
  if (!plugins || plugins.length <= 1) {
89
62
  return [];
@@ -96,6 +69,11 @@ export function usePlugins(state, activeTab, plugins) {
96
69
  const handleRetry = useCallback(() => {
97
70
  setRetries(prev => ++prev);
98
71
  }, []);
72
+ const {
73
+ items,
74
+ isLoading,
75
+ error
76
+ } = pluginState;
99
77
  return {
100
78
  tabs,
101
79
  items,
@@ -0,0 +1,37 @@
1
+ import { useReducer } from 'react';
2
+ export const ACTION_CLEAR = 'CLEAR';
3
+ export const ACTION_LOADING = 'LOADING';
4
+ export const ACTION_SUCCESS = 'SUCCESS';
5
+ export const ACTION_ERRORED = 'ERROR';
6
+ export const INITIAL_STATE = {
7
+ items: null,
8
+ error: null,
9
+ isLoading: false
10
+ };
11
+ export function reducer(state, action) {
12
+ switch (action.type) {
13
+ case ACTION_CLEAR:
14
+ return { ...INITIAL_STATE
15
+ };
16
+
17
+ case ACTION_LOADING:
18
+ return { ...INITIAL_STATE,
19
+ isLoading: true
20
+ };
21
+
22
+ case ACTION_SUCCESS:
23
+ return { ...state,
24
+ ...action.payload,
25
+ error: null
26
+ };
27
+
28
+ case ACTION_ERRORED:
29
+ return { ...INITIAL_STATE,
30
+ error: action.payload
31
+ };
32
+
33
+ default:
34
+ return state;
35
+ }
36
+ }
37
+ export const usePluginReducer = () => useReducer(reducer, INITIAL_STATE);
@@ -0,0 +1,59 @@
1
+ export class CancellationError extends Error {}
2
+ export const cancellable = promise => {
3
+ let reject;
4
+ return {
5
+ promise: new Promise((resolve, _reject) => {
6
+ reject = _reject;
7
+ promise.then(resolve, reject);
8
+ }),
9
+ cancel: () => {
10
+ reject(new CancellationError('Promise cancelled'));
11
+ }
12
+ };
13
+ };
14
+ /**
15
+ * Wraps the resolve with a cancellation wrapper and makes promise + generator plugin resolves more compatible
16
+ * Calling cancel for generator plugin prevents it from yielding any further updates
17
+ */
18
+
19
+ export const resolvePluginUpdates = (plugin, state) => {
20
+ const updates = plugin.resolve(state); // Promise plugin
21
+
22
+ if (updates instanceof Promise) {
23
+ const {
24
+ promise,
25
+ cancel
26
+ } = cancellable(updates);
27
+ return {
28
+ cancel,
29
+ next: async () => ({
30
+ value: await promise,
31
+ done: true
32
+ })
33
+ };
34
+ } // Generator plugin
35
+
36
+
37
+ const cancellationCallbacks = [];
38
+ let cancelled = false;
39
+ return {
40
+ cancel: () => {
41
+ cancelled = true;
42
+ cancellationCallbacks.forEach(cb => cb());
43
+ },
44
+ next: () => {
45
+ const {
46
+ promise,
47
+ cancel
48
+ } = cancellable(updates.next());
49
+
50
+ if (cancelled) {
51
+ cancel();
52
+ } else {
53
+ cancellationCallbacks.push(cancel);
54
+ }
55
+
56
+ return promise;
57
+ }
58
+ };
59
+ };
@@ -5,7 +5,7 @@ import { COMPONENT_NAME, ANALYTICS_CHANNEL } from '../common/constants';
5
5
  import { name as packageName, version as packageVersion } from '../version.json';
6
6
  import { ErrorBoundaryFallback } from './error-boundary-fallback';
7
7
  import { LoaderFallback } from './loader-fallback';
8
- const PACKAGE_DATA = {
8
+ export const PACKAGE_DATA = {
9
9
  packageName,
10
10
  packageVersion,
11
11
  componentName: COMPONENT_NAME
@@ -0,0 +1,51 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
3
+ /** @jsx jsx */
4
+ import { jsx } from '@emotion/react';
5
+ import { useIntl, defineMessages } from 'react-intl-next';
6
+ import Button, { ButtonGroup } from '@atlaskit/button';
7
+ import { formFooterStyles } from './styled';
8
+ import { memo } from 'react';
9
+ const messages = defineMessages({
10
+ cancelButton: {
11
+ id: 'fabric.linkPicker.button.cancel',
12
+ defaultMessage: 'Cancel',
13
+ description: 'Button to cancel and dismiss the link picker'
14
+ },
15
+ saveButton: {
16
+ id: 'fabric.linkPicker.button.save',
17
+ defaultMessage: 'Save',
18
+ description: 'Button to save edited link'
19
+ },
20
+ insertButton: {
21
+ id: 'fabric.linkPicker.button.insert',
22
+ defaultMessage: 'Insert',
23
+ description: 'Button to insert searched or selected link'
24
+ }
25
+ });
26
+ export const testIds = {
27
+ insertButton: 'link-picker-insert-button',
28
+ cancelButton: 'link-picker-cancel-button'
29
+ };
30
+
31
+ const FormFooter = ({
32
+ isEditing,
33
+ onCancel,
34
+ ...restProps
35
+ }) => {
36
+ const intl = useIntl();
37
+ const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
38
+ return jsx("footer", _extends({
39
+ css: formFooterStyles
40
+ }, restProps), jsx(ButtonGroup, null, jsx(Button, {
41
+ appearance: "default",
42
+ onClick: onCancel,
43
+ testId: testIds.cancelButton
44
+ }, intl.formatMessage(messages.cancelButton)), jsx(Button, {
45
+ type: "submit",
46
+ appearance: "primary",
47
+ testId: testIds.insertButton
48
+ }, intl.formatMessage(insertButtonMsg))));
49
+ };
50
+
51
+ export default /*#__PURE__*/memo(FormFooter);
@@ -0,0 +1,5 @@
1
+ import { css } from '@emotion/react';
2
+ export const formFooterStyles = css`
3
+ display: flex;
4
+ justify-content: flex-end;
5
+ `;
@@ -5,8 +5,6 @@ import { Fragment, useCallback, useEffect, useLayoutEffect, useReducer, memo } f
5
5
  import { jsx } from '@emotion/react';
6
6
  import { useIntl, FormattedMessage } from 'react-intl-next';
7
7
  import { useAnalyticsEvents } from '@atlaskit/analytics-next';
8
- import Button, { ButtonGroup } from '@atlaskit/button';
9
- import { FormFooter } from '@atlaskit/form';
10
8
  import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
11
9
  import Tabs, { Tab, TabList } from '@atlaskit/tabs';
12
10
  import VisuallyHidden from '@atlaskit/visually-hidden';
@@ -17,14 +15,15 @@ import { useSearchQuery } from '../../services/use-search-query';
17
15
  import { withLinkPickerAnalyticsContext } from '../../services/with-link-picker-analytics';
18
16
  import { messages } from './messages';
19
17
  import PanelTextInput from './text-input';
20
- import LinkSearchList from './link-search-list';
21
18
  import { normalizeUrl, isSafeUrl } from '../../common/utils/url';
22
- import { rootContainerStyles, searchIconStyles, listTitleStyles, tabsWrapperStyles } from './styled';
19
+ import useFixHeight from '../../controllers/use-fix-height';
20
+ import { rootContainerStyles, searchIconStyles, tabsWrapperStyles, flexColumnStyles, formFooterMargin } from './styled';
23
21
  import { browser } from './browser';
24
22
  import Announcer from './announcer';
25
- import LinkSearchNoResults from './link-search-no-results';
26
23
  import { transformTimeStamp } from './transformTimeStamp';
27
- import LinkSearchError from './link-search-error';
24
+ import LinkSearchList, { testIds as listTestIds } from './link-search-list';
25
+ import LinkSearchError, { testIds as searchErrorTestIds } from './link-search-error';
26
+ import FormFooter, { testIds as formFooterTestIds } from './form-footer';
28
27
  export const RECENT_SEARCH_LIST_SIZE = 5;
29
28
  export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
30
29
  export const testIds = {
@@ -32,18 +31,13 @@ export const testIds = {
32
31
  urlInputField: 'link-url',
33
32
  textInputField: 'link-text',
34
33
  searchIcon: 'link-picker-search-icon',
35
- insertButton: 'link-picker-insert-button',
36
- cancelButton: 'link-picker-cancel-button',
34
+ ...formFooterTestIds,
35
+ ...searchErrorTestIds,
36
+ ...listTestIds,
37
37
  clearUrlButton: 'clear-text',
38
- resultListTitle: 'link-picker-list-title',
39
- emptyResultPage: 'link-search-no-results',
40
- searchResultList: 'link-search-list',
41
- searchResultItem: 'link-search-list-item',
42
- searchResultLoadingIndicator: 'link-picker.results-loading-indicator',
43
38
  urlError: 'link-error',
44
39
  tabList: 'link-picker-tabs',
45
- tabItem: 'link-picker-tab',
46
- searchError: 'link-search-error'
40
+ tabItem: 'link-picker-tab'
47
41
  };
48
42
  const initState = {
49
43
  url: '',
@@ -76,7 +70,7 @@ function LinkPicker({
76
70
  url: initUrl,
77
71
  displayText: initDisplayText
78
72
  }) {
79
- var _queryState$query, _items$length, _errorFallback;
73
+ var _errorFallback;
80
74
 
81
75
  const {
82
76
  createAnalyticsEvent
@@ -104,6 +98,7 @@ function LinkPicker({
104
98
  retry,
105
99
  errorFallback
106
100
  } = usePlugins(queryState, activeTab, plugins);
101
+ const fixListHeightProps = useFixHeight(isLoading);
107
102
  const isEditing = !!initUrl;
108
103
  const selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
109
104
  const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
@@ -189,7 +184,11 @@ function LinkPicker({
189
184
  }
190
185
  }, [activeIndex, items]);
191
186
  const handleInsert = useCallback((url, title, inputType) => {
192
- const event = createAnalyticsEvent(createEventPayload('ui.form.submitted.linkPicker', {}));
187
+ const event = createAnalyticsEvent(createEventPayload('ui.form.submitted.linkPicker', {})); // Clone the event so that it can be emitted for consumer usage
188
+ // This must happen BEFORE the original event is fired!
189
+
190
+ const consumerEvent = event.clone(); // Dispatch the original event to our channel
191
+
193
192
  event.fire(ANALYTICS_CHANNEL);
194
193
  onSubmit({
195
194
  url,
@@ -201,7 +200,7 @@ function LinkPicker({
201
200
  ...(inputType === 'manual' ? {
202
201
  rawUrl: state.url
203
202
  } : {})
204
- });
203
+ }, consumerEvent);
205
204
  }, [displayText, onSubmit, state.url, createAnalyticsEvent]);
206
205
  const handleSelected = useCallback(objectId => {
207
206
  const selectedItem = items === null || items === void 0 ? void 0 : items.find(item => item.objectId === objectId);
@@ -233,9 +232,6 @@ function LinkPicker({
233
232
  });
234
233
  }, [handleInsert, isSelectedItem, selectedItem, url]);
235
234
  const linkPlaceHolder = isActivePlugin ? messages.placeholder : messages.linkPlaceholder;
236
- const linkListTitle = (queryState === null || queryState === void 0 ? void 0 : (_queryState$query = queryState.query) === null || _queryState$query === void 0 ? void 0 : _queryState$query.length) !== 0 ? messages.titleResults : messages.titleRecentlyViewed;
237
- const insertButtonMsg = isEditing ? messages.saveButton : messages.insertButton;
238
- const noResults = (items === null || items === void 0 ? void 0 : items.length) === 0 && !error;
239
235
  const screenReaderDescriptionId = 'search-recent-links-field-description';
240
236
  const linkSearchListId = 'link-picker-search-list';
241
237
  const ariaActiveDescendant = selectedIndex > -1 ? `link-search-list-item-${selectedIndex}` : ''; // Added workaround with a screen reader Announcer specifically for VoiceOver + Safari
@@ -293,7 +289,7 @@ function LinkPicker({
293
289
  onClear: handleClear,
294
290
  onKeyDown: handleKeyDown,
295
291
  onChange: handleChangeText
296
- }), queryState && jsx(Fragment, null, tabs.length > 0 && jsx("div", {
292
+ }), isActivePlugin && !!queryState && jsx(Fragment, null, tabs.length > 0 && jsx("div", {
297
293
  css: tabsWrapperStyles
298
294
  }, jsx(Tabs, {
299
295
  id: testIds.tabList,
@@ -307,59 +303,25 @@ function LinkPicker({
307
303
  }, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
308
304
  key: tab.tabTitle,
309
305
  testId: testIds.tabItem
310
- }, tab.tabTitle))))), !!(items !== null && items !== void 0 && items.length) && jsx("div", {
311
- css: listTitleStyles,
312
- id: testIds.resultListTitle,
313
- "data-testid": testIds.resultListTitle
314
- }, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
315
- id: "fabric.smartcard.linkpicker.suggested.results"
316
- }, url && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
317
- values: {
318
- count: (_items$length = items === null || items === void 0 ? void 0 : items.length) !== null && _items$length !== void 0 ? _items$length : 0
319
- },
320
- "aria-live": "polite",
321
- "aria-atomic": "true"
322
- }))), jsx(LinkSearchList, {
323
- ariaLabelledBy: testIds.resultListTitle,
324
- ariaControls: "fabric.smartcard.linkpicker.suggested.results",
306
+ }, tab.tabTitle))))), jsx("div", _extends({
307
+ css: flexColumnStyles
308
+ }, fixListHeightProps), !error && jsx(LinkSearchList, {
325
309
  id: linkSearchListId,
326
310
  role: "listbox",
327
- items: items !== null && items !== void 0 ? items : [],
311
+ items: items,
328
312
  isLoading: isLoading,
329
313
  selectedIndex: selectedIndex,
330
314
  activeIndex: activeIndex,
331
315
  onSelect: handleSelected,
332
316
  onMouseEnter: handleMouseEnterResultItem,
333
- onMouseLeave: handleMouseLeaveResultItem
334
- }), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, {
335
- testId: testIds.searchError,
336
- header: intl.formatMessage(messages.searchErrorHeader),
337
- description: jsx(FormattedMessage, _extends({}, messages.searchErrorDescription, {
338
- values: {
339
- a: label => jsx(Button, {
340
- appearance: "link",
341
- spacing: "none",
342
- href: CONTACT_SUPPORT_LINK,
343
- target: "_blank",
344
- rel: "noopener noreferrer"
345
- }, label)
346
- }
347
- }))
348
- })), noResults && jsx(LinkSearchNoResults, {
349
- testId: testIds.emptyResultPage,
350
- header: intl.formatMessage(messages.noResults),
351
- description: intl.formatMessage(messages.noResultsDescription)
352
- })), jsx(FormFooter, {
353
- align: "end"
354
- }, jsx(ButtonGroup, null, jsx(Button, {
355
- appearance: "default",
356
- onClick: onCancel,
357
- testId: testIds.cancelButton
358
- }, intl.formatMessage(messages.cancelButton)), jsx(Button, {
359
- type: "submit",
360
- appearance: "primary",
361
- testId: testIds.insertButton
362
- }, intl.formatMessage(insertButtonMsg)))));
317
+ onMouseLeave: handleMouseLeaveResultItem,
318
+ error: error,
319
+ hasSearchTerm: !!(queryState !== null && queryState !== void 0 && queryState.query.length)
320
+ }), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, null)))), jsx(FormFooter, {
321
+ css: !queryState || !(plugins !== null && plugins !== void 0 && plugins.length) ? formFooterMargin : undefined,
322
+ isEditing: isEditing,
323
+ onCancel: onCancel
324
+ }));
363
325
  }
364
326
 
365
327
  export default withLinkPickerAnalyticsContext( /*#__PURE__*/memo(LinkPicker));
@@ -1,10 +1,46 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React from 'react';
2
+
3
+ /** @jsx jsx */
4
+ import { jsx } from '@emotion/react';
5
+ import { useIntl, FormattedMessage, defineMessages } from 'react-intl-next';
6
+ import Button from '@atlaskit/button';
7
+ export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
8
+ export const messages = defineMessages({
9
+ searchErrorHeader: {
10
+ id: 'fabric.linkPicker.search.error.heading',
11
+ defaultMessage: 'We’re having trouble loading data.',
12
+ description: 'Heading message shown when a search throws an error'
13
+ },
14
+ searchErrorDescription: {
15
+ id: 'fabric.linkPicker.search.error.description',
16
+ defaultMessage: 'Refresh the page, or contact <a>Atlassian Support</a> if this keeps happening.',
17
+ description: 'Describes possible actions when search throws an error'
18
+ }
19
+ });
3
20
  import EmptyState from '@atlaskit/empty-state';
4
21
  import GenericErrorSVG from '../../../common/generic-error-svg';
22
+ export const testIds = {
23
+ searchError: 'link-search-error'
24
+ };
5
25
 
6
- const LinkSearchError = props => /*#__PURE__*/React.createElement(EmptyState, _extends({}, props, {
7
- renderImage: () => /*#__PURE__*/React.createElement(GenericErrorSVG, null)
8
- }));
26
+ const LinkSearchError = () => {
27
+ const intl = useIntl();
28
+ return jsx(EmptyState, {
29
+ testId: testIds.searchError,
30
+ header: intl.formatMessage(messages.searchErrorHeader),
31
+ description: jsx(FormattedMessage, _extends({}, messages.searchErrorDescription, {
32
+ values: {
33
+ a: label => jsx(Button, {
34
+ appearance: "link",
35
+ spacing: "none",
36
+ href: CONTACT_SUPPORT_LINK,
37
+ target: "_blank",
38
+ rel: "noopener noreferrer"
39
+ }, label)
40
+ }
41
+ })),
42
+ renderImage: () => jsx(GenericErrorSVG, null)
43
+ });
44
+ };
9
45
 
10
46
  export default LinkSearchError;
@@ -1,11 +1,38 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
1
3
  /** @jsx jsx */
4
+ import { forwardRef, Fragment } from 'react';
2
5
  import { jsx } from '@emotion/react';
6
+ import { defineMessages, FormattedMessage } from 'react-intl-next';
3
7
  import Spinner from '@atlaskit/spinner';
8
+ import VisuallyHidden from '@atlaskit/visually-hidden';
4
9
  import LinkSearchListItem from '../list-item';
5
- import { listContainerStyles, spinnerContainerStyles, listStyles } from './styled';
6
- import { testIds } from '../';
7
-
8
- const LinkSearchList = ({
10
+ import { listContainerStyles, spinnerContainerStyles, listStyles, listTitleStyles } from './styled';
11
+ import LinkSearchNoResults, { testIds as noResultsTestIds } from './link-search-no-results';
12
+ export const messages = defineMessages({
13
+ titleRecentlyViewed: {
14
+ id: 'fabric.linkPicker.listTitle.recentlyViewed',
15
+ defaultMessage: 'Recently Viewed',
16
+ description: 'Describes type of items shown in the list for screen-reader users'
17
+ },
18
+ titleResults: {
19
+ id: 'fabric.linkPicker.listTitle.results',
20
+ defaultMessage: 'Results',
21
+ description: 'Describes type of items shown in the list for screen-reader users'
22
+ },
23
+ searchLinkResults: {
24
+ id: 'fabric.linkPicker.hyperlink.searchLinkResults',
25
+ defaultMessage: '{count, plural, =0 {no results} one {# result} other {# results}} found',
26
+ description: 'Announce search results for screen-reader users.'
27
+ }
28
+ });
29
+ export const testIds = { ...noResultsTestIds,
30
+ resultListTitle: 'link-picker-list-title',
31
+ searchResultItem: 'link-search-list-item',
32
+ searchResultList: 'link-search-list',
33
+ searchResultLoadingIndicator: 'link-picker.results-loading-indicator'
34
+ };
35
+ const LinkSearchList = /*#__PURE__*/forwardRef(({
9
36
  onSelect,
10
37
  onMouseEnter,
11
38
  onMouseLeave,
@@ -16,18 +43,37 @@ const LinkSearchList = ({
16
43
  ariaControls,
17
44
  ariaLabelledBy,
18
45
  role,
19
- id
20
- }) => {
46
+ id,
47
+ hasSearchTerm,
48
+ ...restProps
49
+ }, ref) => {
21
50
  let itemsContent;
22
51
  let loadingContent;
52
+ const linkListTitle = hasSearchTerm ? messages.titleResults : messages.titleRecentlyViewed;
53
+
54
+ if ((items === null || items === void 0 ? void 0 : items.length) === 0) {
55
+ return jsx(LinkSearchNoResults, null);
56
+ }
23
57
 
24
58
  if (items && items.length > 0) {
25
- itemsContent = jsx("ul", {
59
+ itemsContent = jsx(Fragment, null, jsx("div", {
60
+ css: listTitleStyles,
61
+ id: testIds.resultListTitle,
62
+ "data-testid": testIds.resultListTitle
63
+ }, jsx(FormattedMessage, linkListTitle)), jsx(VisuallyHidden, {
64
+ id: "fabric.smartcard.linkpicker.suggested.results"
65
+ }, hasSearchTerm && jsx(FormattedMessage, _extends({}, messages.searchLinkResults, {
66
+ values: {
67
+ count: items.length
68
+ },
69
+ "aria-live": "polite",
70
+ "aria-atomic": "true"
71
+ }))), jsx("ul", {
26
72
  id: id,
27
73
  role: role,
28
74
  css: listStyles,
29
- "aria-controls": ariaControls,
30
- "aria-labelledby": ariaLabelledBy,
75
+ "aria-controls": "fabric.smartcard.linkpicker.suggested.results",
76
+ "aria-labelledby": testIds.resultListTitle,
31
77
  "data-testid": testIds.searchResultList
32
78
  }, items.map((item, index) => jsx(LinkSearchListItem, {
33
79
  id: `${testIds.searchResultItem}-${index}`,
@@ -39,7 +85,7 @@ const LinkSearchList = ({
39
85
  onMouseLeave: onMouseLeave,
40
86
  onSelect: onSelect,
41
87
  key: item.objectId
42
- })));
88
+ }))));
43
89
  }
44
90
 
45
91
  if (isLoading) {
@@ -51,9 +97,9 @@ const LinkSearchList = ({
51
97
  }));
52
98
  }
53
99
 
54
- return jsx("div", {
100
+ return jsx("div", _extends({
101
+ ref: ref,
55
102
  css: listContainerStyles
56
- }, itemsContent, loadingContent);
57
- };
58
-
103
+ }, restProps), itemsContent, loadingContent);
104
+ });
59
105
  export default LinkSearchList;