Skip to content
Merged
41 changes: 5 additions & 36 deletions components/src/FormatControls/FormatControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Switch, SwitchProps } from '@mui/material';
import {
FormatOptions,
UNIT_CONFIG,
UnitConfig,
isUnitWithDecimalPlaces,
isUnitWithShortValues,
shouldShortenValues,
} from '@perses-dev/core';
import { FormatOptions, isUnitWithDecimalPlaces, isUnitWithShortValues, shouldShortenValues } from '@perses-dev/core';
import { ReactElement } from 'react';
import { OptionsEditorControl } from '../OptionsEditorLayout';
import { SettingsAutocomplete } from '../SettingsAutocomplete';
import { UnitSelector } from './UnitSelector';

export interface FormatControlsProps {
value: FormatOptions;
onChange: (unit: FormatOptions) => void;
disabled?: boolean;
}

type AutocompleteUnitOption = UnitConfig & {
id: NonNullable<FormatOptions['unit']>;
};

const KIND_OPTIONS: readonly AutocompleteUnitOption[] = Object.entries(UNIT_CONFIG)
.map<AutocompleteUnitOption>(([id, config]) => {
return {
...config,
id: id as AutocompleteUnitOption['id'],
group: config.group || 'Decimal',
};
})
.filter((config) => !config.disableSelectorOption);

