Make token update synchronous on FE (#11486)
1. Removing tokenPair internal variable of ApolloFactory. We will relay on cookieStorage 2. setting the cookie explicitely instead of only relaying on recoil cookieEffect which is too late
This commit is contained in:
@ -127,7 +127,7 @@ export const CalendarEventDetails = ({
|
|||||||
size={ChipSize.Large}
|
size={ChipSize.Large}
|
||||||
variant={ChipVariant.Highlighted}
|
variant={ChipVariant.Highlighted}
|
||||||
clickable={false}
|
clickable={false}
|
||||||
leftComponent={() => <IconCalendarEvent size={theme.icon.size.md} />}
|
leftComponent={<IconCalendarEvent size={theme.icon.size.md} />}
|
||||||
label="Event"
|
label="Event"
|
||||||
/>
|
/>
|
||||||
<StyledHeader>
|
<StyledHeader>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { MessageThreadSubscriber } from '@/activities/emails/types/MessageThreadSubscriber';
|
import { MessageThreadSubscriber } from '@/activities/emails/types/MessageThreadSubscriber';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Avatar, AvatarGroup, IconChevronDown } from 'twenty-ui/display';
|
|
||||||
import { Chip, ChipVariant } from 'twenty-ui/components';
|
import { Chip, ChipVariant } from 'twenty-ui/components';
|
||||||
|
import { Avatar, AvatarGroup, IconChevronDown } from 'twenty-ui/display';
|
||||||
import { ThemeContext } from 'twenty-ui/theme';
|
import { ThemeContext } from 'twenty-ui/theme';
|
||||||
|
|
||||||
const MAX_NUMBER_OF_AVATARS = 3;
|
const MAX_NUMBER_OF_AVATARS = 3;
|
||||||
@ -46,9 +46,8 @@ export const MessageThreadSubscribersChip = ({
|
|||||||
<Chip
|
<Chip
|
||||||
label={label}
|
label={label}
|
||||||
variant={ChipVariant.Highlighted}
|
variant={ChipVariant.Highlighted}
|
||||||
leftComponent={() => {
|
leftComponent={
|
||||||
if (isOnlyOneSubscriber) {
|
isOnlyOneSubscriber ? (
|
||||||
return (
|
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={firstAvatarUrl}
|
avatarUrl={firstAvatarUrl}
|
||||||
placeholderColorSeed={firstAvatarColorSeed}
|
placeholderColorSeed={firstAvatarColorSeed}
|
||||||
@ -56,10 +55,7 @@ export const MessageThreadSubscribersChip = ({
|
|||||||
size="md"
|
size="md"
|
||||||
type={'rounded'}
|
type={'rounded'}
|
||||||
/>
|
/>
|
||||||
);
|
) : (
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
avatars={subscriberNames.map((name, index) => (
|
avatars={subscriberNames.map((name, index) => (
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -70,8 +66,8 @@ export const MessageThreadSubscribersChip = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}}
|
}
|
||||||
rightComponent={() => <IconChevronDown size={theme.icon.size.sm} />}
|
rightComponent={() => <IconChevronDown size={theme.icon.size.sm} />}
|
||||||
clickable
|
clickable
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
|||||||
|
|
||||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ApolloFactory, Options } from '../services/apollo.factory';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ApolloFactory, Options } from '../services/apollo.factory';
|
||||||
|
|
||||||
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||||
// eslint-disable-next-line @nx/workspace-no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
@ -26,7 +26,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isMatchingLocation } = useIsMatchingLocation();
|
const { isMatchingLocation } = useIsMatchingLocation();
|
||||||
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
currentWorkspaceState,
|
currentWorkspaceState,
|
||||||
);
|
);
|
||||||
@ -62,8 +62,6 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
connectToDevTools: isDebugMode,
|
connectToDevTools: isDebugMode,
|
||||||
// We don't want to re-create the client on token change or it will cause infinite loop
|
|
||||||
initialTokenPair: tokenPair,
|
|
||||||
currentWorkspaceMember: currentWorkspaceMember,
|
currentWorkspaceMember: currentWorkspaceMember,
|
||||||
onTokenPairChange: (tokenPair) => {
|
onTokenPairChange: (tokenPair) => {
|
||||||
setTokenPair(tokenPair);
|
setTokenPair(tokenPair);
|
||||||
@ -104,12 +102,6 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
|||||||
setPreviousUrl,
|
setPreviousUrl,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
|
||||||
if (isDefined(apolloRef.current)) {
|
|
||||||
apolloRef.current.updateTokenPair(tokenPair);
|
|
||||||
}
|
|
||||||
}, [tokenPair]);
|
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
if (isDefined(apolloRef.current)) {
|
if (isDefined(apolloRef.current)) {
|
||||||
apolloRef.current.updateWorkspaceMember(currentWorkspaceMember);
|
apolloRef.current.updateWorkspaceMember(currentWorkspaceMember);
|
||||||
|
|||||||
@ -33,10 +33,6 @@ const mockWorkspaceMember = {
|
|||||||
|
|
||||||
const createMockOptions = (): Options<any> => ({
|
const createMockOptions = (): Options<any> => ({
|
||||||
uri: 'http://localhost:3000',
|
uri: 'http://localhost:3000',
|
||||||
initialTokenPair: {
|
|
||||||
accessToken: { token: 'mockAccessToken', expiresAt: '' },
|
|
||||||
refreshToken: { token: 'mockRefreshToken', expiresAt: '' },
|
|
||||||
},
|
|
||||||
currentWorkspaceMember: mockWorkspaceMember,
|
currentWorkspaceMember: mockWorkspaceMember,
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
isDebugMode: true,
|
isDebugMode: true,
|
||||||
|
|||||||
@ -18,9 +18,11 @@ import { logDebug } from '~/utils/logDebug';
|
|||||||
|
|
||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
import { GraphQLFormattedError } from 'graphql';
|
import { GraphQLFormattedError } from 'graphql';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { cookieStorage } from '~/utils/cookie-storage';
|
||||||
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
import { ApolloManager } from '../types/apolloManager.interface';
|
import { ApolloManager } from '../types/apolloManager.interface';
|
||||||
import { loggerLink } from '../utils/loggerLink';
|
import { loggerLink } from '../utils/loggerLink';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
const logger = loggerLink(() => 'Twenty');
|
const logger = loggerLink(() => 'Twenty');
|
||||||
|
|
||||||
@ -29,7 +31,6 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
|
|||||||
onNetworkError?: (err: Error | ServerParseError | ServerError) => void;
|
onNetworkError?: (err: Error | ServerParseError | ServerError) => void;
|
||||||
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
|
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
|
||||||
onUnauthenticatedError?: () => void;
|
onUnauthenticatedError?: () => void;
|
||||||
initialTokenPair: AuthTokenPair | null;
|
|
||||||
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
||||||
extraLinks?: ApolloLink[];
|
extraLinks?: ApolloLink[];
|
||||||
isDebugMode?: boolean;
|
isDebugMode?: boolean;
|
||||||
@ -37,7 +38,6 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
|
|||||||
|
|
||||||
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||||
private client: ApolloClient<TCacheShape>;
|
private client: ApolloClient<TCacheShape>;
|
||||||
private tokenPair: AuthTokenPair | null = null;
|
|
||||||
private currentWorkspaceMember: CurrentWorkspaceMember | null = null;
|
private currentWorkspaceMember: CurrentWorkspaceMember | null = null;
|
||||||
|
|
||||||
constructor(opts: Options<TCacheShape>) {
|
constructor(opts: Options<TCacheShape>) {
|
||||||
@ -47,28 +47,45 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
|||||||
onNetworkError,
|
onNetworkError,
|
||||||
onTokenPairChange,
|
onTokenPairChange,
|
||||||
onUnauthenticatedError,
|
onUnauthenticatedError,
|
||||||
initialTokenPair,
|
|
||||||
currentWorkspaceMember,
|
currentWorkspaceMember,
|
||||||
extraLinks,
|
extraLinks,
|
||||||
isDebugMode,
|
isDebugMode,
|
||||||
...options
|
...options
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
this.tokenPair = initialTokenPair;
|
|
||||||
this.currentWorkspaceMember = currentWorkspaceMember;
|
this.currentWorkspaceMember = currentWorkspaceMember;
|
||||||
|
|
||||||
|
const getTokenPair = () => {
|
||||||
|
const stringTokenPair = cookieStorage.getItem('tokenPair');
|
||||||
|
const tokenPair = isDefined(stringTokenPair)
|
||||||
|
? (JSON.parse(stringTokenPair) as AuthTokenPair)
|
||||||
|
: undefined;
|
||||||
|
return tokenPair;
|
||||||
|
};
|
||||||
|
|
||||||
const buildApolloLink = (): ApolloLink => {
|
const buildApolloLink = (): ApolloLink => {
|
||||||
const httpLink = createUploadLink({
|
const httpLink = createUploadLink({
|
||||||
uri,
|
uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
const authLink = setContext(async (_, { headers }) => {
|
const authLink = setContext(async (_, { headers }) => {
|
||||||
|
const tokenPair = getTokenPair();
|
||||||
|
|
||||||
|
if (isUndefinedOrNull(tokenPair)) {
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
...options.headers,
|
...options.headers,
|
||||||
authorization: this.tokenPair?.accessToken.token
|
},
|
||||||
? `Bearer ${this.tokenPair?.accessToken.token}`
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
...options.headers,
|
||||||
|
authorization: tokenPair.accessToken.token
|
||||||
|
? `Bearer ${tokenPair.accessToken.token}`
|
||||||
: '',
|
: '',
|
||||||
...(this.currentWorkspaceMember?.locale
|
...(this.currentWorkspaceMember?.locale
|
||||||
? { 'x-locale': this.currentWorkspaceMember.locale }
|
? { 'x-locale': this.currentWorkspaceMember.locale }
|
||||||
@ -93,7 +110,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
|||||||
for (const graphQLError of graphQLErrors) {
|
for (const graphQLError of graphQLErrors) {
|
||||||
if (graphQLError.message === 'Unauthorized') {
|
if (graphQLError.message === 'Unauthorized') {
|
||||||
return fromPromise(
|
return fromPromise(
|
||||||
renewToken(uri, this.tokenPair)
|
renewToken(uri, getTokenPair())
|
||||||
.then((tokens) => {
|
.then((tokens) => {
|
||||||
if (isDefined(tokens)) {
|
if (isDefined(tokens)) {
|
||||||
onTokenPairChange?.(tokens);
|
onTokenPairChange?.(tokens);
|
||||||
@ -108,10 +125,14 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
|||||||
switch (graphQLError?.extensions?.code) {
|
switch (graphQLError?.extensions?.code) {
|
||||||
case 'UNAUTHENTICATED': {
|
case 'UNAUTHENTICATED': {
|
||||||
return fromPromise(
|
return fromPromise(
|
||||||
renewToken(uri, this.tokenPair)
|
renewToken(uri, getTokenPair())
|
||||||
.then((tokens) => {
|
.then((tokens) => {
|
||||||
if (isDefined(tokens)) {
|
if (isDefined(tokens)) {
|
||||||
onTokenPairChange?.(tokens);
|
onTokenPairChange?.(tokens);
|
||||||
|
cookieStorage.setItem(
|
||||||
|
'tokenPair',
|
||||||
|
JSON.stringify(tokens),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -162,10 +183,6 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTokenPair(tokenPair: AuthTokenPair | null) {
|
|
||||||
this.tokenPair = tokenPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null) {
|
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null) {
|
||||||
this.currentWorkspaceMember = workspaceMember;
|
this.currentWorkspaceMember = workspaceMember;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { ApolloClient } from '@apollo/client';
|
import { ApolloClient } from '@apollo/client';
|
||||||
|
|
||||||
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
|
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { AuthTokenPair } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export interface ApolloManager<TCacheShape> {
|
export interface ApolloManager<TCacheShape> {
|
||||||
getClient(): ApolloClient<TCacheShape>;
|
getClient(): ApolloClient<TCacheShape>;
|
||||||
updateTokenPair(tokenPair: AuthTokenPair | null): void;
|
|
||||||
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null): void;
|
updateWorkspaceMember(workspaceMember: CurrentWorkspaceMember | null): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,6 +63,7 @@ import { useSearchParams } from 'react-router-dom';
|
|||||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { iconsState } from 'twenty-ui/display';
|
import { iconsState } from 'twenty-ui/display';
|
||||||
|
import { cookieStorage } from '~/utils/cookie-storage';
|
||||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||||
|
|
||||||
@ -348,6 +349,12 @@ export const useAuth = () => {
|
|||||||
setTokenPair(
|
setTokenPair(
|
||||||
getAuthTokensResult.data?.getAuthTokensFromLoginToken.tokens,
|
getAuthTokensResult.data?.getAuthTokensFromLoginToken.tokens,
|
||||||
);
|
);
|
||||||
|
cookieStorage.setItem(
|
||||||
|
'tokenPair',
|
||||||
|
JSON.stringify(
|
||||||
|
getAuthTokensResult.data?.getAuthTokensFromLoginToken.tokens,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await refreshObjectMetadataItems();
|
await refreshObjectMetadataItems();
|
||||||
await loadCurrentUser();
|
await loadCurrentUser();
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField';
|
import { useEmailsFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useEmailsFieldDisplay';
|
||||||
import { EmailsDisplay } from '@/ui/field/display/components/EmailsDisplay';
|
import { EmailsDisplay } from '@/ui/field/display/components/EmailsDisplay';
|
||||||
|
|
||||||
export const EmailsFieldDisplay = () => {
|
export const EmailsFieldDisplay = () => {
|
||||||
const { fieldValue } = useEmailsField();
|
const { fieldValue } = useEmailsFieldDisplay();
|
||||||
|
|
||||||
return <EmailsDisplay value={fieldValue} />;
|
return <EmailsDisplay value={fieldValue} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,9 +2,7 @@ import { Theme, withTheme } from '@emotion/react';
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { Ref } from 'react';
|
import { Ref } from 'react';
|
||||||
|
|
||||||
const StyledOuterContainer = styled.div<{
|
const StyledOuterContainer = styled.div`
|
||||||
hasSoftFocus?: boolean;
|
|
||||||
}>`
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -50,7 +48,6 @@ export const RecordTableCellDisplayContainer = ({
|
|||||||
}
|
}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
hasSoftFocus={softFocus}
|
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
>
|
>
|
||||||
{placeholderForEmptyCell ? (
|
{placeholderForEmptyCell ? (
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
@ -9,6 +7,7 @@ import { isTableCellInEditModeComponentFamilyState } from '@/object-record/recor
|
|||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export const RecordTableCellWrapper = ({
|
export const RecordTableCellWrapper = ({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@ -18,25 +18,19 @@ const StyledTd = styled.td<{
|
|||||||
width?: number;
|
width?: number;
|
||||||
}>`
|
}>`
|
||||||
border-bottom: 1px solid
|
border-bottom: 1px solid
|
||||||
${({ borderColor, hasBottomBorder }) =>
|
${({ borderColor, hasBottomBorder, isDragging }) =>
|
||||||
hasBottomBorder ? borderColor : 'transparent'};
|
hasBottomBorder && !isDragging ? borderColor : 'transparent'};
|
||||||
color: ${({ fontColor }) => fontColor};
|
color: ${({ fontColor }) => fontColor};
|
||||||
border-right: ${({ borderColor, hasRightBorder }) =>
|
border-right: ${({ borderColor, hasRightBorder, isDragging }) =>
|
||||||
hasRightBorder ? `1px solid ${borderColor}` : 'none'};
|
hasRightBorder && !isDragging ? `1px solid ${borderColor}` : 'none'};
|
||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
background: ${({ backgroundColor }) => backgroundColor};
|
background: ${({ backgroundColor, isDragging }) =>
|
||||||
${({ isDragging }) =>
|
isDragging ? 'transparent' : backgroundColor};
|
||||||
isDragging
|
|
||||||
? `
|
|
||||||
background-color: transparent;
|
|
||||||
border-color: transparent;
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
|
|
||||||
${({ freezeFirstColumns }) =>
|
${({ freezeFirstColumns }) =>
|
||||||
freezeFirstColumns
|
freezeFirstColumns
|
||||||
|
|||||||
@ -6,10 +6,9 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
|||||||
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
||||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
|
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
|
||||||
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
|
||||||
import {
|
import {
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
@ -18,6 +17,8 @@ import {
|
|||||||
IconSortDescending,
|
IconSortDescending,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
import { MenuItem } from 'twenty-ui/navigation';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
|
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||||
|
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||||
|
|
||||||
export type RecordTableColumnHeadDropdownMenuProps = {
|
export type RecordTableColumnHeadDropdownMenuProps = {
|
||||||
column: ColumnDefinition<FieldMetadata>;
|
column: ColumnDefinition<FieldMetadata>;
|
||||||
@ -28,6 +29,9 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
|||||||
}: RecordTableColumnHeadDropdownMenuProps) => {
|
}: RecordTableColumnHeadDropdownMenuProps) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||||
|
useToggleScrollWrapper();
|
||||||
|
|
||||||
const visibleTableColumns = useRecoilComponentValueV2(
|
const visibleTableColumns = useRecoilComponentValueV2(
|
||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
@ -46,16 +50,21 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
|||||||
|
|
||||||
const { closeDropdown } = useDropdown(column.fieldMetadataId + '-header');
|
const { closeDropdown } = useDropdown(column.fieldMetadataId + '-header');
|
||||||
|
|
||||||
const handleColumnMoveLeft = () => {
|
const closeDropdownAndToggleScroll = () => {
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
|
toggleScrollXWrapper(true);
|
||||||
|
toggleScrollYWrapper(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColumnMoveLeft = () => {
|
||||||
|
closeDropdownAndToggleScroll();
|
||||||
if (!canMoveLeft) return;
|
if (!canMoveLeft) return;
|
||||||
|
|
||||||
handleMoveTableColumn('left', column);
|
handleMoveTableColumn('left', column);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnMoveRight = () => {
|
const handleColumnMoveRight = () => {
|
||||||
closeDropdown();
|
closeDropdownAndToggleScroll();
|
||||||
|
|
||||||
if (!canMoveRight) return;
|
if (!canMoveRight) return;
|
||||||
|
|
||||||
@ -63,7 +72,7 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnVisibility = () => {
|
const handleColumnVisibility = () => {
|
||||||
closeDropdown();
|
closeDropdownAndToggleScroll();
|
||||||
handleColumnVisibilityChange(column);
|
handleColumnVisibilityChange(column);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,13 +84,13 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSortClick = () => {
|
const handleSortClick = () => {
|
||||||
closeDropdown();
|
closeDropdownAndToggleScroll();
|
||||||
|
|
||||||
onToggleColumnSort?.(column.fieldMetadataId);
|
onToggleColumnSort?.(column.fieldMetadataId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterClick = () => {
|
const handleFilterClick = () => {
|
||||||
closeDropdown();
|
closeDropdownAndToggleScroll();
|
||||||
|
|
||||||
onToggleColumnFilter?.(column.fieldMetadataId);
|
onToggleColumnFilter?.(column.fieldMetadataId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
|
||||||
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
||||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||||
@ -9,7 +8,6 @@ type RecordTableTrEffectProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableTrEffect = ({ recordId }: RecordTableTrEffectProps) => {
|
export const RecordTableTrEffect = ({ recordId }: RecordTableTrEffectProps) => {
|
||||||
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
|
|
||||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
|
|
||||||
const setIsRowVisible = useSetRecoilComponentFamilyStateV2(
|
const setIsRowVisible = useSetRecoilComponentFamilyStateV2(
|
||||||
@ -29,7 +27,6 @@ export const RecordTableTrEffect = ({ recordId }: RecordTableTrEffectProps) => {
|
|||||||
const isIntersecting = entry.isIntersecting;
|
const isIntersecting = entry.isIntersecting;
|
||||||
|
|
||||||
if (isIntersecting) {
|
if (isIntersecting) {
|
||||||
onIndexRecordsLoaded?.();
|
|
||||||
setIsRowVisible(true);
|
setIsRowVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,12 +47,7 @@ export const RecordTableTrEffect = ({ recordId }: RecordTableTrEffectProps) => {
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [
|
}, [recordId, scrollWrapperHTMLElement, setIsRowVisible]);
|
||||||
onIndexRecordsLoaded,
|
|
||||||
recordId,
|
|
||||||
scrollWrapperHTMLElement,
|
|
||||||
setIsRowVisible,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import { FieldEmailsValue } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldEmailsValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||||
import styled from '@emotion/styled';
|
import { styled } from '@linaria/react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { RoundedLink } from 'twenty-ui/navigation';
|
import { RoundedLink } from 'twenty-ui/navigation';
|
||||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import styled from '@emotion/styled';
|
import { styled } from '@linaria/react';
|
||||||
import { SelectOption } from 'twenty-ui/input';
|
|
||||||
import { Tag } from 'twenty-ui/components';
|
import { Tag } from 'twenty-ui/components';
|
||||||
|
import { SelectOption } from 'twenty-ui/input';
|
||||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||||
|
|
||||||
const spacing1 = THEME_COMMON.spacing(1);
|
const spacing1 = THEME_COMMON.spacing(1);
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||||
|
|
||||||
import { DEFAULT_PHONE_CALLING_CODE } from '@/object-record/record-field/meta-types/input/components/PhonesFieldInput';
|
import { DEFAULT_PHONE_CALLING_CODE } from '@/object-record/record-field/meta-types/input/components/PhonesFieldInput';
|
||||||
|
import { styled } from '@linaria/react';
|
||||||
import { parsePhoneNumber } from 'libphonenumber-js';
|
import { parsePhoneNumber } from 'libphonenumber-js';
|
||||||
import { logError } from '~/utils/logError';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { RoundedLink } from 'twenty-ui/navigation';
|
import { RoundedLink } from 'twenty-ui/navigation';
|
||||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||||
|
import { logError } from '~/utils/logError';
|
||||||
|
|
||||||
type PhonesDisplayProps = {
|
type PhonesDisplayProps = {
|
||||||
value?: FieldPhonesValue;
|
value?: FieldPhonesValue;
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
|
||||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||||
|
|
||||||
type TextDisplayProps = {
|
type TextDisplayProps = {
|
||||||
@ -7,13 +6,11 @@ type TextDisplayProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TextDisplay = ({ text, displayedMaxRows }: TextDisplayProps) => {
|
export const TextDisplay = ({ text, displayedMaxRows }: TextDisplayProps) => {
|
||||||
const { isInlineCellInEditMode } = useInlineCell();
|
|
||||||
return (
|
return (
|
||||||
<OverflowingTextWithTooltip
|
<OverflowingTextWithTooltip
|
||||||
text={text}
|
text={text}
|
||||||
displayedMaxRows={displayedMaxRows}
|
displayedMaxRows={displayedMaxRows}
|
||||||
isTooltipMultiline={true}
|
isTooltipMultiline={true}
|
||||||
hideTooltip={isInlineCellInEditMode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,19 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
|
import { LinkType, RoundedLink, SocialLink } from 'twenty-ui/navigation';
|
||||||
import { checkUrlType } from '~/utils/checkUrlType';
|
import { checkUrlType } from '~/utils/checkUrlType';
|
||||||
import { EllipsisDisplay } from './EllipsisDisplay';
|
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||||
import { LinkType, RoundedLink, SocialLink } from 'twenty-ui/navigation';
|
|
||||||
|
|
||||||
const StyledRawLink = styled(RoundedLink)`
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
a {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type URLDisplayProps = {
|
type URLDisplayProps = {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
@ -48,7 +37,7 @@ export const URLDisplay = ({ value }: URLDisplayProps) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<EllipsisDisplay>
|
<EllipsisDisplay>
|
||||||
<StyledRawLink
|
<RoundedLink
|
||||||
href={absoluteUrl}
|
href={absoluteUrl}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
label={displayedValue}
|
label={displayedValue}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
|
|||||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||||
|
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
||||||
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
||||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||||
@ -54,11 +55,10 @@ const StyledBar = styled.div`
|
|||||||
z-index: 4;
|
z-index: 4;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledChipcontainer = styled.div`
|
const StyledChipContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
overflow: scroll;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
@ -195,7 +195,11 @@ export const ViewBarDetails = ({
|
|||||||
return (
|
return (
|
||||||
<StyledBar>
|
<StyledBar>
|
||||||
<StyledFilterContainer>
|
<StyledFilterContainer>
|
||||||
<StyledChipcontainer>
|
<ScrollWrapper
|
||||||
|
componentInstanceId={viewBarId}
|
||||||
|
defaultEnableYScroll={false}
|
||||||
|
>
|
||||||
|
<StyledChipContainer>
|
||||||
{isDefined(softDeleteFilter) && (
|
{isDefined(softDeleteFilter) && (
|
||||||
<SoftDeleteFilterChip
|
<SoftDeleteFilterChip
|
||||||
key={softDeleteFilter.fieldMetadataId}
|
key={softDeleteFilter.fieldMetadataId}
|
||||||
@ -239,7 +243,8 @@ export const ViewBarDetails = ({
|
|||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||||
))}
|
))}
|
||||||
</StyledChipcontainer>
|
</StyledChipContainer>
|
||||||
|
</ScrollWrapper>
|
||||||
{hasFilterButton && (
|
{hasFilterButton && (
|
||||||
<StyledAddFilterContainer>
|
<StyledAddFilterContainer>
|
||||||
<AddObjectFilterFromDetailsButton
|
<AddObjectFilterFromDetailsButton
|
||||||
|
|||||||
@ -132,6 +132,11 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
'**/RecordTableHeaderDragDropColumn.tsx',
|
'**/RecordTableHeaderDragDropColumn.tsx',
|
||||||
'**/ActorDisplay.tsx',
|
'**/ActorDisplay.tsx',
|
||||||
'**/AvatarChip.tsx',
|
'**/AvatarChip.tsx',
|
||||||
|
'**/URLDisplay.tsx',
|
||||||
|
'**/EmailsDisplay.tsx',
|
||||||
|
'**/PhonesDisplay.tsx',
|
||||||
|
'**/MultiSelectDisplay.tsx',
|
||||||
|
|
||||||
],
|
],
|
||||||
babelOptions: {
|
babelOptions: {
|
||||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export const AvatarChip = ({
|
|||||||
label={name}
|
label={name}
|
||||||
variant={ChipVariant.Transparent}
|
variant={ChipVariant.Transparent}
|
||||||
size={size}
|
size={size}
|
||||||
leftComponent={() => (
|
leftComponent={
|
||||||
<AvatarChipsLeftComponent
|
<AvatarChipsLeftComponent
|
||||||
name={name}
|
name={name}
|
||||||
LeftIcon={LeftIcon}
|
LeftIcon={LeftIcon}
|
||||||
@ -29,7 +29,7 @@ export const AvatarChip = ({
|
|||||||
isIconInverted={isIconInverted}
|
isIconInverted={isIconInverted}
|
||||||
placeholderColorSeed={placeholderColorSeed}
|
placeholderColorSeed={placeholderColorSeed}
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
clickable={false}
|
clickable={false}
|
||||||
className={className}
|
className={className}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import { styled } from '@linaria/react';
|
||||||
import { Avatar } from '@ui/display/avatar/components/Avatar';
|
import { Avatar } from '@ui/display/avatar/components/Avatar';
|
||||||
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
||||||
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export const LinkAvatarChip = ({
|
|||||||
: ChipVariant.Regular
|
: ChipVariant.Regular
|
||||||
}
|
}
|
||||||
size={size}
|
size={size}
|
||||||
leftComponent={() => (
|
leftComponent={
|
||||||
<AvatarChipsLeftComponent
|
<AvatarChipsLeftComponent
|
||||||
name={name}
|
name={name}
|
||||||
LeftIcon={LeftIcon}
|
LeftIcon={LeftIcon}
|
||||||
@ -49,7 +49,7 @@ export const LinkAvatarChip = ({
|
|||||||
isIconInverted={isIconInverted}
|
isIconInverted={isIconInverted}
|
||||||
placeholderColorSeed={placeholderColorSeed}
|
placeholderColorSeed={placeholderColorSeed}
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
className={className}
|
className={className}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export type ChipProps = {
|
|||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
variant?: ChipVariant;
|
variant?: ChipVariant;
|
||||||
accent?: ChipAccent;
|
accent?: ChipAccent;
|
||||||
leftComponent?: (() => ReactNode) | null;
|
leftComponent?: ReactNode | null;
|
||||||
rightComponent?: (() => ReactNode) | null;
|
rightComponent?: (() => ReactNode) | null;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@ -146,7 +146,7 @@ export const Chip = ({
|
|||||||
className={className}
|
className={className}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
>
|
>
|
||||||
{leftComponent?.()}
|
{leftComponent}
|
||||||
{!isLabelHidden && (
|
{!isLabelHidden && (
|
||||||
<OverflowingTextWithTooltip size={size} text={label} />
|
<OverflowingTextWithTooltip size={size} text={label} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import styled from '@emotion/styled';
|
import { styled } from '@linaria/react';
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
ChipAccent,
|
ChipAccent,
|
||||||
@ -17,8 +17,6 @@ export type LinkChipProps = Omit<
|
|||||||
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ideally we would use the UndecoratedLink component from @ui/navigation
|
|
||||||
// but it led to a bug probably linked to circular dependencies, which was hard to solve
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { isNonEmptyString, isNull, isUndefined } from '@sniptt/guards';
|
import { isNonEmptyString, isNull, isUndefined } from '@sniptt/guards';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
|
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
|
||||||
import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPropertiesBySize';
|
import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPropertiesBySize';
|
||||||
@ -11,6 +10,7 @@ import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
|||||||
import { ThemeContext } from '@ui/theme';
|
import { ThemeContext } from '@ui/theme';
|
||||||
import { Nullable, stringToHslColor } from '@ui/utilities';
|
import { Nullable, stringToHslColor } from '@ui/utilities';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
|
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
import { getImageAbsoluteURI } from 'twenty-shared/utils';
|
import { getImageAbsoluteURI } from 'twenty-shared/utils';
|
||||||
|
|
||||||
const StyledAvatar = styled.div<{
|
const StyledAvatar = styled.div<{
|
||||||
|
|||||||
@ -7,6 +7,7 @@ type RoundedLinkProps = {
|
|||||||
href: string;
|
href: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fontSizeMd = FONT_COMMON.size.md;
|
const fontSizeMd = FONT_COMMON.size.md;
|
||||||
@ -59,7 +60,12 @@ const StyledLink = styled.a<{
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RoundedLink = ({ label, href, onClick }: RoundedLinkProps) => {
|
export const RoundedLink = ({
|
||||||
|
label,
|
||||||
|
href,
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
}: RoundedLinkProps) => {
|
||||||
const { theme } = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
|
|
||||||
const background = theme.background.transparent.lighter;
|
const background = theme.background.transparent.lighter;
|
||||||
@ -89,6 +95,7 @@ export const RoundedLink = ({ label, href, onClick }: RoundedLinkProps) => {
|
|||||||
backgroundHover={backgroundHover}
|
backgroundHover={backgroundHover}
|
||||||
backgroundActive={backgroundActive}
|
backgroundActive={backgroundActive}
|
||||||
border={border}
|
border={border}
|
||||||
|
className={className}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
|
|||||||
@ -84,6 +84,9 @@ export default defineConfig(({ command }) => {
|
|||||||
'**/Tag.tsx',
|
'**/Tag.tsx',
|
||||||
'**/Avatar.tsx',
|
'**/Avatar.tsx',
|
||||||
'**/Chip.tsx',
|
'**/Chip.tsx',
|
||||||
|
'**/LinkChip.tsx',
|
||||||
|
'**/Avatar.tsx',
|
||||||
|
'**/AvatarChipLeftComponent.tsx',
|
||||||
'**/ContactLink.tsx',
|
'**/ContactLink.tsx',
|
||||||
'**/RoundedLink.tsx',
|
'**/RoundedLink.tsx',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user