@atlaskit/link-picker 1.11.1 → 1.12.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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/analytics.codegen.js +1 -1
  3. package/dist/cjs/common/analytics/context.js +8 -2
  4. package/dist/cjs/ui/link-picker/index.js +32 -38
  5. package/dist/cjs/ui/link-picker/list-item/index.js +1 -1
  6. package/dist/cjs/ui/link-picker/text-input/index.js +8 -5
  7. package/dist/cjs/ui/link-picker/track-mount/index.js +47 -0
  8. package/dist/cjs/ui/link-picker/track-tab-viewed/index.js +32 -0
  9. package/dist/cjs/ui/scrolling-tabs/index.js +192 -0
  10. package/dist/cjs/ui/scrolling-tabs/scrolling-tabs.js +123 -0
  11. package/dist/cjs/ui/scrolling-tabs/styles.js +64 -0
  12. package/dist/cjs/version.json +1 -1
  13. package/dist/es2019/analytics.codegen.js +1 -1
  14. package/dist/es2019/common/analytics/context.js +8 -2
  15. package/dist/es2019/ui/link-picker/index.js +29 -33
  16. package/dist/es2019/ui/link-picker/list-item/index.js +1 -1
  17. package/dist/es2019/ui/link-picker/text-input/index.js +4 -1
  18. package/dist/es2019/ui/link-picker/track-mount/index.js +31 -0
  19. package/dist/es2019/ui/link-picker/track-tab-viewed/index.js +19 -0
  20. package/dist/es2019/ui/scrolling-tabs/index.js +127 -0
  21. package/dist/es2019/ui/scrolling-tabs/scrolling-tabs.js +90 -0
  22. package/dist/es2019/ui/scrolling-tabs/styles.js +49 -0
  23. package/dist/es2019/version.json +1 -1
  24. package/dist/esm/analytics.codegen.js +1 -1
  25. package/dist/esm/common/analytics/context.js +8 -2
  26. package/dist/esm/ui/link-picker/index.js +32 -39
  27. package/dist/esm/ui/link-picker/list-item/index.js +1 -1
  28. package/dist/esm/ui/link-picker/text-input/index.js +4 -1
  29. package/dist/esm/ui/link-picker/track-mount/index.js +31 -0
  30. package/dist/esm/ui/link-picker/track-tab-viewed/index.js +19 -0
  31. package/dist/esm/ui/scrolling-tabs/index.js +165 -0
  32. package/dist/esm/ui/scrolling-tabs/scrolling-tabs.js +96 -0
  33. package/dist/esm/ui/scrolling-tabs/styles.js +49 -0
  34. package/dist/esm/version.json +1 -1
  35. package/dist/types/analytics.codegen.d.ts +4 -1
  36. package/dist/types/ui/link-picker/index.d.ts +3 -2
  37. package/dist/types/ui/link-picker/link-search-list/index.d.ts +0 -1
  38. package/dist/types/ui/link-picker/text-input/index.d.ts +4 -0
  39. package/dist/types/ui/link-picker/track-mount/index.d.ts +5 -0
  40. package/dist/types/ui/link-picker/track-tab-viewed/index.d.ts +4 -0
  41. package/dist/types/ui/scrolling-tabs/index.d.ts +7 -0
  42. package/dist/types/ui/scrolling-tabs/scrolling-tabs.d.ts +11 -0
  43. package/dist/types/ui/scrolling-tabs/styles.d.ts +6 -0
  44. package/dist/types-ts4.0/analytics.codegen.d.ts +4 -1
  45. package/dist/types-ts4.0/ui/link-picker/index.d.ts +3 -2
  46. package/dist/types-ts4.0/ui/link-picker/link-search-list/index.d.ts +0 -1
  47. package/dist/types-ts4.0/ui/link-picker/text-input/index.d.ts +4 -0
  48. package/dist/types-ts4.0/ui/link-picker/track-mount/index.d.ts +5 -0
  49. package/dist/types-ts4.0/ui/link-picker/track-tab-viewed/index.d.ts +4 -0
  50. package/dist/types-ts4.0/ui/scrolling-tabs/index.d.ts +7 -0
  51. package/dist/types-ts4.0/ui/scrolling-tabs/scrolling-tabs.d.ts +11 -0
  52. package/dist/types-ts4.0/ui/scrolling-tabs/styles.d.ts +6 -0
  53. package/package.json +20 -15
  54. package/report.api.md +21 -12
  55. package/tmp/api-report-tmp.d.ts +95 -0
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.scrollingContainerStyles = exports.nextButtonStyles = exports.containerStyles = exports.buttonContainerStyles = exports.backButtonStyles = void 0;
7
+
8
+ var _react = require("@emotion/react");
9
+
10
+ var _tokens = require("@atlaskit/tokens");
11
+
12
+ var _colors = require("@atlaskit/theme/colors");
13
+
14
+ var scrollingContainerStyles = (0, _react.css)({
15
+ overflowX: 'auto',
16
+ scrollBehavior: 'smooth',
17
+ scrollPadding: '0 24px',
18
+ scrollbarWidth: 'none',
19
+ '&::-webkit-scrollbar': {
20
+ display: 'none'
21
+ },
22
+ '&::before': {
23
+ content: '""',
24
+ borderRadius: 2,
25
+ bottom: 0,
26
+ margin: 0,
27
+ position: 'absolute',
28
+ width: 'inherit',
29
+ left: 8,
30
+ right: 8,
31
+ height: 2,
32
+ backgroundColor: (0, _tokens.token)('color.border', _colors.N30)
33
+ }
34
+ });
35
+ exports.scrollingContainerStyles = scrollingContainerStyles;
36
+ var containerStyles = (0, _react.css)({
37
+ position: 'relative',
38
+ '[role="tablist"]': {
39
+ '&::before': {
40
+ display: 'none'
41
+ }
42
+ },
43
+ // Overrides Atlaskit tabs styles to stop overflowing with ellipsis
44
+ '[role="tab"]': {
45
+ overflow: 'unset !important',
46
+ textOverflow: 'unset !important'
47
+ }
48
+ });
49
+ exports.containerStyles = containerStyles;
50
+ var buttonContainerStyles = (0, _react.css)({
51
+ position: 'absolute',
52
+ top: 4,
53
+ zIndex: 999,
54
+ backgroundColor: (0, _tokens.token)('elevation.surface', _colors.N0)
55
+ });
56
+ exports.buttonContainerStyles = buttonContainerStyles;
57
+ var backButtonStyles = (0, _react.css)([buttonContainerStyles, {
58
+ left: 0
59
+ }]);
60
+ exports.backButtonStyles = backButtonStyles;
61
+ var nextButtonStyles = (0, _react.css)([buttonContainerStyles, {
62
+ right: 0
63
+ }]);
64
+ exports.nextButtonStyles = nextButtonStyles;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "sideEffects": false
5
5
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Generates analytics utilities from the package analytics spec yaml
5
5
  *
