aa4aea0f9b
Summary
Fixed the dark-mode regression in the layout customization banner at
packages/twenty-front/src/modules/layout-customization/components/LayoutCustomizationBar.tsx:
- The banner's background is themeCssVariables.color.blue
(theme-independent), so its text color must be theme-independent too.
- Replaced color: ${themeCssVariables.font.color.inverted} (which
resolves to near-black in dark mode) with color:
${GRAY_SCALE_LIGHT.gray1} (always white), matching the convention used
in AnimatedButton.tsx for text on colored backgrounds.
## Before
<img width="1508" height="185" alt="Screenshot 2026-04-22 at 19 50 03"
src="https://github.com/user-attachments/assets/b0a95fc2-05d5-4207-9d72-64a83daf94ae"
/>
## After
<img width="1511" height="190" alt="Screenshot 2026-04-22 at 19 49 39"
src="https://github.com/user-attachments/assets/9ce33353-412f-4db2-9322-a6f293f6f1b4"
/>
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
import { styled } from '@linaria/react';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { useAtomValue } from 'jotai';
|
|
import { useContext } from 'react';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { IconCheck, IconPaint } from 'twenty-ui/display';
|
|
import { GRAY_SCALE_LIGHT } from 'twenty-ui/theme';
|
|
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
|
|
|
import { LayoutCustomizationBarMenuDropdown } from '@/layout-customization/components/LayoutCustomizationBarMenuDropdown';
|
|
import { LayoutCustomizationBarResetConfirmationModal } from '@/layout-customization/components/LayoutCustomizationBarResetConfirmationModal';
|
|
import { useCancelLayoutCustomization } from '@/layout-customization/hooks/useCancelLayoutCustomization';
|
|
import { useIsLayoutCustomizationDirty } from '@/layout-customization/hooks/useIsLayoutCustomizationDirty';
|
|
import { useSaveLayoutCustomization } from '@/layout-customization/hooks/useSaveLayoutCustomization';
|
|
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
|
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
|
import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
|
|
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { PageLayoutType } from '~/generated-metadata/graphql';
|
|
|
|
const StyledContainer = styled.div`
|
|
align-items: center;
|
|
background: ${themeCssVariables.color.blue};
|
|
box-sizing: border-box;
|
|
color: ${GRAY_SCALE_LIGHT.gray1};
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
|
|
width: 100%;
|
|
|
|
button,
|
|
button * {
|
|
color: ${GRAY_SCALE_LIGHT.gray1};
|
|
}
|
|
|
|
button[type='submit']:not(:disabled):not(:focus) {
|
|
border-color: color(display-p3 1 1 1 / 0.5);
|
|
}
|
|
`;
|
|
|
|
const StyledLeftSection = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex: 1;
|
|
gap: ${themeCssVariables.spacing[2]};
|
|
`;
|
|
|
|
const StyledTitle = styled.span`
|
|
align-items: center;
|
|
display: flex;
|
|
flex: 1;
|
|
gap: ${themeCssVariables.spacing[2]};
|
|
justify-content: center;
|
|
text-align: center;
|
|
`;
|
|
|
|
const StyledRightSection = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex: 1;
|
|
justify-content: flex-end;
|
|
`;
|
|
|
|
const LayoutCustomizationBarContent = () => {
|
|
const { theme } = useContext(ThemeContext);
|
|
const { t } = useLingui();
|
|
|
|
const { save, isSaving } = useSaveLayoutCustomization();
|
|
const { cancel } = useCancelLayoutCustomization();
|
|
const { isDirty } = useIsLayoutCustomizationDirty();
|
|
|
|
const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState);
|
|
const persistedPageLayout = useAtomValue(
|
|
pageLayoutPersistedComponentState.atomFamily({
|
|
instanceId: currentPageLayoutId ?? '',
|
|
}),
|
|
);
|
|
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
|
|
|
const recordPageLayoutObject =
|
|
isDefined(currentPageLayoutId) &&
|
|
persistedPageLayout?.type === PageLayoutType.RECORD_PAGE
|
|
? objectMetadataItems.find(
|
|
(item) => item.id === persistedPageLayout.objectMetadataId,
|
|
)
|
|
: undefined;
|
|
|
|
const title = isDefined(recordPageLayoutObject)
|
|
? t`${recordPageLayoutObject.labelPlural} layout edition`
|
|
: t`Layout customization`;
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
transition={{
|
|
duration: theme.animation.duration.normal,
|
|
ease: 'easeInOut',
|
|
}}
|
|
>
|
|
<StyledContainer data-globally-prevent-click-outside="true">
|
|
<StyledLeftSection>
|
|
{isDefined(recordPageLayoutObject) && (
|
|
<LayoutCustomizationBarMenuDropdown />
|
|
)}
|
|
</StyledLeftSection>
|
|
<StyledTitle>
|
|
<IconPaint size={theme.icon.size.md} />
|
|
{title}
|
|
</StyledTitle>
|
|
<StyledRightSection>
|
|
<SaveAndCancelButtons
|
|
onSave={save}
|
|
onCancel={cancel}
|
|
isSaveDisabled={!isDirty || isSaving}
|
|
isCancelDisabled={isSaving}
|
|
isLoading={isSaving}
|
|
inverted
|
|
saveIcon={IconCheck}
|
|
/>
|
|
</StyledRightSection>
|
|
</StyledContainer>
|
|
{isDefined(recordPageLayoutObject) && isDefined(currentPageLayoutId) && (
|
|
<LayoutCustomizationBarResetConfirmationModal
|
|
pageLayoutId={currentPageLayoutId}
|
|
/>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export const LayoutCustomizationBar = () => {
|
|
const isLayoutCustomizationModeEnabled = useAtomStateValue(
|
|
isLayoutCustomizationModeEnabledState,
|
|
);
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isLayoutCustomizationModeEnabled && <LayoutCustomizationBarContent />}
|
|
</AnimatePresence>
|
|
);
|
|
};
|