Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ temp.js
.vscode/settings.json
demo/src/firebaseConfig.json

# Built package in demo
# Built package in demo/custom library
demo/src/package
custom-component-library/components/package
.original-readme.md
.npm-readme.md
demo/src/imports/import.ts
24 changes: 24 additions & 0 deletions custom-component-library/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
35 changes: 35 additions & 0 deletions custom-component-library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
A collection of [Custom Components](https://github.com/CarlosNZ/json-edit-react#custom-nodes) for **json-edit-react**.

A work in progress.

Contains a [Vite](https://vite.dev/) web-app for previewing and developing components.

The individual components are in the `/components` folder, along with demo data (in `data.ts`).

## Components

These are the ones I'm planning for now:

- [x] Hyperlink/URL
- [x] Undefined
- [x] Date Object
- [ ] Date Picker (with ISO string)
- [ ] `NaN`
- [ ] BigInt

## Development

From within this folder: `/custom-component-library`:

Install dependencies:

```js
yarn install
```

Launch app:

```js
yarn dev
```

38 changes: 38 additions & 0 deletions custom-component-library/components/DateObject/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useRef } from 'react'
import { StringDisplay, toPathString, StringEdit, type CustomNodeProps } from 'json-edit-react'

export const DateObjectCustomComponent: React.FC<CustomNodeProps<unknown>> = (props) => {
const { nodeData, isEditing, setValue, getStyles, canEdit, value, handleEdit, onError } = props
const lastValidDate = useRef(value)

if (value instanceof Date) lastValidDate.current = value

return isEditing ? (
<StringEdit
styles={getStyles('input', nodeData)}
pathString={toPathString(nodeData.path)}
{...props}
value={value instanceof Date ? value.toISOString() : (value as string)}
setValue={setValue as React.Dispatch<React.SetStateAction<string>>}
handleEdit={() => {
const newDate = new Date(value as string)
try {
// Check if date is valid by trying to convert to ISO
newDate.toISOString()
handleEdit(newDate)
} catch {
handleEdit(lastValidDate.current)
onError({ code: 'UPDATE_ERROR', message: 'Invalid Date' }, value)
}
}}
/>
) : (
<StringDisplay
{...props}
styles={getStyles('string', nodeData)}
canEdit={canEdit}
pathString={toPathString(nodeData.path)}
value={nodeData.value.toLocaleString()}
/>
)
}
12 changes: 12 additions & 0 deletions custom-component-library/components/DateObject/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DateObjectCustomComponent } from './component'
import { type CustomNodeDefinition } from 'json-edit-react'

export const DateObjectDefinition: CustomNodeDefinition = {
condition: (nodeData) => nodeData.value instanceof Date,
element: DateObjectCustomComponent,
showEditTools: true,
showOnEdit: true,
name: 'Date Object', // shown in the Type selector menu
showInTypesSelector: true,
defaultValue: new Date(),
}
1 change: 1 addition & 0 deletions custom-component-library/components/DateObject/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition'
46 changes: 46 additions & 0 deletions custom-component-library/components/Hyperlink/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* An URL display Custom Component
*
* A simple custom node which detects urls in data and makes them active
* hyperlinks.
*/

import React from 'react'
import { toPathString, type CustomNodeProps, StringDisplay } from 'json-edit-react'

export const LinkCustomComponent: React.FC<
CustomNodeProps<{ linkStyles?: React.CSSProperties; stringTruncate?: number }>
> = (props) => {
const { setIsEditing, getStyles, nodeData, customNodeProps = {} } = props
const styles = getStyles('string', nodeData)
const { linkStyles = { fontWeight: 'bold', textDecoration: 'underline' }, stringTruncate = 60 } =
customNodeProps
return (
<div
onClick={(e) => {
if (e.getModifierState('Control') || e.getModifierState('Meta')) setIsEditing(true)
}}
style={styles}
>
<StringDisplay
{...props}
pathString={toPathString(nodeData.path)}
styles={{ ...styles }}
value={nodeData.value as string}
stringTruncate={stringTruncate}
TextWrapper={({ children }) => {
return (
<a
href={nodeData.value as string}
target="_blank"
rel="noreferrer"
style={{ ...styles, ...linkStyles, cursor: 'pointer' }}
>
{children}
</a>
)
}}
/>
</div>
)
}
14 changes: 14 additions & 0 deletions custom-component-library/components/Hyperlink/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type CustomNodeDefinition } from 'json-edit-react'
import { LinkCustomComponent } from './component'

