Skip to main content

Version 8 to 9

This migration guide contains migration instructions for:

Required changes

Migration time: ~45min to a couple of hours, depending on project size and components used.

  1. Update composer packages and check if Eightshift Libs are on version 6.4 (or higher)
  2. Update @eightshift/frontend-libs in your package.json file to the latest version:
    "@eightshift/frontend-libs": "^8.0.0",
  3. Important: Delete your lockfile (package.lock) and your node_modules folder, then run npm install
  4. Rename updated components, update changed properties and replace deprecated components (see chapter below)
  5. Do a npm start, check that you have no build errors visible
  6. Smoke test your blocks - to verify everything was migrated properly, go through all the blocks and check if everything looks good visually and that everything is functional

Component updates and replacements

Below you will find some of the more common components that will need to be modified, and also some possible caveats.

Your code editor should mark the components that need replacement with a strikethrough over the component name:

Component deprecation notice in Visual Studio Code

Common between components

  • if you have a label with an IconLabel inside, you can migrate it to separate icon and label props:
    label={<IconLabel icon={icons.color} label={__('Background', 'domain')} />}
    becomes
    icon={icons.color}
    label={__('Background', 'domain')}
  • if the component is the last in the list of options, you can add noBottomSpacing to make everything look a bit nicer
  • if you want to visually group two similar components, e.g. toggles, you can bring them vertically closer together with reducedBottomSpacing

CollapsableComponentUseToggle

  • rename the component and imports to UseToggle
  • change showUseToggle to noUseToggle and flip its logic
  • change showLabel to noLabel and flip its logic
  • change showExpanderButton to noExpandButton and flip its logic
  • consider adding noExpandButton to your attributes and add it to all your blocks, so the options render nicely

ComponentUseToggle

  • replace the component with UseToggle
  • move all of the options inside the useToggle
  • change showUseToggle to noUseToggle and flip its logic
  • change showLabel to noLabel and flip its logic

Example

Before

<ComponentUseToggle
label={label}
checked={accordionUse}
onChange={(value) => setAttributes({ [getAttrKey('accordionUse', attributes, manifest)]: value })}
showUseToggle={showAccordionUse}
showLabel={showLabel}
/>

{accordionUse &&
// Other options.
}

After:

<UseToggle
label={label}
checked={accordionUse}
onChange={(value) => setAttributes({ [getAttrKey('accordionUse', attributes, manifest)]: value })}
noUseToggle={!showAccordionUse} // Inverted logic!
noLabel={!showLabel} // Inverted logic!
noBottomSpacing // If the component is the only one in the options panel
>
// Other options.
</UseToggle>

ColorPaletteCustom

  • rename the component and imports to ColorPalette
  • remove the inline prop if you have it added
  • change the layout prop to a string instead of a ColorPaletteCustomLayout object (hint: use autocomplete to see all the possible values)
  • if you have it set, change groupShades={false} to noShadeGrouping

ColorPickerComponent

  • rename the component and imports to ColorPicker
  • change the type prop to a string instead of a ColorPickerType object (hint: use autocomplete to see all the possible values)
  • if you have it set, change groupShades={false} to noShadeGrouping
  • if you have it set, replace includeWpBottomSpacing={false} with noBottomSpacing

Responsive

  • check and remove all breakpoint labels you had set manually, they're now automatically rendered by the component

CompactResponsive

  • rename the component and imports to Responsive
  • check and remove all breakpoint labels you had set manually, they're now automatically rendered by the component

CustomSelect

  • this component has been split up into 4 more specific components
    • if you had a CustomSelect without multiple and with options, replace it with Select
    • if you had a CustomSelect without multiple and with loadOptions, replace it with MultiSelect
    • if you had a CustomSelect with multiple and with options, replace it with MultiSelect
    • if you had a CustomSelect with multiple and with loadOptions, replace it with AsyncMultiSelect
  • replace isClearable with clearable if you had it set to true
  • replace isSearchable with noSearch if you had it set to false, otherwise remove it
  • remove reFetchOnSearch, as it was removed
  • remove multiple (make sure you add the proper kind of Select!)
  • if you had an async select (with loadOptions), and had simpleValue set, you will need to find a slightly different solution, as this is not supported anymore

LinkEditComponent

  • ⚠️ props have changed here, it'll leave URL pickers broken if you forget to change them!
  • replace setAttributes, urlAttrName, isNewTabAttrName (if set) with an onChange callback (see example below)
  • remove title with label if you want to keep it customized (you can also just remove it)
  • replace showNewTabOption with hideOpensInNewTab and invert its logic
  • you can now hide the anchor notice with hideAnchorNotice

Example

Before

