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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@ Upload custom SVG icons and insert them inline in the block editor.
**Features:**
- Custom SVG icon library with secure sanitization
- Inline insertion via RichText toolbar (inherits surrounding font size)
- Standalone SVG Icon block with alignment, size, and color controls
- Standalone metadata-driven SVG Icon block with alignment, native text color, spacing, anchor, and custom class support
- Pixel, `em`, and `rem` sizing with bounded values
- Monochrome or original-color rendering
- Decorative or informative accessibility modes with custom labels
- Searchable, paginated, keyboard-accessible icon picker with recent icons first
- Zero frontend footprint when no icons are used

**Navigate to:** `?page=functionalities&module=svg-icons`
Expand Down
2 changes: 2 additions & 0 deletions assets/blocks/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
// Silence is golden.
63 changes: 63 additions & 0 deletions assets/blocks/svg-icon/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "functionalities/svg-icon-block",
"version": "1.4.8",
"title": "SVG Icon",
"category": "design",
"icon": "flag",
"description": "Insert an SVG icon from your Functionalities library.",
"textdomain": "functionalities",
"attributes": {
"iconSlug": {
"type": "string",
"default": ""
},
"size": {
"type": "number",
"default": 48
},
"sizeUnit": {
"type": "string",
"default": "px"
},
"align": {
"type": "string",
"default": "none"
},
"color": {
"type": "string",
"default": ""
},
"colorMode": {
"type": "string",
"default": "monochrome"
},
"decorative": {
"type": "boolean",
"default": true
},
"label": {
"type": "string",
"default": ""
}
},
"supports": {
"html": false,
"anchor": true,
"customClassName": true,
"align": [
"left",
"center",
"right"
],
"color": {
"text": true,
"background": false,
"gradients": false
},
"spacing": {
"margin": true
}
}
}
2 changes: 2 additions & 0 deletions assets/blocks/svg-icon/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
// Silence is golden.
165 changes: 68 additions & 97 deletions assets/css/svg-icons-editor.css
Original file line number Diff line number Diff line change
@@ -1,50 +1,36 @@
/**
* SVG Icons Editor Styles
* SVG Icons editor styles.
*
* @package Functionalities
* @since 0.11.0
*/

/* Icon wrapper - inline-flex for proper sizing */
.func-icon-wrapper {
display: inline-flex !important;
align-items: center;
line-height: 0;
}

/* Inline SVG icon styling */
.func-icon-wrapper .func-icon,
svg.func-icon,
.func-icon {
display: inline-block !important;
width: 1em !important;
height: 1em !important;
vertical-align: -0.125em;
fill: currentColor;
}

/* Ensure path elements inherit fill */
.func-icon path,
.func-icon-wrapper svg path {
fill: inherit;
}

/* Hide the placeholder bullet character inside func-icon.
We use a bullet to force Gutenberg to keep the closing tag.
The ::before pseudo-element shows the icon, text is hidden. */
i.func-icon,
span.func-icon {
font-size: 0 !important;
line-height: 0 !important;
}

/* The icon is shown via ::before pseudo-element, which uses normal font-size */
i.func-icon::before,
span.func-icon::before {
font-size: 1rem;
}

/* Popover styles */
.func-svg-icon-popover {
z-index: 100001;
}
Expand All @@ -55,94 +41,63 @@ span.func-icon::before {
}

.func-svg-icon-picker {
box-sizing: border-box;
padding: 12px;
min-width: 280px;
max-width: 340px;
background: #fff;
width: min(360px, calc(100vw - 32px));
background: var(--wp-components-color-background, #fff);
color: var(--wp-components-color-foreground, #1e1e1e);
}

/* Search wrapper */
.func-icon-search-wrapper {
.func-icon-search {
margin-bottom: 12px;
}

.func-icon-search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #949494;
border-radius: 4px;
font-size: 14px;
outline: none;
transition: border-color 0.1s ease-in-out;
}

.func-icon-search-input:focus {
border-color: #007cba;
box-shadow: 0 0 0 1px #007cba;
}

/* Icon grid in popover */
.func-icon-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-columns: repeat(6, minmax(36px, 1fr));
gap: 6px;
max-height: 240px;
max-height: 264px;
overflow-y: auto;
padding: 4px;
}

.func-icon-grid::-webkit-scrollbar {
width: 6px;
}

.func-icon-grid::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 3px;
}

