Unterschiedliche Farbpaletten mit Gutenberg erstellen

Seit Freitag arbeite ich an meinem ersten Gutenberg-Projekt und hatte gleich das erste langwierige Problem. In dem Projekt gibt es eine Farbpalette für Textfarben und eine für mögliche Hintergrundfarben. In Gutenberg ist standardmäßig nur eine Farbpalette vorgesehen, die sich zwar problemlos anpassen lässt, aber für das Projekt sollen bei dem Farbwähler für die Textfarbe halt nicht dieselben wie bei der Hintergrundfarbe angezeigt werden. Hier zeige ich, wie sich das umsetzen lässt.

Update vom 11. Juli 2018: Damit die Markierung der gewählten Farbe nach dem Neuladen des Editors funktioniert, müssen im letzten Code-Block noch die beiden Farb-Attribute als attributes angegeben werden.

Die Zielsetzung

Ziel war, quasi die Farbwähler nachzubauen, die bei dem Absatz-Block vorhanden sind:

Screenshot der beiden Farbwähler-Komponenten für Text- und Hintergrundfarbe in der Sidebar des Gutenberg-Editors bei ausgewähltem Absatz-Block.

Dabei befinden sich die Farben innerhalb eines auf- und zuklappbaren Bereichs und die ausgewählte Farbe wird neben dem Titel dieses Bereichs angezeigt. Wird eine Farbe ausgewählt, wird für das Absatz-Element eine entsprechende Klasse eingefügt und ein Inline-Style gesetzt.

Der Ansatz

Recht schnell bin ich im Gutenberg-GitHub-Repo auf die ColorPalette-Komponente gestoßen, der man auch eigene Farben mitgeben kann, um so die Standard-Farbpalette beziehungsweise die über add_theme_support() definierte Palette zu überschreiben. Das hat auch funktioniert, aber damit das so aussieht wie bei dem Absatz-Block, muss die PanelColor-Komponente verwendet werden – der wiederum können keine eigenen Farben übergeben werden.

Nach längerem Hin und Her bin ich dann darauf gekommen, die Komponente nachzubauen, da sie intern auch nur ColorPalette verwendet. Mit dem Code aus der Komponente des Absatz-Blocks hatte ich dazu die Lösung gefunden, wie der Inline-Style gesetzt werden kann, aber das mit dem Klassennamen hat noch nicht funktioniert.

Schließlich stellt sich heraus: um die Klassennamen zu erstellen, greift Gutenberg intern auf die Standard-Farbpalette zurück, nicht auf die, die an ColorPalette übergeben wird. Die Lösung für das Problem ist, alle benötigten Farben über add_theme_support() anzulegen und dann in der Komponente jeweils die Untermenge zu definieren.

Die Lösung

Die Lösung besteht damit quasi aus drei Teilen:

Ihr seht: es ist quasi alles für die Lösung im Gutenberg-Repo vorhanden – man muss es nur finden 🙈😃

Ich gehe davon aus, dass ihr Gutenberg installiert und eine JavaScript-Datei im Editor eingebunden habt und bereits eine package.json nutzt. Ich nutze hier moderne JavaScript-Syntax, die mit Babel in ES5 konvertiert wird.

Farbpalette definieren

Zunächst definieren wir unsere Farben, die wir später nutzen wollen:

add_action( 'after_setup_theme', 'setup_theme_supported_features' );

/**
 * Remove unused Gutenberg features.
 */
function setup_theme_supported_features() {
	add_theme_support( 'editor-color-palette', 
		[
			'name' => 'Puerto Rico',
			'slug' => 'puerto-rico',
			'color' => '#53c4ab',
		],
		[
			'name' => 'Tundora',
			'slug' => 'tundora',
			'color' => '#454545',
		],
		[
			'name' => 'Butterfly Bush',
			'slug' => 'butterfly-bush',
			'color' => '#5151a0',
		],
		[
			'name' => 'White',
			'slug' => 'white',
			'color' => '#ffffff',
		],
	);

	add_theme_support( 'disable-custom-colors' );
}

Der JavaScript-Part

Neben Komponenten von Gutenberg brauchen wir noch die classnames-Bibliothek. Um die zu installieren, führen wir folgenden Befehl in der Kommandozeile aus:

npm install classnames

Kommen wir jetzt zunächst zu den ganzen externen Dingen, die wir in unsere JavaScript-Datei importieren müssen.

import classnames from 'classnames';

const {
	registerBlockType,
} = wp.blocks;
const {
	InspectorControls,
	InnerBlocks,
	withColors,
	getColorClass
} = wp.editor;
const {
	PanelBody,
	withFallbackStyles,
	PanelColor,
	ColorPalette,
} = wp.components;

const {
	compose,
	Component,
} = wp.element;

const {getComputedStyle} = window;

const FallbackStyles = withFallbackStyles((node, ownProps) => {
	const {textColor, backgroundColor} = ownProps.attributes;
	const editableNode = node.querySelector('[contenteditable="true"]');
	//verify if editableNode is available, before using getComputedStyle.
	const computedStyles = editableNode ? getComputedStyle(editableNode) : null;
	return {
		fallbackBackgroundColor: backgroundColor || !computedStyles ? undefined : computedStyles.backgroundColor,
		fallbackTextColor: textColor || !computedStyles ? undefined : computedStyles.color,
	};
});

Wie bereits geschrieben, ist die Lösung ja aus bestehenden Gutenberg-Komponenten zusammengesucht – dementsprechend lassen sich diese Definitionen und die withFallbackStyles()-Funktion im Absatz-Block und der PanelColor-Komponente finden.

