1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

Sticky batch actions bar (#3366)

This commit is contained in:
Tymoteusz Czech 2023-03-22 13:15:53 +01:00 committed by GitHub
parent e03307e286
commit 5585a9bed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 240 deletions

View File

@ -1,6 +1,6 @@
import { FC, useState } from 'react';
import { Button } from '@mui/material';
import { Undo } from '@mui/icons-material';
import { Delete, Undo } from '@mui/icons-material';
import {
DELETE_FEATURE,
UPDATE_FEATURE,
@ -69,7 +69,7 @@ export const ArchiveBatchActions: FC<IArchiveBatchActionsProps> = ({
{({ hasAccess }) => (
<Button
disabled={!hasAccess}
startIcon={<Undo />}
startIcon={<Delete />}
variant="outlined"
size="small"
onClick={onDelete}

View File

@ -294,64 +294,67 @@ export const ArchiveTable = ({
}, [loading, sortBy, searchValue]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<PageContent
isLoading={loading}
header={
<PageHeader
titleElement={`${title} (${
rows.length < data.length
? `${rows.length} of ${data.length}`
: data.length
})`}
actions={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={() => (
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching &ldquo;
{searchValue}&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
None of the feature toggles were archived yet.
</TablePlaceholder>
<>
<PageContent
isLoading={loading}
header={
<PageHeader
titleElement={`${title} (${
rows.length < data.length
? `${rows.length} of ${data.length}`
: data.length
})`}
actions={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
)}
/>
<ArchivedFeatureDeleteConfirm
deletedFeatures={[deletedFeature?.name!]}
projectId={projectId!}
open={deleteModalOpen}
setOpen={setDeleteModalOpen}
refetch={refetch}
/>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={() => (
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching &ldquo;
{searchValue}&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
None of the feature toggles were archived
yet.
</TablePlaceholder>
}
/>
)}
/>
<ArchivedFeatureDeleteConfirm
deletedFeatures={[deletedFeature?.name!]}
projectId={projectId!}
open={deleteModalOpen}
setOpen={setDeleteModalOpen}
refetch={refetch}
/>
</PageContent>
<ConditionallyRender
condition={Boolean(projectId)}
show={
<BatchSelectionActionsBar
selectedIds={Object.keys(selectedRowIds)}
count={Object.keys(selectedRowIds).length}
>
<ArchiveBatchActions
selectedIds={Object.keys(selectedRowIds)}
@ -360,6 +363,6 @@ export const ArchiveTable = ({
</BatchSelectionActionsBar>
}
/>
</PageContent>
</>
);
};

View File

@ -2,14 +2,21 @@ import { FC } from 'react';
import { Box, Paper, styled, Typography } from '@mui/material';
interface IBatchSelectionActionsBarProps {
selectedIds: string[];
count: number;
}
const StyledContainer = styled(Box)(() => ({
const StyledStickyContainer = styled('div')(() => ({
position: 'sticky',
marginTop: 'auto',
bottom: 0,
}));
const StyledContainer = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
width: '100%',
flexWrap: 'wrap',
paddingBottom: theme.spacing(2),
}));
const StyledBar = styled(Paper)(({ theme }) => ({
@ -40,22 +47,24 @@ const StyledText = styled(Typography)(({ theme }) => ({
}));
export const BatchSelectionActionsBar: FC<IBatchSelectionActionsBarProps> = ({
selectedIds,
count,
children,
}) => {
if (selectedIds.length === 0) {
if (count === 0) {
return null;
}
return (
<StyledContainer>
<StyledBar elevation={4}>
<StyledText>
<StyledCount>{selectedIds.length}</StyledCount>
&ensp;selected
</StyledText>
{children}
</StyledBar>
</StyledContainer>
<StyledStickyContainer>
<StyledContainer>
<StyledBar elevation={4}>
<StyledText>
<StyledCount>{count}</StyledCount>
&ensp;selected
</StyledText>
{children}
</StyledBar>
</StyledContainer>
</StyledStickyContainer>
);
};

View File

@ -91,7 +91,6 @@ export const InstanceStatus: FC = ({ children }) => {
useInstanceStatus();
const { extendTrial } = useInstanceStatusApi();
const { setToastApiError } = useToast();
const theme = useTheme();
const onExtendTrial = async () => {
try {
@ -103,12 +102,7 @@ export const InstanceStatus: FC = ({ children }) => {
};
return (
<div
style={{
height: '100%',
backgroundColor: theme.palette.background.paper,
}}
>
<>
<ConditionallyRender
condition={isBilling && Boolean(instanceStatus)}
show={() => (
@ -124,7 +118,7 @@ export const InstanceStatus: FC = ({ children }) => {
)}
/>
{children}
</div>
</>
);
};

View File

@ -21,12 +21,15 @@ interface IMainLayoutProps {
const MainLayoutContainer = styled(Grid)(() => ({
height: '100%',
justifyContent: 'space-between',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
position: 'relative',
}));
const MainLayoutContentWrapper = styled('main')(({ theme }) => ({
margin: theme.spacing(0, 'auto'),
overflow: 'auto', // prevent margin collapsing
flex: 1,
flexGrow: 1,
width: '100%',
backgroundColor: theme.palette.background.application,
position: 'relative',

View File

@ -554,174 +554,187 @@ export const ProjectFeatureToggles = ({
]);
return (
<PageContent
isLoading={loading}
className={styles.container}
header={
<PageHeader
titleElement={`Feature toggles (${rows.length})`}
actions={
<>
<ConditionallyRender
condition={!isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
<ColumnsMenu
allColumns={allColumns}
staticColumns={staticColumns}
dividerAfter={['createdAt']}
dividerBefore={['Actions']}
isCustomized={Boolean(storedParams.columns)}
setHiddenColumns={setHiddenColumns}
/>
<PageHeader.Divider sx={{ marginLeft: 0 }} />
<ConditionallyRender
condition={Boolean(
uiConfig?.flags?.featuresExportImport
)}
show={
<Tooltip
title="Export toggles visible in the table below"
arrow
>
<IconButton
onClick={() =>
setShowExportDialog(true)
}
sx={theme => ({
marginRight: theme.spacing(2),
})}
<>
<PageContent
isLoading={loading}
className={styles.container}
header={
<PageHeader
titleElement={`Feature toggles (${rows.length})`}
actions={
<>
<ConditionallyRender
condition={!isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
<ColumnsMenu
allColumns={allColumns}
staticColumns={staticColumns}
dividerAfter={['createdAt']}
dividerBefore={['Actions']}
isCustomized={Boolean(storedParams.columns)}
setHiddenColumns={setHiddenColumns}
/>
<PageHeader.Divider sx={{ marginLeft: 0 }} />
<ConditionallyRender
condition={Boolean(
uiConfig?.flags?.featuresExportImport
)}
show={
<Tooltip
title="Export toggles visible in the table below"
arrow
>
<FileDownload />
</IconButton>
</Tooltip>
}
/>
<StyledResponsiveButton
onClick={() =>
navigate(getCreateTogglePath(projectId))
}
maxWidth="960px"
Icon={Add}
projectId={projectId}
permission={CREATE_FEATURE}
data-testid="NAVIGATE_TO_CREATE_FEATURE"
>
New feature toggle
</StyledResponsiveButton>
</>
<IconButton
onClick={() =>
setShowExportDialog(true)
}
sx={theme => ({
marginRight:
theme.spacing(2),
})}
>
<FileDownload />
</IconButton>
</Tooltip>
}
/>
<StyledResponsiveButton
onClick={() =>
navigate(getCreateTogglePath(projectId))
}
maxWidth="960px"
Icon={Add}
projectId={projectId}
permission={CREATE_FEATURE}
data-testid="NAVIGATE_TO_CREATE_FEATURE"
>
New feature toggle
</StyledResponsiveButton>
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
</PageHeader>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching &ldquo;
{searchValue}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No feature toggles available. Get started by
adding a new feature toggle.
</TablePlaceholder>
}
/>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
</PageHeader>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching &ldquo;
{searchValue}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No feature toggles available. Get started by
adding a new feature toggle.
</TablePlaceholder>
}
/>
}
/>
<EnvironmentStrategyDialog
onClose={() =>
setStrategiesDialogState(prev => ({ ...prev, open: false }))
}
projectId={projectId}
{...strategiesDialogState}
/>
<FeatureStaleDialog
isStale={featureStaleDialogState.stale === true}
isOpen={Boolean(featureStaleDialogState.featureId)}
onClose={() => {
setFeatureStaleDialogState({});
refetch();
}}
featureId={featureStaleDialogState.featureId || ''}
projectId={projectId}
/>
<FeatureArchiveDialog
isOpen={Boolean(featureArchiveState)}
onConfirm={() => {
refetch();
}}
onClose={() => {
setFeatureArchiveState(undefined);
}}
featureIds={[featureArchiveState || '']}
projectId={projectId}
/>{' '}
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm}
messageComponent={
<UpdateEnabledMessage
featureName={changeRequestDialogDetails.featureName!}
enabled={changeRequestDialogDetails.enabled!}
environment={changeRequestDialogDetails?.environment!}
/>
}
/>
<ConditionallyRender
condition={
Boolean(uiConfig?.flags?.featuresExportImport) && !loading
}
show={
<ExportDialog
showExportDialog={showExportDialog}
data={data}
onClose={() => setShowExportDialog(false)}
environments={environments}
/>
}
/>
<BatchSelectionActionsBar selectedIds={Object.keys(selectedRowIds)}>
<EnvironmentStrategyDialog
onClose={() =>
setStrategiesDialogState(prev => ({
...prev,
open: false,
}))
}
projectId={projectId}
{...strategiesDialogState}
/>
<FeatureStaleDialog
isStale={featureStaleDialogState.stale === true}
isOpen={Boolean(featureStaleDialogState.featureId)}
onClose={() => {
setFeatureStaleDialogState({});
refetch();
}}
featureId={featureStaleDialogState.featureId || ''}
projectId={projectId}
/>
<FeatureArchiveDialog
isOpen={Boolean(featureArchiveState)}
onConfirm={() => {
refetch();
}}
onClose={() => {
setFeatureArchiveState(undefined);
}}
featureIds={[featureArchiveState || '']}
projectId={projectId}
/>{' '}
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm}
messageComponent={
<UpdateEnabledMessage
featureName={
changeRequestDialogDetails.featureName!
}
enabled={changeRequestDialogDetails.enabled!}
environment={
changeRequestDialogDetails?.environment!
}
/>
}
/>
<ConditionallyRender
condition={
Boolean(uiConfig?.flags?.featuresExportImport) &&
!loading
}
show={
<ExportDialog
showExportDialog={showExportDialog}
data={data}
onClose={() => setShowExportDialog(false)}
environments={environments}
/>
}
/>
</PageContent>
<BatchSelectionActionsBar
count={Object.keys(selectedRowIds).length}
>
<ProjectFeaturesBatchActions
selectedIds={Object.keys(selectedRowIds)}
data={features}
projectId={projectId}
/>
</BatchSelectionActionsBar>
</PageContent>
</>
);
};

View File

@ -9,10 +9,13 @@ html {
}
body {
height: 100%;
min-height: 100%;
font-family: 'Sen', sans-serif;
font-size: 16px;
font-variant-ligatures: none;
padding: 0;
display: flex;
flex-direction: column;
}
button {
@ -139,7 +142,10 @@ a:hover {
}
#app {
height: 100%;
flex-grow: 1;
min-height: 100%;
display: flex;
flex-direction: column;
}
.MuiCardHeader-title {