From 138e93c41afb854c64372ee5bcd02690ed87eeeb Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 27 Mar 2025 11:16:37 +0100 Subject: [PATCH] chore: drag-n-drop tooltip for strategies (#9623) Implements the drag-n-drop tooltip the first time the user sees a strategy drag handle on the feature env overview. It uses React Joyride, which is the same system we use for the demo. The design is a little different from the sketches because I couldn't find a quick way to move the content (and the arrow) to be shifted correctly. If the demo is also active the first time a user visits a strategy page, it'll render both the demo steps and this, but this tooltip doesn't prevent the user from finishing the tour. It might be possible to avoid that through checking state in localstorage, but I'd like to get this approved first. The tooltip uses the auth splash system to decide whether to show the tooltip, meaning it's stored per user in the DB. To avoid it re-rendering before you refetch from the back end, we also use a temporary variable to check whether the user has closed it. Rendered: ![image](https://github.com/user-attachments/assets/5912d055-10d5-4a1d-93f4-f12ff4ef7419) If the tour is also active: ![image](https://github.com/user-attachments/assets/b0028a0f-3a0f-48aa-9ab9-8d7cf399055a) --- .../StrategyItemContainer.tsx | 1 + .../FeatureOverview/FeatureOverview.tsx | 24 ++++- .../FeatureOverviewEnvironment.tsx | 8 +- .../FeatureOverviewEnvironments.tsx | 4 +- .../FeatureOverview/StrategyDragTooltip.tsx | 101 ++++++++++++++++++ 5 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/StrategyDragTooltip.tsx diff --git a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx index c008fd69ee..13f6ae1432 100644 --- a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx +++ b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx @@ -96,6 +96,7 @@ export const StrategyItemContainer: FC = ({ {onDragStart ? ( ({ display: 'flex', @@ -51,6 +54,22 @@ export const FeatureOverview = () => { return ; } + const { setSplashSeen } = useSplashApi(); + const { splash } = useAuthSplash(); + const dragTooltipSplashId = 'strategy-drag-tooltip'; + const shouldShowStrategyDragTooltip = !splash?.[dragTooltipSplashId]; + const [showTooltip, setShowTooltip] = useState(false); + const [hasClosedTooltip, setHasClosedTooltip] = useState(false); + const toggleShowTooltip = (envIsOpen: boolean) => { + setShowTooltip( + !hasClosedTooltip && shouldShowStrategyDragTooltip && envIsOpen, + ); + }; + const onTooltipClose = () => { + setHasClosedTooltip(true); + setSplashSeen(dragTooltipSplashId); + }; + return (
@@ -63,6 +82,7 @@ export const FeatureOverview = () => {
@@ -92,6 +112,8 @@ export const FeatureOverview = () => { } /> + +
); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index 3adc2244c0..f683dd8959 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -59,12 +59,14 @@ type FeatureOverviewEnvironmentProps = { }; metrics?: Pick; otherEnvironments?: string[]; + onToggleEnvOpen?: (isOpen: boolean) => void; }; export const FeatureOverviewEnvironment = ({ environment, metrics = { yes: 0, no: 0 }, otherEnvironments = [], + onToggleEnvOpen = () => {}, }: FeatureOverviewEnvironmentProps) => { const [isOpen, setIsOpen] = useState(false); const projectId = useRequiredPathParam('projectId'); @@ -83,7 +85,11 @@ export const FeatureOverviewEnvironment = ({ data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${environment.name}`} expanded={isOpen && hasActivations} disabled={!hasActivations} - onChange={() => setIsOpen(isOpen ? !isOpen : hasActivations)} + onChange={() => { + const state = isOpen ? !isOpen : hasActivations; + onToggleEnvOpen(state); + setIsOpen(state); + }} > void; }; const FeatureOverviewWithReleasePlans: FC< @@ -33,7 +34,7 @@ const FeatureOverviewWithReleasePlans: FC< export const FeatureOverviewEnvironments: FC< FeatureOverviewEnvironmentsProps -> = ({ hiddenEnvironments = [] }) => { +> = ({ hiddenEnvironments = [], onToggleEnvOpen }) => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const { feature } = useFeature(projectId, featureId); @@ -60,6 +61,7 @@ export const FeatureOverviewEnvironments: FC< ?.filter((env) => !hiddenEnvironments.includes(env.name)) .map((env) => ( ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + maxWidth: '300px', + background: '#201e42', + borderRadius: theme.shape.borderRadiusMedium, + color: theme.palette.common.white, + padding: theme.spacing(2), + paddingRight: theme.spacing(1), + fontSize: theme.typography.body2.fontSize, +})); + +const OkButton = styled(Button)(({ theme }) => ({ + color: theme.palette.secondary.border, + alignSelf: 'start', + marginLeft: theme.spacing(-1), +})); + +const StyledCloseButton = styled(IconButton)(({ theme }) => ({ + color: theme.palette.common.white, + background: 'none', + border: 'none', + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(1), + svg: { + width: theme.spacing(2), + height: theme.spacing(2), + }, +})); + +const StyledHeader = styled('p')(({ theme }) => ({ + fontSize: theme.typography.body1.fontSize, + fontWeight: 'bold', +})); + +const CustomTooltip = ({ closeProps }: TooltipRenderProps) => { + return ( + + + + + Decide the order evaluation +

+ Strategies are evaluated in the order presented here. Drag and + rearrange the strategies to get the order you prefer. +

+ + Ok, got it! + +
+ ); +}; + +type Props = { + show: boolean; + onClose: () => void; +}; + +export const StrategyDragTooltip: FC = ({ show, onClose }) => { + return ( + { + if (action === 'close') { + onClose(); + } + }} + floaterProps={{ + styles: { + arrow: { + color: '#201e42', + spread: 16, + length: 10, + }, + }, + }} + run={show} + disableOverlay + disableScrolling + tooltipComponent={CustomTooltip} + steps={[ + { + disableBeacon: true, + offset: 0, + target: '.strategy-drag-handle', + content: <>, + }, + ]} + /> + ); +};