Wie im Absatz-Block zu sehen, werden wir den Edit-Teil unseres Blocks als Unterklasse von Component anlegen:

class OneColumnBlock extends Component {
	constructor() {
		super(...arguments);
	}

	render() {
		const {
			attributes,
			setAttributes,
			mergeBlocks,
			onReplace,
			className,
			backgroundColor,
			textColor,
			setBackgroundColor,
			setTextColor,
			fallbackBackgroundColor,
			fallbackTextColor,
			fallbackFontSize,
		} = this.props;

		const textColors = [
			{
				name: 'Tundora',
				slug: 'tundora',
				color: '#454545'
			},
		];
		const backgroundColors = [
			{
				name: 'Puerto Rico',
				slug: 'puerto-rico',
				color: '#53c4ab'
			},
			{
				name: 'Butterfly Bush',
				slug: 'butterfly-bush',
				color: '#5151a0'
			},
			{
				name: 'White',
				slug: 'white',
				color: '#ffffff'
			}
		];

		return (
			<div
				className={classnames(className, {
					'has-background': backgroundColor.value,
					[backgroundColor.class]: backgroundColor.class,
					[textColor.class]: textColor.class,
				})}
				
				style={{
					backgroundColor: backgroundColor.value,
					color: textColor.value,
				}}
			>
				<InnerBlocks/>
				<InspectorControls>
					<PanelBody title={'Farbschema'}>
						<PanelColor {...{title: 'Textfarbe', colorName: textColor.name, colorValue: textColor.value, initialOpen: false }} >
							<ColorPalette
								colors={textColors}
								disableCustomColors={true}
								value={textColor.value}
								onChange={setTextColor}
							/>
						</PanelColor>

						<PanelColor {...{title: 'Hintergrundfarbe', colorName: backgroundColor.name, colorValue: backgroundColor.value, initialOpen: false }} >
							<ColorPalette
								colors={backgroundColors}
								disableCustomColors={true}
								value={backgroundColor.value}
								onChange={setBackgroundColor}
							/>
						</PanelColor>
					</PanelBody>
				</InspectorControls>
			</div>
		);
	}
}

Interessant ist der render-Part. Die Konstanten vom Anfang sind aus dem Absatz-Block übernommen – textColors und backgroundColors sind unsere Untermengen der über add_theme_support( 'editor-color-palette' ) definierten Farben.

Im return-Teil wird zunächst ein div geöffnet, in dem die Klassennamen und die Inline-Styles ausgegeben werden. Auch das ist aus der Absatz-Komponente übernommen. InnerBlocks ist eine Komponente, die es ermöglicht, andere Blöcke in diesem Block zu verwenden, und danach geht es innerhalb von InspectorControls und PanelBody an unsere Farbwähler.

Normalerweise würde jetzt nur die PanelColor-Komponente kommen, wie in dem Absatz-Block zu sehen. Dabei würde die Komponente dann aus wp.editor importiert, nicht wie hier aus wp.components. Ich habe mich hingegen an dem Vorgehen innerhalb dieser PanelColor-Komponente aus wp.editor orientiert, die PanelColor aus wp.components und darin ColorPalette verwendet.

Für PanelColor geben wir den Titel, den Farbnamen, den Farbwert und über initialOpen: false an, dass das Panel nicht standardmäßig geöffnet sein soll. Für ColorPalette geben wir mit colors jeweils die Farben an, die gewählt werden können. Wir deaktivieren die Möglichkeit, eine eigene Farbe zu wählen und geben als Wert für den Farbwähler textColor.value beziehungsweise backgroundColor.value an. Als Wert für onChange geben wir die Werte an, die bei der Absatz-Komponente in PanelColor angegeben sind.

Ein letztes Stück Code haben wir jetzt noch – ebenfalls wieder an die Absatz-Komponente angelehnt:

export default registerBlockType('slug/one-column', {
	title: 'Eine Spalte',
	icon: 'admin-post',
	category: 'layout',
	attributes: {
		backgroundColor: {
			type: 'string',
		},
		textColor: {
			type: 'string',
		},
	},
	edit: compose([
		withColors('backgroundColor', {textColor: 'color'}),
		FallbackStyles,
	])(OneColumnBlock),
	save: props => {
		const {
			backgroundColor,
			textColor,
			customBackgroundColor,
			customTextColor,
		} = props.attributes;

		const textClass = getColorClass( 'color', textColor );
		const backgroundClass = getColorClass( 'background-color', backgroundColor );
		
		const className = classnames( {
			'has-background': backgroundColor || customBackgroundColor,
			[ textClass ]: textClass,
			[ backgroundClass ]: backgroundClass,
		} );
		return (
			<div className={className}>
				<InnerBlocks.Content/>
			</div>
		);
	},
});

Hier registrieren wir jetzt unseren konkreten Block slug/one-column und geben ein paar Metadaten an. Als edit legen wir mit compose den Teil aus OneColumnBlock fest und in save bilden wir die eventuell notwendigen Farbklassen und weisen sie dem div zu, das den Inhalt von unserem InnerBlocks-Block umschließt.

Und so sieht das Ergebnis aus (ich habe bei dem Projekt ein paar Farben mehr als im Beispiel):

Screenshot of two color pickers with different color palettes.

Den kompletten Code aus dem Beispiel könnt ihr als Gist anschauen.

Das könnte auch interessant sein

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.