From 96354443067d20d68bfa5749199612de0f0a0adc Mon Sep 17 00:00:00 2001 From: Hannes Tiede <951466+toxsick@users.noreply.github.com> Date: Sun, 14 Apr 2024 20:31:25 +0200 Subject: [PATCH] feat(pixels): add Json component --- packages/pixels/src/Json/Json.module.css | 17 + packages/pixels/src/Json/Json.stories.tsx | 42 + packages/pixels/src/Json/Json.test.tsx | 9 + packages/pixels/src/Json/Json.tsx | 115 ++ .../src/Json/__snapshots__/Json.test.tsx.snap | 1729 +++++++++++++++++ packages/pixels/src/Json/index.ts | 3 + 6 files changed, 1915 insertions(+) create mode 100644 packages/pixels/src/Json/Json.module.css create mode 100644 packages/pixels/src/Json/Json.stories.tsx create mode 100644 packages/pixels/src/Json/Json.test.tsx create mode 100644 packages/pixels/src/Json/Json.tsx create mode 100644 packages/pixels/src/Json/__snapshots__/Json.test.tsx.snap create mode 100644 packages/pixels/src/Json/index.ts diff --git a/packages/pixels/src/Json/Json.module.css b/packages/pixels/src/Json/Json.module.css new file mode 100644 index 00000000..0ae1dc63 --- /dev/null +++ b/packages/pixels/src/Json/Json.module.css @@ -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; + } + } + } +} diff --git a/packages/pixels/src/Json/Json.stories.tsx b/packages/pixels/src/Json/Json.stories.tsx new file mode 100644 index 00000000..288c36cb --- /dev/null +++ b/packages/pixels/src/Json/Json.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Json from './Json'; + +const meta: Meta = { + title: 'pixels/Json', + component: Json, +}; + +export default meta; +type Story = StoryObj; + +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', + }, +}; diff --git a/packages/pixels/src/Json/Json.test.tsx b/packages/pixels/src/Json/Json.test.tsx new file mode 100644 index 00000000..e6b9cfe0 --- /dev/null +++ b/packages/pixels/src/Json/Json.test.tsx @@ -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); +}); diff --git a/packages/pixels/src/Json/Json.tsx b/packages/pixels/src/Json/Json.tsx new file mode 100644 index 00000000..9266160c --- /dev/null +++ b/packages/pixels/src/Json/Json.tsx @@ -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 = ( + + ); + } catch (err) { + error = ( +
+
+
+ + Failed to parse JSON data +
+ +
+ {showDetails && ( +
+
+ Error: +
+                {/* @ts-expect-error is ok */}
+                {err?.name}: {err?.message}
+              
+
+
+ Data: +
+                {typeof value !== 'string'
+                  ? JSON.stringify(value, null, 2)
+                  : value}
+              
+
+
+ )} +
+ ); + } + return ( +
{error || content}
+ ); +}; + +export default Json; diff --git a/packages/pixels/src/Json/__snapshots__/Json.test.tsx.snap b/packages/pixels/src/Json/__snapshots__/Json.test.tsx.snap new file mode 100644 index 00000000..52f48373 --- /dev/null +++ b/packages/pixels/src/Json/__snapshots__/Json.test.tsx.snap @@ -0,0 +1,1729 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Story Snapshots > AsJsonString 1`] = ` +
+
+
+
+
+
+ + +
+ + + + + +
+ + + { + + + +
+
+
+
+ + +
+ + + + + +
+ + + + " + + + aList + + + " + + + + : + + + + [ + +
+ +
+
+
+
+ + 0 +
+ : +
+
+
+
+ 1 +
+
+ +
+
+ + 1 +
+ : +
+
+
+
+ 2 +
+
+ +
+
+ + 2 +
+ : +
+
+
+
+ 3 +
+
+ +
+
+
+ + + ] + + +
+
+ + +
+ + + + + +
+ + + + " + + + anObject + + + " + + + + : + + + + { + +
+ +
+
+
+
+ + + + " + + + bool + + + " + + + + : + + +
+
+ true +
+
+ +
+
+ + + + " + + + number + + + " + + + + : + + +
+
+ 123 +
+
+ +
+
+ + + + " + + + null + + + " + + + + : + + +
+
+ NULL +
+
+ +
+
+ + + + " + + + string + + + " + + + + : + + +
+
+ + " + a string + " + +
+
+ +
+
+
+ + + } + + +
+
+
+ + + } + + +
+
+
+
+
+
+`; + +exports[`Story Snapshots > AsObject 1`] = ` +
+
+
+
+
+
+ + +
+ + + + + +
+ + + { + + + +
+
+
+
+ + +
+ + + + + +
+ + + + " + + + aList + + + " + + + + : + + + + [ + +
+ +
+
+
+
+ + 0 +
+ : +
+
+
+
+ 1 +
+
+ +
+
+ + 1 +
+ : +
+
+
+
+ 2 +
+
+ +
+
+ + 2 +
+ : +
+
+
+
+ 3 +
+
+ +
+
+
+ + + ] + + +
+
+ + +
+ + + + + +
+ + + + " + + + anObject + + + " + + + + : + + + + { + +
+ +
+
+
+
+ + + + " + + + bool + + + " + + + + : + + +
+
+ true +
+
+ +
+
+ + + + " + + + number + + + " + + + + : + + +
+
+ 123 +
+
+ +
+
+ + + + " + + + null + + + " + + + + : + + +
+
+ NULL +
+
+ +
+
+ + + + " + + + string + + + " + + + + : + + +
+
+ + " + a string + " + +
+
+ +
+
+
+ + + } + + +
+
+
+ + + } + + +
+
+
+
+
+
+`; + +exports[`Story Snapshots > DefaultCollapsed 1`] = ` +
+
+
+
+
+
+ + +
+ + + + + +
+ + + { + + +
+
+ ... +
+ + + } + + + +
+
+
+
+
+
+`; + +exports[`Story Snapshots > InvalidJson 1`] = ` +
+
+ +
+
+`; diff --git a/packages/pixels/src/Json/index.ts b/packages/pixels/src/Json/index.ts new file mode 100644 index 00000000..fa8489ce --- /dev/null +++ b/packages/pixels/src/Json/index.ts @@ -0,0 +1,3 @@ +import Json from './Json'; + +export default Json;