├── .gitignore
├── .npmignore
├── .prettierignore
├── .storybook
├── main.ts
├── preview.ts
├── public
│ ├── favicon.ico
│ └── favicon.svg
└── style.css
├── LICENSE
├── README.md
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── postcss.config.js
├── prettier.config.cjs
├── rollup.config.js
├── sample.png
├── src
├── components
│ ├── copy-button.tsx
│ ├── json-node.tsx
│ ├── json-view.tsx
│ ├── large-array-node.tsx
│ ├── large-array.tsx
│ ├── long-string.tsx
│ ├── name-value.tsx
│ └── object-node.tsx
├── dark.css
├── global.d.ts
├── index.tsx
├── stories
│ ├── editable.stories.tsx
│ ├── json-view.stories.tsx
│ ├── share.ts
│ └── theme.stories.tsx
├── style.css
├── svgs
│ ├── add-circle.svg
│ ├── add-square.svg
│ ├── angle-down.svg
│ ├── cancel.svg
│ ├── copied.svg
│ ├── copy.svg
│ ├── custom-add.svg
│ ├── custom-copied.svg
│ ├── custom-copy.svg
│ ├── done.svg
│ ├── edit.svg
│ ├── link.svg
│ └── trash.svg
├── types.ts
└── utils.ts
├── tailwind.config.cjs
├── theme.png
├── tsconfig.json
├── turbo.json
├── vercel.json
└── website
├── .gitignore
├── .prettierignore
├── README.md
├── global.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── prettier.config.js
├── public
├── favicon.svg
└── og.png
├── src
├── app
│ ├── footer.tsx
│ ├── globals.css
│ ├── head.tsx
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── single-loading.tsx
│ └── theme.tsx
├── contents
│ ├── collapse-string.tsx
│ ├── collapsed.tsx
│ ├── customization.tsx
│ ├── display-array-index.tsx
│ ├── display-size.tsx
│ ├── editable.tsx
│ ├── hero.tsx
│ ├── installation.tsx
│ ├── themes.tsx
│ └── usage.tsx
├── hooks
│ └── useTheme.ts
├── lib
│ ├── clipboard.ts
│ ├── hljs.ts
│ └── storage.ts
└── svgs
│ ├── angle-down.svg
│ ├── copied.svg
│ ├── copy.svg
│ ├── dark.svg
│ ├── github.svg
│ ├── light.svg
│ └── system.svg
├── tailwind.config.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
4 | # Editor directories and files
5 | .vscode/*
6 | !.vscode/extensions.json
7 | .idea
8 | .DS_Store
9 | *.suo
10 | *.ntvs*
11 | *.njsproj
12 | *.sln
13 | *.sw?
14 |
15 | # Storybook
16 | storybook-static
17 |
18 | .turbo
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | sample.png
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | README.md
4 | .next
5 | pnpm-lock.yaml
6 | dist
7 | storybook-static
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-vite'
2 | import { mergeConfig } from 'vite'
3 | import svgr from 'vite-plugin-svgr'
4 |
5 | const config: StorybookConfig = {
6 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
7 | addons: [
8 | '@storybook/addon-links',
9 | '@storybook/addon-essentials',
10 | '@storybook/addon-interactions',
11 | {
12 | name: '@storybook/addon-styling',
13 | options: {
14 | postCss: true
15 | }
16 | }
17 | ],
18 | framework: {
19 | name: '@storybook/react-vite',
20 | options: {}
21 | },
22 | docs: {
23 | autodocs: true
24 | },
25 | viteFinal(config) {
26 | return mergeConfig(config, {
27 | plugins: [svgr()]
28 | })
29 | },
30 | staticDirs: ['./public'],
31 | managerHead: head => `
32 | ${head}
33 |
34 | `
35 | }
36 | export default config
37 |
--------------------------------------------------------------------------------
/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import type { Preview } from '@storybook/react'
2 | import '../src/style.css'
3 | import './style.css'
4 |
5 | const preview: Preview = {
6 | parameters: {
7 | actions: { argTypesRegex: '^on[A-Z].*' },
8 | controls: {
9 | matchers: {
10 | color: /(background|color)$/i,
11 | date: /Date$/
12 | }
13 | }
14 | }
15 | }
16 |
17 | export default preview
18 |
--------------------------------------------------------------------------------
/.storybook/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYsuni/react18-json-view/b8513baf7ee215ee6105aa05a6c6c71c6515ee4e/.storybook/public/favicon.ico
--------------------------------------------------------------------------------
/.storybook/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.storybook/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #storybook-root {
8 | @apply h-full;
9 | }
10 | body {
11 | padding: 0 !important;
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Suni
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [React18 JSON View](https://jv.yysuni.com/)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | React function component for displaying javascript arrays and JSON objects. Supports all JS types.
16 |
17 | [Website](https://jv.yysuni.com/), [Storybook](https://react18-json-view.vercel.app/),[Online](https://json-view-online.vercel.app/)
18 |
19 | 
20 |
21 | ## Installation
22 |
23 | ```bash
24 | npm i react18-json-view
25 | ```
26 |
27 | ```bash
28 | npm i react18-json-view@canary
29 | ```
30 |
31 | ## Usage
32 |
33 | ```tsx
34 | import JsonView from 'react18-json-view'
35 | import 'react18-json-view/src/style.css'
36 | // If dark mode is needed, import `dark.css`.
37 | // import 'react18-json-view/src/dark.css'
38 |
39 |
40 |
41 | // If needed, you can use the internal stringify function.
42 | // import { stringify } from 'react18-json-view'
43 | ```
44 |
45 | ### Props
46 |
47 | | Name | Type | Default | Description |
48 | | :------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49 | | `src` | `JSON Object` | None | This property contains your input JSON
|
50 | | `className` | `string` | None | The CSS class name(s) to apply to the component. |
51 | | `style` | `JSON Object` | None | An object containing custom style rules to apply to the component. |
52 | | `dark` | `boolean` | `false` | Keep in dark mode (Don't forget to import `dark.css`) |
53 | | `theme` | `default` \| `a11y` \| `github` \| `vscode` \| `atom`\|`winter-is-coming` | `'default'` | Color theme |
54 | | `enableClipboard` | `boolean` | `true` | Whether enable clipboard feature. |
55 | | `matchesURL` | `boolean` | `true` | Show the link icon if value is string and matches URL regex pattern. |
56 | | `urlRegExp` | `RegExp` | `/^(((ht\|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/` | URL RegExp pattern. |
57 | | `displaySize` | `boolean` \| `integer` \| 'collapsed' \| 'expanded' | `false` | Whether display the size of `Object`, `Array`. |
58 | | `displayArrayIndex` | `boolean` | `true` | Whether display the index of `Array`. |
59 | | `collapseStringsAfterLength` | `integer` | `99` | When an integer value is assigned, strings longer than that length will be truncated and indicated by an ellipsis. To expand or collapse the string content, simply click on the string value. |
60 | | `customizeCollapseStringUI` | ` (str_show: string, truncated: boolean) => (JSX.Element \| string)` \| `string` | - | Customize the collapse string UI. |
61 | | `ignoreLargeArray` | `boolean` | `false` | Prevent collapsing large array(length > 100) behavior since v0.2.7 |
62 | | `collapseStringMode` | `'directly'` \| `'word'` \| `'address'` | `'directly'` | If the `word` is assigned, the collapsed length will be adjusted to fully display the last word. |
63 | | `collapsed` | `boolean` \| `integer` \| `function` | `false` | When set to true(false), all nodes will be (not) collapsed by default. When using an integer value, it will collapse at a specific depth. The collapsed also can be a function. |
64 | | `onCollapse` | `function` | - | `(params: { isCollapsing: boolean, node: Record \| Array, indexOrName: string \| number \| undefined, depth: number }) => void` |
65 | | `collapseObjectsAfterLength` | `integer` | `99` | When an integer value is assigned, the object and array will initially collapse. |
66 | | `editable` | `boolean` \| {add?: `boolean`, edit?: `boolean`, delete?: `boolean`} | `false` | When set to true, you can add, edit, or delete the property, and the actions will trigger onAdd, onEdit, or onDelete. Options is available. |
67 | | `onAdd` | `function` | - | `(params: { indexOrName: string\| number, depth: number, src: any; parentType: 'object' \| 'array' }) => void` |
68 | | `onDelete` | `function` | - | `(params:{ value: any,indexOrName: string \| number,depth: number,src: any,parentType: 'object' \| 'array'}) => void` |
69 | | `onEdit` | `function` | - | `(params: { newValue: any, oldValue: any, depth: number, src: any, indexOrName: string \| number, parentType: 'object' \| 'array'}) => void` |
70 | | `customizeNode` | `ReactElement`\|`ReactComponent`\|`Options` | - | Highly customize every node. |
71 | | `customizeCopy` | `(node: any, nodeMeta: NodeMeta) => any` | internal stringify | Customize copy behavior, only the returned non-empty string will be written to clipboard. |
72 | | `CopyComponent` \/ `DoneComponent` \/ `CancelComponent` | `React.FC` \/ `React.Component` `<{ onClick: (event: React.MouseEvent) => void; className: string ; style: React.CSSProperties}>` | - | Customize copy icon. |
73 | | `CopiedComponent` | `React.FC` \/ `React.Component` `<{ className: string; style: React.CSSProperties }>` | - | Customize copied icon. |
74 | | `CustomOperation` | `React.FC` \/ `React.Component` `<{ node: any }>` | - | Custom Operation |
75 |
76 | ### Collapsed function
77 |
78 | ```ts
79 | ;(params: {
80 | node: Record | Array // Object or array
81 | indexOrName: number | string | undefined
82 | depth: number
83 | size: number // Object's size or array's length
84 | }) => boolean
85 | ```
86 |
87 | ### Editable options
88 |
89 | ```ts
90 | {
91 | add?: boolean
92 | edit?: boolean
93 | delete?: boolean
94 | }
95 | ```
96 |
97 | > onEdit, OnDelete, onAdd and OnChange add `parentPath` field, allow us to correctly indicate which field in the JSON is being edited.
98 |
99 | ### CustomizeNode
100 |
101 | ```ts
102 | (params: { node: any; indexOrName: number | string | undefined; depth: number }) =>
103 | | {
104 | add?: boolean
105 | edit?: boolean
106 | delete?: boolean
107 | enableClipboard?: boolean
108 | collapsed?: boolean
109 | className?: string
110 | }
111 | | React.FC
112 | | typeof React.Component
113 | | React.ReactElement
114 | ```
115 |
116 | ### NodeMeta
117 |
118 | ```ts
119 | { depth: number; indexOrName?: number | string; parent?: Record | Array; parentPath: string[], currentPath: string[] }
120 | ```
121 |
122 | ## Editable
123 |
124 | ### How to generate object/array
125 |
126 | The editor uses `JSON.parse()`. While in edit mode, you can enter `({})` or `([])`, which will cause the result of eval to become a new object or array.
127 |
128 | > `{}` and `[]` will be auto convert to `({})`,`([])`
129 |
130 | ### How the editor works
131 |
132 | This component does not perform any cloning operations, so every step of the operation is carried out on the original object. If cloning is required, please handle it yourself.
133 |
134 | ### Edit keyboard shortcuts
135 |
136 | When element is editable:
137 |
138 | - `Ctrl/Cmd+Click` => Edit Mode
139 | - `Enter` => Submit
140 | - `Esc` => Cancel
141 |
142 | ## Custom themes
143 |
144 | Below are the default theme variables that you can easily customize to fit your needs.
145 |
146 | ```css
147 | .json-view {
148 | color: #4d4d4d;
149 | --json-property: #009033;
150 | --json-index: #676dff;
151 | --json-number: #676dff;
152 | --json-string: #b2762e;
153 | --json-boolean: #dc155e;
154 | --json-null: #dc155e;
155 | }
156 | .json-view .json-view--property {
157 | color: var(--json-property);
158 | }
159 | .json-view .json-view--index {
160 | color: var(--json-index);
161 | }
162 | .json-view .json-view--number {
163 | color: var(--json-number);
164 | }
165 | .json-view .json-view--string {
166 | color: var(--json-string);
167 | }
168 | .json-view .json-view--boolean {
169 | color: var(--json-boolean);
170 | }
171 | .json-view .json-view--null {
172 | color: var(--json-null);
173 | }
174 | ```
175 |
176 | ## Why
177 |
178 | react-json-view does not support React 18.
179 |
180 | ## Todo
181 |
182 | - [x] copy (enableClipboard)
183 | - [x] css
184 | - [x] collapse at a particular depth (collapsed)
185 | - [x] editable
186 | - [x] add
187 | - [x] edit
188 | - [x] delete
189 | - [x] onChange
190 | - [ ] onSelect
191 | - [x] dark mode
192 | - [x] custom icon
193 | - [x] export default icons
194 | - [x] more usability/scenarios
195 | - [ ] gif guide
196 | - [x] more color themes(dark)
197 | - [x] collapse objects callback
198 | - [x] editable option
199 | - [x] advance customization
200 | - [ ] access internal actions
201 | - [ ] map/set viewer
202 | - [ ] display data type
203 | - [x] display object size
204 | - [ ] handle circle loop
205 | - [x] redesign docs
206 | - [x] truncate long strings
207 | - [x] custom `stringify`
208 | - [x] split large array
209 |
210 | * tree?
211 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react18-json-view",
3 | "version": "0.2.9",
4 | "type": "module",
5 | "description": "JSON viewer for react18",
6 | "main": "dist/cjs/index.cjs",
7 | "types": "dist/index",
8 | "module": "dist/es/index.mjs",
9 | "brower": "dist/es/index.mjs",
10 | "scripts": {
11 | "dev": "rollup -cw",
12 | "dev:website": "turbo run dev --filter=website...",
13 | "build": "rollup -c",
14 | "storybook": "storybook dev -p 6006",
15 | "build-storybook": "storybook build",
16 | "prepare": "husky install",
17 | "clean": "rm -rf dist",
18 | "format": "prettier --write .",
19 | "prebuild": "npm run clean",
20 | "prepublishOnly": "npm run build"
21 | },
22 | "files": [
23 | "src",
24 | "dist"
25 | ],
26 | "devDependencies": {
27 | "@rollup/plugin-commonjs": "^24.0.0",
28 | "@rollup/plugin-node-resolve": "^15.0.1",
29 | "@rollup/plugin-url": "^8.0.1",
30 | "@storybook/addon-essentials": "^7.3.2",
31 | "@storybook/addon-interactions": "^7.3.2",
32 | "@storybook/addon-links": "^7.3.2",
33 | "@storybook/addon-styling": "^1.3.6",
34 | "@storybook/blocks": "^7.3.2",
35 | "@storybook/react": "^7.3.2",
36 | "@storybook/react-vite": "^7.3.2",
37 | "@svgr/rollup": "^8.1.0",
38 | "@types/node": "^18.11.18",
39 | "@types/react": "^18.0.26",
40 | "@types/react-dom": "^18.0.8",
41 | "autoprefixer": "^10.4.14",
42 | "husky": "^8.0.3",
43 | "lint-staged": "^13.1.0",
44 | "postcss": "^8.4.23",
45 | "postcss-loader": "^7.2.4",
46 | "prettier": "^2.8.2",
47 | "prettier-plugin-tailwindcss": "^0.2.8",
48 | "react": "^18.2.0",
49 | "rollup": "^3.9.1",
50 | "rollup-plugin-peer-deps-external": "^2.2.4",
51 | "rollup-plugin-typescript2": "^0.34.1",
52 | "storybook": "^7.3.2",
53 | "tailwindcss": "^3.3.2",
54 | "tslib": "^2.7.0",
55 | "turbo": "^1.10.13",
56 | "typescript": "^5.0.4",
57 | "vite-plugin-svgr": "^3.2.0"
58 | },
59 | "peerDependencies": {
60 | "react": ">=16.8.0"
61 | },
62 | "lint-staged": {
63 | "**/*": "prettier --write --ignore-unknown"
64 | },
65 | "license": "MIT",
66 | "author": "Suni",
67 | "keywords": [
68 | "function component",
69 | "interactive",
70 | "interactive-json",
71 | "json",
72 | "json-component",
73 | "json-display",
74 | "json-tree",
75 | "json-view",
76 | "json-viewer",
77 | "json-inspector",
78 | "json-tree",
79 | "react",
80 | "react18",
81 | "react-component",
82 | "react-json",
83 | "theme",
84 | "tree",
85 | "tree-view",
86 | "treeview"
87 | ],
88 | "homepage": "https://github.com/YYsuni/react18-json-view#readme",
89 | "repository": {
90 | "type": "git",
91 | "url": "git+https://github.com/YYsuni/react18-json-view.git"
92 | },
93 | "bugs": {
94 | "url": "https://github.com/YYsuni/react18-json-view/issues"
95 | },
96 | "sideEffects": [
97 | "*.css"
98 | ],
99 | "dependencies": {
100 | "copy-to-clipboard": "^3.3.3"
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'website'
3 | - '.'
4 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/prettier.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | jsxSingleQuote: true,
3 | jsxBracketSameLine: true,
4 | printWidth: 160,
5 | singleQuote: true,
6 | trailingComma: 'none',
7 | useTabs: true,
8 | tabWidth: 2,
9 | semi: false,
10 | arrowParens: 'avoid',
11 | bracketSameLine: true
12 | }
13 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import { nodeResolve } from '@rollup/plugin-node-resolve'
3 | import commonjs from '@rollup/plugin-commonjs'
4 | import external from 'rollup-plugin-peer-deps-external'
5 | import svgr from '@svgr/rollup'
6 | import url from '@rollup/plugin-url'
7 |
8 | const plugins = [
9 | external(),
10 | url(),
11 | svgr({
12 | svgoConfig: {
13 | plugins: [
14 | {
15 | name: 'preset-default',
16 | params: {
17 | overrides: {
18 | removeViewBox: false
19 | }
20 | }
21 | }
22 | ]
23 | }
24 | }),
25 | typescript({ useTsconfigDeclarationDir: true }),
26 | nodeResolve(),
27 | commonjs()
28 | ]
29 |
30 | /** @type {import('rollup').InputOption} */
31 | export default {
32 | input: ['src/index.tsx'],
33 | output: [
34 | {
35 | dir: 'dist/cjs',
36 | format: 'cjs',
37 | sourcemap: true,
38 | entryFileNames: '[name].cjs',
39 | chunkFileNames: '[name]-[hash].cjs'
40 | },
41 | {
42 | dir: 'dist/es',
43 | format: 'es',
44 | sourcemap: true,
45 | entryFileNames: '[name].mjs',
46 | chunkFileNames: '[name]-[hash].mjs'
47 | }
48 | ],
49 | plugins
50 | }
51 |
--------------------------------------------------------------------------------
/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYsuni/react18-json-view/b8513baf7ee215ee6105aa05a6c6c71c6515ee4e/sample.png
--------------------------------------------------------------------------------
/src/components/copy-button.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useState } from 'react'
2 | import { ReactComponent as CopySVG } from '../svgs/copy.svg'
3 | import { ReactComponent as CopiedSVG } from '../svgs/copied.svg'
4 | import { JsonViewContext } from './json-view'
5 | import { writeClipboard } from 'src/utils'
6 | import { NodeMeta } from 'src/types'
7 |
8 | interface Props {
9 | node: any
10 | nodeMeta: NodeMeta
11 | }
12 |
13 | export default function CopyButton({ node, nodeMeta }: Props) {
14 | const { customizeCopy, CopyComponent, CopiedComponent } = useContext(JsonViewContext)
15 |
16 | const [copied, setCopied] = useState(false)
17 |
18 | const copyHandler = (event: React.MouseEvent) => {
19 | event.stopPropagation()
20 |
21 | const value = customizeCopy(node, nodeMeta)
22 |
23 | if (typeof value === 'string' && value) {
24 | writeClipboard(value)
25 | }
26 |
27 | setCopied(true)
28 | setTimeout(() => setCopied(false), 3000)
29 | }
30 |
31 | return copied ? (
32 | typeof CopiedComponent === 'function' ? (
33 |
34 | ) : (
35 |
36 | )
37 | ) : typeof CopyComponent === 'function' ? (
38 |
39 | ) : (
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/json-node.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useRef, useState, isValidElement, useMemo, useCallback } from 'react'
2 | import { JsonViewContext } from './json-view'
3 | import {
4 | customCopy,
5 | customDelete,
6 | customEdit,
7 | editableDelete,
8 | editableEdit,
9 | isObject,
10 | isReactComponent,
11 | safeCall,
12 | stringifyForCopying,
13 | resolveEvalFailedNewValue,
14 | customMatchesURL
15 | } from '../utils'
16 | import ObjectNode from './object-node'
17 | import LongString from './long-string'
18 | import CopyButton from './copy-button'
19 | import { ReactComponent as EditSVG } from '../svgs/edit.svg'
20 | import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
21 | import { ReactComponent as DoneSVG } from '../svgs/done.svg'
22 | import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
23 | import { ReactComponent as LinkSVG } from '../svgs/link.svg'
24 | import type { CustomizeNode, CustomizeOptions } from '../types'
25 |
26 | interface Props {
27 | node: any
28 | depth: number
29 | deleteHandle?: (indexOrName: string | number, parentPath: string[]) => void
30 | editHandle?: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void
31 | indexOrName?: number | string
32 | parent?: Record | Array
33 | parentPath: string[]
34 | }
35 |
36 | export default function JsonNode({ node, depth, deleteHandle: _deleteHandle, indexOrName, parent, editHandle, parentPath }: Props) {
37 | // prettier-ignore
38 | const { collapseStringsAfterLength, enableClipboard, editable, src, onDelete, onChange, customizeNode, matchesURL, urlRegExp, EditComponent, DoneComponent, CancelComponent, CustomOperation } = useContext(JsonViewContext)
39 |
40 | let customReturn: ReturnType | undefined
41 | if (typeof customizeNode === 'function') customReturn = safeCall(customizeNode, [{ node, depth, indexOrName }])
42 |
43 | if (customReturn) {
44 | if (isValidElement(customReturn)) return customReturn
45 | else if (isReactComponent(customReturn)) {
46 | const CustomComponent = customReturn
47 | return
48 | }
49 | }
50 |
51 | if (Array.isArray(node) || isObject(node)) {
52 | return (
53 |
62 | )
63 | } else {
64 | const type = typeof node
65 | const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
66 |
67 | const [editing, setEditing] = useState(false)
68 | const [deleting, setDeleting] = useState(false)
69 | const valueRef = useRef(null)
70 |
71 | const edit = () => {
72 | setEditing(true)
73 | setTimeout(() => {
74 | window.getSelection()?.selectAllChildren(valueRef.current!)
75 | valueRef.current?.focus()
76 | })
77 | }
78 |
79 | const done = useCallback(() => {
80 | let newValue = valueRef.current!.innerText
81 |
82 | try {
83 | const parsedValue = JSON.parse(newValue)
84 |
85 | if (editHandle) editHandle(indexOrName!, parsedValue, node, parentPath)
86 | } catch (e) {
87 | const trimmedStringValue = resolveEvalFailedNewValue(type, newValue)
88 | if (editHandle) editHandle(indexOrName!, trimmedStringValue, node, parentPath)
89 | }
90 |
91 | setEditing(false)
92 | }, [editHandle, indexOrName, node, parentPath, type])
93 | const cancel = () => {
94 | setEditing(false)
95 | setDeleting(false)
96 | }
97 | const deleteHandle = () => {
98 | setDeleting(false)
99 | if (_deleteHandle) _deleteHandle(indexOrName!, parentPath)
100 | if (onDelete)
101 | onDelete({
102 | value: node,
103 | depth,
104 | src,
105 | indexOrName: indexOrName!,
106 | parentType: Array.isArray(parent) ? 'array' : 'object',
107 | parentPath
108 | })
109 | if (onChange)
110 | onChange({
111 | depth,
112 | src,
113 | indexOrName: indexOrName!,
114 | parentType: Array.isArray(parent) ? 'array' : 'object',
115 | type: 'delete',
116 | parentPath
117 | })
118 | }
119 |
120 | const handleKeyDown = useCallback(
121 | (event: React.KeyboardEvent) => {
122 | if (event.key === 'Enter') {
123 | event.preventDefault()
124 | done()
125 | } else if (event.key === 'Escape') {
126 | cancel()
127 | }
128 | },
129 | [done]
130 | )
131 |
132 | const isEditing = editing || deleting
133 | const ctrlClick =
134 | !isEditing && editableEdit(editable) && customEdit(customReturn as CustomizeOptions | undefined) && editHandle
135 | ? (event: React.MouseEvent) => {
136 | if (event.ctrlKey || event.metaKey) edit()
137 | }
138 | : undefined
139 | const Icons = (
140 | <>
141 | {isEditing &&
142 | (typeof DoneComponent === 'function' ? (
143 |
144 | ) : (
145 |
146 | ))}
147 | {isEditing &&
148 | (typeof CancelComponent === 'function' ? (
149 |
150 | ) : (
151 |
152 | ))}
153 |
154 | {!isEditing && enableClipboard && customCopy(customReturn as CustomizeOptions | undefined) && (
155 |
156 | )}
157 | {!isEditing && matchesURL && type === 'string' && urlRegExp.test(node) && customMatchesURL(customReturn as CustomizeOptions | undefined) && (
158 |
159 |
160 |
161 | )}
162 |
163 | {!isEditing &&
164 | editableEdit(editable) &&
165 | customEdit(customReturn as CustomizeOptions | undefined) &&
166 | editHandle &&
167 | (typeof EditComponent === 'function' ? (
168 |
169 | ) : (
170 |
171 | ))}
172 | {!isEditing && editableDelete(editable) && customDelete(customReturn as CustomizeOptions | undefined) && _deleteHandle && (
173 | setDeleting(true)} />
174 | )}
175 | {typeof CustomOperation === 'function' ? : null}
176 | >
177 | )
178 |
179 | let className = 'json-view--string'
180 |
181 | switch (type) {
182 | case 'number':
183 | case 'bigint':
184 | className = 'json-view--number'
185 | break
186 | case 'boolean':
187 | className = 'json-view--boolean'
188 | break
189 | case 'object':
190 | className = 'json-view--null'
191 | break
192 | }
193 |
194 | if (typeof (customReturn as CustomizeOptions)?.className === 'string') className += ' ' + (customReturn as CustomizeOptions).className
195 |
196 | if (deleting) className += ' json-view--deleting'
197 |
198 | let displayValue = String(node)
199 | if (type === 'bigint') displayValue += 'n'
200 |
201 | const EditingElement = useMemo(
202 | () => (
203 |
210 | ),
211 | [displayValue, type, handleKeyDown]
212 | )
213 |
214 | if (type === 'string')
215 | return (
216 | <>
217 | {editing ? (
218 | EditingElement
219 | ) : node.length > collapseStringsAfterLength ? (
220 |
221 | ) : (
222 |
223 | "{displayValue}"
224 |
225 | )}
226 |
227 | {Icons}
228 | >
229 | )
230 | else {
231 | return (
232 | <>
233 | {editing ? (
234 | EditingElement
235 | ) : (
236 |
237 | {displayValue}
238 |
239 | )}
240 |
241 | {Icons}
242 | >
243 | )
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/components/json-view.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement, createContext, useCallback, useEffect, useState } from 'react'
2 | import JsonNode from './json-node'
3 | import type { Collapsed, CustomizeCollapseStringUI, CustomizeNode, DisplaySize, Editable, NodeMeta } from '../types'
4 | import { stringifyForCopying } from '../utils'
5 |
6 | type OnEdit = (params: {
7 | newValue: any
8 | oldValue: any
9 | depth: number
10 | src: any
11 | indexOrName: string | number
12 | parentType: 'object' | 'array' | null
13 | parentPath: string[]
14 | }) => void
15 | type OnDelete = (params: {
16 | value: any
17 | indexOrName: string | number
18 | depth: number
19 | src: any
20 | parentType: 'object' | 'array' | null
21 | parentPath: string[]
22 | }) => void
23 | type OnAdd = (params: { indexOrName: string | number; depth: number; src: any; parentType: 'object' | 'array'; parentPath: string[] }) => void
24 | type OnChange = (params: {
25 | indexOrName: string | number
26 | depth: number
27 | src: any
28 | parentType: 'object' | 'array' | null
29 | type: 'add' | 'edit' | 'delete'
30 | parentPath: string[]
31 | }) => void
32 | type OnCollapse = (params: { isCollapsing: boolean; node: Record | Array; indexOrName: string | number | undefined; depth: number }) => void
33 |
34 | export const defaultURLRegExp = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
35 |
36 | export const JsonViewContext = createContext({
37 | src: undefined as any,
38 |
39 | collapseStringsAfterLength: 99,
40 | collapseStringMode: 'directly' as 'directly' | 'word' | 'address',
41 | customizeCollapseStringUI: undefined as CustomizeCollapseStringUI | undefined,
42 |
43 | collapseObjectsAfterLength: 20,
44 | collapsed: false as Collapsed,
45 | onCollapse: undefined as OnCollapse | undefined,
46 | enableClipboard: true,
47 |
48 | editable: false as Editable,
49 | onEdit: undefined as OnEdit | undefined,
50 | onDelete: undefined as OnDelete | undefined,
51 | onAdd: undefined as OnAdd | undefined,
52 | onChange: undefined as OnChange | undefined,
53 |
54 | forceUpdate: () => {},
55 |
56 | customizeNode: undefined as CustomizeNode | undefined,
57 | customizeCopy: (() => {}) as (node: any, nodeMeta?: NodeMeta) => any,
58 |
59 | displaySize: undefined as DisplaySize,
60 | displayArrayIndex: true,
61 |
62 | matchesURL: false,
63 | urlRegExp: defaultURLRegExp,
64 |
65 | ignoreLargeArray: false,
66 |
67 | CopyComponent: undefined as
68 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
69 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
70 | | undefined,
71 | CopiedComponent: undefined as
72 | | React.FC<{ className: string; style: React.CSSProperties }>
73 | | React.Component<{ className: string; style: React.CSSProperties }>
74 | | undefined,
75 | EditComponent: undefined as
76 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
77 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
78 | | undefined,
79 | CancelComponent: undefined as
80 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
81 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
82 | | undefined,
83 | DoneComponent: undefined as
84 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
85 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
86 | | undefined,
87 | CustomOperation: undefined as React.FC<{ node: any }> | React.Component<{ node: any }> | undefined
88 | })
89 |
90 | export interface JsonViewProps {
91 | src: any
92 |
93 | collapseStringsAfterLength?: number
94 | collapseStringMode?: 'directly' | 'word' | 'address'
95 | customizeCollapseStringUI?: CustomizeCollapseStringUI
96 |
97 | collapseObjectsAfterLength?: number
98 | collapsed?: Collapsed
99 | onCollapse?: OnCollapse
100 |
101 | enableClipboard?: boolean
102 |
103 | editable?: Editable
104 | onEdit?: OnEdit
105 | onDelete?: OnDelete
106 | onAdd?: OnAdd
107 | onChange?: OnChange
108 |
109 | customizeNode?: CustomizeNode
110 | customizeCopy?: (node: any, nodeMeta?: NodeMeta) => any
111 |
112 | dark?: boolean
113 | theme?: 'default' | 'a11y' | 'github' | 'vscode' | 'atom' | 'winter-is-coming'
114 |
115 | displaySize?: DisplaySize
116 | displayArrayIndex?: boolean
117 |
118 | style?: React.CSSProperties
119 | className?: string
120 |
121 | matchesURL?: boolean
122 | urlRegExp?: RegExp
123 |
124 | ignoreLargeArray?: boolean
125 |
126 | CopyComponent?:
127 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
128 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
129 | CopiedComponent?: React.FC<{ className: string; style: React.CSSProperties }> | React.Component<{ className: string; style: React.CSSProperties }>
130 |
131 | EditComponent?:
132 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
133 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
134 |
135 | CancelComponent?:
136 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
137 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
138 |
139 | DoneComponent?:
140 | | React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
141 | | React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
142 |
143 | CustomOperation?: React.FC<{ node: any }> | React.Component<{ node: any }>
144 | }
145 |
146 | export default function JsonView({
147 | src: _src,
148 |
149 | collapseStringsAfterLength = 99,
150 | collapseStringMode = 'directly',
151 | customizeCollapseStringUI,
152 |
153 | collapseObjectsAfterLength = 99,
154 | collapsed,
155 | onCollapse,
156 |
157 | enableClipboard = true,
158 |
159 | editable = false,
160 | onEdit,
161 | onDelete,
162 | onAdd,
163 | onChange,
164 |
165 | dark = false,
166 | theme = 'default',
167 |
168 | customizeNode,
169 | customizeCopy = node => stringifyForCopying(node),
170 |
171 | displaySize,
172 | displayArrayIndex = true,
173 |
174 | style,
175 | className,
176 |
177 | matchesURL = false,
178 | urlRegExp = defaultURLRegExp,
179 |
180 | ignoreLargeArray = false,
181 |
182 | CopyComponent,
183 | CopiedComponent,
184 |
185 | EditComponent,
186 | CancelComponent,
187 | DoneComponent,
188 | CustomOperation
189 | }: JsonViewProps) {
190 | const [_, update] = useState(0)
191 | const forceUpdate = useCallback(() => update(state => ++state), [])
192 | const [src, setSrc] = useState(_src)
193 | useEffect(() => setSrc(_src), [_src])
194 | return (
195 |
235 |
238 | {
242 | setSrc(newValue)
243 | if (onEdit)
244 | onEdit({
245 | newValue,
246 | oldValue,
247 | depth: 1,
248 | src,
249 | indexOrName: indexOrName,
250 | parentType: null,
251 | parentPath: parentPath
252 | })
253 | if (onChange) onChange({ type: 'edit', depth: 1, src, indexOrName: indexOrName, parentType: null, parentPath: parentPath })
254 | }}
255 | deleteHandle={(indexOrName: number | string, parentPath: string[]) => {
256 | setSrc(undefined)
257 | if (onDelete)
258 | onDelete({
259 | value: src,
260 | depth: 1,
261 | src,
262 | indexOrName: indexOrName,
263 | parentType: null,
264 | parentPath: parentPath
265 | })
266 | if (onChange)
267 | onChange({
268 | depth: 1,
269 | src,
270 | indexOrName: indexOrName,
271 | parentType: null,
272 | type: 'delete',
273 | parentPath: parentPath
274 | })
275 | }}
276 | parentPath={[]}
277 | />
278 |
279 |
280 | )
281 | }
282 |
--------------------------------------------------------------------------------
/src/components/large-array-node.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useContext, useState } from 'react'
2 | import { JsonViewContext } from './json-view'
3 | import { customCopy, objectSize, ifDisplay } from '../utils'
4 | import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
5 | import CopyButton from './copy-button'
6 | import NameValue from './name-value'
7 | import type { CustomizeOptions } from '../types'
8 |
9 | interface Props {
10 | originNode: Array
11 | node: Array
12 | depth: number
13 | index: number
14 | deleteHandle?: (_: string | number, currentPath: string[]) => void
15 | customOptions?: CustomizeOptions
16 | startIndex: number
17 | parent?: Record | Array
18 | parentPath: string[]
19 | }
20 |
21 | export default function LargeArrayNode({ originNode, node, depth, index, deleteHandle: _deleteSelf, customOptions, startIndex, parent, parentPath }: Props) {
22 | const { enableClipboard, src, onEdit, onChange, forceUpdate, displaySize, CustomOperation } = useContext(JsonViewContext)
23 |
24 | const currentPath = [...parentPath, String(index)]
25 |
26 | const [fold, setFold] = useState(true)
27 |
28 | // Edit property
29 | const editHandle = useCallback(
30 | (indexOrName: number | string, newValue: any, oldValue: any) => {
31 | originNode[indexOrName as number] = newValue
32 | if (onEdit)
33 | onEdit({
34 | newValue,
35 | oldValue,
36 | depth,
37 | src,
38 | indexOrName,
39 | parentType: 'array',
40 | parentPath
41 | })
42 | if (onChange) onChange({ type: 'edit', depth, src, indexOrName, parentType: 'array', parentPath })
43 | forceUpdate()
44 | },
45 | [node, onEdit, onChange, forceUpdate]
46 | )
47 |
48 | // Delete property
49 | const deleteHandle = (index: number | string) => {
50 | originNode.splice(index as number, 1)
51 | if (_deleteSelf) _deleteSelf(index, parentPath)
52 | forceUpdate()
53 | }
54 |
55 | const Icons = (
56 | <>
57 | {!fold && (
58 | setFold(true)} className='jv-size-chevron'>
59 | {ifDisplay(displaySize, depth, fold) && {objectSize(node)} Items}
60 |
61 |
62 |
63 | )}
64 |
65 | {!fold && enableClipboard && customCopy(customOptions) && (
66 |
67 | )}
68 | {typeof CustomOperation === 'function' ? : null}
69 | >
70 | )
71 |
72 | return (
73 |
74 |
{'['}
75 |
76 | {Icons}
77 |
78 | {!fold ? (
79 |
80 | {node.map((n, i) => (
81 |
91 | ))}
92 |
93 | ) : (
94 |
97 | )}
98 |
99 |
{']'}
100 |
101 | )
102 | }
103 |
--------------------------------------------------------------------------------
/src/components/large-array.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from 'react'
2 | import { JsonViewContext } from './json-view'
3 | import { isCollapsed_largeArray, ifDisplay, editableAdd, editableDelete, customAdd, customCopy, customDelete } from '../utils'
4 | import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
5 | import CopyButton from './copy-button'
6 | import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
7 | import { ReactComponent as AddSVG } from '../svgs/add-square.svg'
8 | import { ReactComponent as DoneSVG } from '../svgs/done.svg'
9 | import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
10 | import type { CustomizeOptions } from '../types'
11 | import LargeArrayNode from './large-array-node'
12 |
13 | interface Props {
14 | node: Array
15 | depth: number
16 | indexOrName?: number | string
17 | deleteHandle?: (_: string | number, currentPath: string[]) => void
18 | customOptions?: CustomizeOptions
19 | parent?: Record | Array
20 | parentPath: string[]
21 | }
22 |
23 | export default function LargeArray({ node, depth, deleteHandle: _deleteSelf, indexOrName, customOptions, parent, parentPath }: Props) {
24 | const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
25 |
26 | const nestCollapsedArray: any[] = []
27 | for (let i = 0; i < node.length; i += 100) {
28 | nestCollapsedArray.push(node.slice(i, i + 100))
29 | }
30 |
31 | const { collapsed, enableClipboard, collapseObjectsAfterLength, editable, onDelete, src, onAdd, CustomOperation, onChange, forceUpdate, displaySize } =
32 | useContext(JsonViewContext)
33 |
34 | const [fold, setFold] = useState(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
35 | useEffect(() => {
36 | setFold(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
37 | }, [collapsed, collapseObjectsAfterLength])
38 |
39 | // Delete self
40 | const [deleting, setDeleting] = useState(false)
41 | const deleteSelf = () => {
42 | setDeleting(false)
43 | if (_deleteSelf) _deleteSelf(indexOrName!, parentPath)
44 | if (onDelete) onDelete({ value: node, depth, src, indexOrName: indexOrName!, parentType: 'array', parentPath })
45 | if (onChange)
46 | onChange({
47 | type: 'delete',
48 | depth,
49 | src,
50 | indexOrName: indexOrName!,
51 | parentType: 'array',
52 | parentPath
53 | })
54 | }
55 |
56 | // Add
57 | const [adding, setAdding] = useState(false)
58 | const add = () => {
59 | const arr = node as unknown as any[]
60 | arr.push(null)
61 | if (onAdd) onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath })
62 | if (onChange) onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath })
63 | forceUpdate()
64 | }
65 |
66 | const isEditing = deleting || adding
67 | const cancel = () => {
68 | setDeleting(false)
69 | setAdding(false)
70 | }
71 |
72 | const Icons = (
73 | <>
74 | {!fold && !isEditing && (
75 | setFold(true)} className='jv-size-chevron'>
76 | {ifDisplay(displaySize, depth, fold) && {node.length} Items}
77 |
78 |
79 |
80 | )}
81 |
82 | {isEditing && }
83 | {isEditing && }
84 |
85 | {!fold && !isEditing && enableClipboard && customCopy(customOptions) && (
86 |
87 | )}
88 | {!fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (
89 | {
92 | add()
93 | }}
94 | />
95 | )}
96 | {!fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (
97 | setDeleting(true)} />
98 | )}
99 | {typeof CustomOperation === 'function' ? : null}
100 | >
101 | )
102 |
103 | return (
104 | <>
105 | {'['}
106 |
107 | {Icons}
108 |
109 | {!fold ? (
110 |
111 | {nestCollapsedArray.map((item, index) => (
112 |
123 | ))}
124 |
125 | ) : (
126 |
129 | )}
130 |
131 | {']'}
132 |
133 | {fold && ifDisplay(displaySize, depth, fold) && (
134 | setFold(false)} className='jv-size'>
135 | {node.length} Items
136 |
137 | )}
138 | >
139 | )
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/long-string.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useRef, useState } from 'react'
2 | import { JsonViewContext } from './json-view'
3 |
4 | interface Props {
5 | str: string
6 | className: string
7 | ctrlClick: ((event: React.MouseEvent) => void) | undefined
8 | }
9 |
10 | const LongString = React.forwardRef(({ str, className, ctrlClick }, ref) => {
11 | let { collapseStringMode, collapseStringsAfterLength, customizeCollapseStringUI } = useContext(JsonViewContext)
12 | const [truncated, setTruncated] = useState(true)
13 | const strRef = useRef(null)
14 |
15 | collapseStringsAfterLength = collapseStringsAfterLength > 0 ? collapseStringsAfterLength : 0
16 |
17 | const str_show = str.replace(/\s+/g, ' ')
18 | const collapseStringUI =
19 | typeof customizeCollapseStringUI === 'function'
20 | ? customizeCollapseStringUI(str_show, truncated)
21 | : typeof customizeCollapseStringUI === 'string'
22 | ? customizeCollapseStringUI
23 | : '...'
24 |
25 | const clickToTruncateOrEdit = (event: React.MouseEvent) => {
26 | if ((event.ctrlKey || event.metaKey) && ctrlClick) {
27 | ctrlClick(event)
28 | } else {
29 | const selection = window.getSelection()
30 |
31 | if (selection && selection.anchorOffset !== selection.focusOffset && selection.anchorNode?.parentElement === strRef.current) return
32 |
33 | setTruncated(!truncated)
34 | }
35 | }
36 |
37 | if (str.length <= collapseStringsAfterLength)
38 | return (
39 |
40 | "{str}"
41 |
42 | )
43 |
44 | if (collapseStringMode === 'address')
45 | return str.length <= 10 ? (
46 |
47 | "{str}"
48 |
49 | ) : (
50 |
51 | "{truncated ? [str_show.slice(0, 6), collapseStringUI, str_show.slice(-4)] : str}"
52 |
53 | )
54 |
55 | if (collapseStringMode === 'directly') {
56 | return (
57 |
58 | "{truncated ? [str_show.slice(0, collapseStringsAfterLength), collapseStringUI] : str}"
59 |
60 | )
61 | }
62 |
63 | if (collapseStringMode === 'word') {
64 | let index_ahead = collapseStringsAfterLength
65 | let index_behind = collapseStringsAfterLength + 1
66 | let str_collapsed = str_show
67 | let count = 1
68 |
69 | while (true) {
70 | if (/\W/.test(str[index_ahead])) {
71 | str_collapsed = str.slice(0, index_ahead)
72 | break
73 | }
74 | if (/\W/.test(str[index_behind])) {
75 | str_collapsed = str.slice(0, index_behind)
76 | break
77 | }
78 | if (count === 6) {
79 | str_collapsed = str.slice(0, collapseStringsAfterLength)
80 | break
81 | }
82 |
83 | count++
84 | index_ahead--
85 | index_behind++
86 | }
87 |
88 | return (
89 |
90 | "{truncated ? [str_collapsed, collapseStringUI] : str}"
91 |
92 | )
93 | }
94 |
95 | return (
96 |
97 | "{str}"
98 |
99 | )
100 | })
101 |
102 | export default LongString
103 |
--------------------------------------------------------------------------------
/src/components/name-value.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | import { JsonViewContext } from './json-view'
3 |
4 | import JsonNode from './json-node'
5 |
6 | interface Props {
7 | indexOrName: number | string
8 | value: any
9 | depth: number
10 | parent?: Record | Array
11 | parentPath: string[]
12 | deleteHandle: (indexOrName: string | number, parentPath: string[]) => void
13 | editHandle: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void
14 | }
15 |
16 | export default function NameValue({ indexOrName, value, depth, deleteHandle, editHandle, parent, parentPath }: Props) {
17 | const { displayArrayIndex } = useContext(JsonViewContext)
18 | const isArray = Array.isArray(parent)
19 |
20 | return (
21 |
22 | {!isArray || (isArray && displayArrayIndex) ? (
23 | <>
24 | {indexOrName}:{' '}
25 | >
26 | ) : (
27 | <>>
28 | )}
29 | deleteHandle(indexOrName, parentPath)}
33 | editHandle={(indexOrName, newValue, oldValue, parentPath) => editHandle(indexOrName, newValue, oldValue, parentPath)}
34 | parent={parent}
35 | indexOrName={indexOrName}
36 | parentPath={parentPath}
37 | />
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/object-node.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useContext, useEffect, useRef, useState } from 'react'
2 | import { JsonViewContext } from './json-view'
3 | import { isObject, customAdd, customCopy, customDelete, editableAdd, editableDelete, isCollapsed, objectSize, ifDisplay } from '../utils'
4 | import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
5 | import CopyButton from './copy-button'
6 | import NameValue from './name-value'
7 | import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
8 | import { ReactComponent as AddSVG } from '../svgs/add-square.svg'
9 | import { ReactComponent as DoneSVG } from '../svgs/done.svg'
10 | import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
11 | import type { CustomizeOptions } from '../types'
12 | import LargeArray from './large-array'
13 |
14 | interface Props {
15 | node: Record | Array
16 | depth: number
17 | indexOrName?: number | string
18 | deleteHandle?: (_: string | number, currentPath: string[]) => void
19 | customOptions?: CustomizeOptions
20 | parent?: Record | Array
21 | parentPath: string[]
22 | }
23 |
24 | export default function ObjectNode({ node, depth, indexOrName, deleteHandle: _deleteSelf, customOptions, parent, parentPath }: Props) {
25 | const {
26 | collapsed,
27 | onCollapse,
28 | enableClipboard,
29 | ignoreLargeArray,
30 | collapseObjectsAfterLength,
31 | editable,
32 | onDelete,
33 | src,
34 | onAdd,
35 | onEdit,
36 | onChange,
37 | forceUpdate,
38 | displaySize,
39 | CustomOperation
40 | } = useContext(JsonViewContext)
41 |
42 | const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
43 |
44 | if (!ignoreLargeArray && Array.isArray(node) && node.length > 100) {
45 | return
46 | }
47 |
48 | const isPlainObject = isObject(node)
49 |
50 | const [fold, _setFold] = useState(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
51 |
52 | const setFold = (value: boolean) => {
53 | onCollapse?.({ isCollapsing: !value, node, depth, indexOrName })
54 | _setFold(value)
55 | }
56 |
57 | useEffect(() => {
58 | setFold(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
59 | }, [collapsed, collapseObjectsAfterLength])
60 |
61 | // Edit property
62 | const editHandle = useCallback(
63 | (indexOrName: number | string, newValue: any, oldValue: any) => {
64 | if (Array.isArray(node)) {
65 | node[+indexOrName] = newValue
66 | } else if (node) {
67 | node[indexOrName] = newValue
68 | }
69 | if (onEdit)
70 | onEdit({
71 | newValue,
72 | oldValue,
73 | depth,
74 | src,
75 | indexOrName: indexOrName,
76 | parentType: isPlainObject ? 'object' : 'array',
77 | parentPath: currentPath
78 | })
79 | if (onChange) onChange({ type: 'edit', depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath })
80 | forceUpdate()
81 | },
82 | [node, onEdit, onChange, forceUpdate]
83 | )
84 |
85 | // Delete property
86 | const deleteHandle = (indexOrName: number | string) => {
87 | if (Array.isArray(node)) {
88 | node.splice(+indexOrName, 1)
89 | } else if (node) {
90 | delete node[indexOrName]
91 | }
92 | forceUpdate()
93 | }
94 |
95 | // Delete self
96 | const [deleting, setDeleting] = useState(false)
97 | const deleteSelf = () => {
98 | setDeleting(false)
99 | if (_deleteSelf) _deleteSelf(indexOrName!, currentPath)
100 | if (onDelete) onDelete({ value: node, depth, src, indexOrName: indexOrName!, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath })
101 | if (onChange)
102 | onChange({
103 | type: 'delete',
104 | depth,
105 | src,
106 | indexOrName: indexOrName!,
107 | parentType: isPlainObject ? 'object' : 'array',
108 | parentPath: currentPath
109 | })
110 | }
111 |
112 | // Add
113 | const [adding, setAdding] = useState(false)
114 | const inputRef = useRef(null)
115 | const add = () => {
116 | if (isPlainObject) {
117 | const inputName = inputRef.current?.value
118 |
119 | if (inputName) {
120 | ;(node as Record)[inputName] = null
121 |
122 | if (inputRef.current) inputRef.current.value = ''
123 | setAdding(false)
124 |
125 | if (onAdd) onAdd({ indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath })
126 | if (onChange) onChange({ type: 'add', indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath })
127 | }
128 | } else if (Array.isArray(node)) {
129 | const arr = node as unknown as any[]
130 | arr.push(null)
131 | if (onAdd) onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath })
132 | if (onChange) onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath })
133 | }
134 | forceUpdate()
135 | }
136 | const handleAddKeyDown = (event: React.KeyboardEvent) => {
137 | if (event.key === 'Enter') {
138 | event.preventDefault()
139 | add()
140 | } else if (event.key === 'Escape') {
141 | cancel()
142 | }
143 | }
144 |
145 | const isEditing = deleting || adding
146 | const cancel = () => {
147 | setDeleting(false)
148 | setAdding(false)
149 | }
150 |
151 | const Icons = (
152 | <>
153 | {!fold && !isEditing && (
154 | setFold(true)} className='jv-size-chevron'>
155 | {ifDisplay(displaySize, depth, fold) && {objectSize(node)} Items}
156 |
157 |
158 |
159 | )}
160 |
161 | {adding && isPlainObject && }
162 |
163 | {isEditing && }
164 | {isEditing && }
165 |
166 | {!fold && !isEditing && enableClipboard && customCopy(customOptions) && (
167 |
168 | )}
169 | {!fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (
170 | {
173 | if (isPlainObject) {
174 | setAdding(true)
175 | setTimeout(() => inputRef.current?.focus())
176 | } else {
177 | add()
178 | }
179 | }}
180 | />
181 | )}
182 | {!fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (
183 | setDeleting(true)} />
184 | )}
185 | {typeof CustomOperation === 'function' ? : null}
186 | >
187 | )
188 |
189 | if (Array.isArray(node)) {
190 | return (
191 | <>
192 | {'['}
193 |
194 | {Icons}
195 |
196 | {!fold ? (
197 |
198 | {node.map((n, i) => (
199 |
209 | ))}
210 |
211 | ) : (
212 |
215 | )}
216 |
217 | {']'}
218 |
219 | {fold && ifDisplay(displaySize, depth, fold) && (
220 | setFold(false)} className='jv-size'>
221 | {objectSize(node)} Items
222 |
223 | )}
224 | >
225 | )
226 | } else if (isPlainObject) {
227 | return (
228 | <>
229 | {'{'}
230 |
231 | {Icons}
232 |
233 | {!fold ? (
234 |
235 | {Object.entries(node).map(([name, value]) => (
236 |
246 | ))}
247 |
248 | ) : (
249 |
252 | )}
253 |
254 | {'}'}
255 |
256 | {fold && ifDisplay(displaySize, depth, fold) && (
257 | setFold(false)} className='jv-size'>
258 | {objectSize(node)} Items
259 |
260 | )}
261 | >
262 | )
263 | } else {
264 | return {String(node)}
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/dark.css:
--------------------------------------------------------------------------------
1 | :is(.dark .json-view, .dark.json-view) {
2 | color: #d1d1d1;
3 | --json-property: #009033;
4 | --json-index: #5d75f2;
5 | --json-number: #5d75f2;
6 | --json-string: #c57e29;
7 | --json-boolean: #e4407b;
8 | --json-null: #e4407b;
9 | }
10 | :is(.dark .json-view_a11y, .dark.json-view_a11y) {
11 | color: #d1d1d1;
12 | --json-property: #ffd700;
13 | --json-index: #00e0e0;
14 | --json-number: #00e0e0;
15 | --json-string: #abe338;
16 | --json-boolean: #ffa07a;
17 | --json-null: #ffa07a;
18 | }
19 | :is(.dark .json-view_github, .dark.json-view_github) {
20 | color: #79b8ff;
21 | --json-property: #79b8ff;
22 | --json-index: #79b8ff;
23 | --json-number: #79b8ff;
24 | --json-string: #9ecbff;
25 | --json-boolean: #79b8ff;
26 | --json-null: #79b8ff;
27 | }
28 | :is(.dark .json-view_vscode, .dark.json-view_vscode) {
29 | color: #da70d6;
30 | --json-property: #9cdcfe;
31 | --json-index: #b5cea8;
32 | --json-number: #b5cea8;
33 | --json-string: #ce9178;
34 | --json-boolean: #569cd6;
35 | --json-null: #569cd6;
36 | }
37 | :is(.dark .json-view_atom, .dark.json-view_atom) {
38 | color: #abb2bf;
39 | --json-property: #e06c75;
40 | --json-index: #d19a66;
41 | --json-number: #d19a66;
42 | --json-string: #98c379;
43 | --json-boolean: #56b6c2;
44 | --json-null: #56b6c2;
45 | }
46 | :is(.dark .json-view_winter-is-coming, .dark.json-view_winter-is-coming) {
47 | color: #a7dbf7;
48 | --json-property: #91dacd;
49 | --json-index: #8dec95;
50 | --json-number: #8dec95;
51 | --json-string: #e0aff5;
52 | --json-boolean: #f29fd8;
53 | --json-null: #f29fd8;
54 | }
55 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const src: string
3 | export default src
4 | export const ReactComponent: React.FC>
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import JsonView, { defaultURLRegExp, JsonViewProps } from './components/json-view'
2 | import { stringifyForCopying as stringify } from './utils'
3 |
4 | import { ReactComponent as EditSVG } from './svgs/edit.svg'
5 | import { ReactComponent as DeleteSVG } from './svgs/trash.svg'
6 | import { ReactComponent as DoneSVG } from './svgs/done.svg'
7 | import { ReactComponent as CancelSVG } from './svgs/cancel.svg'
8 | import { ReactComponent as CopySVG } from './svgs/copy.svg'
9 | import { ReactComponent as CopiedSVG } from './svgs/copied.svg'
10 | import { ReactComponent as LinkSVG } from './svgs/link.svg'
11 |
12 | export { JsonView as default, stringify, defaultURLRegExp, EditSVG, DeleteSVG, DoneSVG, CancelSVG, CopySVG, CopiedSVG, LinkSVG }
13 | export type { JsonViewProps }
14 |
--------------------------------------------------------------------------------
/src/stories/editable.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Meta, StoryObj } from '@storybook/react'
3 | import JsonView from '../index'
4 | import { argTypes, largeArray } from './share'
5 | import { stringifyForCopying } from '../utils'
6 |
7 | type TYPE_FC = typeof JsonView
8 |
9 | export default {
10 | title: 'Editable',
11 | component: JsonView,
12 |
13 | argTypes,
14 | args: {
15 | enableClipboard: true,
16 | editable: true,
17 | src: {
18 | string: 'string',
19 | longString: 'long string long string long string long string long string long string',
20 | number: 123456,
21 | boolean: false,
22 | null: null,
23 | func: function () {
24 | console.log('Hello World')
25 | },
26 | Symbol: Symbol('JSON View'),
27 | obj: {
28 | k1: 123,
29 | k2: '123',
30 | k3: false,
31 | nested: {
32 | k4: 'nested'
33 | }
34 | },
35 | arr: ['string', 123456, false, null]
36 | },
37 | onEdit: ({ newValue, src, oldValue, indexOrName, parentPath }) => {
38 | console.log('[onEdit]', indexOrName, newValue, oldValue, src, parentPath)
39 | },
40 | onDelete: ({ value, src, indexOrName, parentPath }) => {
41 | console.log('[onDelete]', indexOrName, value, src, parentPath)
42 | },
43 | onAdd: ({ src, indexOrName, parentPath }) => {
44 | console.log('[onAdd]', indexOrName, src, parentPath)
45 | },
46 | onChange: ({ src, indexOrName, parentPath }) => {
47 | console.log('[onChange]', indexOrName, src, parentPath)
48 | }
49 | },
50 | decorators: [
51 | Story => (
52 |
59 | )
60 | ]
61 | } as Meta
62 |
63 | export const Primary: StoryObj = {}
64 |
65 | export const DisplaySize: StoryObj = {
66 | args: {
67 | src: {
68 | string: 'string',
69 | longString: 'long string long string long string long string long string long string',
70 | number: 123456,
71 | boolean: false,
72 | null: null,
73 | func: function () {},
74 | Symbol: Symbol('JSON View'),
75 | arr: ['string', 123456, false, null],
76 | nest: {
77 | nest: {
78 | nest: {
79 | nest: {
80 | over: 'over'
81 | }
82 | }
83 | }
84 | }
85 | },
86 | displaySize: 'collapsed'
87 | }
88 | }
89 |
90 | export const XS: StoryObj = {
91 | decorators: [
92 | Story => (
93 |
94 |
95 |
96 | )
97 | ]
98 | }
99 | export const LG: StoryObj = {
100 | decorators: [
101 | Story => (
102 |
103 |
104 |
105 | )
106 | ]
107 | }
108 | export const XL: StoryObj = {
109 | decorators: [
110 | Story => (
111 |
112 |
113 |
114 | )
115 | ]
116 | }
117 |
118 | export const String: StoryObj = {
119 | args: {
120 | src: 'string'
121 | }
122 | }
123 |
124 | export const LongString: StoryObj = {
125 | args: {
126 | src: 'long string long string long string long string'
127 | }
128 | }
129 |
130 | export const Number: StoryObj = {
131 | args: {
132 | src: 12312312321
133 | }
134 | }
135 |
136 | export const Null: StoryObj = {
137 | args: {
138 | src: null
139 | }
140 | }
141 |
142 | export const Boolean: StoryObj = {
143 | args: {
144 | src: true
145 | }
146 | }
147 |
148 | export const Undefined: StoryObj = {
149 | args: {
150 | src: undefined
151 | }
152 | }
153 |
154 | export const Collapsed_Boolean: StoryObj = {
155 | args: {
156 | src: {
157 | string: 'string',
158 | longString: 'long string long string long string long string long string long string',
159 | number: 123456,
160 | boolean: false,
161 | null: null,
162 | func: function () {},
163 | Symbol: Symbol('JSON View'),
164 | obj: {
165 | k1: 123,
166 | k2: '123',
167 | k3: false
168 | },
169 | arr: ['string', 123456, false, null]
170 | },
171 | collapsed: true
172 | },
173 | decorators: [
174 | Story => (
175 |
176 |
177 |
178 | )
179 | ]
180 | }
181 |
182 | export const Collapsed_Number: StoryObj = {
183 | args: {
184 | src: {
185 | boolean: false,
186 | null: null,
187 | obj: {
188 | k1: 123,
189 | k2: '123',
190 | k3: false,
191 | k4: {
192 | k: {
193 | k: {
194 | k: 'k5'
195 | }
196 | }
197 | }
198 | },
199 | arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
200 | },
201 | collapsed: 2
202 | },
203 | decorators: [
204 | Story => (
205 |
206 |
207 |
208 | )
209 | ]
210 | }
211 |
212 | export const Editable_Options: StoryObj = {
213 | args: {
214 | src: {
215 | boolean: false,
216 | null: null,
217 | obj: {
218 | k1: 123,
219 | k2: '123',
220 | k3: false,
221 | k4: {
222 | k: {
223 | k: {
224 | k: 'k5'
225 | }
226 | }
227 | }
228 | },
229 | arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
230 | },
231 | editable: {
232 | add: false,
233 | edit: true,
234 | delete: false
235 | }
236 | },
237 | decorators: [
238 | Story => (
239 |
240 |
241 |
242 | )
243 | ]
244 | }
245 |
246 | export const MatchesURL: StoryObj = {
247 | args: {
248 | src: {
249 | string: 'string',
250 | link: 'https://www.google.com/',
251 | number: 123456,
252 | boolean: false,
253 | null: null,
254 | func: function () {},
255 | Symbol: Symbol('JSON View'),
256 | obj: {
257 | k1: 123,
258 | k2: '123',
259 | k3: false
260 | },
261 | arr: ['string', 123456, false, null]
262 | },
263 | matchesURL: true,
264 | editable: true
265 | }
266 | }
267 |
268 | export const CustomizeCopy: StoryObj = {
269 | args: {
270 | src: {
271 | string: 'string',
272 | link: 'https://www.google.com/',
273 | number: 123456,
274 | boolean: false,
275 | null: null,
276 | func: function () {},
277 | Symbol: Symbol('JSON View'),
278 | obj: {
279 | k1: 123,
280 | k2: '123',
281 | k3: false
282 | },
283 | arr: ['string', 123456, false, null]
284 | },
285 | editable: true,
286 | customizeCopy: (node: any) => stringifyForCopying(node, 4)
287 | }
288 | }
289 |
290 | export const LargeArray: StoryObj = {
291 | args: {
292 | src: {
293 | obj: {
294 | k1: 123,
295 | k2: '123',
296 | k3: false
297 | },
298 | largeArray: largeArray
299 | },
300 | editable: true,
301 | displaySize: true,
302 | collapsed: 2
303 | }
304 | }
305 |
306 | export const CustomIcons: StoryObj = {
307 | args: {
308 | src: {
309 | obj: {
310 | k1: 123,
311 | k2: '123',
312 | k3: false
313 | }
314 | },
315 | editable: true,
316 | displaySize: true,
317 | CopyComponent: ({ onClick, className }) => (
318 |
321 | ),
322 | CopiedComponent: ({ className, style }) => (
323 |
326 | ),
327 | EditComponent: ({ onClick, className }) => (
328 |
329 | ),
330 | CancelComponent: ({ onClick, className }) => (
331 |
332 | ),
333 | DoneComponent: ({ onClick, className }) => (
334 |
335 | ),
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/src/stories/json-view.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Meta, StoryObj } from '@storybook/react'
3 | import JsonView from '../index'
4 | import { argTypes, largeArray } from './share'
5 | import { stringifyForCopying } from '../utils'
6 |
7 | type TYPE_FC = typeof JsonView
8 |
9 | export default {
10 | title: 'JSON View',
11 | component: JsonView,
12 |
13 | argTypes,
14 | decorators: [
15 | Story => (
16 |
23 | )
24 | ]
25 | } as Meta
26 |
27 | export const Primary: StoryObj = {
28 | args: {
29 | src: {
30 | string: 'string',
31 | longString: 'long string long string long string long string long string long string',
32 | number: 123456,
33 | boolean: false,
34 | null: null,
35 | func: function () {},
36 | Symbol: Symbol('JSON View'),
37 | obj: {
38 | k1: 123,
39 | k2: '123',
40 | k3: false
41 | },
42 | arr: ['string', 123456, false, null]
43 | }
44 | }
45 | }
46 |
47 | export const DisplaySize: StoryObj = {
48 | args: {
49 | src: {
50 | string: 'string',
51 | longString: 'long string long string long string long string long string long string',
52 | number: 123456,
53 | boolean: false,
54 | null: null,
55 | func: function () {},
56 | Symbol: Symbol('JSON View'),
57 | arr: ['string', 123456, false, null]
58 | },
59 | displaySize: true
60 | }
61 | }
62 |
63 | export const SM: StoryObj = {
64 | args: {
65 | src: {
66 | string: 'string',
67 | longString: 'long string long string long string long string long string long string',
68 | number: 123456,
69 | boolean: false,
70 | null: null,
71 | func: function () {},
72 | Symbol: Symbol('JSON View'),
73 | arr: ['string', 123456, false, null]
74 | },
75 | enableClipboard: true,
76 | displaySize: true
77 | },
78 | decorators: [
79 | Story => (
80 |
81 |
82 |
83 | )
84 | ]
85 | }
86 |
87 | export const XS: StoryObj = {
88 | args: {
89 | src: {
90 | string: 'string',
91 | longString: 'long string long string long string long string long string long string',
92 | number: 123456,
93 | boolean: false,
94 | null: null,
95 | func: function () {},
96 | Symbol: Symbol('JSON View'),
97 | arr: ['string', 123456, false, null]
98 | },
99 | enableClipboard: true,
100 | displaySize: true
101 | },
102 | decorators: [
103 | Story => (
104 |
105 |
106 |
107 | )
108 | ]
109 | }
110 | export const LG: StoryObj = {
111 | args: {
112 | src: {
113 | string: 'string',
114 | longString: 'long string long string long string long string long string long string',
115 | number: 123456,
116 | boolean: false,
117 | null: null,
118 | func: function () {},
119 | Symbol: Symbol('JSON View'),
120 | arr: ['string', 123456, false, null]
121 | },
122 | enableClipboard: true,
123 | displaySize: true
124 | },
125 | decorators: [
126 | Story => (
127 |
128 |
129 |
130 | )
131 | ]
132 | }
133 | export const XL: StoryObj = {
134 | args: {
135 | src: {
136 | string: 'string',
137 | longString: 'long string long string long string long string long string long string',
138 | number: 123456,
139 | boolean: false,
140 | null: null,
141 | func: function () {},
142 | Symbol: Symbol('JSON View'),
143 | arr: ['string', 123456, false, null]
144 | },
145 | enableClipboard: true,
146 | displaySize: true
147 | },
148 | decorators: [
149 | Story => (
150 |
151 |
152 |
153 | )
154 | ]
155 | }
156 |
157 | export const BigObject: StoryObj = {
158 | args: {
159 | src: {
160 | string: 'string',
161 | longString: 'long string long string long string long string',
162 | number: 123456,
163 | boolean: false,
164 | null: null,
165 | Date: new Date(),
166 | Symbol: Symbol('JSON View'),
167 | arr: ['string', 123456, false, null],
168 |
169 | bigObject: {
170 | abc0: 'abc',
171 | abc1: 'abc',
172 | abc2: 'abc',
173 | abc3: 'abc',
174 | abc4: 'abc',
175 | abc5: 'abc',
176 | abc6: 'abc',
177 | abc7: 'abc',
178 | abc8: 'abc',
179 | abc9: 'abc',
180 | abc10: 'abc',
181 | abc11: 'abc',
182 | abc12: 'abc',
183 | abc13: 'abc',
184 | abc14: 'abc',
185 | abc15: 'abc',
186 | abc16: 'abc',
187 | abc17: 'abc',
188 | abc18: 'abc',
189 | abc19: 'abc',
190 | abc20: 'abc',
191 | abc21: 'abc',
192 | abc22: 'abc',
193 | abc23: 'abc',
194 | abc24: 'abc',
195 | abc25: 'abc',
196 | abc26: 'abc',
197 | abc27: 'abc',
198 | abc28: 'abc',
199 | abc29: 'abc'
200 | }
201 | },
202 | collapseObjectsAfterLength: 20
203 | }
204 | }
205 |
206 | export const Array: StoryObj = {
207 | args: {
208 | src: ['string', 123456, false, null, { string: 'string', number: 123456, boolean: false, null: null, Date: new Date(), Symbol: Symbol('JSON View') }],
209 | collapsed: 1
210 | }
211 | }
212 |
213 | export const BigArray: StoryObj = {
214 | args: {
215 | src: [
216 | 'string',
217 | 123456,
218 | false,
219 | null,
220 | { string: 'string', number: 123456, boolean: false, null: null, Date: new Date(), Symbol: Symbol('JSON View') },
221 | [
222 | 'string',
223 | 123456,
224 | false,
225 | 'string',
226 | 123456,
227 | false,
228 | 'string',
229 | 123456,
230 | false,
231 | 'string',
232 | 123456,
233 | false,
234 | 'string',
235 | 123456,
236 | false,
237 | 'string',
238 | 123456,
239 | false,
240 | 'string',
241 | 123456,
242 | false,
243 | 'string',
244 | 123456,
245 | false
246 | ]
247 | ],
248 | collapseObjectsAfterLength: 20
249 | }
250 | }
251 |
252 | export const String: StoryObj = {
253 | args: {
254 | src: 'string'
255 | }
256 | }
257 |
258 | export const LongString: StoryObj = {
259 | args: {
260 | src: 'long string long string long string long string',
261 | collapseStringsAfterLength: 20,
262 | collapseStringMode: 'word'
263 | }
264 | }
265 |
266 | export const CustomizeCollapseStringUI: StoryObj = {
267 | args: {
268 | src: 'long string long string long string long string',
269 | collapseStringsAfterLength: 20,
270 | collapseStringMode: 'word',
271 | customizeCollapseStringUI: () => (
272 | <>
273 | ...
274 | >
275 | )
276 | }
277 | }
278 |
279 | export const Number: StoryObj = {
280 | args: {
281 | src: 12312312321
282 | }
283 | }
284 |
285 | export const Null: StoryObj = {
286 | args: {
287 | src: null
288 | }
289 | }
290 |
291 | export const Boolean: StoryObj = {
292 | args: {
293 | src: true
294 | }
295 | }
296 |
297 | export const Undefined: StoryObj = {
298 | args: {
299 | src: undefined
300 | }
301 | }
302 |
303 | export const Collapsed_Boolean: StoryObj = {
304 | args: {
305 | src: {
306 | string: 'string',
307 | longString: 'long string long string long string long string long string long string',
308 | number: 123456,
309 | boolean: false,
310 | null: null,
311 | func: function () {},
312 | Symbol: Symbol('JSON View'),
313 | obj: {
314 | k1: 123,
315 | k2: '123',
316 | k3: false
317 | },
318 | arr: ['string', 123456, false, null]
319 | },
320 | collapsed: true
321 | },
322 | decorators: [
323 | Story => (
324 |
325 |
326 |
327 | )
328 | ]
329 | }
330 |
331 | export const Collapsed_Number: StoryObj = {
332 | args: {
333 | src: {
334 | boolean: false,
335 | null: null,
336 | obj: {
337 | k1: 123,
338 | k2: '123',
339 | k3: false,
340 | k4: {
341 | k: {
342 | k: {
343 | k: 'k5'
344 | }
345 | }
346 | }
347 | },
348 | arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
349 | },
350 | collapsed: 2
351 | },
352 | decorators: [
353 | Story => (
354 |
355 |
356 |
357 | )
358 | ]
359 | }
360 |
361 | export const Collapsed_Function: StoryObj = {
362 | args: {
363 | src: {
364 | boolean: false,
365 | null: null,
366 | obj: {
367 | k1: 123,
368 | k2: '123',
369 | k3: false,
370 | k4: {
371 | k: {
372 | k: {
373 | k: 'k5'
374 | }
375 | }
376 | }
377 | },
378 | arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]],
379 | arr2: [1, 2]
380 | },
381 | collapsed: params => {
382 | if (params.depth > 3) return true
383 | if (params.depth > 2 && params.size > 4) return true
384 | return false
385 | }
386 | },
387 | decorators: [
388 | Story => (
389 |
390 |
391 |
392 | )
393 | ]
394 | }
395 |
396 | export const CustomizeNode: StoryObj = {
397 | args: {
398 | editable: true,
399 | src: {
400 | suni: 'suni',
401 | string: 'string',
402 | longString: 'long string long string long string long string long string long string',
403 | number: 123456,
404 | boolean: false,
405 | null: null,
406 | func: function () {},
407 | Symbol: Symbol('JSON View'),
408 | obj: {
409 | k1: 123,
410 | k2: '123',
411 | k3: false
412 | },
413 | arr: ['string', 123456, false, null, [1, 2, 3]]
414 | },
415 | customizeNode: params => {
416 | if (params.node === 'suni') return () => suni
417 | if (params.node === 123) return 123
418 | if (params.indexOrName === 'obj') return { add: false, delete: false, enableClipboard: false }
419 | if (params.node === 'string') return { edit: true, enableClipboard: false, delete: false }
420 | if (params.indexOrName === 'arr') return { collapsed: false }
421 | if (params.depth > 2) return { collapsed: true }
422 | if (params.indexOrName === 'func') return { className: 'underline' }
423 | }
424 | },
425 | decorators: [
426 | Story => (
427 |
428 |
429 |
430 | )
431 | ]
432 | }
433 |
434 | export const MatchesURL: StoryObj = {
435 | args: {
436 | src: {
437 | string: 'string',
438 | link: 'https://www.google.com/',
439 | number: 123456,
440 | boolean: false,
441 | null: null,
442 | func: function () {},
443 | Symbol: Symbol('JSON View'),
444 | obj: {
445 | k1: 123,
446 | k2: '123',
447 | k3: false
448 | },
449 | arr: ['string', 123456, false, null]
450 | },
451 | matchesURL: true
452 | }
453 | }
454 |
455 | export const CustomizeCopy: StoryObj = {
456 | args: {
457 | src: {
458 | string: 'string',
459 | link: 'https://www.google.com/',
460 | number: 123456,
461 | boolean: false,
462 | null: null,
463 | func: function () {},
464 | Symbol: Symbol('JSON View'),
465 | obj: {
466 | k1: 123,
467 | k2: '123',
468 | k3: false
469 | },
470 | arr: ['string', 123456, false, null]
471 | },
472 | customizeCopy: (node: any) => stringifyForCopying(node, 4)
473 | }
474 | }
475 |
476 | export const LargeArray: StoryObj = {
477 | args: {
478 | src: {
479 | obj: {
480 | k1: 123,
481 | k2: '123',
482 | k3: false
483 | },
484 | largeArray: largeArray
485 | },
486 | displaySize: true
487 | }
488 | }
489 |
490 | export const CustomIcons: StoryObj = {
491 | args: {
492 | src: {
493 | obj: {
494 | k1: 123,
495 | k2: '123',
496 | k3: false
497 | }
498 | },
499 | displaySize: true,
500 | CopyComponent: ({ onClick, className }) => (
501 |
504 | ),
505 | CopiedComponent: ({ className, style }) => (
506 |
509 | )
510 | }
511 | }
512 |
--------------------------------------------------------------------------------
/src/stories/share.ts:
--------------------------------------------------------------------------------
1 | export const argTypes = {
2 | src: {
3 | description: 'Array | Object'
4 | },
5 | className: {
6 | control: 'text',
7 | description: 'String'
8 | },
9 | style: {
10 | control: 'object',
11 | description: 'Object'
12 | },
13 | dark: {
14 | control: 'boolean',
15 | description: 'Boolean',
16 | table: {
17 | defaultValue: { summary: false }
18 | }
19 | },
20 | theme: {
21 | control: 'select',
22 | options: ['default', 'a11y', 'github', 'vscode', 'atom', 'winter-is-coming'],
23 | table: {
24 | defaultValue: { summary: false }
25 | }
26 | },
27 | collapseStringsAfterLength: {
28 | control: 'number',
29 | description:
30 | 'When an integer value is assigned, strings longer than that length will be truncated and indicated by an ellipsis. To expand or collapse the string content, simply click on the string value.',
31 | table: {
32 | defaultValue: { summary: 99 }
33 | }
34 | },
35 | collapseStringMode: {
36 | control: 'select',
37 | options: ['directly', 'word', 'address'],
38 | table: {
39 | defaultValue: { summary: 'word' }
40 | }
41 | },
42 | collapseObjectsAfterLength: {
43 | control: 'number',
44 | description: 'When an integer value is assigned, the object and array will initially collapse.',
45 | table: {
46 | defaultValue: { summary: 99 }
47 | }
48 | },
49 | collapsed: {
50 | description:
51 | 'When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a specific depth. The collapsed also can be a function. ',
52 | table: {
53 | defaultValue: { summary: false }
54 | }
55 | },
56 | enableClipboard: {
57 | control: 'boolean',
58 | description: 'Boolean',
59 | table: {
60 | defaultValue: { summary: false }
61 | }
62 | },
63 | matchesURL: {
64 | control: 'boolean',
65 | description: 'Boolean',
66 | table: {
67 | defaultValue: { summary: false }
68 | }
69 | },
70 | editable: {
71 | table: {
72 | defaultValue: { summary: false }
73 | },
74 | description: 'When set to true, you can add, edit, or delete the property, and the actions will trigger onAdd, onEdit, or onDelete. Options is available.'
75 | },
76 | onAdd: {
77 | description: `(params: { indexOrName: string | number, depth: number, src: any; parentType: 'object' | 'array' }) => void`
78 | },
79 | onDelete: {
80 | description: `(params: {
81 | value: any,
82 | indexOrName: string | number,
83 | depth: number,
84 | src: any,
85 | parentType: 'object' | 'array',
86 | }) => void`
87 | },
88 | onEdit: {
89 | description: `(params: {
90 | newValue: any,
91 | oldValue: any,
92 | depth: number,
93 | src: any,
94 | indexOrName: string | number,
95 | parentType: 'object' | 'array',
96 | }) => void`
97 | },
98 | customizeNode: {
99 | description: 'Highly customize every node.'
100 | },
101 | customizeCopy: {
102 | description: 'Customize copy behavior.'
103 | }
104 | }
105 |
106 | export const largeArray = [
107 | 80,
108 | { 1: 1, 2: { 2: 2 } },
109 | [1, [2, 2], [3, [3]]],
110 | 65,
111 | 85,
112 | 1,
113 | 0,
114 | 0,
115 | 0,
116 | 3,
117 | 184,
118 | 1,
119 | 0,
120 | 0,
121 | 0,
122 | 3,
123 | 13,
124 | 2,
125 | 121,
126 | 186,
127 | 6,
128 | 56,
129 | 151,
130 | 112,
131 | 160,
132 | 192,
133 | 103,
134 | 230,
135 | 223,
136 | 173,
137 | 213,
138 | 151,
139 | 104,
140 | 95,
141 | 90,
142 | 175,
143 | 137,
144 | 114,
145 | 220,
146 | 5,
147 | 166,
148 | 242,
149 | 114,
150 | 62,
151 | 120,
152 | 82,
153 | 231,
154 | 37,
155 | 102,
156 | 145,
157 | 116,
158 | 74,
159 | 228,
160 | 166,
161 | 36,
162 | 138,
163 | 186,
164 | 227,
165 | 23,
166 | 146,
167 | 212,
168 | 225,
169 | 12,
170 | 185,
171 | 71,
172 | 197,
173 | 15,
174 | 101,
175 | 203,
176 | 86,
177 | 2,
178 | 248,
179 | 12,
180 | 115,
181 | 233,
182 | 201,
183 | 42,
184 | 79,
185 | 226,
186 | 138,
187 | 44,
188 | 12,
189 | 1,
190 | 4,
191 | 157,
192 | 208,
193 | 184,
194 | 187,
195 | 127,
196 | 90,
197 | 250,
198 | 216,
199 | 242,
200 | 181,
201 | 250,
202 | 189,
203 | 98,
204 | 16,
205 | 235,
206 | 234,
207 | 149,
208 | 144,
209 | 123,
210 | 224,
211 | 41,
212 | 167,
213 | 81,
214 | 44,
215 | 171,
216 | 90,
217 | 47,
218 | 85,
219 | 152,
220 | 223,
221 | 136,
222 | 190,
223 | 1,
224 | 152,
225 | 0,
226 | 68,
227 | 157,
228 | 166,
229 | 231,
230 | 23,
231 | 165,
232 | 185,
233 | 184,
234 | 220,
235 | 182,
236 | 37,
237 | 60,
238 | 52,
239 | 146,
240 | 241,
241 | 170,
242 | 36,
243 | 164,
244 | 117,
245 | 93,
246 | 110,
247 | 3,
248 | 198,
249 | 12,
250 | 87,
251 | 249,
252 | 33,
253 | 18,
254 | 196,
255 | 1,
256 | 6,
257 | 226,
258 | 188,
259 | 138,
260 | 202,
261 | 169,
262 | 152,
263 | 161,
264 | 6,
265 | 36,
266 | 226,
267 | 186,
268 | 234,
269 | 50,
270 | 74,
271 | 8,
272 | 46,
273 | 231,
274 | 253,
275 | 157,
276 | 60,
277 | 41,
278 | 51,
279 | 98,
280 | 145,
281 | 149,
282 | 145,
283 | 8,
284 | 207,
285 | 26,
286 | 202,
287 | 192,
288 | 1,
289 | 41,
290 | 107,
291 | 95,
292 | 9,
293 | 167,
294 | 138,
295 | 95,
296 | 103,
297 | 203,
298 | 155,
299 | 75,
300 | 88,
301 | 163,
302 | 170,
303 | 8,
304 | 149,
305 | 93,
306 | 71,
307 | 210,
308 | 244,
309 | 125,
310 | 230,
311 | 249,
312 | 150,
313 | 103,
314 | 251,
315 | 134,
316 | 124,
317 | 149,
318 | 173,
319 | 106,
320 | 28,
321 | 0,
322 | 7,
323 | 243,
324 | 226,
325 | 158,
326 | 84,
327 | 94,
328 | 194,
329 | 7,
330 | 95,
331 | 243,
332 | 235,
333 | 143,
334 | 250,
335 | 119,
336 | 21,
337 | 129,
338 | 234,
339 | 93,
340 | 225,
341 | 206,
342 | 24,
343 | 134,
344 | 130,
345 | 131,
346 | 43,
347 | 108,
348 | 48,
349 | 241,
350 | 59,
351 | 139,
352 | 69,
353 | 79,
354 | 187,
355 | 51,
356 | 105,
357 | 199,
358 | 20,
359 | 222,
360 | 225,
361 | 123,
362 | 106,
363 | 82,
364 | 34,
365 | 60,
366 | 187,
367 | 60,
368 | 250,
369 | 222,
370 | 191,
371 | 119,
372 | 86,
373 | 123,
374 | 51,
375 | 46,
376 | 237,
377 | 214,
378 | 161,
379 | 238,
380 | 142,
381 | 88,
382 | 43,
383 | 36,
384 | 139,
385 | 3,
386 | 212,
387 | 1,
388 | 8,
389 | 123,
390 | 24,
391 | 130,
392 | 228,
393 | 156,
394 | 250,
395 | 90,
396 | 197,
397 | 208,
398 | 225,
399 | 102,
400 | 222,
401 | 101,
402 | 70,
403 | 61,
404 | 85,
405 | 229,
406 | 205,
407 | 172,
408 | 144,
409 | 26,
410 | 85,
411 | 84,
412 | 121,
413 | 229,
414 | 122,
415 | 78,
416 | 18,
417 | 7,
418 | 24,
419 | 33,
420 | 110,
421 | 58,
422 | 41,
423 | 161,
424 | 239,
425 | 202,
426 | 74,
427 | 43,
428 | 216,
429 | 59,
430 | 151,
431 | 116,
432 | 197,
433 | 17,
434 | 122,
435 | 185,
436 | 4,
437 | 16,
438 | 189,
439 | 224,
440 | 113,
441 | 9,
442 | 177,
443 | 139,
444 | 141,
445 | 189,
446 | 143,
447 | 255,
448 | 146,
449 | 90,
450 | 187,
451 | 182,
452 | 110,
453 | 1,
454 | 10,
455 | 94,
456 | 76,
457 | 53,
458 | 238,
459 | 86,
460 | 212,
461 | 41,
462 | 129,
463 | 7,
464 | 115,
465 | 109,
466 | 165,
467 | 226,
468 | 99,
469 | 212,
470 | 149,
471 | 45,
472 | 163,
473 | 37,
474 | 57,
475 | 86,
476 | 235,
477 | 246,
478 | 99,
479 | 111,
480 | 247,
481 | 150,
482 | 238,
483 | 119,
484 | 184,
485 | 252,
486 | 247,
487 | 46,
488 | 36,
489 | 139,
490 | 58,
491 | 178,
492 | 13,
493 | 179,
494 | 57,
495 | 16,
496 | 145,
497 | 29,
498 | 74,
499 | 222,
500 | 92,
501 | 185,
502 | 174,
503 | 15,
504 | 2,
505 | 25,
506 | 209,
507 | 241,
508 | 45,
509 | 81,
510 | 58,
511 | 65,
512 | 83,
513 | 214,
514 | 61,
515 | 155,
516 | 169,
517 | 115,
518 | 245,
519 | 0,
520 | 11,
521 | 211,
522 | 8,
523 | 189,
524 | 14,
525 | 182,
526 | 190,
527 | 141,
528 | 26,
529 | 120,
530 | 10,
531 | 30,
532 | 246,
533 | 214,
534 | 214,
535 | 34,
536 | 191,
537 | 166,
538 | 28,
539 | 29,
540 | 154,
541 | 31,
542 | 90,
543 | 48,
544 | 146,
545 | 74,
546 | 18,
547 | 177,
548 | 206,
549 | 66,
550 | 255,
551 | 10,
552 | 62,
553 | 108,
554 | 39,
555 | 176,
556 | 121,
557 | 177,
558 | 231,
559 | 137,
560 | 161,
561 | 34,
562 | 133,
563 | 187,
564 | 38,
565 | 187,
566 | 213,
567 | 9,
568 | 64,
569 | 150,
570 | 194,
571 | 156,
572 | 90,
573 | 150,
574 | 240,
575 | 203,
576 | 148,
577 | 123,
578 | 12,
579 | 237,
580 | 72,
581 | 161,
582 | 169,
583 | 227,
584 | 187,
585 | 1,
586 | 12,
587 | 131,
588 | 85,
589 | 178,
590 | 81,
591 | 89,
592 | 109,
593 | 167,
594 | 107,
595 | 249,
596 | 126,
597 | 154,
598 | 249,
599 | 39,
600 | 104,
601 | 38,
602 | 205,
603 | 192,
604 | 41,
605 | 175,
606 | 151,
607 | 96,
608 | 20,
609 | 8,
610 | 117,
611 | 58,
612 | 50,
613 | 175,
614 | 52,
615 | 57,
616 | 199,
617 | 104,
618 | 58,
619 | 81,
620 | 243,
621 | 55,
622 | 161,
623 | 163,
624 | 108,
625 | 21,
626 | 103,
627 | 141,
628 | 61,
629 | 253,
630 | 26,
631 | 136,
632 | 173,
633 | 48,
634 | 119,
635 | 223,
636 | 246,
637 | 111,
638 | 99,
639 | 213,
640 | 205,
641 | 233,
642 | 136,
643 | 1,
644 | 225,
645 | 183,
646 | 105,
647 | 95,
648 | 228,
649 | 42,
650 | 182,
651 | 1,
652 | 13,
653 | 22,
654 | 113,
655 | 162,
656 | 211,
657 | 40,
658 | 246,
659 | 12,
660 | 213,
661 | 30,
662 | 107,
663 | 124,
664 | 117,
665 | 9,
666 | 233,
667 | 211,
668 | 50,
669 | 61,
670 | 98,
671 | 87,
672 | 145,
673 | 76,
674 | 169,
675 | 58,
676 | 134,
677 | 16,
678 | 204,
679 | 246,
680 | 120,
681 | 40,
682 | 103,
683 | 72,
684 | 197,
685 | 77,
686 | 1,
687 | 161,
688 | 214,
689 | 193,
690 | 248,
691 | 238,
692 | 182,
693 | 78,
694 | 11,
695 | 236,
696 | 218,
697 | 157,
698 | 43,
699 | 4,
700 | 186,
701 | 2,
702 | 26,
703 | 121,
704 | 139,
705 | 144,
706 | 66,
707 | 26,
708 | 17,
709 | 94,
710 | 138,
711 | 109,
712 | 115,
713 | 191,
714 | 140,
715 | 33,
716 | 213,
717 | 0,
718 | 14,
719 | 156,
720 | 79,
721 | 100,
722 | 224,
723 | 40,
724 | 213,
725 | 31,
726 | 196,
727 | 39,
728 | 191,
729 | 235,
730 | 47,
731 | 231,
732 | 136,
733 | 145,
734 | 139,
735 | 9,
736 | 74,
737 | 238,
738 | 243,
739 | 133,
740 | 111,
741 | 84,
742 | 199,
743 | 150,
744 | 217,
745 | 235,
746 | 7,
747 | 250,
748 | 125,
749 | 61,
750 | 175,
751 | 89,
752 | 144,
753 | 43,
754 | 233,
755 | 236,
756 | 187,
757 | 26,
758 | 246,
759 | 99,
760 | 134,
761 | 51,
762 | 132,
763 | 220,
764 | 49,
765 | 87,
766 | 214,
767 | 76,
768 | 201,
769 | 139,
770 | 67,
771 | 205,
772 | 31,
773 | 229,
774 | 9,
775 | 113,
776 | 224,
777 | 89,
778 | 213,
779 | 99,
780 | 113,
781 | 71,
782 | 111,
783 | 0,
784 | 15,
785 | 33,
786 | 114,
787 | 28,
788 | 210,
789 | 244,
790 | 89,
791 | 240,
792 | 151,
793 | 165,
794 | 241,
795 | 212,
796 | 120,
797 | 2,
798 | 194,
799 | 233,
800 | 180,
801 | 93,
802 | 5,
803 | 238,
804 | 120,
805 | 15,
806 | 139,
807 | 224,
808 | 179,
809 | 187,
810 | 176,
811 | 185,
812 | 70,
813 | 52,
814 | 123,
815 | 25,
816 | 145,
817 | 33,
818 | 242,
819 | 192,
820 | 117,
821 | 55,
822 | 235,
823 | 250,
824 | 74,
825 | 149,
826 | 215,
827 | 111,
828 | 95,
829 | 244,
830 | 185,
831 | 242,
832 | 14,
833 | 159,
834 | 52,
835 | 238,
836 | 173,
837 | 97,
838 | 254,
839 | 208,
840 | 85,
841 | 204,
842 | 160,
843 | 42,
844 | 174,
845 | 22,
846 | 227,
847 | 13,
848 | 8,
849 | 1,
850 | 16,
851 | 68,
852 | 136,
853 | 108,
854 | 58,
855 | 224,
856 | 187,
857 | 244,
858 | 212,
859 | 255,
860 | 71,
861 | 34,
862 | 221,
863 | 27,
864 | 253,
865 | 138,
866 | 77,
867 | 202,
868 | 245,
869 | 102,
870 | 133,
871 | 211,
872 | 211,
873 | 174,
874 | 227,
875 | 2,
876 | 104,
877 | 62,
878 | 208,
879 | 61,
880 | 15,
881 | 196,
882 | 163,
883 | 112,
884 | 57,
885 | 166,
886 | 122,
887 | 110,
888 | 128,
889 | 17,
890 | 224,
891 | 33,
892 | 109,
893 | 12,
894 | 28,
895 | 74,
896 | 246,
897 | 150,
898 | 182,
899 | 255,
900 | 146,
901 | 249,
902 | 9,
903 | 28,
904 | 214,
905 | 123,
906 | 221,
907 | 120,
908 | 185,
909 | 141,
910 | 201,
911 | 44,
912 | 108,
913 | 34,
914 | 102,
915 | 0,
916 | 18,
917 | 108,
918 | 193,
919 | 84,
920 | 172,
921 | 81,
922 | 179,
923 | 225,
924 | 195,
925 | 131,
926 | 2,
927 | 0,
928 | 21,
929 | 142,
930 | 140,
931 | 50,
932 | 3,
933 | 223,
934 | 162,
935 | 195,
936 | 223,
937 | 208,
938 | 99,
939 | 252,
940 | 191,
941 | 50,
942 | 216,
943 | 13,
944 | 152,
945 | 236,
946 | 171,
947 | 172,
948 | 245,
949 | 18,
950 | 147,
951 | 156,
952 | 67,
953 | 192,
954 | 130,
955 | 107,
956 | 124,
957 | 117,
958 | 167,
959 | 120,
960 | 89,
961 | 201,
962 | 67,
963 | 141,
964 | 136,
965 | 32,
966 | 158,
967 | 64,
968 | 128,
969 | 249,
970 | 224,
971 | 22,
972 | 48,
973 | 175,
974 | 103,
975 | 224,
976 | 155,
977 | 5,
978 | 235,
979 | 207,
980 | 246,
981 | 1,
982 | 101,
983 | 164,
984 | 214,
985 | 118,
986 | 0,
987 | 0,
988 | 0,
989 | 0,
990 | 0,
991 | 26,
992 | 225,
993 | 1,
994 | 250,
995 | 237,
996 | 172,
997 | 88,
998 | 81,
999 | 227,
1000 | 43,
1001 | 155,
1002 | 35,
1003 | 181,
1004 | 249,
1005 | 65,
1006 | 26,
1007 | 140,
1008 | 43,
1009 | 172,
1010 | 74,
1011 | 174,
1012 | 62,
1013 | 212,
1014 | 221,
1015 | 123,
1016 | 129,
1017 | 29,
1018 | 209,
1019 | 167,
1020 | 46,
1021 | 164,
1022 | 170,
1023 | 113,
1024 | 0,
1025 | 0,
1026 | 0,
1027 | 0,
1028 | 2,
1029 | 41,
1030 | 109,
1031 | 38,
1032 | 1,
1033 | 65,
1034 | 85,
1035 | 87,
1036 | 86,
1037 | 0,
1038 | 0,
1039 | 0,
1040 | 0,
1041 | 0,
1042 | 7,
1043 | 48,
1044 | 177,
1045 | 76,
1046 | 0,
1047 | 0,
1048 | 39,
1049 | 16,
1050 | 103,
1051 | 123,
1052 | 175,
1053 | 16,
1054 | 218,
1055 | 153,
1056 | 59,
1057 | 20,
1058 | 203,
1059 | 105,
1060 | 228,
1061 | 123,
1062 | 46,
1063 | 106,
1064 | 212,
1065 | 223,
1066 | 151,
1067 | 222,
1068 | 99,
1069 | 254,
1070 | 3,
1071 | 0,
1072 | 85,
1073 | 0,
1074 | 35,
1075 | 86,
1076 | 175,
1077 | 149,
1078 | 41,
1079 | 161,
1080 | 6,
1081 | 77,
1082 | 65,
1083 | 227,
1084 | 45,
1085 | 97,
1086 | 126,
1087 | 44,
1088 | 225,
1089 | 220,
1090 | 165,
1091 | 115,
1092 | 58,
1093 | 250,
1094 | 144,
1095 | 29,
1096 | 171,
1097 | 169,
1098 | 226,
1099 | 182,
1100 | 141,
1101 | 238,
1102 | 93,
1103 | 83,
1104 | 236,
1105 | 249,
1106 | 0,
1107 | 0,
1108 | 0,
1109 | 0,
1110 | 17,
1111 | 227,
1112 | 150,
1113 | 179,
1114 | 0,
1115 | 0,
1116 | 0,
1117 | 0,
1118 | 0,
1119 | 5,
1120 | 0,
1121 | 129,
1122 | 255,
1123 | 255,
1124 | 255,
1125 | 248,
1126 | 0,
1127 | 0,
1128 | 0,
1129 | 0,
1130 | 101,
1131 | 164,
1132 | 214,
1133 | 118,
1134 | 0,
1135 | 0,
1136 | 0,
1137 | 0,
1138 | 101,
1139 | 164,
1140 | 214,
1141 | 117,
1142 | 0,
1143 | 0,
1144 | 0,
1145 | 0,
1146 | 17,
1147 | 201,
1148 | 8,
1149 | 84,
1150 | 0,
1151 | 0,
1152 | 0,
1153 | 0,
1154 | 0,
1155 | 4,
1156 | 47,
1157 | 79,
1158 | 10,
1159 | 54,
1160 | 90,
1161 | 117,
1162 | 73,
1163 | 30,
1164 | 131,
1165 | 22,
1166 | 242,
1167 | 0,
1168 | 165,
1169 | 35,
1170 | 44,
1171 | 86,
1172 | 5,
1173 | 90,
1174 | 115,
1175 | 157,
1176 | 217,
1177 | 149,
1178 | 77,
1179 | 197,
1180 | 4,
1181 | 182,
1182 | 88,
1183 | 183,
1184 | 242,
1185 | 234,
1186 | 63,
1187 | 42,
1188 | 48,
1189 | 41,
1190 | 224,
1191 | 189,
1192 | 89,
1193 | 90,
1194 | 239,
1195 | 224,
1196 | 65,
1197 | 86,
1198 | 24,
1199 | 255,
1200 | 22,
1201 | 53,
1202 | 122,
1203 | 134,
1204 | 59,
1205 | 16,
1206 | 136,
1207 | 202,
1208 | 164,
1209 | 149,
1210 | 192,
1211 | 39,
1212 | 9,
1213 | 255,
1214 | 236,
1215 | 225,
1216 | 223,
1217 | 168,
1218 | 52,
1219 | 245,
1220 | 29,
1221 | 162,
1222 | 237,
1223 | 64,
1224 | 118,
1225 | 214,
1226 | 185,
1227 | 55,
1228 | 180,
1229 | 148,
1230 | 75,
1231 | 225,
1232 | 6,
1233 | 172,
1234 | 83,
1235 | 182,
1236 | 51,
1237 | 7,
1238 | 30,
1239 | 149,
1240 | 254,
1241 | 106,
1242 | 192,
1243 | 190,
1244 | 190,
1245 | 210,
1246 | 26,
1247 | 82,
1248 | 55,
1249 | 170,
1250 | 114,
1251 | 167,
1252 | 239,
1253 | 162,
1254 | 50,
1255 | 8,
1256 | 5,
1257 | 205,
1258 | 42,
1259 | 26,
1260 | 75,
1261 | 62,
1262 | 25,
1263 | 231,
1264 | 118,
1265 | 33,
1266 | 11,
1267 | 230,
1268 | 205,
1269 | 211,
1270 | 188,
1271 | 63,
1272 | 108,
1273 | 109,
1274 | 196,
1275 | 180,
1276 | 204,
1277 | 33,
1278 | 157,
1279 | 219,
1280 | 96,
1281 | 125,
1282 | 163,
1283 | 248,
1284 | 66,
1285 | 114,
1286 | 19,
1287 | 51,
1288 | 30,
1289 | 86,
1290 | 120,
1291 | 134,
1292 | 104,
1293 | 56,
1294 | 150,
1295 | 91,
1296 | 57,
1297 | 7,
1298 | 189,
1299 | 180,
1300 | 47,
1301 | 22,
1302 | 113,
1303 | 27,
1304 | 232,
1305 | 251,
1306 | 21,
1307 | 137,
1308 | 9,
1309 | 138,
1310 | 1,
1311 | 34,
1312 | 243,
1313 | 166,
1314 | 138,
1315 | 10,
1316 | 87,
1317 | 100,
1318 | 191,
1319 | 76,
1320 | 186,
1321 | 1,
1322 | 125,
1323 | 233,
1324 | 5,
1325 | 40,
1326 | 136,
1327 | 151,
1328 | 152,
1329 | 33,
1330 | 111,
1331 | 163,
1332 | 151,
1333 | 137,
1334 | 129,
1335 | 233,
1336 | 204,
1337 | 121,
1338 | 168,
1339 | 205,
1340 | 19,
1341 | 38,
1342 | 187,
1343 | 5,
1344 | 218,
1345 | 26,
1346 | 166,
1347 | 80,
1348 | 231,
1349 | 173,
1350 | 125,
1351 | 181,
1352 | 64,
1353 | 61,
1354 | 25,
1355 | 241,
1356 | 220,
1357 | 70,
1358 | 252,
1359 | 0,
1360 | 85,
1361 | 0,
1362 | 35,
1363 | 215,
1364 | 49,
1365 | 81,
1366 | 19,
1367 | 245,
1368 | 177,
1369 | 211,
1370 | 186,
1371 | 122,
1372 | 131,
1373 | 96,
1374 | 76,
1375 | 68,
1376 | 185,
1377 | 77,
1378 | 121,
1379 | 244,
1380 | 253,
1381 | 105,
1382 | 175,
1383 | 119,
1384 | 248,
1385 | 4,
1386 | 252,
1387 | 127,
1388 | 146,
1389 | 10,
1390 | 109,
1391 | 198,
1392 | 87,
1393 | 68,
1394 | 0,
1395 | 0,
1396 | 0,
1397 | 0,
1398 | 8,
1399 | 80,
1400 | 101,
1401 | 172,
1402 | 0,
1403 | 0,
1404 | 0,
1405 | 0,
1406 | 0,
1407 | 2,
1408 | 52,
1409 | 101,
1410 | 255,
1411 | 255,
1412 | 255,
1413 | 248,
1414 | 0,
1415 | 0,
1416 | 0,
1417 | 0,
1418 | 101,
1419 | 164,
1420 | 214,
1421 | 118,
1422 | 0,
1423 | 0,
1424 | 0,
1425 | 0,
1426 | 101,
1427 | 164,
1428 | 214,
1429 | 117,
1430 | 0,
1431 | 0,
1432 | 0,
1433 | 0,
1434 | 8,
1435 | 63,
1436 | 95,
1437 | 21,
1438 | 0,
1439 | 0,
1440 | 0,
1441 | 0,
1442 | 0,
1443 | 2,
1444 | 119,
1445 | 209,
1446 | 10,
1447 | 161,
1448 | 248,
1449 | 146,
1450 | 171,
1451 | 227,
1452 | 175,
1453 | 169,
1454 | 205,
1455 | 54,
1456 | 92,
1457 | 186,
1458 | 2,
1459 | 102,
1460 | 211,
1461 | 1,
1462 | 167,
1463 | 46,
1464 | 10,
1465 | 7,
1466 | 36,
1467 | 26,
1468 | 152,
1469 | 85,
1470 | 185,
1471 | 126,
1472 | 91,
1473 | 143,
1474 | 115,
1475 | 22,
1476 | 145,
1477 | 254,
1478 | 90,
1479 | 227,
1480 | 75,
1481 | 109,
1482 | 86,
1483 | 213,
1484 | 8,
1485 | 95,
1486 | 199,
1487 | 6,
1488 | 217,
1489 | 169,
1490 | 240,
1491 | 165,
1492 | 134,
1493 | 181,
1494 | 72,
1495 | 203,
1496 | 142,
1497 | 42,
1498 | 137,
1499 | 177,
1500 | 26,
1501 | 141,
1502 | 240,
1503 | 137,
1504 | 153,
1505 | 206,
1506 | 206,
1507 | 245,
1508 | 29,
1509 | 162,
1510 | 237,
1511 | 64,
1512 | 118,
1513 | 214,
1514 | 185,
1515 | 55,
1516 | 180,
1517 | 148,
1518 | 75,
1519 | 225,
1520 | 6,
1521 | 172,
1522 | 83,
1523 | 182,
1524 | 51,
1525 | 7,
1526 | 30,
1527 | 149,
1528 | 254,
1529 | 106,
1530 | 192,
1531 | 190,
1532 | 190,
1533 | 210,
1534 | 26,
1535 | 82,
1536 | 55,
1537 | 170,
1538 | 114,
1539 | 167,
1540 | 239,
1541 | 162,
1542 | 50,
1543 | 8,
1544 | 5,
1545 | 205,
1546 | 42,
1547 | 26,
1548 | 75,
1549 | 62,
1550 | 25,
1551 | 231,
1552 | 118,
1553 | 33,
1554 | 11,
1555 | 230,
1556 | 205,
1557 | 211,
1558 | 188,
1559 | 63,
1560 | 108,
1561 | 109,
1562 | 196,
1563 | 180,
1564 | 204,
1565 | 33,
1566 | 157,
1567 | 219,
1568 | 96,
1569 | 125,
1570 | 163,
1571 | 248,
1572 | 66,
1573 | 114,
1574 | 19,
1575 | 51,
1576 | 30,
1577 | 86,
1578 | 120,
1579 | 134,
1580 | 104,
1581 | 56,
1582 | 150,
1583 | 91,
1584 | 57,
1585 | 7,
1586 | 189,
1587 | 180,
1588 | 47,
1589 | 22,
1590 | 113,
1591 | 27,
1592 | 232,
1593 | 251,
1594 | 21,
1595 | 137,
1596 | 9,
1597 | 138,
1598 | 1,
1599 | 34,
1600 | 243,
1601 | 166,
1602 | 138,
1603 | 10,
1604 | 87,
1605 | 100,
1606 | 191,
1607 | 76,
1608 | 186,
1609 | 1,
1610 | 125,
1611 | 233,
1612 | 5,
1613 | 40,
1614 | 136,
1615 | 151,
1616 | 152,
1617 | 33,
1618 | 111,
1619 | 163,
1620 | 151,
1621 | 137,
1622 | 129,
1623 | 233,
1624 | 204,
1625 | 121,
1626 | 168,
1627 | 205,
1628 | 19,
1629 | 38,
1630 | 187,
1631 | 5,
1632 | 218,
1633 | 26,
1634 | 166,
1635 | 80,
1636 | 231,
1637 | 173,
1638 | 125,
1639 | 181,
1640 | 64,
1641 | 61,
1642 | 25,
1643 | 241,
1644 | 220,
1645 | 70,
1646 | 252,
1647 | 0,
1648 | 85,
1649 | 0,
1650 | 229,
1651 | 178,
1652 | 116,
1653 | 178,
1654 | 97,
1655 | 17,
1656 | 67,
1657 | 223,
1658 | 5,
1659 | 93,
1660 | 110,
1661 | 124,
1662 | 216,
1663 | 217,
1664 | 63,
1665 | 225,
1666 | 150,
1667 | 23,
1668 | 22,
1669 | 188,
1670 | 212,
1671 | 220,
1672 | 161,
1673 | 202,
1674 | 216,
1675 | 122,
1676 | 131,
1677 | 188,
1678 | 30,
1679 | 120,
1680 | 193,
1681 | 239,
1682 | 0,
1683 | 0,
1684 | 0,
1685 | 0,
1686 | 0,
1687 | 211,
1688 | 102,
1689 | 126,
1690 | 0,
1691 | 0,
1692 | 0,
1693 | 0,
1694 | 0,
1695 | 0,
1696 | 81,
1697 | 70,
1698 | 255,
1699 | 255,
1700 | 255,
1701 | 248,
1702 | 0,
1703 | 0,
1704 | 0,
1705 | 0,
1706 | 101,
1707 | 164,
1708 | 214,
1709 | 118,
1710 | 0,
1711 | 0,
1712 | 0,
1713 | 0,
1714 | 101,
1715 | 164,
1716 | 214,
1717 | 117,
1718 | 0,
1719 | 0,
1720 | 0,
1721 | 0,
1722 | 0,
1723 | 209,
1724 | 139,
1725 | 204,
1726 | 0,
1727 | 0,
1728 | 0,
1729 | 0,
1730 | 0,
1731 | 0,
1732 | 96,
1733 | 149,
1734 | 10,
1735 | 62,
1736 | 205,
1737 | 57,
1738 | 44,
1739 | 196,
1740 | 138,
1741 | 88,
1742 | 133,
1743 | 149,
1744 | 104,
1745 | 117,
1746 | 136,
1747 | 186,
1748 | 96,
1749 | 134,
1750 | 179,
1751 | 113,
1752 | 63,
1753 | 17,
1754 | 85,
1755 | 189,
1756 | 83,
1757 | 37,
1758 | 152,
1759 | 240,
1760 | 134,
1761 | 20,
1762 | 35,
1763 | 121,
1764 | 186,
1765 | 18,
1766 | 20,
1767 | 29,
1768 | 151,
1769 | 145,
1770 | 128,
1771 | 223,
1772 | 94,
1773 | 86,
1774 | 115,
1775 | 69,
1776 | 94,
1777 | 149,
1778 | 125,
1779 | 204,
1780 | 127,
1781 | 169,
1782 | 100,
1783 | 3,
1784 | 245,
1785 | 207,
1786 | 96,
1787 | 108,
1788 | 201,
1789 | 211,
1790 | 142,
1791 | 212,
1792 | 199,
1793 | 162,
1794 | 124,
1795 | 184,
1796 | 247,
1797 | 189,
1798 | 70,
1799 | 48,
1800 | 68,
1801 | 237,
1802 | 226,
1803 | 173,
1804 | 185,
1805 | 89,
1806 | 156,
1807 | 206,
1808 | 175,
1809 | 110,
1810 | 124,
1811 | 87,
1812 | 236,
1813 | 119,
1814 | 94,
1815 | 1,
1816 | 123,
1817 | 213,
1818 | 151,
1819 | 224,
1820 | 235,
1821 | 40,
1822 | 250,
1823 | 126,
1824 | 237,
1825 | 49,
1826 | 45,
1827 | 62,
1828 | 253,
1829 | 237,
1830 | 210,
1831 | 241,
1832 | 105,
1833 | 61,
1834 | 20,
1835 | 143,
1836 | 60,
1837 | 15,
1838 | 223,
1839 | 245,
1840 | 76,
1841 | 16,
1842 | 5,
1843 | 239,
1844 | 174,
1845 | 171,
1846 | 17,
1847 | 109,
1848 | 109,
1849 | 133,
1850 | 211,
1851 | 33,
1852 | 191,
1853 | 55,
1854 | 118,
1855 | 234,
1856 | 62,
1857 | 174,
1858 | 91,
1859 | 116,
1860 | 191,
1861 | 120,
1862 | 177,
1863 | 136,
1864 | 168,
1865 | 7,
1866 | 179,
1867 | 144,
1868 | 172,
1869 | 193,
1870 | 10,
1871 | 147,
1872 | 48,
1873 | 210,
1874 | 119,
1875 | 200,
1876 | 51,
1877 | 96,
1878 | 50,
1879 | 193,
1880 | 173,
1881 | 73,
1882 | 115,
1883 | 253,
1884 | 163,
1885 | 102,
1886 | 6,
1887 | 92,
1888 | 4,
1889 | 120,
1890 | 17,
1891 | 65,
1892 | 115,
1893 | 253,
1894 | 103,
1895 | 67,
1896 | 35,
1897 | 245,
1898 | 187,
1899 | 83,
1900 | 59,
1901 | 183,
1902 | 69,
1903 | 77,
1904 | 240,
1905 | 152,
1906 | 215,
1907 | 75,
1908 | 10,
1909 | 159,
1910 | 61,
1911 | 21,
1912 | 46,
1913 | 141,
1914 | 255,
1915 | 205,
1916 | 19,
1917 | 38,
1918 | 187,
1919 | 5,
1920 | 218,
1921 | 26,
1922 | 166,
1923 | 80,
1924 | 231,
1925 | 173,
1926 | 125,
1927 | 181,
1928 | 64,
1929 | 61,
1930 | 25,
1931 | 241,
1932 | 220,
1933 | 70,
1934 | 252
1935 | ]
1936 |
--------------------------------------------------------------------------------
/src/stories/theme.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Meta, StoryObj } from '@storybook/react'
3 | import JsonView from '../index'
4 | import '../dark.css'
5 | import { argTypes } from './share'
6 |
7 | type TYPE_FC = typeof JsonView
8 |
9 | export default {
10 | title: 'Themes',
11 | component: JsonView,
12 |
13 | argTypes,
14 | args: {
15 | enableClipboard: true,
16 | editable: true,
17 | src: {
18 | string: 'string',
19 | longString: 'long string long string long string long string long string long string',
20 | number: 123456,
21 | boolean: false,
22 | null: null,
23 | func: function () {
24 | console.log('Hello World')
25 | },
26 | Symbol: Symbol('JSON View'),
27 | obj: {
28 | k1: 123,
29 | k2: '123',
30 | k3: false
31 | },
32 | arr: ['string', 123456, false, null]
33 | }
34 | }
35 | } as Meta
36 |
37 | export const Default: StoryObj = {
38 | args: {
39 | theme: 'default'
40 | },
41 | decorators: [
42 | Story => (
43 |
48 | )
49 | ]
50 | }
51 | export const Default_Dark: StoryObj = {
52 | args: {
53 | theme: 'default',
54 | dark: true
55 | },
56 | decorators: [
57 | Story => (
58 |
63 | )
64 | ]
65 | }
66 |
67 | export const Accessibility: StoryObj = {
68 | args: {
69 | theme: 'a11y'
70 | },
71 | decorators: [
72 | Story => (
73 |
78 | )
79 | ]
80 | }
81 | export const Accessibility_Dark: StoryObj = {
82 | args: {
83 | theme: 'a11y',
84 | dark: true
85 | },
86 | decorators: [
87 | Story => (
88 |
93 | )
94 | ]
95 | }
96 |
97 | export const Github: StoryObj = {
98 | args: {
99 | theme: 'github'
100 | },
101 | decorators: [
102 | Story => (
103 |
108 | )
109 | ]
110 | }
111 | export const Github_Dark: StoryObj = {
112 | args: {
113 | theme: 'github',
114 | dark: true
115 | },
116 | decorators: [
117 | Story => (
118 |
123 | )
124 | ]
125 | }
126 |
127 | export const Vscode: StoryObj = {
128 | args: {
129 | theme: 'vscode'
130 | },
131 | decorators: [
132 | Story => (
133 |
138 | )
139 | ]
140 | }
141 | export const Vscode_Dark: StoryObj = {
142 | args: {
143 | theme: 'vscode',
144 | dark: true
145 | },
146 | decorators: [
147 | Story => (
148 |
153 | )
154 | ]
155 | }
156 |
157 | export const Atom: StoryObj = {
158 | args: {
159 | theme: 'atom'
160 | },
161 | decorators: [
162 | Story => (
163 |
168 | )
169 | ]
170 | }
171 | export const Atom_Dark: StoryObj = {
172 | args: {
173 | theme: 'atom',
174 | dark: true
175 | },
176 | decorators: [
177 | Story => (
178 |
183 | )
184 | ]
185 | }
186 |
187 | export const Winter_is_Coming: StoryObj = {
188 | args: {
189 | theme: 'winter-is-coming'
190 | },
191 | decorators: [
192 | Story => (
193 |
198 | )
199 | ]
200 | }
201 | export const Winter_is_Coming_Dark: StoryObj = {
202 | args: {
203 | theme: 'winter-is-coming',
204 | dark: true
205 | },
206 | decorators: [
207 | Story => (
208 |
213 | )
214 | ]
215 | }
216 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | .json-view {
2 | display: block;
3 | color: #4d4d4d;
4 | text-align: left;
5 | --json-property: #009033;
6 | --json-index: #676dff;
7 | --json-number: #676dff;
8 | --json-string: #b2762e;
9 | --json-boolean: #dc155e;
10 | --json-null: #dc155e;
11 | }
12 | .json-view .json-view--property {
13 | color: var(--json-property);
14 | }
15 | .json-view .json-view--index {
16 | color: var(--json-index);
17 | }
18 | .json-view .json-view--number {
19 | color: var(--json-number);
20 | }
21 | .json-view .json-view--string {
22 | color: var(--json-string);
23 | }
24 | .json-view .json-view--boolean {
25 | color: var(--json-boolean);
26 | }
27 | .json-view .json-view--null {
28 | color: var(--json-null);
29 | }
30 |
31 | .json-view .jv-indent {
32 | padding-left: 1em;
33 | }
34 | .json-view .jv-chevron {
35 | display: inline-block;
36 | vertical-align: -20%;
37 | cursor: pointer;
38 | opacity: 0.4;
39 | width: 1em;
40 | height: 1em;
41 | }
42 | :is(.json-view .jv-chevron:hover, .json-view .jv-size:hover + .jv-chevron) {
43 | opacity: 0.8;
44 | }
45 | .json-view .jv-size {
46 | cursor: pointer;
47 | opacity: 0.4;
48 | font-size: 0.875em;
49 | font-style: italic;
50 | margin-left: 0.5em;
51 | vertical-align: -5%;
52 | line-height: 1;
53 | }
54 |
55 | .json-view :is(.json-view--copy, .json-view--edit),
56 | .json-view .json-view--link svg {
57 | display: none;
58 | width: 1em;
59 | height: 1em;
60 | margin-left: 0.25em;
61 | cursor: pointer;
62 | }
63 |
64 | .json-view .json-view--input {
65 | width: 120px;
66 | margin-left: 0.25em;
67 | border-radius: 4px;
68 | border: 1px solid currentColor;
69 | padding: 0px 4px;
70 | font-size: 87.5%;
71 | line-height: 1.25;
72 | background: transparent;
73 | }
74 | .json-view .json-view--deleting {
75 | outline: 1px solid #da0000;
76 | background-color: #da000011;
77 | text-decoration-line: line-through;
78 | }
79 |
80 | :is(.json-view:hover, .json-view--pair:hover) > :is(.json-view--copy, .json-view--edit),
81 | :is(.json-view:hover, .json-view--pair:hover) > .json-view--link svg {
82 | display: inline-block;
83 | }
84 |
85 | .json-view .jv-button {
86 | background: transparent;
87 | outline: none;
88 | border: none;
89 | cursor: pointer;
90 | color: inherit;
91 | }
92 | .json-view .cursor-pointer {
93 | cursor: pointer;
94 | }
95 |
96 | .json-view svg {
97 | vertical-align: -10%;
98 | }
99 | .jv-size-chevron ~ svg {
100 | vertical-align: -16%;
101 | }
102 |
103 | /* Themes */
104 | .json-view_a11y {
105 | color: #545454;
106 | --json-property: #aa5d00;
107 | --json-index: #007299;
108 | --json-number: #007299;
109 | --json-string: #008000;
110 | --json-boolean: #d91e18;
111 | --json-null: #d91e18;
112 | }
113 | .json-view_github {
114 | color: #005cc5;
115 | --json-property: #005cc5;
116 | --json-index: #005cc5;
117 | --json-number: #005cc5;
118 | --json-string: #032f62;
119 | --json-boolean: #005cc5;
120 | --json-null: #005cc5;
121 | }
122 | .json-view_vscode {
123 | color: #005cc5;
124 | --json-property: #0451a5;
125 | --json-index: #0000ff;
126 | --json-number: #0000ff;
127 | --json-string: #a31515;
128 | --json-boolean: #0000ff;
129 | --json-null: #0000ff;
130 | }
131 | .json-view_atom {
132 | color: #383a42;
133 | --json-property: #e45649;
134 | --json-index: #986801;
135 | --json-number: #986801;
136 | --json-string: #50a14f;
137 | --json-boolean: #0184bc;
138 | --json-null: #0184bc;
139 | }
140 | .json-view_winter-is-coming {
141 | color: #0431fa;
142 | --json-property: #3a9685;
143 | --json-index: #ae408b;
144 | --json-number: #ae408b;
145 | --json-string: #8123a9;
146 | --json-boolean: #0184bc;
147 | --json-null: #0184bc;
148 | }
149 |
--------------------------------------------------------------------------------
/src/svgs/add-circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/add-square.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/angle-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/cancel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/copied.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/custom-add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/custom-copied.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/custom-copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/done.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svgs/link.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/svgs/trash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export declare type Collapsed =
2 | | undefined
3 | | number
4 | | boolean
5 | | ((params: { node: Record | Array; indexOrName: number | string | undefined; depth: number; size: number }) => boolean | void)
6 |
7 | export type DisplaySize = undefined | number | boolean | 'collapsed' | 'expanded'
8 |
9 | export declare type Editable =
10 | | boolean
11 | | {
12 | add?: boolean
13 | edit?: boolean
14 | delete?: boolean
15 | }
16 |
17 | export declare type CustomizeOptions = {
18 | add?: boolean
19 | edit?: boolean
20 | delete?: boolean
21 | enableClipboard?: boolean
22 | matchesURL?: boolean
23 | collapsed?: boolean
24 | className?: string
25 | }
26 | export declare type CustomizeNode = (params: {
27 | node: any
28 | indexOrName: number | string | undefined
29 | depth: number
30 | }) => CustomizeOptions | React.FC | React.Component | React.ReactElement | undefined
31 |
32 | export type CustomizeCollapseStringUI = ((str_show: string, truncated: boolean) => JSX.Element | string) | string
33 |
34 | export type NodeMeta = { depth: number; indexOrName?: number | string; parent?: Record | Array; parentPath: string[], currentPath: string[] }
35 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { Collapsed, CustomizeOptions, DisplaySize, Editable } from './types'
2 | import copy from 'copy-to-clipboard'
3 |
4 | export function isObject(node: any): node is Record {
5 | return Object.prototype.toString.call(node) === '[object Object]'
6 | }
7 |
8 | export function objectSize(node: Record | Array) {
9 | return Array.isArray(node) ? node.length : isObject(node) ? Object.keys(node).length : 0
10 | }
11 |
12 | export function stringifyForCopying(node: any, space?: string | number | undefined) {
13 | // return single string nodes without quotes
14 | if (typeof node === 'string') {
15 | return node
16 | }
17 |
18 | try {
19 | return JSON.stringify(
20 | node,
21 | (key, value) => {
22 | switch (typeof value) {
23 | case 'bigint':
24 | return String(value) + 'n'
25 | case 'number':
26 | case 'boolean':
27 | case 'object':
28 | case 'string':
29 | return value
30 | default:
31 | return String(value)
32 | }
33 | },
34 | space
35 | )
36 | } catch (error: any) {
37 | return `${error.name}: ${error.message}` || 'JSON.stringify failed'
38 | }
39 | }
40 |
41 | export async function writeClipboard(value: string) {
42 | try {
43 | await navigator.clipboard.writeText(value)
44 | } catch (err) {
45 | copy(value)
46 | }
47 | }
48 |
49 | export function isCollapsed(
50 | node: Record | Array,
51 | depth: number,
52 | indexOrName: number | string | undefined,
53 | collapsed: Collapsed,
54 | collapseObjectsAfterLength: number,
55 | customOptions?: CustomizeOptions
56 | ): boolean {
57 | if (customOptions && customOptions.collapsed !== undefined) return !!customOptions.collapsed
58 | if (typeof collapsed === 'boolean') return collapsed
59 | if (typeof collapsed === 'number' && depth > collapsed) return true
60 |
61 | const size = objectSize(node)
62 |
63 | if (typeof collapsed === 'function') {
64 | const result = safeCall(collapsed, [{ node, depth, indexOrName, size }])
65 | if (typeof result === 'boolean') return result
66 | }
67 |
68 | if (Array.isArray(node) && size > collapseObjectsAfterLength) return true
69 | if (isObject(node) && size > collapseObjectsAfterLength) return true
70 | return false
71 | }
72 | export function isCollapsed_largeArray(
73 | node: Record | Array,
74 | depth: number,
75 | indexOrName: number | string | undefined,
76 | collapsed: Collapsed,
77 | collapseObjectsAfterLength: number,
78 | customOptions?: CustomizeOptions
79 | ): boolean {
80 | if (customOptions && customOptions.collapsed !== undefined) return !!customOptions.collapsed
81 | if (typeof collapsed === 'boolean') return collapsed
82 | if (typeof collapsed === 'number' && depth > collapsed) return true
83 |
84 | const size = Math.ceil(node.length / 100)
85 |
86 | if (typeof collapsed === 'function') {
87 | const result = safeCall(collapsed, [{ node, depth, indexOrName, size }])
88 | if (typeof result === 'boolean') return result
89 | }
90 |
91 | if (Array.isArray(node) && size > collapseObjectsAfterLength) return true
92 | if (isObject(node) && size > collapseObjectsAfterLength) return true
93 | return false
94 | }
95 |
96 | export function ifDisplay(displaySize: DisplaySize, depth: number, fold: boolean) {
97 | if (typeof displaySize === 'boolean') return displaySize
98 | if (typeof displaySize === 'number' && depth > displaySize) return true
99 | if (displaySize === 'collapsed' && fold) return true
100 | if (displaySize === 'expanded' && !fold) return true
101 |
102 | return false
103 | }
104 |
105 | export function safeCall any>(func: T, params: Parameters) {
106 | try {
107 | return func(...params)
108 | } catch (event) {
109 | reportError(event)
110 | }
111 | }
112 |
113 | export function editableAdd(editable: Editable) {
114 | if (editable === true) return true
115 | if (isObject(editable) && (editable as { add: boolean }).add === true) return true
116 | }
117 | export function editableEdit(editable: Editable) {
118 | if (editable === true) return true
119 | if (isObject(editable) && (editable as { edit: boolean }).edit === true) return true
120 | }
121 | export function editableDelete(editable: Editable) {
122 | if (editable === true) return true
123 | if (isObject(editable) && (editable as { delete: boolean }).delete === true) return true
124 | }
125 |
126 | function isClassComponent(component: any) {
127 | return typeof component === 'function' && !!component.prototype?.isReactComponent
128 | }
129 | export function isReactComponent(component: any): component is (new () => React.Component) | React.FC {
130 | return typeof component === 'function'
131 | }
132 |
133 | export function customAdd(customOptions?: CustomizeOptions) {
134 | return !customOptions || customOptions.add === undefined || !!customOptions.add
135 | }
136 | export function customEdit(customOptions?: CustomizeOptions) {
137 | return !customOptions || customOptions.edit === undefined || !!customOptions.edit
138 | }
139 | export function customDelete(customOptions?: CustomizeOptions) {
140 | return !customOptions || customOptions.delete === undefined || !!customOptions.delete
141 | }
142 | export function customCopy(customOptions?: CustomizeOptions) {
143 | return !customOptions || customOptions.enableClipboard === undefined || !!customOptions.enableClipboard
144 | }
145 | export function customMatchesURL(customOptions?: CustomizeOptions) {
146 | return !customOptions || customOptions.matchesURL === undefined || !!customOptions.matchesURL
147 | }
148 |
149 | export function resolveEvalFailedNewValue(type: string, value: string) {
150 | if (type === 'string') {
151 | return value.trim().replace(/^\"([\s\S]+?)\"$/, '$1')
152 | }
153 | return value
154 | }
155 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{tsx,css}']
4 | }
5 |
--------------------------------------------------------------------------------
/theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYsuni/react18-json-view/b8513baf7ee215ee6105aa05a6c6c71c6515ee4e/theme.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ES6",
5 | "skipLibCheck": true,
6 | "esModuleInterop": false,
7 | "allowSyntheticDefaultImports": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "module": "ESNext",
10 | "moduleResolution": "Node",
11 | "isolatedModules": true,
12 | "jsx": "react-jsx",
13 | "sourceMap": true,
14 | "declaration": true,
15 | "baseUrl": ".",
16 | "declarationDir": "./dist"
17 | },
18 | "include": ["./src"],
19 | "exclude": ["./node_modules", "./test", "**/*.stories.tsx"]
20 | }
21 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "pipeline": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": ["dist/**", ".next/**"]
8 | },
9 | "dev": {
10 | "cache": false
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
3 | }
4 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/website/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | README.md
4 | .next
5 | pnpm-lock.yaml
6 | dist
7 | storybook-static
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
18 |
19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/website/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | export default React.FC>
3 | }
4 |
5 | declare type NullableObject = Record | null
6 | declare type NullableArray = Record[] | null
7 | declare type Nullable = T | null
8 |
--------------------------------------------------------------------------------
/website/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | webpack: config => {
4 | config.module.rules.push({
5 | test: /\.svg$/i,
6 | use: [{ loader: '@svgr/webpack', options: { dimensions: false } }]
7 | })
8 | return config
9 | }
10 | }
11 |
12 | module.exports = nextConfig
13 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "format": "prettier --write ."
11 | },
12 | "dependencies": {
13 | "@types/node": "20.5.9",
14 | "@types/react": "18.2.21",
15 | "@types/react-dom": "18.2.7",
16 | "autoprefixer": "10.4.15",
17 | "clsx": "^2.0.0",
18 | "highlight.js": "^11.8.0",
19 | "hljs": "^6.2.3",
20 | "next": "13.4.19",
21 | "postcss": "8.4.29",
22 | "prettier": "^2.8.2",
23 | "prettier-plugin-tailwindcss": "^0.2.8",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "react18-json-view": "workspace:*",
27 | "tailwindcss": "3.3.3",
28 | "typescript": "5.2.2",
29 | "zustand": "^4.4.1"
30 | },
31 | "devDependencies": {
32 | "@svgr/webpack": "^8.1.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/website/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/website/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | jsxSingleQuote: true,
3 | jsxBracketSameLine: true,
4 | printWidth: 120,
5 | singleQuote: true,
6 | trailingComma: 'none',
7 | useTabs: true,
8 | tabWidth: 2,
9 | semi: false,
10 | arrowParens: 'avoid',
11 | bracketSameLine: true,
12 | plugins: [require('prettier-plugin-tailwindcss')]
13 | }
14 |
--------------------------------------------------------------------------------
/website/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/website/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YYsuni/react18-json-view/b8513baf7ee215ee6105aa05a6c6c71c6515ee4e/website/public/og.png
--------------------------------------------------------------------------------
/website/src/app/footer.tsx:
--------------------------------------------------------------------------------
1 | export default function Footer() {
2 | return (
3 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/website/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body {
7 | @apply h-full;
8 | }
9 |
10 | body {
11 | background-image: linear-gradient(to bottom, #eef4f6 0, transparent 400px);
12 | background-repeat: no-repeat;
13 | @apply bg-[#fefefe] text-[#383a42] dark:bg-[#272a34] dark:text-[#eee];
14 | }
15 |
16 | .dark body {
17 | background-image: none;
18 | }
19 |
--------------------------------------------------------------------------------
/website/src/app/head.tsx:
--------------------------------------------------------------------------------
1 | export default function Head() {
2 | return (
3 | <>
4 | JsonView
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | >
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/website/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import Footer from './footer'
2 | import './globals.css'
3 | import Head from './head'
4 |
5 | export default function RootLayout({ children }: { children: React.ReactNode }) {
6 | return (
7 |
8 |
9 |
10 |
27 |
28 | {children}
29 |
30 |
31 |
32 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/website/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import 'react18-json-view/src/style.css'
4 | import 'react18-json-view/src/dark.css'
5 |
6 | import Installation from '@/contents/installation'
7 | import Hero from '@/contents/hero'
8 | import Usage from '@/contents/usage'
9 | import Themes from '@/contents/themes'
10 | import Collapsed from '@/contents/collapsed'
11 | import Editable from '@/contents/editable'
12 | import Customization from '@/contents/customization'
13 | import CollapseString from '@/contents/collapse-string'
14 | import DisplaySize from '@/contents/display-size'
15 | import DisplayArrayIndex from '@/contents/display-array-index'
16 |
17 | export default function Home() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/website/src/components/single-loading.tsx:
--------------------------------------------------------------------------------
1 | export default function SingleLoading({ width = 16, height = 16 }) {
2 | return (
3 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/website/src/components/theme.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import LightSVG from '@/svgs/light.svg'
4 | import DarkSVG from '@/svgs/dark.svg'
5 | import SystemSVG from '@/svgs/system.svg'
6 | import { useEffect } from 'react'
7 | import { setStorage } from '@/lib/storage'
8 | import { useTheme } from '@/hooks/useTheme'
9 | import { useState } from 'react'
10 | import SingleLoading from './single-loading'
11 |
12 | export default function Theme() {
13 | const { theme, setTheme } = useTheme()
14 |
15 | const [show, setShow] = useState(false)
16 |
17 | useEffect(() => {
18 | setShow(true)
19 | }, [])
20 |
21 | useEffect(() => {
22 | setStorage('theme', theme)
23 |
24 | if (theme === 'dark') {
25 | document.documentElement.classList.add('dark')
26 | } else if (theme === 'light') {
27 | document.documentElement.classList.remove('dark')
28 | } else {
29 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
30 | document.documentElement.classList.add('dark')
31 | } else {
32 | document.documentElement.classList.remove('dark')
33 | }
34 |
35 | const listener = (event: MediaQueryListEvent) => {
36 | if (event.matches) {
37 | document.documentElement.classList.add('dark')
38 | } else {
39 | document.documentElement.classList.remove('dark')
40 | }
41 | }
42 |
43 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', listener)
44 |
45 | return window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', listener)
46 | }
47 | }, [theme])
48 |
49 | if (show)
50 | return (
51 |
70 | )
71 | return (
72 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/website/src/contents/collapse-string.tsx:
--------------------------------------------------------------------------------
1 | import { writeText } from '@/lib/clipboard'
2 | import clsx from 'clsx'
3 | import hljs from 'highlight.js/lib/core'
4 | import { useState } from 'react'
5 | import JsonView from 'react18-json-view'
6 | import CopySVG from '@/svgs/copy.svg'
7 | import CopiedSVG from '@/svgs/copied.svg'
8 |
9 | type Option = 'directly' | 'word' | 'address'
10 | const options: Option[] = ['directly', 'word', 'address']
11 |
12 | export default function CollapseString() {
13 | const [selected, setSelected] = useState(options[0])
14 | const [length, setLength] = useState(20)
15 |
16 | const code = ``
17 |
18 | const [copied, setCopied] = useState(false)
19 |
20 | const copy = () => {
21 | writeText(code)
22 | setCopied(true)
23 | setTimeout(() => setCopied(false), 2000)
24 | }
25 |
26 | const highlightedCode = hljs.highlight(code, { language: 'js' }).value
27 |
28 | return (
29 | <>
30 | Collapse String
31 |
32 |
33 |
34 | {options.map(item => (
35 | - setSelected(item)}>
42 | {item}
43 |
44 | ))}
45 |
46 |
47 |
{
52 | const target = e.target as HTMLInputElement
53 | const value = target.value
54 |
55 | setLength(+value)
56 | }}
57 | />
58 |
59 |
60 |
61 |
62 |
67 |
68 |
71 |
72 |
73 |
74 | {
90 | const functors = [({imports: $h_imports, liveVar: $h_live, onceVar: $h_once, importMeta: $h____meta})=>{
91 | $h_imports([]);
92 | const universalThis = globalThis;
93 | $h_once.universalThis(universalThis);
94 | const {Array: Array, Date: Date, FinalizationRegistry: FinalizationRegistry, Float32Array: Float32Array, JSON: JSON, Map: Map, Math: Math, Number: Number, Object: Object, Promise: Promise, Proxy: Proxy, Reflect: Reflect, RegExp: FERAL_REG_EXP, Set: Set, String: String, Symbol: Symbol, WeakMap: WeakMap, WeakSet: WeakSet} = globalThis;
95 | $h_once.Array(Array),
96 | $h_once.Date(Date),
97 | $h_once.FinalizationRegistry(FinalizationRegistry),
98 | $h_once.Float32Array(Float32Array),
99 | $h_once.JSON(JSON),
100 | $h_once.Map(Map),
101 | $h_once.Math(Math),
102 | $h_once.Number(Number),`,
103 | multiSpaces: ` blank tab lf
104 |
105 | `
106 | }}
107 | />
108 |
109 | >
110 | )
111 | }
112 |
--------------------------------------------------------------------------------
/website/src/contents/collapsed.tsx:
--------------------------------------------------------------------------------
1 | import { writeText } from '@/lib/clipboard'
2 | import clsx from 'clsx'
3 | import hljs from 'highlight.js/lib/core'
4 | import { useState } from 'react'
5 | import JsonView from 'react18-json-view'
6 | import CopySVG from '@/svgs/copy.svg'
7 | import CopiedSVG from '@/svgs/copied.svg'
8 |
9 | type Option = '0' | '1' | '2' | '3' | 'true' | 'false' | 'function'
10 | const options: Option[] = ['0', '1', '2', '3', 'true', 'false', 'function']
11 |
12 | const valueMap: Record