Added and optimized missing RatingFieldDisplay component (#5904)
The display for Rating field type was missing, I just added it based on RatingInput in readonly mode and optimized a bit for performance also. Fixes https://github.com/twentyhq/twenty/issues/5900
This commit is contained in:
@ -2,9 +2,11 @@ import { useContext } from 'react';
|
||||
|
||||
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
|
||||
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
|
||||
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
|
||||
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
||||
import { isFieldChipDisplay } from '@/object-record/utils/getRecordChipGeneratorPerObjectPerField';
|
||||
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
@ -82,5 +84,7 @@ export const FieldDisplay = () => {
|
||||
<JsonFieldDisplay />
|
||||
) : isFieldBoolean(fieldDefinition) ? (
|
||||
<BooleanFieldDisplay />
|
||||
) : isFieldRating(fieldDefinition) ? (
|
||||
<RatingFieldDisplay />
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { useRatingFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRatingFieldDisplay';
|
||||
import { RatingInput } from '@/ui/field/input/components/RatingInput';
|
||||
|
||||
export const RatingFieldDisplay = () => {
|
||||
const { rating } = useRatingFieldDisplay();
|
||||
|
||||
return <RatingInput value={rating} readonly />;
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/RatingFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testRating'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: RatingFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof RatingFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'RatingFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldRatingValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useRatingFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue(entityId, fieldName) as
|
||||
| FieldRatingValue
|
||||
| undefined;
|
||||
|
||||
const rating = fieldValue ?? 'RATING_1';
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
rating,
|
||||
};
|
||||
};
|
||||
@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconTwentyStarFilled } from 'twenty-ui';
|
||||
import { useContext, useState } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { IconTwentyStarFilled, THEME_COMMON, ThemeContext } from 'twenty-ui';
|
||||
|
||||
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
|
||||
import { FieldRatingValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
@ -11,29 +10,38 @@ const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledRatingIconContainer = styled.div<{ isActive?: boolean }>`
|
||||
color: ${({ isActive, theme }) =>
|
||||
isActive ? theme.font.color.secondary : theme.background.quaternary};
|
||||
const StyledRatingIconContainer = styled.div<{
|
||||
color: string;
|
||||
}>`
|
||||
color: ${({ color }) => color};
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
type RatingInputProps = {
|
||||
onChange: (newValue: FieldRatingValue) => void;
|
||||
onChange?: (newValue: FieldRatingValue) => void;
|
||||
value: FieldRatingValue;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
const iconSizeMd = THEME_COMMON.icon.size.md;
|
||||
|
||||
export const RatingInput = ({
|
||||
onChange,
|
||||
value,
|
||||
readonly,
|
||||
}: RatingInputProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const activeColor = theme.font.color.secondary;
|
||||
const inactiveColor = theme.background.quaternary;
|
||||
|
||||
const [hoveredValue, setHoveredValue] = useState<FieldRatingValue | null>(
|
||||
null,
|
||||
);
|
||||
const currentValue = hoveredValue ?? value;
|
||||
|
||||
const selectedIndex = RATING_VALUES.indexOf(currentValue);
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
role="slider"
|
||||
@ -44,17 +52,17 @@ export const RatingInput = ({
|
||||
tabIndex={0}
|
||||
>
|
||||
{RATING_VALUES.map((value, index) => {
|
||||
const currentIndex = RATING_VALUES.indexOf(currentValue);
|
||||
const isActive = index <= selectedIndex;
|
||||
|
||||
return (
|
||||
<StyledRatingIconContainer
|
||||
key={index}
|
||||
isActive={index <= currentIndex}
|
||||
onClick={readonly ? undefined : () => onChange(value)}
|
||||
color={isActive ? activeColor : inactiveColor}
|
||||
onClick={readonly ? undefined : () => onChange?.(value)}
|
||||
onMouseEnter={readonly ? undefined : () => setHoveredValue(value)}
|
||||
onMouseLeave={readonly ? undefined : () => setHoveredValue(null)}
|
||||
>
|
||||
<IconTwentyStarFilled size={theme.icon.size.md} />
|
||||
<IconTwentyStarFilled size={iconSizeMd} />
|
||||
</StyledRatingIconContainer>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user