Skip to content

Commit 2ecf7b5

Browse files
authored
Merge pull request #432 from wpengine/toolbar
feat: Initial scaffold of Toolbar
2 parents f96be7a + a5ada57 commit 2ecf7b5

Some content is hidden

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

57 files changed

+15069
-252
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ node_modules
44

55
# Build outputs
66
dist
7+
build
8+
.next
9+
out
710

811
# Testing
912
coverage
@@ -13,6 +16,7 @@ test-results/
1316
# WordPress
1417
.wp-env
1518
.wp-env.override.json
19+
wp-env/
1620
uploads/
1721
debug.log
1822
__MACOSX
@@ -29,4 +33,6 @@ __MACOSX
2933
## Examples
3034
examples/**/package-lock.json
3135
examples/**/__MACOSX
36+
examples/**/mu-plugin.php
37+
examples/**/.wp-env.json
3238
``
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# BEGIN WordPress
2+
<IfModule mod_rewrite.c>
3+
RewriteEngine On
4+
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
5+
RewriteBase /
6+
RewriteRule ^index\.php$ - [L]
7+
RewriteCond %{REQUEST_FILENAME} !-f
8+
RewriteCond %{REQUEST_FILENAME} !-d
9+
RewriteRule . /index.php [L]
10+
</IfModule>
11+
# END WordPress

packages/toolbar/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dist
2+
node_modules
3+
*.log
4+
.DS_Store
5+
*.tsbuildinfo

packages/toolbar/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.1.0] - 2025-01-09
9+
10+
### Added
11+
- Initial release of @wpengine/hwp-toolbar
12+
- Framework-agnostic core toolbar with modern state management
13+
- React adapter with hooks (useToolbar, useToolbarState, useWordPressContext)
14+
- Vanilla JavaScript renderer for non-React applications
15+
- WordPress context management with separate API (setWordPressContext)
16+
- Plugin system via register() method for custom toolbar nodes
17+
- Configurable positioning (top/bottom) and theming (light/dark/auto)
18+
- Minimal base styles for full developer control
19+
- TypeScript support with complete type definitions
20+
- CSS exports for styling integration
21+
22+
[0.1.0]: https://github.com/wpengine/hwptoolkit/releases/tag/toolbar-v0.1.0