export const LinkCustomNodeDefinition: CustomNodeDefinition<{
linkStyles?: React.CSSProperties
stringTruncate?: number
}> = {
// Condition is a regex to match url strings
condition: ({ value }) => typeof value === 'string' && /^https?:\/\/.+\..+$/.test(value),
element: LinkCustomComponent,
// customNodeProps: { stringTruncate: 80 },
showOnView: true,
showOnEdit: false,
}
1 change: 1 addition & 0 deletions custom-component-library/components/Hyperlink/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition'
6 changes: 6 additions & 0 deletions custom-component-library/components/Undefined/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'
import { type CustomNodeProps } from 'json-edit-react'

export const UndefinedCustomComponent: React.FC<CustomNodeProps<unknown>> = () => (
<div style={{ fontStyle: 'italic', color: '#9b9b9b' }}>undefined</div>
)
12 changes: 12 additions & 0 deletions custom-component-library/components/Undefined/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UndefinedCustomComponent } from './component'
import { type CustomNodeDefinition } from 'json-edit-react'

export const UndefinedDefinition: CustomNodeDefinition = {
condition: ({ value }) => value === undefined,
element: UndefinedCustomComponent,
showEditTools: true,
showOnEdit: true,
name: 'undefined', // shown in the Type selector menu
showInTypesSelector: true,
defaultValue: undefined,
}
1 change: 1 addition & 0 deletions custom-component-library/components/Undefined/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition'
14 changes: 14 additions & 0 deletions custom-component-library/components/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* The data to be shown in the json-edit-react component, which showcases the
* custom components defined in here.
*/

export const testData = {
header: 'A selection of custom components for json-edit-react',
url: 'https://carlosnz.github.io/json-edit-react/',
longUrl:
'https://www.google.com/maps/place/Sky+Tower/@-36.8465603,174.7609398,818m/data=!3m1!1e3!4m6!3m5!1s0x6d0d47f06d4bdc25:0x2d1b5c380ad9387!8m2!3d-36.848448!4d174.762191!16zL20vMDFuNXM2?entry=ttu&g_ep=EgoyMDI1MDQwOS4wIKXMDSoASAFQAw%3D%3D',
DateObject: new Date(),
DateISOstring: new Date().toISOString(),
undefined: undefined,
}
3 changes: 3 additions & 0 deletions custom-component-library/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Hyperlink'
export * from './DateObject'
export * from './Undefined'
28 changes: 28 additions & 0 deletions custom-component-library/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
17 changes: 17 additions & 0 deletions custom-component-library/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="96x96" href="/favicon_96.png">

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JSON Edit React custom components</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
30 changes: 30 additions & 0 deletions custom-component-library/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "custom-component-library",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"json-edit-react": "1.25.6-beta3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
Binary file added custom-component-library/public/favicon_96.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions custom-component-library/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LinkCustomNodeDefinition, DateObjectDefinition, UndefinedDefinition } from '@components'
import { testData } from '@components/data'
import { JsonEditor } from 'json-edit-react'

function App() {
return (
<div id="container">
<h1>json-edit-react</h1>
<h2>Custom component library</h2>
<JsonEditor
data={testData}
customNodeDefinitions={[
LinkCustomNodeDefinition,
DateObjectDefinition,
UndefinedDefinition,
]}
rootName=""
/>
</div>
)
}

export default App
Loading