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
15 changes: 0 additions & 15 deletions .eslintrc.cjs

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/npm-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x, 24.x]
node-version: [20.x, 22.x, 24.x]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
registry-url: https://registry.npmjs.org/
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ on:
jobs:
test:
timeout-minutes: 60
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser: [chromium, firefox, webkit]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: 20
cache: "npm"
Expand All @@ -32,10 +31,10 @@ jobs:
run: npx playwright test --project=${{ matrix.browser }}

- name: Upload test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.browser }}
name: playwright-report-${{ matrix.browser }}
path: playwright-report/
retention-days: 30

Expand Down
119 changes: 103 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,123 @@
# json-tree-view-vue3

[![npm version](https://badge.fury.io/js/json-tree-view-vue3.svg)](https://www.npmjs.com/package/json-tree-view-vue3) [![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://github.com/ellerbrock/typescript-badges/) ![npm bundle size](https://img.shields.io/bundlephobia/min/json-tree-view-vue3.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![npm version](https://img.shields.io/npm/v/json-tree-view-vue3.svg)](https://www.npmjs.com/package/json-tree-view-vue3)
[![CI](https://img.shields.io/github/actions/workflow/status/seijikohara/json-tree-view-vue3/npm-ci.yml?branch=main&label=CI)](https://github.com/seijikohara/json-tree-view-vue3/actions/workflows/npm-ci.yml)
[![E2E Tests](https://img.shields.io/github/actions/workflow/status/seijikohara/json-tree-view-vue3/playwright.yml?branch=main&label=E2E)](https://github.com/seijikohara/json-tree-view-vue3/actions/workflows/playwright.yml)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
[![npm bundle size](https://img.shields.io/bundlephobia/min/json-tree-view-vue3.svg)](https://bundlephobia.com/package/json-tree-view-vue3)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A Vue3 component that displays JSON in a collapsible tree.
Inspired by [vue-json-component](https://www.npmjs.com/package/vue-json-component) and [vue-json-tree-view](https://www.npmjs.com/package/vue-json-tree-view) to work with Vue3 and TypeScript.
A Vue 3 component for rendering JSON data as an interactive, collapsible tree structure.

## Example
Inspired by [vue-json-component](https://www.npmjs.com/package/vue-json-component) and [vue-json-tree-view](https://www.npmjs.com/package/vue-json-tree-view), this library provides native Vue 3 and TypeScript support.

## Installation

```bash
npm install json-tree-view-vue3
```

## Usage

```vue
<script setup lang="ts">
import { JsonTreeView } from "json-tree-view-vue3";
import 'json-tree-view-vue3/dist/style.css'
import "json-tree-view-vue3/dist/style.css";

const json = `{"string":"text","number":123,"boolean":true,"null":null,"array":["A","B","C"],"object":{"prop1":"value1","nestedObject":{"prop2":"value2"}}}`
const json = `{"string":"text","number":123,"boolean":true,"null":null,"array":["A","B","C"],"object":{"prop1":"value1","nestedObject":{"prop2":"value2"}}}`;
</script>

<template>
<JsonTreeView :json="json" :maxDepth="3" />
</template>
```

<img width="296" alt="image" src="https://github.com/seijikohara/json-tree-view-vue3/assets/9543980/0d4d74bc-367d-4fd1-a47a-edfb857a6478">
### Example Output

<img width="296" alt="JSON tree view example" src="https://github.com/seijikohara/json-tree-view-vue3/assets/9543980/0d4d74bc-367d-4fd1-a47a-edfb857a6478">

## API Reference

### Props

| Property | Type | Required | Default | Description |
|---------------|----------|----------|-----------|------------------------------------------------------------------|
| `json` | `string` | Yes | - | A valid JSON string to be rendered as a tree structure |
| `rootKey` | `string` | No | `"/"` | The label displayed for the root node of the tree |
| `maxDepth` | `number` | No | `1` | The initial depth level to which the tree will be expanded |
| `colorScheme` | `string` | No | `"light"` | Visual theme of the component. Accepts `"light"` or `"dark"` |

### Events

#### `selected`

Emitted when a user selects a value in the tree.

**Payload Type:**
```typescript
{
key: string; // The key of the selected node
value: PrimitiveTypes; // The value of the selected node (string, number, boolean, or null)
path: string; // The full path to the selected node
}
```

## Styling and Customization

The component uses CSS custom properties (variables) for theming, allowing you to customize colors and dimensions to match your application's design system.

### Available CSS Variables

```css
/* Color palette */
--jtv-key-color: oklch(0.55 0.15 240); /* Object/Array key color */
--jtv-valueKey-color: oklch(0.25 0.05 210); /* Primitive value key color */
--jtv-string-color: oklch(0.6 0.12 230); /* String value color */
--jtv-number-color: oklch(0.65 0.1 180); /* Number value color */
--jtv-boolean-color: oklch(0.55 0.15 40); /* Boolean value color */
--jtv-null-color: oklch(0.55 0.12 280); /* Null value color */

/* UI colors */
--jtv-arrow-color: oklch(0.3 0 0); /* Expand/collapse arrow color */
--jtv-hover-color: oklch(0 0 0 / 0.1); /* Hover background color */

/* Dimensions */
--jtv-arrow-size: 6px; /* Size of the expand/collapse arrow */
--jtv-spacing-unit: 4px; /* Base spacing unit */
```

### Custom Styling Example

You can override these variables to customize the appearance:

```vue
<style>
.json-tree-view {
/* Custom color scheme */
--jtv-key-color: #2563eb;
--jtv-valueKey-color: #1e293b;
--jtv-string-color: #059669;
--jtv-number-color: #dc2626;
--jtv-boolean-color: #7c3aed;
--jtv-null-color: #64748b;

/* Custom dimensions */
--jtv-arrow-size: 8px;
--jtv-spacing-unit: 6px;
}
</style>

<template>
<div class="json-tree-view">
<JsonTreeView :json="json" />
</div>
</template>
```

## Props
### Dark Mode

| Props | Required | Param Type | Default value | Description |
|-------------|----------|------------|---------------|-------------------------------------------------------|
| json | true | string | | JSON string to display the tree |
| rootKey | false | string | "/" | Top root-level name |
| maxDepth | false | number | 1 | The depth of the tree that will be open when rendered |
| colorScheme | false | string | "light" | "light" or "dark" can be used. |
The component includes built-in dark mode support through the `colorScheme` prop. Alternatively, you can customize the dark theme by overriding the CSS variables within your own dark mode class.

## Events
## License

- **selected**(event: `{key: string, value: PrimitiveTypes, path: string}`]
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
25 changes: 9 additions & 16 deletions e2e/json-tree-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,18 @@ test.describe('JsonTreeView', () => {
test('should show/hide children on toggle', async ({ page }) => {
const lightTheme = page.locator('.theme-light')
const toggleButton = lightTheme.locator('.data-key').first()
const valueKeys = lightTheme.locator('.value-key')
const firstChild = toggleButton.locator('..').locator('.json-view-item').first()

// Initial state - children visible
const initialCount = await valueKeys.count()
expect.soft(initialCount).toBeGreaterThan(0)
await expect.soft(firstChild).toBeVisible()

// Click to collapse
await toggleButton.click()
const collapsedCount = await valueKeys.count()
expect.soft(collapsedCount).toBeLessThan(initialCount)
await expect.soft(firstChild).toBeHidden()

// Click to expand
await toggleButton.click()
const expandedCount = await valueKeys.count()
expect.soft(expandedCount).toBe(initialCount)
await expect.soft(firstChild).toBeVisible()
})
})

Expand All @@ -131,8 +128,8 @@ test.describe('JsonTreeView', () => {
const stringValue = lightTheme.getByText('"text"')

await expect.soft(stringValue).toBeVisible()
// String values should have specific color
await expect.soft(stringValue).toHaveCSS('color', /rgb\(\d+, \d+, \d+\)/)
// String values should have specific color (rgb or oklch format)
await expect.soft(stringValue).toHaveCSS('color', /(rgb\(\d+, \d+, \d+\)|oklch\([^)]+\))/)
})

test('should render number values', async ({ page }) => {
Expand Down Expand Up @@ -193,7 +190,7 @@ test.describe('JsonTreeView', () => {
const lightTheme = page.locator('.theme-light')
const items = lightTheme.locator('.json-view-item:not(.root-item)')

if (await items.count() > 0) {
if ((await items.count()) > 0) {
await expect(items.first()).toHaveCSS('margin-left', '15px')
}
})
Expand All @@ -220,12 +217,8 @@ test.describe('JsonTreeView', () => {
await expect.soft(lightTheme).toBeVisible()
await expect.soft(darkTheme).toBeVisible()

const lightBg = await lightTheme.evaluate((el) =>
window.getComputedStyle(el).backgroundColor
)
const darkBg = await darkTheme.evaluate((el) =>
window.getComputedStyle(el).backgroundColor
)
const lightBg = await lightTheme.evaluate((el) => window.getComputedStyle(el).backgroundColor)
const darkBg = await darkTheme.evaluate((el) => window.getComputedStyle(el).backgroundColor)

expect.soft(lightBg).not.toBe(darkBg)
})
Expand Down
29 changes: 29 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pluginVue from 'eslint-plugin-vue'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginPrettierRecommended from 'eslint-plugin-prettier/recommended'

export default defineConfigWithVueTs(
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
},

pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,

pluginPrettierRecommended,

{
name: 'app/custom-rules',
rules: {
'vue/multi-word-component-names': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
]
}
}
)
Loading