@siratik/wp-component-library 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +43 -5
- package/components/attachment-image/README.md +118 -0
- package/components/attachment-image/index.js +105 -0
- package/components/attributes-panel/README.md +92 -0
- package/components/attributes-panel/index.js +324 -0
- package/components/badge/README.md +61 -0
- package/components/badge/index.js +105 -0
- package/components/block-appender/README.md +158 -0
- package/components/block-appender/index.js +135 -0
- package/components/block-extension/index.js +214 -0
- package/components/business-hours/SpecialHoursControl.js +211 -0
- package/components/business-hours/WeeklyHoursControl.js +419 -0
- package/components/business-hours/index.js +8 -0
- package/components/button/README.md +172 -0
- package/components/button/index.js +84 -0
- package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
- package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
- package/components/captcha-placeholder/HCaptcha.js +99 -0
- package/components/captcha-placeholder/README.md +228 -0
- package/components/captcha-placeholder/index.js +39 -0
- package/components/captcha-placeholder/styles.js +33 -0
- package/components/geocoder/AddressEntryControl.js +390 -0
- package/components/geocoder/GeocoderSearchControl.js +353 -0
- package/components/geocoder/countryOptions.js +66 -0
- package/components/geocoder/geocoder.js +400 -0
- package/components/geocoder/index.js +16 -0
- package/components/icon-picker/Icon.js +53 -0
- package/components/icon-picker/IconPicker.js +70 -0
- package/components/icon-picker/IconPickerModal.js +474 -0
- package/components/icon-picker/IconPickerToolbarButton.js +60 -0
- package/components/icon-picker/InlineIconPicker.js +92 -0
- package/components/icon-picker/api.js +181 -0
- package/components/icon-picker/config-bootstrap.js +108 -0
- package/components/icon-picker/hooks.js +157 -0
- package/components/icon-picker/index.js +39 -0
- package/components/icon-picker/load-script.js +33 -0
- package/components/icon-picker/loaders.js +79 -0
- package/components/icon-picker/store.js +223 -0
- package/components/icon-picker/styles.js +232 -0
- package/components/icon-picker/utils.js +138 -0
- package/components/layout/README.md +268 -0
- package/components/link-tools-panel/README.md +49 -0
- package/components/link-tools-panel/index.js +198 -0
- package/components/link-tools-panel/styles.js +90 -0
- package/components/media/README.md +288 -0
- package/components/media/SettingsMediaUpload.js +187 -0
- package/components/media/editor-media-upload.js +65 -0
- package/components/media/image-controls.js +196 -0
- package/components/media/index.js +13 -0
- package/components/media/inspector-media-upload.js +290 -0
- package/components/media/toolbar-media-upload.js +53 -0
- package/components/media/utils/aspect-ratios.js +118 -0
- package/components/meta/MetaPanel.js +75 -0
- package/components/meta/README.md +46 -0
- package/components/meta/index.js +6 -0
- package/components/meta/meta-gated-editor.js +282 -0
- package/components/query/README.md +340 -0
- package/components/query/cardTemplatePartPanel.js +69 -0
- package/components/query/cardTemplatePartSelect.js +165 -0
- package/components/query/cardTemplatePartUtils.js +119 -0
- package/components/query/columnCountControl.js +63 -0
- package/components/query/displayTypeSelect.js +52 -0
- package/components/query/examples.js +166 -0
- package/components/query/hideCurrentPostControl.js +47 -0
- package/components/query/index.js +20 -0
- package/components/query/manualPostSelector.js +125 -0
- package/components/query/manualTermSelector.js +141 -0
- package/components/query/orderBySelect.js +95 -0
- package/components/query/postTypeSelect.js +67 -0
- package/components/query/postsPerPageControl.js +51 -0
- package/components/query/selectionModeControl.js +61 -0
- package/components/query/taxonomySelect.js +139 -0
- package/components/query/utils.js +75 -0
- package/components/repeater/README.md +159 -0
- package/components/repeater/index.js +829 -0
- package/components/segmented-bar/README.md +30 -0
- package/components/segmented-bar/index.js +68 -0
- package/components/sortable-select/README.md +153 -0
- package/components/sortable-select/custom-sortable-token.js +127 -0
- package/components/sortable-select/index.js +693 -0
- package/components/style-picker/README.md +36 -0
- package/components/style-picker/index.js +92 -0
- package/components/variable-field/README.md +299 -0
- package/components/variable-field/index.js +592 -0
- package/components/variable-field/value-to-chip-html.js +38 -0
- package/components/variable-inserter/README.md +68 -0
- package/components/variable-inserter/index.js +168 -0
- package/constants/statusColors.js +1 -1
- package/editor.js +16 -0
- package/index.js +127 -0
- package/package.json +21 -13
- package/utils/kitaris-localize.js +128 -0
- package/utils/slug-aware-color-setter.js +27 -0
- package/build/index.asset.php +0 -20
- package/build/index.js +0 -1242
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import {
|
|
6
|
+
__experimentalToolsPanel as ToolsPanel,
|
|
7
|
+
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
8
|
+
ComboboxControl,
|
|
9
|
+
Button
|
|
10
|
+
} from '@wordpress/components';
|
|
11
|
+
import { useState, useEffect, useMemo } from '@wordpress/element';
|
|
12
|
+
import { useSelect } from '@wordpress/data';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Attributes whose value is loaded or followed as a URL.
|
|
16
|
+
*/
|
|
17
|
+
const URL_ATTRIBUTES = [ 'href', 'src', 'xlink:href', 'action', 'formaction', 'poster', 'srcset' ];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Event handler attributes (onclick, onerror, ...) run script, so they are
|
|
21
|
+
* never offered or accepted.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} name Attribute name.
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function isUnsafeAttributeName( name ) {
|
|
27
|
+
return typeof name === 'string' && /^\s*on/i.test( name );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* javascript:, vbscript: and data: URLs can run script, so they are rejected
|
|
32
|
+
* for URL attributes. Whitespace and control characters are ignored when
|
|
33
|
+
* reading the scheme, as browsers do ("java\tscript:").
|
|
34
|
+
*
|
|
35
|
+
* @param {string} name Attribute name.
|
|
36
|
+
* @param {string} value Attribute value.
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function isUnsafeAttributeValue( name, value ) {
|
|
40
|
+
if ( typeof name !== 'string' || typeof value !== 'string' ) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if ( ! URL_ATTRIBUTES.includes( name.trim().toLowerCase() ) ) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const compact = value.replace( /[\u0000-\u0020]/g, '' ).toLowerCase();
|
|
47
|
+
return /(^|,)(javascript|vbscript|data):/.test( compact );
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* AttributesPanel component that uses ToolsPanel and ComboboxControl
|
|
52
|
+
* to manage block attributes with meta field selection.
|
|
53
|
+
*
|
|
54
|
+
* The panel refuses event handler names (on*) and script/data URLs in URL
|
|
55
|
+
* attributes, but that is only a UI guard: whoever outputs the attributes
|
|
56
|
+
* must still allowlist names and escape values (esc_attr / esc_url) on render.
|
|
57
|
+
*
|
|
58
|
+
* @since 1.0.0
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} props Component properties.
|
|
61
|
+
* @param {Array} props.attributes Array of attribute objects with id, label, and value.
|
|
62
|
+
* @param {Function} props.onChange Callback when attributes change.
|
|
63
|
+
* @param {string} props.panelId Unique ID for the tools panel.
|
|
64
|
+
* @returns {WPElement} AttributesPanel element to render.
|
|
65
|
+
*/
|
|
66
|
+
function AttributesPanel({
|
|
67
|
+
attributes = [],
|
|
68
|
+
onChange,
|
|
69
|
+
panelId = 'attributes-panel',
|
|
70
|
+
}) {
|
|
71
|
+
const [localAttributes, setLocalAttributes] = useState(attributes);
|
|
72
|
+
|
|
73
|
+
// Get available meta fields from the current post type
|
|
74
|
+
const metaFields = useSelect((select) => {
|
|
75
|
+
const { getEditedPostAttribute } = select('core/editor');
|
|
76
|
+
|
|
77
|
+
// Try to get meta from editor first
|
|
78
|
+
const meta = getEditedPostAttribute?.('meta') || {};
|
|
79
|
+
|
|
80
|
+
// Combine meta keys from various sources
|
|
81
|
+
const metaKeys = new Set();
|
|
82
|
+
|
|
83
|
+
// Add keys from current post meta
|
|
84
|
+
Object.keys(meta).forEach(key => metaKeys.add(key));
|
|
85
|
+
|
|
86
|
+
// Add common WordPress meta fields
|
|
87
|
+
const commonMetaFields = [
|
|
88
|
+
'_thumbnail_id',
|
|
89
|
+
'_wp_page_template',
|
|
90
|
+
'_wp_attachment_metadata',
|
|
91
|
+
'_wp_attached_file',
|
|
92
|
+
'_edit_lock',
|
|
93
|
+
'_edit_last',
|
|
94
|
+
];
|
|
95
|
+
commonMetaFields.forEach(key => metaKeys.add(key));
|
|
96
|
+
|
|
97
|
+
// Convert to array of options for ComboboxControl
|
|
98
|
+
return Array.from(metaKeys).map(key => ({
|
|
99
|
+
value: key,
|
|
100
|
+
label: key.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase()),
|
|
101
|
+
}));
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
// Create options for ComboboxControl including meta fields and custom values
|
|
105
|
+
const comboboxOptions = useMemo(() => {
|
|
106
|
+
const options = [
|
|
107
|
+
// Default HTML attributes
|
|
108
|
+
{ value: 'id', label: __('ID', 'wp-component-library') },
|
|
109
|
+
{ value: 'class', label: __('Class', 'wp-component-library') },
|
|
110
|
+
{ value: 'style', label: __('Style', 'wp-component-library') },
|
|
111
|
+
{ value: 'title', label: __('Title', 'wp-component-library') },
|
|
112
|
+
{ value: 'alt', label: __('Alt Text', 'wp-component-library') },
|
|
113
|
+
{ value: 'rel', label: __('Rel', 'wp-component-library') },
|
|
114
|
+
{ value: 'target', label: __('Target', 'wp-component-library') },
|
|
115
|
+
{ value: 'href', label: __('Href', 'wp-component-library') },
|
|
116
|
+
{ value: 'src', label: __('Src', 'wp-component-library') },
|
|
117
|
+
// Data attributes
|
|
118
|
+
{ value: 'data-id', label: __('Data ID', 'wp-component-library') },
|
|
119
|
+
{ value: 'data-type', label: __('Data Type', 'wp-component-library') },
|
|
120
|
+
{ value: 'data-value', label: __('Data Value', 'wp-component-library') },
|
|
121
|
+
// ARIA attributes
|
|
122
|
+
{ value: 'aria-label', label: __('ARIA Label', 'wp-component-library') },
|
|
123
|
+
{ value: 'aria-describedby', label: __('ARIA Described By', 'wp-component-library') },
|
|
124
|
+
{ value: 'aria-hidden', label: __('ARIA Hidden', 'wp-component-library') },
|
|
125
|
+
// Meta fields
|
|
126
|
+
...metaFields.map(field => ({
|
|
127
|
+
...field,
|
|
128
|
+
label: `Meta: ${field.label}`,
|
|
129
|
+
})),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// Add any existing custom attributes that aren't in the default list
|
|
133
|
+
localAttributes.forEach(attr => {
|
|
134
|
+
if (!options.find(opt => opt.value === attr.id)) {
|
|
135
|
+
options.push({
|
|
136
|
+
value: attr.id,
|
|
137
|
+
label: attr.label || attr.id,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return options;
|
|
143
|
+
}, [metaFields, localAttributes]);
|
|
144
|
+
|
|
145
|
+
// Update local state when props change
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
setLocalAttributes(attributes);
|
|
148
|
+
}, [attributes]);
|
|
149
|
+
|
|
150
|
+
const handleAttributeChange = (id, value) => {
|
|
151
|
+
if ( isUnsafeAttributeValue( id, value ) ) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const updatedAttributes = localAttributes.map((attr) =>
|
|
155
|
+
attr.id === id ? { ...attr, value } : attr
|
|
156
|
+
);
|
|
157
|
+
setLocalAttributes(updatedAttributes);
|
|
158
|
+
onChange?.(updatedAttributes);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const handleAttributeSelect = (id, selectedValue) => {
|
|
162
|
+
if (!selectedValue) { return; }
|
|
163
|
+
if ( isUnsafeAttributeName( selectedValue ) ) { return; }
|
|
164
|
+
|
|
165
|
+
// Check if attribute already exists
|
|
166
|
+
const existingAttr = localAttributes.find(attr => attr.id === id);
|
|
167
|
+
if (existingAttr) {
|
|
168
|
+
// Update existing attribute
|
|
169
|
+
handleAttributeChange(id, selectedValue);
|
|
170
|
+
} else {
|
|
171
|
+
// Find the label for this value
|
|
172
|
+
const option = comboboxOptions.find(opt => opt.value === selectedValue);
|
|
173
|
+
const label = option?.label || selectedValue;
|
|
174
|
+
|
|
175
|
+
// Update the attribute
|
|
176
|
+
const updatedAttributes = localAttributes.map(attr =>
|
|
177
|
+
attr.id === id ? { ...attr, id: selectedValue, label } : attr
|
|
178
|
+
);
|
|
179
|
+
setLocalAttributes(updatedAttributes);
|
|
180
|
+
onChange?.(updatedAttributes);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const addNewAttribute = () => {
|
|
185
|
+
const newAttribute = {
|
|
186
|
+
id: `attr-${Date.now()}`,
|
|
187
|
+
label: '',
|
|
188
|
+
value: '',
|
|
189
|
+
isNew: true,
|
|
190
|
+
};
|
|
191
|
+
const updatedAttributes = [...localAttributes, newAttribute];
|
|
192
|
+
setLocalAttributes(updatedAttributes);
|
|
193
|
+
onChange?.(updatedAttributes);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const removeAttribute = (id) => {
|
|
197
|
+
const updatedAttributes = localAttributes.filter(attr => attr.id !== id);
|
|
198
|
+
setLocalAttributes(updatedAttributes);
|
|
199
|
+
onChange?.(updatedAttributes);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const resetAll = () => {
|
|
203
|
+
const resetAttributes = localAttributes.map(attr => ({ ...attr, value: '' }));
|
|
204
|
+
setLocalAttributes(resetAttributes);
|
|
205
|
+
onChange?.(resetAttributes);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const resetAttribute = (id) => {
|
|
209
|
+
handleAttributeChange(id, '');
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<ToolsPanel
|
|
214
|
+
label={__('Attributes', 'wp-component-library')}
|
|
215
|
+
panelId={panelId}
|
|
216
|
+
hasInnerWrapper={true}
|
|
217
|
+
className="attributes-tools-panel"
|
|
218
|
+
resetAll={resetAll}
|
|
219
|
+
dropdownMenuProps={{
|
|
220
|
+
popoverProps: {
|
|
221
|
+
placement: 'left-start',
|
|
222
|
+
offset: 10,
|
|
223
|
+
},
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
{localAttributes.map((attribute) => (
|
|
227
|
+
<ToolsPanelItem
|
|
228
|
+
key={attribute.id}
|
|
229
|
+
hasValue={() => !!attribute.value}
|
|
230
|
+
label={attribute.label || __('Select attribute', 'wp-component-library')}
|
|
231
|
+
onDeselect={() => removeAttribute(attribute.id)}
|
|
232
|
+
resetAllFilter={() => resetAttribute(attribute.id)}
|
|
233
|
+
panelId={panelId}
|
|
234
|
+
>
|
|
235
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'flex-end' }}>
|
|
236
|
+
<div style={{ flex: 1 }}>
|
|
237
|
+
<ComboboxControl
|
|
238
|
+
label={attribute.isNew ? __('Attribute', 'wp-component-library') : attribute.label}
|
|
239
|
+
value={attribute.isNew ? '' : attribute.id}
|
|
240
|
+
onChange={(value) => handleAttributeSelect(attribute.id, value)}
|
|
241
|
+
options={comboboxOptions}
|
|
242
|
+
allowReset={false}
|
|
243
|
+
placeholder={__('Select or type attribute', 'wp-component-library')}
|
|
244
|
+
/>
|
|
245
|
+
{!attribute.isNew && (
|
|
246
|
+
<ComboboxControl
|
|
247
|
+
label={__('Value', 'wp-component-library')}
|
|
248
|
+
value={attribute.value || ''}
|
|
249
|
+
onChange={(value) => handleAttributeChange(attribute.id, value)}
|
|
250
|
+
options={[]}
|
|
251
|
+
allowReset={false}
|
|
252
|
+
placeholder={__('Enter value', 'wp-component-library')}
|
|
253
|
+
help={attribute.id.startsWith('meta:') ? __('This will pull from post meta', 'wp-component-library') : null}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</ToolsPanelItem>
|
|
259
|
+
))}
|
|
260
|
+
|
|
261
|
+
<div style={{ padding: '16px 16px 8px' }}>
|
|
262
|
+
<Button
|
|
263
|
+
variant="secondary"
|
|
264
|
+
size="small"
|
|
265
|
+
onClick={addNewAttribute}
|
|
266
|
+
style={{ width: '100%' }}
|
|
267
|
+
>
|
|
268
|
+
{__('Add attribute', 'wp-component-library')}
|
|
269
|
+
</Button>
|
|
270
|
+
</div>
|
|
271
|
+
</ToolsPanel>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Hook to manage attributes state
|
|
277
|
+
*
|
|
278
|
+
* @param {Array} initialAttributes Initial attributes configuration
|
|
279
|
+
* @returns {Object} Attributes state and handlers
|
|
280
|
+
*/
|
|
281
|
+
export function useAttributes(initialAttributes = []) {
|
|
282
|
+
const [attributes, setAttributes] = useState(initialAttributes);
|
|
283
|
+
|
|
284
|
+
const updateAttribute = (id, value) => {
|
|
285
|
+
setAttributes((prev) =>
|
|
286
|
+
prev.map((attr) => (attr.id === id ? { ...attr, value } : attr))
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const addAttribute = (label, value = '') => {
|
|
291
|
+
const newAttribute = {
|
|
292
|
+
id: `custom-${Date.now()}`,
|
|
293
|
+
label,
|
|
294
|
+
value,
|
|
295
|
+
isCustom: true,
|
|
296
|
+
};
|
|
297
|
+
setAttributes((prev) => [...prev, newAttribute]);
|
|
298
|
+
return newAttribute;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const removeAttribute = (id) => {
|
|
302
|
+
setAttributes((prev) => prev.filter((attr) => attr.id !== id));
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const resetAttributes = () => {
|
|
306
|
+
setAttributes((prev) => prev.map((attr) => ({ ...attr, value: '' })));
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const getAttributeValue = (id) => {
|
|
310
|
+
return attributes.find((attr) => attr.id === id)?.value || '';
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
attributes,
|
|
315
|
+
setAttributes,
|
|
316
|
+
updateAttribute,
|
|
317
|
+
addAttribute,
|
|
318
|
+
removeAttribute,
|
|
319
|
+
resetAttributes,
|
|
320
|
+
getAttributeValue,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export default AttributesPanel;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Badge Component
|
|
2
|
+
|
|
3
|
+
A small status indicator component that displays text with semantic colors, based on the WordPress Gutenberg Badge component design.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { Badge } from '@siratik/wp-component-library';
|
|
9
|
+
|
|
10
|
+
// Basic usage
|
|
11
|
+
<Badge intent="success">Passed</Badge>
|
|
12
|
+
|
|
13
|
+
// With icon
|
|
14
|
+
<Badge intent="error" showIcon>Critical</Badge>
|
|
15
|
+
|
|
16
|
+
// Custom icon
|
|
17
|
+
<Badge intent="info" icon={customIcon}>Custom</Badge>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `children` | `string\|node` | - | Badge text content |
|
|
25
|
+
| `intent` | `string` | `'default'` | Visual intent: `'default'`, `'error'`, `'critical'`, `'warning'`, `'info'`, `'suggestion'`, `'success'`, `'passed'` |
|
|
26
|
+
| `showIcon` | `boolean` | `true` | Whether to show an icon based on intent |
|
|
27
|
+
| `icon` | `Object` | `null` | Custom icon to display (overrides showIcon) |
|
|
28
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
29
|
+
|
|
30
|
+
## Intent Colors
|
|
31
|
+
|
|
32
|
+
- **error/critical**: Red background with red text
|
|
33
|
+
- **warning**: Yellow background with amber text
|
|
34
|
+
- **info/suggestion**: Blue background with blue text
|
|
35
|
+
- **success/passed**: Green background with green text
|
|
36
|
+
- **default**: Gray background with dark gray text
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### Status Indicators
|
|
41
|
+
```jsx
|
|
42
|
+
<Badge intent="success">Active</Badge>
|
|
43
|
+
<Badge intent="error">Failed</Badge>
|
|
44
|
+
<Badge intent="warning">Pending</Badge>
|
|
45
|
+
<Badge intent="info">New</Badge>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### SEO Analysis Results
|
|
49
|
+
```jsx
|
|
50
|
+
<Badge intent="critical">Critical</Badge>
|
|
51
|
+
<Badge intent="warning">Warning</Badge>
|
|
52
|
+
<Badge intent="suggestion">Suggestion</Badge>
|
|
53
|
+
<Badge intent="passed">Passed</Badge>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### With Icons
|
|
57
|
+
```jsx
|
|
58
|
+
<Badge intent="success" showIcon>Complete</Badge>
|
|
59
|
+
<Badge intent="error" showIcon>Error</Badge>
|
|
60
|
+
<Badge intent="info" showIcon>Info</Badge>
|
|
61
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge Component
|
|
3
|
+
*
|
|
4
|
+
* A small status indicator that displays text with semantic colors and icons.
|
|
5
|
+
* Matches WordPress Gutenberg Badge component design exactly.
|
|
6
|
+
*
|
|
7
|
+
* @package WPComponentLibrary
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import styled from "@emotion/styled";
|
|
11
|
+
import { Icon } from "@wordpress/components";
|
|
12
|
+
import { caution, check, info } from "@wordpress/icons";
|
|
13
|
+
import { getBadgeIntentColors } from "../../constants/statusColors";
|
|
14
|
+
|
|
15
|
+
// Badge container with intent-based styling - matches Gutenberg exactly
|
|
16
|
+
const BadgeContainer = styled.span`
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 4px;
|
|
20
|
+
padding: 0 8px;
|
|
21
|
+
height: 24px;
|
|
22
|
+
border-radius: 2px;
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
font-weight: 400;
|
|
25
|
+
line-height: 24px;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
|
|
28
|
+
// Intent-based color schemes matching Gutenberg Badge
|
|
29
|
+
${(props) => {
|
|
30
|
+
const { background, color } = getBadgeIntentColors(props.intent);
|
|
31
|
+
|
|
32
|
+
return `
|
|
33
|
+
background-color: ${background};
|
|
34
|
+
color: ${color};
|
|
35
|
+
`;
|
|
36
|
+
}}
|
|
37
|
+
|
|
38
|
+
// Icon styling to match text color
|
|
39
|
+
svg {
|
|
40
|
+
fill: currentColor;
|
|
41
|
+
width: 18px;
|
|
42
|
+
height: 18px;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Badge Component
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} props - Component props
|
|
51
|
+
* @param {string} props.children - Badge text content
|
|
52
|
+
* @param {string} props.intent - Visual intent: 'default', 'error', 'critical', 'warning', 'info', 'suggestion', 'success', 'passed'
|
|
53
|
+
* @param {boolean} props.showIcon - Whether to show an icon (default: true)
|
|
54
|
+
* @param {Object} props.icon - Custom icon to display (overrides default icon)
|
|
55
|
+
* @param {string} props.className - Additional CSS classes
|
|
56
|
+
* @param {Object} props...otherProps - Any other props to pass to the container
|
|
57
|
+
*
|
|
58
|
+
* @returns {JSX.Element} Badge component
|
|
59
|
+
*/
|
|
60
|
+
export function Badge({
|
|
61
|
+
children,
|
|
62
|
+
intent = "default",
|
|
63
|
+
showIcon = true,
|
|
64
|
+
icon = null,
|
|
65
|
+
className = "",
|
|
66
|
+
...otherProps
|
|
67
|
+
}) {
|
|
68
|
+
// Determine icon based on intent - matches Gutenberg icons
|
|
69
|
+
const getIntentIcon = () => {
|
|
70
|
+
if (icon) { return icon; }
|
|
71
|
+
if (!showIcon) { return null; }
|
|
72
|
+
|
|
73
|
+
switch (intent) {
|
|
74
|
+
case "error":
|
|
75
|
+
case "critical":
|
|
76
|
+
return caution;
|
|
77
|
+
case "warning":
|
|
78
|
+
return caution;
|
|
79
|
+
case "info":
|
|
80
|
+
case "suggestion":
|
|
81
|
+
return info;
|
|
82
|
+
case "success":
|
|
83
|
+
case "passed":
|
|
84
|
+
return check;
|
|
85
|
+
default:
|
|
86
|
+
return info;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const intentIcon = getIntentIcon();
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<BadgeContainer
|
|
94
|
+
intent={intent}
|
|
95
|
+
className={`wp-component-library-badge ${className}`}
|
|
96
|
+
{...otherProps}
|
|
97
|
+
>
|
|
98
|
+
{intentIcon && <Icon icon={intentIcon} size={16} />}
|
|
99
|
+
{children}
|
|
100
|
+
</BadgeContainer>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Export as default and named export for flexibility
|
|
105
|
+
export default Badge;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Block Appender Components
|
|
2
|
+
|
|
3
|
+
A collection of customizable block appender components for different contexts in the WordPress block editor. These components provide consistent UI for adding new blocks while adapting to their specific use cases.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### CustomBlockAppender
|
|
8
|
+
Standard block appender with tooltip for general use.
|
|
9
|
+
|
|
10
|
+
### CustomInspectorAppender
|
|
11
|
+
Block appender styled for inspector panels with secondary button styling.
|
|
12
|
+
|
|
13
|
+
### CustomColumnAppender
|
|
14
|
+
Column-specific appender with wrapper div for layout containers.
|
|
15
|
+
|
|
16
|
+
### CustomInlineAppender
|
|
17
|
+
Context-aware appender that only displays when its parent block is selected.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
import {
|
|
23
|
+
CustomBlockAppender,
|
|
24
|
+
CustomInspectorAppender,
|
|
25
|
+
CustomColumnAppender,
|
|
26
|
+
CustomInlineAppender
|
|
27
|
+
} from '@siratik/wp-component-library/components/block-appender';
|
|
28
|
+
|
|
29
|
+
// Standard block appender
|
|
30
|
+
<CustomBlockAppender
|
|
31
|
+
rootClientId={clientId}
|
|
32
|
+
appenderTitle="Add Content Block"
|
|
33
|
+
appenderLabel="Click to add a new content block"
|
|
34
|
+
appenderClassModifier="content"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
// Inspector panel appender
|
|
38
|
+
<CustomInspectorAppender
|
|
39
|
+
rootClientId={clientId}
|
|
40
|
+
appenderTitle="Add Setting Block"
|
|
41
|
+
appenderLabel="Add a configuration block"
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
// Column layout appender
|
|
45
|
+
<CustomColumnAppender
|
|
46
|
+
rootClientId={columnId}
|
|
47
|
+
appenderTitle="Add Column Content"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
// Inline context-aware appender
|
|
51
|
+
<CustomInlineAppender
|
|
52
|
+
clientId={parentBlockId}
|
|
53
|
+
rootClientId={parentBlockId}
|
|
54
|
+
label="Add Paragraph"
|
|
55
|
+
blockName="core/paragraph"
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Common Props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
|------|------|---------|-------------|
|
|
63
|
+
| `rootClientId` | `string` | Required | Parent block ID for insertion context |
|
|
64
|
+
| `appenderTitle` | `string` | - | Display text for the appender button |
|
|
65
|
+
| `appenderLabel` | `string` | - | Accessibility label for screen readers |
|
|
66
|
+
| `appenderClassModifier` | `string` | - | CSS class modifier for custom styling |
|
|
67
|
+
|
|
68
|
+
## CustomInlineAppender Specific Props
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
|------|------|---------|-------------|
|
|
72
|
+
| `clientId` | `string` | Required | Block ID to monitor for selection state |
|
|
73
|
+
| `label` | `string` | `"Add Block"` | Button label text |
|
|
74
|
+
| `blockName` | `string` | `"core/paragraph"` | Default block type to insert |
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
### Quick Insert Mode
|
|
79
|
+
All appenders use WordPress's quick insert mode for faster block addition.
|
|
80
|
+
|
|
81
|
+
### Context-Aware Visibility
|
|
82
|
+
CustomInlineAppender automatically shows/hides based on parent block selection state.
|
|
83
|
+
|
|
84
|
+
### Consistent Styling
|
|
85
|
+
Each appender type has appropriate styling for its context:
|
|
86
|
+
- Standard: Primary button with tooltip
|
|
87
|
+
- Inspector: Secondary button for panel integration
|
|
88
|
+
- Column: Wrapped for layout containers
|
|
89
|
+
- Inline: Minimal styling for inline contexts
|
|
90
|
+
|
|
91
|
+
### Accessibility
|
|
92
|
+
- Proper ARIA labels for screen readers
|
|
93
|
+
- Keyboard navigation support
|
|
94
|
+
- Tooltip hints for standard appender
|
|
95
|
+
|
|
96
|
+
## CSS Classes
|
|
97
|
+
|
|
98
|
+
Generated classes follow the pattern:
|
|
99
|
+
- Base: `custom-appender`
|
|
100
|
+
- With modifier: `custom-appender custom-appender--{modifier}`
|
|
101
|
+
- Inspector: `custom-inspector-appender`
|
|
102
|
+
- Column: `custom-column-appender`
|
|
103
|
+
- Inline: `wp-block-appender` (uses core styling)
|
|
104
|
+
|
|
105
|
+
## Examples
|
|
106
|
+
|
|
107
|
+
### Adding to a Custom Container Block
|
|
108
|
+
```jsx
|
|
109
|
+
function ContainerEdit({ clientId }) {
|
|
110
|
+
return (
|
|
111
|
+
<div className="container-content">
|
|
112
|
+
<InnerBlocks />
|
|
113
|
+
<CustomBlockAppender
|
|
114
|
+
rootClientId={clientId}
|
|
115
|
+
appenderTitle="Add Container Item"
|
|
116
|
+
appenderClassModifier="container"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Inspector Panel Integration
|
|
124
|
+
```jsx
|
|
125
|
+
<InspectorControls>
|
|
126
|
+
<PanelBody title="Content Blocks">
|
|
127
|
+
<p>Add configuration blocks below:</p>
|
|
128
|
+
<CustomInspectorAppender
|
|
129
|
+
rootClientId={clientId}
|
|
130
|
+
appenderTitle="Add Config"
|
|
131
|
+
/>
|
|
132
|
+
</PanelBody>
|
|
133
|
+
</InspectorControls>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Inline Editing Experience
|
|
137
|
+
```jsx
|
|
138
|
+
function InlineEditor({ clientId, isSelected }) {
|
|
139
|
+
return (
|
|
140
|
+
<div className="inline-content">
|
|
141
|
+
<InnerBlocks />
|
|
142
|
+
<CustomInlineAppender
|
|
143
|
+
clientId={clientId}
|
|
144
|
+
rootClientId={clientId}
|
|
145
|
+
label="Add Text"
|
|
146
|
+
blockName="core/paragraph"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Notes
|
|
154
|
+
|
|
155
|
+
- All appenders integrate with WordPress's Inserter component
|
|
156
|
+
- The quick inserter mode provides faster access to commonly used blocks
|
|
157
|
+
- CustomInlineAppender uses block editor selection state for smart visibility
|
|
158
|
+
- Appenders respect block insertion rules and allowed blocks
|