mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-15 01:16:22 +02:00
chore(1-3380): handle narrow windows for the flag header. (#9321)
Makes it so that the actions/tabs wrap on narrow width screens. Constraints: - When wrapping, the actions should go **before** the tabs, when not wrapping, they should be placed **after** - Need to maintain a logical tab order for wrapping, so just using `flex-flow: row wrap-reverse` doesn't work because the tab order will be wrong - When the elements switch, you shouldn't lose your tab place in the document This solution uses container queries to determine the container size and uses that to set the wrapping. Falls back to media queries if container queries aren't supported (it's supported on >93% of browsers according to caniuse). The wrapping points don't use predefined media queries because: - containers don't care about the size of the screen. It's the intrinsic size of the container that matters. - wrapping at 900px seemed too far out if container queries are unsupported. But it's a fallback, so we can switch to that if we want. If your keyboard focus is on one of the actions on a wide screen, and the screen goes narrow, your focus will still be after the tabs (staying consistent), so tabbing to the next element will take you into the flag details, while backtab takes you back to the tabs. Before wrapping:  After wrapping:  ## A note on accessibility: The spec for flexbox (taken from [MDN's doc](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Ordering_flex_items)) states: > "Authors must not use order or the *-reverse values of [flex-flow](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow)/flex-direction as a substitute for correct source ordering, as that can ruin the accessibility of the document." So even if wrap-reverse works visually, it's not a good solution for this.
This commit is contained in:
parent
bdecad10c9
commit
6e1f683902
@ -4,6 +4,7 @@ import {
|
||||
styled,
|
||||
Tab,
|
||||
Tabs,
|
||||
type Theme,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
@ -49,8 +50,16 @@ const NewStyledHeader = styled('div')(({ theme }) => ({
|
||||
backgroundColor: 'none',
|
||||
marginBottom: theme.spacing(2),
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
containerType: 'inline-size',
|
||||
}));
|
||||
|
||||
const onNarrowHeader = (theme: Theme, css: object) => ({
|
||||
'@container (max-width: 650px)': css,
|
||||
'@supports not (container-type: inline-size)': {
|
||||
[theme.breakpoints.down('md')]: css,
|
||||
},
|
||||
});
|
||||
|
||||
const UpperHeaderRow = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row wrap',
|
||||
@ -60,12 +69,23 @@ const UpperHeaderRow = styled('div')(({ theme }) => ({
|
||||
|
||||
const LowerHeaderRow = styled(UpperHeaderRow)(({ theme }) => ({
|
||||
justifyContent: 'space-between',
|
||||
flexFlow: 'row nowrap',
|
||||
...onNarrowHeader(theme, {
|
||||
flexFlow: 'column nowrap',
|
||||
alignItems: 'flex-start',
|
||||
}),
|
||||
}));
|
||||
|
||||
const HeaderActions = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
const HeaderActions = styled('div', {
|
||||
shouldForwardProp: (propName) => propName !== 'showOnNarrowScreens',
|
||||
})<{ showOnNarrowScreens?: boolean }>(({ theme, showOnNarrowScreens }) => ({
|
||||
display: showOnNarrowScreens ? 'none' : 'flex',
|
||||
flexFlow: 'row nowrap',
|
||||
alignItems: 'center',
|
||||
|
||||
...onNarrowHeader(theme, {
|
||||
display: showOnNarrowScreens ? 'flex' : 'none',
|
||||
}),
|
||||
}));
|
||||
|
||||
const IconButtonWithTooltip: FC<
|
||||
@ -174,6 +194,79 @@ const useLegacyVariants = (environments: IFeatureToggle['environments']) => {
|
||||
return enableLegacyVariants || existingLegacyVariantsExist;
|
||||
};
|
||||
|
||||
type HeaderActionsProps = {
|
||||
feature: IFeatureToggle;
|
||||
showOnNarrowScreens?: boolean;
|
||||
onFavorite: () => void;
|
||||
handleCopyToClipboard: () => void;
|
||||
isFeatureNameCopied: boolean;
|
||||
openStaleDialog: () => void;
|
||||
openDeleteDialog: () => void;
|
||||
};
|
||||
|
||||
const HeaderActionsComponent = ({
|
||||
showOnNarrowScreens,
|
||||
feature,
|
||||
onFavorite,
|
||||
handleCopyToClipboard,
|
||||
isFeatureNameCopied,
|
||||
openStaleDialog,
|
||||
openDeleteDialog,
|
||||
}: HeaderActionsProps) => (
|
||||
<HeaderActions showOnNarrowScreens={showOnNarrowScreens}>
|
||||
<IconButtonWithTooltip
|
||||
label='Favorite this feature flag'
|
||||
onClick={onFavorite}
|
||||
data-loading
|
||||
>
|
||||
{feature.favorite ? <Star /> : <StarBorder />}
|
||||
</IconButtonWithTooltip>
|
||||
|
||||
<IconButtonWithTooltip
|
||||
label='Copy flag name'
|
||||
onClick={handleCopyToClipboard}
|
||||
data-loading
|
||||
>
|
||||
{isFeatureNameCopied ? <Check /> : <FileCopyOutlined />}
|
||||
</IconButtonWithTooltip>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={feature.project}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${feature.project}/features/${feature.name}/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Clone',
|
||||
}}
|
||||
>
|
||||
<LibraryAddOutlined />
|
||||
</PermissionIconButton>
|
||||
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={feature.project}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature flag',
|
||||
}}
|
||||
data-loading
|
||||
onClick={openDeleteDialog}
|
||||
>
|
||||
<ArchiveOutlined />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={openStaleDialog}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={feature.project}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLaterOutlined />
|
||||
</PermissionIconButton>
|
||||
</HeaderActions>
|
||||
);
|
||||
|
||||
type Props = {
|
||||
feature: IFeatureToggle;
|
||||
};
|
||||
@ -260,6 +353,22 @@ export const FeatureViewHeader: FC<Props> = ({ feature }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const HeaderActionsInner: FC<{ showOnNarrowScreens?: boolean }> = ({
|
||||
showOnNarrowScreens,
|
||||
}) => {
|
||||
return (
|
||||
<HeaderActionsComponent
|
||||
showOnNarrowScreens={showOnNarrowScreens}
|
||||
feature={feature}
|
||||
onFavorite={onFavorite}
|
||||
handleCopyToClipboard={handleCopyToClipboard}
|
||||
isFeatureNameCopied={isFeatureNameCopied}
|
||||
openStaleDialog={() => setOpenStaleDialog(true)}
|
||||
openDeleteDialog={() => setShowDelDialog(true)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{flagOverviewRedesign ? (
|
||||
@ -271,6 +380,7 @@ export const FeatureViewHeader: FC<Props> = ({ feature }) => {
|
||||
) : null}
|
||||
</UpperHeaderRow>
|
||||
<LowerHeaderRow>
|
||||
<HeaderActionsInner showOnNarrowScreens />
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor='primary'
|
||||
@ -286,62 +396,7 @@ export const FeatureViewHeader: FC<Props> = ({ feature }) => {
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
<HeaderActions>
|
||||
<IconButtonWithTooltip
|
||||
label='Favorite this feature flag'
|
||||
onClick={onFavorite}
|
||||
data-loading
|
||||
>
|
||||
{feature.favorite ? <Star /> : <StarBorder />}
|
||||
</IconButtonWithTooltip>
|
||||
|
||||
<IconButtonWithTooltip
|
||||
label='Copy flag name'
|
||||
onClick={handleCopyToClipboard}
|
||||
data-loading
|
||||
>
|
||||
{isFeatureNameCopied ? (
|
||||
<Check />
|
||||
) : (
|
||||
<FileCopyOutlined />
|
||||
)}
|
||||
</IconButtonWithTooltip>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Clone',
|
||||
}}
|
||||
>
|
||||
<LibraryAddOutlined />
|
||||
</PermissionIconButton>
|
||||
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature flag',
|
||||
}}
|
||||
data-loading
|
||||
onClick={() => setShowDelDialog(true)}
|
||||
>
|
||||
<ArchiveOutlined />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenStaleDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLaterOutlined />
|
||||
</PermissionIconButton>
|
||||
</HeaderActions>
|
||||
<HeaderActionsInner />
|
||||
</LowerHeaderRow>
|
||||
</NewStyledHeader>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user