@siratik/wp-component-library 0.1.1 → 0.1.3

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.
Files changed (96) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +43 -5
  3. package/components/attachment-image/README.md +118 -0
  4. package/components/attachment-image/index.js +105 -0
  5. package/components/attributes-panel/README.md +92 -0
  6. package/components/attributes-panel/index.js +324 -0
  7. package/components/badge/README.md +61 -0
  8. package/components/badge/index.js +105 -0
  9. package/components/block-appender/README.md +158 -0
  10. package/components/block-appender/index.js +135 -0
  11. package/components/block-extension/index.js +214 -0
  12. package/components/business-hours/SpecialHoursControl.js +211 -0
  13. package/components/business-hours/WeeklyHoursControl.js +419 -0
  14. package/components/business-hours/index.js +8 -0
  15. package/components/button/README.md +172 -0
  16. package/components/button/index.js +84 -0
  17. package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
  18. package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
  19. package/components/captcha-placeholder/HCaptcha.js +99 -0
  20. package/components/captcha-placeholder/README.md +228 -0
  21. package/components/captcha-placeholder/index.js +39 -0
  22. package/components/captcha-placeholder/styles.js +33 -0
  23. package/components/geocoder/AddressEntryControl.js +390 -0
  24. package/components/geocoder/GeocoderSearchControl.js +353 -0
  25. package/components/geocoder/countryOptions.js +66 -0
  26. package/components/geocoder/geocoder.js +400 -0
  27. package/components/geocoder/index.js +16 -0
  28. package/components/icon-picker/Icon.js +53 -0
  29. package/components/icon-picker/IconPicker.js +70 -0
  30. package/components/icon-picker/IconPickerModal.js +474 -0
  31. package/components/icon-picker/IconPickerToolbarButton.js +60 -0
  32. package/components/icon-picker/InlineIconPicker.js +92 -0
  33. package/components/icon-picker/api.js +181 -0
  34. package/components/icon-picker/config-bootstrap.js +108 -0
  35. package/components/icon-picker/hooks.js +157 -0
  36. package/components/icon-picker/index.js +39 -0
  37. package/components/icon-picker/load-script.js +33 -0
  38. package/components/icon-picker/loaders.js +79 -0
  39. package/components/icon-picker/store.js +223 -0
  40. package/components/icon-picker/styles.js +232 -0
  41. package/components/icon-picker/utils.js +138 -0
  42. package/components/layout/README.md +268 -0
  43. package/components/link-tools-panel/README.md +49 -0
  44. package/components/link-tools-panel/index.js +198 -0
  45. package/components/link-tools-panel/styles.js +90 -0
  46. package/components/media/README.md +288 -0
  47. package/components/media/SettingsMediaUpload.js +186 -0
  48. package/components/media/editor-media-upload.js +65 -0
  49. package/components/media/image-controls.js +196 -0
  50. package/components/media/index.js +13 -0
  51. package/components/media/inspector-media-upload.js +290 -0
  52. package/components/media/toolbar-media-upload.js +53 -0
  53. package/components/media/utils/aspect-ratios.js +118 -0
  54. package/components/meta/MetaPanel.js +75 -0
  55. package/components/meta/README.md +46 -0
  56. package/components/meta/index.js +6 -0
  57. package/components/meta/meta-gated-editor.js +282 -0
  58. package/components/query/README.md +340 -0
  59. package/components/query/cardTemplatePartPanel.js +69 -0
  60. package/components/query/cardTemplatePartSelect.js +165 -0
  61. package/components/query/cardTemplatePartUtils.js +119 -0
  62. package/components/query/columnCountControl.js +63 -0
  63. package/components/query/displayTypeSelect.js +52 -0
  64. package/components/query/examples.js +166 -0
  65. package/components/query/hideCurrentPostControl.js +47 -0
  66. package/components/query/index.js +20 -0
  67. package/components/query/manualPostSelector.js +125 -0
  68. package/components/query/manualTermSelector.js +141 -0
  69. package/components/query/orderBySelect.js +95 -0
  70. package/components/query/postTypeSelect.js +67 -0
  71. package/components/query/postsPerPageControl.js +51 -0
  72. package/components/query/selectionModeControl.js +61 -0
  73. package/components/query/taxonomySelect.js +139 -0
  74. package/components/query/utils.js +75 -0
  75. package/components/repeater/README.md +159 -0
  76. package/components/repeater/index.js +829 -0
  77. package/components/segmented-bar/README.md +30 -0
  78. package/components/segmented-bar/index.js +68 -0
  79. package/components/sortable-select/README.md +153 -0
  80. package/components/sortable-select/custom-sortable-token.js +127 -0
  81. package/components/sortable-select/index.js +693 -0
  82. package/components/style-picker/README.md +36 -0
  83. package/components/style-picker/index.js +92 -0
  84. package/components/variable-field/README.md +299 -0
  85. package/components/variable-field/index.js +592 -0
  86. package/components/variable-field/value-to-chip-html.js +38 -0
  87. package/components/variable-inserter/README.md +68 -0
  88. package/components/variable-inserter/index.js +168 -0
  89. package/constants/statusColors.js +1 -1
  90. package/editor.js +16 -0
  91. package/index.js +127 -0
  92. package/package.json +21 -13
  93. package/utils/kitaris-localize.js +128 -0
  94. package/utils/slug-aware-color-setter.js +27 -0
  95. package/build/index.asset.php +0 -20
  96. 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