Fix raw json field display in read only (#16502)
Preview <img width="1185" height="617" alt="Screenshot 2025-12-11 at 15 54 07" src="https://github.com/user-attachments/assets/863fb58c-04df-4e4d-bf8c-0045c833466c" /> Fixes https://github.com/twentyhq/twenty/issues/16311
This commit is contained in:
+45
-2
@@ -1,9 +1,29 @@
|
||||
import { useJsonFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay';
|
||||
import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay';
|
||||
import { ExpandedFieldDisplay } from '@/ui/layout/expandable-list/components/ExpandedFieldDisplay';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRef, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isTwoFirstDepths, JsonTree } from 'twenty-ui/json-visualizer';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
|
||||
export const JsonFieldDisplay = () => {
|
||||
const { fieldValue, maxWidth } = useJsonFieldDisplay();
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
const { fieldValue, maxWidth, isRecordFieldReadOnly } = useJsonFieldDisplay();
|
||||
|
||||
const [isJsonTreeViewOpen, setIsJsonTreeViewOpen] = useState(false);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleClick = () => {
|
||||
if (isRecordFieldReadOnly) {
|
||||
setIsJsonTreeViewOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutside = () => {
|
||||
setIsJsonTreeViewOpen(false);
|
||||
};
|
||||
|
||||
if (!isDefined(fieldValue)) {
|
||||
return <></>;
|
||||
@@ -11,5 +31,28 @@ export const JsonFieldDisplay = () => {
|
||||
|
||||
const value = JSON.stringify(fieldValue);
|
||||
|
||||
return <JsonDisplay text={value} maxWidth={maxWidth} />;
|
||||
return (
|
||||
<>
|
||||
<div ref={anchorRef} onClick={handleClick}>
|
||||
<JsonDisplay text={value} maxWidth={maxWidth} />
|
||||
</div>
|
||||
{isJsonTreeViewOpen && (
|
||||
<ExpandedFieldDisplay
|
||||
anchorElement={anchorRef.current ?? undefined}
|
||||
onClickOutside={handleClickOutside}
|
||||
>
|
||||
<JsonTree
|
||||
value={fieldValue}
|
||||
shouldExpandNodeInitially={isTwoFirstDepths}
|
||||
emptyArrayLabel={t`Empty Array`}
|
||||
emptyObjectLabel={t`Empty Object`}
|
||||
emptyStringLabel={t`[empty string]`}
|
||||
arrowButtonCollapsedLabel={t`Expand`}
|
||||
arrowButtonExpandedLabel={t`Collapse`}
|
||||
onNodeValueClick={copyToClipboard}
|
||||
/>
|
||||
</ExpandedFieldDisplay>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-1
@@ -7,7 +7,8 @@ import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecor
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useJsonFieldDisplay = () => {
|
||||
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
|
||||
const { recordId, fieldDefinition, maxWidth, isRecordFieldReadOnly } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
@@ -25,5 +26,6 @@ export const useJsonFieldDisplay = () => {
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue: formattedFieldValue,
|
||||
isRecordFieldReadOnly,
|
||||
};
|
||||
};
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import styled from '@emotion/styled';
|
||||
import { flip, FloatingPortal, offset, useFloating } from '@floating-ui/react';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
type ExpandedFieldDisplayProps = {
|
||||
anchorElement?: HTMLElement;
|
||||
children: ReactNode;
|
||||
onClickOutside?: () => void;
|
||||
};
|
||||
|
||||
const StyledExpandedFieldContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
height: 300px;
|
||||
width: 400px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
z-index: ${RootStackingContextZIndices.DropdownPortalBelowModal};
|
||||
`;
|
||||
|
||||
export const ExpandedFieldDisplay = ({
|
||||
anchorElement,
|
||||
children,
|
||||
onClickOutside,
|
||||
}: ExpandedFieldDisplayProps) => {
|
||||
const { refs, floatingStyles } = useFloating({
|
||||
placement: 'bottom-start',
|
||||
middleware: [
|
||||
flip(),
|
||||
offset({
|
||||
mainAxis: -29,
|
||||
crossAxis: -10,
|
||||
}),
|
||||
],
|
||||
elements: { reference: anchorElement },
|
||||
});
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [refs.domReference, refs.floating],
|
||||
callback: () => {
|
||||
onClickOutside?.();
|
||||
},
|
||||
listenerId: 'expanded-field-display',
|
||||
});
|
||||
|
||||
return (
|
||||
<FloatingPortal>
|
||||
<StyledContainer
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
data-globally-prevent-click-outside="true"
|
||||
>
|
||||
<OverlayContainer>
|
||||
<StyledExpandedFieldContainer>
|
||||
{children}
|
||||
</StyledExpandedFieldContainer>
|
||||
</OverlayContainer>
|
||||
</StyledContainer>
|
||||
</FloatingPortal>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user