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:
+1
-1
@@ -117,7 +117,7 @@ export const ChartSettingItem = ({
|
||||
id={item.id}
|
||||
dropdownId={item.id}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownContent widthInPixels={item.dropdownWidth}>
|
||||
{item.DropdownContent && <item.DropdownContent />}
|
||||
</DropdownContent>
|
||||
}
|
||||
|
||||
+76
@@ -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>
|
||||
);
|
||||
};
|
||||
+61
@@ -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>
|
||||
);
|
||||
};
|
||||
+56
-30
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ export type ChartSettingsItem = {
|
||||
isBoolean: boolean;
|
||||
dependsOn?: CHART_CONFIGURATION_SETTING_IDS[];
|
||||
DropdownContent?: ComponentType;
|
||||
dropdownWidth?: number;
|
||||
isInput?: boolean;
|
||||
inputPlaceholder?: MessageDescriptor;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user