Skip to content

Commit 86ba57e

Browse files
authored
Component library in demo (#206)
* Implement in Demo, Improvements to Markdown renderer * Lazy loading * Update dependencies * Add blurb * Fix import, improve type detection for copy
1 parent bb571dd commit 86ba57e

File tree

19 files changed

+1398
-999
lines changed

19 files changed

+1398
-999
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ Your `translations` object doesn't have to be exhaustive — only define the key
924924
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.)
925925
926926
> [!TIP]
927-
> 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).
927+
> 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) — see examples in the [Demo app](https://carlosnz.github.io/json-edit-react/?data=customComponentLibrary).
928928
> Please contribute your own if you think they'd be useful to others.
929929
930930
Custom nodes are provided in the `customNodeDefinitions` prop, as an array of objects of following structure:

custom-component-library/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ Eventually, I'd like to publish these in a separate package so you can easily im
1010

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

13-
The individual components are in the `/components` folder, along with demo data (in `data.ts`).
13+
The individual components are in the `/components` folder.
14+
15+
There is a React app provided for demo-ing and testing these components -- [Development](#development) below.
1416

1517
> [!NOTE]
1618
> 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.

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

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@
88
* rather than requiring the user to edit the ISO string directly.
99
*/
1010

11-
import React from 'react'
12-
import DatePicker from 'react-datepicker'
11+
import React, { lazy, Suspense } from 'react'
1312
import { Button } from './Button'
1413
import { CustomNodeProps } from '@json-edit-react'
1514

1615
// Styles
1716
import 'react-datepicker/dist/react-datepicker.css'
1817
import './style.css'
18+
import { Loading } from '../_common/Loading'
19+
20+
const DatePicker = lazy(() => import('react-datepicker'))
1921

2022
export interface DatePickerCustomProps {
2123
dateFormat?: string
2224
dateTimeFormat?: string
2325
showTime?: boolean
26+
loadingText?: string
2427
}
2528

2629
export const DateTimePicker: React.FC<CustomNodeProps<DatePickerCustomProps>> = ({
@@ -39,6 +42,7 @@ export const DateTimePicker: React.FC<CustomNodeProps<DatePickerCustomProps>> =
3942
dateFormat = 'MMM d, yyyy',
4043
dateTimeFormat = 'MMM d, yyyy h:mm aa',
4144
showTime = true,
45+
loadingText = 'Loading Date Picker',
4246
} = customNodeProps ?? {}
4347

4448
const date = new Date(value as string)
@@ -54,26 +58,38 @@ export const DateTimePicker: React.FC<CustomNodeProps<DatePickerCustomProps>> =
5458
// at all when viewing (and so will show raw ISO strings). However, we've
5559
// defined an alternative here too, when showOnView == true, in which case
5660
// the date/time string is shown as a localised date/time.
57-
<DatePicker
58-
// Check to prevent invalid date (from previous data value) crashing the
59-
// component
60-
61-
// @ts-expect-error -- isNan can take any input
62-
selected={isNaN(date) ? null : date}
63-
showTimeSelect={showTime}
64-
dateFormat={showTime ? dateTimeFormat : dateFormat}
65-
onChange={(date: Date | null) => date && setValue(date.toISOString())}
66-
open={true}
67-
onKeyDown={handleKeyPress}
61+
<Suspense
62+
fallback={
63+
<div style={stringStyle}>
64+
<Loading text={loadingText} />
65+
</div>
66+
}
6867
>
69-
<div style={{ display: 'inline-flex', gap: 10 }}>
70-
{/* These buttons are not really necessary -- you can either use the
68+
<DatePicker
69+
// Check to prevent invalid date (from previous data value) crashing the
70+
// component
71+
// @ts-expect-error -- isNan can take any input
72+
selected={isNaN(date) ? null : date}
73+
showTimeSelect={showTime}
74+
dateFormat={showTime ? dateTimeFormat : dateFormat}
75+
onChange={(date: Date | null) => date && setValue(date.toISOString())}
76+
open={true}
77+
onKeyDown={handleKeyPress}
78+
>
79+
<div style={{ display: 'inline-flex', gap: 10 }}>
80+
{/* These buttons are not really necessary -- you can either use the
7181
standard Ok/Cancel icons, or keyboard Enter/Esc, but shown for demo
7282
purposes */}
73-
<Button textColor={textColour} color={okColour} onClick={handleEdit} text="OK" />
74-
<Button textColor={textColour} color={cancelColour} onClick={handleCancel} text="Cancel" />
75-
</div>
76-
</DatePicker>
83+
<Button textColor={textColour} color={okColour} onClick={handleEdit} text="OK" />
84+
<Button
85+
textColor={textColour}
86+
color={cancelColour}
87+
onClick={handleCancel}
88+
text="Cancel"
89+
/>
90+
</div>
91+
</DatePicker>
92+
</Suspense>
7793
) : (
7894
<div
7995
// Double-click behaviour same as standard elements

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
* Uses react-markdown to render the markdown content
55
*/
66

7-
import React from 'react'
7+
import React, { lazy, Suspense } from 'react'
88
import { type CustomNodeProps } from '@json-edit-react'
9-
import Markdown from 'react-markdown'
9+
import { Options } from 'react-markdown'
10+
import { Loading } from '../_common/Loading'
1011

11-
export interface LinkProps {
12-
linkStyles?: React.CSSProperties
13-
stringTruncate?: number
14-
[key: string]: unknown
12+
const Markdown = lazy(() => import('react-markdown'))
13+
14+
export interface MarkdownCustomProps extends Options {
15+
loadingText?: string
1516
}
1617

17-
export const MarkdownComponent: React.FC<CustomNodeProps<LinkProps>> = (props) => {
18-
const { setIsEditing, getStyles, nodeData } = props
18+
export const MarkdownComponent: React.FC<CustomNodeProps<MarkdownCustomProps>> = (props) => {
19+
const { setIsEditing, getStyles, nodeData, customNodeProps } = props
1920
const styles = getStyles('string', nodeData)
2021

2122
return (
@@ -24,10 +25,12 @@ export const MarkdownComponent: React.FC<CustomNodeProps<LinkProps>> = (props) =
2425
if (e.getModifierState('Control') || e.getModifierState('Meta')) setIsEditing(true)
2526
}}
2627
onDoubleClick={() => setIsEditing(true)}
27-
style={styles}
28+
style={{ ...styles }}
29+
className="jer-markdown-block"
2830
>
29-
{/* TO-DO: Style over-rides */}
30-
<Markdown>{nodeData.value as string}</Markdown>
31+
<Suspense fallback={<Loading text={customNodeProps?.loadingText ?? 'Loading Markdown'} />}>
32+
<Markdown {...props.customNodeProps}>{nodeData.value as string}</Markdown>
33+
</Suspense>
3134
</div>
3235
)
3336
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type CustomNodeDefinition } from '@json-edit-react'
2-
import { MarkdownComponent, LinkProps } from './component'
2+
import { MarkdownComponent, MarkdownCustomProps } from './component'
33

4-
export const MarkdownNodeDefinition: CustomNodeDefinition<LinkProps> = {
4+
export const MarkdownNodeDefinition: CustomNodeDefinition<MarkdownCustomProps> = {
55
condition: () => false, // Over-ride this for specific cases
66
element: MarkdownComponent,
77
// customNodeProps: {},
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react'
2+
import './style.css'
3+
4+
export const Loading: React.FC<{ text?: string }> = ({ text = 'Loading' }) => {
5+
return <div className="jer-simple-loader">{text}...</div>
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.jer-simple-loader {
2+
width: fit-content;
3+
font-style: italic;
4+
clip-path: inset(0 3ch 0 0);
5+
animation: l4 1s steps(4) infinite;
6+
line-height: 1.2em;
7+
}
8+
@keyframes l4 {
9+
to {
10+
clip-path: inset(0 -1ch 0 0);
11+
}
12+
}

custom-component-library/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@
1919
"react-markdown": "^10.1.0"
2020
},
2121
"devDependencies": {
22-
"@eslint/js": "^9.21.0",
22+
"@eslint/js": "^9.28.0",
2323
"@types/react": "^19.0.10",
2424
"@types/react-dom": "^19.0.4",
25-
"@vitejs/plugin-react": "^4.3.4",
26-
"eslint": "^9.21.0",
25+
"@vitejs/plugin-react": "^4.5.2",
26+
"eslint": "^9.28.0",
2727
"eslint-plugin-react-hooks": "^5.1.0",
28-
"eslint-plugin-react-refresh": "^0.4.19",
28+
"eslint-plugin-react-refresh": "^0.4.20",
2929
"globals": "^15.15.0",
3030
"typescript": "~5.7.2",
31-
"typescript-eslint": "^8.24.1",
32-
"vite": "^6.2.0"
31+
"typescript-eslint": "^8.34.0",
32+
"vite": "^6.3.5"
3333
}
3434
}

custom-component-library/src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@ import {
1414
MarkdownNodeDefinition,
1515
EnhancedLinkCustomNodeDefinition,
1616
} from '../components'
17-
import { testData } from '../components/data'
17+
import { testData } from './data'
1818
import { JsonData, JsonEditor } from '@json-edit-react'
1919

2020
if (testData?.['Date & Time']) {
2121
// @ts-expect-error redefine after initialisation
2222
testData['Date & Time'].Date = STORE_DATE_AS_DATE_OBJECT ? new Date() : new Date().toISOString()
2323

24+
// @ts-expect-error adding property
2425
testData['Date & Time'].info = STORE_DATE_AS_DATE_OBJECT
2526
? 'Date is stored a JS Date object. To use ISO string, set STORE_DATE_AS_DATE_OBJECT to false in App.tsx.'
2627
: 'Date is stored as ISO string. To use JS Date objects, set STORE_DATE_AS_DATE_OBJECT to true in App.tsx.'
28+
29+
// @ts-expect-error only used in Demo app
30+
delete testData['Date & Time']['Date Object']
2731
}
2832

2933
type TestData = typeof testData

custom-component-library/components/data.ts renamed to custom-component-library/src/data.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* The data to be shown in the json-edit-react component, which showcases the
3-
* custom components defined in here.
3+
* custom components defined here.
44
*/
55

66
export const testData = {
@@ -14,6 +14,7 @@ export const testData = {
1414
- DateObject
1515
- Undefined
1616
- Markdown
17+
- "Enhanced" link
1718
- BigInt
1819
- BooleanToggle
1920
- NaN
@@ -26,14 +27,16 @@ export const testData = {
2627
'Long URL':
2728
'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',
2829
'Enhanced Link': {
29-
text: 'This link displays custom text',
30+
text: 'This link displays custom text — try editing me!',
3031
url: 'https://github.com/CarlosNZ/json-edit-react/tree/main/custom-component-library#custom-component-library',
3132
},
3233
},
34+
'Simple boolean toggle': false,
3335
'Date & Time': {
3436
Date: new Date().toISOString(),
37+
'Date Object': new Date(),
3538
'Show Time in Date?': true,
36-
info: 'Replaced in App.tsx',
39+
// info: 'Inserted in App.tsx',
3740
},
3841

3942
'Non-JSON types': {
@@ -43,5 +46,6 @@ export const testData = {
4346
Symbol2: Symbol('Second one'),
4447
BigInt: 1234567890123456789012345678901234567890n,
4548
},
46-
Markdown: 'This text is **bold** and this is *italic*',
49+
Markdown:
50+
'Uses [react-markdown](https://www.npmjs.com/package/react-markdown) to render **Markdown** *text content*. ',
4751
}

0 commit comments

Comments
 (0)