.func-icon-grid::-webkit-scrollbar-thumb {
background: #c0c0c0;
border-radius: 3px;
padding: 2px;
}

/* Empty state */
.func-icon-empty {
text-align: center;
color: #666;
padding: 20px;
color: #646970;
padding: 20px 8px;
margin: 0;
grid-column: 1 / -1;
font-size: 13px;
}

/* Icon button in grid */
.func-icon-btn {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
min-width: 40px;
height: 40px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 2px;
background: transparent;
color: currentColor;
cursor: pointer;
transition: all 0.15s ease;
}

.func-icon-btn:hover {
border-color: #007cba;
background: #f0f6fc;
transform: scale(1.05);
border-color: var(--wp-admin-theme-color, #007cba);
background: rgba(0, 124, 186, 0.08);
}

.func-icon-btn:focus {
outline: 2px solid #007cba;
outline-offset: -2px;
box-shadow: none;
.func-icon-btn:focus-visible {
outline: 2px solid var(--wp-admin-theme-color, #007cba);
outline-offset: 1px;
}

.func-icon-btn.is-selected {
border-color: var(--wp-admin-theme-color, #007cba);
box-shadow: inset 0 0 0 1px var(--wp-admin-theme-color, #007cba);
}

/* Icon preview wrapper */
.func-icon-preview {
display: flex;
align-items: center;
Expand All @@ -156,60 +111,76 @@ span.func-icon::before {
height: 22px;
max-width: 100%;
max-height: 100%;
fill: currentColor;
color: #1e1e1e;
}

/* Make SVGs inside icon buttons scale properly */
.func-icon-btn svg[viewBox],
.func-icon-btn svg[viewbox] {
width: 100%;
height: 100%;
max-width: 22px;
max-height: 22px;
.func-icon-loading {
display: flex;
justify-content: center;
padding: 12px;
}

.func-icon-load-more {
display: flex;
margin: 12px auto 0;
}

/* Block styles */
.func-svg-icon-block-wrapper {
margin: 1em 0;
--func-icon-size: 48px;
clear: both;
line-height: 0;
}

.func-svg-icon-block-render {
display: inline-block;
width: var(--func-icon-size);
height: var(--func-icon-size);
line-height: 0;
}

.func-svg-icon-block-render svg {
display: block;
width: 100%;
height: 100%;
fill: currentColor;
}

.func-svg-icon-block-render svg path {
fill: inherit;
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg [fill]:not([fill="none" i]) {
fill: currentColor !important;
}

.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg [stroke]:not([stroke="none" i]) {
stroke: currentColor !important;
}

.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg path:not([fill]),
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg circle:not([fill]),
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg ellipse:not([fill]),
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg rect:not([fill]),
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg polygon:not([fill]),
.func-svg-icon-block-wrapper.is-color-monochrome .func-svg-icon-block-render svg polyline:not([fill]) {
fill: currentColor;
}

.func-svg-icon-block-placeholder {
display: flex !important;
align-items: center;
justify-content: center;
width: 100%;
padding: 40px !important;
background: #f0f0f0 !important;
border: 2px dashed #ccc !important;
color: #666 !important;
border-radius: 4px;
min-height: 112px;
padding: 32px !important;
background: #f6f7f7 !important;
border: 1px dashed #a7aaad !important;
color: #50575e !important;
border-radius: 2px;
cursor: pointer;
}

.func-svg-icon-block-placeholder:hover {
background: #e8e8e8 !important;
border-color: #999 !important;
background: #f0f0f1 !important;
border-color: #646970 !important;
}

.func-icon-btn.is-selected {
border-color: #007cba;
background: #f0f6fc;
box-shadow: 0 0 0 1px #007cba;
}
@media (max-width: 480px) {
.func-icon-grid {
grid-template-columns: repeat(5, minmax(36px, 1fr));
}
}
Loading
Loading