mirror of https://github.com/portainer/portainer
feat(a11y): add labels and roles [EE-6717] (#11181)
parent
6c89d3c0c9
commit
ce3a1b8ba5
|
@ -209,7 +209,7 @@
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
<uib-tab index="1" disable="!buildLogs">
|
<uib-tab index="1" disable="!buildLogs">
|
||||||
<uib-tab-heading class="vertical-center"> <pr-icon icon="'file-text'" class="leading-none"></pr-icon> Output </uib-tab-heading>
|
<uib-tab-heading class="vertical-center"> <pr-icon icon="'file-text'" class="leading-none"></pr-icon> Output </uib-tab-heading>
|
||||||
<pre class="log_viewer">
|
<pre class="log_viewer" data-cy="logViewer">
|
||||||
<div ng-repeat="line in buildLogs track by $index" class="line"><p class="inner_line" ng-click="active=!active" ng-class="{'line_selected': active}">{{ line }}</p></div>
|
<div ng-repeat="line in buildLogs track by $index" class="line"><p class="inner_line" ng-click="active=!active" ng-class="{'line_selected': active}">{{ line }}</p></div>
|
||||||
<div ng-if="!buildLogs.length" class="line"><p class="inner_line">No build output available.</p></div>
|
<div ng-if="!buildLogs.length" class="line"><p class="inner_line">No build output available.</p></div>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!-- helm chart -->
|
<!-- helm chart -->
|
||||||
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item mx-0" ng-click="$ctrl.onSelect($ctrl.model)">
|
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item mx-0" ng-click="$ctrl.onSelect($ctrl.model)" role="listitem">
|
||||||
<div class="blocklist-item-box">
|
<div class="blocklist-item-box">
|
||||||
<!-- helmchart-image -->
|
<!-- helmchart-image -->
|
||||||
<span class="shrink-0">
|
<span class="shrink-0">
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
></beta-alert>
|
></beta-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blocklist !px-0">
|
<div class="blocklist !px-0" role="list">
|
||||||
<helm-templates-list-item
|
<helm-templates-list-item
|
||||||
ng-repeat="chart in allCharts = ($ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory)"
|
ng-repeat="chart in allCharts = ($ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory)"
|
||||||
model="chart"
|
model="chart"
|
||||||
|
|
|
@ -184,6 +184,7 @@ export const ngModule = angular
|
||||||
'components',
|
'components',
|
||||||
'isLoading',
|
'isLoading',
|
||||||
'noOptionsMessage',
|
'noOptionsMessage',
|
||||||
|
'aria-label',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
.component(
|
.component(
|
||||||
|
|
|
@ -69,10 +69,13 @@ export function Badge({
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<Props>) {
|
}: PropsWithChildren<Props>) {
|
||||||
const baseClasses =
|
const baseClasses =
|
||||||
'flex w-fit items-center !text-xs font-medium rounded-full px-2 py-0.5';
|
'inline-flex w-fit items-center !text-xs font-medium rounded-full px-2 py-0.5';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={clsx(baseClasses, typeClasses[type], className)}>
|
<span
|
||||||
|
className={clsx(baseClasses, typeClasses[type], className)}
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,7 @@ export function BlocklistItem<T extends ElementType>({
|
||||||
'blocklist-item--selected': isSelected,
|
'blocklist-item--selected': isSelected,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
role="listitem"
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|
|
@ -188,6 +188,7 @@ export function Datatable<D extends DefaultType>({
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onSortChange={handleSortChange}
|
onSortChange={handleSortChange}
|
||||||
data-cy={dataCy}
|
data-cy={dataCy}
|
||||||
|
aria-label={`${title} table`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DatatableFooter
|
<DatatableFooter
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface Props<D extends DefaultType> extends AutomationTestingProps {
|
||||||
onSortChange?(colId: string, desc: boolean): void;
|
onSortChange?(colId: string, desc: boolean): void;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
emptyContentLabel?: string;
|
emptyContentLabel?: string;
|
||||||
|
'aria-label'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DatatableContent<D extends DefaultType>({
|
export function DatatableContent<D extends DefaultType>({
|
||||||
|
@ -20,12 +21,13 @@ export function DatatableContent<D extends DefaultType>({
|
||||||
isLoading,
|
isLoading,
|
||||||
emptyContentLabel,
|
emptyContentLabel,
|
||||||
'data-cy': dataCy,
|
'data-cy': dataCy,
|
||||||
|
'aria-label': ariaLabel,
|
||||||
}: Props<D>) {
|
}: Props<D>) {
|
||||||
const headerGroups = tableInstance.getHeaderGroups();
|
const headerGroups = tableInstance.getHeaderGroups();
|
||||||
const pageRowModel = tableInstance.getPaginationRowModel();
|
const pageRowModel = tableInstance.getPaginationRowModel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table data-cy={dataCy} className="nowrap-cells">
|
<Table data-cy={dataCy} className="nowrap-cells" aria-label={ariaLabel}>
|
||||||
<thead>
|
<thead>
|
||||||
{headerGroups.map((headerGroup) => (
|
{headerGroups.map((headerGroup) => (
|
||||||
<Table.HeaderRow<D>
|
<Table.HeaderRow<D>
|
||||||
|
|
|
@ -28,6 +28,8 @@ interface Props<D extends DefaultType> {
|
||||||
* keyword to filter by
|
* keyword to filter by
|
||||||
*/
|
*/
|
||||||
search?: string;
|
search?: string;
|
||||||
|
|
||||||
|
'aria-label'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NestedDatatable<D extends DefaultType>({
|
export function NestedDatatable<D extends DefaultType>({
|
||||||
|
@ -39,6 +41,7 @@ export function NestedDatatable<D extends DefaultType>({
|
||||||
isLoading,
|
isLoading,
|
||||||
initialSortBy,
|
initialSortBy,
|
||||||
search,
|
search,
|
||||||
|
'aria-label': ariaLabel,
|
||||||
}: Props<D>) {
|
}: Props<D>) {
|
||||||
const tableInstance = useReactTable<D>({
|
const tableInstance = useReactTable<D>({
|
||||||
columns,
|
columns,
|
||||||
|
@ -70,6 +73,7 @@ export function NestedDatatable<D extends DefaultType>({
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
emptyContentLabel={emptyContentLabel}
|
emptyContentLabel={emptyContentLabel}
|
||||||
renderRow={(row) => <Table.Row<D> cells={row.getVisibleCells()} />}
|
renderRow={(row) => <Table.Row<D> cells={row.getVisibleCells()} />}
|
||||||
|
aria-label={ariaLabel}
|
||||||
/>
|
/>
|
||||||
</Table.Container>
|
</Table.Container>
|
||||||
</NestedTable>
|
</NestedTable>
|
||||||
|
|
|
@ -42,6 +42,7 @@ export function SearchBar({
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
data-cy={dataCy}
|
data-cy={dataCy}
|
||||||
|
aria-label="Search input"
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={onClear} icon={X} color="none" disabled={!searchValue} />
|
<Button onClick={onClear} icon={X} color="none" disabled={!searchValue} />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { PropsWithChildren } from 'react';
|
import { AriaAttributes, PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import { AutomationTestingProps } from '@/types';
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
|
@ -14,23 +14,21 @@ import { TableHeaderCell } from './TableHeaderCell';
|
||||||
import { TableHeaderRow } from './TableHeaderRow';
|
import { TableHeaderRow } from './TableHeaderRow';
|
||||||
import { TableRow } from './TableRow';
|
import { TableRow } from './TableRow';
|
||||||
|
|
||||||
interface Props extends AutomationTestingProps {
|
interface Props extends AutomationTestingProps, AriaAttributes {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MainComponent({
|
function MainComponent({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
'data-cy': dataCy,
|
...props
|
||||||
}: PropsWithChildren<Props>) {
|
}: PropsWithChildren<Props>) {
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table
|
<table
|
||||||
data-cy={dataCy}
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
className={clsx(
|
{...props}
|
||||||
'table-hover table-filters nowrap-cells table',
|
className={clsx('table-hover table-filters table', className)}
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function TableTitle({
|
||||||
<>
|
<>
|
||||||
<div className={clsx('toolBar flex-col', className)} id={id}>
|
<div className={clsx('toolBar flex-col', className)} id={id}>
|
||||||
<div className="flex w-full items-center gap-1 p-0">
|
<div className="flex w-full items-center gap-1 p-0">
|
||||||
<div className="toolBarTitle">
|
<h2 className="toolBarTitle m-0 text-2xl">
|
||||||
{icon && (
|
{icon && (
|
||||||
<div className="widget-icon">
|
<div className="widget-icon">
|
||||||
<Icon icon={icon} className="space-right" />
|
<Icon icon={icon} className="space-right" />
|
||||||
|
@ -31,7 +31,7 @@ export function TableTitle({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</h2>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,7 @@ export function buildExpandColumn<T extends DefaultType>(): ColumnDef<T> {
|
||||||
onClick={table.getToggleAllRowsExpandedHandler()}
|
onClick={table.getToggleAllRowsExpandedHandler()}
|
||||||
color="none"
|
color="none"
|
||||||
icon={table.getIsAllRowsExpanded() ? ChevronDown : ChevronUp}
|
icon={table.getIsAllRowsExpanded() ? ChevronDown : ChevronUp}
|
||||||
|
title="Expand all"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -32,6 +33,7 @@ export function buildExpandColumn<T extends DefaultType>(): ColumnDef<T> {
|
||||||
}}
|
}}
|
||||||
color="none"
|
color="none"
|
||||||
icon={row.getIsExpanded() ? ChevronDown : ChevronUp}
|
icon={row.getIsExpanded() ? ChevronDown : ChevronUp}
|
||||||
|
title={row.getIsExpanded() ? 'Collapse' : 'Expand'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
SelectComponentsConfig,
|
SelectComponentsConfig,
|
||||||
} from 'react-select';
|
} from 'react-select';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { AriaAttributes } from 'react';
|
||||||
|
|
||||||
import { AutomationTestingProps } from '@/types';
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
|
@ -20,7 +21,9 @@ type Options<TValue> = OptionsOrGroups<
|
||||||
GroupBase<Option<TValue>>
|
GroupBase<Option<TValue>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
interface SharedProps extends AutomationTestingProps {
|
interface SharedProps
|
||||||
|
extends AutomationTestingProps,
|
||||||
|
Pick<AriaAttributes, 'aria-label'> {
|
||||||
name?: string;
|
name?: string;
|
||||||
inputId?: string;
|
inputId?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -87,6 +90,8 @@ export function SingleSelect<TValue = string>({
|
||||||
components,
|
components,
|
||||||
isLoading,
|
isLoading,
|
||||||
noOptionsMessage,
|
noOptionsMessage,
|
||||||
|
isMulti,
|
||||||
|
...aria
|
||||||
}: SingleProps<TValue>) {
|
}: SingleProps<TValue>) {
|
||||||
const selectedValue =
|
const selectedValue =
|
||||||
value || (typeof value === 'number' && value === 0)
|
value || (typeof value === 'number' && value === 0)
|
||||||
|
@ -111,6 +116,8 @@ export function SingleSelect<TValue = string>({
|
||||||
components={components}
|
components={components}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...aria}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -152,6 +159,7 @@ export function MultiSelect<TValue = string>({
|
||||||
components,
|
components,
|
||||||
isLoading,
|
isLoading,
|
||||||
noOptionsMessage,
|
noOptionsMessage,
|
||||||
|
...aria
|
||||||
}: Omit<MultiProps<TValue>, 'isMulti'>) {
|
}: Omit<MultiProps<TValue>, 'isMulti'>) {
|
||||||
const selectedOptions = findSelectedOptions(options, value);
|
const selectedOptions = findSelectedOptions(options, value);
|
||||||
return (
|
return (
|
||||||
|
@ -174,6 +182,8 @@ export function MultiSelect<TValue = string>({
|
||||||
components={components}
|
components={components}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...aria}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,11 @@ export function NestedNetworksDatatable({
|
||||||
const isSwarm = useIsSwarm(environmentId);
|
const isSwarm = useIsSwarm(environmentId);
|
||||||
|
|
||||||
const columns = useColumns(isSwarm);
|
const columns = useColumns(isSwarm);
|
||||||
return <NestedDatatable columns={columns} dataset={dataset} />;
|
return (
|
||||||
|
<NestedDatatable
|
||||||
|
columns={columns}
|
||||||
|
dataset={dataset}
|
||||||
|
aria-label="Networks table"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { truncate } from '@/portainer/filters/filters';
|
import { truncate } from '@/portainer/filters/filters';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
|
import { Badge } from '@@/Badge';
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
import { columnHelper } from './helper';
|
||||||
|
|
||||||
|
@ -18,12 +19,9 @@ export const name = columnHelper.accessor('Name', {
|
||||||
{truncate(item.Name, 40)}
|
{truncate(item.Name, 40)}
|
||||||
</Link>
|
</Link>
|
||||||
{item.ResourceControl?.System && (
|
{item.ResourceControl?.System && (
|
||||||
<span
|
<Badge type="info" className="ml-2">
|
||||||
style={{ marginLeft: '10px' }}
|
|
||||||
className="label label-info image-tag space-left"
|
|
||||||
>
|
|
||||||
System
|
System
|
||||||
</span>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,7 @@ export function TasksDatatable({
|
||||||
dataset={dataset}
|
dataset={dataset}
|
||||||
search={search}
|
search={search}
|
||||||
emptyContentLabel="No task matching filter."
|
emptyContentLabel="No task matching filter."
|
||||||
|
aria-label="Tasks table"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,9 @@ function Cell({
|
||||||
</Authorized>
|
</Authorized>
|
||||||
)}
|
)}
|
||||||
{item.dangling && (
|
{item.dangling && (
|
||||||
<span className="label label-warning image-tag ml-2">Unused</span>
|
<span className="label label-warning image-tag ml-2" role="status">
|
||||||
|
Unused
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -212,6 +212,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
|
||||||
<div
|
<div
|
||||||
className="blocklist mt-5 !space-y-2 !p-0"
|
className="blocklist mt-5 !space-y-2 !p-0"
|
||||||
data-cy="home-endpointList"
|
data-cy="home-endpointList"
|
||||||
|
role="list"
|
||||||
>
|
>
|
||||||
{renderItems(
|
{renderItems(
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
|
@ -31,9 +31,10 @@ export function RefField({
|
||||||
stackId,
|
stackId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [inputValue, updateInputValue] = useStateWrapper(value, onChange);
|
const [inputValue, updateInputValue] = useStateWrapper(value, onChange);
|
||||||
|
const inputId = 'repository-reference-field';
|
||||||
return isBE ? (
|
return isBE ? (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
inputId={inputId}
|
||||||
errors={error}
|
errors={error}
|
||||||
tip={
|
tip={
|
||||||
<>
|
<>
|
||||||
|
@ -44,6 +45,7 @@ export function RefField({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<RefSelector
|
<RefSelector
|
||||||
|
inputId={inputId}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
model={model}
|
model={model}
|
||||||
|
@ -53,6 +55,7 @@ export function RefField({
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
) : (
|
) : (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
inputId={inputId}
|
||||||
errors={error}
|
errors={error}
|
||||||
tip={
|
tip={
|
||||||
<>
|
<>
|
||||||
|
@ -65,6 +68,7 @@ export function RefField({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
id={inputId}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => updateInputValue(e.target.value)}
|
onChange={(e) => updateInputValue(e.target.value)}
|
||||||
placeholder="refs/heads/main"
|
placeholder="refs/heads/main"
|
||||||
|
@ -77,7 +81,8 @@ function Wrapper({
|
||||||
tip,
|
tip,
|
||||||
children,
|
children,
|
||||||
errors,
|
errors,
|
||||||
}: PropsWithChildren<{ tip: ReactNode; errors?: string }>) {
|
inputId,
|
||||||
|
}: PropsWithChildren<{ tip: ReactNode; errors?: string; inputId: string }>) {
|
||||||
return (
|
return (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<span className="col-sm-12 mb-2">
|
<span className="col-sm-12 mb-2">
|
||||||
|
@ -86,7 +91,7 @@ function Wrapper({
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Repository reference"
|
label="Repository reference"
|
||||||
inputId="stack_repository_reference_name"
|
inputId={inputId}
|
||||||
required
|
required
|
||||||
errors={errors}
|
errors={errors}
|
||||||
>
|
>
|
||||||
|
|
|
@ -13,12 +13,14 @@ export function RefSelector({
|
||||||
onChange,
|
onChange,
|
||||||
isUrlValid,
|
isUrlValid,
|
||||||
stackId,
|
stackId,
|
||||||
|
inputId,
|
||||||
}: {
|
}: {
|
||||||
model: RefFieldModel;
|
model: RefFieldModel;
|
||||||
value: string;
|
value: string;
|
||||||
stackId?: StackId;
|
stackId?: StackId;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
isUrlValid?: boolean;
|
isUrlValid?: boolean;
|
||||||
|
inputId: string;
|
||||||
}) {
|
}) {
|
||||||
const creds = getAuthentication(model);
|
const creds = getAuthentication(model);
|
||||||
const payload = {
|
const payload = {
|
||||||
|
@ -64,6 +66,7 @@ export function RefSelector({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PortainerSelect
|
<PortainerSelect
|
||||||
|
inputId={inputId}
|
||||||
value={value}
|
value={value}
|
||||||
options={refs || [{ value: 'refs/heads/main', label: 'refs/heads/main' }]}
|
options={refs || [{ value: 'refs/heads/main', label: 'refs/heads/main' }]}
|
||||||
onChange={(e) => e && onChange(e)}
|
onChange={(e) => e && onChange(e)}
|
||||||
|
|
|
@ -86,7 +86,7 @@ export function AppTemplatesList({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]">
|
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]" role="list">
|
||||||
{pagedTemplates.map((template) => (
|
{pagedTemplates.map((template) => (
|
||||||
<AppTemplatesListItem
|
<AppTemplatesListItem
|
||||||
key={template.Id}
|
key={template.Id}
|
||||||
|
|
|
@ -55,6 +55,7 @@ export function Filters({
|
||||||
value={listState.category}
|
value={listState.category}
|
||||||
bindToBody
|
bindToBody
|
||||||
isClearable
|
isClearable
|
||||||
|
aria-label="Category filter"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -71,6 +72,7 @@ export function Filters({
|
||||||
value={listState.types}
|
value={listState.types}
|
||||||
bindToBody
|
bindToBody
|
||||||
isClearable
|
isClearable
|
||||||
|
aria-label="Type filter"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -83,6 +85,7 @@ export function Filters({
|
||||||
options={orderByFields}
|
options={orderByFields}
|
||||||
placeholder="Sort By"
|
placeholder="Sort By"
|
||||||
value={listState.sortBy}
|
value={listState.sortBy}
|
||||||
|
aria-label="Sort"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,7 +63,7 @@ export function CustomTemplatesList({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]">
|
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]" role="list">
|
||||||
{pagedTemplates.map((template) => (
|
{pagedTemplates.map((template) => (
|
||||||
<CustomTemplatesListItem
|
<CustomTemplatesListItem
|
||||||
key={template.Id}
|
key={template.Id}
|
||||||
|
|
|
@ -182,7 +182,7 @@ export function DockerSidebar({ environmentId, environment }: Props) {
|
||||||
icon={setupSubMenuProps.icon}
|
icon={setupSubMenuProps.icon}
|
||||||
to={setupSubMenuProps.to}
|
to={setupSubMenuProps.to}
|
||||||
params={{ endpointId: environmentId }}
|
params={{ endpointId: environmentId }}
|
||||||
data-cy="portainerSidebar-host"
|
data-cy="portainerSidebar-host-area"
|
||||||
>
|
>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label="Details"
|
label="Details"
|
||||||
|
|
Loading…
Reference in New Issue