const DECIMAL_PLACES_OPTIONS: Array<{ id: string; label: string; decimalPlaces?: number }> = [
{ id: 'default', label: 'Default', decimalPlaces: undefined },
{ id: '0', label: '0', decimalPlaces: 0 },
Expand All @@ -62,8 +42,8 @@ export function FormatControls({ value, onChange, disabled = false }: FormatCont
const hasDecimalPlaces = isUnitWithDecimalPlaces(value);
const hasShortValues = isUnitWithShortValues(value);

const handleKindChange = (_: unknown, newValue: AutocompleteUnitOption | null): void => {
onChange({ unit: newValue?.id || 'decimal' } as FormatOptions); // Fallback to 'decimal' if no unit is selected
const handleUnitChange = (newValue: FormatOptions | undefined): void => {
onChange(newValue || { unit: 'decimal' }); // Fallback to 'decimal' if undefined
};

const handleDecimalPlacesChange = ({
Expand All @@ -90,8 +70,6 @@ export function FormatControls({ value, onChange, disabled = false }: FormatCont
}
};

const unitConfig = UNIT_CONFIG[value?.unit || 'decimal'];

return (
<>
<OptionsEditorControl
Expand All @@ -106,16 +84,7 @@ export function FormatControls({ value, onChange, disabled = false }: FormatCont
/>
<OptionsEditorControl
label="Unit"
control={
<SettingsAutocomplete<AutocompleteUnitOption, false, true>
value={{ id: value?.unit || 'decimal', ...unitConfig }}
options={KIND_OPTIONS}
groupBy={(option) => option.group ?? 'Decimal'}
onChange={handleKindChange}
disableClearable
disabled={disabled}
/>
}
control={<UnitSelector value={value} onChange={handleUnitChange} disabled={disabled} />}
/>
<OptionsEditorControl
label="Decimals"
Expand Down
59 changes: 59 additions & 0 deletions components/src/FormatControls/UnitSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright The Perses 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 { FormatOptions, UNIT_CONFIG, UnitConfig } from '@perses-dev/core';
import { ReactElement } from 'react';
import { SettingsAutocomplete } from '../SettingsAutocomplete';

export interface UnitSelectorProps {
value?: FormatOptions;
onChange: (format: FormatOptions | undefined) => void;
disabled?: boolean;
}

type AutocompleteUnitOption = UnitConfig & {
id: NonNullable<FormatOptions['unit']>;
};

const KIND_OPTIONS: readonly AutocompleteUnitOption[] = Object.entries(UNIT_CONFIG)
.map<AutocompleteUnitOption>(([id, config]) => {
return {
...config,
id: id as AutocompleteUnitOption['id'],
group: config.group || 'Decimal',
};
})
.filter((config) => !config.disableSelectorOption);

export function UnitSelector({ value, onChange, disabled = false, ...otherProps }: UnitSelectorProps): ReactElement {
const unitConfig = UNIT_CONFIG[value?.unit || 'decimal'];

const handleChange = (_: unknown, newValue: AutocompleteUnitOption | null): void => {
if (newValue === null) {
onChange(undefined);
} else {
onChange({ unit: newValue.id } as FormatOptions);
}
};

return (
<SettingsAutocomplete<AutocompleteUnitOption, false, false>
value={value ? { id: value.unit || 'decimal', ...unitConfig } : null}
options={KIND_OPTIONS}
groupBy={(option) => option.group ?? 'Decimal'}
onChange={handleChange}
disabled={disabled}
{...otherProps}
/>
);
}
1 change: 1 addition & 0 deletions components/src/FormatControls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
// limitations under the License.

export * from './FormatControls';
export * from './UnitSelector';
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,20 @@ export const OptionsEditorControl = ({ label, control, description }: OptionsEdi
// controls for a11y.
const generatedControlId = useId('EditorSectionControl');
const controlId = `${generatedControlId}-control`;
const labelId = `${generatedControlId}-label`;

const controlProps = {
id: controlId,
'aria-labelledby': labelId,
};

return (
<FormControl>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Stack direction="row" alignItems="center" justifyContent="center">
<FormLabel htmlFor={controlId}>{label}</FormLabel>
<FormLabel id={labelId} htmlFor={controlId}>
{label}
</FormLabel>
{description && (
<InfoTooltip description={description} enterDelay={100}>
<IconButton
Expand Down
16 changes: 15 additions & 1 deletion components/src/SettingsAutocomplete/SettingsAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export function SettingsAutocomplete<
>({
options,
renderInput = (params): ReactElement => <TextField {...params} />,
id,
'aria-labelledby': ariaLabelledby,
...otherProps
}: SettingsAutocompleteProps<OptionType, Multiple, DisableClearable>): ReactElement {
const getOptionLabel: UseAutocompleteProps<OptionType, undefined, boolean, undefined>['getOptionLabel'] = (
Expand All @@ -82,6 +84,18 @@ export function SettingsAutocomplete<
return option.label ?? option.id;
};

// Merge id and aria-labelledby props into the input element for proper accessibility
// and form association, while still allowing custom renderInput implementations.
const handleRenderInput: AutocompleteProps<OptionType, Multiple, DisableClearable, false>['renderInput'] = (
params
) => {
const mergedParams = {
...params,
inputProps: { ...params.inputProps, id, 'aria-labelledby': ariaLabelledby },
};
return renderInput(mergedParams);
};

// Note: this component currently is not virtualized because it is specific
// to being used for settings, which generally have a pretty small list of
// options. If this changes to include values with many options, virtualization
Expand All @@ -92,7 +106,7 @@ export function SettingsAutocomplete<
getOptionDisabled={(option) => !!option.disabled}
getOptionLabel={getOptionLabel}
options={options}
renderInput={renderInput}
renderInput={handleRenderInput}
renderOption={({ key, ...props }, option) => {
return (
<li key={key} {...props}>
Expand Down
6 changes: 6 additions & 0 deletions components/src/TimeSeriesTooltip/TimeChartTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export interface TimeChartTooltipProps {
containerId?: string;
onUnpinClick?: () => void;
format?: FormatOptions;
/**
* Map of series ID to format options for per-series formatting (used with multiple Y axes)
*/
seriesFormatMap?: Map<string, FormatOptions>;
wrapLabels?: boolean;
}

Expand All @@ -47,6 +51,7 @@ export const TimeChartTooltip = memo(function TimeChartTooltip({
enablePinning = true,
wrapLabels,
format,
seriesFormatMap,
onUnpinClick,
pinnedPos,
}: TimeChartTooltipProps) {
Expand Down Expand Up @@ -79,6 +84,7 @@ export const TimeChartTooltip = memo(function TimeChartTooltip({
pinnedPos,
chart,
format,
seriesFormatMap,
showAllSeries,
});
if (nearbySeries.length === 0) {
Expand Down
Loading