Merge remote-tracking branch 'origin/main' into feat/view-groups

This commit is contained in:
Jérémy Magrin
2024-09-23 16:30:19 +02:00
232 changed files with 3822 additions and 2088 deletions

View File

@@ -176,7 +176,7 @@ export const CalendarEventRow = ({
: participant.displayName
}
placeholderColorSeed={
participant.workspaceMemberId ?? participant.personId
participant.workspaceMemberId || participant.personId
}
type="rounded"
/>

View File

@@ -1,5 +1,5 @@
import { useRef } from 'react';
import styled from '@emotion/styled';
import { useRef } from 'react';
import { useRecoilCallback } from 'recoil';
import { Avatar, GRAY_SCALE } from 'twenty-ui';
@@ -155,12 +155,20 @@ export const EmailThreadPreview = ({
<Avatar
avatarUrl={thread?.firstParticipant?.avatarUrl}
placeholder={thread.firstParticipant.displayName}
placeholderColorSeed={
thread.firstParticipant.workspaceMemberId ||
thread.firstParticipant.personId
}
type="rounded"
/>
{thread?.lastTwoParticipants?.[0] && (
<StyledAvatar
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
placeholder={thread.lastTwoParticipants[0].displayName}
placeholderColorSeed={
thread.lastTwoParticipants[0].workspaceMemberId ||
thread.lastTwoParticipants[0].personId
}
type="rounded"
/>
)}

View File

@@ -13,33 +13,38 @@ import { Button } from '@/ui/input/button/components/Button';
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { IconArrowBackUp } from 'twenty-ui';
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
flex: 1;
height: 85%;
justify-content: flex-start;
overflow-y: auto;
position: relative;
`;
const StyledButtonContainer = styled.div`
const StyledButtonContainer = styled.div<{ isMobile: boolean }>`
background: ${({ theme }) => theme.background.secondary};
bottom: 0;
border-top: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
height: 110px;
left: 0;
padding-left: ${({ theme }) => theme.spacing(7)};
padding-top: ${({ theme }) => theme.spacing(5)};
position: fixed;
right: 0;
justify-content: flex-end;
height: ${({ isMobile }) => (isMobile ? '100px' : '50px')};
padding: ${({ theme }) => theme.spacing(2)};
width: 100%;
box-sizing: border-box;
`;
export const RightDrawerEmailThread = () => {
const setMessageThread = useSetRecoilState(messageThreadState);
const isMobile = useIsMobile();
const {
thread,
messages,
@@ -118,47 +123,49 @@ export const RightDrawerEmailThread = () => {
return null;
}
return (
<StyledContainer>
{threadLoading ? (
<EmailLoader loadingText="Loading thread" />
) : (
<>
<EmailThreadHeader
subject={subject}
lastMessageSentAt={lastMessage.receivedAt}
/>
{firstMessages.map((message) => (
<EmailThreadMessage
key={message.id}
participants={message.messageParticipants}
body={message.text}
sentAt={message.receivedAt}
<StyledWrapper>
<StyledContainer>
{threadLoading ? (
<EmailLoader loadingText="Loading thread" />
) : (
<>
<EmailThreadHeader
subject={subject}
lastMessageSentAt={lastMessage.receivedAt}
/>
))}
<IntermediaryMessages messages={intermediaryMessages} />
<EmailThreadMessage
key={lastMessage.id}
participants={lastMessage.messageParticipants}
body={lastMessage.text}
sentAt={lastMessage.receivedAt}
isExpanded
/>
<CustomResolverFetchMoreLoader
loading={threadLoading}
onLastRowVisible={fetchMoreMessages}
/>
</>
)}
{canReply && !messageChannelLoading ? (
<StyledButtonContainer>
{firstMessages.map((message) => (
<EmailThreadMessage
key={message.id}
participants={message.messageParticipants}
body={message.text}
sentAt={message.receivedAt}
/>
))}
<IntermediaryMessages messages={intermediaryMessages} />
<EmailThreadMessage
key={lastMessage.id}
participants={lastMessage.messageParticipants}
body={lastMessage.text}
sentAt={lastMessage.receivedAt}
isExpanded
/>
<CustomResolverFetchMoreLoader
loading={threadLoading}
onLastRowVisible={fetchMoreMessages}
/>
</>
)}
</StyledContainer>
{canReply && !messageChannelLoading && (
<StyledButtonContainer isMobile={isMobile}>
<Button
onClick={handleReplyClick}
title="Reply (View in Gmail)"
title="Reply"
Icon={IconArrowBackUp}
disabled={!canReply}
></Button>
/>
</StyledButtonContainer>
) : null}
</StyledContainer>
)}
</StyledWrapper>
);
};

View File

@@ -51,13 +51,12 @@ export const useActivities = <T extends Task | Note>({
),
];
const skipBecauseNoActivityTargetFound = activityIds.length === 0;
const filter: RecordGqlOperationFilter = {
id:
targetableObjects.length > 0
? {
in: activityIds,
}
: undefined,
id: {
in: activityIds,
},
...activitiesFilters,
};
@@ -69,7 +68,7 @@ export const useActivities = <T extends Task | Note>({
const { records: activities, loading: loadingActivities } =
useFindManyRecords<Task | Note>({
skip: skip || loadingActivityTargets,
skip: skip || loadingActivityTargets || skipBecauseNoActivityTargetFound,
objectNameSingular:
FIND_ACTIVITIES_OPERATION_SIGNATURE.objectNameSingular,
recordGqlFields: FIND_ACTIVITIES_OPERATION_SIGNATURE.fields,

View File

@@ -1,9 +1,8 @@
import { useRecoilValue } from 'recoil';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton';
import { SingleEntityObjectFilterDropdownButton } from './SingleEntityObjectFilterDropdownButton';
@@ -16,12 +15,9 @@ export const ObjectFilterDropdownButton = ({
filterDropdownId,
hotkeyScope,
}: ObjectFilterDropdownButtonProps) => {
const { availableFilterDefinitionsState } = useFilterDropdown({
filterDropdownId: filterDropdownId,
});
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
filterDropdownId,
);
const hasOnlyOneEntityFilter =

View File

@@ -1,8 +1,6 @@
import styled from '@emotion/styled';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
@@ -12,6 +10,8 @@ import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { isDefined } from 'twenty-ui';
export const StyledInput = styled.input`
@@ -43,10 +43,8 @@ export const StyledInput = styled.input`
export const ObjectFilterDropdownFilterSelect = () => {
const [searchText, setSearchText] = useState('');
const { availableFilterDefinitionsState } = useFilterDropdown();
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
);
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]

View File

@@ -7,7 +7,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from '~/utils/isDefined';
@@ -17,6 +17,7 @@ export const MAX_RECORDS_TO_DISPLAY = 3;
type ObjectFilterDropdownRecordSelectProps = {
viewComponentId?: string;
};
export const ObjectFilterDropdownRecordSelect = ({
viewComponentId,
}: ObjectFilterDropdownRecordSelectProps) => {
@@ -31,7 +32,9 @@ export const ObjectFilterDropdownRecordSelect = ({
emptyFilterButKeepDefinition,
} = useFilterDropdown();
const { removeCombinedViewFilter } = useCombinedViewFilters(viewComponentId);
const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(viewComponentId);
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId);
@@ -78,7 +81,7 @@ export const ObjectFilterDropdownRecordSelect = ({
if (newSelectedRecordIds.length === 0) {
emptyFilterButKeepDefinition();
removeCombinedViewFilter(fieldId);
deleteCombinedViewFilter(fieldId);
return;
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { useTheme } from '@emotion/react';
import React from 'react';
import { useRecoilValue } from 'recoil';
import { IconChevronDown } from 'twenty-ui';
@@ -11,8 +11,9 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
@@ -25,14 +26,13 @@ export const SingleEntityObjectFilterDropdownButton = ({
hotkeyScope: HotkeyScope;
}) => {
const {
availableFilterDefinitionsState,
selectedFilterState,
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
} = useFilterDropdown();
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
);
const selectedFilter = useRecoilValue(selectedFilterState);

View File

@@ -2,8 +2,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { FieldMetadataType } from '~/generated/graphql';
@@ -17,9 +19,12 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
component: MultipleFiltersDropdownButton,
decorators: [
(Story) => {
const { setAvailableFilterDefinitions } = useFilterDropdown({
filterDropdownId: 'entity-tasks-filter-scope',
});
const instanceId = 'entity-tasks-filter-scope';
const setAvailableFilterDefinitions = useSetRecoilComponentStateV2(
availableFilterDefinitionsComponentState,
instanceId,
);
setAvailableFilterDefinitions([
{
fieldMetadataId: '1',
@@ -47,9 +52,11 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
},
]);
return (
<ObjectFilterDropdownScope filterScopeId="entity-tasks-filter-scope">
<Story />
</ObjectFilterDropdownScope>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<ObjectFilterDropdownScope filterScopeId={instanceId}>
<Story />
</ObjectFilterDropdownScope>
</ViewComponentInstanceContext.Provider>
);
},
ObjectMetadataItemsDecorator,

View File

@@ -6,6 +6,8 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
import { useFilterDropdownStates } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdownStates';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
const filterDropdownId = 'filterDropdownId';
@@ -35,11 +37,13 @@ describe('useFilterDropdown', () => {
it('should set availableFilterDefinitions', async () => {
const { result } = renderHook(() => {
useFilterDropdown({ filterDropdownId });
const { availableFilterDefinitionsState } =
useFilterDropdownStates(filterDropdownId);
const [availableFilterDefinitions, setAvailableFilterDefinitions] =
useRecoilState(availableFilterDefinitionsState);
useRecoilComponentStateV2(
availableFilterDefinitionsComponentState,
filterDropdownId,
);
return { availableFilterDefinitions, setAvailableFilterDefinitions };
}, renderHookConfig);

View File

@@ -18,7 +18,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
);
const {
availableFilterDefinitionsState,
filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState,
objectFilterDropdownSelectedRecordIdsState,
@@ -73,9 +72,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
],
);
const setAvailableFilterDefinitions = useSetRecoilState(
availableFilterDefinitionsState,
);
const setSelectedFilter = useSetRecoilState(selectedFilterState);
const setSelectedOperandInDropdown = useSetRecoilState(
selectedOperandInDropdownState,
@@ -106,7 +102,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
resetFilter,
setSelectedFilter,
setSelectedOperandInDropdown,
setAvailableFilterDefinitions,
setFilterDefinitionUsedInDropdown,
setObjectFilterDropdownSearchInput,
// setObjectFilterDropdownSelectedEntityId,
@@ -116,7 +111,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setIsObjectFilterDropdownUnfolded,
setOnFilterSelect,
emptyFilterButKeepDefinition,
availableFilterDefinitionsState,
filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState,
// objectFilterDropdownSelectedEntityIdState,

View File

@@ -8,14 +8,8 @@ import { onFilterSelectComponentState } from '@/object-record/object-filter-drop
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
export const useFilterDropdownStates = (scopeId: string) => {
const availableFilterDefinitionsState = extractComponentState(
availableFilterDefinitionsComponentState,
scopeId,
);
const filterDefinitionUsedInDropdownState = extractComponentState(
filterDefinitionUsedInDropdownComponentState,
scopeId,
@@ -63,7 +57,6 @@ export const useFilterDropdownStates = (scopeId: string) => {
);
return {
availableFilterDefinitionsState,
filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState,
objectFilterDropdownSelectedRecordIdsState,

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type ObjectFilterDropdownScopeInternalContextProps = ComponentStateKey;
type ObjectFilterDropdownScopeInternalContextProps = RecoilComponentStateKey;
export const ObjectFilterDropdownScopeInternalContext =
createScopeInternalContext<ObjectFilterDropdownScopeInternalContextProps>();

View File

@@ -6,6 +6,8 @@ import { useSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useS
import { useSortDropdownStates } from '@/object-record/object-sort-dropdown/hooks/useSortDropdownStates';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
@@ -24,11 +26,13 @@ describe('useSortDropdown', () => {
it('should set availableSortDefinitions', async () => {
const { result } = renderHook(() => {
useSortDropdown({ sortDropdownId });
const { availableSortDefinitionsState } =
useSortDropdownStates(sortDropdownId);
// TODO: verify this instance id works
const [availableSortDefinitions, setAvailableSortDefinitions] =
useRecoilState(availableSortDefinitionsState);
useRecoilComponentStateV2(
availableSortDefinitionsComponentState,
sortDropdownId,
);
return {
availableSortDefinitions,

View File

@@ -7,6 +7,8 @@ import selectedSortDirectionState from '@/object-record/object-sort-dropdown/sta
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
import {
OBJECT_SORT_DROPDOWN_ID,
VIEW_SORT_DROPDOWN_ID,
@@ -41,7 +43,6 @@ export const useObjectSortDropdown = () => {
};
const {
availableSortDefinitionsState,
onSortSelectState,
isSortSelectedState,
objectSortDropdownSearchInputState,
@@ -52,8 +53,10 @@ export const useObjectSortDropdown = () => {
});
const isSortSelected = useRecoilValue(isSortSelectedState);
const availableSortDefinitions = useRecoilValue(
availableSortDefinitionsState,
const availableSortDefinitions = useRecoilComponentValueV2(
availableSortDefinitionsComponentState,
VIEW_SORT_DROPDOWN_ID,
);
const onSortSelect = useRecoilValue(onSortSelectState);

View File

@@ -14,8 +14,8 @@ export const useSortDropdown = (props?: UseSortProps) => {
ObjectSortDropdownScopeInternalContext,
props?.sortDropdownId,
);
const {
availableSortDefinitionsState,
isSortSelectedState,
onSortSelectState,
objectSortDropdownSearchInputState,
@@ -35,7 +35,6 @@ export const useSortDropdown = (props?: UseSortProps) => {
return {
scopeId,
availableSortDefinitionsState,
isSortSelectedState,
onSortSelectState,
objectSortDropdownSearchInputState,

View File

@@ -2,14 +2,8 @@ import { isSortSelectedComponentState } from '@/object-record/object-sort-dropdo
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
import { onSortSelectComponentState } from '@/object-record/object-sort-dropdown/states/onSortSelectScopedState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
export const useSortDropdownStates = (scopeId: string) => {
const availableSortDefinitionsState = extractComponentState(
availableSortDefinitionsComponentState,
scopeId,
);
const isSortSelectedState = extractComponentState(
isSortSelectedComponentState,
scopeId,
@@ -26,7 +20,6 @@ export const useSortDropdownStates = (scopeId: string) => {
);
return {
availableSortDefinitionsState,
isSortSelectedState,
onSortSelectState,
objectSortDropdownSearchInputState,

View File

@@ -1,9 +1,9 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
import { Sort } from '../../types/Sort';
type ObjectSortDropdownScopeInternalContextProps = ComponentStateKey & {
type ObjectSortDropdownScopeInternalContextProps = RecoilComponentStateKey & {
onSortSelect?: (sort: Sort) => void;
};

View File

@@ -2,9 +2,9 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type RecordBoardScopeInternalContextProps = ComponentStateKey & {
type RecordBoardScopeInternalContextProps = RecoilComponentStateKey & {
onFieldsChange: (fields: FieldDefinition<FieldMetadata>[]) => void;
onColumnsChange: (column: RecordGroupDefinition[]) => void;
};

View File

@@ -27,7 +27,7 @@ export const useInitDraftValueV2 = <FieldValue>() => {
const recordFieldInputScopeId = `${getRecordFieldInputId(
recordId,
fieldDefinition?.metadata?.fieldName,
)}-scope`;
)}`;
const getDraftValueSelector = extractComponentSelector<
FieldInputDraftValue<FieldValue> | undefined

View File

@@ -3,8 +3,7 @@ import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleT
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
import { usePersistField } from '../../../hooks/usePersistField';
import { isDoubleTextFieldEmpty } from '@/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty';
import { FieldInputEvent } from './DateTimeFieldInput';
const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
@@ -28,46 +27,55 @@ export const FullNameFieldInput = ({
onTab,
onShiftTab,
}: FullNameFieldInputProps) => {
const { hotkeyScope, draftValue, setDraftValue } = useFullNameField();
const persistField = usePersistField();
const { hotkeyScope, draftValue, setDraftValue, persistFullNameField } =
useFullNameField();
const convertToFullName = (newDoubleText: FieldDoubleText) => {
return {
firstName: newDoubleText.firstValue,
lastName: newDoubleText.secondValue,
firstName: newDoubleText.firstValue.trim(),
lastName: newDoubleText.secondValue.trim(),
};
};
const getRequiredDraftValueFromDoubleText = (
newDoubleText: FieldDoubleText,
) => {
return isDoubleTextFieldEmpty(newDoubleText)
? undefined
: convertToFullName(newDoubleText);
};
const handleEnter = (newDoubleText: FieldDoubleText) => {
onEnter?.(() => persistField(convertToFullName(newDoubleText)));
onEnter?.(() => persistFullNameField(convertToFullName(newDoubleText)));
};
const handleEscape = (newDoubleText: FieldDoubleText) => {
onEscape?.(() => persistField(convertToFullName(newDoubleText)));
onEscape?.(() => persistFullNameField(convertToFullName(newDoubleText)));
};
const handleClickOutside = (
event: MouseEvent | TouchEvent,
newDoubleText: FieldDoubleText,
) => {
onClickOutside?.(() => persistField(convertToFullName(newDoubleText)));
onClickOutside?.(() =>
persistFullNameField(convertToFullName(newDoubleText)),
);
};
const handleTab = (newDoubleText: FieldDoubleText) => {
onTab?.(() => persistField(convertToFullName(newDoubleText)));
onTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
};
const handleShiftTab = (newDoubleText: FieldDoubleText) => {
onShiftTab?.(() => persistField(convertToFullName(newDoubleText)));
onShiftTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
};
const handleChange = (newDoubleText: FieldDoubleText) => {
setDraftValue(convertToFullName(newDoubleText));
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
};
const handlePaste = (newDoubleText: FieldDoubleText) => {
setDraftValue(convertToFullName(newDoubleText));
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
};
return (

View File

@@ -16,6 +16,7 @@ import { MenuItemMultiSelectTag } from '@/ui/navigation/menu-item/components/Men
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
const StyledRelationPickerContainer = styled.div`
left: -1px;
@@ -109,7 +110,11 @@ export const MultiSelectFieldInput = ({
<DropdownMenu data-select-disable>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) => setSearchFilter(event.currentTarget.value)}
onChange={(event) =>
setSearchFilter(
turnIntoEmptyStringIfWhitespacesOnly(event.currentTarget.value),
)
}
autoFocus
/>
<DropdownMenuSeparator />

View File

@@ -60,7 +60,7 @@ export const NumberFieldInput = ({
<TextInput
placeholder={fieldDefinition.metadata.placeHolder}
autoFocus
value={draftValue ?? ''}
value={draftValue?.toString() ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}

View File

@@ -4,6 +4,7 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
import { usePersistField } from '../../../hooks/usePersistField';
import { useTextField } from '../../hooks/useTextField';
import { turnIntoUndefinedIfWhitespacesOnly } from '~/utils/string/turnIntoUndefinedIfWhitespacesOnly';
import { FieldInputEvent } from './DateTimeFieldInput';
export type TextFieldInputProps = {
@@ -25,32 +26,31 @@ export const TextFieldInput = ({
useTextField();
const persistField = usePersistField();
const handleEnter = (newText: string) => {
onEnter?.(() => persistField(newText));
onEnter?.(() => persistField(newText.trim()));
};
const handleEscape = (newText: string) => {
onEscape?.(() => persistField(newText));
onEscape?.(() => persistField(newText.trim()));
};
const handleClickOutside = (
event: MouseEvent | TouchEvent,
newText: string,
) => {
onClickOutside?.(() => persistField(newText));
onClickOutside?.(() => persistField(newText.trim()));
};
const handleTab = (newText: string) => {
onTab?.(() => persistField(newText));
onTab?.(() => persistField(newText.trim()));
};
const handleShiftTab = (newText: string) => {
onShiftTab?.(() => persistField(newText));
onShiftTab?.(() => persistField(newText.trim()));
};
const handleChange = (newText: string) => {
setDraftValue(newText);
setDraftValue(turnIntoUndefinedIfWhitespacesOnly(newText));
};
return (

View File

@@ -0,0 +1,9 @@
import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleText';
export const isDoubleTextFieldEmpty = (doubleText: FieldDoubleText) => {
const { firstValue, secondValue } = doubleText;
const totalLength = firstValue.trim().length + secondValue.trim().length;
return totalLength > 0 ? false : true;
};

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type RecordFieldInputScopeInternalContextProps = ComponentStateKey;
type RecordFieldInputScopeInternalContextProps = RecoilComponentStateKey;
export const RecordFieldInputScopeInternalContext =
createScopeInternalContext<RecordFieldInputScopeInternalContextProps>();

View File

@@ -25,7 +25,7 @@ import {
} from '@/object-record/record-field/types/FieldMetadata';
export type FieldTextDraftValue = string;
export type FieldNumberDraftValue = string;
export type FieldNumberDraftValue = number;
export type FieldDateTimeDraftValue = string;
export type FieldPhoneDraftValue = string;
export type FieldPhonesDraftValue = {

View File

@@ -24,6 +24,7 @@ import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { ViewField } from '@/views/types/ViewField';
import { ViewType } from '@/views/types/ViewType';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
@@ -143,81 +144,91 @@ export const RecordIndexContainer = () => {
return (
<StyledContainer>
<InformationBannerWrapper />
<RecordFieldValueSelectorContextProvider>
<SpreadsheetImportProvider>
<StyledContainerWithPadding>
<ViewBar
viewBarId={recordIndexId}
optionsDropdownButton={
<RecordIndexOptionsDropdown
recordIndexId={recordIndexId}
objectNameSingular={objectNameSingular}
viewType={recordIndexViewType ?? ViewType.Table}
/>
}
onCurrentViewChange={(view) => {
if (!view) {
return;
<ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<RecordFieldValueSelectorContextProvider>
<SpreadsheetImportProvider>
<StyledContainerWithPadding>
<ViewBar
viewBarId={recordIndexId}
optionsDropdownButton={
<RecordIndexOptionsDropdown
recordIndexId={recordIndexId}
objectNameSingular={objectNameSingular}
viewType={recordIndexViewType ?? ViewType.Table}
/>
}
onCurrentViewChange={(view) => {
if (!view) {
return;
}
onViewFieldsChange(view.viewFields);
setTableFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setRecordIndexFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setTableSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
setRecordIndexIsCompactModeActive(view.isCompact);
}}
/>
<RecordIndexViewBarEffect
objectNamePlural={objectNamePlural}
viewBarId={recordIndexId}
/>
</StyledContainerWithPadding>
</SpreadsheetImportProvider>
onViewFieldsChange(view.viewFields);
setTableFilters(
mapViewFiltersToFilters(
view.viewFilters,
filterDefinitions,
),
);
setRecordIndexFilters(
mapViewFiltersToFilters(
view.viewFilters,
filterDefinitions,
),
);
setTableSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
setRecordIndexIsCompactModeActive(view.isCompact);
}}
/>
<RecordIndexViewBarEffect
objectNamePlural={objectNamePlural}
viewBarId={recordIndexId}
/>
</StyledContainerWithPadding>
</SpreadsheetImportProvider>
{recordIndexViewType === ViewType.Table && (
<>
<RecordIndexTableContainer
recordTableId={recordIndexId}
viewBarId={recordIndexId}
/>
<RecordIndexTableContainerEffect
objectNameSingular={objectNameSingular}
recordTableId={recordIndexId}
viewBarId={recordIndexId}
/>
</>
)}
{recordIndexViewType === ViewType.Kanban && (
<StyledContainerWithPadding fullHeight>
<RecordIndexBoardContainer
recordBoardId={recordIndexId}
viewBarId={recordIndexId}
objectNameSingular={objectNameSingular}
/>
<RecordIndexBoardDataLoader
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
<RecordIndexBoardDataLoaderEffect
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
</StyledContainerWithPadding>
)}
</RecordFieldValueSelectorContextProvider>
{recordIndexViewType === ViewType.Table && (
<>
<RecordIndexTableContainer
recordTableId={recordIndexId}
viewBarId={recordIndexId}
/>
<RecordIndexTableContainerEffect
objectNameSingular={objectNameSingular}
recordTableId={recordIndexId}
viewBarId={recordIndexId}
/>
</>
)}
{recordIndexViewType === ViewType.Kanban && (
<StyledContainerWithPadding fullHeight>
<RecordIndexBoardContainer
recordBoardId={recordIndexId}
viewBarId={recordIndexId}
objectNameSingular={objectNameSingular}
/>
<RecordIndexBoardDataLoader
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
<RecordIndexBoardDataLoaderEffect
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
</StyledContainerWithPadding>
)}
</RecordFieldValueSelectorContextProvider>
</ViewComponentInstanceContext.Provider>
</StyledContainer>
);
};

View File

@@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
import { useDeleteCombinedViewSorts } from '@/views/hooks/useDeleteCombinedViewSorts';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
export const RecordIndexRemoveSortingModal = ({
@@ -21,11 +21,11 @@ export const RecordIndexRemoveSortingModal = ({
isRemoveSortingModalOpenState,
);
const { removeCombinedViewSort } = useCombinedViewSorts(recordTableId);
const { deleteCombinedViewSort } = useDeleteCombinedViewSorts(recordTableId);
const handleRemoveClick = () => {
fieldMetadataIds.forEach((id) => {
removeCombinedViewSort(id);
deleteCombinedViewSort(id);
});
};

View File

@@ -8,8 +8,9 @@ import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
type RecordIndexTableContainerEffectProps = {
objectNameSingular: string;
@@ -50,9 +51,10 @@ export const RecordIndexTableContainerEffect = ({
const { tableRowIdsState, hasUserSelectedAllRowsState } =
useRecordTableStates(recordTableId);
const { entityCountInCurrentViewState } = useViewStates(recordTableId);
const entityCountInCurrentView = useRecoilValue(
entityCountInCurrentViewState,
// TODO: verify this instance id works
const entityCountInCurrentView = useRecoilComponentValueV2(
entityCountInCurrentViewComponentState,
recordTableId,
);
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
const tableRowIds = useRecoilValue(tableRowIdsState);

View File

@@ -1,8 +1,8 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
export const useHandleIndexIdentifierClick = ({
objectMetadataItem,
@@ -13,10 +13,9 @@ export const useHandleIndexIdentifierClick = ({
}) => {
const navigate = useNavigate();
const currentViewId = useRecoilValue(
currentViewIdComponentState({
scopeId: recordIndexId,
}),
const currentViewId = useRecoilComponentValueV2(
currentViewIdComponentState,
recordIndexId,
);
const handleIndexIdentifierClick = (recordId: string) => {

View File

@@ -7,7 +7,7 @@ import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldM
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
import { isDefined } from '~/utils/isDefined';
type UseHandleToggleColumnFilterProps = {
@@ -26,7 +26,7 @@ export const useHandleToggleColumnFilter = ({
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId);
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
const { openDropdown } = useDropdownV2();
const handleToggleColumnFilter = useCallback(

View File

@@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
import { useUpsertCombinedViewSorts } from '@/views/hooks/useUpsertCombinedViewSorts';
import { isDefined } from '~/utils/isDefined';
type UseHandleToggleColumnSortProps = {
@@ -22,7 +22,7 @@ export const useHandleToggleColumnSort = ({
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { upsertCombinedViewSort } = useCombinedViewSorts(viewBarId);
const { upsertCombinedViewSort } = useUpsertCombinedViewSorts(viewBarId);
const handleToggleColumnSort = useCallback(
(fieldMetadataId: string) => {

View File

@@ -6,7 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useRecoilCallback } from 'recoil';
import { isDefined } from '~/utils/isDefined';
@@ -27,7 +27,7 @@ export const useHandleToggleTrashColumnFilter = ({
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId);
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
const { isSoftDeleteActiveState } = useRecordTableStates(viewBarId);
const handleToggleTrashColumnFilter = useCallback(() => {

View File

@@ -1,10 +1,10 @@
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { act, renderHook, waitFor } from '@testing-library/react';
import { percentage, sleep, useTableData } from '../useTableData';
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState';
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { ViewType } from '@/views/types/ViewType';
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
@@ -148,15 +148,19 @@ const mocks: MockedResponse[] = [
];
const Wrapper = ({ children }: { children: ReactNode }) => (
<Router>
<RecoilRoot>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<SnackBarManagerScopeInternalContext.Provider
value={{
scopeId: 'snack-bar-manager',
}}
>
<Router>
<RecoilRoot>
<MockedProvider addTypename={false} mocks={mocks}>
{children}
</MockedProvider>
</SnackBarProviderScope>
</RecoilRoot>
</Router>
</RecoilRoot>
</Router>
</SnackBarManagerScopeInternalContext.Provider>
);
const graphqlEmptyResponse = [
@@ -174,15 +178,19 @@ const graphqlEmptyResponse = [
];
const WrapperWithEmptyResponse = ({ children }: { children: ReactNode }) => (
<Router>
<RecoilRoot>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<SnackBarManagerScopeInternalContext.Provider
value={{
scopeId: 'snack-bar-manager',
}}
>
<Router>
<RecoilRoot>
<MockedProvider addTypename={false} mocks={graphqlEmptyResponse}>
{children}
</MockedProvider>
</SnackBarProviderScope>
</RecoilRoot>
</Router>
</RecoilRoot>
</Router>
</SnackBarManagerScopeInternalContext.Provider>
);
describe('useTableData', () => {
@@ -191,13 +199,13 @@ describe('useTableData', () => {
describe('data fetching', () => {
it('should handle no records', async () => {
const callback = jest.fn();
const { result } = renderHook(
() =>
useTableData({
recordIndexId,
objectNameSingular,
callback,
delayMs: 0,
viewType: ViewType.Kanban,
}),
@@ -209,7 +217,7 @@ describe('useTableData', () => {
});
await waitFor(() => {
expect(callback).toHaveBeenCalledWith([], []);
expect(callback).not.toHaveBeenCalled();
});
});

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from 'react';
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
@@ -8,8 +8,8 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { useHandleViews } from '@/views/hooks/useHandleViews';
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
import { GraphQLView } from '@/views/types/GraphQLView';
import { mapBoardFieldDefinitionsToViewFields } from '@/views/utils/mapBoardFieldDefinitionsToViewFields';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
@@ -31,7 +31,7 @@ export const useRecordIndexOptionsForBoard = ({
useRecoilState(recordIndexFieldDefinitionsState);
const { saveViewFields } = useSaveCurrentViewFields(viewBarId);
const { updateCurrentView } = useHandleViews(viewBarId);
const { updateCurrentView } = useUpdateCurrentView(viewBarId);
const { isCompactModeActiveState } = useRecordBoard(recordBoardId);
const [isCompactModeActive, setIsCompactModeActive] = useRecoilState(

View File

@@ -2,7 +2,8 @@ import { useRecoilValue } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
export const RecordTableActionBar = ({
recordTableId,
@@ -15,10 +16,12 @@ export const RecordTableActionBar = ({
hasUserSelectedAllRowsState,
} = useRecordTableStates(recordTableId);
const { entityCountInCurrentViewState } = useViewStates(recordTableId);
const entityCountInCurrentView = useRecoilValue(
entityCountInCurrentViewState,
// TODO: verify this instance id works
const entityCountInCurrentView = useRecoilComponentValueV2(
entityCountInCurrentViewComponentState,
recordTableId,
);
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
const tableRowIds = useRecoilValue(tableRowIdsState);
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());

View File

@@ -5,7 +5,7 @@ import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/h
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
@@ -13,7 +13,8 @@ export const RecordTableEmptyStateSoftDelete = () => {
const { objectMetadataItem, objectNameSingular, recordTableId } =
useContext(RecordTableContext);
const { removeCombinedViewFilter } = useCombinedViewFilters(recordTableId);
const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(recordTableId);
const { tableFiltersState } = useRecordTableStates(recordTableId);
const tableFilters = useRecoilValue(tableFiltersState);
@@ -24,7 +25,7 @@ export const RecordTableEmptyStateSoftDelete = () => {
});
const handleButtonClick = async () => {
removeCombinedViewFilter(
deleteCombinedViewFilter(
tableFilters.find(
(filter) =>
filter.definition.label === 'Deleted at' &&

View File

@@ -48,14 +48,14 @@ describe('useSelectedTableCellEditMode', () => {
expect(mockCallbackInterface.set).toHaveBeenCalledWith(
{
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":0,"row":0},"scopeId":"yourScopeId-scope"}',
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":0,"row":0},"scopeId":"yourScopeId"}',
},
false,
);
expect(mockCallbackInterface.set).toHaveBeenCalledWith(
{
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":5,"row":1},"scopeId":"yourScopeId-scope"}',
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":5,"row":1},"scopeId":"yourScopeId"}',
},
true,
);

View File

@@ -1,11 +1,11 @@
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
import { ColumnDefinition } from '../../types/ColumnDefinition';
// TODO: separate scope contexts from event contexts
type RecordTableScopeInternalContextProps = ComponentStateKey & {
type RecordTableScopeInternalContextProps = RecoilComponentStateKey & {
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
};

View File

@@ -1,8 +1,8 @@
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
export const hasRecordTableFetchedAllRecordsComponentStateV2 =
createComponentStateV2<boolean>({
createComponentStateV2_alpha<boolean>({
key: 'hasRecordTableFetchedAllRecordsComponentStateV2',
componentContext: RecordTableScopeInternalContext,
defaultValue: false,

View File

@@ -1,8 +1,8 @@
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
export const isRecordTableScrolledLeftComponentState =
createComponentStateV2<boolean>({
createComponentStateV2_alpha<boolean>({
key: 'isRecordTableScrolledLeftComponentState',
componentContext: RecordTableScopeInternalContext,
defaultValue: true,

View File

@@ -1,8 +1,8 @@
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
export const isRecordTableScrolledTopComponentState =
createComponentStateV2<boolean>({
createComponentStateV2_alpha<boolean>({
key: 'isRecordTableScrolledTopComponentState',
componentContext: RecordTableScopeInternalContext,
defaultValue: true,

View File

@@ -1,5 +1,5 @@
import { isNonEmptyString } from '@sniptt/guards';
import { useRef } from 'react';
import { Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { IconComponent, IconPlus } from 'twenty-ui';
@@ -158,16 +158,15 @@ export const SingleEntitySelectMenuItems = ({
switch (entity.id) {
case 'add-new': {
return (
<>
<Fragment key={entity.id}>
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
<CreateNewButton
key={entity.id}
onClick={onCreate}
LeftIcon={IconPlus}
text="Add New"
hovered={isSelectedAddNewButton}
/>
</>
</Fragment>
);
}
case 'select-none': {

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type RelationPickerScopeInternalContextProps = ComponentStateKey;
type RelationPickerScopeInternalContextProps = RecoilComponentStateKey;
export const RelationPickerScopeInternalContext =
createScopeInternalContext<RelationPickerScopeInternalContextProps>();

View File

@@ -44,6 +44,7 @@ export const SettingsAccountsMessageChannelsContainer = () => {
in: accounts.map((account) => account.id),
},
},
skip: !accounts.length,
});
const tabs = [

View File

@@ -1,10 +1,11 @@
import { useCallback, useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { useCallback, useEffect, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useDebouncedCallback } from 'use-debounce';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { TextInput } from '@/ui/input/components/TextInput';
import isEmpty from 'lodash.isempty';
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@@ -40,6 +41,7 @@ export const NameField = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedUpdate = useCallback(
useDebouncedCallback(async (name: string) => {
if (isEmpty(name)) return;
// update local recoil state when workspace name is updated
setCurrentWorkspace((currentValue) => {
if (currentValue === null) {

View File

@@ -4,6 +4,7 @@ import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { ViewType } from '@/views/types/ViewType';
const StyledContainer = styled.div`
@@ -21,28 +22,30 @@ export const SignInBackgroundMockContainer = () => {
return (
<StyledContainer>
<ViewBar
viewBarId={viewBarId}
onCurrentViewChange={async () => {}}
optionsDropdownButton={
<RecordIndexOptionsDropdown
recordIndexId={recordIndexId}
objectNameSingular={objectNameSingular}
viewType={ViewType.Table}
/>
}
/>
<SignInBackgroundMockContainerEffect
objectNamePlural={objectNamePlural}
recordTableId={recordIndexId}
viewId={viewBarId}
/>
<RecordTableWithWrappers
objectNameSingular={objectNameSingular}
recordTableId={recordIndexId}
viewBarId={viewBarId}
updateRecordMutation={() => {}}
/>
<ViewComponentInstanceContext.Provider value={{ instanceId: viewBarId }}>
<ViewBar
viewBarId={viewBarId}
onCurrentViewChange={async () => {}}
optionsDropdownButton={
<RecordIndexOptionsDropdown
recordIndexId={recordIndexId}
objectNameSingular={objectNameSingular}
viewType={ViewType.Table}
/>
}
/>
<SignInBackgroundMockContainerEffect
objectNamePlural={objectNamePlural}
recordTableId={recordIndexId}
viewId={viewBarId}
/>
<RecordTableWithWrappers
objectNameSingular={objectNameSingular}
recordTableId={recordIndexId}
viewBarId={viewBarId}
updateRecordMutation={() => {}}
/>
</ViewComponentInstanceContext.Provider>
</StyledContainer>
);
};

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type DialogManagerScopeInternalContextProps = ComponentStateKey;
type DialogManagerScopeInternalContextProps = RecoilComponentStateKey;
export const DialogManagerScopeInternalContext =
createScopeInternalContext<DialogManagerScopeInternalContextProps>();

View File

@@ -1,7 +1,7 @@
import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { isUndefined } from '@sniptt/guards';
import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react';
import {
IconAlertTriangle,
IconInfoCircle,
@@ -46,7 +46,6 @@ const StyledContainer = styled.div`
box-shadow: ${({ theme }) => theme.boxShadow.strong};
box-sizing: border-box;
cursor: pointer;
height: 61px;
padding: ${({ theme }) => theme.spacing(2)};
position: relative;
width: 296px;
@@ -90,7 +89,6 @@ const StyledDescription = styled.div`
padding-left: ${({ theme }) => theme.spacing(6)};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 200px;
`;

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type SnackBarManagerScopeInternalContextProps = ComponentStateKey;
type SnackBarManagerScopeInternalContextProps = RecoilComponentStateKey;
export const SnackBarManagerScopeInternalContext =
createScopeInternalContext<SnackBarManagerScopeInternalContextProps>();

View File

@@ -8,6 +8,7 @@ import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
import { parsePhoneNumber } from 'libphonenumber-js';
import { isDefined } from '~/utils/isDefined';
import { logError } from '~/utils/logError';
type PhonesDisplayProps = {
value?: FieldPhonesValue;
@@ -39,7 +40,7 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
countryCode: value.primaryPhoneCountryCode,
}
: null,
...(value?.additionalPhones ?? []),
...parseAdditionalPhones(value?.additionalPhones),
]
.filter(isDefined)
.map(({ number, countryCode }) => {
@@ -85,3 +86,23 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
</StyledContainer>
);
};
const parseAdditionalPhones = (additionalPhones?: any) => {
if (!additionalPhones) {
return [];
}
if (typeof additionalPhones === 'object') {
return additionalPhones;
}
if (typeof additionalPhones === 'string') {
try {
return JSON.parse(additionalPhones);
} catch (error) {
logError(`Error parsing additional phones' : ` + error);
}
}
return [];
};

View File

@@ -13,6 +13,8 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined';
import { splitFullName } from '~/utils/format/spiltFullName';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
import { StyledTextInput } from './TextInput';
const StyledContainer = styled.div`
@@ -167,9 +169,12 @@ export const DoubleTextInput = ({
const name = event.clipboardData.getData('Text');
const splittedName = name.split(' ');
const splittedName = splitFullName(name);
onPaste?.({ firstValue: splittedName[0], secondValue: splittedName[1] });
onPaste?.({
firstValue: splittedName[0],
secondValue: splittedName[1],
});
};
const handleClickToPreventParentClickEvents = (
@@ -189,7 +194,10 @@ export const DoubleTextInput = ({
placeholder={firstValuePlaceholder}
value={firstInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleChange(event.target.value, secondInternalValue);
handleChange(
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
secondInternalValue,
);
}}
onPaste={(event: ClipboardEvent<HTMLInputElement>) =>
handleOnPaste(event)
@@ -203,7 +211,10 @@ export const DoubleTextInput = ({
placeholder={secondValuePlaceholder}
value={secondInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleChange(firstInternalValue, event.target.value);
handleChange(
firstInternalValue,
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
);
}}
onClick={handleClickToPreventParentClickEvents}
/>

View File

@@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import styled from '@emotion/styled';
import { TEXT_INPUT_STYLE } from 'twenty-ui';
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
import { isDefined } from '~/utils/isDefined';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
export type TextAreaInputProps = {
disabled?: boolean;
@@ -67,10 +68,12 @@ export const TextAreaInput = ({
copyButton = true,
}: TextAreaInputProps) => {
const [internalText, setInternalText] = useState(value);
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setInternalText(event.target.value);
onChange?.(event.target.value);
const targetValue = turnIntoEmptyStringIfWhitespacesOnly(
event.target.value,
);
setInternalText(targetValue);
onChange?.(targetValue);
};
const wrapperRef = useRef<HTMLTextAreaElement>(null);

View File

@@ -1,5 +1,5 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { TEXT_INPUT_STYLE } from 'twenty-ui';
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
@@ -44,12 +44,11 @@ export const TextInput = ({
const copyRef = useRef<HTMLDivElement>(null);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setInternalText(event.target.value);
onChange?.(event.target.value);
setInternalText(event.target.value.trim());
onChange?.(event.target.value.trim());
};
useEffect(() => {
setInternalText(value);
setInternalText(value.trim());
}, [value]);
useRegisterInputEvents({

View File

@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { FocusEventHandler } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import styled from '@emotion/styled';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
import { InputHotkeyScope } from '../types/InputHotkeyScope';
const MAX_ROWS = 5;
@@ -75,7 +76,9 @@ export const TextArea = ({
maxRows={MAX_ROWS}
minRows={computedMinRows}
value={value}
onChange={(event) => onChange?.(event.target.value)}
onChange={(event) =>
onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value))
}
onFocus={handleFocus}
onBlur={handleBlur}
disabled={disabled}

View File

@@ -11,6 +11,7 @@ import {
} from 'react';
import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
const StyledContainer = styled.div<
Pick<TextInputV2ComponentProps, 'fullWidth'>
@@ -180,7 +181,7 @@ const TextInputV2Component = (
</StyledLeftIconContainer>
)}
<StyledInput
data-testId={dataTestId}
data-testid={dataTestId}
autoComplete={autoComplete || 'off'}
ref={combinedRef}
tabIndex={tabIndex ?? 0}
@@ -188,7 +189,9 @@ const TextInputV2Component = (
onBlur={onBlur}
type={passwordVisible ? 'text' : type}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange?.(event.target.value);
onChange?.(
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
);
}}
onKeyDown={onKeyDown}
{...{

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type DropdownScopeInternalContextProps = ComponentStateKey;
type DropdownScopeInternalContextProps = RecoilComponentStateKey;
export const DropdownScopeInternalContext =
createScopeInternalContext<DropdownScopeInternalContextProps>();

View File

@@ -30,7 +30,6 @@ const StyledTopBarContainer = styled.div<{ width?: number }>`
padding: ${({ theme }) => theme.spacing(2)};
padding-left: 0;
padding-right: ${({ theme }) => theme.spacing(3)};
z-index: 20;
width: ${({ width }) => width + 'px' || '100%'};
@media (max-width: ${MOBILE_VIEWPORT}px) {

View File

@@ -32,7 +32,7 @@ const StyledContainer = styled(motion.div)`
background: ${({ theme }) => theme.background.primary};
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
height: 100%;
height: 100dvh;
overflow-x: hidden;
position: fixed;

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type SelectableListScopeInternalContextProps = ComponentStateKey;
type SelectableListScopeInternalContextProps = RecoilComponentStateKey;
export const SelectableListScopeInternalContext =
createScopeInternalContext<SelectableListScopeInternalContextProps>();

View File

@@ -4,7 +4,7 @@ import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListState
export const useTabList = (tabListId?: string) => {
const { activeTabIdState } = useTabListStates({
tabListScopeId: `${tabListId}-scope`,
tabListScopeId: tabListId,
});
const setActiveTabId = useSetRecoilState(activeTabIdState);

View File

@@ -1,7 +1,7 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type TabListScopeInternalContextProps = ComponentStateKey;
type TabListScopeInternalContextProps = RecoilComponentStateKey;
export const TabListScopeInternalContext =
createScopeInternalContext<TabListScopeInternalContextProps>();

View File

@@ -1,9 +1,9 @@
import { RecoilState, useRecoilState } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
export const useRecoilScopedStateV2 = <StateType>(
recoilState: (scopedKey: ComponentStateKey) => RecoilState<StateType>,
recoilState: (scopedKey: RecoilComponentStateKey) => RecoilState<StateType>,
scopeId: string,
) => {
return useRecoilState<StateType>(

View File

@@ -1,10 +1,11 @@
import { Context, createContext } from 'react';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type ScopeInternalContext<T extends ComponentStateKey> = Context<T | null>;
type ScopeInternalContext<T extends RecoilComponentStateKey> =
Context<T | null>;
export const createScopeInternalContext = <T extends ComponentStateKey>(
export const createScopeInternalContext = <T extends RecoilComponentStateKey>(
initialValue?: T,
) => {
return createContext<T | null>(

View File

@@ -1,7 +1,7 @@
import { RecoilValueReadOnly } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
export type RecoilScopedSelector<StateType> = (
scopedKey: ComponentStateKey,
scopedKey: RecoilComponentStateKey,
) => RecoilValueReadOnly<StateType>;

View File

@@ -1,7 +1,7 @@
import { RecoilState } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
export type RecoilScopedState<StateType> = (
scopedKey: ComponentStateKey,
scopedKey: RecoilComponentStateKey,
) => RecoilState<StateType>;

View File

@@ -1,2 +1,2 @@
export const getScopeIdFromComponentId = (componentId: string) =>
`${componentId}-scope`;
`${componentId}`;

View File

@@ -0,0 +1,24 @@
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
import { isNonEmptyString } from '@sniptt/guards';
export const useAvailableComponentInstanceIdOrThrow = <
T extends { instanceId: string },
>(
Context: ComponentInstanceStateContext<T>,
instanceIdFromProps?: string,
): string => {
const instanceStateContext = useComponentInstanceStateContext(Context);
const instanceIdFromContext = instanceStateContext?.instanceId;
if (isNonEmptyString(instanceIdFromProps)) {
return instanceIdFromProps;
} else if (isNonEmptyString(instanceIdFromContext)) {
return instanceIdFromContext;
} else {
throw new Error(
'Instance id is not provided and cannot be found in context.',
);
}
};

View File

@@ -0,0 +1,12 @@
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
import { useContext } from 'react';
export const useComponentInstanceStateContext = <
T extends { instanceId: string },
>(
Context: ComponentInstanceStateContext<T>,
) => {
const context = useContext(Context);
return context;
};

View File

@@ -0,0 +1,27 @@
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
export const useRecoilCallbackState = <Value>(
componentState: RecoilComponentState<Value>,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentState.key,
);
if (!componentContext) {
throw new Error(
`Component context for key "${componentState.key}" is not defined`,
);
}
const internalScopeId = useAvailableScopeIdOrThrow(
componentContext,
getScopeIdOrUndefinedFromComponentId(componentId),
);
return componentState.atomFamily({
scopeId: internalScopeId,
});
};

View File

@@ -0,0 +1,128 @@
/* eslint-disable no-redeclare */
/* eslint-disable prefer-arrow/prefer-arrow-functions */
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
import { ComponentReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentReadOnlySelectorV2';
import { ComponentSelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentSelectorV2';
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { RecoilState, RecoilValueReadOnly, SerializableParam } from 'recoil';
export function useRecoilComponentCallbackStateV2<ValueType>(
componentState: ComponentStateV2<ValueType>,
instanceIdFromProps?: string,
): RecoilState<ValueType>;
export function useRecoilComponentCallbackStateV2<ValueType>(
componentSelector: ComponentSelectorV2<ValueType>,
instanceIdFromProps?: string,
): RecoilState<ValueType>;
export function useRecoilComponentCallbackStateV2<ValueType>(
componentReadOnlySelector: ComponentReadOnlySelectorV2<ValueType>,
instanceIdFromProps?: string,
): RecoilValueReadOnly<ValueType>;
export function useRecoilComponentCallbackStateV2<
ValueType,
FamilyKey extends SerializableParam,
>(
componentFamilyState: ComponentFamilyStateV2<ValueType, FamilyKey>,
instanceIdFromProps?: string,
): (familyKey: FamilyKey) => RecoilState<ValueType>;
export function useRecoilComponentCallbackStateV2<
ValueType,
FamilyKey extends SerializableParam,
>(
componentFamilySelector: ComponentFamilySelectorV2<ValueType, FamilyKey>,
instanceIdFromProps?: string,
): (familyKey: FamilyKey) => RecoilState<ValueType>;
export function useRecoilComponentCallbackStateV2<
ValueType,
FamilyKey extends SerializableParam,
>(
componentFamilyReadOnlySelector: ComponentFamilyReadOnlySelectorV2<
ValueType,
FamilyKey
>,
instanceIdFromProps?: string,
): (familyKey: FamilyKey) => RecoilValueReadOnly<ValueType>;
export function useRecoilComponentCallbackStateV2<
ValueType,
FamilyKey extends SerializableParam,
>(
componentFamilyState: ComponentFamilyStateV2<ValueType, FamilyKey>,
instanceIdFromProps?: string,
): (familyKey: FamilyKey) => RecoilState<ValueType>;
export function useRecoilComponentCallbackStateV2<
ComponentState extends
| ComponentStateV2<ValueType>
| ComponentSelectorV2<ValueType>
| ComponentReadOnlySelectorV2<ValueType>
| ComponentFamilyStateV2<ValueType, FamilyKey>
| ComponentFamilySelectorV2<ValueType, FamilyKey>
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>,
ValueType,
FamilyKey extends SerializableParam = never,
>(
componentState: ComponentState,
instanceIdFromProps?: string,
):
| RecoilState<ValueType>
| RecoilValueReadOnly<ValueType>
| ((familyKey: FamilyKey) => RecoilState<ValueType>)
| ((familyKey: FamilyKey) => RecoilValueReadOnly<ValueType>) {
const componentStateKey = componentState.key;
const componentInstanceContext =
globalComponentInstanceContextMap.get(componentStateKey);
if (!componentInstanceContext) {
throw new Error(
`Instance context for key "${componentStateKey}" is not defined, check the component state declaration.`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
componentInstanceContext,
instanceIdFromProps,
);
switch (componentState.type) {
case 'ComponentState': {
return componentState.atomFamily({
instanceId,
});
}
case 'ComponentSelector': {
return componentState.selectorFamily({
instanceId,
});
}
case 'ComponentReadOnlySelector': {
return componentState.selectorFamily({
instanceId,
});
}
case 'ComponentFamilyState': {
return (familyKey: FamilyKey) =>
componentState.atomFamily({
instanceId,
familyKey,
});
}
case 'ComponentFamilySelector': {
return (familyKey: FamilyKey) =>
componentState.selectorFamily({
instanceId,
familyKey,
});
}
case 'ComponentFamilyReadOnlySelector': {
return (familyKey: FamilyKey) =>
componentState.selectorFamily({
instanceId,
familyKey,
});
}
}
}

View File

@@ -0,0 +1,52 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { SerializableParam, useRecoilValue } from 'recoil';
export const useRecoilComponentFamilyValueV2 = <
StateType,
FamilyKey extends SerializableParam,
>(
componentStateV2:
| ComponentFamilyStateV2<StateType, FamilyKey>
| ComponentFamilySelectorV2<StateType, FamilyKey>
| ComponentFamilyReadOnlySelectorV2<StateType, FamilyKey>,
familyKey: FamilyKey,
instanceIdFromProps?: string,
): StateType => {
const instanceContext = globalComponentInstanceContextMap.get(
componentStateV2.key,
);
if (!instanceContext) {
throw new Error(
`Instance context for key "${componentStateV2.key}" is not defined`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
instanceContext,
instanceIdFromProps,
);
switch (componentStateV2.type) {
case 'ComponentFamilyState': {
return useRecoilValue(
componentStateV2.atomFamily({ familyKey, instanceId }),
);
}
case 'ComponentFamilySelector': {
return useRecoilValue(
componentStateV2.selectorFamily({ familyKey, instanceId }),
);
}
case 'ComponentFamilyReadOnlySelector': {
return useRecoilValue(
componentStateV2.selectorFamily({ familyKey, instanceId }),
);
}
}
};

View File

@@ -0,0 +1,28 @@
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
import { useRecoilState } from 'recoil';
export const useRecoilComponentState = <StateType>(
componentState: RecoilComponentState<StateType>,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentState.key,
);
if (!componentContext) {
throw new Error(
`Component context for key "${componentState.key}" is not defined`,
);
}
const internalComponentId = useAvailableScopeIdOrThrow(
componentContext,
getScopeIdOrUndefinedFromComponentId(componentId),
);
return useRecoilState(
componentState.atomFamily({ scopeId: internalComponentId }),
);
};

View File

@@ -0,0 +1,26 @@
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { useRecoilState } from 'recoil';
export const useRecoilComponentStateV2 = <StateType>(
componentState: ComponentStateV2<StateType>,
instanceIdFromProps?: string,
) => {
const componentInstanceContext = globalComponentInstanceContextMap.get(
componentState.key,
);
if (!componentInstanceContext) {
throw new Error(
`Instance context for key "${componentState.key}" is not defined`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
componentInstanceContext,
instanceIdFromProps,
);
return useRecoilState(componentState.atomFamily({ instanceId }));
};

View File

@@ -2,10 +2,10 @@ import { useRecoilValue } from 'recoil';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
export const useRecoilComponentValue = <StateType>(
componentState: ComponentState<StateType>,
componentState: RecoilComponentState<StateType>,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(

View File

@@ -0,0 +1,26 @@
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { useRecoilValue } from 'recoil';
export const useRecoilComponentValueV2 = <StateType>(
componentStateV2: ComponentStateV2<StateType>,
instanceIdFromProps?: string,
) => {
const instanceContext = globalComponentInstanceContextMap.get(
componentStateV2.key,
);
if (!instanceContext) {
throw new Error(
`Instance context for key "${componentStateV2.key}" is not defined`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
instanceContext,
instanceIdFromProps,
);
return useRecoilValue(componentStateV2.atomFamily({ instanceId }));
};

View File

@@ -1,9 +1,9 @@
import { useScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContext';
import { ComponentFamilyState } from '@/ui/utilities/state/component-state/types/ComponentFamilyState';
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
export const useScopeIdFromStateContext = (
componentState: ComponentState<any> | ComponentFamilyState<any, any>,
componentState: RecoilComponentState<any> | ComponentFamilyState<any, any>,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentState.key,

View File

@@ -0,0 +1,52 @@
/* eslint-disable react-hooks/rules-of-hooks */
// We're disabling rules-of-hooks because we're sure that the call order cannot be modified
// because a component state cannot change its type during its lifecycle
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { SerializableParam, SetterOrUpdater, useSetRecoilState } from 'recoil';
export const useSetRecoilComponentFamilyStateV2 = <
StateType,
FamilyKey extends SerializableParam,
>(
componentState:
| ComponentFamilyStateV2<StateType, FamilyKey>
| ComponentFamilySelectorV2<StateType, FamilyKey>,
familyKey: FamilyKey,
instanceIdFromProps?: string,
): SetterOrUpdater<StateType> => {
const componentInstanceContext = globalComponentInstanceContextMap.get(
componentState.key,
);
if (!componentInstanceContext) {
throw new Error(
`Instance context for key "${componentState.key}" is not defined`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
componentInstanceContext,
instanceIdFromProps,
);
switch (componentState.type) {
case 'ComponentFamilyState': {
return useSetRecoilState(
componentState.atomFamily({ familyKey, instanceId }),
);
}
case 'ComponentFamilySelector': {
return useSetRecoilState(
componentState.selectorFamily({
familyKey,
instanceId,
}),
);
}
}
};

View File

@@ -2,10 +2,10 @@ import { useSetRecoilState } from 'recoil';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
export const useSetRecoilComponentState = <StateType>(
componentState: ComponentState<StateType>,
componentState: RecoilComponentState<StateType>,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(

View File

@@ -0,0 +1,26 @@
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { SetterOrUpdater, useSetRecoilState } from 'recoil';
export const useSetRecoilComponentStateV2 = <ValueType>(
componentState: ComponentStateV2<ValueType>,
instanceIdFromProps?: string,
): SetterOrUpdater<ValueType> => {
const componentInstanceContext = globalComponentInstanceContextMap.get(
componentState.key,
);
if (!componentInstanceContext) {
throw new Error(
`Instance context for key "${componentState.key}" is not defined`,
);
}
const instanceId = useAvailableComponentInstanceIdOrThrow(
componentInstanceContext,
instanceIdFromProps,
);
return useSetRecoilState(componentState.atomFamily({ instanceId }));
};

View File

@@ -0,0 +1,14 @@
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilValueReadOnly, SerializableParam } from 'recoil';
export type ComponentFamilyReadOnlySelectorV2<
StateType,
FamilyKey extends SerializableParam,
> = {
type: Extract<ComponentStateTypeV2, 'ComponentFamilyReadOnlySelector'>;
key: string;
selectorFamily: (
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
) => RecoilValueReadOnly<StateType>;
};

View File

@@ -0,0 +1,14 @@
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilState, SerializableParam } from 'recoil';
export type ComponentFamilySelectorV2<
StateType,
FamilyKey extends SerializableParam,
> = {
type: Extract<ComponentStateTypeV2, 'ComponentFamilySelector'>;
key: string;
selectorFamily: (
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
) => RecoilState<StateType>;
};

View File

@@ -0,0 +1,7 @@
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
import { SerializableParam } from 'recoil';
export type ComponentFamilyStateKeyV2<FamilyKey extends SerializableParam> =
ComponentStateKeyV2 & {
familyKey: FamilyKey;
};

View File

@@ -0,0 +1,14 @@
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilState, SerializableParam } from 'recoil';
export type ComponentFamilyStateV2<
StateType,
FamilyKey extends SerializableParam,
> = {
type: Extract<ComponentStateTypeV2, 'ComponentFamilyState'>;
key: string;
atomFamily: (
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
) => RecoilState<StateType>;
};

View File

@@ -0,0 +1,4 @@
import { Context } from 'react';
export type ComponentInstanceStateContext<T extends { instanceId: string }> =
Context<T | null>;

View File

@@ -0,0 +1,11 @@
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilValueReadOnly } from 'recoil';
export type ComponentReadOnlySelectorV2<StateType> = {
type: Extract<ComponentStateTypeV2, 'ComponentReadOnlySelector'>;
key: string;
selectorFamily: (
componentStateKey: ComponentStateKeyV2,
) => RecoilValueReadOnly<StateType>;
};

View File

@@ -0,0 +1,11 @@
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilState } from 'recoil';
export type ComponentSelectorV2<StateType> = {
type: Extract<ComponentStateTypeV2, 'ComponentSelector'>;
key: string;
selectorFamily: (
componentStateKey: ComponentStateKeyV2,
) => RecoilState<StateType>;
};

View File

@@ -1,8 +0,0 @@
import { RecoilState } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
export type ComponentState<StateType> = {
key: string;
atomFamily: (componentStateKey: ComponentStateKey) => RecoilState<StateType>;
};

View File

@@ -1,3 +0,0 @@
export type ComponentStateKey = {
scopeId: string;
};

View File

@@ -0,0 +1,3 @@
export type ComponentStateKeyV2 = {
instanceId: string;
};

View File

@@ -0,0 +1,7 @@
export type ComponentStateTypeV2 =
| 'ComponentState'
| 'ComponentFamilyState'
| 'ComponentSelector'
| 'ComponentReadOnlySelector'
| 'ComponentFamilySelector'
| 'ComponentFamilyReadOnlySelector';

View File

@@ -0,0 +1,11 @@
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
import { RecoilState } from 'recoil';
export type ComponentStateV2<StateType> = {
type: Extract<ComponentStateTypeV2, 'ComponentState'>;
key: string;
atomFamily: (
componentStateKey: ComponentStateKeyV2,
) => RecoilState<StateType>;
};

View File

@@ -0,0 +1,9 @@
import { RecoilState } from 'recoil';
import { RecoilComponentStateKey } from './RecoilComponentStateKey';
export type RecoilComponentState<StateType> = {
key: string;
atomFamily: (
componentStateKey: RecoilComponentStateKey,
) => RecoilState<StateType>;
};

View File

@@ -0,0 +1,3 @@
export type RecoilComponentStateKey = {
scopeId: string;
};

View File

@@ -0,0 +1,58 @@
import { selectorFamily, SerializableParam } from 'recoil';
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
import { SelectorSetter } from '@/ui/utilities/state/types/SelectorSetter';
import { isDefined } from 'twenty-ui';
export const createComponentFamilySelectorV2 = <
ValueType,
FamilyKey extends SerializableParam,
>({
key,
get,
set,
componentInstanceContext,
}: {
key: string;
get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
set?: SelectorSetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
componentInstanceContext: ComponentInstanceStateContext<any> | null;
}):
| ComponentFamilySelectorV2<ValueType, FamilyKey>
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey> => {
if (isDefined(componentInstanceContext)) {
globalComponentInstanceContextMap.set(key, componentInstanceContext);
}
if (isDefined(set)) {
return {
type: 'ComponentFamilySelector',
key,
selectorFamily: selectorFamily<
ValueType,
ComponentFamilyStateKeyV2<FamilyKey>
>({
key,
get,
set,
}),
} satisfies ComponentFamilySelectorV2<ValueType, FamilyKey>;
} else {
return {
type: 'ComponentFamilyReadOnlySelector',
key,
selectorFamily: selectorFamily<
ValueType,
ComponentFamilyStateKeyV2<FamilyKey>
>({
key,
get,
}),
} satisfies ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>;
}
};

View File

@@ -1,13 +1,15 @@
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
import { ScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopeInternalContext';
import { ComponentFamilyStateKey } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKey';
import { isDefined } from 'twenty-ui';
type CreateComponentFamilyStateV2Type<ValueType> = {
type CreateComponentFamilyStateArgs<ValueType> = {
key: string;
defaultValue: ValueType;
componentContext: ScopeInternalContext<any> | null;
componentInstanceContext: ComponentInstanceStateContext<any> | null;
effects?: AtomEffect<ValueType>[];
};
@@ -18,22 +20,22 @@ export const createComponentFamilyStateV2 = <
key,
effects,
defaultValue,
componentContext,
}: CreateComponentFamilyStateV2Type<ValueType>) => {
if (isDefined(componentContext)) {
if (!isDefined((window as any).componentContextStateMap)) {
(window as any).componentContextStateMap = new Map();
}
(window as any).componentContextStateMap.set(key, componentContext);
componentInstanceContext,
}: CreateComponentFamilyStateArgs<ValueType>): ComponentFamilyStateV2<
ValueType,
FamilyKey
> => {
if (isDefined(componentInstanceContext)) {
globalComponentInstanceContextMap.set(key, componentInstanceContext);
}
return {
type: 'ComponentFamilyState',
key,
atomFamily: atomFamily<ValueType, ComponentFamilyStateKey<FamilyKey>>({
atomFamily: atomFamily<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>({
key,
default: defaultValue,
effects,
}),
};
} satisfies ComponentFamilyStateV2<ValueType, FamilyKey>;
};

View File

@@ -0,0 +1,13 @@
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
import { createContext } from 'react';
export const createComponentInstanceContext = <
T extends ComponentStateKeyV2 = ComponentStateKeyV2,
>(
initialValue?: T,
) => {
return createContext<T | null>(
initialValue ?? null,
) as ComponentInstanceStateContext<T>;
};

View File

@@ -1,6 +1,6 @@
import { selectorFamily } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
export const createComponentReadOnlySelector = <ValueType>({
@@ -8,9 +8,9 @@ export const createComponentReadOnlySelector = <ValueType>({
get,
}: {
key: string;
get: SelectorGetter<ValueType, ComponentStateKey>;
get: SelectorGetter<ValueType, RecoilComponentStateKey>;
}) => {
return selectorFamily<ValueType, ComponentStateKey>({
return selectorFamily<ValueType, RecoilComponentStateKey>({
key,
get,
});

View File

@@ -1,6 +1,6 @@
import { selectorFamily } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
import { SelectorSetter } from '@/ui/utilities/state/types/SelectorSetter';
@@ -10,10 +10,10 @@ export const createComponentSelector = <ValueType>({
set,
}: {
key: string;
get: SelectorGetter<ValueType, ComponentStateKey>;
set: SelectorSetter<ValueType, ComponentStateKey>;
get: SelectorGetter<ValueType, RecoilComponentStateKey>;
set: SelectorSetter<ValueType, RecoilComponentStateKey>;
}) => {
return selectorFamily<ValueType, ComponentStateKey>({
return selectorFamily<ValueType, RecoilComponentStateKey>({
key,
get,
set,

Some files were not shown because too many files have changed in this diff Show More