Skip to content

Commit

Permalink
fix(pf5): allow menu footer to be sticky and use danger style for del…
Browse files Browse the repository at this point in the history
…ete/clear buttons (#1346)

Signed-off-by: Thuan Vo <[email protected]>
  • Loading branch information
tthvo authored Sep 9, 2024
1 parent 3582233 commit fb87a10
Show file tree
Hide file tree
Showing 18 changed files with 107 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/app/Dashboard/AddCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ const SelectControl: React.FC<SelectControlProps> = ({ handleChange, control, se
appendTo: portalRoot,
}}
isScrollable
maxMenuHeight={'30vh'}
maxMenuHeight="40vh"
onOpenChange={setSelectOpen}
onOpenChangeKeys={['Escape']}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const AutomatedAnalysisNameFilter: React.FC<AutomatedAnalysisNameFilterPr
onOpenChangeKeys={['Escape']}
shouldFocusFirstItemOnOpen={false}
isScrollable
maxMenuHeight={'30vh'}
maxMenuHeight="40vh"
>
<SelectList id="typeahead-filter-select">
{selectOptionProps.map(({ value, children }, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const AutomatedAnalysisTopicFilter: React.FC<AutomatedAnalysisTopicFilter
onOpenChangeKeys={['Escape']}
shouldFocusFirstItemOnOpen={false}
isScrollable
maxMenuHeight={'30vh'}
maxMenuHeight="40vh"
>
<SelectList id="typeahead-topic-filter">
{selectOptionProps.map(({ value, children }, index) => (
Expand Down
10 changes: 6 additions & 4 deletions src/app/Dashboard/DashboardCardActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/
import { portalRoot } from '@app/utils/utils';
import { Dropdown, DropdownItem, DropdownList, MenuToggle, MenuToggleElement } from '@patternfly/react-core';
import { Divider, Dropdown, DropdownItem, DropdownList, MenuToggle, MenuToggleElement } from '@patternfly/react-core';
import { EllipsisVIcon } from '@patternfly/react-icons';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -66,12 +66,14 @@ export const DashboardCardActionMenu: React.FC<DashboardCardActionProps> = ({ on
<DropdownItem key="View" onClick={onView}>
{t('VIEW', { ns: 'common' })}
</DropdownItem>
<DropdownItem key="Remove" onClick={onRemove}>
{t('REMOVE', { ns: 'common' })}
</DropdownItem>

<DropdownItem key="Reset Size" onClick={onResetSize}>
{t('DashboardCardActionMenu.RESET_SIZE')}
</DropdownItem>
<Divider />
<DropdownItem key="Remove" onClick={onRemove} isDanger>
{t('REMOVE', { ns: 'common' })}
</DropdownItem>
</DropdownList>
</Dropdown>
);
Expand Down
5 changes: 3 additions & 2 deletions src/app/Dashboard/DashboardLayoutToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ export const DashboardLayoutToolbar: React.FC<DashboardLayoutToolbarProps> = (_p
<DropdownItem key="download" value={'download'}>
{t('DashboardLayoutToolbar.DOWNLOAD_AS_TEMPLATE')}
</DropdownItem>
<DropdownItem key="clearAll" value={'clearAll'} isDisabled={currLayout.cards.length < 1}>
<Divider />
<DropdownItem key="clearAll" value={'clearAll'} isDisabled={currLayout.cards.length < 1} isDanger>
{t('DashboardLayoutToolbar.CLEAR_LAYOUT')}
</DropdownItem>
</>
Expand Down Expand Up @@ -499,7 +500,7 @@ export const DashboardLayoutToolbar: React.FC<DashboardLayoutToolbarProps> = (_p
)}
>
<Menu isScrollable onSelect={onLayoutSelect} onActionClick={onActionClick}>
<MenuContent maxMenuHeight="21.5em" id="dashboard-layout-menu-content">
<MenuContent maxMenuHeight="40vh" id="dashboard-layout-menu-content">
{menuGroups(t('DashboardLayoutToolbar.MENU.FAVORITES'), true)}
<Divider />
{menuGroups(t('DashboardLayoutToolbar.MENU.OTHERS'), false)}
Expand Down
4 changes: 3 additions & 1 deletion src/app/Dashboard/LayoutTemplateGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
DropdownList,
MenuToggle,
MenuToggleElement,
Divider,
} from '@patternfly/react-core';
import { EllipsisVIcon } from '@patternfly/react-icons';
import * as React from 'react';
Expand Down Expand Up @@ -175,7 +176,8 @@ export const KebabCatalogTileBadge: React.FC<KebabCatalogTileBadgeProps> = ({ te
<DropdownItem key={'download'} onClick={handleTemplateDownload}>
{t('DOWNLOAD', { ns: 'common' })}
</DropdownItem>,
<DropdownItem key={'delete'} onClick={handleTemplateDelete}>
<Divider key="divider" />,
<DropdownItem key={'delete'} onClick={handleTemplateDelete} isDanger>
{t('DELETE', { ns: 'common' })}
</DropdownItem>,
];
Expand Down
2 changes: 1 addition & 1 deletion src/app/DateTimePicker/TimezonePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const TimezonePicker: React.FC<TimezonePickerProps> = ({
onOpenChange={(isOpen) => setIsTimezoneOpen(isOpen)}
onOpenChangeKeys={['Escape']}
isScrollable
menuHeight="20vh"
maxMenuHeight="40vh"
>
<MenuSearch>
<MenuSearchInput>
Expand Down
1 change: 1 addition & 0 deletions src/app/Events/EventTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export const EventTemplates: React.FC<EventTemplatesProps> = () => {
{
title: 'Delete',
onClick: () => handleDeleteButton(t),
isDanger: true,
},
]);
}
Expand Down
5 changes: 3 additions & 2 deletions src/app/Rules/Rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { MatchExpressionDisplay } from '@app/Shared/Components/MatchExpression/M
import { Rule, NotificationCategory } from '@app/Shared/Services/api.types';
import { ServiceContext } from '@app/Shared/Services/Services';
import { useSubscriptions } from '@app/utils/hooks/useSubscriptions';
import { TableColumn, formatBytes, formatDuration, sortResources } from '@app/utils/utils';
import { TableColumn, formatBytes, formatDuration, sortResources, portalRoot } from '@app/utils/utils';
import {
Button,
Card,
Expand Down Expand Up @@ -301,6 +301,7 @@ export const RulesTable: React.FC<RulesTableProps> = () => {
{
title: t('DELETE', { ns: 'common' }),
onClick: () => handleDeleteButton(rule),
isDanger: true,
},
];
},
Expand Down Expand Up @@ -366,7 +367,7 @@ export const RulesTable: React.FC<RulesTableProps> = () => {
<ActionsColumn
items={actionResolver(r)}
popperProps={{
appendTo: () => document.getElementById('automated-rule-toolbar') || document.body,
appendTo: portalRoot,
position: 'right',
}}
/>
Expand Down
26 changes: 12 additions & 14 deletions src/app/Settings/Config/DatetimeControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,22 @@ const Component = () => {
}}
selected={datetimeFormat.dateLocale}
onSelect={handleDateLocaleSelect}
menuHeight="20vh"
maxMenuHeight="40vh"
isScrollable
onOpenChange={setDateLocaleOpen}
onOpenChangeKeys={['Escape']}
>
<SelectList>
<MenuSearch>
<MenuSearchInput>
<SearchInput
placeholder={t('SETTINGS.DATETIME_CONTROL.SEARCH_PLACEHOLDER')}
value={searchTerm}
onChange={onInputChange}
/>
</MenuSearchInput>
</MenuSearch>
<Divider />
{dateLocaleOptions}
</SelectList>
<MenuSearch>
<MenuSearchInput>
<SearchInput
placeholder={t('SETTINGS.DATETIME_CONTROL.SEARCH_PLACEHOLDER')}
value={searchTerm}
onChange={onInputChange}
/>
</MenuSearchInput>
</MenuSearch>
<Divider />
<SelectList>{dateLocaleOptions}</SelectList>
</Select>
</FormGroup>
</StackItem>
Expand Down
32 changes: 32 additions & 0 deletions src/app/Shared/Components/ScrollableMenuContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The Cryostat Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Panel, PanelMain, PanelMainBody } from '@patternfly/react-core';
import * as React from 'react';

export interface ScrollableMenuContentProps {
maxHeight?: string;
}

// Use case: Menu footer or search bar needs to be sticky.
export const ScrollableMenuContent: React.FC<ScrollableMenuContentProps> = ({ children, maxHeight }) => {
return (
<Panel isScrollable>
<PanelMain maxHeight={maxHeight}>
<PanelMainBody style={{ padding: 0 }}>{children}</PanelMainBody>
</PanelMain>
</Panel>
);
};
46 changes: 24 additions & 22 deletions src/app/TargetView/TargetContextSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/
import { LinearDotSpinner } from '@app/Shared/Components/LinearDotSpinner';
import { ScrollableMenuContent } from '@app/Shared/Components/ScrollableMenuContent';
import { Target } from '@app/Shared/Services/api.types';
import { isEqualTarget, getTargetRepresentation } from '@app/Shared/Services/api.utils';
import { ServiceContext } from '@app/Shared/Services/Services';
Expand All @@ -33,8 +34,8 @@ import {
MenuToggle,
MenuSearch,
MenuSearchInput,
Split,
SplitItem,
ActionList,
ActionListItem,
} from '@patternfly/react-core';
import _ from 'lodash';
import * as React from 'react';
Expand Down Expand Up @@ -206,18 +207,18 @@ export const TargetContextSelector: React.FC<TargetContextSelectorProps> = ({ cl

const selectFooter = React.useMemo(
() => (
<Split hasGutter>
<SplitItem>
<ActionList>
<ActionListItem>
<Button variant="secondary" component={(props) => <Link {...props} to={'/topology/create-custom-target'} />}>
{t('TargetContextSelector.CREATE_TARGET')}
</Button>
</SplitItem>
<SplitItem>
<Button variant="tertiary" onClick={onClearSelection}>
</ActionListItem>
<ActionListItem>
<Button variant="link" onClick={onClearSelection}>
{t('TargetContextSelector.CLEAR_SELECTION')}
</Button>
</SplitItem>
</Split>
</ActionListItem>
</ActionList>
),
[t, onClearSelection],
);
Expand All @@ -230,10 +231,9 @@ export const TargetContextSelector: React.FC<TargetContextSelectorProps> = ({ cl
) : (
<Dropdown
className={className}
isScrollable
placeholder={t('TargetContextSelector.TOGGLE_PLACEHOLDER')}
isOpen={isTargetOpen}
onOpenChange={(isOpen) => setIsTargetOpen(isOpen)}
onOpenChange={setIsTargetOpen}
onOpenChangeKeys={['Escape']}
onSelect={onSelect}
onActionClick={onFavoriteClick}
Expand All @@ -256,17 +256,19 @@ export const TargetContextSelector: React.FC<TargetContextSelectorProps> = ({ cl
appendTo: portalRoot,
}}
>
<MenuSearch>
<MenuSearchInput>
<SearchInput
placeholder={t('TargetContextSelector.SEARCH_PLACEHOLDER')}
value={searchTerm}
onChange={(_, v) => setSearchTerm(v)}
/>
</MenuSearchInput>
</MenuSearch>
<Divider />
<DropdownList>{selectOptions}</DropdownList>
<ScrollableMenuContent maxHeight="50vh">
<MenuSearch>
<MenuSearchInput>
<SearchInput
placeholder={t('TargetContextSelector.SEARCH_PLACEHOLDER')}
value={searchTerm}
onChange={(_, v) => setSearchTerm(v)}
/>
</MenuSearchInput>
</MenuSearch>
<Divider />
<DropdownList>{selectOptions}</DropdownList>
</ScrollableMenuContent>
<MenuFooter>{selectFooter}</MenuFooter>
</Dropdown>
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/TargetView/TargetSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export const TargetSelect: React.FC<TargetSelectProps> = ({ onSelect, simple, ..
<CardBody>
<Dropdown
isScrollable
maxMenuHeight="40vh"
placeholder={t('TargetContextSelector.TOGGLE_PLACEHOLDER')}
isOpen={isDropdownOpen}
onOpenChange={setIsDropdownOpen}
Expand Down
4 changes: 3 additions & 1 deletion src/app/Topology/Actions/NodeActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ContextMenuItemProps
element: GraphElement | ListElement;
variant: MenuItemVariant;
isDisabled?: (element: GraphElement | ListElement, actionUtils: ActionUtils) => Observable<boolean>;
isDanger?: boolean;
}

export const ContextMenuItem: React.FC<ContextMenuItemProps> = ({
Expand All @@ -51,6 +52,7 @@ export const ContextMenuItem: React.FC<ContextMenuItemProps> = ({
onClick,
variant,
isDisabled,
isDanger,
...props
}) => {
const navigate = useNavigate();
Expand Down Expand Up @@ -102,7 +104,7 @@ export const ContextMenuItem: React.FC<ContextMenuItemProps> = ({
}

return (
<Component {...props} onClick={handleOnclick} isDisabled={disabled}>
<Component {...props} onClick={handleOnclick} isDisabled={disabled} isDanger={!disabled && isDanger}>
{children}
</Component>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/Topology/Actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface NodeAction {
readonly action?: NodeActionFunction;
readonly title?: React.ReactNode;
readonly isSeparator?: boolean;
readonly isDanger?: boolean;
readonly isDisabled?: (element: GraphElement | ListElement, actionUtils: ActionUtils) => Observable<boolean>;
readonly allowed?: (element: GraphElement | ListElement) => boolean; // Undefined means allowing all
readonly blocked?: (element: GraphElement | ListElement) => boolean; // Undefined means blocking none
Expand Down
15 changes: 12 additions & 3 deletions src/app/Topology/Actions/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const nodeActions: NodeAction[] = [
{ key: '', isSeparator: true },
{
key: 'DELETE_TARGET',
isDanger: true,
action: (element, { services }) => {
const targetNode: TargetNode = element.getData();
services.api.deleteTarget(targetNode.target).subscribe(() => undefined);
Expand Down Expand Up @@ -173,7 +174,7 @@ export const nodeActions: NodeAction[] = [
services.api
.graphql<GroupActionResponse>(
`
query DeleteRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) {
query ArchiveRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) {
environmentNodes(filter: $groupFilter) {
name
descendantTargets {
Expand Down Expand Up @@ -258,6 +259,7 @@ export const nodeActions: NodeAction[] = [
key: 'GROUP_DELETE_RECORDING',
title: 'Delete Recording',
isGroup: true,
isDanger: true,
action: (element, { services, notifications }) => {
const group: EnvironmentNode = element.getData();
services.api
Expand Down Expand Up @@ -361,12 +363,19 @@ export const actionFactory = (
}
filtered = stop >= 0 ? filtered.slice(0, stop + 1) : [];

return filtered.map(({ isSeparator, key, title, isDisabled, action }, index) => {
return filtered.map(({ isSeparator, key, title, isDisabled, action, isDanger }, index) => {
if (isSeparator) {
return <ContextMenuSeparator key={`separator-${index}`} />;
}
return (
<ContextMenuItem key={key} element={element} onClick={action} variant={variant} isDisabled={isDisabled}>
<ContextMenuItem
key={key}
element={element}
onClick={action}
variant={variant}
isDisabled={isDisabled}
isDanger={isDanger}
>
{title}
</ContextMenuItem>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/Topology/Toolbar/HelpButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const HelpButton: React.FC<HelpButtonProps> = ({ visualization, ...props
return (
<Popover bodyContent={content} position={'left'} {...props}>
<Button variant="plain" className="topology__help-icon-button">
<Icon status="info">
<Icon>
<InfoCircleIcon className="topology__help-icon" />
</Icon>
</Button>
Expand Down
2 changes: 1 addition & 1 deletion src/app/Topology/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ Below CSS rules only apply to Topology components
}

.topology__help-icon {
color: var(--pf-v5-global--palette--blue-400);
color: var(--pf-v5-c-button--m-link--Color);
}

.topology__help-icon-button {
Expand Down

0 comments on commit fb87a10

Please sign in to comment.