@atlaskit/flag 14.4.1
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 +1244 -0
- package/LICENSE +13 -0
- package/README.md +13 -0
- package/__perf__/autodismiss.tsx +18 -0
- package/__perf__/default.tsx +18 -0
- package/__perf__/withFlagGroup.tsx +20 -0
- package/auto-dismiss-flag/package.json +7 -0
- package/constants/package.json +7 -0
- package/dist/cjs/auto-dismiss-flag.js +89 -0
- package/dist/cjs/constants.js +8 -0
- package/dist/cjs/expander.js +41 -0
- package/dist/cjs/flag-actions.js +64 -0
- package/dist/cjs/flag-group.js +107 -0
- package/dist/cjs/flag-provider.js +119 -0
- package/dist/cjs/flag.js +219 -0
- package/dist/cjs/index.js +51 -0
- package/dist/cjs/theme.js +160 -0
- package/dist/cjs/types.js +10 -0
- package/dist/cjs/version.json +5 -0
- package/dist/es2019/auto-dismiss-flag.js +67 -0
- package/dist/es2019/constants.js +1 -0
- package/dist/es2019/expander.js +33 -0
- package/dist/es2019/flag-actions.js +73 -0
- package/dist/es2019/flag-group.js +130 -0
- package/dist/es2019/flag-provider.js +68 -0
- package/dist/es2019/flag.js +245 -0
- package/dist/es2019/index.js +4 -0
- package/dist/es2019/theme.js +119 -0
- package/dist/es2019/types.js +2 -0
- package/dist/es2019/version.json +5 -0
- package/dist/esm/auto-dismiss-flag.js +67 -0
- package/dist/esm/constants.js +1 -0
- package/dist/esm/expander.js +29 -0
- package/dist/esm/flag-actions.js +50 -0
- package/dist/esm/flag-group.js +85 -0
- package/dist/esm/flag-provider.js +92 -0
- package/dist/esm/flag.js +195 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/theme.js +129 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/version.json +5 -0
- package/dist/types/auto-dismiss-flag.d.ts +4 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/expander.d.ts +9 -0
- package/dist/types/flag-actions.d.ts +14 -0
- package/dist/types/flag-group.d.ts +27 -0
- package/dist/types/flag-provider.d.ts +26 -0
- package/dist/types/flag.d.ts +3 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/theme.d.ts +9 -0
- package/dist/types/types.d.ts +79 -0
- package/expander/package.json +7 -0
- package/extract-react-types/show-flag-args.tsx +5 -0
- package/flag/package.json +7 -0
- package/flag-actions/package.json +7 -0
- package/flag-group/package.json +7 -0
- package/flag-provider/package.json +7 -0
- package/package.json +80 -0
- package/theme/package.json +7 -0
- package/types/package.json +7 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
|
|
4
|
+
import Flag from './flag';
|
|
5
|
+
import { useFlagGroup } from './flag-group';
|
|
6
|
+
const packageName = "@atlaskit/flag";
|
|
7
|
+
const packageVersion = "14.4.1";
|
|
8
|
+
export const AUTO_DISMISS_SECONDS = 8;
|
|
9
|
+
|
|
10
|
+
function noop() {}
|
|
11
|
+
|
|
12
|
+
const AutoDismissFlag = props => {
|
|
13
|
+
const {
|
|
14
|
+
id,
|
|
15
|
+
analyticsContext,
|
|
16
|
+
onDismissed: onDismissedProp = noop
|
|
17
|
+
} = props;
|
|
18
|
+
const autoDismissTimer = useRef(null);
|
|
19
|
+
const {
|
|
20
|
+
onDismissed: onDismissedFromFlagGroup,
|
|
21
|
+
isDismissAllowed
|
|
22
|
+
} = useFlagGroup();
|
|
23
|
+
const onDismissed = useCallback((id, analyticsEvent) => {
|
|
24
|
+
onDismissedProp(id, analyticsEvent);
|
|
25
|
+
onDismissedFromFlagGroup(id, analyticsEvent);
|
|
26
|
+
}, [onDismissedProp, onDismissedFromFlagGroup]);
|
|
27
|
+
const onDismissedAnalytics = usePlatformLeafEventHandler({
|
|
28
|
+
fn: onDismissed,
|
|
29
|
+
action: 'dismissed',
|
|
30
|
+
analyticsData: analyticsContext,
|
|
31
|
+
componentName: 'flag',
|
|
32
|
+
packageName,
|
|
33
|
+
packageVersion
|
|
34
|
+
});
|
|
35
|
+
const isAutoDismissAllowed = isDismissAllowed && onDismissed;
|
|
36
|
+
const dismissFlag = useCallback(() => {
|
|
37
|
+
if (isAutoDismissAllowed) {
|
|
38
|
+
onDismissedAnalytics(id);
|
|
39
|
+
}
|
|
40
|
+
}, [id, onDismissedAnalytics, isAutoDismissAllowed]);
|
|
41
|
+
const stopAutoDismissTimer = useCallback(() => {
|
|
42
|
+
if (autoDismissTimer.current) {
|
|
43
|
+
clearTimeout(autoDismissTimer.current);
|
|
44
|
+
autoDismissTimer.current = null;
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
const startAutoDismissTimer = useCallback(() => {
|
|
48
|
+
if (!isAutoDismissAllowed) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stopAutoDismissTimer();
|
|
53
|
+
autoDismissTimer.current = window.setTimeout(dismissFlag, AUTO_DISMISS_SECONDS * 1000);
|
|
54
|
+
}, [dismissFlag, stopAutoDismissTimer, isAutoDismissAllowed]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
startAutoDismissTimer();
|
|
57
|
+
return stopAutoDismissTimer;
|
|
58
|
+
}, [startAutoDismissTimer, stopAutoDismissTimer]);
|
|
59
|
+
return /*#__PURE__*/React.createElement(Flag, _extends({}, props, {
|
|
60
|
+
onMouseOver: stopAutoDismissTimer,
|
|
61
|
+
onFocus: stopAutoDismissTimer,
|
|
62
|
+
onMouseOut: startAutoDismissTimer,
|
|
63
|
+
onBlur: startAutoDismissTimer
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default AutoDismissFlag;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_APPEARANCE = 'normal';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { css, jsx } from '@emotion/core';
|
|
3
|
+
import { ExitingPersistence, FadeIn } from '@atlaskit/motion';
|
|
4
|
+
import { gridSize } from '@atlaskit/theme/constants';
|
|
5
|
+
const paddingLeft = gridSize() * 5;
|
|
6
|
+
|
|
7
|
+
const Expander = ({
|
|
8
|
+
children,
|
|
9
|
+
isExpanded,
|
|
10
|
+
testId
|
|
11
|
+
}) => {
|
|
12
|
+
// Need to always render the ExpanderInternal otherwise the
|
|
13
|
+
// reveal transition doesn't happen. We can't use CSS animation for
|
|
14
|
+
// the the reveal because we don't know the height of the content.
|
|
15
|
+
return jsx("div", {
|
|
16
|
+
"aria-hidden": !isExpanded,
|
|
17
|
+
css: css`
|
|
18
|
+
max-height: ${isExpanded ? 150 : 0}px;
|
|
19
|
+
transition: max-height 0.3s;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex: 1 1 100%;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
padding: 0 0 0 ${paddingLeft}px;
|
|
26
|
+
`,
|
|
27
|
+
"data-testid": testId && `${testId}-expander`
|
|
28
|
+
}, jsx(ExitingPersistence, {
|
|
29
|
+
appear: true
|
|
30
|
+
}, isExpanded && jsx(FadeIn, null, props => jsx("div", props, children))));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default Expander;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { css, jsx } from '@emotion/core';
|
|
3
|
+
import Button from '@atlaskit/button/custom-theme-button';
|
|
4
|
+
import { gridSize as getGridSize } from '@atlaskit/theme/constants';
|
|
5
|
+
import { DEFAULT_APPEARANCE } from './constants';
|
|
6
|
+
import { getActionBackground, getActionColor, getFlagFocusRingColor } from './theme';
|
|
7
|
+
const gridSize = getGridSize();
|
|
8
|
+
const separatorWidth = gridSize * 2;
|
|
9
|
+
const defaultAppearanceTranslate = gridSize / 4;
|
|
10
|
+
|
|
11
|
+
const FlagActions = props => {
|
|
12
|
+
const {
|
|
13
|
+
appearance = DEFAULT_APPEARANCE,
|
|
14
|
+
actions = [],
|
|
15
|
+
linkComponent,
|
|
16
|
+
mode,
|
|
17
|
+
testId
|
|
18
|
+
} = props;
|
|
19
|
+
|
|
20
|
+
if (!actions.length) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isBold = appearance !== DEFAULT_APPEARANCE;
|
|
25
|
+
return jsx("div", {
|
|
26
|
+
css: css`
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-wrap: wrap;
|
|
29
|
+
padding-top: ${gridSize}px;
|
|
30
|
+
transform: ${appearance === DEFAULT_APPEARANCE ? `translateX(-${defaultAppearanceTranslate}px)` : 0};
|
|
31
|
+
align-items: center;
|
|
32
|
+
`,
|
|
33
|
+
"data-testid": testId && `${testId}-actions`
|
|
34
|
+
}, actions.map((action, index) => [index && !isBold ? jsx("div", {
|
|
35
|
+
css: css`
|
|
36
|
+
text-align: center;
|
|
37
|
+
display: inline-block;
|
|
38
|
+
width: ${separatorWidth}px;
|
|
39
|
+
`,
|
|
40
|
+
key: index + 0.5
|
|
41
|
+
}, "\xB7") : '', jsx(Button, {
|
|
42
|
+
onClick: action.onClick,
|
|
43
|
+
href: action.href,
|
|
44
|
+
target: action.target,
|
|
45
|
+
appearance: isBold ? 'default' : 'link',
|
|
46
|
+
component: linkComponent,
|
|
47
|
+
spacing: "compact",
|
|
48
|
+
testId: action.testId,
|
|
49
|
+
key: index,
|
|
50
|
+
css: css`
|
|
51
|
+
&&,
|
|
52
|
+
a&& {
|
|
53
|
+
margin-left: ${index && isBold ? gridSize : 0}px;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
padding: 0 ${appearance === 'normal' ? 0 : gridSize}px !important;
|
|
56
|
+
background: ${getActionBackground(appearance, mode)};
|
|
57
|
+
color: ${getActionColor(appearance, mode)} !important;
|
|
58
|
+
}
|
|
59
|
+
&&:focus,
|
|
60
|
+
a&&:focus {
|
|
61
|
+
box-shadow: 0 0 0 2px ${getFlagFocusRingColor(appearance, mode)};
|
|
62
|
+
}
|
|
63
|
+
&&:hover,
|
|
64
|
+
&&:active,
|
|
65
|
+
a&&:hover,
|
|
66
|
+
a&&:active {
|
|
67
|
+
text-decoration: underline;
|
|
68
|
+
}
|
|
69
|
+
`
|
|
70
|
+
}, action.content)]));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default FlagActions;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
|
|
3
|
+
/** @jsx jsx */
|
|
4
|
+
import { Children, createContext, useContext, useMemo } from 'react';
|
|
5
|
+
import { css, jsx } from '@emotion/core';
|
|
6
|
+
import { easeIn, ExitingPersistence, SlideIn } from '@atlaskit/motion';
|
|
7
|
+
import Portal from '@atlaskit/portal';
|
|
8
|
+
import { gridSize as getGridSize, layers } from '@atlaskit/theme/constants';
|
|
9
|
+
const gridSize = getGridSize();
|
|
10
|
+
export const flagWidth = gridSize * 50;
|
|
11
|
+
export const flagAnimationTime = 400;
|
|
12
|
+
const flagBottom = gridSize * 6;
|
|
13
|
+
const flagLeft = gridSize * 10;
|
|
14
|
+
|
|
15
|
+
function noop() {}
|
|
16
|
+
|
|
17
|
+
const defaultFlagGroupContext = {
|
|
18
|
+
onDismissed: () => {},
|
|
19
|
+
isDismissAllowed: false
|
|
20
|
+
};
|
|
21
|
+
export const FlagGroupContext = /*#__PURE__*/createContext(defaultFlagGroupContext);
|
|
22
|
+
export function useFlagGroup() {
|
|
23
|
+
return useContext(FlagGroupContext);
|
|
24
|
+
} // transition: none is set on first-of-type to prevent a bug in Firefox
|
|
25
|
+
// that causes a broken transition
|
|
26
|
+
|
|
27
|
+
const baseStyles = `
|
|
28
|
+
bottom: 0;
|
|
29
|
+
position: absolute;
|
|
30
|
+
width: ${flagWidth}px;
|
|
31
|
+
transition: transform ${flagAnimationTime}ms ease-in-out;
|
|
32
|
+
|
|
33
|
+
@media (max-width: 560px) {
|
|
34
|
+
width: 100vw;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:first-of-type {
|
|
38
|
+
transition: none;
|
|
39
|
+
transform: translate(0,0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&:nth-of-type(n + 2) {
|
|
43
|
+
animation-duration: 0ms;
|
|
44
|
+
transform: translateX(0) translateY(100%) translateY(${2 * gridSize}px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Layer the 'primary' flag above the 'secondary' flag */
|
|
48
|
+
&:nth-of-type(1) {
|
|
49
|
+
z-index: 5;
|
|
50
|
+
}
|
|
51
|
+
&:nth-of-type(2) {
|
|
52
|
+
z-index: 4;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:nth-of-type(n + 4) {
|
|
56
|
+
visibility: hidden;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const FlagGroup = props => {
|
|
61
|
+
const {
|
|
62
|
+
id,
|
|
63
|
+
label = 'Flag notifications',
|
|
64
|
+
labelTag: LabelTag = 'h2',
|
|
65
|
+
children,
|
|
66
|
+
onDismissed = noop
|
|
67
|
+
} = props;
|
|
68
|
+
const hasFlags = Array.isArray(children) ? children.length > 0 : Boolean(children);
|
|
69
|
+
const dismissFlagContext = useMemo(() => ({
|
|
70
|
+
onDismissed: onDismissed,
|
|
71
|
+
isDismissAllowed: true
|
|
72
|
+
}), [onDismissed]);
|
|
73
|
+
|
|
74
|
+
const renderChildren = () => {
|
|
75
|
+
return children && typeof children === 'object' ? Children.map(children, (flag, index) => {
|
|
76
|
+
const isDismissAllowed = index === 0;
|
|
77
|
+
return jsx(SlideIn, {
|
|
78
|
+
enterFrom: 'left',
|
|
79
|
+
fade: 'inout',
|
|
80
|
+
duration: flagAnimationTime,
|
|
81
|
+
animationTimingFunction: () => easeIn
|
|
82
|
+
}, props => jsx("div", _extends({}, props, {
|
|
83
|
+
css: css`
|
|
84
|
+
${baseStyles}
|
|
85
|
+
${isDismissAllowed ? // Transform needed to push up while 1st flag is leaving
|
|
86
|
+
// Exiting time should match the exiting time of motion so is halved
|
|
87
|
+
`
|
|
88
|
+
&& + * {
|
|
89
|
+
transform: translate(0, 0);
|
|
90
|
+
transition-duration: ${flagAnimationTime / 2}ms
|
|
91
|
+
}` : ''}
|
|
92
|
+
`
|
|
93
|
+
}), jsx(FlagGroupContext.Provider, {
|
|
94
|
+
value: // Only the first flag should be able to be dismissed.
|
|
95
|
+
isDismissAllowed ? dismissFlagContext : defaultFlagGroupContext
|
|
96
|
+
}, flag)));
|
|
97
|
+
}) : false;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return jsx(Portal, {
|
|
101
|
+
zIndex: layers.flag()
|
|
102
|
+
}, jsx("div", {
|
|
103
|
+
id: id,
|
|
104
|
+
css: css`
|
|
105
|
+
bottom: ${flagBottom}px;
|
|
106
|
+
left: ${flagLeft}px;
|
|
107
|
+
position: fixed;
|
|
108
|
+
z-index: ${layers.flag()};
|
|
109
|
+
@media (max-width: 560px) {
|
|
110
|
+
bottom: 0;
|
|
111
|
+
left: 0;
|
|
112
|
+
}
|
|
113
|
+
`
|
|
114
|
+
}, hasFlags ? jsx(LabelTag, {
|
|
115
|
+
css: css`
|
|
116
|
+
border: 0;
|
|
117
|
+
clip: rect(1px, 1px, 1px, 1px);
|
|
118
|
+
height: 1px;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
padding: 0;
|
|
121
|
+
position: absolute;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
width: 1px;
|
|
124
|
+
`
|
|
125
|
+
}, label) : null, jsx(ExitingPersistence, {
|
|
126
|
+
appear: false
|
|
127
|
+
}, renderChildren())));
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default FlagGroup;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
|
3
|
+
import AutoDismissFlag from './auto-dismiss-flag';
|
|
4
|
+
import Flag from './flag';
|
|
5
|
+
import FlagGroup from './flag-group';
|
|
6
|
+
const FlagContext = /*#__PURE__*/React.createContext(null);
|
|
7
|
+
export function useFlags() {
|
|
8
|
+
const api = useContext(FlagContext);
|
|
9
|
+
|
|
10
|
+
if (api == null) {
|
|
11
|
+
throw new Error('Unable to find FlagProviderContext');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return api;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getUniqueId = (() => {
|
|
18
|
+
let count = 0;
|
|
19
|
+
return () => `flag-provider-unique-id:${count++}`;
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
export function FlagsProvider({
|
|
23
|
+
children
|
|
24
|
+
}) {
|
|
25
|
+
const [flags, setFlags] = useState([]);
|
|
26
|
+
const removeFlag = useCallback(id => {
|
|
27
|
+
setFlags(current => {
|
|
28
|
+
return current.slice(0).filter(flag => flag.id !== id);
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
const api = useMemo(() => ({
|
|
32
|
+
showFlag: function show(value) {
|
|
33
|
+
const flag = { ...value,
|
|
34
|
+
id: value.id || getUniqueId()
|
|
35
|
+
};
|
|
36
|
+
setFlags(current => {
|
|
37
|
+
const index = current.findIndex(value => value.id === flag.id); // If flag is not found add it
|
|
38
|
+
|
|
39
|
+
if (index === -1) {
|
|
40
|
+
return [flag, ...current];
|
|
41
|
+
} // If flag already exists with the same id, then replace it
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const shallow = [...current];
|
|
45
|
+
shallow[index] = flag;
|
|
46
|
+
return shallow;
|
|
47
|
+
});
|
|
48
|
+
return function dismiss() {
|
|
49
|
+
removeFlag(flag.id);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}), [removeFlag]);
|
|
53
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FlagContext.Provider, {
|
|
54
|
+
value: api
|
|
55
|
+
}, children), /*#__PURE__*/React.createElement(FlagGroup, {
|
|
56
|
+
onDismissed: removeFlag
|
|
57
|
+
}, flags.map(flag => {
|
|
58
|
+
const {
|
|
59
|
+
isAutoDismiss,
|
|
60
|
+
...restProps
|
|
61
|
+
} = flag;
|
|
62
|
+
const FlagType = isAutoDismiss ? AutoDismissFlag : Flag;
|
|
63
|
+
return /*#__PURE__*/React.createElement(FlagType, _extends({}, restProps, {
|
|
64
|
+
key: flag.id
|
|
65
|
+
}));
|
|
66
|
+
})));
|
|
67
|
+
}
|
|
68
|
+
export const withFlagsProvider = fn => /*#__PURE__*/React.createElement(FlagsProvider, null, fn());
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
|
|
3
|
+
/** @jsx jsx */
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { css, jsx } from '@emotion/core';
|
|
6
|
+
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down';
|
|
7
|
+
import ChevronUpIcon from '@atlaskit/icon/glyph/chevron-up';
|
|
8
|
+
import CrossIcon from '@atlaskit/icon/glyph/cross';
|
|
9
|
+
import GlobalTheme from '@atlaskit/theme/components';
|
|
10
|
+
import { borderRadius, gridSize as getGridSize, layers } from '@atlaskit/theme/constants';
|
|
11
|
+
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
|
|
12
|
+
import { DEFAULT_APPEARANCE } from './constants';
|
|
13
|
+
import { flagBorderColor, flagShadowColor, getFlagBackgroundColor, getFlagFocusRingColor, getFlagTextColor } from './theme';
|
|
14
|
+
import Expander from './expander';
|
|
15
|
+
import Actions from './flag-actions';
|
|
16
|
+
import { useFlagGroup } from './flag-group';
|
|
17
|
+
|
|
18
|
+
function noop() {}
|
|
19
|
+
|
|
20
|
+
const analyticsAttributes = {
|
|
21
|
+
componentName: 'flag',
|
|
22
|
+
packageName: "@atlaskit/flag",
|
|
23
|
+
packageVersion: "14.4.1"
|
|
24
|
+
};
|
|
25
|
+
const gridSize = getGridSize();
|
|
26
|
+
const doubleGridSize = gridSize * 2;
|
|
27
|
+
const headerHeight = gridSize * 4;
|
|
28
|
+
|
|
29
|
+
const Flag = props => {
|
|
30
|
+
const {
|
|
31
|
+
actions = [],
|
|
32
|
+
appearance = DEFAULT_APPEARANCE,
|
|
33
|
+
icon,
|
|
34
|
+
title,
|
|
35
|
+
description,
|
|
36
|
+
linkComponent,
|
|
37
|
+
onMouseOver,
|
|
38
|
+
onFocus = noop,
|
|
39
|
+
onMouseOut,
|
|
40
|
+
onBlur = noop,
|
|
41
|
+
onDismissed: onDismissedProp = noop,
|
|
42
|
+
testId,
|
|
43
|
+
id,
|
|
44
|
+
analyticsContext
|
|
45
|
+
} = props;
|
|
46
|
+
const {
|
|
47
|
+
onDismissed: onDismissedFromFlagGroup,
|
|
48
|
+
isDismissAllowed
|
|
49
|
+
} = useFlagGroup();
|
|
50
|
+
const onDismissed = useCallback((id, analyticsEvent) => {
|
|
51
|
+
onDismissedProp(id, analyticsEvent);
|
|
52
|
+
onDismissedFromFlagGroup(id, analyticsEvent);
|
|
53
|
+
}, [onDismissedProp, onDismissedFromFlagGroup]);
|
|
54
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
55
|
+
const onDismissedAnalytics = usePlatformLeafEventHandler({
|
|
56
|
+
fn: onDismissed,
|
|
57
|
+
action: 'dismissed',
|
|
58
|
+
analyticsData: analyticsContext,
|
|
59
|
+
...analyticsAttributes
|
|
60
|
+
});
|
|
61
|
+
const isBold = appearance !== DEFAULT_APPEARANCE;
|
|
62
|
+
const renderToggleOrDismissButton = useCallback(({
|
|
63
|
+
mode
|
|
64
|
+
}) => {
|
|
65
|
+
// If it is normal appearance a toggle button cannot be rendered
|
|
66
|
+
// Ensure onDismissed is defined and isDismissAllowed is true to render
|
|
67
|
+
// the dismiss button
|
|
68
|
+
if (!isBold && !isDismissAllowed) {
|
|
69
|
+
return null;
|
|
70
|
+
} // If it is bold then ensure there is a description or actions to render
|
|
71
|
+
// the toggle button
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if (isBold && !description && !actions.length) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let ButtonIcon = CrossIcon;
|
|
79
|
+
let buttonLabel = 'Dismiss';
|
|
80
|
+
|
|
81
|
+
let buttonAction = () => {
|
|
82
|
+
if (isDismissAllowed) {
|
|
83
|
+
onDismissedAnalytics(id);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let size = 'small';
|
|
88
|
+
let buttonTestId = testId && `${testId}-dismiss`;
|
|
89
|
+
let a11yProps = {};
|
|
90
|
+
|
|
91
|
+
if (isBold) {
|
|
92
|
+
ButtonIcon = isExpanded ? ChevronUpIcon : ChevronDownIcon;
|
|
93
|
+
buttonLabel = isExpanded ? 'Collapse' : 'Expand';
|
|
94
|
+
|
|
95
|
+
buttonAction = () => setIsExpanded(!isExpanded);
|
|
96
|
+
|
|
97
|
+
size = 'large';
|
|
98
|
+
buttonTestId = testId && `${testId}-toggle`;
|
|
99
|
+
a11yProps = {
|
|
100
|
+
'aria-expanded': isExpanded
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return jsx("button", _extends({
|
|
105
|
+
css: css`
|
|
106
|
+
appearance: none;
|
|
107
|
+
background: none;
|
|
108
|
+
border: none;
|
|
109
|
+
border-radius: ${borderRadius()}px;
|
|
110
|
+
color: ${getFlagTextColor(appearance, mode)};
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
flex: 0 0 auto;
|
|
113
|
+
line-height: 1;
|
|
114
|
+
margin-left: ${gridSize}px;
|
|
115
|
+
padding: 0;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
&:focus {
|
|
118
|
+
outline: none;
|
|
119
|
+
box-shadow: 0 0 0 2px ${getFlagFocusRingColor(appearance, mode)};
|
|
120
|
+
}
|
|
121
|
+
`,
|
|
122
|
+
onClick: buttonAction,
|
|
123
|
+
"data-testid": buttonTestId,
|
|
124
|
+
type: "button"
|
|
125
|
+
}, a11yProps), jsx(ButtonIcon, {
|
|
126
|
+
label: buttonLabel,
|
|
127
|
+
size: size
|
|
128
|
+
}));
|
|
129
|
+
}, [actions.length, appearance, description, id, isBold, isDismissAllowed, isExpanded, onDismissedAnalytics, testId]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
// If buttons are removed as a prop, update isExpanded to be false
|
|
132
|
+
if (isBold && isExpanded && !description && !actions.length) {
|
|
133
|
+
setIsExpanded(false);
|
|
134
|
+
}
|
|
135
|
+
}, [actions.length, description, isBold, isExpanded]);
|
|
136
|
+
const onFocusAnalytics = usePlatformLeafEventHandler({
|
|
137
|
+
fn: onFocus,
|
|
138
|
+
action: 'focused',
|
|
139
|
+
analyticsData: analyticsContext,
|
|
140
|
+
...analyticsAttributes
|
|
141
|
+
});
|
|
142
|
+
const onBlurAnalytics = usePlatformLeafEventHandler({
|
|
143
|
+
fn: onBlur,
|
|
144
|
+
action: 'blurred',
|
|
145
|
+
analyticsData: analyticsContext,
|
|
146
|
+
...analyticsAttributes
|
|
147
|
+
});
|
|
148
|
+
const autoDismissProps = {
|
|
149
|
+
onMouseOver,
|
|
150
|
+
onFocus: onFocusAnalytics,
|
|
151
|
+
onMouseOut,
|
|
152
|
+
onBlur: onBlurAnalytics
|
|
153
|
+
};
|
|
154
|
+
const OptionalDismissButton = renderToggleOrDismissButton;
|
|
155
|
+
let boxShadow = `0 20px 32px -8px ${flagShadowColor}`;
|
|
156
|
+
|
|
157
|
+
if (!isBold) {
|
|
158
|
+
boxShadow = `0 0 1px ${flagBorderColor}, ${boxShadow}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return jsx(GlobalTheme.Consumer, null, tokens => {
|
|
162
|
+
const mode = tokens.mode;
|
|
163
|
+
const textColour = getFlagTextColor(appearance, mode);
|
|
164
|
+
return jsx("div", _extends({
|
|
165
|
+
css: css`
|
|
166
|
+
background-color: ${getFlagBackgroundColor(appearance, mode)};
|
|
167
|
+
border-radius: ${borderRadius()}px;
|
|
168
|
+
box-shadow: ${boxShadow};
|
|
169
|
+
color: ${textColour};
|
|
170
|
+
transition: background-color 200ms;
|
|
171
|
+
width: 100%;
|
|
172
|
+
z-index: ${layers.flag()};
|
|
173
|
+
`,
|
|
174
|
+
role: "alert",
|
|
175
|
+
"data-testid": testId
|
|
176
|
+
}, autoDismissProps), jsx("div", {
|
|
177
|
+
css: css`
|
|
178
|
+
width: 100%;
|
|
179
|
+
padding: ${doubleGridSize}px;
|
|
180
|
+
box-sizing: border-box;
|
|
181
|
+
border-radius: ${borderRadius()}px;
|
|
182
|
+
|
|
183
|
+
&:focus-visible {
|
|
184
|
+
outline: none;
|
|
185
|
+
box-shadow: 0 0 0 2px
|
|
186
|
+
${getFlagFocusRingColor(appearance, mode)};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@supports not selector(*:focus-visible) {
|
|
190
|
+
&:focus {
|
|
191
|
+
outline: none;
|
|
192
|
+
box-shadow: 0 0 0 2px
|
|
193
|
+
${getFlagFocusRingColor(appearance, mode)};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@media screen and (forced-colors: active),
|
|
198
|
+
screen and (-ms-high-contrast: active) {
|
|
199
|
+
&:focus-visible {
|
|
200
|
+
outline: 1px solid;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
` // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
204
|
+
,
|
|
205
|
+
tabIndex: 0
|
|
206
|
+
}, jsx("div", {
|
|
207
|
+
css: css`
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
height: ${headerHeight}px;
|
|
211
|
+
`
|
|
212
|
+
}, icon, jsx("span", {
|
|
213
|
+
css: css`
|
|
214
|
+
color: ${textColour};
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
flex: 1;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
text-overflow: ellipsis;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
padding: 0 0 0 ${doubleGridSize}px;
|
|
221
|
+
`
|
|
222
|
+
}, title), jsx(OptionalDismissButton, {
|
|
223
|
+
mode: mode
|
|
224
|
+
})), jsx(Expander, {
|
|
225
|
+
isExpanded: !isBold || isExpanded,
|
|
226
|
+
testId: testId
|
|
227
|
+
}, description && jsx("div", {
|
|
228
|
+
css: css`
|
|
229
|
+
color: ${textColour};
|
|
230
|
+
word-wrap: break-word;
|
|
231
|
+
overflow: auto;
|
|
232
|
+
max-height: 100px; /* height is defined as 5 lines maximum by design */
|
|
233
|
+
`,
|
|
234
|
+
"data-testid": testId && `${testId}-description`
|
|
235
|
+
}, description), jsx(Actions, {
|
|
236
|
+
actions: actions,
|
|
237
|
+
appearance: appearance,
|
|
238
|
+
linkComponent: linkComponent,
|
|
239
|
+
testId: testId,
|
|
240
|
+
mode: mode
|
|
241
|
+
}))));
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export default Flag;
|