mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
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:  If the tour is also active: 
This commit is contained in:
parent
6aae9be19c
commit
138e93c41a
@ -96,6 +96,7 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
|
||||
<StyledHeader disabled={Boolean(strategy?.disabled)}>
|
||||
{onDragStart ? (
|
||||
<DragIcon
|
||||
className='strategy-drag-handle'
|
||||
draggable
|
||||
disableRipple
|
||||
size='small'
|
||||
|
@ -9,12 +9,15 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { styled } from '@mui/material';
|
||||
import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
|
||||
import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
|
||||
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
||||
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
||||
import { StrategyDragTooltip } from './StrategyDragTooltip';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -51,6 +54,22 @@ export const FeatureOverview = () => {
|
||||
return <LegacyFleatureOverview />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<StyledContainer>
|
||||
<div>
|
||||
@ -63,6 +82,7 @@ export const FeatureOverview = () => {
|
||||
</div>
|
||||
<StyledMainContent>
|
||||
<FeatureOverviewEnvironments
|
||||
onToggleEnvOpen={toggleShowTooltip}
|
||||
hiddenEnvironments={hiddenEnvironments}
|
||||
/>
|
||||
</StyledMainContent>
|
||||
@ -92,6 +112,8 @@ export const FeatureOverview = () => {
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
|
||||
<StrategyDragTooltip show={showTooltip} onClose={onTooltipClose} />
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -59,12 +59,14 @@ type FeatureOverviewEnvironmentProps = {
|
||||
};
|
||||
metrics?: Pick<IFeatureEnvironmentMetrics, 'yes' | 'no'>;
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<EnvironmentHeader
|
||||
environmentMetadata={{
|
||||
|
@ -10,6 +10,7 @@ import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePla
|
||||
|
||||
type FeatureOverviewEnvironmentsProps = {
|
||||
hiddenEnvironments?: string[];
|
||||
onToggleEnvOpen?: (isOpen: boolean) => 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) => (
|
||||
<FeatureOverviewWithReleasePlans
|
||||
onToggleEnvOpen={onToggleEnvOpen}
|
||||
environment={env}
|
||||
key={env.name}
|
||||
metrics={featureMetrics.find(
|
||||
|
@ -0,0 +1,101 @@
|
||||
import Close from '@mui/icons-material/Close';
|
||||
import { Box, Button, IconButton, styled } from '@mui/material';
|
||||
import type { FC } from 'react';
|
||||
import Joyride, { type TooltipRenderProps } from 'react-joyride';
|
||||
|
||||
const StyledTooltip = styled(Box)(({ theme }) => ({
|
||||
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 (
|
||||
<StyledTooltip component='article'>
|
||||
<StyledCloseButton type='button' {...closeProps}>
|
||||
<Close />
|
||||
</StyledCloseButton>
|
||||
<StyledHeader>Decide the order evaluation</StyledHeader>
|
||||
<p>
|
||||
Strategies are evaluated in the order presented here. Drag and
|
||||
rearrange the strategies to get the order you prefer.
|
||||
</p>
|
||||
<OkButton
|
||||
type='button'
|
||||
data-action={closeProps['data-action']}
|
||||
onClick={closeProps.onClick}
|
||||
>
|
||||
Ok, got it!
|
||||
</OkButton>
|
||||
</StyledTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const StrategyDragTooltip: FC<Props> = ({ show, onClose }) => {
|
||||
return (
|
||||
<Joyride
|
||||
callback={({ action }) => {
|
||||
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: <></>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user