@atlaskit/flag
Version:
A flag is used for confirmations, alerts, and acknowledgments that require minimal user interaction, often displayed using a flag group.
238 lines (229 loc) • 10.5 kB
JavaScript
/* flag-group.tsx generated by @compiled/babel-plugin v3.0.2 */
import "./flag-group.compiled.css";
import * as React from 'react';
import { ax, ix } from "@compiled/react/runtime";
import { Children, useEffect, useMemo, useRef } from 'react';
import { bind } from 'bind-event-listener';
import { useAnalyticsEvents } from '@atlaskit/analytics-next/useAnalyticsEvents';
import { getDocument } from '@atlaskit/browser-apis';
import { cx } from '@atlaskit/css';
import noop from '@atlaskit/ds-lib/noop';
import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
import SlideIn from '@atlaskit/motion/slide-in';
import Motion from '@atlaskit/motion/entering/motion';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import Portal from '@atlaskit/portal/portal';
import { Box } from '@atlaskit/primitives/compiled';
import { layers } from '@atlaskit/theme/constants';
import { Popover } from '@atlaskit/top-layer/popover/popover';
import VisuallyHidden from '@atlaskit/visually-hidden/visually-hidden';
import { defaultFlagGroupContext } from './internal/default-flag-group-context';
import { FlagGroupContext } from './internal/flag-group-context';
/* eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- VOLTC-139 tracks removal of these deprecated re-export shims. */
/**
* @deprecated Use `import { FlagGroupContext } from '@atlaskit/flag/flag-group-context'` instead.
*/
export { FlagGroupContext } from './internal/flag-group-context';
/* eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- VOLTC-139 tracks removal of these deprecated re-export shims. */
/**
* @deprecated Use `import { useFlagGroup } from '@atlaskit/flag/use-flag-group'` instead.
*/
export { useFlagGroup } from './internal/use-flag-group';
/* eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- VOLTC-139 tracks removal of these deprecated re-export shims. */
/**
* @deprecated Use `import { flagWidth } from '@atlaskit/flag/constants'` instead.
*/
export { flagWidth } from './constants';
// transition: none is set on first-of-type to prevent a bug in Firefox
// that causes a broken transition
const groupStyles = {
root: "_v5641b8g _1bsb1kdj _kqswstnw _u7coidpf _1eqkauwl",
first: "_v564glyw _t9ec1ygq _1pbyqfx8",
second: "_1pbyegat",
nth: "_5sag5cps _t9ec1hw2",
hidden: "_3um015vq"
};
// transition: none is set on first-of-type to prevent a bug in Firefox
// that causes a broken transition
const groupStylesNew = {
root: "_v564t8bh _kqswstnw _u7coidpf _1bsb1kdj _1eqkauwl",
first: "_t9ec1ygq _1pbyqfx8",
second: "_t9ec1pi4 _1pbyegat",
nth: "_t9ec15wd",
hidden: "_3um015vq"
};
// Transform needed to push up while 1st flag is leaving
// Exiting time should match the exiting time of motion so is halved
const dismissAllowedStyles = null;
const flagGroupContainerStyles = null;
// When rendering in top layer, z-index is redundant (top layer stacks above everything).
// Override to avoid leaving an explicit z-index on the container.
const flagGroupContainerStylesTopLayer = null;
// cc_mohiti_flag_anchoring: update left inset from space.1000 (80px) to space.600 (48px)
// to match the bottom inset and produce a symmetric 48px × 48px anchor.
// The 80px left offset was a legacy artefact from the old left-rail nav sidebar.
// The mobile reset is mirrored from the base style so the override is self-contained
// and doesn't depend on @compiled's atomic CSS emit order at the ≤560px breakpoint.
const flagGroupContainerStylesSymmetric = null;
/**
* __Flag group__
*
* A flag group is used to group a set of related flags, with entry and exit animations.
*
* - [Examples](https://atlassian.design/components/flag/flag-group/examples)
* - [Code](https://atlassian.design/components/flag/flag-group/code)
*/
export const FlagGroup = props => {
const {
id,
label = 'Flag notifications',
labelTag: LabelTag = 'h2',
shouldRenderToParent = false,
children,
onDismissed = noop
} = props;
const hasFlags = Array.isArray(children) ? children.length > 0 : Boolean(children);
const dismissFlagContext = useMemo(() => ({
onDismissed: onDismissed,
isDismissAllowed: true
}), [onDismissed]);
// Keep a stable reference to the latest children, dismiss handler, and
// analytics creator so the keydown listener never reads stale closures.
const {
createAnalyticsEvent
} = useAnalyticsEvents();
const latestRef = useRef({
children,
onDismissed,
createAnalyticsEvent
});
latestRef.current = {
children,
onDismissed,
createAnalyticsEvent
};
// Accessibility (JRACLOUD-97876): allow keyboard-only and assistive
// technology users to dismiss the topmost (and only dismissable) flag with
// the Escape key, without having to tab through the entire page to reach
// the dismiss button. Behaviour is gated behind a feature flag so it can
// be rolled out and validated incrementally.
useEffect(() => {
if (!fg('platform_dst_flag_keyboard_dismiss')) {
return;
}
if (!hasFlags) {
return;
}
const doc = getDocument();
if (!doc) {
return;
}
const timeoutIds = new Set();
const unbind = bind(doc, {
type: 'keydown',
listener: event => {
var _props;
if (event.key !== 'Escape' || event.defaultPrevented) {
return;
}
const currentChildren = latestRef.current.children;
const firstFlag = Array.isArray(currentChildren) ? currentChildren.find(Boolean) : currentChildren;
if (!firstFlag || typeof firstFlag !== 'object') {
return;
}
const id = (_props = firstFlag.props) === null || _props === void 0 ? void 0 : _props.id;
if (id === undefined || id === null || id === '') {
return;
}
const analyticsEvent = latestRef.current.createAnalyticsEvent({
action: 'dismissed',
actionSubject: 'flag',
attributes: {
dismissedVia: 'keyboardShortcut',
key: 'Escape'
}
});
const timeoutId = setTimeout(() => {
var _latestRef$current$on, _latestRef$current;
timeoutIds.delete(timeoutId);
if (event.defaultPrevented) {
return;
}
event.preventDefault();
(_latestRef$current$on = (_latestRef$current = latestRef.current).onDismissed) === null || _latestRef$current$on === void 0 ? void 0 : _latestRef$current$on.call(_latestRef$current, id, analyticsEvent);
});
timeoutIds.add(timeoutId);
},
options: {
capture: true
}
});
return () => {
unbind();
timeoutIds.forEach(clearTimeout);
timeoutIds.clear();
};
}, [hasFlags]);
const renderChildren = () => {
return children && typeof children === 'object' ? Children.map(children, (flag, index) => {
const isDismissAllowed = index === 0;
return fg('platform-dst-motion-uplift') ? /*#__PURE__*/React.createElement(Box, {
xcss: cx(groupStylesNew.root, index === 0 && groupStylesNew.first, index === 1 && groupStylesNew.second, index >= 2 && groupStylesNew.nth, index >= 3 && groupStylesNew.hidden),
"data-vc-oob": true
}, /*#__PURE__*/React.createElement(Motion, {
enteringAnimation: "var(--ds-flag-enter, 250ms cubic-bezier(0, 0.4, 0, 1) SlideIn50PercentLeft, 250ms cubic-bezier(0, 0.4, 0, 1) FadeIn0to100)",
exitingAnimation: "var(--ds-flag-exit, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) SlideOut15PercentLeft, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"
}, /*#__PURE__*/React.createElement(FlagGroupContext.Provider, {
value:
// Only the first flag should be able to be dismissed.
isDismissAllowed ? dismissFlagContext : defaultFlagGroupContext
}, flag))) : /*#__PURE__*/React.createElement(SlideIn, {
enterFrom: "left",
fade: "inout",
duration: "medium",
animationTimingFunction: "ease-in"
}, ({
className,
ref
}) => /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ax([groupStyles.root, index === 0 && groupStyles.first, index === 1 && groupStyles.second, index >= 1 && groupStyles.nth, index >= 2 && groupStyles.hidden, isDismissAllowed && "_12a7luct", className]),
ref: ref,
"data-vc-oob": true
}, /*#__PURE__*/React.createElement(FlagGroupContext.Provider, {
value:
// Only the first flag should be able to be dismissed.
isDismissAllowed ? dismissFlagContext : defaultFlagGroupContext
}, flag)));
}) : false;
};
const useTopLayer = fg('platform-dst-top-layer');
const useSymmetricAnchor = fg('cc_mohiti_flag_anchoring');
const isKeyboardDismissEnabled = fg('platform_dst_flag_keyboard_dismiss');
// When the keyboard dismiss shortcut is available, surface it to assistive
// technology users via the existing visually-hidden landmark heading so they
// know they can press Escape to dismiss the topmost flag.
const screenReaderLabel = isKeyboardDismissEnabled ? `${label}. Press Escape to dismiss.` : label;
const flags = /*#__PURE__*/React.createElement("div", {
id: id,
"data-vc-oob": true,
className: ax(["_kqsw1n9t _1pbyxejw _u7cooahv _1e021epz _1iafidpf _il0widpf", useTopLayer && "_1pby1wug", useSymmetricAnchor && "_1e02oahv _il0widpf"])
}, hasFlags ? /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement(LabelTag, null, screenReaderLabel)) : null, /*#__PURE__*/React.createElement(ExitingPersistence, {
appear: false
}, renderChildren()));
if (useTopLayer) {
return /*#__PURE__*/React.createElement(Popover, {
mode: "manual",
isOpen: true
}, flags);
}
return shouldRenderToParent ? flags : /*#__PURE__*/React.createElement(Portal, {
zIndex: layers.flag()
}, flags);
};
/* eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- VOLTC-139 tracks removal of these deprecated re-export shims. */
/**
* @deprecated Use `import { FlagGroup } from '@atlaskit/flag/flag-group'` instead.
*/
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports -- VOLTC-139 tracks removal of this deprecated default export shim.
export default FlagGroup;