6
- * @codegen <<SignedSource::05d8b1c9e46066efd0d11fa9d19c32a9>>
6
+ * @codegen <<SignedSource::c0fb3bb39f50f3a7c35ebd90e8903280>>
7
7
  * @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
8
8
  */
9
9
  function createEventPayload(eventKey, attributes) {
@@ -9,7 +9,8 @@ const DEFAULT_CONTEXT_ATTRIBUTES = {
9
9
  linkFieldContentInputMethod: null,
10
10
  linkFieldContentInputSource: null,
11
11
  displayTextFieldContent: null,
12
- displayTextFieldContentInputMethod: null
12
+ displayTextFieldContentInputMethod: null,
13
+ tab: null
13
14
  };
14
15
  const LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
15
16
  trackAttribute: () => {},
@@ -18,13 +19,18 @@ const LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
18
19
  });
19
20
  /**
20
21
  * TODO: Should we hook this into the form state/reducer?
22
+ * This function continues to duplicate / assume logic that runs in the picker
21
23
  */
22
24
 
23
25
  const contextAttributesFromIntialProps = props => {
26
+ var _props$plugins$0$tabK, _props$plugins, _props$plugins$;
27
+
24
28
  return {
25
29
  linkState: normalizeUrl(props.url) ? 'editLink' : 'newLink',
26
30
  linkFieldContent: normalizeUrl(props.url) ? 'url' : null,
27
- displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null
31
+ displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null,
32
+ // TODO: Assumes we alway load the first plugin as the current tab
33
+ tab: (_props$plugins$0$tabK = (_props$plugins = props.plugins) === null || _props$plugins === void 0 ? void 0 : (_props$plugins$ = _props$plugins[0]) === null || _props$plugins$ === void 0 ? void 0 : _props$plugins$.tabKey) !== null && _props$plugins$0$tabK !== void 0 ? _props$plugins$0$tabK : null
28
34
  };
29
35
  };
