--- /dev/null
+<ul class="labelSelection">
+ {foreach from=$field->getOptions() item=color}
+ <li{if $color == 'custom'} class="custom"{/if}>
+ <label class="labelSelection__label">
+ <input {*
+ *}type="radio" {*
+ *}name="{$field->getPrefixedId()}" {*
+ *}value="{$color}"{*
+ *}{if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item=class glue=' '}{$class}{/implode}"{/if}{*
+ *}{if $field->getValue() !== null && $field->getValue() == $color} checked{/if}{*
+ *}{if $field->isImmutable()} disabled{/if}{*
+ *}{foreach from=$field->getFieldAttributes() key=attributeName item=attributeValue} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}>
+ {if $color == 'custom'}
+ <span class="labelSelection__span">
+ <input type="text" id="{$field->getPrefixedId()}Custom" name="{$field->getPrefixedId()}customCssClassName" value="{$field->getCustomClassName()}" class="long labelSelection__custom__input">
+ </span>
+ {else}
+ <span class="labelSelection__span badge label{if $color != 'none'} {$color}{/if}">{$field->getDefaultLabelText()}</span>
+ {/if}
+ </label>
+ </li>
+ {/foreach}
+</ul>
+
+<script data-relocate="true">
+ {if $field->getTextReferenceNodeId()}
+ require(["WoltLabSuite/Core/Form/Builder/Field/Controller/BadgeColor"], ({ BadgeColorPreview }) => {
+ new BadgeColorPreview(
+ '{unsafe:$field->getPrefixedId()|encodeJS}Container',
+ '{unsafe:$field->getTextReferenceNodeId()|encodeJS}',
+ '{unsafe:$field->getDefaultLabelText()|encodeJS}',
+ );
+ });
+ {/if}
+ const customInput = document.querySelector('#{unsafe:$field->getPrefixedId()|encodeJS}Container .labelSelection__custom__input');
+ const customRadioInput = document.querySelector('#{unsafe:$field->getPrefixedId()|encodeJS}Container .custom > .labelSelection__label > input[type="radio"]');
+ if (customInput && customRadioInput) {
+ customInput.addEventListener("focus", () => {
+ customRadioInput.checked = true;
+ });
+ }
+</script>
--- /dev/null
+/**
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+
+export class BadgeColorPreview {
+ readonly #container: HTMLElement | null;
+ readonly #referenceField: HTMLInputElement | null;
+ readonly #defaultLabelText: string;
+
+ constructor(fieldId: string, referenceFieldId: string, defaultLabelText: string) {
+ this.#defaultLabelText = defaultLabelText;
+ this.#container = document.getElementById(fieldId);
+
+ if (this.#container === null) {
+ throw new Error("Unknown field with id '" + fieldId + "'.");
+ }
+ this.#referenceField = document.getElementById(referenceFieldId) as HTMLInputElement | null;
+ if (this.#referenceField === null) {
+ throw new Error("Unknown reference element '" + referenceFieldId + "'.");
+ }
+
+ this.#referenceField.addEventListener("input", () => this.#updatePreview());
+ }
+
+ #updatePreview(): void {
+ const value = this.#referenceField!.value.trim() || this.#defaultLabelText;
+ this.#container!.querySelectorAll(".labelSelection__span.badge").forEach((span: HTMLSpanElement) => {
+ span.textContent = value;
+ });
+ }
+}
--- /dev/null
+/**
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+define(["require", "exports"], function (require, exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.BadgeColorPreview = void 0;
+ class BadgeColorPreview {
+ #container;
+ #referenceField;
+ #defaultLabelText;
+ constructor(fieldId, referenceFieldId, defaultLabelText) {
+ this.#defaultLabelText = defaultLabelText;
+ this.#container = document.getElementById(fieldId);
+ if (this.#container === null) {
+ throw new Error("Unknown field with id '" + fieldId + "'.");
+ }
+ this.#referenceField = document.getElementById(referenceFieldId);
+ if (this.#referenceField === null) {
+ throw new Error("Unknown reference element '" + referenceFieldId + "'.");
+ }
+ this.#referenceField.addEventListener("input", () => this.#updatePreview());
+ }
+ #updatePreview() {
+ const value = this.#referenceField.value.trim() || this.#defaultLabelText;
+ this.#container.querySelectorAll(".labelSelection__span.badge").forEach((span) => {
+ span.textContent = value;
+ });
+ }
+ }
+ exports.BadgeColorPreview = BadgeColorPreview;
+});
--- /dev/null
+<?php
+
+namespace wcf\system\form\builder\field;
+
+use wcf\system\WCF;
+
+/**
+ * Implementation of a badge color form field for selecting a single color or a custom color.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ *
+ * @since 6.2
+ */
+final class BadgeColorFormField extends RadioButtonFormField implements IPatternFormField
+{
+ use TPatternFormField;
+
+ public const AVAILABLE_CSS_CLASSNAMES = [
+ 'yellow',
+ 'orange',
+ 'brown',
+ 'red',
+ 'pink',
+ 'purple',
+ 'blue',
+ 'green',
+ 'black',
+
+ 'none', /* not a real value */
+ 'custom', /* not a real value */
+ ];
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = 'shared_badgeColorFormField';
+ protected ?string $textReferenceNodeId;
+ protected string $defaultLabelText;
+ protected string $customClassName = '';
+
+ public function __construct()
+ {
+ $this
+ ->addFieldClass('labelSelection__radio')
+ ->defaultLabelText(WCF::getLanguage()->get('wcf.acp.label.defaultValue'))
+ ->options(\array_combine(self::AVAILABLE_CSS_CLASSNAMES, self::AVAILABLE_CSS_CLASSNAMES))
+ ->pattern('^-?[_a-zA-Z]+[_a-zA-Z0-9-]+$');
+ }
+
+ public function defaultLabelText(string $text): self
+ {
+ $this->defaultLabelText = $text;
+
+ return $this;
+ }
+
+ public function textReferenceNode(IFormField $field): self
+ {
+ $this->textReferenceNodeId = $field->getId();
+
+ return $this;
+ }
+
+ public function textReferenceNodeId(string $id): self
+ {
+ $this->textReferenceNodeId = $id;
+
+ return $this;
+ }
+
+ public function getDefaultLabelText(): string
+ {
+ return $this->defaultLabelText;
+ }
+
+ public function getTextReferenceNodeId(): ?string
+ {
+ return $this->textReferenceNodeId;
+ }
+
+ public function getCustomClassName(): string
+ {
+ return $this->customClassName;
+ }
+}
.labelChooser > .dropdownToggle > span {
cursor: pointer;
}
+
+.labelSelection > li.custom {
+ display: flex;
+}
+
+.labelSelection__label {
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.labelSelection__radio {
+ flex: 0 0 auto;
+ margin-right: 7px;
+}
+
+.labelSelection > li.custom .labelSelection__span {
+ flex: 1 1 auto;
+}