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) => {
|
const useFilter = (filterName: string, searchValue: string) => {
|
||||||
cy.get("[id$='filter-workspaces-dropdown']").click();
|
cy.get("[id$='filter-workspaces-dropdown']").click();
|
||||||
cy.get(`[id$='filter-workspaces-dropdown-${filterName}']`).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(filterName);
|
||||||
cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue);
|
cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue);
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,7 @@ describe('Application', () => {
|
||||||
cy.intercept('GET', '/api/v1/workspaces/default', {
|
cy.intercept('GET', '/api/v1/workspaces/default', {
|
||||||
body: mockBFFResponse(mockWorkspaces),
|
body: mockBFFResponse(mockWorkspaces),
|
||||||
});
|
});
|
||||||
|
cy.intercept('GET', '/api/namespaces/test-namespace/workspaces').as('getWorkspaces');
|
||||||
});
|
});
|
||||||
it('filter rows with single filter', () => {
|
it('filter rows with single filter', () => {
|
||||||
home.visit();
|
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,
|
Brand,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Label,
|
Label,
|
||||||
SearchInput,
|
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarContent,
|
ToolbarContent,
|
||||||
ToolbarItem,
|
ToolbarItem,
|
||||||
|
@ -44,6 +43,7 @@ import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||||
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||||
import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||||
import { WorkspaceKindsColumnNames } from '~/app/types';
|
import { WorkspaceKindsColumnNames } from '~/app/types';
|
||||||
|
import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput';
|
||||||
|
|
||||||
export enum ActionType {
|
export enum ActionType {
|
||||||
ViewDetails,
|
ViewDetails,
|
||||||
|
@ -178,32 +178,6 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
||||||
[sortedWorkspaceKinds, onFilter],
|
[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
|
// Set up status single select
|
||||||
const [isStatusMenuOpen, setIsStatusMenuOpen] = React.useState<boolean>(false);
|
const [isStatusMenuOpen, setIsStatusMenuOpen] = React.useState<boolean>(false);
|
||||||
const statusToggleRef = React.useRef<HTMLButtonElement>(null);
|
const statusToggleRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
@ -509,7 +483,15 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
||||||
categoryName="Name"
|
categoryName="Name"
|
||||||
showToolbarItem={activeAttributeMenu === '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>
|
||||||
<ToolbarFilter
|
<ToolbarFilter
|
||||||
labels={
|
labels={
|
||||||
|
@ -522,7 +504,15 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
||||||
categoryName="Description"
|
categoryName="Description"
|
||||||
showToolbarItem={activeAttributeMenu === '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>
|
||||||
<ToolbarFilter
|
<ToolbarFilter
|
||||||
labels={statusSelection !== '' ? [statusSelection] : ([] as string[])}
|
labels={statusSelection !== '' ? [statusSelection] : ([] as string[])}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
Label,
|
Label,
|
||||||
PaginationVariant,
|
PaginationVariant,
|
||||||
Pagination,
|
Pagination,
|
||||||
Button,
|
|
||||||
Content,
|
Content,
|
||||||
Brand,
|
Brand,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
@ -32,8 +31,7 @@ import {
|
||||||
QuestionCircleIcon,
|
QuestionCircleIcon,
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
import { useCallback, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||||
|
@ -63,11 +61,6 @@ export enum ActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Workspaces: React.FunctionComponent = () => {
|
export const Workspaces: React.FunctionComponent = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const createWorkspace = useCallback(() => {
|
|
||||||
navigate('/workspaces/create');
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
const [workspaceKinds] = useWorkspaceKinds();
|
const [workspaceKinds] = useWorkspaceKinds();
|
||||||
const kindLogoDict = buildKindLogoDictionary(workspaceKinds);
|
const kindLogoDict = buildKindLogoDictionary(workspaceKinds);
|
||||||
const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds);
|
const workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds);
|
||||||
|
@ -455,9 +448,6 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
<br />
|
<br />
|
||||||
<Content style={{ display: 'flex', alignItems: 'flex-start', columnGap: '20px' }}>
|
<Content style={{ display: 'flex', alignItems: 'flex-start', columnGap: '20px' }}>
|
||||||
<Filter id="filter-workspaces" onFilter={onFilter} columnNames={filterableColumns} />
|
<Filter id="filter-workspaces" onFilter={onFilter} columnNames={filterableColumns} />
|
||||||
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
|
||||||
Create Workspace
|
|
||||||
</Button>
|
|
||||||
</Content>
|
</Content>
|
||||||
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
||||||
<Thead>
|
<Thead>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Menu,
|
Menu,
|
||||||
MenuContent,
|
MenuContent,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
@ -7,7 +8,6 @@ import {
|
||||||
MenuToggle,
|
MenuToggle,
|
||||||
MenuToggleElement,
|
MenuToggleElement,
|
||||||
Popper,
|
Popper,
|
||||||
SearchInput,
|
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarContent,
|
ToolbarContent,
|
||||||
ToolbarFilter,
|
ToolbarFilter,
|
||||||
|
@ -16,6 +16,9 @@ import {
|
||||||
ToolbarToggleGroup,
|
ToolbarToggleGroup,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { FilterIcon } from '@patternfly/react-icons';
|
import { FilterIcon } from '@patternfly/react-icons';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import ThemeAwareSearchInput from '~/app/components/ThemeAwareSearchInput';
|
||||||
|
|
||||||
export interface FilterProps {
|
export interface FilterProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -41,6 +44,11 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
||||||
const filterMenuRef = React.useRef<HTMLDivElement | null>(null);
|
const filterMenuRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
const filterContainerRef = 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(
|
const handleFilterMenuKeys = React.useCallback(
|
||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
if (!isFilterMenuOpen) {
|
if (!isFilterMenuOpen) {
|
||||||
|
@ -202,12 +210,13 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
||||||
<ToolbarGroup variant="filter-group">
|
<ToolbarGroup variant="filter-group">
|
||||||
<ToolbarItem id={`${id}-dropdown`}>{filterDropdown}</ToolbarItem>
|
<ToolbarItem id={`${id}-dropdown`}>{filterDropdown}</ToolbarItem>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<SearchInput
|
<ThemeAwareSearchInput
|
||||||
id={`${id}-search-input`}
|
data-testid={`${id}-search-input`}
|
||||||
placeholder={`Filter by ${activeFilter.columnName}`}
|
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={(_event, value) => onSearchChange(value)}
|
onChange={onSearchChange}
|
||||||
onClear={() => onSearchChange('')}
|
placeholder={`Filter by ${activeFilter.columnName}`}
|
||||||
|
fieldLabel={`Find by ${activeFilter.columnName}`}
|
||||||
|
aria-label={`Filter by ${activeFilter.columnName}`}
|
||||||
/>
|
/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
{filters.map((filter) => (
|
{filters.map((filter) => (
|
||||||
|
@ -222,6 +231,9 @@ const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
||||||
</ToolbarFilter>
|
</ToolbarFilter>
|
||||||
))}
|
))}
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
|
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
||||||
|
Create Workspace
|
||||||
|
</Button>
|
||||||
</ToolbarToggleGroup>
|
</ToolbarToggleGroup>
|
||||||
</ToolbarContent>
|
</ToolbarContent>
|
||||||
</Toolbar>
|
</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 {
|
.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--BackgroundColor: var(--mui-palette-primary-main);
|
||||||
--pf-v6-c-progress-stepper__step-icon--Color: var(--mui-palette-common-white);
|
--pf-v6-c-progress-stepper__step-icon--Color: var(--mui-palette-common-white);
|
||||||
|
@ -828,4 +828,13 @@
|
||||||
.mui-theme .pf-v6-c-masthead {
|
.mui-theme .pf-v6-c-masthead {
|
||||||
padding-left: var(--kf-central-app-drawer-width);
|
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