Skip to content

Commit

Permalink
feat(pixels): add Json component
Browse files Browse the repository at this point in the history
  • Loading branch information
toxsick committed Apr 14, 2024
1 parent 62c29b3 commit 9635444
Show file tree
Hide file tree
Showing 6 changed files with 1,915 additions and 0 deletions.
17 changes: 17 additions & 0 deletions packages/pixels/src/Json/Json.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.wrapper {
:global {
.variable-row .copy-to-clipboard-container,
.object-meta-data .copy-to-clipboard-container {
display: inline-block;
visibility: hidden;
}

.object-meta-data:hover,
.variable-row:hover,
.object-key-val > span:hover {
.copy-to-clipboard-container {
visibility: visible;
}
}
}
}
42 changes: 42 additions & 0 deletions packages/pixels/src/Json/Json.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Meta, StoryObj } from '@storybook/react';

import Json from './Json';

const meta: Meta<typeof Json> = {
title: 'pixels/Json',
component: Json,
};

export default meta;
type Story = StoryObj<typeof Json>;

const testObject = {
aList: [1, 2, 3],
anObject: { bool: true, number: 123, null: null, string: 'a string' },
};

// TODO: Some weird glitching behavior in storybook.
export const AsJsonString: Story = {
args: {
value: JSON.stringify(testObject, null, 2),
},
};

export const AsObject: Story = {
args: {
value: testObject,
},
};

export const DefaultCollapsed: Story = {
args: {
collapsed: true,
value: testObject,
},
};

export const InvalidJson: Story = {
args: {
value: 'this is a string, not a Json',
},
};
9 changes: 9 additions & 0 deletions packages/pixels/src/Json/Json.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { describe } from 'vitest';

import storySnapshots from 'storybook-config/story-snapshots';

import * as stories from './Json.stories';

describe('Story Snapshots', () => {
storySnapshots(stories);
});
115 changes: 115 additions & 0 deletions packages/pixels/src/Json/Json.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { ReactNode } from 'react';

import { useState } from 'react';
import { FaChevronDown, FaChevronUp, FaTimesCircle } from 'react-icons/fa';
import ReactJson from 'react-json-view';

import cn from 'classnames';

import Button from '../Button';

import styles from './Json.module.css';

/**
* returns the value (JSON string or object) as object
*/
const getValue = (value: string | object) => {
if (typeof value === 'string') {
return JSON.parse(value);
}
if (typeof value === 'object') {
return value;
}
throw new Error(`${typeof value} can not be visualized`);
};

export interface JsonProps {
/** CSS class name */
className?: string | string[] | null;
/** When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. */
collapsed?: boolean | number;
/** Object to be visualized JSON string or object */
value: string | object;
}

/**
* Json renderer based on [react-json-view](https://mac-s-g.github.io/react-json-view/demo/dist/)
*/
const Json = ({ className = null, collapsed = false, value }: JsonProps) => {
const isDarkMode = document.body.classList.contains('dark');

let content: ReactNode = null;
let error: ReactNode = null;

const [showDetails, setShowDetails] = useState(false);

try {
content = (
<ReactJson
theme={isDarkMode ? 'tomorrow' : 'rjv-default'}
style={{
fontSize: '12px',
backgroundColor: 'unset',
}}
name={false}
displayDataTypes={false}
src={getValue(value)}
collapsed={collapsed}
/>
);
} catch (err) {
error = (
<div
className="mb-4 flex flex-col items-center rounded-lg border border-danger bg-danger-50 p-4 text-sm text-danger"
role="alert"
>
<div className="flex w-full justify-between gap-6">
<div className="flex items-center">
<FaTimesCircle className="mr-2" />
<span className="font-medium">Failed to parse JSON data</span>
</div>
<Button
color="danger"
size="sm"
variant="light"
onClick={() => setShowDetails(!showDetails)}
>
{showDetails ? (
<>
<FaChevronUp /> Hide Details
</>
) : (
<>
<FaChevronDown /> Show Details
</>
)}
</Button>
</div>
{showDetails && (
<div className="mt-4 w-full text-left">
<div>
<strong>Error:</strong>
<pre>
{/* @ts-expect-error is ok */}
{err?.name}: {err?.message}
</pre>
</div>
<div className="mt-4">
<strong>Data:</strong>
<pre>
{typeof value !== 'string'
? JSON.stringify(value, null, 2)
: value}
</pre>
</div>
</div>
)}
</div>
);
}
return (
<div className={cn(styles.wrapper, className)}>{error || content}</div>
);
};

export default Json;
Loading

0 comments on commit 9635444

Please sign in to comment.