mirror of
https://github.com/apache/superset.git
synced 2026-04-24 02:25:13 +00:00
refactor(word-cloud): convert rotation and color controls to React components (POC) (#36275)
Co-authored-by: BrandanBurgess <brandanbb13@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
ControlPanelConfig,
|
||||
getStandardizedControls,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { RotationControl, ColorSchemeControl } from './controls';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [
|
||||
@@ -63,25 +64,14 @@ const config: ControlPanelConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[<RotationControl name="rotation" key="rotation" renderTrigger />],
|
||||
[
|
||||
{
|
||||
name: 'rotation',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
label: t('Word Rotation'),
|
||||
choices: [
|
||||
['random', t('random')],
|
||||
['flat', t('flat')],
|
||||
['square', t('square')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
default: 'square',
|
||||
clearable: false,
|
||||
description: t('Rotation to apply to words in the cloud'),
|
||||
},
|
||||
},
|
||||
<ColorSchemeControl
|
||||
name="color_scheme"
|
||||
key="color_scheme"
|
||||
renderTrigger
|
||||
/>,
|
||||
],
|
||||
['color_scheme'],
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { getCategoricalSchemeRegistry } from '@superset-ui/core';
|
||||
import InternalColorSchemeControl from './ColorSchemeControl/index';
|
||||
import { ColorSchemes } from './ColorSchemeControl/index';
|
||||
// NOTE: We copied the Explore ColorSchemeControl into this plugin to avoid
|
||||
// pulling the entire frontend src tree into this package’s tsconfig (importing
|
||||
// from src/ was dragging in fixtures, tests, and other plugins). Keep this copy
|
||||
// in sync with upstream changes, and consider moving it into a shared package
|
||||
// once the control-panel refactor settles so all consumers can reuse it.
|
||||
import { ControlComponentProps } from '@superset-ui/chart-controls';
|
||||
|
||||
type ColorSchemeControlWrapperProps = ControlComponentProps<string> & {
|
||||
clearable?: boolean;
|
||||
};
|
||||
|
||||
export default function ColorSchemeControlWrapper({
|
||||
name = 'color_scheme',
|
||||
value,
|
||||
onChange,
|
||||
clearable = true,
|
||||
label,
|
||||
description,
|
||||
...rest
|
||||
}: ColorSchemeControlWrapperProps) {
|
||||
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
||||
const choices = categoricalSchemeRegistry.keys().map(s => [s, s]);
|
||||
const schemes = categoricalSchemeRegistry.getMap() as ColorSchemes;
|
||||
|
||||
return (
|
||||
<InternalColorSchemeControl
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
onChange={onChange}
|
||||
clearable={clearable}
|
||||
choices={choices}
|
||||
schemes={schemes}
|
||||
hasCustomLabelsColor={false}
|
||||
label={typeof label === 'string' ? label : undefined}
|
||||
description={typeof description === 'string' ? description : undefined}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ColorSchemeControlWrapper.displayName = 'ColorSchemeControlWrapper';
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { css, SupersetTheme } from '@apache-superset/core/ui';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Tooltip } from '@superset-ui/core/components';
|
||||
|
||||
type ColorSchemeLabelProps = {
|
||||
colors: string[];
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export default function ColorSchemeLabel(props: ColorSchemeLabelProps) {
|
||||
const { id, label, colors } = props;
|
||||
const [showTooltip, setShowTooltip] = useState<boolean>(false);
|
||||
const labelNameRef = useRef<HTMLElement>(null);
|
||||
const labelsColorRef = useRef<HTMLElement>(null);
|
||||
const handleShowTooltip = () => {
|
||||
const labelNameElement = labelNameRef.current;
|
||||
const labelsColorElement = labelsColorRef.current;
|
||||
if (
|
||||
labelNameElement &&
|
||||
labelsColorElement &&
|
||||
(labelNameElement.scrollWidth > labelNameElement.offsetWidth ||
|
||||
labelNameElement.scrollHeight > labelNameElement.offsetHeight ||
|
||||
labelsColorElement.scrollWidth > labelsColorElement.offsetWidth ||
|
||||
labelsColorElement.scrollHeight > labelsColorElement.offsetHeight)
|
||||
) {
|
||||
setShowTooltip(true);
|
||||
}
|
||||
};
|
||||
const handleHideTooltip = () => {
|
||||
setShowTooltip(false);
|
||||
};
|
||||
|
||||
const colorsList = () =>
|
||||
colors.map((color: string, i: number) => (
|
||||
<span
|
||||
data-test="color"
|
||||
key={`${id}-${i}`}
|
||||
css={(theme: { sizeUnit: number }) => css`
|
||||
padding-left: ${theme.sizeUnit / 2}px;
|
||||
:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
background-color: ${color};
|
||||
border: 1px solid ${color === 'white' ? 'black' : color};
|
||||
width: 9px;
|
||||
height: 10px;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
));
|
||||
|
||||
const tooltipContent = () => (
|
||||
<>
|
||||
<span>{label}</span>
|
||||
<div>{colorsList()}</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
data-testid="tooltip"
|
||||
overlayClassName="color-scheme-tooltip"
|
||||
title={tooltipContent()}
|
||||
key={id}
|
||||
open={showTooltip}
|
||||
>
|
||||
<span
|
||||
className="color-scheme-option"
|
||||
onMouseEnter={handleShowTooltip}
|
||||
onMouseLeave={handleHideTooltip}
|
||||
css={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`}
|
||||
data-test={id}
|
||||
>
|
||||
<span
|
||||
className="color-scheme-label"
|
||||
ref={labelNameRef}
|
||||
css={(theme: SupersetTheme) => css`
|
||||
min-width: 125px;
|
||||
padding-right: ${theme.sizeUnit * 2}px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
ref={labelsColorRef}
|
||||
css={(theme: SupersetTheme) => css`
|
||||
flex: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding-right: ${theme.sizeUnit}px;
|
||||
`}
|
||||
>
|
||||
{colorsList()}
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { useMemo, ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
ColorScheme,
|
||||
ColorSchemeGroup,
|
||||
SequentialScheme,
|
||||
t,
|
||||
getLabelsColorMap,
|
||||
CategoricalColorNamespace,
|
||||
} from '@superset-ui/core';
|
||||
import { css, useTheme } from '@apache-superset/core/ui';
|
||||
import { sortBy } from 'lodash';
|
||||
import { ControlHeader } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
Tooltip,
|
||||
Select,
|
||||
type SelectOptionsType,
|
||||
} from '@superset-ui/core/components';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import ColorSchemeLabel from './ColorSchemeLabel';
|
||||
|
||||
const getColorNamespace = (namespace?: string) => namespace || undefined;
|
||||
|
||||
export type OptionData = SelectOptionsType[number]['options'][number] & {
|
||||
searchText?: string;
|
||||
};
|
||||
|
||||
export interface ColorSchemes {
|
||||
[key: string]: ColorScheme;
|
||||
}
|
||||
|
||||
export interface ColorSchemeControlProps {
|
||||
hasCustomLabelsColor: boolean;
|
||||
hasDashboardColorScheme?: boolean;
|
||||
hasSharedLabelsColor?: boolean;
|
||||
sharedLabelsColors?: string[];
|
||||
mapLabelsColors?: Record<string, any>;
|
||||
colorNamespace?: string;
|
||||
chartId?: number;
|
||||
dashboardId?: number;
|
||||
label?: string;
|
||||
name: string;
|
||||
onChange?: (value: string) => void;
|
||||
value: string;
|
||||
clearable: boolean;
|
||||
defaultScheme?: string;
|
||||
choices: string[][] | (() => string[][]);
|
||||
schemes: ColorSchemes | (() => ColorSchemes);
|
||||
isLinear?: boolean;
|
||||
description?: string;
|
||||
hovered?: boolean;
|
||||
}
|
||||
|
||||
const CUSTOM_LABEL_ALERT = t(
|
||||
`The colors of this chart might be overridden by custom label colors of the related dashboard.
|
||||
Check the JSON metadata in the Advanced settings.`,
|
||||
);
|
||||
|
||||
const DASHBOARD_ALERT = t(
|
||||
`The color scheme is determined by the related dashboard.
|
||||
Edit the color scheme in the dashboard properties.`,
|
||||
);
|
||||
|
||||
const DASHBOARD_CONTEXT_ALERT = t(
|
||||
`You are viewing this chart in a dashboard context with labels shared across multiple charts.
|
||||
The color scheme selection is disabled.`,
|
||||
);
|
||||
|
||||
const DASHBOARD_CONTEXT_TOOLTIP = t(
|
||||
`You are viewing this chart in the context of a dashboard that is directly affecting its colors.
|
||||
To edit the color scheme, open this chart outside of the dashboard.`,
|
||||
);
|
||||
|
||||
const Label = ({
|
||||
label,
|
||||
dashboardId,
|
||||
hasSharedLabelsColor,
|
||||
hasCustomLabelsColor,
|
||||
hasDashboardColorScheme,
|
||||
}: Pick<
|
||||
ColorSchemeControlProps,
|
||||
| 'label'
|
||||
| 'dashboardId'
|
||||
| 'hasCustomLabelsColor'
|
||||
| 'hasSharedLabelsColor'
|
||||
| 'hasDashboardColorScheme'
|
||||
>) => {
|
||||
const theme = useTheme();
|
||||
if (hasSharedLabelsColor || hasCustomLabelsColor || hasDashboardColorScheme) {
|
||||
const alertTitle =
|
||||
hasCustomLabelsColor && !hasSharedLabelsColor
|
||||
? CUSTOM_LABEL_ALERT
|
||||
: dashboardId && hasDashboardColorScheme
|
||||
? DASHBOARD_ALERT
|
||||
: DASHBOARD_CONTEXT_ALERT;
|
||||
return (
|
||||
<>
|
||||
{label}{' '}
|
||||
<Tooltip title={alertTitle}>
|
||||
<Icons.WarningOutlined
|
||||
iconColor={theme.colorWarning}
|
||||
css={css`
|
||||
vertical-align: baseline;
|
||||
`}
|
||||
iconSize="s"
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <>{label}</>;
|
||||
};
|
||||
|
||||
const ColorSchemeControl = ({
|
||||
hasCustomLabelsColor = false,
|
||||
hasDashboardColorScheme = false,
|
||||
mapLabelsColors = {},
|
||||
sharedLabelsColors = [],
|
||||
dashboardId,
|
||||
colorNamespace,
|
||||
chartId,
|
||||
label = t('Color scheme'),
|
||||
onChange = () => {},
|
||||
value,
|
||||
clearable = false,
|
||||
defaultScheme,
|
||||
choices = [],
|
||||
schemes = {},
|
||||
isLinear,
|
||||
...rest
|
||||
}: ColorSchemeControlProps) => {
|
||||
const countSharedLabelsColor = sharedLabelsColors.length;
|
||||
const colorMapInstance = getLabelsColorMap();
|
||||
const chartLabels = chartId
|
||||
? colorMapInstance.chartsLabelsMap.get(chartId)?.labels || []
|
||||
: [];
|
||||
const hasSharedLabelsColor = !!(
|
||||
dashboardId &&
|
||||
countSharedLabelsColor > 0 &&
|
||||
chartLabels.some(label => sharedLabelsColors.includes(label))
|
||||
);
|
||||
const hasDashboardScheme = dashboardId && hasDashboardColorScheme;
|
||||
const showDashboardLockedOption = hasDashboardScheme || hasSharedLabelsColor;
|
||||
const theme = useTheme();
|
||||
const currentScheme = useMemo(() => {
|
||||
if (showDashboardLockedOption) {
|
||||
return 'dashboard';
|
||||
}
|
||||
let result = value || defaultScheme;
|
||||
if (result === 'SUPERSET_DEFAULT') {
|
||||
const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
|
||||
result = schemesObject?.SUPERSET_DEFAULT?.id;
|
||||
}
|
||||
return result;
|
||||
}, [defaultScheme, schemes, showDashboardLockedOption, value]);
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (showDashboardLockedOption) {
|
||||
return [
|
||||
{
|
||||
value: 'dashboard',
|
||||
label: (
|
||||
<Tooltip title={DASHBOARD_CONTEXT_TOOLTIP}>
|
||||
{t('Dashboard scheme')}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
|
||||
const controlChoices = typeof choices === 'function' ? choices() : choices;
|
||||
const allColorOptions: string[] = [];
|
||||
const filteredColorOptions = controlChoices.filter(o => {
|
||||
const option = o[0];
|
||||
const isValidColorOption =
|
||||
option !== 'SUPERSET_DEFAULT' && !allColorOptions.includes(option);
|
||||
allColorOptions.push(option);
|
||||
return isValidColorOption;
|
||||
});
|
||||
|
||||
const groups = filteredColorOptions.reduce(
|
||||
(acc, [value]) => {
|
||||
const currentScheme = schemesObject[value];
|
||||
|
||||
// For categorical scheme, display all the colors
|
||||
// For sequential scheme, show 10 or interpolate to 10.
|
||||
// Sequential schemes usually have at most 10 colors.
|
||||
let colors: string[] = [];
|
||||
if (currentScheme) {
|
||||
colors = isLinear
|
||||
? (currentScheme as SequentialScheme).getColors(10)
|
||||
: currentScheme.colors;
|
||||
}
|
||||
const option = {
|
||||
label: (
|
||||
<ColorSchemeLabel
|
||||
id={currentScheme.id}
|
||||
label={currentScheme.label}
|
||||
colors={colors}
|
||||
/>
|
||||
) as ReactNode,
|
||||
value,
|
||||
searchText: currentScheme.label,
|
||||
};
|
||||
acc[currentScheme.group ?? ColorSchemeGroup.Other].options.push(option);
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
[ColorSchemeGroup.Custom]: {
|
||||
title: ColorSchemeGroup.Custom,
|
||||
label: t('Custom color palettes'),
|
||||
options: [] as OptionData[],
|
||||
},
|
||||
[ColorSchemeGroup.Featured]: {
|
||||
title: ColorSchemeGroup.Featured,
|
||||
label: t('Featured color palettes'),
|
||||
options: [] as OptionData[],
|
||||
},
|
||||
[ColorSchemeGroup.Other]: {
|
||||
title: ColorSchemeGroup.Other,
|
||||
label: t('Other color palettes'),
|
||||
options: [] as OptionData[],
|
||||
},
|
||||
},
|
||||
);
|
||||
const nonEmptyGroups = Object.values(groups)
|
||||
.filter(group => group.options.length > 0)
|
||||
.map(group => ({
|
||||
...group,
|
||||
options: sortBy(group.options, opt => opt.label),
|
||||
}));
|
||||
|
||||
// if there are no featured or custom color schemes, return the ungrouped options
|
||||
if (
|
||||
nonEmptyGroups.length === 1 &&
|
||||
nonEmptyGroups[0].title === ColorSchemeGroup.Other
|
||||
) {
|
||||
return nonEmptyGroups[0].options.map(opt => ({
|
||||
value: opt.value,
|
||||
label: opt.customLabel || opt.label,
|
||||
}));
|
||||
}
|
||||
return nonEmptyGroups.map(group => ({
|
||||
label: group.label,
|
||||
options: group.options.map(opt => ({
|
||||
value: opt.value,
|
||||
label: opt.customLabel || opt.label,
|
||||
searchText: opt.searchText,
|
||||
})),
|
||||
}));
|
||||
}, [choices, hasDashboardScheme, hasSharedLabelsColor, isLinear, schemes]);
|
||||
|
||||
// We can't pass on change directly because it receives a second
|
||||
// parameter and it would be interpreted as the error parameter
|
||||
const handleOnChange = (value: string) => {
|
||||
if (chartId) {
|
||||
colorMapInstance.setOwnColorScheme(chartId, value);
|
||||
if (dashboardId) {
|
||||
const colorNameSpace = getColorNamespace(colorNamespace);
|
||||
const categoricalNamespace =
|
||||
CategoricalColorNamespace.getNamespace(colorNameSpace);
|
||||
|
||||
const sharedLabelsSet = new Set(sharedLabelsColors);
|
||||
// reset colors except shared and custom labels to keep dashboard consistency
|
||||
const resettableLabels = Object.keys(mapLabelsColors).filter(
|
||||
l => !sharedLabelsSet.has(l),
|
||||
);
|
||||
categoricalNamespace.resetColorsForLabels(resettableLabels);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ControlHeader
|
||||
{...rest}
|
||||
label={
|
||||
<Label
|
||||
label={label}
|
||||
dashboardId={dashboardId}
|
||||
hasCustomLabelsColor={hasCustomLabelsColor}
|
||||
hasDashboardColorScheme={hasDashboardColorScheme}
|
||||
hasSharedLabelsColor={hasSharedLabelsColor}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
css={css`
|
||||
width: 100%;
|
||||
& .ant-select-item.ant-select-item-group {
|
||||
padding-left: ${theme.sizeUnit}px;
|
||||
font-size: ${theme.fontSize}px;
|
||||
}
|
||||
& .ant-select-item-option-grouped {
|
||||
padding-left: ${theme.sizeUnit * 3}px;
|
||||
}
|
||||
`}
|
||||
aria-label={t('Select color scheme')}
|
||||
allowClear={clearable}
|
||||
disabled={hasDashboardScheme || hasSharedLabelsColor}
|
||||
onChange={handleOnChange}
|
||||
placeholder={t('Select scheme')}
|
||||
value={currentScheme}
|
||||
showSearch
|
||||
getPopupContainer={triggerNode => triggerNode.parentNode}
|
||||
options={options}
|
||||
optionFilterProps={['label', 'value', 'searchText']}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorSchemeControl;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { t } from '@superset-ui/core';
|
||||
import { Select, SelectValue } from '@superset-ui/core/components';
|
||||
import { ControlHeader } from '@superset-ui/chart-controls';
|
||||
import { ControlComponentProps } from '@superset-ui/chart-controls';
|
||||
|
||||
type RotationControlProps = ControlComponentProps<string> & {
|
||||
choices?: [string, string][];
|
||||
clearable?: boolean;
|
||||
};
|
||||
|
||||
export default function RotationControl({
|
||||
name = 'rotation',
|
||||
value,
|
||||
onChange,
|
||||
choices = [
|
||||
['random', t('random')],
|
||||
['flat', t('flat')],
|
||||
['square', t('square')],
|
||||
],
|
||||
label = t('Word Rotation'),
|
||||
description = t('Rotation to apply to words in the cloud'),
|
||||
renderTrigger = true,
|
||||
clearable = false,
|
||||
}: RotationControlProps) {
|
||||
return (
|
||||
<div className="Control" data-test={name}>
|
||||
<ControlHeader
|
||||
name={name}
|
||||
label={label}
|
||||
description={description}
|
||||
renderTrigger={renderTrigger}
|
||||
/>
|
||||
<Select
|
||||
value={value ?? 'square'}
|
||||
options={choices.map(([key, text]) => ({ label: text, value: key }))}
|
||||
onChange={(val: SelectValue) => {
|
||||
if (val === null || val === undefined) {
|
||||
return;
|
||||
}
|
||||
// Handle LabeledValue object
|
||||
if (
|
||||
typeof val === 'object' &&
|
||||
'value' in val &&
|
||||
val.value !== undefined
|
||||
) {
|
||||
onChange?.(val.value as string);
|
||||
} else if (typeof val === 'string' || typeof val === 'number') {
|
||||
// Handle raw value
|
||||
onChange?.(String(val));
|
||||
}
|
||||
}}
|
||||
allowClear={clearable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RotationControl.displayName = 'RotationControl';
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
export { default as RotationControl } from './RotationControl';
|
||||
export { default as ColorSchemeControl } from './ColorSchemeControl';
|
||||
Reference in New Issue
Block a user