url={buttonUrl}
<LinkEditComponent
url={buttonUrl}
opensInNewTab={buttonIsNewTab}
setAttributes={setAttributes}
title={variableLabel}
urlAttrName={getAttrKey('buttonUrl', attributes, manifest)}
isNewTabAttrName={getAttrKey('buttonIsNewTab', attributes, manifest)}
showNewTabOption={showButtonIsNewTab}
/>

After

<LinkEditComponent
url={buttonUrl}
opensInNewTab={buttonIsNewTab}
hideOpensInNewTab={!showButtonIsNewTab} // Inverted logic!
onChange={({ url, newTab, isAnchor }) => setAttributes({
[getAttrKey('buttonUrl', attributes, manifest)]: url,
[getAttrKey('buttonIsNewTab', attributes, manifest)]: newTab,
[getAttrKey('buttonIsAnchor', attributes, manifest)]: isAnchor ?? false, // Optional, can replace a manual toggle (detects setting anchor links automatically).
})}
/>

SimpleVerticalSingleSelect

  • replace the component with OptionSelector
  • replace the options prop (one that returns an object) with value, onChange callback and available options (array)

Example

Make sure your options have at least a label and a value!

Before

const sizeOptions = getOption('buttonSize', attributes, manifest).map(({ label, value, icon: iconName }) => {
return {
onClick: () => setAttributes({
[getAttrKey('buttonSize', attributes, manifest)]: value,
[getAttrKey('buttonIsLink', attributes, manifest)]: false
}),
label: label,
isActive: buttonSize === value,
icon: icons[iconName],
};
});

// ...

<SimpleVerticalSingleSelect
label={<IconLabel icon={icons.size} label={__('Size', 'domain')} />}
options={sizeOptions}
/>

After

<OptionSelector
icon={icons.size}
label={__('Size', 'domain')}
options={getOption('buttonSize', attributes, manifest)}
value={buttonSize}
onChange={(value) => setAttributes({
[getAttrKey('buttonSize', attributes, manifest)]: value,
[getAttrKey('buttonIsLink', attributes, manifest)]: false
})}
/>

OptionPicker

  • replace with OptionSelector
  • add noBottomSpacing and border='none'
  • add additionalContainerClass='es-p-1.25' to align it properly with other controls
  • remove the label

Consider relocating your toolbar option to the options sidebar.

LinkToolbarButton

  • replace with LinkEditComponent
    • follow the guide for that component for other prop replacements
  • you might need to place it in a

Example

Before

<LinkToolbarButton
urlAttrName={getAttrKey('chevronUrl', attributes, manifest)}
isNewTabAttrName={getAttrKey('chevronIsNewTab', attributes, manifest)}
url={chevronUrl}
opensInNewTab={chevronIsNewTab}
setAttributes={setAttributes}
title={__(ucfirst(componentName), 'domain')}
/>

After

import { ToolbarButton, ToolbarItem, } from '@wordpress/components';
import { PopoverWithTrigger } from '@eightshift/frontend-libs/scripts';

// ...

<ToolbarItem as='div'>
<PopoverWithTrigger
position='top right'
contentClass='es-w-80 es-p-4'
trigger={
({ ref, setIsOpen, isOpen }) => (
<ToolbarButton
ref={ref}
onClick={() => setIsOpen(!isOpen)}
isPressed={chevronUrl?.length > 0}
icon={icons.link}
/>
)
}
>
<LinkEditComponent
url={chevronUrl}
opensInNewTab={chevronIsNewTab}
onChange={({ url, newTab }) => setAttributes({
[getAttrKey('chevronUrl', attributes, manifest)]: url,
[getAttrKey('chevronIsNewTab', attributes, manifest)]: newTab,
})}
hideOpensInNewTab
noBottomSpacing
/>
</PopoverWithTrigger>
</ToolbarItem>

InlineNotification

  • rename the component and imports to Notification
  • change the type prop to a string instead of a InlineNotificationType object (hint: use autocomplete to see all the possible values)
  • replace removeBottomFieldSpacing with noBottomSpacing, if set
  • remove showContrastOutline as it's not supported anymore

SpacingSlider

  • replace with ResponsiveSlider with the config generator
  • remove deprecated props
  • if you had compensateForRemBase10, add
    	modifyInput={(v) => v * 10}
    modifyOutput={(v) => v / 10}

Example

Before

<SpacingSlider
icon={icons.order}
label={__('Order', 'domain')}
attributeName='columnOrder'
attributes={attributes}
setAttributes={setAttributes}
manifest={manifest}
markSteps={2}
hasInputField={false}
hasValueDisplay
valueDisplayFormat={(v) => !isNaN(v) && v > 0 ? v : '-'}
showDisableButton
disableWithUndefined
isNumeric
/>

After

<ResponsiveSlider
{...generateResponsiveSliderConfig({
attributeName: 'columnOrder',
attributes: attributes,
manifest: manifest,
setAttributes: setAttributes,
})}
icon={icons.order}
label={__('Order', 'domain')}
/>

