Skip to content

Commit 87c4a80

Browse files
authored
Merge pull request #16782 from craftcms/feature/cms-1368-button-group-field-type
Button Group field type
2 parents 7d76515 + 2a8bd44 commit 87c4a80

File tree

10 files changed

+202
-3
lines changed

10 files changed

+202
-3
lines changed

CHANGELOG-WIP.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Improved the accessibility of Tags fields for screen readers. ([#16754](https://github.com/craftcms/cms/pull/16754))
1111

1212
### Administration
13+
- Added the “Button Group” field type. ([#16782](https://github.com/craftcms/cms/pull/16782))
1314
- Added “Icon” and “Color” settings to Checkboxes, Dropdown, Multi-select, and Radio Buttons field options. ([#16645](https://github.com/craftcms/cms/pull/16645))
1415
- The email settings page now shows a “Test” button when `allowAdminChanges` is disabled. ([#16508](https://github.com/craftcms/cms/discussions/16508))
1516
- Added the `--batch-size` option for `resave/*` commands. ([#16586](https://github.com/craftcms/cms/issues/16586))

src/fields/BaseOptionsField.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,10 @@ public function getContentGqlMutationArgumentType(): Type|array
618618
*
619619
* @return string
620620
*/
621-
abstract protected function optionsSettingLabel(): string;
621+
protected function optionsSettingLabel(): string
622+
{
623+
return Craft::t('app', 'Options');
624+
}
622625

623626
/**
624627
* Returns the available options (and optgroups) for the field.

src/fields/ButtonGroup.php

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
/**
3+
* @link https://craftcms.com/
4+
* @copyright Copyright (c) Pixel & Tonic, Inc.
5+
* @license https://craftcms.github.io/license/
6+
*/
7+
8+
namespace craft\fields;
9+
10+
use Craft;
11+
use craft\base\ElementInterface;
12+
use craft\base\SortableFieldInterface;
13+
use craft\fields\data\SingleOptionFieldData;
14+
use craft\helpers\Cp;
15+
use craft\helpers\Html;
16+
17+
/**
18+
* RadioButtons represents a Radio Buttons field.
19+
*
20+
* @author Pixel & Tonic, Inc. <[email protected]>
21+
* @since 3.0.0
22+
*/
23+
class ButtonGroup extends BaseOptionsField implements SortableFieldInterface
24+
{
25+
/**
26+
* @inheritdoc
27+
*/
28+
protected static bool $optionIcons = true;
29+
30+
/**
31+
* @inheritdoc
32+
*/
33+
public static function displayName(): string
34+
{
35+
return Craft::t('app', 'Button Group');
36+
}
37+
38+
/**
39+
* @inheritdoc
40+
*/
41+
public static function icon(): string
42+
{
43+
return 'hand-pointer';
44+
}
45+
46+
/**
47+
* @var bool Whether buttons should only show their icons, hiding their text labels
48+
*/
49+
public bool $iconsOnly = false;
50+
51+
/**
52+
* @inheritdoc
53+
*/
54+
public function getSettingsHtml(): ?string
55+
{
56+
return parent::getSettingsHtml() .
57+
Cp::lightswitchFieldHtml([
58+
'label' => Craft::t('app', 'Icons only'),
59+
'instructions' => Craft::t('app', 'Whether buttons should only show their icons, hiding their text labels.'),
60+
'name' => 'iconsOnly',
61+
'on' => $this->iconsOnly,
62+
]);
63+
}
64+
65+
/**
66+
* @inheritdoc
67+
*/
68+
public function useFieldset(): bool
69+
{
70+
return true;
71+
}
72+
73+
/**
74+
* @inheritdoc
75+
*/
76+
protected function inputHtml(mixed $value, ?ElementInterface $element, bool $inline): string
77+
{
78+
return $this->_inputHtml($value, $element, false);
79+
}
80+
81+
/**
82+
* @inheritdoc
83+
*/
84+
public function getStaticHtml(mixed $value, ElementInterface $element): string
85+
{
86+
return $this->_inputHtml($value, $element, true);
87+
}
88+
89+
private function _inputHtml(SingleOptionFieldData $value, ?ElementInterface $element, bool $static): string
90+
{
91+
/** @var SingleOptionFieldData $value */
92+
if (!$value->valid) {
93+
Craft::$app->getView()->setInitialDeltaValue($this->handle, null);
94+
}
95+
96+
$id = $this->getInputId();
97+
98+
$html = Html::beginTag('div', ['class' => 'btngroup-container']) .
99+
Html::beginTag('div', [
100+
'id' => $id,
101+
'class' => ['btngroup', 'btngroup--exclusive'],
102+
'aria' => [
103+
'labelledby' => $this->getLabelId(),
104+
],
105+
]);
106+
107+
$value = $this->encodeValue($value);
108+
109+
foreach ($this->translatedOptions(true, $value, $element) as $option) {
110+
$selected = $option['value'] === $value;
111+
112+
if ($this->iconsOnly && !empty($option['icon'])) {
113+
$labelHtml = Html::tag('div', Cp::iconSvg($option['icon']), [
114+
'class' => 'cp-icon',
115+
'aria' => [
116+
'label' => $option['label'],
117+
],
118+
]);
119+
} else {
120+
$labelHtml = Html::encode($option['label']);
121+
if (!empty($option['icon'])) {
122+
$labelHtml = Html::beginTag('div', ['class' => ['flex', 'flex-inline', 'gap-xs']]) .
123+
Html::tag('div', Cp::iconSvg($option['icon']), [
124+
'class' => 'cp-icon',
125+
]) .
126+
Html::tag('div', $labelHtml) .
127+
Html::endTag('div');
128+
}
129+
}
130+
131+
$html .= Cp::buttonHtml([
132+
'labelHtml' => $labelHtml,
133+
'type' => 'button',
134+
'class' => array_filter([
135+
$selected ? 'active' : null,
136+
]),
137+
'disabled' => $static,
138+
'attributes' => [
139+
'aria' => [
140+
'pressed' => $selected ? 'true' : 'false',
141+
],
142+
'data' => [
143+
'value' => $option['value'],
144+
],
145+
],
146+
]);
147+
}
148+
149+
$html .= Html::endTag('div') . // .btngroup
150+
Html::endTag('div') . // .btngroup-container
151+
Html::hiddenInput($this->handle, $value, [
152+
'id' => "{$id}-input",
153+
]);
154+
155+
$view = Craft::$app->getView();
156+
$view->registerJsWithVars(fn($id) => <<<JS
157+
(() => {
158+
new Craft.Listbox($('#' + $id), {
159+
onChange: (selectedOption) => {
160+
$('#' + $id + '-input').val(selectedOption.data('value'));
161+
},
162+
});
163+
})();
164+
JS, [
165+
$view->namespaceInputId($id),
166+
]);
167+
168+
return $html;
169+
}
170+
}

src/helpers/Cp.php

+13
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,19 @@ private static function _noticeHtml(string $id, string $class, string $label, ?s
17011701
Html::endTag('p');
17021702
}
17031703

1704+
/**
1705+
* Renders a button’s HTML.
1706+
*
1707+
* @param array $config
1708+
* @return string
1709+
* @throws InvalidArgumentException
1710+
* @since 5.7.0
1711+
*/
1712+
public static function buttonHtml(array $config): string
1713+
{
1714+
return static::renderTemplate('_includes/forms/button.twig', $config);
1715+
}
1716+
17041717
/**
17051718
* Renders a checkbox field’s HTML.
17061719
*

src/services/Fields.php

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use craft\fields\Addresses as AddressesField;
3131
use craft\fields\Assets as AssetsField;
3232
use craft\fields\BaseRelationField;
33+
use craft\fields\ButtonGroup;
3334
use craft\fields\Categories as CategoriesField;
3435
use craft\fields\Checkboxes;
3536
use craft\fields\Color;
@@ -222,6 +223,7 @@ public function getAllFieldTypes(): array
222223
$fieldTypes = [
223224
AddressesField::class,
224225
AssetsField::class,
226+
ButtonGroup::class,
225227
CategoriesField::class,
226228
Checkboxes::class,
227229
Color::class,

src/templates/_includes/forms/button.twig

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
class: (class ?? [])|explodeClass|merge([
1313
'btn',
1414
not (label or labelHtml) ? 'btn-empty' : null,
15+
(disabled ?? false) ? 'disabled',
1516
]|filter),
1617
data: {
1718
'busy-message': busyMessage,

src/translations/en/app.php

+3
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
'Briefly describe your issue or idea.' => 'Briefly describe your issue or idea.',
225225
'Briefly describe your question.' => 'Briefly describe your question.',
226226
'Bug reports and feature requests' => 'Bug reports and feature requests',
227+
'Button Group' => 'Button Group',
227228
'Buy now' => 'Buy now',
228229
'Buy {name}' => 'Buy {name}',
229230
'Bytes' => 'Bytes',
@@ -815,6 +816,7 @@
815816
'How-to’s and other questions' => 'How-to’s and other questions',
816817
'ID' => 'ID',
817818
'Icon' => 'Icon',
819+
'Icons only' => 'Icons only',
818820
'If the URI looks like this' => 'If the URI looks like this',
819821
'Image Format' => 'Image Format',
820822
'Image Height' => 'Image Height',
@@ -1985,6 +1987,7 @@
19851987
'Where transforms should be stored on the filesystem.' => 'Where transforms should be stored on the filesystem.',
19861988
'Whether authors should be able to choose which time zone the time is in.' => 'Whether authors should be able to choose which time zone the time is in.',
19871989
'Whether authors should be able to upload files directly to the field, rather than requiring them to select/upload assets via the selection modal.' => 'Whether authors should be able to upload files directly to the field, rather than requiring them to select/upload assets via the selection modal.',
1990+
'Whether buttons should only show their icons, hiding their text labels.' => 'Whether buttons should only show their icons, hiding their text labels.',
19881991
'Whether cards should be shown in a multi-column grid on wide viewports.' => 'Whether cards should be shown in a multi-column grid on wide viewports.',
19891992
'Whether custom fields should be validated during public registration.' => 'Whether custom fields should be validated during public registration.',
19901993
'Whether empty folders should be listed for deletion.' => 'Whether empty folders should be listed for deletion.',

src/web/assets/cp/dist/css/cp.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/css/cp.css.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/src/css/_main.scss

+6
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,12 @@ ul.icons {
21802180
}
21812181

21822182
/* button groups */
2183+
.btngroup-container {
2184+
margin: -3px;
2185+
padding: 3px;
2186+
overflow-x: auto;
2187+
}
2188+
21832189
.btngroup {
21842190
position: relative;
21852191
z-index: 1;

0 commit comments

Comments
 (0)