✨feature: Initial commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Block asset metadata.
|
||||
*
|
||||
* @package LogoSoup
|
||||
*/
|
||||
|
||||
return array(
|
||||
'dependencies' => array(
|
||||
'wp-block-editor',
|
||||
'wp-blocks',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
),
|
||||
'version' => '0.1.0',
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
.logo-soup-editor {
|
||||
border: 1px dashed #a7aaad;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__list {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__card {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__thumb {
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #f8f8f8, #ececec);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
height: 92px;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__thumb img {
|
||||
max-height: var(--logo-soup-base-size, 52px);
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo-soup-editor__preview.crop-to-content .logo-soup-editor__thumb {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.logo-soup-editor__preview.align-by-visual-center .logo-soup-editor__thumb img {
|
||||
transform: translate(-2%, -2%);
|
||||
}
|
||||
|
||||
.logo-soup-editor__preview.align-by-visual-center-x .logo-soup-editor__thumb img {
|
||||
transform: translateX(-2%);
|
||||
}
|
||||
|
||||
.logo-soup-editor__preview.align-by-visual-center-y .logo-soup-editor__thumb img {
|
||||
transform: translateY(-2%);
|
||||
}
|
||||
|
||||
.logo-soup-editor__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
(function (blocks, blockEditor, components, element, i18n) {
|
||||
const registerBlockType = blocks.registerBlockType;
|
||||
const InspectorControls = blockEditor.InspectorControls;
|
||||
const MediaUpload = blockEditor.MediaUpload;
|
||||
const MediaUploadCheck = blockEditor.MediaUploadCheck;
|
||||
const useBlockProps = blockEditor.useBlockProps;
|
||||
const Button = components.Button;
|
||||
const PanelBody = components.PanelBody;
|
||||
const Placeholder = components.Placeholder;
|
||||
const RangeControl = components.RangeControl;
|
||||
const SelectControl = components.SelectControl;
|
||||
const TextControl = components.TextControl;
|
||||
const ToggleControl = components.ToggleControl;
|
||||
const Fragment = element.Fragment;
|
||||
const el = element.createElement;
|
||||
const __ = i18n.__;
|
||||
|
||||
function updateLogoAtIndex(logos, index, nextLogo) {
|
||||
return logos.map(function (logo, logoIndex) {
|
||||
return logoIndex === index ? nextLogo : logo;
|
||||
});
|
||||
}
|
||||
|
||||
registerBlockType('logo-soup/showcase', {
|
||||
edit: function (props) {
|
||||
const attributes = props.attributes;
|
||||
const setAttributes = props.setAttributes;
|
||||
const logos = attributes.logos || [];
|
||||
const layout = attributes.layout || 'carousel';
|
||||
const grayscale =
|
||||
typeof attributes.grayscale === 'boolean' ? attributes.grayscale : true;
|
||||
const speed = attributes.speed || 22;
|
||||
const baseSize = attributes.baseSize || 48;
|
||||
const scaleFactor =
|
||||
typeof attributes.scaleFactor === 'number'
|
||||
? attributes.scaleFactor
|
||||
: 0.5;
|
||||
const densityAware =
|
||||
typeof attributes.densityAware === 'boolean'
|
||||
? attributes.densityAware
|
||||
: true;
|
||||
const densityFactor =
|
||||
typeof attributes.densityFactor === 'number'
|
||||
? attributes.densityFactor
|
||||
: 0.5;
|
||||
const cropToContent =
|
||||
typeof attributes.cropToContent === 'boolean'
|
||||
? attributes.cropToContent
|
||||
: false;
|
||||
const alignBy = attributes.alignBy || 'bounds';
|
||||
const pauseOnHover =
|
||||
typeof attributes.pauseOnHover === 'boolean'
|
||||
? attributes.pauseOnHover
|
||||
: true;
|
||||
const blockProps = useBlockProps({ className: 'logo-soup-editor' });
|
||||
|
||||
function addLogo(media) {
|
||||
if (!media || !media.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAttributes({
|
||||
logos: logos.concat({
|
||||
id: media.id || 0,
|
||||
url: media.url,
|
||||
alt: media.alt || media.title || '',
|
||||
link: '',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function removeLogo(index) {
|
||||
setAttributes({
|
||||
logos: logos.filter(function (_, logoIndex) {
|
||||
return logoIndex !== index;
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function moveLogo(index, direction) {
|
||||
const nextIndex = index + direction;
|
||||
const nextLogos = logos.slice();
|
||||
const currentLogo = nextLogos[index];
|
||||
|
||||
if (nextIndex < 0 || nextIndex >= logos.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextLogos[index] = nextLogos[nextIndex];
|
||||
nextLogos[nextIndex] = currentLogo;
|
||||
setAttributes({ logos: nextLogos });
|
||||
}
|
||||
|
||||
function renderMediaButton(label) {
|
||||
return el(MediaUploadCheck, null, el(MediaUpload, {
|
||||
onSelect: addLogo,
|
||||
allowedTypes: ['image'],
|
||||
render: function (mediaProps) {
|
||||
return el(
|
||||
Button,
|
||||
{
|
||||
variant: 'primary',
|
||||
onClick: mediaProps.open,
|
||||
},
|
||||
label
|
||||
);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function renderLogoCard(logo, index) {
|
||||
const cardKey = String(logo.id || 'logo') + '-' + String(index);
|
||||
|
||||
return el(
|
||||
'div',
|
||||
{
|
||||
className: 'logo-soup-editor__card',
|
||||
key: cardKey,
|
||||
},
|
||||
[
|
||||
el(
|
||||
'div',
|
||||
{
|
||||
className: 'logo-soup-editor__thumb',
|
||||
key: 'thumb',
|
||||
},
|
||||
el('img', {
|
||||
src: logo.url,
|
||||
alt: logo.alt || '',
|
||||
})
|
||||
),
|
||||
el(TextControl, {
|
||||
key: 'alt',
|
||||
label: __('Alt text', 'logo-soup'),
|
||||
value: logo.alt || '',
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
logos: updateLogoAtIndex(logos, index, {
|
||||
id: logo.id,
|
||||
url: logo.url,
|
||||
alt: value,
|
||||
link: logo.link,
|
||||
}),
|
||||
});
|
||||
},
|
||||
}),
|
||||
el(TextControl, {
|
||||
key: 'link',
|
||||
label: __('Link URL', 'logo-soup'),
|
||||
value: logo.link || '',
|
||||
placeholder: 'https://example.com',
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
logos: updateLogoAtIndex(logos, index, {
|
||||
id: logo.id,
|
||||
url: logo.url,
|
||||
alt: logo.alt,
|
||||
link: value,
|
||||
}),
|
||||
});
|
||||
},
|
||||
}),
|
||||
el(
|
||||
'div',
|
||||
{
|
||||
className: 'logo-soup-editor__actions',
|
||||
key: 'actions',
|
||||
},
|
||||
[
|
||||
el(
|
||||
Button,
|
||||
{
|
||||
key: 'up',
|
||||
disabled: index === 0,
|
||||
onClick: function () {
|
||||
moveLogo(index, -1);
|
||||
},
|
||||
},
|
||||
__('Move Up', 'logo-soup')
|
||||
),
|
||||
el(
|
||||
Button,
|
||||
{
|
||||
key: 'down',
|
||||
disabled: index === logos.length - 1,
|
||||
onClick: function () {
|
||||
moveLogo(index, 1);
|
||||
},
|
||||
},
|
||||
__('Move Down', 'logo-soup')
|
||||
),
|
||||
el(
|
||||
Button,
|
||||
{
|
||||
key: 'remove',
|
||||
isDestructive: true,
|
||||
onClick: function () {
|
||||
removeLogo(index);
|
||||
},
|
||||
},
|
||||
__('Remove', 'logo-soup')
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return el(Fragment, null, [
|
||||
el(
|
||||
InspectorControls,
|
||||
{
|
||||
key: 'controls',
|
||||
},
|
||||
el(
|
||||
PanelBody,
|
||||
{
|
||||
title: __('Display', 'logo-soup'),
|
||||
},
|
||||
[
|
||||
el(SelectControl, {
|
||||
key: 'layout',
|
||||
label: __('Layout', 'logo-soup'),
|
||||
value: layout,
|
||||
options: [
|
||||
{ label: __('Carousel', 'logo-soup'), value: 'carousel' },
|
||||
{ label: __('Grid', 'logo-soup'), value: 'grid' },
|
||||
],
|
||||
onChange: function (value) {
|
||||
setAttributes({ layout: value });
|
||||
},
|
||||
}),
|
||||
el(ToggleControl, {
|
||||
key: 'grayscale',
|
||||
label: __('Use grayscale styling', 'logo-soup'),
|
||||
checked: grayscale,
|
||||
onChange: function (value) {
|
||||
setAttributes({ grayscale: value });
|
||||
},
|
||||
}),
|
||||
el(ToggleControl, {
|
||||
key: 'pauseOnHover',
|
||||
label: __('Pause carousel on hover', 'logo-soup'),
|
||||
checked: pauseOnHover,
|
||||
help: __('Applies to carousel layout only.', 'logo-soup'),
|
||||
onChange: function (value) {
|
||||
setAttributes({ pauseOnHover: value });
|
||||
},
|
||||
}),
|
||||
el(RangeControl, {
|
||||
key: 'speed',
|
||||
label: __('Carousel speed (seconds)', 'logo-soup'),
|
||||
value: speed,
|
||||
min: 8,
|
||||
max: 80,
|
||||
onChange: function (value) {
|
||||
setAttributes({ speed: value });
|
||||
},
|
||||
}),
|
||||
el(RangeControl, {
|
||||
key: 'baseSize',
|
||||
label: __('Base size', 'logo-soup'),
|
||||
value: baseSize,
|
||||
min: 16,
|
||||
max: 160,
|
||||
onChange: function (value) {
|
||||
setAttributes({ baseSize: value });
|
||||
},
|
||||
}),
|
||||
el(RangeControl, {
|
||||
key: 'scaleFactor',
|
||||
label: __('Scale factor', 'logo-soup'),
|
||||
value: scaleFactor,
|
||||
min: 0.1,
|
||||
max: 2,
|
||||
step: 0.1,
|
||||
onChange: function (value) {
|
||||
setAttributes({ scaleFactor: value });
|
||||
},
|
||||
}),
|
||||
el(ToggleControl, {
|
||||
key: 'densityAware',
|
||||
label: __('Density aware', 'logo-soup'),
|
||||
checked: densityAware,
|
||||
onChange: function (value) {
|
||||
setAttributes({ densityAware: value });
|
||||
},
|
||||
}),
|
||||
el(RangeControl, {
|
||||
key: 'densityFactor',
|
||||
label: __('Density factor', 'logo-soup'),
|
||||
value: densityFactor,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
onChange: function (value) {
|
||||
setAttributes({ densityFactor: value });
|
||||
},
|
||||
}),
|
||||
el(ToggleControl, {
|
||||
key: 'cropToContent',
|
||||
label: __('Crop to content', 'logo-soup'),
|
||||
checked: cropToContent,
|
||||
onChange: function (value) {
|
||||
setAttributes({ cropToContent: value });
|
||||
},
|
||||
}),
|
||||
el(SelectControl, {
|
||||
key: 'alignBy',
|
||||
label: __('Align by', 'logo-soup'),
|
||||
value: alignBy,
|
||||
options: [
|
||||
{ label: __('Bounds', 'logo-soup'), value: 'bounds' },
|
||||
{
|
||||
label: __('Visual center', 'logo-soup'),
|
||||
value: 'visual-center',
|
||||
},
|
||||
{
|
||||
label: __('Visual center X', 'logo-soup'),
|
||||
value: 'visual-center-x',
|
||||
},
|
||||
{
|
||||
label: __('Visual center Y', 'logo-soup'),
|
||||
value: 'visual-center-y',
|
||||
},
|
||||
],
|
||||
onChange: function (value) {
|
||||
setAttributes({ alignBy: value });
|
||||
},
|
||||
}),
|
||||
]
|
||||
)
|
||||
),
|
||||
el(
|
||||
'div',
|
||||
blockProps,
|
||||
logos.length === 0
|
||||
? el(
|
||||
Placeholder,
|
||||
{
|
||||
label: __('Logo Soup', 'logo-soup'),
|
||||
instructions: __(
|
||||
'Upload a set of logos and optional links.',
|
||||
'logo-soup'
|
||||
),
|
||||
},
|
||||
renderMediaButton(__('Add Logo', 'logo-soup'))
|
||||
)
|
||||
: el(
|
||||
'div',
|
||||
{
|
||||
className:
|
||||
'logo-soup-editor__preview layout-' +
|
||||
layout +
|
||||
(grayscale ? ' is-grayscale' : '') +
|
||||
(cropToContent ? ' crop-to-content' : '') +
|
||||
(densityAware ? ' is-density-aware' : '') +
|
||||
' align-by-' +
|
||||
alignBy,
|
||||
style: {
|
||||
'--logo-soup-base-size': String(baseSize) + 'px',
|
||||
'--logo-soup-scale-factor': String(scaleFactor),
|
||||
'--logo-soup-density-factor': String(densityFactor),
|
||||
},
|
||||
},
|
||||
[
|
||||
el(
|
||||
'div',
|
||||
{
|
||||
className: 'logo-soup-editor__toolbar',
|
||||
key: 'toolbar',
|
||||
},
|
||||
renderMediaButton(__('Add Another Logo', 'logo-soup'))
|
||||
),
|
||||
el(
|
||||
'div',
|
||||
{
|
||||
className: 'logo-soup-editor__list',
|
||||
key: 'list',
|
||||
},
|
||||
logos.map(renderLogoCard)
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
]);
|
||||
},
|
||||
save: function () {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}(window.wp.blocks, window.wp.blockEditor, window.wp.components, window.wp.element, window.wp.i18n));
|
||||
@@ -0,0 +1,116 @@
|
||||
.logo-soup {
|
||||
--logo-soup-gap: clamp(1rem, 2vw, 2rem);
|
||||
--logo-soup-base-size: 48px;
|
||||
--logo-soup-effective-size: var(--logo-soup-base-size);
|
||||
--logo-soup-gap-factor: 1;
|
||||
--logo-soup-width-factor: 3.5;
|
||||
--logo-soup-gray-opacity: 0.72;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo-soup__track {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: calc(var(--logo-soup-gap) * var(--logo-soup-gap-factor));
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.logo-soup.layout-carousel .logo-soup__track {
|
||||
animation: logo-soup-scroll var(--logo-soup-speed, 22s) linear infinite;
|
||||
}
|
||||
|
||||
.logo-soup.layout-grid .logo-soup__track {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(min(100%, calc(var(--logo-soup-effective-size) * 2.4)), 1fr)
|
||||
);
|
||||
justify-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo-soup.pause-on-hover:hover .logo-soup__track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.logo-soup__item {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
min-height: calc(var(--logo-soup-effective-size) + 1.5rem);
|
||||
padding: 0.75rem 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo-soup.layout-grid .logo-soup__item {
|
||||
padding-inline: calc(var(--logo-soup-gap) * 0.25);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo-soup__image {
|
||||
display: block;
|
||||
height: var(--logo-soup-effective-size);
|
||||
max-width: min(calc(var(--logo-soup-effective-size) * var(--logo-soup-width-factor)), 22vw);
|
||||
object-fit: contain;
|
||||
opacity: 0.92;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: filter 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.logo-soup.layout-grid .logo-soup__image {
|
||||
margin-inline: auto;
|
||||
max-width: min(100%, calc(var(--logo-soup-effective-size) * var(--logo-soup-width-factor)));
|
||||
}
|
||||
|
||||
.logo-soup.crop-to-content .logo-soup__item {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.logo-soup.align-by-visual-center .logo-soup__image {
|
||||
transform: translate(-2%, -2%);
|
||||
}
|
||||
|
||||
.logo-soup.align-by-visual-center-x .logo-soup__image {
|
||||
transform: translateX(-2%);
|
||||
}
|
||||
|
||||
.logo-soup.align-by-visual-center-y .logo-soup__image {
|
||||
transform: translateY(-2%);
|
||||
}
|
||||
|
||||
.logo-soup.is-grayscale .logo-soup__image {
|
||||
filter: grayscale(100%);
|
||||
opacity: var(--logo-soup-gray-opacity);
|
||||
}
|
||||
|
||||
.logo-soup__item:hover .logo-soup__image,
|
||||
.logo-soup__item:focus .logo-soup__image {
|
||||
filter: grayscale(0%);
|
||||
opacity: 1;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@keyframes logo-soup-scroll {
|
||||
from {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(calc(-50% - (var(--logo-soup-gap) / 2)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 781px) {
|
||||
.logo-soup__item {
|
||||
min-height: calc(var(--logo-soup-effective-size) + 1rem);
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.logo-soup__image {
|
||||
max-width: min(calc(var(--logo-soup-effective-size) * 2.5), 34vw);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user