mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
feat: move 'copy flag name' button (#9805)
- moved "copy flag name" action next to the flag name - refactored this component into a separate file - added "Ctrl+C" shortcut
This commit is contained in:
parent
bc7856a23a
commit
18850a5156
@ -0,0 +1,61 @@
|
|||||||
|
import { useState, type FC } from 'react';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { useKeyboardCopy } from 'hooks/useKeyboardCopy';
|
||||||
|
import { IconButton, Tooltip } from '@mui/material';
|
||||||
|
import Check from '@mui/icons-material/Check';
|
||||||
|
import FileCopyOutlined from '@mui/icons-material/FileCopyOutlined';
|
||||||
|
|
||||||
|
const iconSize = 18 / 8;
|
||||||
|
|
||||||
|
export const FeatureCopyName: FC<{ name: string }> = ({ name }) => {
|
||||||
|
const [isFeatureNameCopied, setIsFeatureNameCopied] = useState(false);
|
||||||
|
const { setToastData } = useToast();
|
||||||
|
|
||||||
|
const handleCopyToClipboard = () => {
|
||||||
|
try {
|
||||||
|
copy(name);
|
||||||
|
setIsFeatureNameCopied(true);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setIsFeatureNameCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastData({
|
||||||
|
type: 'error',
|
||||||
|
text: 'Could not copy feature name',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortcutDescription = useKeyboardCopy(handleCopyToClipboard);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
isFeatureNameCopied
|
||||||
|
? 'Copied!'
|
||||||
|
: `Copy name (${shortcutDescription})`
|
||||||
|
}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<IconButton onClick={handleCopyToClipboard}>
|
||||||
|
{isFeatureNameCopied ? (
|
||||||
|
<Check
|
||||||
|
sx={(theme) => ({
|
||||||
|
fontSize: theme.spacing(iconSize),
|
||||||
|
color: theme.palette.success.main,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FileCopyOutlined
|
||||||
|
sx={(theme) => ({ fontSize: theme.spacing(iconSize) })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
@ -12,7 +12,6 @@ import {
|
|||||||
import Archive from '@mui/icons-material/Archive';
|
import Archive from '@mui/icons-material/Archive';
|
||||||
import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
|
import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
|
||||||
import FileCopy from '@mui/icons-material/FileCopy';
|
import FileCopy from '@mui/icons-material/FileCopy';
|
||||||
import FileCopyOutlined from '@mui/icons-material/FileCopyOutlined';
|
|
||||||
import Label from '@mui/icons-material/Label';
|
import Label from '@mui/icons-material/Label';
|
||||||
import WatchLater from '@mui/icons-material/WatchLater';
|
import WatchLater from '@mui/icons-material/WatchLater';
|
||||||
import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined';
|
import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined';
|
||||||
@ -45,6 +44,7 @@ import { ManageTagsDialog } from './FeatureOverview/ManageTagsDialog/ManageTagsD
|
|||||||
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
||||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||||
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
|
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
|
||||||
|
import { FeatureCopyName } from './FeatureCopyName/FeatureCopyName';
|
||||||
|
|
||||||
const NewStyledHeader = styled('div')(({ theme }) => ({
|
const NewStyledHeader = styled('div')(({ theme }) => ({
|
||||||
backgroundColor: 'none',
|
backgroundColor: 'none',
|
||||||
@ -64,7 +64,13 @@ const UpperHeaderRow = styled('div')(({ theme }) => ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexFlow: 'row wrap',
|
flexFlow: 'row wrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
columnGap: theme.spacing(2),
|
columnGap: theme.spacing(1.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
columnGap: theme.spacing(0.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const LowerHeaderRow = styled(UpperHeaderRow)(({ theme }) => ({
|
const LowerHeaderRow = styled(UpperHeaderRow)(({ theme }) => ({
|
||||||
@ -210,8 +216,6 @@ type HeaderActionsProps = {
|
|||||||
feature: IFeatureToggle;
|
feature: IFeatureToggle;
|
||||||
showOnNarrowScreens?: boolean;
|
showOnNarrowScreens?: boolean;
|
||||||
onFavorite: () => void;
|
onFavorite: () => void;
|
||||||
handleCopyToClipboard: () => void;
|
|
||||||
isFeatureNameCopied: boolean;
|
|
||||||
openStaleDialog: () => void;
|
openStaleDialog: () => void;
|
||||||
openDeleteDialog: () => void;
|
openDeleteDialog: () => void;
|
||||||
};
|
};
|
||||||
@ -220,8 +224,6 @@ const HeaderActionsComponent = ({
|
|||||||
showOnNarrowScreens,
|
showOnNarrowScreens,
|
||||||
feature,
|
feature,
|
||||||
onFavorite,
|
onFavorite,
|
||||||
handleCopyToClipboard,
|
|
||||||
isFeatureNameCopied,
|
|
||||||
openStaleDialog,
|
openStaleDialog,
|
||||||
openDeleteDialog,
|
openDeleteDialog,
|
||||||
}: HeaderActionsProps) => (
|
}: HeaderActionsProps) => (
|
||||||
@ -233,14 +235,6 @@ const HeaderActionsComponent = ({
|
|||||||
>
|
>
|
||||||
{feature.favorite ? <Star /> : <StarBorder />}
|
{feature.favorite ? <Star /> : <StarBorder />}
|
||||||
</IconButtonWithTooltip>
|
</IconButtonWithTooltip>
|
||||||
|
|
||||||
<IconButtonWithTooltip
|
|
||||||
label='Copy flag name'
|
|
||||||
onClick={handleCopyToClipboard}
|
|
||||||
data-loading
|
|
||||||
>
|
|
||||||
{isFeatureNameCopied ? <Check /> : <FileCopyOutlined />}
|
|
||||||
</IconButtonWithTooltip>
|
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={CREATE_FEATURE}
|
permission={CREATE_FEATURE}
|
||||||
projectId={feature.project}
|
projectId={feature.project}
|
||||||
@ -373,8 +367,6 @@ export const FeatureViewHeader: FC<Props> = ({ feature }) => {
|
|||||||
showOnNarrowScreens={showOnNarrowScreens}
|
showOnNarrowScreens={showOnNarrowScreens}
|
||||||
feature={feature}
|
feature={feature}
|
||||||
onFavorite={onFavorite}
|
onFavorite={onFavorite}
|
||||||
handleCopyToClipboard={handleCopyToClipboard}
|
|
||||||
isFeatureNameCopied={isFeatureNameCopied}
|
|
||||||
openStaleDialog={() => setOpenStaleDialog(true)}
|
openStaleDialog={() => setOpenStaleDialog(true)}
|
||||||
openDeleteDialog={() => setShowDelDialog(true)}
|
openDeleteDialog={() => setShowDelDialog(true)}
|
||||||
/>
|
/>
|
||||||
@ -386,7 +378,11 @@ export const FeatureViewHeader: FC<Props> = ({ feature }) => {
|
|||||||
{flagOverviewRedesign ? (
|
{flagOverviewRedesign ? (
|
||||||
<NewStyledHeader>
|
<NewStyledHeader>
|
||||||
<UpperHeaderRow>
|
<UpperHeaderRow>
|
||||||
<Typography variant='h1'>{feature.name}</Typography>
|
<StyledTitle>
|
||||||
|
<Typography variant='h1'>{feature.name}</Typography>
|
||||||
|
<FeatureCopyName name={feature.name} />
|
||||||
|
</StyledTitle>
|
||||||
|
<FeatureStatusChip stale={true} />
|
||||||
{feature.stale ? (
|
{feature.stale ? (
|
||||||
<FeatureStatusChip stale={true} />
|
<FeatureStatusChip stale={true} />
|
||||||
) : null}
|
) : null}
|
||||||
|
19
frontend/src/hooks/useKeyboardCopy.ts
Normal file
19
frontend/src/hooks/useKeyboardCopy.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useKeyboardShortcut } from './useKeyboardShortcut';
|
||||||
|
|
||||||
|
export const useKeyboardCopy = (handler: () => void) =>
|
||||||
|
useKeyboardShortcut(
|
||||||
|
{
|
||||||
|
key: 'c',
|
||||||
|
modifiers: ['ctrl'],
|
||||||
|
preventDefault: false,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const selection = window.getSelection?.();
|
||||||
|
if (
|
||||||
|
selection &&
|
||||||
|
(selection.type === 'None' || selection.type === 'Caret')
|
||||||
|
) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user