Update bar chart design (#15372)

- Implement new dynamic color palettes
- Create custom bar component with rounded edges



https://github.com/user-attachments/assets/8246f16d-0239-4807-bb4a-9647367575f2
This commit is contained in:
Raphaël Bosi
2025-10-27 17:46:06 +01:00
committed by GitHub
parent e975fbe217
commit 01d8b99c38
29 changed files with 593 additions and 333 deletions
@@ -117,7 +117,7 @@ export const ChartSettingItem = ({
id={item.id}
dropdownId={item.id}
dropdownComponents={
<DropdownContent>
<DropdownContent widthInPixels={item.dropdownWidth}>
{item.DropdownContent && <item.DropdownContent />}
</DropdownContent>
}
@@ -0,0 +1,76 @@
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
import { generateGroupColor } from '@/page-layout/widgets/graph/utils/generateGroupColor';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type ThemeColor } from 'twenty-ui/theme';
type ChartColorGradientOptionProps = {
colorOption: {
id: string;
name: string;
colorName: ThemeColor | 'auto';
};
selectedItemId: string | null;
currentColor: string | null | undefined;
onSelectColor: (colorName: ThemeColor | 'auto') => void;
};
const StyledColorSamplesContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(0.5)};
`;
const COLOR_GROUP_COUNT = 5;
export const ChartColorGradientOption = ({
colorOption,
selectedItemId,
currentColor,
onSelectColor,
}: ChartColorGradientOptionProps) => {
const colorName = colorOption.colorName as ThemeColor;
const theme = useTheme();
const colorRegistry = createGraphColorRegistry(theme);
const colorSamples = (
<StyledColorSamplesContainer>
{Array.from({ length: COLOR_GROUP_COUNT }).map((_, index) => {
const colorScheme = colorRegistry[colorName];
const reversedIndex = COLOR_GROUP_COUNT - 1 - index;
const groupColor = generateGroupColor({
colorScheme,
groupIndex: reversedIndex,
totalGroups: COLOR_GROUP_COUNT,
});
return <ColorSample key={index} color={groupColor} />;
})}
</StyledColorSamplesContainer>
);
return (
<SelectableListItem
key={colorOption.id}
itemId={colorOption.id}
onEnter={() => {
onSelectColor(colorOption.colorName);
}}
>
<MenuItemSelect
text={colorOption.name}
selected={false}
focused={
selectedItemId === colorOption.id || currentColor === colorOption.id
}
contextualText={colorSamples}
contextualTextPosition="right"
onClick={() => {
onSelectColor(colorOption.colorName);
}}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,61 @@
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import styled from '@emotion/styled';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { MAIN_COLORS, type ThemeColor } from 'twenty-ui/theme';
type ChartColorPaletteOptionProps = {
selectedItemId: string | null;
currentColor: string | null | undefined;
onSelectColor: (colorName: ThemeColor | 'auto') => void;
};
const StyledColorSamplesContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(0.5)};
`;
export const ChartColorPaletteOption = ({
selectedItemId,
currentColor,
onSelectColor,
}: ChartColorPaletteOptionProps) => {
const paletteColors: Array<keyof typeof MAIN_COLORS> = [
'purple',
'pink',
'red',
'orange',
'yellow',
];
const colorSamples = (
<StyledColorSamplesContainer>
{paletteColors.map((paletteColorName) => {
const baseColor = MAIN_COLORS[paletteColorName];
return <ColorSample key={paletteColorName} color={baseColor} />;
})}
</StyledColorSamplesContainer>
);
return (
<SelectableListItem
key={'auto'}
itemId={'auto'}
onEnter={() => {
onSelectColor('auto');
}}
>
<MenuItemSelect
text={'Palette'}
selected={false}
focused={selectedItemId === 'auto' || currentColor === 'auto'}
contextualText={colorSamples}
contextualTextPosition="right"
onClick={() => {
onSelectColor('auto');
}}
/>
</SelectableListItem>
);
};
@@ -1,24 +1,30 @@
import { ChartColorGradientOption } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartColorGradientOption';
import { ChartColorPaletteOption } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartColorPaletteOption';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { MAIN_COLOR_NAMES, type ThemeColor } from 'twenty-ui/theme';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
type ColorOption = {
id: string;
name: string;
colorName: ThemeColor | 'auto';
};
export const ChartColorSelectionDropdownContent = () => {
const [searchQuery, setSearchQuery] = useState('');
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
@@ -54,11 +60,18 @@ export const ChartColorSelectionDropdownContent = () => {
const currentColor = configuration.color;
const colorOptions = MAIN_COLOR_NAMES.map((colorName) => ({
id: colorName,
name: capitalize(colorName),
colorName: colorName,
}));
const colorOptions: ColorOption[] = [
{
id: 'auto',
name: 'Palette',
colorName: 'auto',
},
...MAIN_COLOR_NAMES.map((colorName) => ({
id: colorName,
name: capitalize(colorName),
colorName: colorName,
})),
];
const filteredColorOptions = filterBySearchQuery({
items: colorOptions,
@@ -66,7 +79,7 @@ export const ChartColorSelectionDropdownContent = () => {
getSearchableValues: (item) => [item.name],
});
const handleSelectColor = (colorName: ThemeColor) => {
const handleSelectColor = (colorName: ThemeColor | 'auto') => {
updateCurrentWidgetConfig({
configToUpdate: {
color: colorName,
@@ -75,6 +88,10 @@ export const ChartColorSelectionDropdownContent = () => {
closeDropdown();
};
const regularColorOptions = filteredColorOptions.filter(
(option) => option.colorName !== 'auto',
);
return (
<>
<DropdownMenuSearchInput
@@ -84,6 +101,7 @@ export const ChartColorSelectionDropdownContent = () => {
onChange={(event) => setSearchQuery(event.target.value)}
value={searchQuery}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={dropdownId}
@@ -92,29 +110,37 @@ export const ChartColorSelectionDropdownContent = () => {
(colorOption) => colorOption.id,
)}
>
{filteredColorOptions.map((colorOption) => (
<SelectableListItem
key={colorOption.id}
itemId={colorOption.id}
onEnter={() => {
handleSelectColor(colorOption.colorName);
}}
>
<MenuItemSelect
text={colorOption.name}
selected={currentColor === colorOption.id}
focused={selectedItemId === colorOption.id}
LeftIcon={() => (
<ColorSample colorName={colorOption.colorName} />
)}
onClick={() => {
handleSelectColor(colorOption.colorName);
}}
/>
</SelectableListItem>
))}
<ChartColorPaletteOption
selectedItemId={selectedItemId}
currentColor={currentColor}
onSelectColor={handleSelectColor}
/>
</SelectableList>
</DropdownMenuItemsContainer>
{regularColorOptions.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={filteredColorOptions.map(
(colorOption) => colorOption.id,
)}
>
{regularColorOptions.map((colorOption) => (
<ChartColorGradientOption
key={colorOption.id}
colorOption={colorOption}
selectedItemId={selectedItemId}
currentColor={currentColor}
onSelectColor={handleSelectColor}
/>
))}
</SelectableList>
</DropdownMenuItemsContainer>
</>
)}
</>
);
};
@@ -2,6 +2,7 @@ import { ChartColorSelectionDropdownContent } from '@/command-menu/pages/page-la
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { IconColorSwatch } from 'twenty-ui/display';
export const COLORS_SETTING: ChartSettingsItem = {
@@ -10,4 +11,5 @@ export const COLORS_SETTING: ChartSettingsItem = {
label: CHART_CONFIGURATION_SETTING_LABELS.COLORS,
id: CHART_CONFIGURATION_SETTING_IDS.COLORS,
DropdownContent: ChartColorSelectionDropdownContent,
dropdownWidth: GenericDropdownContentWidth.ExtraLarge,
};
@@ -16,6 +16,7 @@ export type ChartSettingsItem = {
isBoolean: boolean;
dependsOn?: CHART_CONFIGURATION_SETTING_IDS[];
DropdownContent?: ComponentType;
dropdownWidth?: number;
isInput?: boolean;
inputPlaceholder?: MessageDescriptor;
};