Skip to content

Commit 67252d1

Browse files
Copilotideag
andauthored
Add icon support: React component names and SVG URLs
Agent-Logs-Url: https://github.com/tinypluginlabs/demo/sessions/9e1fd017-a1c4-4a7b-a5e6-991025845f5e Co-authored-by: ideag <3252474+ideag@users.noreply.github.com>
1 parent 2399c3d commit 67252d1

4 files changed

Lines changed: 131 additions & 12 deletions

File tree

BLUEPRINT_JSON_STRUCTURE.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This document provides the structure definition for `/blueprints/blueprints.json
1212
"id": "string",
1313
"title": "string",
1414
"path": "string",
15+
"icon": "string",
1516
"disabled": false
1617
}
1718
]
@@ -26,8 +27,27 @@ This document provides the structure definition for `/blueprints/blueprints.json
2627
| `buttons[].id` | String | Yes | Unique identifier for the button (used as React key) |
2728
| `buttons[].title` | String | Yes | Display text shown under the button icon |
2829
| `buttons[].path` | String | Yes | Navigation path (e.g., "/tinyrelated") |
30+
| `buttons[].icon` | String | No | Icon to display (React component name or SVG URL, default: "WordPressIcon") |
2931
| `buttons[].disabled` | Boolean | No | Whether the button is disabled (default: false) |
3032

33+
## Icon Configuration
34+
35+
The `icon` field supports two formats:
36+
37+
### 1. React Component Name
38+
Specify the name of an icon component exported from `@wp-playground/components`:
39+
- `"WordPressIcon"` - WordPress logo (default)
40+
- `"ClockIcon"` - Clock icon
41+
- `"playgroundLogo"` - Playground logo
42+
- `"temporaryStorage"` - Temporary storage icon
43+
44+
### 2. SVG URL
45+
Provide a direct URL to an SVG file:
46+
- `"https://example.com/custom-icon.svg"`
47+
- `"https://cdn.example.com/icons/my-icon.svg"`
48+
49+
If the `icon` field is omitted or an invalid component name is provided, the button will default to using `WordPressIcon`.
50+
3151
## Example Configuration
3252