30
36
  /**
@@ -1,7 +1,7 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
 
3
3
  /** @jsx jsx */
4
- import { Fragment, useCallback, useEffect, useLayoutEffect, useReducer, memo } from 'react';
4
+ import { Fragment, useCallback, useLayoutEffect, useReducer, memo } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
6
  import { useIntl, FormattedMessage } from 'react-intl-next';
7
7
  import { useAnalyticsEvents } from '@atlaskit/analytics-next';
@@ -16,16 +16,17 @@ import { usePlugins } from '../../services/use-plugins';
16
16
  import { useSearchQuery } from '../../services/use-search-query';
17
17
  import useFixHeight from '../../controllers/use-fix-height';
18
18
  import { searchMessages, linkMessages, formMessages, linkTextMessages } from './messages';
19
- import TextInput from './text-input';
19
+ import TextInput, { testIds as textFieldTestIds } from './text-input';
20
20
  import { rootContainerStyles, searchIconStyles, tabsWrapperStyles, flexColumnStyles, formFooterMargin } from './styled';
21
21
  import { browser } from './browser';
22
22
  import Announcer from './announcer';
23
+ import ScrollingTabList from '../scrolling-tabs';
23
24
  import LinkSearchList, { testIds as listTestIds } from './link-search-list';
24
25
  import LinkSearchError, { testIds as searchErrorTestIds } from './link-search-error';
25
26
  import FormFooter, { testIds as formFooterTestIds } from './form-footer';
26
27
  import { getDataSource, getScreenReaderText } from './utils';
27
- import { succeedUfoExperience, ufoExperience } from '../../common/analytics/experiences';
28
- import { useLinkPickerSessionId } from '../../controllers/session-provider';
28
+ import TrackTabViewed from './track-tab-viewed';
29
+ import TrackMount from './track-mount';
29
30
  export const RECENT_SEARCH_LIST_SIZE = 5;
