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:
Jenny 2025-05-19 07:51:36 -04:00 committed by GitHub
parent ea93acc140
commit 79fe52d09a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 140 additions and 48 deletions

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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[])}

View File

@ -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>

View File

@ -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>

View File

@ -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);
}