feat(ws): Apply theme dependent components (#313)
Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix linting errors Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> move button to toolbargroup to fix toolbar alignment fix search input height fix frontend tests Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix testing issues Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> add export default to ThemeAwareSearchInput Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix linting issues fix import add ID Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> add whitespace remove whitespace Signed-off-by: Jenny <32821331+jenny-s51@users.noreply.github.com> fix tests
This commit is contained in:
parent
ea93acc140
commit
79fe52d09a
|
@ -6,7 +6,7 @@ import { home } from '~/__tests__/cypress/cypress/pages/home';
|
|||
const useFilter = (filterName: string, searchValue: string) => {
|
||||
cy.get("[id$='filter-workspaces-dropdown']").click();
|
||||
cy.get(`[id$='filter-workspaces-dropdown-${filterName}']`).click();
|
||||
cy.get("[id$='filter-workspaces-search-input']").type(searchValue);
|
||||
cy.get("[data-testid='filter-workspaces-search-input']").type(searchValue);
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").contains(filterName);
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue);
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ describe('Application', () => {
|
|||
cy.intercept('GET', '/api/v1/workspaces/default', {
|
||||
body: mockBFFResponse(mockWorkspaces),
|
||||
});
|
||||
cy.intercept('GET', '/api/namespaces/test-namespace/workspaces').as('getWorkspaces');
|
||||
});
|
||||
it('filter rows with single filter', () => {
|
||||
home.visit();
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
|
||||
interface FormFieldsetProps {
|
||||
component: ReactNode;
|
||||
field?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FormFieldset: React.FC<FormFieldsetProps> = ({ component, field, className }) => (
|
||||
<div className={className ?? 'form-fieldset-wrapper'}>
|
||||
{component}
|
||||
<fieldset aria-hidden="true" className="form-fieldset">
|
||||
{field && (
|
||||
<legend className="form-fieldset-legend">
|
||||
<span>{field}</span>
|
||||
</legend>
|
||||
)}
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FormFieldset;
|
|
@ -0,0 +1,68 @@
|
|||
import * as React from 'react';
|
||||
import { SearchInput, SearchInputProps, TextInput } from '@patternfly/react-core';
|
||||
import FormFieldset from 'app/components/FormFieldset';
|
||||
import { isMUITheme } from 'app/const';
|
||||
|
||||
type ThemeAwareSearchInputProps = Omit<SearchInputProps, 'onChange' | 'onClear'> & {
|
||||
onChange: (value: string) => void; // Simplified onChange signature
|
||||
onClear?: () => void; // Simplified optional onClear signature
|
||||
fieldLabel?: string; // Additional prop for MUI FormFieldset label
|
||||
'data-testid'?: string;
|
||||
};
|
||||
|
||||
const ThemeAwareSearchInput: React.FC<ThemeAwareSearchInputProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
onClear,
|
||||
fieldLabel,
|
||||
placeholder,
|
||||
isDisabled,
|
||||
className,
|
||||
style,
|
||||
'aria-label': ariaLabel = 'Search',
|
||||
'data-testid': dataTestId,
|
||||
...rest
|
||||
}) => {
|
||||
if (isMUITheme()) {
|
||||
// Render MUI version using TextInput + FormFieldset
|
||||
return (
|
||||
<FormFieldset
|
||||
className={className}
|
||||
field={fieldLabel}
|
||||
component={
|
||||
<TextInput
|
||||
value={value}
|
||||
type="text"
|
||||
onChange={(_event, newValue) => onChange(newValue)} // Adapt signature
|
||||
isDisabled={isDisabled}
|
||||
aria-label={ariaLabel}
|
||||
data-testid={dataTestId}
|
||||
style={style}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Render PF version using SearchInput
|
||||
return (
|
||||
<SearchInput
|
||||
{...rest} // Pass all other applicable SearchInputProps
|
||||
className={className}
|
||||
style={style}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
isDisabled={isDisabled}
|
||||
aria-label={ariaLabel}
|
||||
data-testid={dataTestId}
|
||||
onChange={(_event, newValue) => onChange(newValue)} // Adapt signature
|
||||
onClear={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange('');
|
||||
onClear?.(); // Adapt signature
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeAwareSearchInput;
|
|
@ -8,7 +8,6 @@ import {
|
|||
Brand,
|
||||
Tooltip,
|
||||
Label,
|
||||
SearchInput,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarItem,
|
||||
|
@ -44,6 +43,7 @@ import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
|||
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||
import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspaceKindsColumnNames } from '~/app/types';
|
||||
import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput';
|
||||
|
||||
export enum ActionType {
|
||||
ViewDetails,
|
||||
|
@ -178,32 +178,6 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
[sortedWorkspaceKinds, onFilter],
|
||||
);
|
||||
|
||||
// Set up name search input
|
||||
const searchNameInput = React.useMemo(
|
||||
() => (
|
||||
<SearchInput
|
||||
placeholder="Filter by name"
|
||||
value={searchNameValue}
|
||||
onChange={(_event, value) => onSearchNameChange(value)}
|
||||
onClear={() => onSearchNameChange('')}
|
||||
/>
|
||||
),
|
||||
[searchNameValue, onSearchNameChange],
|
||||
);
|
||||
|
||||
// Set up description search input
|
||||
const searchDescriptionInput = React.useMemo(
|
||||
() => (
|
||||
<SearchInput
|
||||
placeholder="Filter by description"
|
||||
value={searchDescriptionValue}
|
||||
onChange={(_event, value) => onSearchDescriptionChange(value)}
|
||||
onClear={() => onSearchDescriptionChange('')}
|
||||
/>
|
||||
),
|
||||
[searchDescriptionValue, onSearchDescriptionChange],
|
||||
);
|
||||
|
||||
// Set up status single select
|
||||
const [isStatusMenuOpen, setIsStatusMenuOpen] = React.useState<boolean>(false);
|
||||
const statusToggleRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
@ -509,7 +483,15 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
categoryName="Name"
|
||||
showToolbarItem={activeAttributeMenu === 'Name'}
|
||||
>
|
||||
{searchNameInput}
|
||||
<ToolbarItem>
|
||||
<ThemeAwareSearchInput
|
||||
value={searchNameValue}
|
||||
onChange={onSearchNameChange}
|
||||
placeholder="Filter by Name"
|
||||
fieldLabel="Find by Name"
|
||||
aria-label="Filter by Name"
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarFilter>
|
||||
<ToolbarFilter
|
||||
labels={
|
||||
|
@ -522,7 +504,15 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
categoryName="Description"
|
||||
showToolbarItem={activeAttributeMenu === 'Description'}
|
||||
>
|
||||
{searchDescriptionInput}
|
||||
<ToolbarItem>
|
||||
<ThemeAwareSearchInput
|
||||
value={searchDescriptionValue}
|
||||
onChange={onSearchDescriptionChange}
|
||||
placeholder="Filter by Description"
|
||||
fieldLabel="Find by Description"
|
||||
aria-label="Filter by Description"
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarFilter>
|
||||
<ToolbarFilter
|
||||
labels={statusSelection !== '' ? [statusSelection] : ([] as string[])}
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
Label,
|
||||
PaginationVariant,
|
||||
Pagination,
|
||||
Button,
|
||||
Content,
|
||||
Brand,
|
||||
Tooltip,
|
||||
|
@ -32,8 +31,7 @@ import {
|
|||
QuestionCircleIcon,
|
||||
CodeIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||
|
@ -63,11 +61,6 @@ export enum ActionType {
|
|||
}
|
||||
|
||||
export const Workspaces: React.FunctionComponent = () => {
|
||||
const navigate = useNavigate();
|
||||
const createWorkspace = useCallback(() => {
|
||||
navigate('/workspaces/create');
|
||||
}, [navigate]);
|
||||
|
||||
const [workspaceKinds] = useWorkspaceKinds();
|
||||
const kindLogoDict = buildKindLogoDictionary(workspaceKinds);
|
||||
const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds);
|
||||
|
@ -455,9 +448,6 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
<br />
|
||||
<Content style={{ display: 'flex', alignItems: 'flex-start', columnGap: '20px' }}>
|
||||
<Filter id="filter-workspaces" onFilter={onFilter} columnNames={filterableColumns} />
|
||||
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
||||
Create Workspace
|
||||
</Button>
|
||||
</Content>
|
||||
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
||||
<Thead>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Menu,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
|
@ -7,7 +8,6 @@ import {
|
|||
MenuToggle,
|
||||
MenuToggleElement,
|
||||
Popper,
|
||||
SearchInput,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarFilter,
|
||||
|
@ -16,6 +16,9 @@ import {
|
|||
ToolbarToggleGroup,
|
||||
} from '@patternfly/react-core';
|
||||
import { FilterIcon } from '@patternfly/react-icons';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useCallback } from 'react';
|
||||
import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput';
|
||||
|
||||
export interface FilterProps {
|
||||
id: string;
|
||||
|
@ -41,6 +44,11 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
|||
const filterMenuRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const filterContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const createWorkspace = useCallback(() => {
|
||||
navigate('/workspaces/create');
|
||||
}, [navigate]);
|
||||
|
||||
const handleFilterMenuKeys = React.useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (!isFilterMenuOpen) {
|
||||
|
@ -202,12 +210,13 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
|||
<ToolbarGroup variant="filter-group">
|
||||
<ToolbarItem id={`${id}-dropdown`}>{filterDropdown}</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<SearchInput
|
||||
id={`${id}-search-input`}
|
||||
placeholder={`Filter by ${activeFilter.columnName}`}
|
||||
<ThemeAwareSearchInput
|
||||
data-testid={`${id}-search-input`}
|
||||
value={searchValue}
|
||||
onChange={(_event, value) => onSearchChange(value)}
|
||||
onClear={() => onSearchChange('')}
|
||||
onChange={onSearchChange}
|
||||
placeholder={`Filter by ${activeFilter.columnName}`}
|
||||
fieldLabel={`Find by ${activeFilter.columnName}`}
|
||||
aria-label={`Filter by ${activeFilter.columnName}`}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
{filters.map((filter) => (
|
||||
|
@ -222,6 +231,9 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
|||
</ToolbarFilter>
|
||||
))}
|
||||
</ToolbarGroup>
|
||||
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
||||
Create Workspace
|
||||
</Button>
|
||||
</ToolbarToggleGroup>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
|
|
|
@ -573,7 +573,7 @@
|
|||
|
||||
}
|
||||
|
||||
.mui-theme .pf-v6-c-progress-stepper__step.pf-m-info,
|
||||
.mui-theme .pf-v6-c-progress-stepper__step.pf-m-info,
|
||||
.mui-theme .pf-v6-c-progress-stepper__step.pf-m-success {
|
||||
--pf-v6-c-progress-stepper__step-icon--BackgroundColor: var(--mui-palette-primary-main);
|
||||
--pf-v6-c-progress-stepper__step-icon--Color: var(--mui-palette-common-white);
|
||||
|
@ -828,4 +828,13 @@
|
|||
.mui-theme .pf-v6-c-masthead {
|
||||
padding-left: var(--kf-central-app-drawer-width);
|
||||
}
|
||||
}
|
||||
|
||||
.mui-theme .pf-v6-c-toolbar__group.pf-m-filter-group .pf-v6-c-form-control {
|
||||
|
||||
// Override default form control padding to match button padding in this context
|
||||
--pf-v6-c-form-control--PaddingBlockStart: var(--mui-spacing-8px);
|
||||
--pf-v6-c-form-control--PaddingBlockEnd: var(--mui-spacing-8px);
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue