Skip to content

Commit

Permalink
Allow input and display of floats for Number fields (#7340)
Browse files Browse the repository at this point in the history
### Description

- We added a decimal field for a Number Field type in the settings
- We updated the Number Field type create a form with decimals input
- We are not implementing the dropdown present on the Figma because it
seems not related

### Demo


<https://www.loom.com/share/18a8d4b712a14f6d8b66806764f8467f?sid=3fc79b46-ae32-46e3-8635-d0eee02e53b2>

Fixes #6987

---------

Co-authored-by: gitstart-twenty <[email protected]>
Co-authored-by: Marie Stoppa <[email protected]>
  • Loading branch information
3 people authored Oct 4, 2024
1 parent e3ed574 commit 97eff77
Show file tree
Hide file tree
Showing 22 changed files with 478 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const queries = {
${baseFields}
defaultValue
options
settings
}
}
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type FieldMetadataItemOption = {

export type FieldMetadataItem = Omit<
Field,
'__typename' | 'defaultValue' | 'options' | 'settings' | 'relationDefinition'
'__typename' | 'defaultValue' | 'options' | 'relationDefinition'
> & {
__typename?: string;
defaultValue?: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ export const formatFieldMetadataItemAsFieldDefinition = ({
metadata: fieldDefintionMetadata,
type: field.type,
}),
settings: field.settings,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ export const RecordBoardCard = ({
metadata: fieldDefinition.metadata,
type: fieldDefinition.type,
}),
settings: fieldDefinition.settings,
},
useUpdateRecord: useUpdateOneRecordHook,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/h
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';

export const NumberFieldDisplay = () => {
const { fieldValue } = useNumberFieldDisplay();

return <NumberDisplay value={fieldValue} />;
const { fieldValue, fieldDefinition } = useNumberFieldDisplay();
return (
<NumberDisplay
value={fieldValue}
decimals={fieldDefinition.settings?.decimals}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecor
import { FieldNumberValue } from '@/object-record/record-field/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { FieldMetadataType } from '~/generated-metadata/graphql';

import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';
canBeCastAsNumberOrNull,
castAsNumberOrNull,
} from '~/utils/cast-as-number-or-null';

import { FieldContext } from '../../contexts/FieldContext';
import { usePersistField } from '../../hooks/usePersistField';
Expand All @@ -32,11 +33,11 @@ export const useNumberField = () => {
const persistField = usePersistField();

const persistNumberField = (newValue: string) => {
if (!canBeCastAsIntegerOrNull(newValue)) {
if (!canBeCastAsNumberOrNull(newValue)) {
return;
}

const castedValue = castAsIntegerOrNull(newValue);
const castedValue = castAsNumberOrNull(newValue);

persistField(castedValue);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ export type FieldDefinition<T extends FieldMetadata> = {
infoTooltipContent?: string;
defaultValue?: any;
editButtonIcon?: IconComponent;
settings?: {
decimals?: number;
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from 'zod';

export const numberFieldDefaultValueSchema = z.object({
decimals: z.number().nullable(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/
import { SettingsDataModelFieldCurrencySettingsFormCard } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencySettingsFormCard';
import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm';
import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard';
import { settingsDataModelFieldNumberFormSchema } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm';
import { SettingsDataModelFieldNumberSettingsFormCard } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard';
import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm';
import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard';
import {
Expand Down Expand Up @@ -52,6 +54,10 @@ const multiSelectFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.MultiSelect) })
.merge(settingsDataModelFieldMultiSelectFormSchema);

const numberFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.Number) })
.merge(settingsDataModelFieldNumberFormSchema);

const otherFieldsFormSchema = z.object({
type: z.enum(
Object.keys(
Expand All @@ -63,6 +69,7 @@ const otherFieldsFormSchema = z.object({
FieldMetadataType.MultiSelect,
FieldMetadataType.Date,
FieldMetadataType.DateTime,
FieldMetadataType.Number,
]),
) as [FieldMetadataType, ...FieldMetadataType[]],
),
Expand All @@ -78,13 +85,17 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
relationFieldFormSchema,
selectFieldFormSchema,
multiSelectFieldFormSchema,
numberFieldFormSchema,
otherFieldsFormSchema,
],
);

type SettingsDataModelFieldSettingsFormCardProps = {
isCreatingField?: boolean;
fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> &
fieldMetadataItem: Pick<
FieldMetadataItem,
'icon' | 'label' | 'type' | 'isCustom'
> &
Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>;
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;

Expand Down Expand Up @@ -163,6 +174,16 @@ export const SettingsDataModelFieldSettingsFormCard = ({
);
}

if (fieldMetadataItem.type === FieldMetadataType.Number) {
return (
<SettingsDataModelFieldNumberSettingsFormCard
disabled={fieldMetadataItem.isCustom === false}
fieldMetadataItem={fieldMetadataItem}
objectMetadataItem={objectMetadataItem}
/>
);
}

if (
fieldMetadataItem.type === FieldMetadataType.Select ||
fieldMetadataItem.type === FieldMetadataType.MultiSelect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import styled from '@emotion/styled';

import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput';
import { IconInfoCircle, IconMinus, IconPlus } from 'twenty-ui';
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';

type SettingsDataModelFieldNumberDecimalsInputProps = {
value: number;
onChange: (value: number) => void;
disabled?: boolean;
};

const StyledCounterContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.noisy};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: 4px;
display: flex;
flex-direction: column;
flex: 1;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
`;

const StyledExampleText = styled.div`
color: ${({ theme }) => theme.font.color.primary};
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: ${({ theme }) => theme.font.weight.regular};
`;

const StyledCounterControlsIcons = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;

const StyledCounterInnerContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(2)};
height: 24px;
`;

const StyledTextInput = styled(TextInput)`
width: ${({ theme }) => theme.spacing(16)};
input {
width: ${({ theme }) => theme.spacing(16)};
height: ${({ theme }) => theme.spacing(6)};
text-align: center;
font-weight: ${({ theme }) => theme.font.weight.medium};
background: ${({ theme }) => theme.background.noisy};
}
input ~ div {
padding-right: ${({ theme }) => theme.spacing(0)};
border-radius: ${({ theme }) => theme.spacing(1)};
background: ${({ theme }) => theme.background.noisy};
}
`;

const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1)};
`;

const StyledControlButton = styled(Button)`
height: ${({ theme }) => theme.spacing(6)};
width: ${({ theme }) => theme.spacing(6)};
padding: 0;
justify-content: center;
svg {
height: ${({ theme }) => theme.spacing(4)};
width: ${({ theme }) => theme.spacing(4)};
}
`;

const StyledInfoButton = styled(Button)`
height: ${({ theme }) => theme.spacing(6)};
width: ${({ theme }) => theme.spacing(6)};
padding: 0;
justify-content: center;
svg {
color: ${({ theme }) => theme.font.color.extraLight};
height: ${({ theme }) => theme.spacing(4)};
width: ${({ theme }) => theme.spacing(4)};
}
`;

const MIN_VALUE = 0;
const MAX_VALUE = 100;
export const SettingsDataModelFieldNumberDecimalsInput = ({
value,
onChange,
disabled,
}: SettingsDataModelFieldNumberDecimalsInputProps) => {
const exampleValue = (1000).toFixed(value);

const handleIncrementCounter = () => {
if (value < MAX_VALUE) {
const newValue = value + 1;
onChange(newValue);
}
};

const handleDecrementCounter = () => {
if (value > MIN_VALUE) {
const newValue = value - 1;
onChange(newValue);
}
};

const handleTextInputChange = (value: string) => {
const castedNumber = castAsNumberOrNull(value);
if (castedNumber === null) {
onChange(MIN_VALUE);
return;
}

if (castedNumber < MIN_VALUE) {
return;
}

if (castedNumber > MAX_VALUE) {
onChange(MAX_VALUE);
return;
}
onChange(castedNumber);
};
return (
<>
<StyledTitle>Number of decimals</StyledTitle>
<StyledCounterContainer>
<StyledCounterInnerContainer>
<StyledExampleText>Example: {exampleValue}</StyledExampleText>
<StyledCounterControlsIcons>
<StyledInfoButton variant="tertiary" Icon={IconInfoCircle} />
<StyledControlButton
variant="secondary"
onClick={handleDecrementCounter}
Icon={IconMinus}
disabled={disabled}
/>
<StyledTextInput
name="decimals"
fullWidth
value={value.toString()}
onChange={(value) => handleTextInputChange(value)}
disabled={disabled}
/>
<StyledControlButton
variant="secondary"
onClick={handleIncrementCounter}
Icon={IconPlus}
disabled={disabled}
/>
</StyledCounterControlsIcons>
</StyledCounterInnerContainer>
</StyledCounterContainer>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';

import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema';
import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput';
import { CardContent } from '@/ui/layout/card/components/CardContent';
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';

export const settingsDataModelFieldNumberFormSchema = z.object({
settings: numberFieldDefaultValueSchema,
});

export type SettingsDataModelFieldNumberFormValues = z.infer<
typeof settingsDataModelFieldNumberFormSchema
>;

type SettingsDataModelFieldNumberFormProps = {
disabled?: boolean;
fieldMetadataItem: Pick<
FieldMetadataItem,
'icon' | 'label' | 'type' | 'defaultValue' | 'settings'
>;
};

export const SettingsDataModelFieldNumberForm = ({
disabled,
fieldMetadataItem,
}: SettingsDataModelFieldNumberFormProps) => {
const { control } = useFormContext<SettingsDataModelFieldNumberFormValues>();

return (
<CardContent>
<Controller
name="settings"
defaultValue={{
decimals:
fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
}}
control={control}
render={({ field: { onChange, value } }) => {
const count = value?.decimals ?? 0;

return (
<SettingsDataModelFieldNumberDecimalsInput
value={count}
onChange={(value) => onChange({ decimals: value })}
disabled={disabled}
></SettingsDataModelFieldNumberDecimalsInput>
);
}}
/>
</CardContent>
);
};
Loading

0 comments on commit 97eff77

Please sign in to comment.