From b4a3bbc94870f7a64e7022487c4f53565fcf4b63 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Wed, 26 Feb 2025 15:46:38 -0800 Subject: [PATCH] Button Group field type --- src/fields/ButtonGroup.php | 146 ++++++++++++++++++++++++++++++++++++ src/helpers/Cp.php | 13 ++++ src/services/Fields.php | 2 + src/translations/en/app.php | 1 + 4 files changed, 162 insertions(+) create mode 100644 src/fields/ButtonGroup.php diff --git a/src/fields/ButtonGroup.php b/src/fields/ButtonGroup.php new file mode 100644 index 00000000000..14166e0c347 --- /dev/null +++ b/src/fields/ButtonGroup.php @@ -0,0 +1,146 @@ + + * @since 3.0.0 + */ +class ButtonGroup extends BaseOptionsField implements SortableFieldInterface +{ + /** + * @inheritdoc + */ + protected static bool $optionIcons = true; + + /** + * @inheritdoc + */ + public static function displayName(): string + { + return Craft::t('app', 'Button Group'); + } + + /** + * @inheritdoc + */ + public static function icon(): string + { + return 'hand-pointer'; + } + + /** + * @inheritdoc + */ + public function useFieldset(): bool + { + return true; + } + + /** + * @inheritdoc + */ + protected function inputHtml(mixed $value, ?ElementInterface $element, bool $inline): string + { + return $this->_inputHtml($value, $element, false); + } + + /** + * @inheritdoc + */ + public function getStaticHtml(mixed $value, ElementInterface $element): string + { + return $this->_inputHtml($value, $element, true); + } + + private function _inputHtml(SingleOptionFieldData $value, ?ElementInterface $element, bool $static): string + { + /** @var SingleOptionFieldData $value */ + if (!$value->valid) { + Craft::$app->getView()->setInitialDeltaValue($this->handle, null); + } + + $id = $this->getInputId(); + + $html = Html::beginTag('section', [ + 'id' => $id, + 'class' => ['btngroup', 'btngroup--exclusive'], + 'aria' => [ + 'labelledby' => $this->getLabelId(), + ], + ]); + + $value = $this->encodeValue($value); + + foreach ($this->translatedOptions(true, $value, $element) as $option) { + $selected = $option['value'] === $value; + $labelHtml = Html::encode($option['label']); + if (!empty($option['icon'])) { + $labelHtml = Html::beginTag('div', ['class' => ['flex', 'flex-inline']]) . + Html::tag('div', Cp::iconSvg($option['icon']), [ + 'class' => ['cp-icon', 'small'], + ]) . + Html::tag('div', $labelHtml) . + Html::endTag('div'); + } + $html .= Cp::buttonHtml([ + 'labelHtml' => $labelHtml, + 'type' => 'button', + 'class' => array_filter([ + $selected ? 'active' : null, + ]), + 'disabled' => $static, + 'attributes' => [ + 'aria' => [ + 'pressed' => $selected ? 'true' : false, + ], + 'data' => [ + 'value' => $option['value'], + ], + ], + ]); + } + + $html .= Html::endTag('section') . // .btngroup + Html::hiddenInput($this->handle, $value, [ + 'id' => "{$id}-input", + ]); + + $view = Craft::$app->getView(); + $view->registerJsWithVars(fn($id) => << { + new Craft.Listbox($('#' + $id), { + onChange: (selectedOption) => { + $('#' + $id + '-input').val(selectedOption.data('value')); + }, + }); +})(); +JS, [ + $view->namespaceInputId($id), + ]); + + return $html; + } + + /** + * @inheritdoc + */ + protected function optionsSettingLabel(): string + { + return Craft::t('app', 'Radio Button Options'); + } +} diff --git a/src/helpers/Cp.php b/src/helpers/Cp.php index 8a3ed84ccd0..98e08511a58 100644 --- a/src/helpers/Cp.php +++ b/src/helpers/Cp.php @@ -1701,6 +1701,19 @@ private static function _noticeHtml(string $id, string $class, string $label, ?s Html::endTag('p'); } + /** + * Renders a button’s HTML. + * + * @param array $config + * @return string + * @throws InvalidArgumentException + * @since 5.7.0 + */ + public static function buttonHtml(array $config): string + { + return static::renderTemplate('_includes/forms/button.twig', $config); + } + /** * Renders a checkbox field’s HTML. * diff --git a/src/services/Fields.php b/src/services/Fields.php index e0a7483deef..85f09f45978 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -30,6 +30,7 @@ use craft\fields\Addresses as AddressesField; use craft\fields\Assets as AssetsField; use craft\fields\BaseRelationField; +use craft\fields\ButtonGroup; use craft\fields\Categories as CategoriesField; use craft\fields\Checkboxes; use craft\fields\Color; @@ -222,6 +223,7 @@ public function getAllFieldTypes(): array $fieldTypes = [ AddressesField::class, AssetsField::class, + ButtonGroup::class, CategoriesField::class, Checkboxes::class, Color::class, diff --git a/src/translations/en/app.php b/src/translations/en/app.php index 9c76628ed36..aa602807fbf 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -224,6 +224,7 @@ 'Briefly describe your issue or idea.' => 'Briefly describe your issue or idea.', 'Briefly describe your question.' => 'Briefly describe your question.', 'Bug reports and feature requests' => 'Bug reports and feature requests', + 'Button Group' => 'Button Group', 'Buy now' => 'Buy now', 'Buy {name}' => 'Buy {name}', 'Bytes' => 'Bytes',