WidthOffsetRangeSlider

  • wrap some of the deprecated options with a config generator

Example

Before

<WidthOffsetRangeSlider
offsetAttributeName='columnOffset'
widthAttributeName='columnWidth'
manifest={manifest}
attributes={attributes}
setAttributes={setAttributes}
showFullWidthToggle={false}
includeGutters
showOffsetAutoToggle
/>

After

<WidthOffsetRangeSlider
{...generateWidthOffsetRangeSliderConfig({
offsetAttributeName: 'columnOffset',
widthAttributeName: 'columnWidth',
attributes: attributes,
manifest: manifest,
setAttributes: (attr) => {
// This is only required if you have "Auto" offset as an option, and the attributes are numeric, otherwise just pass setAttributes as-is.
const newAttr = {};

Object.entries(attr).forEach(([key, value]) => {
if (value !== 'auto' && typeof value !== 'undefined') {
newAttr[key] = parseInt(value);
return;
}

newAttr[key] = value;
});

setAttributes(newAttr);
},
numOfColumns: 14,
showOffsetAutoToggle: true,
numericValues: true,
showFullWidth: false,
})}
/>

VisibilityToggleResponsive

  • replace with ResponsiveToggleButton and use the config generator

Example

Before

<VisibilityToggleResponsive
attributeName='columnHide'
label={__('Visibility', 'redesign')}
manifest={manifest}
attributes={attributes}
setAttributes={setAttributes}
/>

After

<ResponsiveToggleButton
{...generateResponsiveToggleButtonConfig({
attributeName: 'columnHide',
manifest: manifest,
attributes: attributes,
setAttributes: setAttributes,
})}

label={__('Hide', 'domain')}
icon={icons.hide}
/>

SimpleHorizontalSingleSelect

  • rename the component and imports to OptionSelector
  • remove border='offset' if set, that's now the default
  • replace includeWpBottomSpacing={false} with noBottomSpacing

AlignmentToolbar

  • replace with OptionSelector
  • add optionLabels to transform just values into {label, value} (or {label, value, icon}) entries
  • add noBottomSpacing, additionalContainerClass='es-p-1.25', and border='none' so it fits the toolbar better
  • remove label and type

Example

Before

<AlignmentToolbar
value={cardAlign}
options={getOption('cardAlign', attributes, manifest)}
label={sprintf(__('%s content align', 'domain'), manifestTitle)}
onChange={(value) => setAttributes({ [getAttrKey('cardAlign', attributes, manifest)]: value })}
type={AlignmentToolbarType.HORIZONTAL}
/>

After

<OptionSelector
value={cardAlign}
options={getOption('cardAlign', attributes, manifest)}
onChange={(value) => setAttributes({ [getAttrKey('cardAlign', attributes, manifest)]: value })}
optionLabels={getOption('cardAlign', attributes, manifest).map((v) => ({ label: ucfirst(v), icon: icons[`textAlign${ucfirst(v)}`] }))}
additionalContainerClass='es-p-1.25'
noBottomSpacing
border='none'
iconOnly
/>

HeadingLevel

  • replace with OptionSelector
  • change selectedLevel to value
  • add noBottomSpacing, additionalContainerClass='es-p-1.25', and border='none' so it fits the toolbar better
  • add options with all the available heading level options (see After in the example below)
  • if you want the options to look a bit better, you can add additionalButtonClass, just like in the After example below

Example

Before

<HeadingLevel
selectedLevel={typographyLevel}
onChange={(value) => setAttributes({ [getAttrKey('typographyLevel', attributes, manifest)]: value })}
/>

After

<OptionSelector
value={typographyLevel}
onChange={(value) => setAttributes({ [getAttrKey('typographyLevel', attributes, manifest)]: value })}
additionalContainerClass='es-p-1.25'
noBottomSpacing
border='none'
options={[
{ label: 'H1', tooltip: __('Heading 1', 'domain'), value: 1 },
{ label: 'H2', tooltip: __('Heading 2', 'domain'), value: 2 },
{ label: 'H3', tooltip: __('Heading 3', 'domain'), value: 3 },
{ label: 'H4', tooltip: __('Heading 4', 'domain'), value: 4 },
{ label: 'H5', tooltip: __('Heading 5', 'domain'), value: 5 },
{ label: 'H6', tooltip: __('Heading 6', 'domain'), value: 6 },
]}
additionalButtonClass='es-button-square-36 es-text-4 es-font-weight-300'
/>

CustomSlider

  • rename the component and imports to OptionSelector
  • you might want to remove marks if not very specific, as an auto-generator for marks is now included

SimpleRepeater / SimpleRepeaterItem

  • rename the component and imports to Repeater / RepeaterItem