Skip to content

Commit b39bc62

Browse files
committed
Improve for custom components library (#192)
* DatePicker component etc * Add Boolean Toggle * Symbol component * BigInt component * Create json stringifier and parsers with replacer functions for non-JSON values * Improve inter-linking between packages * Use custom jsonStringify internally
1 parent eab1886 commit b39bc62

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+740
-452
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ temp.js
4747
demo/src/firebaseConfig.json
4848

4949
# Built package in demo/custom library
50+
build_package
5051
demo/src/package
5152
custom-component-library/components/package
5253
.original-readme.md

README.md

+23-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ A highly-configurable [React](https://github.com./facebook/react) component for e
2121
- 🎨 **Customisable UI** — built-in or custom [themes](#themes--styles), CSS overrides or targeted classes
2222
- 📦 **Self-contained** — plain HTML/CSS, so no dependence on external UI libraries
2323
- 🔍 **Search & filter** — find data by key, value or custom function
24-
- 🚧 **[Custom components](#custom-nodes)** — replace specific nodes with specialised components (e.g. date picker, links, images)
24+
- 🚧 **[Custom components](#custom-nodes)** — replace specific nodes with specialised components (e.g. date picker, links, images, `undefined`, `BigInt`, `Symbol`)
2525
- 🌏 **[Localisation](#localisation)** — easily translate UI labels and messages
2626
- 🔄 **[Drag-n-drop](#drag-n-drop)** re-ordering within objects/arrays
2727
- 🎹 **[Keyboard customisation](#keyboard-customisation)** — define your own key bindings
@@ -71,6 +71,7 @@ A highly-configurable [React](https://github.com./facebook/react) component for e
7171
- [Localisation](#localisation)
7272
- [Custom Nodes](#custom-nodes)
7373
- [Active hyperlinks](#active-hyperlinks)
74+
- [Handling JSON](#handling-json)
7475
- [Custom Collection nodes](#custom-collection-nodes)
7576
- [Custom Text](#custom-text)
7677
- [Custom Buttons](#custom-buttons)
@@ -918,8 +919,9 @@ Your `translations` object doesn't have to be exhaustive — only define the key
918919
919920
You can replace certain nodes in the data tree with your own custom components. An example might be for an image display, or a custom date editor, or just to add some visual bling. See the "Custom Nodes" data set in the [interactive demo](https://carlosnz.github.io/json-edit-react/?data=customNodes) to see it in action. (There is also a custom Date picker that appears when editing ISO strings in the other data sets.)
920921
921-
> [!NOTE]
922-
> Coming soon: a **Custom Component** library
922+
> [!TIP]
923+
> There are a selection of useful Custom components ready for you to use in my [Custom Component Library](https://github.com./CarlosNZ/json-edit-react/blob/main/custom-component-library/README.md).
924+
> Please contribute your own if you think they'd be useful to others.
923925
924926
Custom nodes are provided in the `customNodeDefinitions` prop, as an array of objects of following structure:
925927
@@ -942,6 +944,10 @@ Custom nodes are provided in the `customNodeDefinitions` prop, as an array of ob
942944
showCollectionWrapper // boolean (optional), default true
943945
wrapperElement // React component (optional) to wrap *outside* the normal collection wrapper
944946
wrapperProps // object (optional) -- props for the above wrapper component
947+
948+
// For JSON conversion -- only needed if editing as JSON text
949+
stringifyReplacer // function for stringifying to JSON (if non-JSON data type)
950+
parseReviver?: // function for parsing as JSON (if non-JSON data type)
945951
}
946952
```
947953
@@ -973,6 +979,17 @@ return (
973979
)
974980
```
975981
982+
### Handling JSON
983+
984+
If you implement a Custom Node that uses a non-JSON data type (e.g. `BigInt`, `Date`), then if you edit your data as full JSON text, these values will be stripped out by the default `JSON.stringify` and `JSON.parse` methods. In this case, you can provide [**replacer**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#replacer) and [**reviver**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#the_reviver_parameter) methods to serialize and de-serialize your data as you see fit. For example the [`BigInt` component](https://github.com./CarlosNZ/json-edit-react/blob/main/custom-component-library/components/BigInt/definition.ts) in the [custom component library](https://github.com./CarlosNZ/json-edit-react/blob/main/custom-component-library/components/DateObject/definition.ts) serializes the value into JSON text like so:
985+
986+
```json
987+
{
988+
"__type": "BigInt",
989+
"value": 1234567890123456789012345678901234567890
990+
}
991+
```
992+
976993
### Custom Collection nodes
977994
978995
In most cases it will be preferable (and simpler) to create custom nodes to match *value* nodes (i.e. not `array` or `object` *collection* nodes), which is what all the [Demo](https://carlosnz.github.io/json-edit-react/?data=customNodes) examples show. However, if you *do* wish to target a whole collection node, there are a couple of other things to know:
@@ -1199,6 +1216,9 @@ This component is heavily inspired by [react-json-view](https://github.com./mac-s
11991216
12001217
## Changelog
12011218
1219+
- **1.26.0**:
1220+
- Handle non-standard data types (e.g. `undefined`, `BigInt`) when stringifying/parsing JSON
1221+
- More custom components (See [library ReadMe](https://github.com./CarlosNZ/json-edit-react/blob/main/custom-component-library/README.md))
12021222
- **1.25.6**:
12031223
- Expose a few more components and props to custom components
12041224
- Start building Custom Component library (separate to main package)

custom-component-library/README.md

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
A collection of [Custom Components](https://github.com./CarlosNZ/json-edit-react#custom-nodes) for **json-edit-react**.
22

3-
A work in progress.
3+
Eventually, I'd like to publish these in a separate package so you can easily import them. But for now just copy the code out of this repo.
44

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

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

9+
> [!NOTE]
10+
> If you create a custom component that you think would be useful to others, please [create a PR](https://github.com./CarlosNZ/json-edit-react/pulls) for it.
11+
912
## Components
1013

11-
These are the ones I'm planning for now:
14+
These are the ones currently available:
1215

1316
- [x] Hyperlink/URL
1417
- [x] Undefined
1518
- [x] Date Object
16-
- [ ] Date Picker (with ISO string)
17-
- [ ] `NaN`
18-
- [ ] BigInt
19+
- [x] Date/Time Picker (with ISO string)
20+
- [x] Boolean Toggle
21+
- [x] `NaN`
22+
- [x] BigInt
1923

2024
## Development
2125

@@ -33,3 +37,12 @@ Launch app:
3337
yarn dev
3438
```
3539

40+
## Guidelines for development:
41+
42+
Custom components should consider the following:
43+
44+
- Must respect editing restrictions
45+
- If including CSS classes, please prefix with `jer-`
46+
- Handle keyboard input if possible
47+
- Provide customisation options, particularly styles
48+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react'
2+
import { toPathString, StringEdit, type CustomNodeProps } from '@json-edit-react'
3+
4+
export interface BigIntProps {
5+
style?: React.CSSProperties
6+
descriptionStyle?: React.CSSProperties
7+
}
8+
9+
export const BigIntComponent: React.FC<CustomNodeProps<BigIntProps>> = (props) => {
10+
const {
11+
setValue,
12+
isEditing,
13+
getStyles,
14+
nodeData,
15+
customNodeProps = {},
16+
value,
17+
handleEdit,
18+
...rest
19+
} = props
20+
const { path } = nodeData
21+
const { style = { color: '#006291', fontSize: '90%' } } = customNodeProps
22+
23+
const editDisplayValue = typeof value === 'bigint' ? String(value) : (value as string)
24+
25+
return isEditing ? (
26+
<StringEdit
27+
pathString={toPathString(path)}
28+
styles={getStyles('input', nodeData)}
29+
value={editDisplayValue}
30+
setValue={setValue as React.Dispatch<React.SetStateAction<string>>}
31+
{...rest}
32+
handleEdit={() => {
33+
handleEdit(BigInt(nodeData.value as string))
34+
}}
35+
/>
36+
) : (
37+
<span style={style}>{value as bigint}</span>
38+
)
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { isCollection, type CustomNodeDefinition } from '@json-edit-react'
2+
import { BigIntComponent, BigIntProps } from './component'
3+
4+
export const BigIntDefinition: CustomNodeDefinition<BigIntProps> = {
5+
condition: ({ value }) => typeof value === 'bigint',
6+
element: BigIntComponent,
7+
// customNodeProps: {},
8+
showOnView: true,
9+
showEditTools: true,
10+
showOnEdit: true,
11+
name: 'BigInt', // shown in the Type selector menu
12+
showInTypesSelector: true,
13+
defaultValue: BigInt(9007199254740992),
14+
stringifyReplacer: (value) =>
15+
typeof value === 'bigint' ? { __type: 'bigint', value: String(value) } : value,
16+
parseReviver: (value) =>
17+
isCollection(value) && '__type' in value && 'value' in value
18+
? BigInt(value.value as string)
19+
: value,
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './definition'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Boolean Toggle
3+
*/
4+
5+
import React from 'react'
6+
import { toPathString, type CustomNodeProps } from '@json-edit-react'
7+
8+
export const BooleanToggleComponent: React.FC<CustomNodeProps> = (props) => {
9+
const { nodeData, value, handleEdit, canEdit } = props
10+
const { path } = nodeData
11+
return (
12+
<input
13+
className="jer-input-boolean"
14+
type="checkbox"
15+
disabled={!canEdit}
16+
name={toPathString(path)}
17+
checked={value as boolean}
18+
onChange={() => {
19+
// In this case we submit the data value immediately, not just the local
20+
// state
21+
handleEdit(!nodeData.value)
22+
// setValue(!value)
23+
}}
24+
/>
25+
)
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { type CustomNodeDefinition } from '@json-edit-react'
2+
import { BooleanToggleComponent } from './component'
3+
4+
export const BooleanToggleDefinition: CustomNodeDefinition<{
5+
linkStyles?: React.CSSProperties
6+
stringTruncate?: number
7+
}> = {
8+
condition: ({ value }) => typeof value === 'boolean',
9+
element: BooleanToggleComponent,
10+
showOnView: true,
11+
showOnEdit: false,
12+
showEditTools: true,
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './definition'

custom-component-library/components/DateObject/component.tsx

+31-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
import React, { useRef } from 'react'
2-
import { StringDisplay, toPathString, StringEdit, type CustomNodeProps } from 'json-edit-react'
2+
import { StringDisplay, toPathString, StringEdit, type CustomNodeProps } from '@json-edit-react'
33

4-
export const DateObjectCustomComponent: React.FC<CustomNodeProps<unknown>> = (props) => {
5-
const { nodeData, isEditing, setValue, getStyles, canEdit, value, handleEdit, onError } = props
4+
export interface DateObjectProps {
5+
showTime?: boolean
6+
}
7+
8+
export const DateObjectCustomComponent: React.FC<CustomNodeProps<DateObjectProps>> = (props) => {
9+
const {
10+
nodeData,
11+
isEditing,
12+
setValue,
13+
getStyles,
14+
canEdit,
15+
value,
16+
handleEdit,
17+
onError,
18+
customNodeProps = {},
19+
} = props
620
const lastValidDate = useRef(value)
721

22+
const { showTime = true } = customNodeProps
23+
824
if (value instanceof Date) lastValidDate.current = value
925

26+
const editDisplayValue =
27+
value instanceof Date
28+
? showTime
29+
? value.toISOString()
30+
: value.toDateString()
31+
: (value as string)
32+
const displayValue = showTime
33+
? (nodeData.value as Date).toLocaleString()
34+
: (nodeData.value as Date).toLocaleDateString()
35+
1036
return isEditing ? (
1137
<StringEdit
1238
styles={getStyles('input', nodeData)}
1339
pathString={toPathString(nodeData.path)}
1440
{...props}
15-
value={value instanceof Date ? value.toISOString() : (value as string)}
41+
value={editDisplayValue}
1642
setValue={setValue as React.Dispatch<React.SetStateAction<string>>}
1743
handleEdit={() => {
1844
const newDate = new Date(value as string)
@@ -32,7 +58,7 @@ export const DateObjectCustomComponent: React.FC<CustomNodeProps<unknown>> = (pr
3258
styles={getStyles('string', nodeData)}
3359
canEdit={canEdit}
3460
pathString={toPathString(nodeData.path)}
35-
value={nodeData.value.toLocaleString()}
61+
value={displayValue}
3662
/>
3763
)
3864
}

custom-component-library/components/DateObject/definition.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { DateObjectCustomComponent } from './component'
2-
import { type CustomNodeDefinition } from 'json-edit-react'
1+
import { DateObjectCustomComponent, DateObjectProps } from './component'
2+
import { type CustomNodeDefinition } from '@json-edit-react'
33

4-
export const DateObjectDefinition: CustomNodeDefinition = {
4+
export const DateObjectDefinition: CustomNodeDefinition<DateObjectProps> = {
55
condition: (nodeData) => nodeData.value instanceof Date,
66
element: DateObjectCustomComponent,
77
showEditTools: true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react'
2+
3+
// Define the props interface for the Button component
4+
interface ButtonProps {
5+
color?: string
6+
textColor?: string
7+
text?: string
8+
onClick?: () => void
9+
}
10+
11+
export const Button: React.FC<ButtonProps> = ({
12+
color = 'rgb(49, 130, 206)',
13+
textColor = 'white',
14+
text = 'Button',
15+
onClick = () => {},
16+
}) => {
17+
const buttonBaseStyles: React.CSSProperties = {
18+
backgroundColor: color,
19+
color: textColor,
20+
}
21+
22+
return (
23+
<button className="jer-button" style={buttonBaseStyles} onClick={onClick}>
24+
{text}
25+
</button>
26+
)
27+
}

0 commit comments

Comments
 (0)