packages/toolbar/README.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# @wpengine/hwp-toolbar
2+
3+
> Framework-agnostic toolbar for headless WordPress applications
4+
5+
A lightweight, performant toolbar for headless WordPress. Works with any JavaScript framework or vanilla JS.
6+
7+
## Table of Contents
8+
9+
- [Features](#features)
10+
- [Installation](#installation)
11+
- [Quick Start](#quick-start)
12+
- [Example Projects](#example-projects)
13+
- [Vanilla JavaScript](#vanilla-javascript)
14+
- [React (Recommended)](#react-recommended)
15+
- [Core API](#core-api)
16+
- [Toolbar Class](#toolbar-class)
17+
- [Renderers](#renderers)
18+
- [Styling](#styling)
19+
- [React Hooks API](#react-hooks-api)
20+
- [`useToolbar(toolbar)`](#usetoolbartoolbar)
21+
- [`useToolbarState(toolbar)`](#usetoolbarstatetoolbar)
22+
- [`useToolbarNodes(toolbar)`](#usetoolbarnodestoolbar)
23+
- [Framework Examples](#framework-examples)
24+
- [Vue](#vue)
25+
- [Vanilla JavaScript](#vanilla-javascript-1)
26+
- [TypeScript](#typescript)
27+
- [Development](#development)
28+
- [License](#license)
29+
30+
31+
## Features
32+
33+
- **Framework Agnostic** - Works with React, Vue, Svelte, or vanilla JavaScript
34+
- **Zero Dependencies** - Core library has no dependencies
35+
- **React Hooks** - First-class React support with hooks
36+
- **Headless UI** - Full control over rendering and styling
37+
38+
## Installation
39+
40+
```bash
41+
npm install @wpengine/hwp-toolbar
42+
```
43+
44+
## Quick Start
45+
46+
### Example Projects
47+
48+
Check out the complete example projects in the `examples/` directory:
49+
50+
- **Next.js**: `examples/next/` - Full Next.js application with Apollo GraphQL integration
51+
- **Vanilla JavaScript**: `examples/vanilla/` - Pure JavaScript implementation with demo HTML
52+
53+
Each example includes setup instructions and demonstrates different integration patterns.
54+
55+
### Vanilla JavaScript
56+
57+
```javascript
58+
import { Toolbar, VanillaRenderer } from "@wpengine/hwp-toolbar";
59+
import "@wpengine/hwp-toolbar/styles";
60+
61+
const toolbar = new Toolbar({
62+
onPreviewChange: (enabled) => {
63+
console.log("Preview mode:", enabled);
64+
},
65+
});
66+
67+
toolbar.setWordPressContext({
68+
user: { id: 1, name: "Admin" },
69+
site: { url: "https://example.com", adminUrl: "https://example.com/wp-admin" },
70+
post: { id: 123, title: "Hello World", type: "post", status: "draft", slug: "hello-world" },
71+
});
72+
73+
const renderer = new VanillaRenderer(toolbar, "toolbar");
74+
```
75+
76+
### React (Recommended)
77+
78+
```tsx
79+
import { Toolbar } from "@wpengine/hwp-toolbar";
80+
import { useToolbar } from "@wpengine/hwp-toolbar/react";
81+
82+
const toolbar = new Toolbar({
83+
onPreviewChange: (enabled) => {
84+
console.log("Preview mode:", enabled);
85+
},
86+
});
87+
88+
function MyToolbar() {
89+
const { state, nodes } = useToolbar(toolbar);
90+
91+
return (
92+
<div className='toolbar'>
93+
{nodes.map((node) => (
94+
<button key={node.id} onClick={node.onClick}>
95+
{typeof node.label === "function" ? node.label() : node.label}
96+
</button>
97+
))}
98+
{state.user && <span>User: {state.user.name}</span>}
99+
</div>
100+
);
101+
}
102+
```
103+
104+
## API
105+
106+
### Toolbar
107+
108+
**Constructor**
109+
110+
```javascript
111+
new Toolbar(config?)
112+
```
113+
114+
**Config:**
115+
116+
- `onPreviewChange?: (enabled: boolean) => void` - Preview toggle callback
117+
118+
**Methods:**
119+
120+
```javascript
121+
// Register a node
122+
toolbar.register(id, label, onClick?)
123+
toolbar.register('help', 'Help', () => window.open('/help'))
124+
125+
// Update state
126+
toolbar.setState({ user, post, site, preview })
127+
128+
// Set WordPress context (helper)
129+
toolbar.setWordPressContext({ user, post, site })
130+
131+
// Subscribe to changes
132+
const unsubscribe = toolbar.subscribe((nodes, state) => {
133+
console.log('State changed:', state);
134+
})
135+
136+
// Cleanup
137+
toolbar.destroy()
138+
```
139+
140+
### VanillaRenderer
141+
142+
```javascript
143+
const renderer = new VanillaRenderer(toolbar, "element-id");
144+
// or
145+
const renderer = new VanillaRenderer(toolbar, document.getElementById("toolbar"));
146+
147+
// Cleanup
148+
renderer.destroy();
149+
```
150+
151+
## Default Nodes
152+
153+
The toolbar includes three built-in nodes:
154+
155+
- **Edit Post** - Opens WordPress editor (visible when post + user)
156+
- **WP Admin** - Dashboard link (visible when user exists)
157+
- **Preview** - Toggle preview mode (visible when post or user)
158+
159+
## Styling
160+
161+
Import the base styles:
162+
163+
```javascript
164+
import "@wpengine/hwp-toolbar/styles";
165+
```
166+
167+
### Customization
168+
169+
Override CSS custom properties:
170+
171+
```css
172+
:root {
173+
--hwp-toolbar-bg: #1a1a1a;
174+
--hwp-toolbar-primary: #00a0d2;
175+
}
176+
```
177+
178+
## React Hooks API
179+
180+
### `useToolbar(toolbar)`
181+
182+
Returns both state and nodes in a single hook:
183+
184+
```tsx
185+
import { useToolbar } from "@wpengine/hwp-toolbar/react";
186+
187+
function MyToolbar() {
188+
const { state, nodes } = useToolbar(toolbar);
189+
// Full control over rendering
190+
}
191+
```
192+
193+
### `useToolbarState(toolbar)`
194+
195+
Subscribe to toolbar state only:
196+
197+
```tsx
198+
import { useToolbarState } from "@wpengine/hwp-toolbar/react";
199+
200+
function UserDisplay() {
201+
const state = useToolbarState(toolbar);
202+
return <div>{state.user?.name}</div>;
203+
}
204+
```
205+
206+
### `useToolbarNodes(toolbar)`
207+
208+
Subscribe to visible nodes only:
209+
210+
```tsx
211+
import { useToolbarNodes } from "@wpengine/hwp-toolbar/react";
212+
213+
function ToolbarButtons() {
214+
const nodes = useToolbarNodes(toolbar);
215+
return (
216+
<>
217+
{nodes.map((node) => (
218+
<button key={node.id} onClick={node.onClick}>
219+
{typeof node.label === "function" ? node.label() : node.label}
220+
</button>
221+
))}
222+
</>
223+
);
224+
}
225+
```
226+
227+
## Framework Examples
228+
229+
### Vue
230+
231+
```vue
232+
<template>
233+
<div ref="toolbarRef" />
234+
</template>
235+
236+
<script setup>
237+
import { onMounted, onUnmounted, ref } from "vue";
238+
import { Toolbar, VanillaRenderer } from "@wpengine/hwp-toolbar";
239+
240+
const toolbarRef = ref(null);
241+
let toolbar, renderer;
242+
243+
onMounted(() => {
244+
toolbar = new Toolbar();
245+
renderer = new VanillaRenderer(toolbar, toolbarRef.value);
246+
});
247+
248+
onUnmounted(() => {
249+
renderer?.destroy();
250+
toolbar?.destroy();
251+
});
252+
</script>
253+
```
254+
255+
### Vanilla JavaScript
256+
257+
See `demo.html` for a complete example.
258+
259+
## Development
260+
261+
```bash
262+
# Build
263+
npm run build
264+
265+
# Watch mode
266+
npm run dev
267+
268+
# Clean
269+
npm run clean
270+
271+
# View demo
272+
open demo.html
273+
```
274+
275+
## License
276+
277+
BSD-2-Clause
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"phpVersion": "8.4",
3+
"plugins": [
4+
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
5+
"../plugins/hwp-cors-local",
6+
"../plugins/hwp-frontend-links",
7+
"../plugins/hwp-wp-env-helpers"
8+
],
9+
"config": {
10+
"WP_DEBUG": true,
11+
"SCRIPT_DEBUG": false,
12+
"GRAPHQL_DEBUG": true,
13+
"WP_DEBUG_LOG": true,
14+
"WP_DEBUG_DISPLAY": false,
15+
"SAVEQUERIES": false,
16+
"WP_HOME": "http://localhost:8888",
17+
"HEADLESS_FRONTEND_URL": "http://localhost:3000"
18+
},
19+
"mappings": {
20+
".htaccess": "./wp-env/setup/.htaccess"
21+
},
22+
"lifecycleScripts": {
23+
"afterStart": "wp-env run cli -- wp theme activate twentytwentyfour && wp-env run cli -- wp rewrite structure '/%postname%/' && wp-env run cli -- wp rewrite flush"
24+
}
25+
}

0 commit comments

Comments
 (0)