Skip to content

Commit cdded8e

Browse files
authored
#139 Theme refactor (#140)
Move themes outside main bundle for better tree shaking
1 parent 6a3d3ca commit cdded8e

26 files changed

+364
-356
lines changed

README.md

+37-12
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ A [React](https://github.com/facebook/react) component for editing or viewing JS
2626
<img width="392" alt="screenshot" src="image/screenshot.png">
2727

2828
> [!IMPORTANT]
29-
> **Version 1.14.0** has a change which recommends you provide a `setData` prop and not use `onUpdate` for updating your data externally. See [Managing state](#managing-state).
29+
> Breaking changes:
30+
> - **Version 1.19.0** has a change to the `theme` input. Built-in themes must now
31+
> be imported separately and passed in, rather than just naming the theme as a
32+
> string. This is better for tree-shaking, so unused themes won't be bundled
33+
> with your build. See [Themes & Styles](#themes--styles)
34+
> - **Version 1.14.0** has a change which recommends you provide a `setData` prop
35+
> and not use `onUpdate` for updating your data externally. See [Managing
36+
> state](#managing-state).
3037
3138
## Contents <!-- omit in toc -->
3239
- [Installation](#installation)
@@ -136,7 +143,7 @@ The only *required* value is `data` (although you will need to provide a `setDat
136143
| `defaultValue` | `any\|DefaultValueFilterFunction` | `null` | When a new property is added, it is initialised with this value. A [function can be provided](#filter-functions) with the almost the same input as the `FilterFunction`s, but should output a value. This allows a different default value to be used depending on the data state (e.g. default for top level is an object, but a string elsewhere.) |
137144
| `stringTruncate` | `number` | `250` | String values longer than this many characters will be displayed truncated (with `...`). The full string will always be visible when editing. |
138145
| `translations` | `LocalisedStrings` object | `{ }` | UI strings (such as error messages) can be translated by passing an object containing localised string values (there are only a few). See [Localisation](#localisation) |
139-
| `theme` | `string\|ThemeObject\|[string, ThemeObject]` | `"default"` | Either the name of one of the built-in themes, or an object specifying some or all theme properties. See [Themes](#themes--styles). |
146+
| `theme` | `ThemeInput` | `default` | Either one of the built-in themes (imported separately), or an object specifying some or all theme properties. See [Themes](#themes--styles). |
140147
| `className` | `string` | | Name of a CSS class to apply to the component. In most cases, specifying `theme` properties will be more straightforward. |
141148
| `id` | `string` | | Name for the HTML `id` attribute on the main component container. |
142149
| `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here. See [Themes](#themes--styles). | |
@@ -414,14 +421,31 @@ An example custom search function can be seen in the [Demo](#https://carlosnz.gi
414421
415422
## Themes & Styles
416423
417-
There is a small selection of built-in themes (as seen in the [Demo app](https://carlosnz.github.io/json-edit-react/)). In order to use one of these, just pass the name into the `theme` prop (although realistically, these exist more to showcase the capabilities — I'm open to better built-in themes, so feel free to [create an issue](https://github.com/CarlosNZ/json-edit-react/issues) with suggestions). The available themes are:
418-
- `default`
419-
- `githubDark`
420-
- `githubLight`
421-
- `monoDark`
422-
- `monoLight`
423-
- `candyWrapper`
424-
- `psychedelic`
424+
There is a small selection of built-in themes (as seen in the [Demo app](https://carlosnz.github.io/json-edit-react/)). In order to use one of these, just import it from the package and pass it as the theme prop:
425+
426+
```js
427+
import { JsonEditor, githubDarkTheme } from 'json-edit-react'
428+
// ...other imports
429+
430+
const MyApp = () => {
431+
const [ data, setData ] = useState({ one: 1, two: 2 })
432+
433+
return <JsonEditor
434+
data={data}
435+
setData={setData}
436+
theme={githubDarkTheme}
437+
// other props...
438+
/>
439+
}
440+
```
441+
442+
The following themes are available in the package (although realistically, these exist more to showcase the capabilities — I'm open to better built-in themes, so feel free to [create an issue](https://github.com/CarlosNZ/json-edit-react/issues) with suggestions):
443+
- `githubDarkTheme`
444+
- `githubLightTheme`
445+
- `monoDarkTheme`
446+
- `monoLightTheme`
447+
- `candyWrapperTheme`
448+
- `psychedelicTheme`
425449
426450
However, you can pass in your own theme object, or part thereof. The theme structure is as follows (this is the "default" theme definition):
427451
@@ -471,7 +495,7 @@ For a simple example, if you want to use the "githubDark" theme, but just change
471495
```js
472496
// in <JsonEditor /> props
473497
theme={[
474-
'githubDark',
498+
githubDarkTheme,
475499
{
476500
iconEdit: 'grey',
477501
boolean: { color: 'red', fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' },
@@ -488,7 +512,7 @@ Or you could create your own theme from scratch and overwrite the whole theme ob
488512
489513
So, to summarise, the `theme` prop can take *either*:
490514
491-
- a theme name e.g. `"candyWrapper"`
515+
- an imported theme, e.g `"candyWrapperTheme"`
492516
- a theme object:
493517
- can be structured as above with `fragments`, `styles`, `displayName` etc., or just the `styles` part (at the root level)
494518
- a theme name *and* an override object in an array, i.e. `[ "<themeName>, {...overrides } ]`
@@ -751,6 +775,7 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s
751775
752776
## Changelog
753777
778+
- **1.19.0**: Built-in [themes](#themes--styles) must now be imported separately -- this improves tree-shaking to prevent unused themes being bundled with your build
754779
- **1.18.0**:
755780
- Ability to [customise keyboard controls](#keyboard-customisation)
756781
- Option to insert new values at the top

demo/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"ajv": "^8.16.0",
1919
"firebase": "^10.13.0",
2020
"framer-motion": "^11.0.3",
21-
"json-edit-react": "^1.18.0",
21+
"json-edit-react": "^1.19.0-beta1",
2222
"json5": "^2.2.3",
2323
"react": "^18.2.0",
2424
"react-datepicker": "^7.5.0",

demo/src/App.tsx

+37-22
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import JSON5 from 'json5'
44
import 'react-datepicker/dist/react-datepicker.css'
55
import {
66
JsonEditor,
7-
themes,
8-
ThemeName,
97
Theme,
108
FilterFunction,
119
JsonData,
1210
OnErrorFunction,
11+
defaultTheme,
12+
// Additional Themes
13+
githubDarkTheme,
14+
githubLightTheme,
15+
monoLightTheme,
16+
monoDarkTheme,
17+
candyWrapperTheme,
18+
psychedelicTheme,
1319
} from './_imports'
1420
import { FaNpm, FaExternalLinkAlt, FaGithub } from 'react-icons/fa'
1521
import { BiReset } from 'react-icons/bi'
@@ -53,7 +59,7 @@ interface AppState {
5359
collapseLevel: number
5460
collapseTime: number
5561
showCount: 'Yes' | 'No' | 'When closed'
56-
theme: ThemeName | Theme
62+
theme: Theme
5763
allowEdit: boolean
5864
allowDelete: boolean
5965
allowAdd: boolean
@@ -65,6 +71,16 @@ interface AppState {
6571
searchText: string
6672
}
6773

74+
const themes = [
75+
defaultTheme,
76+
githubDarkTheme,
77+
githubLightTheme,
78+
monoLightTheme,
79+
monoDarkTheme,
80+
candyWrapperTheme,
81+
psychedelicTheme,
82+
]
83+
6884
function App() {
6985
const navigate = useLocation()[1]
7086
const searchString = useSearch()
@@ -78,7 +94,7 @@ function App() {
7894
collapseLevel: dataDefinition.collapse ?? 2,
7995
collapseTime: 300,
8096
showCount: 'When closed',
81-
theme: 'default',
97+
theme: defaultTheme,
8298
allowEdit: true,
8399
allowDelete: true,
84100
allowAdd: true,
@@ -91,15 +107,15 @@ function App() {
91107
})
92108

93109
const [isSaving, setIsSaving] = useState(false)
94-
const previousThemeName = useRef('') // Used when resetting after theme editing
110+
const previousTheme = useRef<Theme>() // Used when resetting after theme editing
95111
const toast = useToast()
96112

97113
const { liveData, loading, updateLiveData } = useDatabase()
98114

99115
const [
100116
{ present: data, past, future },
101117
{ set: setData, reset, undo: undoData, redo: redoData, canUndo, canRedo },
102-
] = useUndo(selectedDataSet === 'editTheme' ? themes.default : dataDefinition.data)
118+
] = useUndo(selectedDataSet === 'editTheme' ? defaultTheme : dataDefinition.data)
103119
// Provides a named version of these methods (i.e undo.name = "undo")
104120
const undo = () => undoData()
105121
const redo = () => redoData()
@@ -166,8 +182,8 @@ function App() {
166182

167183
switch (selected) {
168184
case 'editTheme':
169-
previousThemeName.current = theme as string
170-
reset(typeof theme === 'string' ? themes[theme] : theme)
185+
previousTheme.current = theme
186+
reset(theme)
171187
break
172188
case 'liveData':
173189
if (!liveData) reset({ 'Oops!': "We couldn't load this data, sorry " })
@@ -182,11 +198,12 @@ function App() {
182198
}
183199

184200
const handleThemeChange = (e) => {
185-
const selected = e.target.value
186-
updateState({ theme: selected })
201+
const theme = themes.find((th) => th.displayName === e.target.value)
202+
if (!theme) return
203+
updateState({ theme })
187204
if (selectedDataSet === 'editTheme') {
188-
setData(themes[selected])
189-
previousThemeName.current = selected
205+
setData(theme)
206+
previousTheme.current = theme
190207
}
191208
}
192209

@@ -204,8 +221,8 @@ function App() {
204221

205222
switch (selectedDataSet) {
206223
case 'editTheme':
207-
reset(themes[previousThemeName.current])
208-
newState.theme = themes[previousThemeName.current]
224+
reset(previousTheme.current ?? defaultTheme)
225+
newState.theme = previousTheme.current ?? defaultTheme
209226
break
210227
case 'liveData':
211228
setIsSaving(true)
@@ -468,14 +485,12 @@ function App() {
468485
Theme
469486
</FormLabel>
470487
<div className="inputWidth" style={{ flexGrow: 1 }}>
471-
<Select id="themeSelect" onChange={handleThemeChange} value={theme}>
472-
{(Object.entries(themes) as [ThemeName, Theme][]).map(
473-
([theme, { displayName }]) => (
474-
<option value={theme} key={theme}>
475-
{displayName}
476-
</option>
477-
)
478-
)}
488+
<Select id="themeSelect" onChange={handleThemeChange} value={theme.displayName}>
489+
{themes.map((theme) => (
490+
<option value={theme.displayName} key={theme.displayName}>
491+
{theme.displayName}
492+
</option>
493+
))}
479494
</Select>
480495
</div>
481496
</HStack>

demo/src/_imports.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export * from 'json-edit-react'
1010

1111
/* Compiled local package */
1212
// export * from './package/build'
13+
14+
// export * from './json-edit-react/src/additionalThemes/themes'

demo/src/version.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const version = '1.18.0'
1+
export const version = '1.19.0-beta1'

demo/tsconfig.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"strict": true,
1010
"forceConsistentCasingInFileNames": true,
1111
"noFallthroughCasesInSwitch": true,
12-
"module": "esnext",
13-
"moduleResolution": "node",
12+
"module": "NodeNext",
13+
"moduleResolution": "NodeNext",
1414
"resolveJsonModule": true,
1515
"isolatedModules": true,
1616
"noEmit": true,

demo/yarn.lock

+8-8
Original file line numberDiff line numberDiff line change
@@ -8114,13 +8114,13 @@ [email protected]:
81148114
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
81158115
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
81168116

8117-
json-edit-react@^1.18.0:
8118-
version "1.18.0"
8119-
resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.18.0.tgz#396254d9e56e75e2ab0c0f104b6fa506bb77ad78"
8120-
integrity sha512-PJyaJfCWHf9qyAjgIBBeKfF8Vxl2GhfPaFP+Lw3tMfj5SNq81gnO5pU0M+mRUmkqVpYqmUiuipJeqwIKYFv2og==
8117+
json-edit-react@^1.19.0-beta1:
8118+
version "1.19.0-beta1"
8119+
resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.19.0-beta1.tgz#ea8930704c4b11304b760fef1257a7717e36c01c"
8120+
integrity sha512-8kCuXISfk3sOTC+P9UvWSYodDDnvLZB+robPhFM0Igh32L3BQo7pkvvhhvROEuWWbVabRhxeaJUa0FJ/ADXTfg==
81218121
dependencies:
8122-
object-property-assigner "^1.3.1"
8123-
object-property-extractor "^1.0.12"
8122+
object-property-assigner "^1.3.5"
8123+
object-property-extractor "^1.0.13"
81248124

81258125
json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
81268126
version "2.3.1"
@@ -8734,12 +8734,12 @@ object-keys@^1.1.1:
87348734
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
87358735
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
87368736

8737-
object-property-assigner@^1.3.1:
8737+
object-property-assigner@^1.3.5:
87388738
version "1.3.5"
87398739
resolved "https://registry.yarnpkg.com/object-property-assigner/-/object-property-assigner-1.3.5.tgz#f5037ff73b7b015a42e4059c3ff5c84017413ca2"
87408740
integrity sha512-DIzHzNSTnpoG8QPQCDNrHa6O3vLMhktK3Igirqpk523UYIVe8JNCKcn5C9WyLQxJc58EGsAIiiEu10gqPrud8w==
87418741

8742-
object-property-extractor@^1.0.12:
8742+
object-property-extractor@^1.0.13:
87438743
version "1.0.13"
87448744
resolved "https://registry.yarnpkg.com/object-property-extractor/-/object-property-extractor-1.0.13.tgz#fff188d6308ac5574eb2610221af906eb87b4f38"
87458745
integrity sha512-9kgEjTWDhTPuPn7nyof+5mLmCKBPKdU0c7IVpTbOvYKYSdXQ5skH4Pa/8MPbZXeyXBGrqS82JyWecsh6tMxiLw==

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "json-edit-react",
3-
"version": "1.18.0",
3+
"version": "1.19.0-beta1",
44
"description": "React component for editing or viewing JSON/object data",
55
"main": "build/index.cjs.js",
66
"module": "build/index.esm.js",
7-
"sideEffects": false,
87
"types": "build/index.d.ts",
8+
"sideEffects": false,
99
"exports": {
1010
".": {
1111
"types": "./build/index.d.ts",
@@ -45,6 +45,7 @@
4545
"object-property-extractor": "^1.0.13"
4646
},
4747
"devDependencies": {
48+
"@rollup/plugin-node-resolve": "^16.0.0",
4849
"@rollup/plugin-terser": "^0.4.4",
4950
"@rollup/plugin-typescript": "^11.1.6",
5051
"@types/node": "^20.11.17",

rollup.config.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import bundleSize from 'rollup-plugin-bundle-size'
77
import sizes from 'rollup-plugin-sizes'
88

99
export default [
10+
// Main Package
1011
{
1112
input: 'src/index.ts',
1213
output: [
@@ -29,6 +30,7 @@ export default [
2930
],
3031
external: ['object-property-assigner', 'object-property-extractor'],
3132
},
33+
// Types
3234
{
3335
input: './build/dts/index.d.ts',
3436
output: [{ file: 'build/index.d.ts', format: 'es' }],

src/ButtonPanels.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from 'react'
22
import { Icon } from './Icons'
3-
import { useTheme } from './theme'
3+
import { useTheme } from './contexts'
44
import { type TranslateFunction } from './localisation'
55
import {
66
type CollectionKey,

src/CollectionNode.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { type CollectionNodeProps, type NodeData, type CollectionData } from './
77
import { Icon } from './Icons'
88
import { filterNode, getModifier, isCollection } from './helpers'
99
import { AutogrowTextArea } from './AutogrowTextArea'
10-
import { useTheme } from './theme'
11-
import { useTreeState } from './TreeStateProvider'
10+
import { useTheme, useTreeState } from './contexts'
1211
import { useCollapseTransition, useCommon, useDragNDrop } from './hooks'
1312

1413
export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {

src/Icons.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { useTheme } from './theme'
2+
import { useTheme } from './contexts'
33
import { type NodeData } from './types'
44

55
// All icons from: https://reactsvgicons.com/

src/JsonEditor.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import {
2323
type JsonData,
2424
type KeyboardControls,
2525
} from './types'
26-
import { useTheme, ThemeProvider } from './theme'
27-
import { TreeStateProvider } from './TreeStateProvider'
26+
import { useTheme, ThemeProvider, TreeStateProvider, defaultTheme } from './contexts'
2827
import { useData } from './hooks/useData'
2928
import { getTranslateFunction } from './localisation'
3029
import { ValueNodeWrapper } from './ValueNodeWrapper'
30+
3131
import './style.css'
3232

3333
const Editor: React.FC<JsonEditorProps> = ({
@@ -333,7 +333,7 @@ const Editor: React.FC<JsonEditorProps> = ({
333333

334334
export const JsonEditor: React.FC<JsonEditorProps> = (props) => {
335335
return (
336-
<ThemeProvider theme={props.theme} icons={props.icons}>
336+
<ThemeProvider theme={props.theme ?? defaultTheme} icons={props.icons}>
337337
<TreeStateProvider>
338338
<Editor {...props} />
339339
</TreeStateProvider>

src/ValueNodeWrapper.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ import {
1717
type ValueData,
1818
type JsonData,
1919
} from './types'
20-
import { useTheme } from './theme'
20+
import { useTheme, useTreeState } from './contexts'
2121
import { getCustomNode, type CustomNodeData } from './CustomNode'
2222
import { filterNode } from './helpers'
23-
import { useTreeState } from './TreeStateProvider'
2423
import { useCommon, useDragNDrop } from './hooks'
2524

2625
export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {

src/ValueNodes.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useEffect } from 'react'
22
import { AutogrowTextArea } from './AutogrowTextArea'
33
import { toPathString, truncate } from './helpers'
4-
import { useTheme } from './theme'
4+
import { useTheme } from './contexts'
55
import { type InputProps } from './types'
66

77
export const INVALID_FUNCTION_STRING = '**INVALID_FUNCTION**'

0 commit comments

Comments
 (0)