Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/icon-from-svg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@json-edit-react/utils': minor
---

Add `iconFromSvg`: build a `Theme.icons` `IconDefinition` from raw SVG markup (a full `<svg>` string or bare inner markup), a React `<svg>` element (unwrapped via its props/children), or an existing `IconDefinition` (returned unchanged). String inputs are interned, so an inline `iconFromSvg('<svg…>')` keeps a stable reference across renders.
5 changes: 5 additions & 0 deletions .changeset/theme-owned-icons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'json-edit-react': major
---

Themes now own their icon glyphs. The standalone `icons` prop is removed; supply glyphs via `theme.icons` (keyed `add`/`edit`/`delete`/`copy`/`ok`/`cancel`/`collection`), where each value is an `IconDefinition` (`content` plus optional `viewBox`/`svgProps`/`scale`). User-supplied glyphs are themeable via `currentColor`, just like the built-ins. The expand/collapse key is renamed `chevron` → `collection`. The `IconAdd`…`IconChevron` components, `IconProps`, and `IconReplacements` are no longer exported (the built-in glyphs now live on `defaultTheme.icons`); `IconDefinition`, `ThemeIcons`, and `IconSvg` (the glyph renderer — pass an `IconDefinition`'s parts) are added.
5 changes: 5 additions & 0 deletions .changeset/themes-icon-ready.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@json-edit-react/themes': minor
---

The package build now supports themes that ship their own icon glyphs (`theme.icons`): a glyph's JSX `content` compiles against `react/jsx-runtime`, which is kept external (never bundled). `react` is declared as an optional peer dependency — needed only by a theme that defines icons.
64 changes: 48 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ This is a reference list of *all* possible props, divided into related sections.
| Prop | Type | Default | Description |
| ----------------------- | --------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `theme` | `ThemeInput` | `defaultTheme` | Either one of the built-in themes (imported separately), or an object specifying some or all theme properties — see [Themes](#themes--styles). |
| `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here — see [Themes](#themes--styles). | |
| `showIconTooltips` | `boolean` | false | Display icon tooltips when hovering. | |
| `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. |
| `collapse` | `boolean\|number\|FilterFunction` | `false` | Defines which nodes of the JSON tree will be displayed "opened" in the UI on load — see [Collapse](#collapse). |
Expand Down Expand Up @@ -891,6 +890,7 @@ However, you can pass in your own theme object, or part thereof. The theme struc
```js
{
displayName: 'Default',
// icons: { … }, // optional per-glyph IconDefinitions — see "Icons" below
styles: {
container: {
backgroundColor: '#f6f6f6',
Expand Down Expand Up @@ -950,7 +950,7 @@ So, to summarise, the `theme` prop can take *either*:

- an imported theme, e.g `"candyWrapperTheme"`
- a theme object:
- can be structured as above with `fragments`, `styles`, `displayName` etc., or just the `styles` part (at the root level)
- can be structured as above with `fragments`, `styles`, `displayName`, `icons` (glyphs — see [Icons](#icons)) etc., or just the `styles` part (at the root level)
- a theme name *and* an override object in an array, i.e. `[ "<themeName>, {...overrides } ]`

You can play round with live editing of the themes in the [Demo app](https://carlosnz.github.io/json-edit-react/) by selecting "Edit this theme!" from the "Demo data" selector (though you won't be able to create functions in JSON).
Expand Down Expand Up @@ -1000,21 +1000,52 @@ styles: {

### Icons

The default icons can be replaced, but you need to provide them as React/HTML elements. Just define any or all of them within the `icons` prop, keyed as:
A theme owns its icon **glyphs** as well as their colour. The glyph (which shape renders) lives on `theme.icons`; the colour lives on `theme.styles` under `iconAdd`…`iconCollection` (above). The two are independent — restyle the default glyph, swap the glyph, or both.

```js
icons={{
add: <YourIcon />
edit: <YourIcon />
delete: <YourIcon />
copy: <YourIcon />
ok: <YourIcon />
cancel: <YourIcon />
chevron: <YourIcon />
}}
`theme.icons` is keyed by icon name (`add`, `edit`, `delete`, `copy`, `ok`, `cancel`, and `collection` — the expand/collapse chevron), and each value is an `IconDefinition`. Supply only the glyphs you want to replace; the rest fall back to the defaults.

```ts
interface IconDefinition {
content: React.ReactNode // the inner SVG markup — <path>/<circle>/… (no outer <svg>)
viewBox?: string // defaults to '0 0 24 24'
svgProps?: React.SVGProps<SVGSVGElement> // extra <svg> attrs, e.g. a stroke icon: { fill: 'none', stroke: 'currentColor', strokeWidth: 2 }
scale?: number // per-glyph size tweak (default 1)
}
```

Core renders the wrapping `<svg>` itself, so you supply only what goes inside it:

```tsx
const myTheme = {
icons: {
add: { content: <path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4z" /> },
},
styles: { iconAdd: '#2aa198' }, // colours the glyph
}
```

The Icon components will need to have their own styles defined, as the theme styles *won't* be added to the custom elements.
**Colour follows `currentColor`.** Core applies the theme's icon colour to the `<svg>`, so any glyph path that uses `fill="currentColor"` (or sets no `fill`) adopts it. A path with its own explicit `fill` keeps that colour — so multi-colour glyphs (flags, brand logos) survive theming, as long as every coloured path carries its own `fill`.

**Sizing.** Icons render a little larger than text by default; `scale` is a per-glyph multiplier on that baseline (e.g. `scale: 1.3` renders 30% bigger). Use it only to even out a glyph whose artwork over- or under-fills its viewBox — size lives in the glyph, never in `styles`.

**Pasting raw SVG.** The `iconFromSvg` helper in [`@json-edit-react/utils`](#optional-companion-packages) turns a raw SVG string (or a React `<svg>` element) into an `IconDefinition`, so you can drop a copied icon straight in:

```tsx
import { iconFromSvg } from '@json-edit-react/utils'

const myTheme = {
icons: { add: iconFromSvg('<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13 7h-2v4H7…"/></svg>') },
styles: {},
}
```

A **string** passed to `iconFromSvg` is interned, so it's stable to write inline. A React **element**, a pre-built **`IconDefinition`**, or a raw React node placed directly in `theme.icons` is a fresh object each render — define it outside the component or wrap it in `useMemo` (the same rule as any inline `theme` value) so it doesn't churn the editor's re-rendering.

To replace an icon for a single editor instance, layer it onto the `theme` array — the same mechanism as style overrides:

```tsx
theme={[githubDarkTheme, { icons: { add: iconFromSvg('<svg…>') } }]}
```

## Localisation

Expand Down Expand Up @@ -1449,7 +1480,7 @@ A few helper functions, components and types that might be useful in your own im
- `StringDisplay`: main component used to display a string value. Useful as a building block in custom components — handles truncation, "show more / show less" expansion, and the standard double-click-to-edit behaviour.
- `StringEdit`: component used when editing a string value, can be useful for custom components
- `AutogrowTextArea`: the auto-resizing textarea primitive used by `StringEdit` and the built-in string editor
- `IconAdd`, `IconEdit`, `IconDelete`, `IconCopy`, `IconOk`, `IconCancel`, `IconChevron`: all the built-in [icon](#icons) components
- `IconSvg`: renders an `IconDefinition`'s parts (`scale`, `viewBox`, inner markup as `children`, plus its `svgProps`) as an `<svg>` — the same renderer the editor uses for theme [icons](#icons); handy for previewing a glyph outside the editor
- `matchNode`, `matchNodeKey`: helpers for defining custom [Search](#searchfiltering) functions
- `extract`: function to extract a deeply nested object value from a string path. Originally published at [object-property-extractor](https://github.com/CarlosNZ/object-property-extractor)
- `assign`: function to set a deep object value from a string path. Originally published at [object-property-assigner](https://github.com/CarlosNZ/object-property-assigner)
Expand All @@ -1470,7 +1501,8 @@ A few helper functions, components and types that might be useful in your own im
- [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text), [`CustomTextFunction`](#custom-text), [`JsonEditorHandle`](#imperative-handle-editorref), [`JsonViewerHandle`](#imperative-handle-editorref), [`StartEditOptions`](#imperative-handle-editorref), [`StartEditResult`](#imperative-handle-editorref): input/output types of the respective props
- `TranslateFunction`: function that takes a [localisation](#localisation) key and returns a translated string
- `LocalisedString`: keys for the [`translations`](#localisation) object
- `IconReplacements`: input type for the `icons` prop
- `IconDefinition`: a themeable icon glyph (`content` plus optional `viewBox`/`svgProps`/`scale`) — see [Icons](#icons)
- `ThemeIcons`: the `theme.icons` map (icon name → `IconDefinition`)
- `CollectionNodeProps`: all props passed internally to "collection" nodes (i.e. objects/arrays)
- `ValueNodeProps`: all props passed internally to "value" nodes (i.e. *not* objects/arrays)
- `CustomComponentProps`: all props passed internally to [Custom nodes](#custom-nodes); basically the same as `CollectionNodeProps` with an extra `componentProps` field for passing props unique to your component`
Expand Down
33 changes: 33 additions & 0 deletions demo/src/demoData/dataDefinitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
import { ReactDatePicker } from '@json-edit-react/components/widgets'
import {
CustomNodeDefinition,
IconDefinition,
IconSvg,
JsonData,
FilterFunction,
CustomTextDefinitions,
Expand Down Expand Up @@ -634,6 +636,37 @@ export const demoDataDefinitions: Record<string, DemoData> = {
searchFilter: 'key',
searchPlaceholder: 'Search Theme keys',
data: {},
// Render each theme icon glyph (an `IconDefinition`) as the actual icon via
// core's `IconSvg`, rather than the raw React-element internals. Display-only.
customNodeDefinitions: [
{
condition: ({ value }) =>
!!value &&
typeof value === 'object' &&
React.isValidElement((value as { content?: unknown }).content),
renderCollectionAsValue: true,
showEditTools: false,
component: ({ value, nodeData, getStyles }) => {
const { content, viewBox, svgProps, scale } = value as IconDefinition
// Derive the paint key (icon + PascalCase) the same way core does, so
// the preview adopts the theme's icon colour via currentColor.
const key = String(nodeData.key)
const paintKey = `icon${key[0].toUpperCase()}${key.slice(1)}` as Parameters<
typeof getStyles
>[0]
return (
<IconSvg
viewBox={viewBox}
{...svgProps}
scale={scale}
style={{ ...getStyles(paintKey, nodeData), verticalAlign: 'middle' }}
>
{content}
</IconSvg>
)
},
},
],
customTextEditorAvailable: true,
},
customNodes: {
Expand Down
52 changes: 52 additions & 0 deletions migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ If you only have a few minutes, these are the changes most likely to affect exis
| `externalTriggers` prop replaced by an [imperative handle](https://react.dev/reference/react/useImperativeHandle) | Use a `useRef<JsonEditorHandle>` and call `editorRef.current.collapse/startEdit/confirm/cancel` — see [the `editorRef` handle](#12-externaltriggers-prop-replaced-by-the-editorref-imperative-handle) |
| Fine-grained re-rendering: object / array / function props must be referentially stable to benefit | Keep `customNodeDefinitions`, filter functions, `translations`, etc. stable (module scope or `useMemo`); callbacks are stabilised for you — see [stable props](#13-keep-object-and-function-props-referentially-stable) |
| Misc public-export changes — new `AutogrowTextArea`; `toPathString` is now `/`-encoded; `ThemeStyles` is `Partial` | Mostly additive; act only if you parse `toPathString` output or typed against a total `ThemeStyles` — see [Misc changes to public exports](#14-misc-changes-to-public-exports) |
| `icons` prop removed; icon glyphs move into the theme | Move the config to `theme.icons` as `IconDefinition`s (or wrap with `iconFromSvg`); rename `chevron` → `collection` — see [`icons` prop removed](#15-icons-prop-removed-themes-own-their-glyphs) |

---

Expand Down Expand Up @@ -714,6 +715,57 @@ The closing bracket of an expanded object/array now aligns with the key (the sta

---

## 15. `icons` prop removed (themes own their glyphs)

The standalone `icons` prop is gone. Icon **glyphs** now live on the theme, under `theme.icons`, alongside their colours (`theme.styles.iconAdd`…). Each glyph is an `IconDefinition` (`{ content, viewBox?, svgProps?, scale? }`) rather than a bare `JSX.Element`, and the chevron key is renamed `chevron` → `collection`. See [Icons](README.md#icons) for the full shape, the `currentColor` theming rule, and `scale`.

**Why:** icons are part of a theme's look, so a theme can now ship its own glyphs (not just their colours), and a per-instance override composes through the same `theme` layering as style overrides — one mechanism instead of two.

### Migration

Move the config from the `icons` prop into a theme layer, and rename `chevron` → `collection`. `content` is the **inner** SVG markup — core renders the wrapping `<svg>` — so drop the outer `<svg>` and keep its `viewBox` as a sibling field:

```diff
- <JsonEditor
- data={data}
- setData={setData}
- icons={{
- add: <svg viewBox="0 0 24 24"><path d="M13 7…" /></svg>,
- chevron: <svg viewBox="0 0 512 512"><path d="M233 406…" /></svg>,
- }}
- />
+ <JsonEditor
+ data={data}
+ setData={setData}
+ theme={{
+ icons: {
+ add: { content: <path d="M13 7…" />, viewBox: '0 0 24 24' },
+ collection: { content: <path d="M233 406…" />, viewBox: '0 0 512 512' },
+ },
+ styles: {},
+ }}
+ />
```

Or skip the unwrapping — hand the original `<svg>` markup (a string, or a React `<svg>` element) to the `iconFromSvg` helper in [`@json-edit-react/utils`](README.md#icons), which strips the outer `<svg>` and lifts its `viewBox` / attributes for you:

```tsx
import { iconFromSvg } from '@json-edit-react/utils'

theme={{ icons: { add: iconFromSvg('<svg viewBox="0 0 24 24">…</svg>') }, styles: {} }}
```

Keep the theme reference stable (module scope or `useMemo`), as with any `theme` value.

### Removed icon exports

Gone from `json-edit-react`:

- The built-in icon **components** `IconAdd`, `IconEdit`, `IconDelete`, `IconCopy`, `IconOk`, `IconCancel`, `IconChevron` and their props type `IconProps`. The built-in glyphs now live on `defaultTheme.icons` (e.g. `defaultTheme.icons.add`) if you need to reference one.
- The `IconReplacements` type (the old `icons`-prop shape) — replaced by `IconDefinition` and `ThemeIcons`.

---

## Need help?

If you hit something this guide doesn't cover, please [open an issue](https://github.com/CarlosNZ/json-edit-react/issues) — happy to help triage and add to this doc.
Loading
Loading