Extract the JSON visualizer component in twenty-ui (#10937)
- Move the JsonTree component and the other components to twenty-ui - Rely on a React Context to provide translations ## Future work It would be good to migrate the `createRequiredContext` function to `twenty-ui`. I didn't want to migrate it in this PR but would have liked to use it.
This commit is contained in:
committed by
GitHub
parent
428499e222
commit
093d6c0a1a
@ -1,391 +0,0 @@
|
||||
import { JsonTree } from '@/workflow/components/json-visualizer/components/JsonTree';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
expect,
|
||||
userEvent,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@storybook/test';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
|
||||
const meta: Meta<typeof JsonTree> = {
|
||||
title: 'Modules/Workflow/JsonVisualizer/JsonTree',
|
||||
component: JsonTree,
|
||||
args: {},
|
||||
argTypes: {},
|
||||
decorators: [I18nFrontDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof JsonTree>;
|
||||
|
||||
export const String: Story = {
|
||||
args: {
|
||||
value: 'Hello',
|
||||
},
|
||||
};
|
||||
|
||||
export const Number: Story = {
|
||||
args: {
|
||||
value: 42,
|
||||
},
|
||||
};
|
||||
|
||||
export const Boolean: Story = {
|
||||
args: {
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Null: Story = {
|
||||
args: {
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const ArraySimple: Story = {
|
||||
args: {
|
||||
value: [1, 2, 3],
|
||||
},
|
||||
};
|
||||
|
||||
export const ArrayEmpty: Story = {
|
||||
args: {
|
||||
value: [],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const emptyState = await canvas.findByText('Empty Array');
|
||||
|
||||
expect(emptyState).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ArrayNested: Story = {
|
||||
args: {
|
||||
value: [1, 2, ['a', 'b', 'c'], 3],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const nestedArrayElements = await canvas.findByText('[3]');
|
||||
|
||||
expect(nestedArrayElements).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ArrayNestedEmpty: Story = {
|
||||
args: {
|
||||
value: [1, 2, [], 3],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const nestedArrayElements = await canvas.findByText('[0]');
|
||||
|
||||
expect(nestedArrayElements).toBeVisible();
|
||||
|
||||
const emptyState = await canvas.findByText('Empty Array');
|
||||
|
||||
expect(emptyState).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ArrayWithObjects: Story = {
|
||||
args: {
|
||||
value: [
|
||||
{
|
||||
name: 'John Doe',
|
||||
age: 30,
|
||||
},
|
||||
{
|
||||
name: 'John Dowl',
|
||||
age: 42,
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const nestedObjectItemsCounts = await canvas.findAllByText('{2}');
|
||||
|
||||
expect(nestedObjectItemsCounts).toHaveLength(2);
|
||||
},
|
||||
};
|
||||
|
||||
export const ObjectSimple: Story = {
|
||||
args: {
|
||||
value: {
|
||||
name: 'John Doe',
|
||||
age: 30,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ObjectEmpty: Story = {
|
||||
args: {
|
||||
value: {},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const emptyState = await canvas.findByText('Empty Object');
|
||||
|
||||
expect(emptyState).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ObjectNested: Story = {
|
||||
args: {
|
||||
value: {
|
||||
person: {
|
||||
name: 'John Doe',
|
||||
address: {
|
||||
street: '123 Main St',
|
||||
city: 'New York',
|
||||
},
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const nestedObjectItemsCounts = await canvas.findAllByText('{2}');
|
||||
|
||||
expect(nestedObjectItemsCounts).toHaveLength(2);
|
||||
},
|
||||
};
|
||||
|
||||
export const ObjectNestedEmpty: Story = {
|
||||
args: {
|
||||
value: {
|
||||
person: {},
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const nestedObjectItemsCount = await canvas.findByText('{0}');
|
||||
|
||||
expect(nestedObjectItemsCount).toBeVisible();
|
||||
|
||||
const emptyState = await canvas.findByText('Empty Object');
|
||||
|
||||
expect(emptyState).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const ObjectWithArray: Story = {
|
||||
args: {
|
||||
value: {
|
||||
users: [
|
||||
{ id: 1, name: 'John' },
|
||||
{ id: 2, name: 'Jane' },
|
||||
],
|
||||
settings: {
|
||||
theme: 'dark',
|
||||
notifications: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NestedElementCanBeCollapsed: Story = {
|
||||
args: {
|
||||
value: {
|
||||
person: {
|
||||
name: 'John Doe',
|
||||
age: 12,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const toggleButton = await canvas.findByRole('button', {
|
||||
name: 'Collapse',
|
||||
});
|
||||
|
||||
const ageElement = canvas.getByText('age');
|
||||
|
||||
await Promise.all([
|
||||
waitForElementToBeRemoved(ageElement),
|
||||
|
||||
userEvent.click(toggleButton),
|
||||
]);
|
||||
|
||||
expect(toggleButton).toHaveTextContent('Expand');
|
||||
},
|
||||
};
|
||||
|
||||
export const ExpandingElementExpandsAllItsDescendants: Story = {
|
||||
args: {
|
||||
value: {
|
||||
person: {
|
||||
name: 'John Doe',
|
||||
address: {
|
||||
street: '123 Main St',
|
||||
city: 'New York',
|
||||
country: {
|
||||
name: 'USA',
|
||||
code: 'US',
|
||||
},
|
||||
},
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
{
|
||||
const allCollapseButtons = await canvas.findAllByRole('button', {
|
||||
name: 'Collapse',
|
||||
});
|
||||
|
||||
expect(allCollapseButtons).toHaveLength(3);
|
||||
|
||||
for (const collapseButton of allCollapseButtons.reverse()) {
|
||||
await userEvent.click(collapseButton);
|
||||
}
|
||||
}
|
||||
|
||||
const rootExpandButton = await canvas.findByRole('button', {
|
||||
name: 'Expand',
|
||||
});
|
||||
|
||||
await userEvent.click(rootExpandButton);
|
||||
|
||||
{
|
||||
const allCollapseButtons = await canvas.findAllByRole('button', {
|
||||
name: 'Collapse',
|
||||
});
|
||||
|
||||
expect(allCollapseButtons).toHaveLength(3);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const ReallyDeepNestedObject: Story = {
|
||||
args: {
|
||||
value: {
|
||||
a: {
|
||||
b: {
|
||||
c: {
|
||||
d: {
|
||||
e: {
|
||||
f: {
|
||||
g: {
|
||||
h: {
|
||||
i: {
|
||||
j: {
|
||||
k: {
|
||||
l: {
|
||||
m: {
|
||||
n: {
|
||||
o: {
|
||||
p: {
|
||||
q: {
|
||||
r: {
|
||||
s: {
|
||||
t: {
|
||||
u: {
|
||||
v: {
|
||||
w: {
|
||||
x: {
|
||||
y: {
|
||||
z: {
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bis: {
|
||||
c: {
|
||||
d: {
|
||||
e: {
|
||||
f: {
|
||||
g: {
|
||||
h: {
|
||||
i: {
|
||||
j: {
|
||||
k: {
|
||||
l: {
|
||||
m: {
|
||||
n: {
|
||||
o: {
|
||||
p: {
|
||||
q: {
|
||||
r: {
|
||||
s: {
|
||||
t: {
|
||||
u: {
|
||||
v: {
|
||||
w: {
|
||||
x: {
|
||||
y: {
|
||||
z: {
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LongText: Story = {
|
||||
args: {
|
||||
value: {
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum iaculis est tincidunt, sagittis neque vitae, sodales purus.':
|
||||
'Ut lobortis ultricies purus, sit amet porta eros. Suspendisse efficitur quam vitae diam imperdiet feugiat. Etiam vel bibendum elit.',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,37 +0,0 @@
|
||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconBrackets } from 'twenty-ui';
|
||||
import { JsonArray } from 'type-fest';
|
||||
|
||||
export const JsonArrayNode = ({
|
||||
label,
|
||||
value,
|
||||
depth,
|
||||
keyPath,
|
||||
shouldHighlightNode,
|
||||
}: {
|
||||
label?: string;
|
||||
value: JsonArray;
|
||||
depth: number;
|
||||
keyPath: string;
|
||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<JsonNestedNode
|
||||
elements={[...value.entries()].map(([key, value]) => ({
|
||||
id: key,
|
||||
label: String(key),
|
||||
value,
|
||||
}))}
|
||||
renderElementsCount={(count) => `[${count}]`}
|
||||
label={label}
|
||||
Icon={IconBrackets}
|
||||
depth={depth}
|
||||
emptyElementsText={t`Empty Array`}
|
||||
keyPath={keyPath}
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,105 +0,0 @@
|
||||
import { JsonArrow } from '@/workflow/components/json-visualizer/components/internal/JsonArrow';
|
||||
import { JsonList } from '@/workflow/components/json-visualizer/components/internal/JsonList';
|
||||
import { JsonNodeLabel } from '@/workflow/components/json-visualizer/components/internal/JsonNodeLabel';
|
||||
import { JsonNode } from '@/workflow/components/json-visualizer/components/JsonNode';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
import { JsonValue } from 'type-fest';
|
||||
|
||||
const StyledContainer = styled.li`
|
||||
list-style-type: none;
|
||||
display: grid;
|
||||
row-gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledLabelContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledElementsCount = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledEmptyState = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
export const JsonNestedNode = ({
|
||||
label,
|
||||
Icon,
|
||||
elements,
|
||||
renderElementsCount,
|
||||
emptyElementsText,
|
||||
depth,
|
||||
keyPath,
|
||||
shouldHighlightNode,
|
||||
}: {
|
||||
label?: string;
|
||||
Icon: IconComponent;
|
||||
elements: Array<{ id: string | number; label: string; value: JsonValue }>;
|
||||
renderElementsCount?: (count: number) => string;
|
||||
emptyElementsText: string;
|
||||
depth: number;
|
||||
keyPath: string;
|
||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||
}) => {
|
||||
const hideRoot = !isDefined(label);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
const renderedChildren = (
|
||||
<JsonList depth={depth}>
|
||||
{elements.length === 0 ? (
|
||||
<StyledEmptyState>{emptyElementsText}</StyledEmptyState>
|
||||
) : (
|
||||
elements.map(({ id, label, value }) => {
|
||||
const nextKeyPath = isNonEmptyString(keyPath)
|
||||
? `${keyPath}.${id}`
|
||||
: String(id);
|
||||
|
||||
return (
|
||||
<JsonNode
|
||||
key={id}
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth + 1}
|
||||
keyPath={nextKeyPath}
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</JsonList>
|
||||
);
|
||||
|
||||
const handleArrowClick = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
if (hideRoot) {
|
||||
return <StyledContainer>{renderedChildren}</StyledContainer>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLabelContainer>
|
||||
<JsonArrow isOpen={isOpen} onClick={handleArrowClick} />
|
||||
|
||||
<JsonNodeLabel label={label} Icon={Icon} />
|
||||
|
||||
{renderElementsCount && (
|
||||
<StyledElementsCount>
|
||||
{renderElementsCount(elements.length)}
|
||||
</StyledElementsCount>
|
||||
)}
|
||||
</StyledLabelContainer>
|
||||
|
||||
{isOpen && renderedChildren}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,94 +0,0 @@
|
||||
import { JsonArrayNode } from '@/workflow/components/json-visualizer/components/JsonArrayNode';
|
||||
import { JsonObjectNode } from '@/workflow/components/json-visualizer/components/JsonObjectNode';
|
||||
import { JsonValueNode } from '@/workflow/components/json-visualizer/components/JsonValueNode';
|
||||
import { isArray } from '@/workflow/components/json-visualizer/utils/isArray';
|
||||
import { isBoolean, isNull, isNumber, isString } from '@sniptt/guards';
|
||||
import {
|
||||
IconCheckbox,
|
||||
IconCircleOff,
|
||||
IconNumber9,
|
||||
IconTypography,
|
||||
} from 'twenty-ui';
|
||||
import { JsonValue } from 'type-fest';
|
||||
|
||||
export const JsonNode = ({
|
||||
label,
|
||||
value,
|
||||
depth,
|
||||
keyPath,
|
||||
shouldHighlightNode,
|
||||
}: {
|
||||
label?: string;
|
||||
value: JsonValue;
|
||||
depth: number;
|
||||
keyPath: string;
|
||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||
}) => {
|
||||
const isHighlighted = shouldHighlightNode?.(keyPath) ?? false;
|
||||
|
||||
if (isNull(value)) {
|
||||
return (
|
||||
<JsonValueNode
|
||||
label={label}
|
||||
valueAsString="[null]"
|
||||
Icon={IconCircleOff}
|
||||
isHighlighted={isHighlighted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isString(value)) {
|
||||
return (
|
||||
<JsonValueNode
|
||||
label={label}
|
||||
valueAsString={value}
|
||||
Icon={IconTypography}
|
||||
isHighlighted={isHighlighted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isNumber(value)) {
|
||||
return (
|
||||
<JsonValueNode
|
||||
label={label}
|
||||
valueAsString={String(value)}
|
||||
Icon={IconNumber9}
|
||||
isHighlighted={isHighlighted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isBoolean(value)) {
|
||||
return (
|
||||
<JsonValueNode
|
||||
label={label}
|
||||
valueAsString={String(value)}
|
||||
Icon={IconCheckbox}
|
||||
isHighlighted={isHighlighted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isArray(value)) {
|
||||
return (
|
||||
<JsonArrayNode
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth}
|
||||
keyPath={keyPath}
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<JsonObjectNode
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth}
|
||||
keyPath={keyPath}
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,37 +0,0 @@
|
||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconCube } from 'twenty-ui';
|
||||
import { JsonObject } from 'type-fest';
|
||||
|
||||
export const JsonObjectNode = ({
|
||||
label,
|
||||
value,
|
||||
depth,
|
||||
keyPath,
|
||||
shouldHighlightNode,
|
||||
}: {
|
||||
label?: string;
|
||||
value: JsonObject;
|
||||
depth: number;
|
||||
keyPath: string;
|
||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<JsonNestedNode
|
||||
elements={Object.entries(value).map(([key, value]) => ({
|
||||
id: key,
|
||||
label: key,
|
||||
value,
|
||||
}))}
|
||||
renderElementsCount={(count) => `{${count}}`}
|
||||
label={label}
|
||||
Icon={IconCube}
|
||||
depth={depth}
|
||||
emptyElementsText={t`Empty Object`}
|
||||
keyPath={keyPath}
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
import { JsonList } from '@/workflow/components/json-visualizer/components/internal/JsonList';
|
||||
import { JsonNode } from '@/workflow/components/json-visualizer/components/JsonNode';
|
||||
import { JsonValue } from 'type-fest';
|
||||
|
||||
export const JsonTree = ({
|
||||
value,
|
||||
shouldHighlightNode,
|
||||
}: {
|
||||
value: JsonValue;
|
||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||
}) => {
|
||||
return (
|
||||
<JsonList depth={0}>
|
||||
<JsonNode
|
||||
value={value}
|
||||
depth={0}
|
||||
keyPath=""
|
||||
shouldHighlightNode={shouldHighlightNode}
|
||||
/>
|
||||
</JsonList>
|
||||
);
|
||||
};
|
||||
@ -1,42 +0,0 @@
|
||||
import { JsonListItem } from '@/workflow/components/json-visualizer/components/internal/JsonListItem';
|
||||
import { JsonNodeLabel } from '@/workflow/components/json-visualizer/components/internal/JsonNodeLabel';
|
||||
import { JsonNodeValue } from '@/workflow/components/json-visualizer/components/internal/JsonNodeValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
const StyledListItem = styled(JsonListItem)`
|
||||
column-gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type JsonValueNodeProps = {
|
||||
valueAsString: string;
|
||||
isHighlighted: boolean;
|
||||
} & (
|
||||
| {
|
||||
label: string;
|
||||
Icon: IconComponent;
|
||||
}
|
||||
| {
|
||||
label?: never;
|
||||
Icon?: unknown;
|
||||
}
|
||||
);
|
||||
|
||||
export const JsonValueNode = (props: JsonValueNodeProps) => {
|
||||
return (
|
||||
<StyledListItem>
|
||||
{props.label && (
|
||||
<JsonNodeLabel
|
||||
label={props.label}
|
||||
Icon={props.Icon}
|
||||
isHighlighted={props.isHighlighted}
|
||||
/>
|
||||
)}
|
||||
|
||||
<JsonNodeValue
|
||||
valueAsString={props.valueAsString}
|
||||
isHighlighted={props.isHighlighted}
|
||||
/>
|
||||
</StyledListItem>
|
||||
);
|
||||
};
|
||||
@ -1,48 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { IconChevronDown, VisibilityHidden } from 'twenty-ui';
|
||||
|
||||
const StyledButton = styled(motion.button)`
|
||||
align-items: center;
|
||||
border-color: ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-inline: ${({ theme }) => theme.spacing(1)};
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const MotionIconChevronDown = motion.create(IconChevronDown);
|
||||
|
||||
export const JsonArrow = ({
|
||||
isOpen,
|
||||
onClick,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButton onClick={onClick}>
|
||||
<VisibilityHidden>{isOpen ? t`Collapse` : t`Expand`}</VisibilityHidden>
|
||||
|
||||
<MotionIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.secondary}
|
||||
initial={false}
|
||||
animate={{ rotate: isOpen ? -180 : 0 }}
|
||||
/>
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledList = styled.ul<{ depth: number }>`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
display: grid;
|
||||
row-gap: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
${({ theme, depth }) =>
|
||||
depth > 0 &&
|
||||
css`
|
||||
padding-left: ${theme.spacing(8)};
|
||||
`}
|
||||
`;
|
||||
|
||||
export { StyledList as JsonList };
|
||||
@ -1,10 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledListItem = styled.li`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
list-style-type: none;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export { StyledListItem as JsonListItem };
|
||||
@ -1,46 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
const StyledLabelContainer = styled.span<{ isHighlighted?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border-color: ${({ theme }) => theme.border.color.medium};
|
||||
color: ${({ theme, isHighlighted }) =>
|
||||
isHighlighted ? theme.color.blue : theme.font.color.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
height: 24px;
|
||||
box-sizing: border-box;
|
||||
column-gap: ${({ theme }) => theme.spacing(2)};
|
||||
display: flex;
|
||||
font-variant-numeric: tabular-nums;
|
||||
justify-content: center;
|
||||
padding-block: ${({ theme }) => theme.spacing(1)};
|
||||
padding-inline: ${({ theme }) => theme.spacing(2)};
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
export const JsonNodeLabel = ({
|
||||
label,
|
||||
Icon,
|
||||
isHighlighted,
|
||||
}: {
|
||||
label: string;
|
||||
Icon: IconComponent;
|
||||
isHighlighted?: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledLabelContainer isHighlighted={isHighlighted}>
|
||||
<Icon
|
||||
size={theme.icon.size.md}
|
||||
color={isHighlighted ? theme.color.blue : theme.font.color.tertiary}
|
||||
/>
|
||||
|
||||
<span>{label}</span>
|
||||
</StyledLabelContainer>
|
||||
);
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledText = styled.span<{ isHighlighted?: boolean }>`
|
||||
color: ${({ theme, isHighlighted }) =>
|
||||
isHighlighted ? theme.adaptiveColors.blue4 : theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
export const JsonNodeValue = ({
|
||||
valueAsString,
|
||||
isHighlighted,
|
||||
}: {
|
||||
valueAsString: string;
|
||||
isHighlighted?: boolean;
|
||||
}) => {
|
||||
return <StyledText isHighlighted={isHighlighted}>{valueAsString}</StyledText>;
|
||||
};
|
||||
@ -1,5 +0,0 @@
|
||||
import { isArray as _isArray } from '@sniptt/guards';
|
||||
|
||||
export const isArray = (
|
||||
value: unknown,
|
||||
): value is unknown[] | readonly unknown[] => _isArray(value);
|
||||
Reference in New Issue
Block a user