| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var __ = wp.i18n.__; |
| 4 |
var registerBlockType = wp.blocks.registerBlockType; |
| 5 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 6 |
var MediaUpload = wp.blockEditor.MediaUpload; |
| 7 |
var MediaUploadCheck = wp.blockEditor.MediaUploadCheck; |
| 8 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 9 |
var RichText = wp.blockEditor.RichText; |
| 10 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 11 |
var PanelBody = wp.components.PanelBody; |
| 12 |
var ToggleControl = wp.components.ToggleControl; |
| 13 |
var RangeControl = wp.components.RangeControl; |
| 14 |
var SelectControl = wp.components.SelectControl; |
| 15 |
var TextControl = wp.components.TextControl; |
| 16 |
var Button = wp.components.Button; |
| 17 |
var Fragment = wp.element.Fragment; |
| 18 |
|
| 19 |
var _fcaTC, _fcaTV; |
| 20 |
function _tc() { return _fcaTC || (_fcaTC = window.bkbgTypographyControl); } |
| 21 |
function _tv() { return _fcaTV || (_fcaTV = window.bkbgTypoCssVars); } |
| 22 |
|
| 23 |
registerBlockType('blockenberg/filterable-cards', { |
| 24 |
edit: function (props) { |
| 25 |
var attr = props.attributes; |
| 26 |
var set = props.setAttributes; |
| 27 |
var cards = attr.cards || []; |
| 28 |
|
| 29 |
function updateCard(idx, field, val) { |
| 30 |
var updated = cards.map(function (c, i) { |
| 31 |
if (i !== idx) return c; |
| 32 |
var patch = {}; patch[field] = val; |
| 33 |
return Object.assign({}, c, patch); |
| 34 |
}); |
| 35 |
set({ cards: updated }); |
| 36 |
} |
| 37 |
|
| 38 |
function addCard() { |
| 39 |
set({ |
| 40 |
cards: cards.concat([{ |
| 41 |
title: 'New Card', description: 'Card description.', |
| 42 |
category: 'General', imageUrl: '', imageId: 0, imageAlt: '', |
| 43 |
link: '', linkLabel: 'Learn More', label: '', icon: '', featured: false |
| 44 |
}]) |
| 45 |
}); |
| 46 |
} |
| 47 |
|
| 48 |
function duplicateCard(idx) { |
| 49 |
var arr = cards.slice(); |
| 50 |
arr.splice(idx + 1, 0, Object.assign({}, cards[idx])); |
| 51 |
set({ cards: arr }); |
| 52 |
} |
| 53 |
|
| 54 |
function removeCard(idx) { |
| 55 |
set({ cards: cards.filter(function (_, i) { return i !== idx; }) }); |
| 56 |
} |
| 57 |
|
| 58 |
/* unique categories */ |
| 59 |
var cats = [attr.allLabel || 'All']; |
| 60 |
cards.forEach(function (c) { if (c.category && cats.indexOf(c.category) < 0) cats.push(c.category); }); |
| 61 |
|
| 62 |
var ratioMap = { '16:9': '56.25%', '4:3': '75%', '3:2': '66.66%', '1:1': '100%' }; |
| 63 |
var paddingTop = ratioMap[attr.imageRatio] || '66.66%'; |
| 64 |
|
| 65 |
/* Inspector */ |
| 66 |
var inspector = el(InspectorControls, {}, |
| 67 |
el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true }, |
| 68 |
el(RangeControl, { |
| 69 |
label: __('Columns', 'blockenberg'), |
| 70 |
value: attr.columns, min: 1, max: 5, |
| 71 |
onChange: function (v) { set({ columns: v }); }, |
| 72 |
__nextHasNoMarginBottom: true |
| 73 |
}), |
| 74 |
el(SelectControl, { |
| 75 |
label: __('Layout', 'blockenberg'), |
| 76 |
value: attr.layout, |
| 77 |
options: [ |
| 78 |
{ label: 'Grid', value: 'grid' }, |
| 79 |
{ label: 'List', value: 'list' }, |
| 80 |
{ label: 'Masonry', value: 'masonry' } |
| 81 |
], |
| 82 |
onChange: function (v) { set({ layout: v }); }, |
| 83 |
__nextHasNoMarginBottom: true |
| 84 |
}), |
| 85 |
el(SelectControl, { |
| 86 |
label: __('Filter Style', 'blockenberg'), |
| 87 |
value: attr.filterStyle, |
| 88 |
options: [ |
| 89 |
{ label: 'Buttons', value: 'buttons' }, |
| 90 |
{ label: 'Tabs', value: 'tabs' }, |
| 91 |
{ label: 'Dropdown', value: 'dropdown' } |
| 92 |
], |
| 93 |
onChange: function (v) { set({ filterStyle: v }); }, |
| 94 |
__nextHasNoMarginBottom: true |
| 95 |
}), |
| 96 |
el(TextControl, { |
| 97 |
label: __('\"All\" Label', 'blockenberg'), |
| 98 |
value: attr.allLabel, |
| 99 |
onChange: function (v) { set({ allLabel: v }); }, |
| 100 |
__nextHasNoMarginBottom: true |
| 101 |
}), |
| 102 |
el(SelectControl, { |
| 103 |
label: __('Image Ratio', 'blockenberg'), |
| 104 |
value: attr.imageRatio, |
| 105 |
options: [ |
| 106 |
{ label: '16:9', value: '16:9' }, |
| 107 |
{ label: '4:3', value: '4:3' }, |
| 108 |
{ label: '3:2', value: '3:2' }, |
| 109 |
{ label: '1:1 Square', value: '1:1' } |
| 110 |
], |
| 111 |
onChange: function (v) { set({ imageRatio: v }); }, |
| 112 |
__nextHasNoMarginBottom: true |
| 113 |
}), |
| 114 |
el(RangeControl, { |
| 115 |
label: __('Card Radius (px)', 'blockenberg'), |
| 116 |
value: attr.cardRadius, min: 0, max: 40, |
| 117 |
onChange: function (v) { set({ cardRadius: v }); }, |
| 118 |
__nextHasNoMarginBottom: true |
| 119 |
}), |
| 120 |
el(RangeControl, { |
| 121 |
label: __('Gap (px)', 'blockenberg'), |
| 122 |
value: attr.gap, min: 8, max: 80, |
| 123 |
onChange: function (v) { set({ gap: v }); }, |
| 124 |
__nextHasNoMarginBottom: true |
| 125 |
}), |
| 126 |
el(RangeControl, { |
| 127 |
label: __('Padding Top (px)', 'blockenberg'), |
| 128 |
value: attr.paddingTop, min: 0, max: 200, |
| 129 |
onChange: function (v) { set({ paddingTop: v }); }, |
| 130 |
__nextHasNoMarginBottom: true |
| 131 |
}), |
| 132 |
el(RangeControl, { |
| 133 |
label: __('Padding Bottom (px)', 'blockenberg'), |
| 134 |
value: attr.paddingBottom, min: 0, max: 200, |
| 135 |
onChange: function (v) { set({ paddingBottom: v }); }, |
| 136 |
__nextHasNoMarginBottom: true |
| 137 |
}) |
| 138 |
), |
| 139 |
el(PanelBody, { title: __('Visibility', 'blockenberg'), initialOpen: false }, |
| 140 |
el(ToggleControl, { label: __('Show Images', 'blockenberg'), checked: attr.showImages, onChange: function (v) { set({ showImages: v }); }, __nextHasNoMarginBottom: true }), |
| 141 |
el(ToggleControl, { label: __('Show Descriptions', 'blockenberg'), checked: attr.showDescriptions, onChange: function (v) { set({ showDescriptions: v }); }, __nextHasNoMarginBottom: true }), |
| 142 |
el(ToggleControl, { label: __('Show Labels', 'blockenberg'), checked: attr.showLabels, onChange: function (v) { set({ showLabels: v }); }, __nextHasNoMarginBottom: true }), |
| 143 |
el(ToggleControl, { label: __('Show Links', 'blockenberg'), checked: attr.showLinks, onChange: function (v) { set({ showLinks: v }); }, __nextHasNoMarginBottom: true }), |
| 144 |
el(ToggleControl, { label: __('Animate on Filter', 'blockenberg'), checked: attr.animateFilter, onChange: function (v) { set({ animateFilter: v }); }, __nextHasNoMarginBottom: true }) |
| 145 |
), |
| 146 |
el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false }, |
| 147 |
_tc() && _tc()({ label: __('Title', 'blockenberg'), typo: attr.typoTitle, onChange: function (v) { set({ typoTitle: v }); } }), |
| 148 |
_tc() && _tc()({ label: __('Description', 'blockenberg'), typo: attr.typoDesc, onChange: function (v) { set({ typoDesc: v }); } }), |
| 149 |
_tc() && _tc()({ label: __('Filter', 'blockenberg'), typo: attr.typoFilter, onChange: function (v) { set({ typoFilter: v }); } }) |
| 150 |
), |
| 151 |
el(PanelColorSettings, { |
| 152 |
title: __('Colors', 'blockenberg'), initialOpen: false, |
| 153 |
colorSettings: [ |
| 154 |
{ value: attr.bgColor, onChange: function (v) { set({ bgColor: v || '' }); }, label: __('Section Background', 'blockenberg') }, |
| 155 |
{ value: attr.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); }, label: __('Card Background', 'blockenberg') }, |
| 156 |
{ value: attr.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e5e7eb' }); }, label: __('Card Border', 'blockenberg') }, |
| 157 |
{ value: attr.titleColor, onChange: function (v) { set({ titleColor: v || '#111827' }); }, label: __('Title', 'blockenberg') }, |
| 158 |
{ value: attr.descColor, onChange: function (v) { set({ descColor: v || '#6b7280' }); }, label: __('Description', 'blockenberg') }, |
| 159 |
{ value: attr.labelBg, onChange: function (v) { set({ labelBg: v || '#ede9fe' }); }, label: __('Label Background', 'blockenberg') }, |
| 160 |
{ value: attr.labelColor, onChange: function (v) { set({ labelColor: v || '#6366f1' }); }, label: __('Label Text', 'blockenberg') }, |
| 161 |
{ value: attr.linkColor, onChange: function (v) { set({ linkColor: v || '#6366f1' }); }, label: __('Link', 'blockenberg') }, |
| 162 |
{ value: attr.filterBtnBg, onChange: function (v) { set({ filterBtnBg: v || '#f3f4f6' }); }, label: __('Filter Button BG', 'blockenberg') }, |
| 163 |
{ value: attr.filterBtnColor, onChange: function (v) { set({ filterBtnColor: v || '#374151' }); }, label: __('Filter Button Text', 'blockenberg') }, |
| 164 |
{ value: attr.filterBtnActiveBg, onChange: function (v) { set({ filterBtnActiveBg: v || '#6366f1' }); }, label: __('Active Filter BG', 'blockenberg') }, |
| 165 |
{ value: attr.filterBtnActiveColor, onChange: function (v) { set({ filterBtnActiveColor: v || '#ffffff' }); }, label: __('Active Filter Text', 'blockenberg') }, |
| 166 |
{ value: attr.accentColor, onChange: function (v) { set({ accentColor: v || '#6366f1' }); }, label: __('Accent', 'blockenberg') } |
| 167 |
] |
| 168 |
}) |
| 169 |
); |
| 170 |
|
| 171 |
/* Filter bar preview */ |
| 172 |
var filterBar = el('div', { className: 'bkbg-fc-filter bkbg-fc-filter--' + attr.filterStyle, style: { marginBottom: '24px' } }, |
| 173 |
attr.filterStyle === 'dropdown' |
| 174 |
? el('select', { className: 'bkbg-fc-dropdown', style: { background: attr.filterBtnBg, color: attr.filterBtnColor, border: '1px solid ' + attr.cardBorder, borderRadius: '6px', padding: '8px 14px' } }, |
| 175 |
cats.map(function (cat, i) { return el('option', { key: i }, cat); }) |
| 176 |
) |
| 177 |
: el('div', { className: 'bkbg-fc-btns', style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } }, |
| 178 |
cats.map(function (cat, i) { |
| 179 |
var isAll = i === 0; |
| 180 |
return el('span', { |
| 181 |
key: i, className: 'bkbg-fc-btn' + (isAll ? ' active' : ''), |
| 182 |
style: { |
| 183 |
background: isAll ? attr.filterBtnActiveBg : attr.filterBtnBg, |
| 184 |
color: isAll ? attr.filterBtnActiveColor : attr.filterBtnColor, |
| 185 |
padding: '7px 16px', borderRadius: attr.filterStyle === 'tabs' ? '6px 6px 0 0' : '999px', |
| 186 |
cursor: 'pointer', |
| 187 |
border: attr.filterStyle === 'tabs' ? ('1px solid ' + attr.cardBorder) : 'none' |
| 188 |
} |
| 189 |
}, cat); |
| 190 |
}) |
| 191 |
) |
| 192 |
); |
| 193 |
|
| 194 |
/* Cards grid */ |
| 195 |
var gridStyle = { |
| 196 |
display: 'grid', |
| 197 |
gridTemplateColumns: 'repeat(' + attr.columns + ', 1fr)', |
| 198 |
gap: attr.gap + 'px' |
| 199 |
}; |
| 200 |
if (attr.layout === 'list') { |
| 201 |
gridStyle.gridTemplateColumns = '1fr'; |
| 202 |
} |
| 203 |
|
| 204 |
var cardsGrid = el('div', { className: 'bkbg-fc-grid', style: gridStyle }, |
| 205 |
cards.map(function (card, idx) { |
| 206 |
return el('div', { |
| 207 |
key: idx, |
| 208 |
className: 'bkbg-fc-card-wrap', |
| 209 |
style: { |
| 210 |
background: attr.cardBg, |
| 211 |
border: '1px solid ' + (card.featured ? attr.accentColor : attr.cardBorder), |
| 212 |
borderRadius: attr.cardRadius + 'px', |
| 213 |
overflow: 'hidden', |
| 214 |
position: 'relative', |
| 215 |
display: attr.layout === 'list' ? 'flex' : 'block' |
| 216 |
} |
| 217 |
}, |
| 218 |
card.featured && el('div', { style: { position: 'absolute', top: 0, left: 0, right: 0, height: '3px', background: attr.accentColor } }), |
| 219 |
/* Image */ |
| 220 |
attr.showImages && el('div', { |
| 221 |
style: { position: 'relative', paddingTop: paddingTop, background: '#f3f4f6', overflow: 'hidden', flexShrink: 0, width: attr.layout === 'list' ? '200px' : 'auto' } |
| 222 |
}, |
| 223 |
card.imageUrl |
| 224 |
? el('img', { src: card.imageUrl, alt: card.imageAlt || '', style: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', objectFit: 'cover' } }) |
| 225 |
: el('div', { style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#9ca3af', fontSize: '12px' } }, __('No image', 'blockenberg')), |
| 226 |
el(MediaUploadCheck, {}, |
| 227 |
el(MediaUpload, { |
| 228 |
onSelect: function (m) { updateCard(idx, 'imageUrl', m.url); updateCard(idx, 'imageId', m.id); updateCard(idx, 'imageAlt', m.alt || ''); }, |
| 229 |
allowedTypes: ['image'], value: card.imageId, |
| 230 |
render: function (ref) { |
| 231 |
return el(Button, { onClick: ref.open, variant: 'primary', isSmall: true, style: { position: 'absolute', bottom: '6px', right: '6px', zIndex: 1, fontSize: '11px' } }, card.imageUrl ? '↕' : '+'); |
| 232 |
} |
| 233 |
}) |
| 234 |
) |
| 235 |
), |
| 236 |
/* Body */ |
| 237 |
el('div', { style: { padding: '16px', flex: 1 } }, |
| 238 |
el('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' } }, |
| 239 |
el(TextControl, { |
| 240 |
label: __('Category', 'blockenberg'), |
| 241 |
value: card.category, |
| 242 |
onChange: function (v) { updateCard(idx, 'category', v); }, |
| 243 |
placeholder: 'Category', |
| 244 |
style: { flex: 1, marginRight: '8px' }, |
| 245 |
__nextHasNoMarginBottom: true |
| 246 |
}), |
| 247 |
attr.showLabels && el(TextControl, { |
| 248 |
label: __('Label', 'blockenberg'), |
| 249 |
value: card.label, |
| 250 |
onChange: function (v) { updateCard(idx, 'label', v); }, |
| 251 |
placeholder: 'Label', |
| 252 |
style: { width: '80px' }, |
| 253 |
__nextHasNoMarginBottom: true |
| 254 |
}) |
| 255 |
), |
| 256 |
el(RichText, { |
| 257 |
tagName: 'h3', |
| 258 |
className: 'bkbg-fc-card-title', |
| 259 |
value: card.title, |
| 260 |
onChange: function (v) { updateCard(idx, 'title', v); }, |
| 261 |
placeholder: __('Card title', 'blockenberg'), |
| 262 |
style: { color: attr.titleColor, margin: '0 0 6px' } |
| 263 |
}), |
| 264 |
attr.showDescriptions && el(RichText, { |
| 265 |
tagName: 'p', |
| 266 |
className: 'bkbg-fc-card-desc', |
| 267 |
value: card.description, |
| 268 |
onChange: function (v) { updateCard(idx, 'description', v); }, |
| 269 |
placeholder: __('Description...', 'blockenberg'), |
| 270 |
style: { color: attr.descColor, margin: '0 0 10px' } |
| 271 |
}), |
| 272 |
attr.showLinks && el('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, |
| 273 |
el(TextControl, { |
| 274 |
label: __('Link URL', 'blockenberg'), |
| 275 |
value: card.link, |
| 276 |
onChange: function (v) { updateCard(idx, 'link', v); }, |
| 277 |
placeholder: 'https://', |
| 278 |
style: { flex: 1 }, |
| 279 |
__nextHasNoMarginBottom: true |
| 280 |
}), |
| 281 |
el(TextControl, { |
| 282 |
label: __('Link Label', 'blockenberg'), |
| 283 |
value: card.linkLabel, |
| 284 |
onChange: function (v) { updateCard(idx, 'linkLabel', v); }, |
| 285 |
style: { width: '100px' }, |
| 286 |
__nextHasNoMarginBottom: true |
| 287 |
}) |
| 288 |
), |
| 289 |
el(ToggleControl, { |
| 290 |
label: __('Featured', 'blockenberg'), |
| 291 |
checked: card.featured, |
| 292 |
onChange: function (v) { updateCard(idx, 'featured', v); }, |
| 293 |
__nextHasNoMarginBottom: true |
| 294 |
}), |
| 295 |
el('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } }, |
| 296 |
el(Button, { onClick: function () { duplicateCard(idx); }, variant: 'secondary', isSmall: true }, __('Duplicate', 'blockenberg')), |
| 297 |
el(Button, { onClick: function () { removeCard(idx); }, variant: 'link', isDestructive: true, isSmall: true }, __('Remove', 'blockenberg')) |
| 298 |
) |
| 299 |
) |
| 300 |
); |
| 301 |
}), |
| 302 |
el('div', { style: { gridColumn: '1 / -1', textAlign: 'center', paddingTop: '8px' } }, |
| 303 |
el(Button, { onClick: addCard, variant: 'primary', isSmall: true }, __('+ Add Card', 'blockenberg')) |
| 304 |
) |
| 305 |
); |
| 306 |
|
| 307 |
var wrapStyle = Object.assign( |
| 308 |
{ background: attr.bgColor || '', paddingTop: attr.paddingTop + 'px', paddingBottom: attr.paddingBottom + 'px' }, |
| 309 |
_tv()(attr.typoTitle, '--bkbg-fcards-tt-'), |
| 310 |
_tv()(attr.typoDesc, '--bkbg-fcards-td-'), |
| 311 |
_tv()(attr.typoFilter, '--bkbg-fcards-tf-') |
| 312 |
); |
| 313 |
|
| 314 |
return el(Fragment, {}, |
| 315 |
inspector, |
| 316 |
el('div', useBlockProps({ className: 'bkbg-fcards-wrap', style: wrapStyle }), |
| 317 |
filterBar, |
| 318 |
cardsGrid |
| 319 |
) |
| 320 |
); |
| 321 |
}, |
| 322 |
|
| 323 |
save: function (props) { |
| 324 |
var attr = props.attributes; |
| 325 |
return el('div', useBlockProps.save(), |
| 326 |
el('div', { className: 'bkbg-fc-app', 'data-opts': JSON.stringify(attr) }) |
| 327 |
); |
| 328 |
} |
| 329 |
}); |
| 330 |
}() ); |
| 331 |
|