Files
twenty/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx
T
Raphaël Bosi c596a5e342 Rename twenty-ui to twenty-ui-deprecated and twenty-new-ui to twenty-ui to prepare package release (#21315)
## Description

Promotes the next-gen UI library (formerly `twenty-new-ui`) to the name
**`twenty-ui`** (v0.1.0, publishable) and renames the old package to
**`twenty-ui-deprecated`**. Rewrites ~1,730 `twenty-ui` imports →
`twenty-ui-deprecated`, updates all configs/CI/Docker/deps, and migrates
twenty-front's `Toggle` to the new package (first consumer) as a
drop-in.

## Next steps
- Wire the `ui/v*` publish dispatch (`cd-deploy-tag.yaml` +
`.yarnrc.yml`), then tag `ui/v0.1.0` to publish.
- Continue migrating components from `twenty-ui-deprecated` →
`twenty-ui`.
2026-06-08 18:12:28 +02:00

292 lines
11 KiB
TypeScript

import { styled } from '@linaria/react';
import { type ReactNode, useMemo } from 'react';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { AdvancedFilterDropdownButton } from '@/views/advanced-filter-chip/components/AdvancedFilterDropdownButton';
import { ViewBarDetailsAddFilterButton } from '@/views/components/ViewBarDetailsAddFilterButton';
import { EditableSortChip } from '@/views/editable-chip/components/EditableSortChip';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { SoftDeleteFilterChip } from '@/views/components/SoftDeleteFilterChip';
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters';
import { useApplyCurrentViewSortsToCurrentRecordSorts } from '@/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts';
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { AnyFieldSearchDropdownButton } from '@/views/components/AnyFieldSearchDropdownButton';
import { ANY_FIELD_SEARCH_DROPDOWN_ID } from '@/views/constants/AnyFieldSearchDropdownId';
import { EditableFilterDropdownButton } from '@/views/editable-chip/components/EditableFilterDropdownButton';
import { getEditableChipObjectFilterDropdownComponentInstanceId } from '@/views/editable-chip/utils/getEditableChipObjectFilterDropdownComponentInstanceId';
import { useHasFiltersInQueryParams } from '@/views/hooks/internal/useHasFiltersInQueryParams';
import { useApplyCurrentViewAnyFieldFilterToAnyFieldFilter } from '@/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter';
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
import { useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } from '@/views/hooks/useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter';
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
import { t } from '@lingui/core/macro';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { LightButton } from 'twenty-ui-deprecated/input';
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
export type ViewBarDetailsProps = {
hasFilterButton?: boolean;
rightComponent?: ReactNode;
viewBarId: string;
objectNamePlural: string;
};
const StyledBar = styled.div`
align-items: center;
border-top: 1px solid ${themeCssVariables.border.color.light};
display: flex;
flex-direction: row;
justify-content: space-between;
min-height: 32px;
padding-bottom: ${themeCssVariables.spacing[1]};
padding-top: ${themeCssVariables.spacing[1]};
z-index: 4;
`;
const StyledChipContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[2]};
z-index: 1;
`;
const StyledActionButtonContainer = styled.div`
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[2]};
`;
const StyledFilterContainer = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[1]};
overflow-x: hidden;
`;
const StyledSeparatorContainer = styled.div`
align-items: flex-start;
align-self: stretch;
display: flex;
padding-bottom: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[1]};
padding-right: ${themeCssVariables.spacing[1]};
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledSeparator = styled.div`
align-self: stretch;
background: ${themeCssVariables.background.quaternary};
width: 1px;
`;
const StyledAddFilterContainer = styled.div`
z-index: 5;
`;
export const ViewBarDetails = ({
hasFilterButton = false,
rightComponent,
viewBarId,
objectNamePlural,
}: ViewBarDetailsProps) => {
const isViewBarExpanded = useAtomComponentStateValue(
isViewBarExpandedComponentState,
);
const { hasFiltersQueryParams } = useHasFiltersInQueryParams();
const currentRecordFilterGroups = useAtomComponentStateValue(
currentRecordFilterGroupsComponentState,
viewBarId,
);
const currentRecordFilters = useAtomComponentStateValue(
currentRecordFiltersComponentState,
viewBarId,
);
const currentRecordSorts = useAtomComponentStateValue(
currentRecordSortsComponentState,
viewBarId,
);
const anyFieldFilterValue = useAtomComponentStateValue(
anyFieldFilterValueComponentState,
viewBarId,
);
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural: objectNamePlural,
});
const { toggleSoftDeleteFilterState } = useHandleToggleTrashColumnFilter({
objectNameSingular: objectNameSingular,
viewBarId: viewBarId,
});
const { viewFilterGroupsAreDifferentFromRecordFilterGroups } =
useAreViewFilterGroupsDifferentFromRecordFilterGroups();
const { viewFiltersAreDifferentFromRecordFilters } =
useAreViewFiltersDifferentFromRecordFilters();
const { viewSortsAreDifferentFromRecordSorts } =
useAreViewSortsDifferentFromRecordSorts();
const { viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } =
useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter();
const { isSeeDeletedRecordsFilter } = useCheckIsSoftDeleteFilter();
const allSoftDeletedRecordsFilter = currentRecordFilters.find(
(recordFilter) => isSeeDeletedRecordsFilter(recordFilter),
);
const recordFilters = useMemo(() => {
return currentRecordFilters.filter(
(recordFilter) =>
!recordFilter.recordFilterGroupId &&
!isSeeDeletedRecordsFilter(recordFilter),
);
}, [currentRecordFilters, isSeeDeletedRecordsFilter]);
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
const { applyCurrentViewAnyFieldFilterToAnyFieldFilter } =
useApplyCurrentViewAnyFieldFilterToAnyFieldFilter();
const { applyCurrentViewSortsToCurrentRecordSorts } =
useApplyCurrentViewSortsToCurrentRecordSorts();
const handleCancelClick = () => {
applyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
applyCurrentViewFiltersToCurrentRecordFilters();
applyCurrentViewSortsToCurrentRecordSorts();
applyCurrentViewAnyFieldFilterToAnyFieldFilter();
toggleSoftDeleteFilterState(false);
};
const shouldShowAdvancedFilterDropdownButton =
currentRecordFilterGroups.length > 0;
const isDropdownOpen = useAtomComponentStateValue(
isDropdownOpenComponentState,
ANY_FIELD_SEARCH_DROPDOWN_ID,
);
const canResetView =
(viewFiltersAreDifferentFromRecordFilters ||
viewSortsAreDifferentFromRecordSorts ||
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter) &&
!hasFiltersQueryParams;
const shouldShowAnyFieldSearchChip =
isNonEmptyString(anyFieldFilterValue) || isDropdownOpen;
const shouldExpandViewBar =
shouldShowAnyFieldSearchChip ||
viewFiltersAreDifferentFromRecordFilters ||
viewSortsAreDifferentFromRecordSorts ||
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter ||
((currentRecordSorts.length > 0 ||
currentRecordFilters.length > 0 ||
currentRecordFilterGroups.length > 0) &&
isViewBarExpanded);
if (!shouldExpandViewBar) {
return null;
}
return (
<StyledBar>
<StyledFilterContainer>
<ScrollWrapper
componentInstanceId={viewBarId}
defaultEnableYScroll={false}
>
<StyledChipContainer>
{isDefined(allSoftDeletedRecordsFilter) && (
<SoftDeleteFilterChip
key={allSoftDeletedRecordsFilter.fieldMetadataId}
recordFilter={allSoftDeletedRecordsFilter}
viewBarId={viewBarId}
/>
)}
{isDefined(allSoftDeletedRecordsFilter) && (
<StyledSeparatorContainer>
<StyledSeparator />
</StyledSeparatorContainer>
)}
{currentRecordSorts.map((recordSort) => (
<EditableSortChip
key={recordSort.fieldMetadataId}
recordSort={recordSort}
/>
))}
{isNonEmptyArray(recordFilters) &&
isNonEmptyArray(currentRecordSorts) && (
<StyledSeparatorContainer>
<StyledSeparator />
</StyledSeparatorContainer>
)}
{shouldShowAnyFieldSearchChip && <AnyFieldSearchDropdownButton />}
{shouldShowAdvancedFilterDropdownButton && (
<AdvancedFilterDropdownButton />
)}
{recordFilters.map((recordFilter) => (
<ObjectFilterDropdownComponentInstanceContext.Provider
key={recordFilter.id}
value={{
instanceId:
getEditableChipObjectFilterDropdownComponentInstanceId({
recordFilterId: recordFilter.id,
}),
}}
>
<EditableFilterDropdownButton recordFilter={recordFilter} />
</ObjectFilterDropdownComponentInstanceContext.Provider>
))}
</StyledChipContainer>
</ScrollWrapper>
{hasFilterButton && (
<StyledAddFilterContainer>
<ViewBarDetailsAddFilterButton />
</StyledAddFilterContainer>
)}
</StyledFilterContainer>
<StyledActionButtonContainer>
{canResetView && (
<LightButton
data-testid="cancel-button"
accent="tertiary"
title={t`Reset`}
onClick={handleCancelClick}
/>
)}
{rightComponent}
</StyledActionButtonContainer>
</StyledBar>
);
};