@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.
- 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 +186 -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,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Flex,
|
|
3
|
+
FlexBlock,
|
|
4
|
+
SelectControl,
|
|
5
|
+
ToggleControl,
|
|
6
|
+
__experimentalToggleGroupControl as ToggleGroupControl,
|
|
7
|
+
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
|
8
|
+
__experimentalToolsPanel as ToolsPanel,
|
|
9
|
+
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
10
|
+
__experimentalUnitControl as UnitControl,
|
|
11
|
+
} from "@wordpress/components";
|
|
12
|
+
import { useSelect } from "@wordpress/data";
|
|
13
|
+
import { __ } from "@wordpress/i18n";
|
|
14
|
+
|
|
15
|
+
import { useAspectRatioOptions } from "./utils/aspect-ratios";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reusable image controls for aspect ratio, width, height, scale, and caption.
|
|
19
|
+
* @param {object} props
|
|
20
|
+
* @param {string} props.aspectRatio
|
|
21
|
+
* @param {string} props.width
|
|
22
|
+
* @param {string} props.height
|
|
23
|
+
* @param {boolean} props.showCaption
|
|
24
|
+
* @param {string} props.scale - Object-fit value: "cover" or "contain".
|
|
25
|
+
* @param {function} props.setAttributes
|
|
26
|
+
*/
|
|
27
|
+
const SCALE_HELP = {
|
|
28
|
+
cover: __("Image covers the space evenly.", "wp-component-library"),
|
|
29
|
+
contain: __("Image is contained without cropping.", "wp-component-library"),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ImageControls = ({
|
|
33
|
+
aspectRatio = "4/3",
|
|
34
|
+
width = "auto",
|
|
35
|
+
height = "auto",
|
|
36
|
+
showCaption = false,
|
|
37
|
+
scale = "cover",
|
|
38
|
+
imageSize = "",
|
|
39
|
+
setAttributes,
|
|
40
|
+
}) => {
|
|
41
|
+
// Get aspect ratio options from theme.json
|
|
42
|
+
const aspectRatioOptions = useAspectRatioOptions();
|
|
43
|
+
|
|
44
|
+
// Build resolution options from registered image sizes
|
|
45
|
+
const imageSizeOptions = useSelect((select) => {
|
|
46
|
+
const sizes = select("core/block-editor").getSettings().imageSizes || [];
|
|
47
|
+
return [
|
|
48
|
+
{ value: "", label: __("Auto", "wp-component-library") },
|
|
49
|
+
...sizes.map(({ name, slug }) => ({ value: slug, label: name })),
|
|
50
|
+
];
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const resetAllFilter = (newAttributes) => {
|
|
54
|
+
return {
|
|
55
|
+
...newAttributes,
|
|
56
|
+
aspectRatio: "4/3",
|
|
57
|
+
width: "auto",
|
|
58
|
+
height: "auto",
|
|
59
|
+
showCaption: false,
|
|
60
|
+
scale: "cover",
|
|
61
|
+
imageSize: "",
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ToolsPanel
|
|
67
|
+
label={__("Image Settings", "wp-component-library")}
|
|
68
|
+
resetAllFilter={resetAllFilter}
|
|
69
|
+
panelId="image-settings"
|
|
70
|
+
>
|
|
71
|
+
<ToolsPanelItem
|
|
72
|
+
hasValue={() => aspectRatio !== "4/3"}
|
|
73
|
+
label={__("Aspect Ratio", "wp-component-library")}
|
|
74
|
+
onDeselect={() => setAttributes({ aspectRatio: "4/3" })}
|
|
75
|
+
resetAllFilter={resetAllFilter}
|
|
76
|
+
isShownByDefault
|
|
77
|
+
panelId="image-settings"
|
|
78
|
+
>
|
|
79
|
+
<SelectControl
|
|
80
|
+
__nextHasNoMarginBottom={true}
|
|
81
|
+
__next40pxDefaultSize
|
|
82
|
+
label={__("Aspect Ratio", "wp-component-library")}
|
|
83
|
+
value={aspectRatio}
|
|
84
|
+
onChange={(value) => setAttributes({ aspectRatio: value })}
|
|
85
|
+
options={aspectRatioOptions}
|
|
86
|
+
/>
|
|
87
|
+
</ToolsPanelItem>
|
|
88
|
+
|
|
89
|
+
<ToolsPanelItem
|
|
90
|
+
hasValue={() => scale !== "cover"}
|
|
91
|
+
label={__("Scale", "wp-component-library")}
|
|
92
|
+
onDeselect={() => setAttributes({ scale: "cover" })}
|
|
93
|
+
resetAllFilter={resetAllFilter}
|
|
94
|
+
isShownByDefault
|
|
95
|
+
panelId="image-settings"
|
|
96
|
+
>
|
|
97
|
+
<ToggleGroupControl
|
|
98
|
+
__nextHasNoMarginBottom={true}
|
|
99
|
+
__next40pxDefaultSize
|
|
100
|
+
label={__("Scale", "wp-component-library")}
|
|
101
|
+
value={scale}
|
|
102
|
+
onChange={(value) => setAttributes({ scale: value })}
|
|
103
|
+
isBlock
|
|
104
|
+
help={SCALE_HELP[scale] ?? ""}
|
|
105
|
+
>
|
|
106
|
+
<ToggleGroupControlOption
|
|
107
|
+
value="cover"
|
|
108
|
+
label={__("Cover", "wp-component-library")}
|
|
109
|
+
/>
|
|
110
|
+
<ToggleGroupControlOption
|
|
111
|
+
value="contain"
|
|
112
|
+
label={__("Contain", "wp-component-library")}
|
|
113
|
+
/>
|
|
114
|
+
</ToggleGroupControl>
|
|
115
|
+
</ToolsPanelItem>
|
|
116
|
+
|
|
117
|
+
<Flex gap={4} style={{ minWidth: 250 }}>
|
|
118
|
+
<FlexBlock>
|
|
119
|
+
<ToolsPanelItem
|
|
120
|
+
hasValue={() => width !== "auto"}
|
|
121
|
+
label={__("Width", "wp-component-library")}
|
|
122
|
+
onDeselect={() => setAttributes({ width: "auto" })}
|
|
123
|
+
resetAllFilter={resetAllFilter}
|
|
124
|
+
panelId="image-settings"
|
|
125
|
+
>
|
|
126
|
+
<UnitControl
|
|
127
|
+
__next40pxDefaultSize
|
|
128
|
+
label={__("Width", "wp-component-library")}
|
|
129
|
+
onChange={(value) =>
|
|
130
|
+
setAttributes({ width: value })
|
|
131
|
+
}
|
|
132
|
+
value={width}
|
|
133
|
+
placeholder={__("Auto", "wp-component-library")}
|
|
134
|
+
/>
|
|
135
|
+
</ToolsPanelItem>
|
|
136
|
+
</FlexBlock>
|
|
137
|
+
<FlexBlock>
|
|
138
|
+
<ToolsPanelItem
|
|
139
|
+
hasValue={() => height !== "auto"}
|
|
140
|
+
label={__("Height", "wp-component-library")}
|
|
141
|
+
onDeselect={() => setAttributes({ height: "auto" })}
|
|
142
|
+
resetAllFilter={resetAllFilter}
|
|
143
|
+
panelId="image-settings"
|
|
144
|
+
>
|
|
145
|
+
<UnitControl
|
|
146
|
+
__next40pxDefaultSize
|
|
147
|
+
label={__("Height", "wp-component-library")}
|
|
148
|
+
onChange={(value) =>
|
|
149
|
+
setAttributes({ height: value })
|
|
150
|
+
}
|
|
151
|
+
value={height}
|
|
152
|
+
placeholder={__("Auto", "wp-component-library")}
|
|
153
|
+
/>
|
|
154
|
+
</ToolsPanelItem>
|
|
155
|
+
</FlexBlock>
|
|
156
|
+
</Flex>
|
|
157
|
+
|
|
158
|
+
<ToolsPanelItem
|
|
159
|
+
hasValue={() => showCaption !== false}
|
|
160
|
+
label={__("Show Caption", "wp-component-library")}
|
|
161
|
+
onDeselect={() => setAttributes({ showCaption: false })}
|
|
162
|
+
resetAllFilter={resetAllFilter}
|
|
163
|
+
isShownByDefault={false}
|
|
164
|
+
panelId="image-settings"
|
|
165
|
+
>
|
|
166
|
+
<ToggleControl
|
|
167
|
+
__nextHasNoMarginBottom={true}
|
|
168
|
+
label={__("Show Caption", "wp-component-library")}
|
|
169
|
+
checked={showCaption}
|
|
170
|
+
onChange={(value) => setAttributes({ showCaption: value })}
|
|
171
|
+
/>
|
|
172
|
+
</ToolsPanelItem>
|
|
173
|
+
|
|
174
|
+
<ToolsPanelItem
|
|
175
|
+
hasValue={() => !!imageSize}
|
|
176
|
+
label={__("Resolution", "wp-component-library")}
|
|
177
|
+
onDeselect={() => setAttributes({ imageSize: "" })}
|
|
178
|
+
resetAllFilter={resetAllFilter}
|
|
179
|
+
isShownByDefault={false}
|
|
180
|
+
panelId="image-settings"
|
|
181
|
+
>
|
|
182
|
+
<SelectControl
|
|
183
|
+
__nextHasNoMarginBottom={true}
|
|
184
|
+
__next40pxDefaultSize
|
|
185
|
+
label={__("Resolution", "wp-component-library")}
|
|
186
|
+
value={imageSize}
|
|
187
|
+
options={imageSizeOptions}
|
|
188
|
+
onChange={(value) => setAttributes({ imageSize: value })}
|
|
189
|
+
help={__("Override the default image resolution. Leave as Auto for smart sizing.", "wp-component-library")}
|
|
190
|
+
/>
|
|
191
|
+
</ToolsPanelItem>
|
|
192
|
+
</ToolsPanel>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export { ImageControls };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Upload Components
|
|
3
|
+
*
|
|
4
|
+
* A collection of media upload components for different contexts
|
|
5
|
+
* within the WordPress block editor and admin settings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export individual media upload components
|
|
9
|
+
export { EditorMediaUpload } from "./editor-media-upload";
|
|
10
|
+
export { ImageControls } from "./image-controls";
|
|
11
|
+
export { InspectorMediaUpload } from "./inspector-media-upload";
|
|
12
|
+
export { SettingsMediaUpload } from "./SettingsMediaUpload";
|
|
13
|
+
export { ToolbarMediaUpload } from "./toolbar-media-upload";
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspector Media Upload Component
|
|
3
|
+
*
|
|
4
|
+
* Uses wp.media() directly instead of the block editor's MediaUpload
|
|
5
|
+
* slot, ensuring it works on both block editor and settings pages.
|
|
6
|
+
*/
|
|
7
|
+
import styled from "@emotion/styled";
|
|
8
|
+
import { BaseControl, Button, Flex, Placeholder } from "@wordpress/components";
|
|
9
|
+
import { useCallback } from "@wordpress/element";
|
|
10
|
+
import { __ } from "@wordpress/i18n";
|
|
11
|
+
import { AttachmentImage } from "../attachment-image";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* "auto", a number ("1.91") or a ratio ("16/9", "16 / 9").
|
|
15
|
+
*/
|
|
16
|
+
const ASPECT_RATIO_PATTERN = /^(auto|\d+(\.\d+)?(\s*\/\s*\d+(\.\d+)?)?)$/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A number with an optional length unit ("400", "64px", "20rem", "50%").
|
|
20
|
+
*/
|
|
21
|
+
const MAX_WIDTH_PATTERN = /^\d+(\.\d+)?(px|em|rem|%|vw|ch)?$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* CSS aspect-ratio accepts a number (e.g. 1.91) or a ratio string ("16/9").
|
|
25
|
+
* Anything else falls back to 16/9, so a prop value can never add CSS rules.
|
|
26
|
+
*
|
|
27
|
+
* @param {number|string} ratio
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function toCssAspectRatio(ratio) {
|
|
31
|
+
if (typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0) {
|
|
32
|
+
return String(ratio);
|
|
33
|
+
}
|
|
34
|
+
if (typeof ratio === "string" && ASPECT_RATIO_PATTERN.test(ratio.trim())) {
|
|
35
|
+
return ratio.trim();
|
|
36
|
+
}
|
|
37
|
+
return String(16 / 9);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* CSS max-width for the preview (number = px; empty or invalid = none).
|
|
42
|
+
*
|
|
43
|
+
* @param {number|string|null} maxWidth
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function toCssMaxWidth(maxWidth) {
|
|
47
|
+
if (typeof maxWidth === "number" && Number.isFinite(maxWidth) && maxWidth >= 0) {
|
|
48
|
+
return `${maxWidth}px`;
|
|
49
|
+
}
|
|
50
|
+
if (typeof maxWidth === "string" && MAX_WIDTH_PATTERN.test(maxWidth.trim())) {
|
|
51
|
+
return maxWidth.trim();
|
|
52
|
+
}
|
|
53
|
+
return "none";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Styled components
|
|
57
|
+
const StyledWrapper = styled.div`
|
|
58
|
+
max-width: ${(props) => toCssMaxWidth(props.maxWidth)};
|
|
59
|
+
|
|
60
|
+
/* Shared frame so placeholder and selected image occupy the same box. */
|
|
61
|
+
.built-inspector-media-preview {
|
|
62
|
+
width: 100%;
|
|
63
|
+
aspect-ratio: ${(props) => props.$aspectRatio};
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
background: #f0f0f0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.built-inspector-media-preview .components-placeholder {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: 100%;
|
|
72
|
+
min-height: 0 !important;
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: 0;
|
|
75
|
+
box-sizing: border-box;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.built-inspector-media-preview img {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
object-fit: cover;
|
|
82
|
+
object-position: center;
|
|
83
|
+
display: block;
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const StyledImageContainer = styled.div`
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: 100%;
|
|
90
|
+
|
|
91
|
+
.components-flex {
|
|
92
|
+
height: auto;
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
const StyledActions = styled.div`
|
|
97
|
+
margin-top: 12px;
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
const ALLOWED_MEDIA_TYPES = ["image"];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hook to open the native wp.media frame.
|
|
104
|
+
*/
|
|
105
|
+
function useMediaOpen({ onSelect, multiple, allowedTypes }) {
|
|
106
|
+
return useCallback(() => {
|
|
107
|
+
if (typeof wp === "undefined" || !wp.media) { return; }
|
|
108
|
+
|
|
109
|
+
const frame = wp.media({
|
|
110
|
+
title: __("Select or Upload Media", "wp-component-library"),
|
|
111
|
+
multiple: multiple || false,
|
|
112
|
+
library: { type: allowedTypes || ["image"] },
|
|
113
|
+
button: { text: __("Select", "wp-component-library") },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
frame.on("select", () => {
|
|
117
|
+
const selection = frame.state().get("selection");
|
|
118
|
+
if (multiple) {
|
|
119
|
+
onSelect(selection.map((att) => att.toJSON()));
|
|
120
|
+
} else {
|
|
121
|
+
onSelect(selection.first().toJSON());
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
frame.open();
|
|
126
|
+
}, [onSelect, multiple, allowedTypes]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inspector Media Upload
|
|
131
|
+
*
|
|
132
|
+
* @param {object} props
|
|
133
|
+
* @param {number|string|null} [props.maxWidth] Optional max width for the
|
|
134
|
+
* preview/placeholder (number = px). Default: none (full width).
|
|
135
|
+
* @param {number|string} [props.aspectRatio=16/9] Locked preview box ratio;
|
|
136
|
+
* selected images use object-fit: cover inside the same box as the placeholder.
|
|
137
|
+
* @returns {JSX.Element}
|
|
138
|
+
*/
|
|
139
|
+
function InspectorMediaUpload({
|
|
140
|
+
mediaIDs,
|
|
141
|
+
onSelect,
|
|
142
|
+
onRemove,
|
|
143
|
+
multiple,
|
|
144
|
+
buttonTitle,
|
|
145
|
+
variant,
|
|
146
|
+
showFeatureImage = false,
|
|
147
|
+
featureImage = null,
|
|
148
|
+
label = null,
|
|
149
|
+
help = null,
|
|
150
|
+
showImagePlaceholder,
|
|
151
|
+
aspectRatio = 16 / 9,
|
|
152
|
+
maxWidth = null,
|
|
153
|
+
}) {
|
|
154
|
+
// Handle ID, [id], and attachment object formats for mediaIDs.
|
|
155
|
+
const getMediaId = (mediaData) => {
|
|
156
|
+
if (!mediaData) { return null; }
|
|
157
|
+
if (typeof mediaData === "number") { return mediaData; }
|
|
158
|
+
if (typeof mediaData === "string" && /^\d+$/.test(mediaData)) {
|
|
159
|
+
return parseInt(mediaData, 10);
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(mediaData)) {
|
|
162
|
+
return getMediaId(mediaData[0]);
|
|
163
|
+
}
|
|
164
|
+
if (typeof mediaData === "object" && mediaData.id) { return mediaData.id; }
|
|
165
|
+
return null;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const mediaId = getMediaId(mediaIDs);
|
|
169
|
+
const hasImage = !!mediaId;
|
|
170
|
+
const hasFeatureImage = !!(
|
|
171
|
+
showFeatureImage &&
|
|
172
|
+
featureImage &&
|
|
173
|
+
featureImage !== 0
|
|
174
|
+
);
|
|
175
|
+
const cssAspectRatio = toCssAspectRatio(aspectRatio);
|
|
176
|
+
|
|
177
|
+
// Generate a unique ID for BaseControl
|
|
178
|
+
const controlId = `inspector-media-upload-${mediaId || "new"}`;
|
|
179
|
+
|
|
180
|
+
// Open native wp.media frame
|
|
181
|
+
const openMedia = useMediaOpen({
|
|
182
|
+
onSelect,
|
|
183
|
+
multiple,
|
|
184
|
+
allowedTypes: ALLOWED_MEDIA_TYPES,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const imageDisplay = (
|
|
188
|
+
<>
|
|
189
|
+
{showImagePlaceholder && !hasImage && !hasFeatureImage && (
|
|
190
|
+
<div className="built-inspector-media-preview">
|
|
191
|
+
<Placeholder
|
|
192
|
+
withIllustration={true}
|
|
193
|
+
className="built-editor-panel-image placeholder-image placeholder-image--built"
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{showImagePlaceholder && hasImage && (
|
|
199
|
+
<div className="built-inspector-media-preview">
|
|
200
|
+
<StyledImageContainer className="built-editor-panel-image">
|
|
201
|
+
<AttachmentImage
|
|
202
|
+
className="built-editor-panel-image"
|
|
203
|
+
imageId={mediaId}
|
|
204
|
+
size="wide_medium"
|
|
205
|
+
includeFigure={false}
|
|
206
|
+
aspectRatio={cssAspectRatio}
|
|
207
|
+
/>
|
|
208
|
+
</StyledImageContainer>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{showImagePlaceholder && hasFeatureImage && !hasImage && (
|
|
213
|
+
<div className="built-inspector-media-preview">
|
|
214
|
+
<StyledImageContainer className="built-editor-panel-image">
|
|
215
|
+
<AttachmentImage
|
|
216
|
+
className="built-editor-panel-image"
|
|
217
|
+
imageId={featureImage.id || featureImage}
|
|
218
|
+
size="wide_medium"
|
|
219
|
+
includeFigure={false}
|
|
220
|
+
aspectRatio={cssAspectRatio}
|
|
221
|
+
/>
|
|
222
|
+
</StyledImageContainer>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const buttons = (
|
|
229
|
+
<StyledActions>
|
|
230
|
+
{!hasImage ? (
|
|
231
|
+
<Button
|
|
232
|
+
__next40pxDefaultSize
|
|
233
|
+
className="is-full-width"
|
|
234
|
+
size="default"
|
|
235
|
+
variant={variant || "secondary"}
|
|
236
|
+
onClick={openMedia}
|
|
237
|
+
>
|
|
238
|
+
{buttonTitle ||
|
|
239
|
+
__("Select or Upload Media", "wp-component-library")}
|
|
240
|
+
</Button>
|
|
241
|
+
) : (
|
|
242
|
+
<Flex>
|
|
243
|
+
<Button
|
|
244
|
+
__next40pxDefaultSize
|
|
245
|
+
size="default"
|
|
246
|
+
className="is-full-width"
|
|
247
|
+
variant="secondary"
|
|
248
|
+
onClick={openMedia}
|
|
249
|
+
>
|
|
250
|
+
{__("Edit/Replace", "wp-component-library")}
|
|
251
|
+
</Button>
|
|
252
|
+
<Button
|
|
253
|
+
__next40pxDefaultSize
|
|
254
|
+
size="default"
|
|
255
|
+
variant="secondary"
|
|
256
|
+
className="is-full-width"
|
|
257
|
+
onClick={onRemove}
|
|
258
|
+
isDestructive
|
|
259
|
+
>
|
|
260
|
+
{__("Remove", "wp-component-library")}
|
|
261
|
+
</Button>
|
|
262
|
+
</Flex>
|
|
263
|
+
)}
|
|
264
|
+
</StyledActions>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// If label or help text is provided, wrap in BaseControl
|
|
268
|
+
if (label || help) {
|
|
269
|
+
return (
|
|
270
|
+
<StyledWrapper maxWidth={maxWidth} $aspectRatio={cssAspectRatio}>
|
|
271
|
+
<BaseControl id={controlId} label={label} help={help}>
|
|
272
|
+
{imageDisplay}
|
|
273
|
+
</BaseControl>
|
|
274
|
+
{buttons}
|
|
275
|
+
</StyledWrapper>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Otherwise, return controls without BaseControl wrapper
|
|
280
|
+
return (
|
|
281
|
+
<StyledWrapper maxWidth={maxWidth} $aspectRatio={cssAspectRatio}>
|
|
282
|
+
<Flex direction="column" expanded={true} style={{ flexGrow: 1 }}>
|
|
283
|
+
{imageDisplay}
|
|
284
|
+
{buttons}
|
|
285
|
+
</Flex>
|
|
286
|
+
</StyledWrapper>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export { InspectorMediaUpload };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolbar Media Upload Component
|
|
3
|
+
*/
|
|
4
|
+
import { MediaUpload, MediaUploadCheck } from "@wordpress/block-editor";
|
|
5
|
+
import { ToolbarButton } from "@wordpress/components";
|
|
6
|
+
import { __ } from "@wordpress/i18n";
|
|
7
|
+
import { pencil } from "@wordpress/icons";
|
|
8
|
+
|
|
9
|
+
const ALLOWED_MEDIA_TYPES = ["image"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Toolbar Media Upload
|
|
13
|
+
*
|
|
14
|
+
* @param {object} props
|
|
15
|
+
* @returns {JSX.Element}
|
|
16
|
+
*/
|
|
17
|
+
function ToolbarMediaUpload({
|
|
18
|
+
mediaIDs,
|
|
19
|
+
onSelect,
|
|
20
|
+
gallery,
|
|
21
|
+
multiple,
|
|
22
|
+
}) {
|
|
23
|
+
// Handle both ID and object formats for mediaIDs
|
|
24
|
+
const getMediaId = (mediaData) => {
|
|
25
|
+
if (!mediaData) { return null; }
|
|
26
|
+
if (typeof mediaData === "number") { return mediaData; }
|
|
27
|
+
if (typeof mediaData === "object" && mediaData.id) { return mediaData.id; }
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const mediaId = getMediaId(mediaIDs);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<MediaUploadCheck>
|
|
35
|
+
<MediaUpload
|
|
36
|
+
onSelect={onSelect}
|
|
37
|
+
allowedTypes={ALLOWED_MEDIA_TYPES}
|
|
38
|
+
value={mediaId}
|
|
39
|
+
render={({ open }) => (
|
|
40
|
+
<ToolbarButton
|
|
41
|
+
icon={pencil}
|
|
42
|
+
label={__("Edit/Replace Media", "wp-component-library")}
|
|
43
|
+
onClick={open}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
gallery={gallery}
|
|
47
|
+
multiple={multiple}
|
|
48
|
+
/>
|
|
49
|
+
</MediaUploadCheck>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { ToolbarMediaUpload };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useSettings } from "@wordpress/block-editor";
|
|
2
|
+
import { useMemo } from "@wordpress/element";
|
|
3
|
+
import { __ } from "@wordpress/i18n";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WordPress core aspect ratio presets (when theme enables defaultAspectRatios).
|
|
7
|
+
* @see https://developer.wordpress.org/news/2024/08/registering-custom-aspect-ratios-in-wordpress-6-6/
|
|
8
|
+
*/
|
|
9
|
+
const getDefaultCoreAspectRatios = () => [
|
|
10
|
+
{ name: __("Square", "wp-component-library"), ratio: "1" },
|
|
11
|
+
{ name: __("Standard", "wp-component-library"), ratio: "4/3" },
|
|
12
|
+
{ name: __("Portrait", "wp-component-library"), ratio: "3/4" },
|
|
13
|
+
{ name: __("Classic", "wp-component-library"), ratio: "3/2" },
|
|
14
|
+
{ name: __("Classic Portrait", "wp-component-library"), ratio: "2/3" },
|
|
15
|
+
{ name: __("Wide", "wp-component-library"), ratio: "16/9" },
|
|
16
|
+
{ name: __("Extra Wide", "wp-component-library"), ratio: "21/9" },
|
|
17
|
+
{ name: __("Tall", "wp-component-library"), ratio: "9/16" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Flatten aspect ratio presets from useSettings (array or { default, theme } object).
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} setting
|
|
24
|
+
* @return {Array<{ name: string, ratio: string }>}
|
|
25
|
+
*/
|
|
26
|
+
function collectAspectRatioPresets(setting) {
|
|
27
|
+
if (!setting) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(setting)) {
|
|
32
|
+
return setting;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof setting !== "object") {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const merged = [];
|
|
40
|
+
|
|
41
|
+
for (const key of ["default", "theme", "custom"]) {
|
|
42
|
+
if (Array.isArray(setting[key])) {
|
|
43
|
+
merged.push(...setting[key]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hook to get aspect ratio options from theme.json
|
|
52
|
+
* Returns whatever aspect ratios are defined (default or custom)
|
|
53
|
+
* Includes "Original" option like core/image block
|
|
54
|
+
*/
|
|
55
|
+
export function useAspectRatioOptions() {
|
|
56
|
+
const [
|
|
57
|
+
aspectRatiosSetting,
|
|
58
|
+
defaultPresets,
|
|
59
|
+
themePresets,
|
|
60
|
+
defaultAspectRatiosEnabled,
|
|
61
|
+
] = useSettings([
|
|
62
|
+
"dimensions.aspectRatios",
|
|
63
|
+
"dimensions.aspectRatios.default",
|
|
64
|
+
"dimensions.aspectRatios.theme",
|
|
65
|
+
"dimensions.defaultAspectRatios",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
return useMemo(() => {
|
|
69
|
+
const originalOption = {
|
|
70
|
+
label: __("Original", "wp-component-library"),
|
|
71
|
+
value: "original",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let presets = [
|
|
75
|
+
...collectAspectRatioPresets(aspectRatiosSetting),
|
|
76
|
+
...collectAspectRatioPresets(defaultPresets),
|
|
77
|
+
...collectAspectRatioPresets(themePresets),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// Deduplicate by ratio value.
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
presets = presets.filter((ratio) => {
|
|
83
|
+
if (
|
|
84
|
+
!ratio ||
|
|
85
|
+
typeof ratio !== "object" ||
|
|
86
|
+
!ratio.name ||
|
|
87
|
+
!ratio.ratio
|
|
88
|
+
) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (seen.has(ratio.ratio)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
seen.add(ratio.ratio);
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (presets.length === 0 && defaultAspectRatiosEnabled !== false) {
|
|
99
|
+
presets = getDefaultCoreAspectRatios();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ratioOptions = presets
|
|
103
|
+
.filter(
|
|
104
|
+
(ratio) => ratio.ratio !== "original" && ratio.ratio !== "auto",
|
|
105
|
+
)
|
|
106
|
+
.map((ratio) => ({
|
|
107
|
+
label: ratio.name,
|
|
108
|
+
value: ratio.ratio,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
return [originalOption, ...ratioOptions];
|
|
112
|
+
}, [
|
|
113
|
+
aspectRatiosSetting,
|
|
114
|
+
defaultPresets,
|
|
115
|
+
themePresets,
|
|
116
|
+
defaultAspectRatiosEnabled,
|
|
117
|
+
]);
|
|
118
|
+
}
|