30
31
  export const testIds = {
31
32
  linkPickerRoot: 'link-picker-root',
@@ -36,8 +37,7 @@ export const testIds = {
36
37
  ...formFooterTestIds,
37
38
  ...searchErrorTestIds,
38
39
  ...listTestIds,
39
- clearUrlButton: 'clear-text',
40
- urlError: 'link-error',
40
+ ...textFieldTestIds,
41
41
  tabList: 'link-picker-tabs',
42
42
  tabItem: 'link-picker-tab'
43
43
  };
@@ -87,7 +87,8 @@ function LinkPicker({
87
87
  onContentResize,
88
88
  plugins,
89
89
  url: initUrl,
90
- displayText: initDisplayText
90
+ displayText: initDisplayText,
91
+ featureFlags
91
92
  }) {
92
93
  var _errorFallback;
93
94
 
@@ -126,20 +127,6 @@ function LinkPicker({
126
127
  trackAttribute,
127
128
  getAttributes
128
129
  } = useLinkPickerAnalytics();
129
- const linkPickerSessionId = useLinkPickerSessionId();
130
- useLayoutEffect(() => {
131
- succeedUfoExperience(ufoExperience.mounted, linkPickerSessionId);
132
- }, [linkPickerSessionId]);
133
- useEffect(() => {
134
- // Anything in here is fired on component mount.
135
- const event = createAnalyticsEvent(createEventPayload('ui.inlineDialog.viewed.linkPicker', {}));
136
- event.fire(ANALYTICS_CHANNEL);
137
- return () => {
138
- // Anything in here is fired on component unmount.
139
- const event = createAnalyticsEvent(createEventPayload('ui.inlineDialog.closed.linkPicker', {}));
140
- event.fire(ANALYTICS_CHANNEL);
141
- };
142
- }, [createAnalyticsEvent]);
143
130
  useLayoutEffect(() => {
144
131
  if (onContentResize) {
145
132
  onContentResize();
@@ -285,6 +272,17 @@ function LinkPicker({
285
272
  invalidUrl: true
286
273
  });
287
274
  }, [handleInsert, isSelectedItem, selectedItem, url]);
275
+ const handleTabChange = useCallback(activeTab => {
276
+ var _plugins$activeTab$ta, _plugins$activeTab;
277
+
278
+ dispatch({
279
+ selectedIndex: -1,
280
+ activeIndex: -1,
281
+ invalidUrl: false,
282
+ activeTab
283
+ });
284
+ trackAttribute('tab', (_plugins$activeTab$ta = plugins === null || plugins === void 0 ? void 0 : (_plugins$activeTab = plugins[activeTab]) === null || _plugins$activeTab === void 0 ? void 0 : _plugins$activeTab.tabKey) !== null && _plugins$activeTab$ta !== void 0 ? _plugins$activeTab$ta : null);
285
+ }, [dispatch, plugins, trackAttribute]);
288
286
  const messages = isActivePlugin ? searchMessages : linkMessages;
289
287
  const screenReaderDescriptionId = 'search-recent-links-field-description';
290
288
  const linkSearchListId = 'link-picker-search-list';
@@ -300,11 +298,15 @@ function LinkPicker({
300
298
  size: "medium",
301
299
  label: ''
302
300
  }));
301
+ const tabList = jsx(TabList, null, tabs.map(tab => jsx(Tab, {
302
+ key: tab.tabTitle,
303
+ testId: testIds.tabItem
304
+ }, tab.tabTitle)));
303
305
  return jsx("form", {
304
306
  "data-testid": testIds.linkPicker,
305
307
  css: rootContainerStyles,
306
308
  onSubmit: handleSubmit
307
- }, screenReaderText && jsx(Announcer, {
309
+ }, jsx(TrackMount, null), screenReaderText && jsx(Announcer, {
308
310
  ariaLive: "assertive",
309
311
  text: screenReaderText,
310
312
  ariaRelevant: "additions",
@@ -348,16 +350,11 @@ function LinkPicker({
348
350
  }, jsx(Tabs, {
349
351
  id: testIds.tabList,
350
352
  testId: testIds.tabList,
351
- onChange: activeTab => dispatch({
352
- selectedIndex: -1,
353
- activeIndex: -1,
354
- invalidUrl: false,
355
- activeTab
356
- })
357
- }, jsx(TabList, null, tabs.map(tab => jsx(Tab, {
358
- key: tab.tabTitle,
359
- testId: testIds.tabItem
360
- }, tab.tabTitle))))), jsx("div", _extends({
353
+ selected: activeTab,
354
+ onChange: handleTabChange
355
+ }, featureFlags !== null && featureFlags !== void 0 && featureFlags.scrollingTabs ? jsx(ScrollingTabList, null, tabList) : tabList), jsx(TrackTabViewed, {
356
+ activeTab: activeTab
357
+ })), jsx("div", _extends({
361
358
  css: flexColumnStyles
362
359
  }, fixListHeightProps), !error && jsx(LinkSearchList, {
363
360
  id: linkSearchListId,
@@ -369,7 +366,6 @@ function LinkPicker({
369
366
  onSelect: handleSelected,
370
367
  onMouseEnter: handleMouseEnterResultItem,
371
368
  onMouseLeave: handleMouseLeaveResultItem,
372
- error: error,
373
369
  hasSearchTerm: !!(queryState !== null && queryState !== void 0 && queryState.query.length)
374
370
  }), error && ((_errorFallback = errorFallback === null || errorFallback === void 0 ? void 0 : errorFallback(error, retry)) !== null && _errorFallback !== void 0 ? _errorFallback : jsx(LinkSearchError, null)))), jsx(FormFooter, {
375
371
  css: !queryState || !(plugins !== null && plugins !== void 0 && plugins.length) ? formFooterMargin : undefined,
@@ -4,7 +4,7 @@ import { jsx } from '@emotion/react';
4
4
  import { injectIntl } from 'react-intl-next';
5
5
  import { transformTimeStamp } from '../transformTimeStamp';
6
6
  import { itemNameStyles, itemIconStyles, listItemContextStyles, listItemNameStyles, composeListItemStyles, imgStyles, listItemContainerStyles, listItemContainerInnerStyles } from './styled';
7
- import { testIds } from '../';
7
+ import { testIds } from '..';
8
8
 
9
9
  const LinkSearchListItem = ({
10
10
  item,
@@ -8,8 +8,11 @@ import Tooltip from '@atlaskit/tooltip';
8
8
  import Textfield from '@atlaskit/textfield';
9
9
  import Selectclear from '@atlaskit/icon/glyph/select-clear';
10
10
  import { isRedoEvent, isUndoEvent } from '../utils';
11
- import { testIds } from '../';
12
11
  import { clearTextButtonStyles, fieldStyles } from './styled';
12
+ export const testIds = {
13
+ urlError: 'link-error',
14
+ clearUrlButton: 'clear-text'
15
+ };
13
16
 
14
17
  const TextInput = ({
15
18
  name,
@@ -0,0 +1,31 @@
1
+ /** @jsx jsx */
2
+ import { useLayoutEffect } from 'react';
3
+ import { useAnalyticsEvents } from '@atlaskit/analytics-next';
4
+ import { succeedUfoExperience, ufoExperience } from '../../../common/analytics/experiences';
5
+ import createEventPayload from '../../../analytics.codegen';
6
+ import { ANALYTICS_CHANNEL } from '../../../common/constants';
7
+ import { useLinkPickerSessionId } from '../../../controllers/session-provider';
8
+ /**
9
+ * UFO + Analytics tracking for component mount (and unmount)
10
+ */
11
+
12
+ function TrackMount() {
13
+ const {
14
+ createAnalyticsEvent
15
+ } = useAnalyticsEvents();
16
+ const linkPickerSessionId = useLinkPickerSessionId();
17
+ useLayoutEffect(() => {
18
+ succeedUfoExperience(ufoExperience.mounted, linkPickerSessionId);
19
+ }, [linkPickerSessionId]);
20
+ useLayoutEffect(() => {
21
+ // Anything in here is fired on component mount.
22
+ createAnalyticsEvent(createEventPayload('ui.inlineDialog.viewed.linkPicker', {})).fire(ANALYTICS_CHANNEL);
23
+ return () => {
24
+ // Anything in here is fired on component unmount.
25
+ createAnalyticsEvent(createEventPayload('ui.inlineDialog.closed.linkPicker', {})).fire(ANALYTICS_CHANNEL);
26
+ };
27
+ }, [createAnalyticsEvent]);
28
+ return null;
29
+ }
30
+
31
+ export default TrackMount;
@@ -0,0 +1,19 @@
1
+ /** @jsx jsx */
2
+ import { useLayoutEffect } from 'react';
3
+ import { useAnalyticsEvents } from '@atlaskit/analytics-next';
4
+ import createEventPayload from '../../../analytics.codegen';
5
+ import { ANALYTICS_CHANNEL } from '../../../common/constants';
6
+
7
+ function TrackTabViewed({
8
+ activeTab
9
+ }) {
10
+ const {
11
+ createAnalyticsEvent
12
+ } = useAnalyticsEvents();
13
+ useLayoutEffect(() => {
14
+ createAnalyticsEvent(createEventPayload('ui.tab.viewed', {})).fire(ANALYTICS_CHANNEL);
15
+ }, [activeTab, createAnalyticsEvent]);
16
+ return null;
17
+ }
18
+
19
+ export default TrackTabViewed;
@@ -0,0 +1,127 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
3
+ /** @jsx jsx */
4
+ import React, { useRef, useMemo, useState, useCallback } from 'react';
5
+ import { jsx } from '@emotion/react';
6
+ import Button from '@atlaskit/button';
7
+ import ChevronLeftIcon from '@atlaskit/icon/glyph/chevron-left';
8
+ import ChevronRightIcon from '@atlaskit/icon/glyph/chevron-right';
9
+ import { scrollBack, scrollForward, calculateConditionalButtons, getTabList, createGhost } from './scrolling-tabs';
10
+ import { containerStyles, scrollingContainerStyles, nextButtonStyles, backButtonStyles } from './styles';
11
+
12
+ function isTouchDevice() {
13
+ return 'ontouchstart' in window || // eslint-disable-next-line compat/compat
14
+ navigator.maxTouchPoints > 0;
15
+ }
16
+
17
+ const initialConditionalButtonsState = {
18
+ back: false,
19
+ forward: false
20
+ };
21
+ export const ScrollingTabList = props => {
22
+ const ref = useRef(null);
23
+ const [conditionalButtons, setConditionalButtons] = useState(initialConditionalButtonsState);
24
+ const ghost = useMemo(() => createGhost(), []);
25
+ const onTabClick = useCallback(e => {
26
+ const target = e.currentTarget;
27
+
28
+ if (target) {
29
+ target.scrollIntoView({
30
+ behavior: 'smooth'
31
+ });
32
+ }
33
+ }, []);
34
+ React.useLayoutEffect(() => {
35
+ const container = ref.current;
36
+ let scrollingContainer;
37
+ let tabs;
38
+
39
+ const handleConditionalButtonsChange = () => {
40
+ const buttons = calculateConditionalButtons(scrollingContainer, isTouchDevice());
41
+ setConditionalButtons(buttons);
42
+ };
43
+
44
+ const observerCallback = mutationList => {
45
+ const tablist = getTabList(ref);
46
+
47
+ for (const mutation of mutationList) {
48
+ if (mutation.type === 'childList') {
49
+ const addedNodes = Array.from(mutation.addedNodes);
50
+ const found = addedNodes.find(node => node.getAttribute('role') === 'tab');
51
+
52
+ if (found && tablist) {
53
+ ghost.remove();
54
+ const tabs = Array.from(tablist.children);
55
+ tabs.forEach(tab => {
56
+ tab.removeEventListener('click', onTabClick);
57
+ tab.addEventListener('click', onTabClick);
58
+ });
59
+ tablist.appendChild(ghost);
60
+ handleConditionalButtonsChange();
61
+ }
62
+ }
63
+ }
64
+ };
65
+
66
+ const observer = new MutationObserver(observerCallback);
67
+
68
+ if (container) {
69
+ const tablist = getTabList(ref);
70
+ scrollingContainer = tablist === null || tablist === void 0 ? void 0 : tablist.parentElement;
71
+ observer.observe(container, {
72
+ attributes: false,
73
+ childList: true,
74
+ subtree: true
75
+ });
76
+
77
+ if (scrollingContainer instanceof HTMLElement && tablist) {
78
+ tablist.appendChild(ghost);
79
+ tabs = Array.from(tablist.children);
80
+ tabs.forEach(tab => tab.addEventListener('click', onTabClick));
81
+ handleConditionalButtonsChange();
82
+ scrollingContainer.addEventListener('scroll', handleConditionalButtonsChange);
83
+ }
84
+
85
+ return () => {
86
+ if (scrollingContainer) {
87
+ scrollingContainer.removeEventListener('scroll', handleConditionalButtonsChange);
88
+ }
89
+
90
+ if (tabs.length) {
91
+ tabs.forEach(tab => tab.removeEventListener('click', onTabClick));
92
+ }
93
+ };
94
+ }
95
+ }, [onTabClick, ghost, ref]);
96
+ const buttonProps = {
97
+ appearance: 'subtle',
98
+ spacing: 'none'
99
+ };
100
+ return jsx("div", {
101
+ css: containerStyles,
102
+ ref: ref,
103
+ "data-testid": "scrolling-tabs"
104
+ }, conditionalButtons.back && jsx("div", {
105
+ className: "back",
106
+ css: backButtonStyles
107
+ }, jsx(Button, _extends({
108
+ "data-test-id": "back"
109
+ }, buttonProps, {
110
+ iconBefore: jsx(ChevronLeftIcon, {
111
+ label: "back"
112
+ }),
113
+ onClick: () => scrollBack(ref)
114
+ }))), jsx("div", {
115
+ css: scrollingContainerStyles
116
+ }, props.children), conditionalButtons.forward && jsx("div", {
117
+ css: nextButtonStyles
118
+ }, jsx(Button, _extends({
119
+ "data-test-id": "forward"
120
+ }, buttonProps, {
121
+ iconBefore: jsx(ChevronRightIcon, {
122
+ label: "forward"
123
+ }),
124
+ onClick: () => scrollForward(ref)
125
+ }))));
126
+ };
127
+ export default ScrollingTabList;
@@ -0,0 +1,90 @@
1
+ const BUTTON_SIZE = 24;
2
+ const GUTTER = 48;
3
+ const GHOST_WIDTH = 342 - GUTTER - 40;
4
+ export const scrollBack = ref => {
5
+ moveTabsScroll(ref, findPrevious);
6
+ };
7
+ export const scrollForward = ref => {
8
+ moveTabsScroll(ref, findNext);
9
+ };
10
+ export const calculateConditionalButtons = (container, hideButtons) => {
11
+ const initialState = {
12
+ back: false,
13
+ forward: false
14
+ };
15
+
16
+ if (!container || hideButtons) {
17
+ return initialState;
18
+ }
19
+
20
+ const maxScrollValue = container.scrollWidth - container.clientWidth;
21
+ return {
22
+ back: container.scrollLeft > 0,
23
+ forward: container.scrollLeft < maxScrollValue - GHOST_WIDTH
24
+ };
25
+ };
26
+ export const createGhost = () => {
27
+ const ghost = document.createElement('div');
28
+ ghost.setAttribute('id', 'scrolling-tabs-ghost');
29
+ ghost.style.minWidth = `${GHOST_WIDTH}px`;
30
+ ghost.style.pointerEvents = 'none';
31
+ return ghost;
32
+ };
33
+
34
+ const moveTabsScroll = (ref, findFn) => {
35
+ const container = getContainer(ref);
36
+
37
+ if (container) {
38
+ const tablist = container.children[0];
39
+ const tabs = [...tablist.children];
40
+
41
+ if (tabs.length > 0) {
42
+ const target = findFn(container, tabs);
43
+
44
+ if (target) {
45
+ const left = container.scrollLeft + target.getBoundingClientRect().left - container.getBoundingClientRect().left - BUTTON_SIZE;
46
+ scrollTo(container, left);
47
+ }
48
+ }
49
+ }
50
+ };
51
+
52
+ const scrollTo = (container, left) => {
53
+ container.scrollTo({
54
+ left
55
+ });
56
+ };
57
+
58
+ export const getTabList = ref => {
59
+ var _ref$current;
60
+
61
+ return (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.querySelector('[role="tablist"]');
62
+ };
63
+ export const getContainer = ref => {
64
+ var _getTabList;
65
+
66
+ return (_getTabList = getTabList(ref)) === null || _getTabList === void 0 ? void 0 : _getTabList.parentElement;
67
+ };
68
+
69
+ const findPrevious = (container, tabs) => {
70
+ const previousTabs = tabs.filter(tab => {
71
+ const [containerRect, tabRect] = [container.getBoundingClientRect(), tab.getBoundingClientRect()];
72
+ const [containerArea, tabArea] = [containerRect.left + container.scrollLeft - GUTTER, container.scrollLeft + tabRect.left + tabRect.width];
73
+ return containerArea > tabArea;
74
+ });
75
+
76
+ if (previousTabs.length < 3) {
77
+ return previousTabs[0];
78
+ }
79
+
80
+ return previousTabs[previousTabs.length - 1];
81
+ };
82
+
83
+ const findNext = (container, tabs) => {
84
+ const containerRect = container.getBoundingClientRect();
85
+ return tabs.find(tab => {
86
+ const tabRect = tab.getBoundingClientRect();
87
+ const [containerArea, tabArea] = [container.clientWidth + containerRect.left - GUTTER, tabRect.left + tabRect.width];
88
+ return containerArea < tabArea;
89
+ });
90
+ };
@@ -0,0 +1,49 @@
1
+ import { css } from '@emotion/react';
2
+ import { token } from '@atlaskit/tokens';
3
+ import { N0, N30 } from '@atlaskit/theme/colors';
4
+ export const scrollingContainerStyles = css({
5
+ overflowX: 'auto',
6
+ scrollBehavior: 'smooth',
7
+ scrollPadding: '0 24px',
8
+ scrollbarWidth: 'none',
9
+ '&::-webkit-scrollbar': {
10
+ display: 'none'
11
+ },
12
+ '&::before': {
13
+ content: '""',
14
+ borderRadius: 2,
15
+ bottom: 0,
16
+ margin: 0,
17
+ position: 'absolute',
18
+ width: 'inherit',
19
+ left: 8,
20
+ right: 8,
21
+ height: 2,
22
+ backgroundColor: token('color.border', N30)
23
+ }
24
+ });
25
+ export const containerStyles = css({
26
+ position: 'relative',
27
+ '[role="tablist"]': {
28
+ '&::before': {
29
+ display: 'none'
30
+ }
31
+ },
32
+ // Overrides Atlaskit tabs styles to stop overflowing with ellipsis
33
+ '[role="tab"]': {
34
+ overflow: 'unset !important',
35
+ textOverflow: 'unset !important'
36
+ }
37
+ });
38
+ export const buttonContainerStyles = css({
39
+ position: 'absolute',
40
+ top: 4,
41
+ zIndex: 999,
42
+ backgroundColor: token('elevation.surface', N0)
43
+ });
44
+ export const backButtonStyles = css([buttonContainerStyles, {
45
+ left: 0
46
+ }]);
47
+ export const nextButtonStyles = css([buttonContainerStyles, {
48
+ right: 0
49
+ }]);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/link-picker",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "sideEffects": false
5
5
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Generates analytics utilities from the package analytics spec yaml
5
5
  *
6
- * @codegen <<SignedSource::05d8b1c9e46066efd0d11fa9d19c32a9>>
6
+ * @codegen <<SignedSource::c0fb3bb39f50f3a7c35ebd90e8903280>>
7
7
  * @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
8
8
  */
9
9
  function createEventPayload(eventKey, attributes) {
@@ -16,7 +16,8 @@ var DEFAULT_CONTEXT_ATTRIBUTES = {
16
16
  linkFieldContentInputMethod: null,
17
17
  linkFieldContentInputSource: null,
18
18
  displayTextFieldContent: null,
19
- displayTextFieldContentInputMethod: null
19
+ displayTextFieldContentInputMethod: null,
20
+ tab: null
20
21
  };
21
22
  var LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
22
23
  trackAttribute: function trackAttribute() {},
@@ -27,13 +28,18 @@ var LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
27
28
  });
28
29
  /**
29
30
  * TODO: Should we hook this into the form state/reducer?
31
+ * This function continues to duplicate / assume logic that runs in the picker
30
32
  */
31
33
 
32
34
  var contextAttributesFromIntialProps = function contextAttributesFromIntialProps(props) {
35
+ var _props$plugins$0$tabK, _props$plugins, _props$plugins$;
36
+
33
37
  return {
34
38
  linkState: normalizeUrl(props.url) ? 'editLink' : 'newLink',
35
39
  linkFieldContent: normalizeUrl(props.url) ? 'url' : null,
36
- displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null
40
+ displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null,
41
+ // TODO: Assumes we alway load the first plugin as the current tab
42
+ tab: (_props$plugins$0$tabK = (_props$plugins = props.plugins) === null || _props$plugins === void 0 ? void 0 : (_props$plugins$ = _props$plugins[0]) === null || _props$plugins$ === void 0 ? void 0 : _props$plugins$.tabKey) !== null && _props$plugins$0$tabK !== void 0 ? _props$plugins$0$tabK : null
37
43
  };
38
44
  };
39
45
  /**