3353
```json
@@ -37,12 +57,14 @@ This document provides the structure definition for `/blueprints/blueprints.json
3757
"id": "tinyrelated",
3858
"title": "tinyRelated",
3959
"path": "/tinyrelated",
60+
"icon": "WordPressIcon",
4061
"disabled": false
4162
},
4263
{
4364
"id": "tinyrating",
4465
"title": "tinyRating",
4566
"path": "/tinyrating",
67+
"icon": "https://example.com/rating-icon.svg",
4668
"disabled": false
4769
},
4870
{
@@ -129,11 +151,19 @@ When a button is clicked:
129151
2. The path is resolved through the blueprint preset system
130152
3. The corresponding blueprint ZIP file is loaded (e.g., `/blueprints/tinyrelated.zip`)
131153

154+
## Icon Resolution
155+
156+
Icons are resolved in the following order:
157+
1. If `icon` is omitted, use `WordPressIcon` (default)
158+
2. If `icon` starts with `http://` or `https://`, load as external SVG image
159+
3. If `icon` matches a component name in `@wp-playground/components`, use that component
160+
4. If none of the above, fall back to `WordPressIcon`
161+
132162
## Limitations
133163

134-
- All buttons currently use the WordPress icon (not configurable via JSON)
135164
- The `disabled` field only affects button interaction, not visibility
136165
- Button order in the JSON determines display order in the overlay
166+
- External SVG icons must be accessible from the client (CORS considerations apply)
137167

138168
## Related Documentation
139169

IMPLEMENTATION_SUMMARY.md

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface BlueprintButton {
1515
id: string; // Unique identifier
1616
title: string; // Display title
1717
path: string; // Navigation path
18+
icon?: string; // Optional: React component name or SVG URL
1819
disabled?: boolean; // Optional: button disabled state
1920
}
2021

@@ -30,23 +31,45 @@ Updated the `SavedPlaygroundsOverlay` component to:
3031
- Use the `useFetch` hook to load `/blueprints/blueprints.json`
3132
- Fall back to hardcoded defaults if the JSON file cannot be fetched
3233
- Transform the JSON data into button configurations with onClick handlers
34+
- Resolve icons dynamically from React component names or SVG URLs
3335

3436
Key changes:
3537
```typescript
38+
// Helper function to resolve icon from string (component name or URL)
39+
function resolveIcon(iconSpec?: string): React.ReactNode {
40+
if (!iconSpec) {
41+
return <WordPressIcon />;
42+
}
43+
44+
// Check if it's a URL (SVG from external source)
45+
if (iconSpec.startsWith('http://') || iconSpec.startsWith('https://')) {
46+
return <img src={iconSpec} alt="" style={{ width: '100%', height: '100%' }} />;
47+
}
48+
49+
// Try to resolve as a React component from @wp-playground/components
50+
const IconComponent = (PlaygroundIcons as any)[iconSpec];
51+
if (IconComponent && typeof IconComponent === 'function') {
52+
return <IconComponent />;
53+
}
54+
55+
// Fallback to WordPressIcon if component not found
56+
return <WordPressIcon />;
57+
}
58+
3659
// Fetch from JSON
3760
const { data: blueprintsConfig } = useFetch<BlueprintsConfig>(
3861
'/blueprints/blueprints.json'
3962
);
4063

4164
// Fallback to defaults
42-
const defaultCreationOptions = [...];
65+
const defaultCreationOptions: BlueprintButton[] = [...];
4366
const buttonsConfig = blueprintsConfig?.buttons || defaultCreationOptions;
4467

45-
// Transform to button props
68+
// Transform to button props with dynamic icon resolution
4669
const creationOptions = buttonsConfig.map((button) => ({
4770
id: button.id,
4871
title: button.title,
49-
iconComponent: <WordPressIcon />,
72+
iconComponent: resolveIcon(button.icon),
5073
onClick: () => { window.location.href = button.path; },
5174
disabled: button.disabled ?? false,
5275
}));
@@ -96,7 +119,12 @@ The CI process should deploy a `blueprints.json` file to `/blueprints/blueprints
96119
### Normal Operation
97120
1. When the overlay loads, it fetches `/blueprints/blueprints.json`
98121
2. If successful, buttons are created from the JSON data
99-
3. Each button navigates to the specified `path` when clicked
122+
3. Icons are resolved dynamically:
123+
- If `icon` is omitted, uses `WordPressIcon` (default)
124+
- If `icon` starts with `http://` or `https://`, loads as external SVG image
125+
- If `icon` matches a component name in `@wp-playground/components`, uses that component
126+
- If none of the above, falls back to `WordPressIcon`
127+
4. Each button navigates to the specified `path` when clicked
100128

101129
### Fallback Mechanism
102130
If the JSON file cannot be fetched (404, network error, etc.):
@@ -108,6 +136,11 @@ If the JSON file cannot be fetched (404, network error, etc.):
108136
- Buttons appear immediately using defaults or fetched data
109137
- This provides a seamless user experience
110138

139+
### Icon Support
140+
Buttons can display custom icons in two ways:
141+
1. **React Component Names**: Use exported icon components from `@wp-playground/components` (e.g., "WordPressIcon", "ClockIcon", "playgroundLogo")
142+
2. **SVG URLs**: Provide a direct URL to an SVG file (e.g., "https://example.com/icon.svg")
143+
111144
## Testing
112145

113146
### Type Checking
@@ -138,12 +171,28 @@ npm run dev
138171

139172
4. **Validation**: Consider adding JSON schema validation in your CI process to ensure the file structure is correct before deployment
140173

174+
5. **Icon Support**: Icons can be specified as React component names (from `@wp-playground/components`) or as URLs to SVG files. External SVG icons must be accessible from the client (CORS considerations apply).
175+
141176
## Icon Customization
142177

143-
Currently, all buttons use the `WordPressIcon` component. If you need to support different icons in the future, you would need to:
144-
1. Add an `icon` field to the `BlueprintButton` interface
145-
2. Create a mapping of icon names to icon components
146-
3. Update the button rendering logic to use the specified icon
178+
Icons are now fully customizable via the `icon` field in the JSON configuration:
179+
180+
### Available React Component Icons
181+
From `@wp-playground/components`:
182+
- `WordPressIcon` - WordPress logo (default)
183+
- `ClockIcon` - Clock icon
184+
- `playgroundLogo` - Playground logo
185+
- `temporaryStorage` - Temporary storage icon
186+
187+
### Using External SVG Icons
188+
Provide a direct URL to an SVG file:
189+
```json
190+
{
191+
"icon": "https://example.com/custom-icon.svg"
192+
}
193+
```
194+
195+
Note: External SVG icons must be accessible from the client browser. Ensure proper CORS headers are set if the SVG is hosted on a different domain.
147196

148197
## Security Considerations
149198

packages/playground/website/src/components/saved-playgrounds-overlay/index.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import css from './style.module.css';
23
import classNames from 'classnames';
34
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
@@ -24,6 +25,7 @@ import {
2425
setSiteSlugToRename,
2526
} from '../../lib/state/redux/slice-ui';
2627
import { WordPressIcon } from '@wp-playground/components';
28+
import * as PlaygroundIcons from '@wp-playground/components';
2729
import { PlaygroundRoute, redirectTo } from '../../lib/state/url/router';
2830
import {
2931
Overlay,
@@ -32,7 +34,7 @@ import {
3234
OverlaySection,
3335
} from '../overlay';
3436
import { useFetch } from '../../lib/hooks/use-fetch';
35-
import type { BlueprintsConfig } from '../../lib/types/blueprints-config';
37+
import type { BlueprintsConfig, BlueprintButton } from '../../lib/types/blueprints-config';
3638

3739
interface SavedPlaygroundsOverlayProps {
3840
onClose: () => void;
@@ -91,13 +93,40 @@ export function SavedPlaygroundsOverlay({
9193
onClose();
9294
}
9395

96+
// Helper function to resolve icon from string (component name or URL)
97+
function resolveIcon(iconSpec?: string): React.ReactNode {
98+
if (!iconSpec) {
99+
return <WordPressIcon />;
100+
}
101+
102+
// Check if it's a URL (SVG from external source)
103+
if (iconSpec.startsWith('http://') || iconSpec.startsWith('https://')) {
104+
return (
105+
<img
106+
src={iconSpec}
107+
alt=""
108+
style={{ width: '100%', height: '100%' }}
109+
/>
110+
);
111+
}
112+
113+
// Try to resolve as a React component from @wp-playground/components
114+
const IconComponent = (PlaygroundIcons as any)[iconSpec];
115+
if (IconComponent && typeof IconComponent === 'function') {
116+
return <IconComponent />;
117+
}
118+
119+
// Fallback to WordPressIcon if component not found
120+
return <WordPressIcon />;
121+
}
122+
94123
// Fetch blueprints configuration from /blueprints/blueprints.json
95124
const { data: blueprintsConfig } = useFetch<BlueprintsConfig>(
96125
'/blueprints/blueprints.json'
97126
);
98127

99128
// Fallback to hardcoded buttons if JSON fetch fails or is loading
100-
const defaultCreationOptions = [
129+
const defaultCreationOptions: BlueprintButton[] = [
101130
{
102131
id: 'tinyrelated',
103132
title: 'tinyRelated',
@@ -125,7 +154,7 @@ export function SavedPlaygroundsOverlay({
125154
const creationOptions = buttonsConfig.map((button) => ({
126155
id: button.id,
127156
title: button.title,
128-
iconComponent: <WordPressIcon />,
157+
iconComponent: resolveIcon(button.icon),
129158
onClick: () => {
130159
window.location.href = button.path;
131160
},

packages/playground/website/src/lib/types/blueprints-config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
* "id": "tinyrelated",
1313
* "title": "tinyRelated",
1414
* "path": "/tinyrelated",
15+
* "icon": "WordPressIcon",
1516
* "disabled": false
1617
* },
1718
* {
1819
* "id": "tinyrating",
1920
* "title": "tinyRating",
2021
* "path": "/tinyrating",
22+
* "icon": "https://example.com/icon.svg",
2123
* "disabled": false
2224
* }
2325
* ]
@@ -43,6 +45,15 @@ export interface BlueprintButton {
4345
*/
4446
path: string;
4547

48+
/**
49+
* Icon to display on the button
50+
* Can be:
51+
* - A React component name from @wp-playground/components (e.g., "WordPressIcon", "ClockIcon")
52+
* - A URL to an SVG file (e.g., "https://example.com/icon.svg")
53+
* - If omitted, defaults to "WordPressIcon"
54+
*/
55+
icon?: string;
56+
4657
/**
4758
* Whether the button should be disabled
4859
* @default false

0 commit comments

Comments
 (0)