Skip to content

Commit 90709af

Browse files
committed
Refactoring
1 parent 7ef1c1c commit 90709af

3 files changed

Lines changed: 158 additions & 59 deletions

File tree

assets/css/setting-fields.css

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@
5050
========================================================================== */
5151

5252
@keyframes setting-fields-spin {
53-
from { transform: rotate(0deg); }
54-
to { transform: rotate(360deg); }
53+
from {
54+
transform: rotate(0deg);
55+
}
56+
to {
57+
transform: rotate(360deg);
58+
}
5559
}
5660

5761
/* ==========================================================================
@@ -2752,4 +2756,6 @@ a.setting-fields-badge--gold:hover {
27522756
*/
27532757
th .setting-fields-badge {
27542758
pointer-events: auto;
2759+
margin-top: 6px;
2760+
margin-left: 0;
27552761
}

docs/badges.md

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
# Badges
22

3-
Display upgrade or tier indicators on individual fields or entire sections. Badges render as small inline pills next to labels and section titles. Combine with `disabled` to create a locked preview state for premium features.
3+
Display upgrade or tier indicators on individual fields or entire sections. Badges render as small inline pills next to
4+
labels and section titles. When a badge is visible, the associated field is automatically disabled — creating a locked
5+
preview state for premium features.
46

57
## Field Badge
68

79
Add a `badge` key to any field config:
810

911
```php
1012
'advanced_sync' => [
11-
'type' => 'toggle',
12-
'label' => 'Advanced Sync',
13-
'badge' => 'Pro',
14-
'disabled' => true,
13+
'type' => 'toggle',
14+
'label' => 'Advanced Sync',
15+
'badge' => 'Pro',
1516
],
1617
```
1718

18-
The badge appears inline after the field label, between the required asterisk and the tooltip icon.
19+
The badge appears inline after the field label, between the required asterisk and the tooltip icon. When the badge is
20+
visible, the field is automatically disabled — no need to set `disabled` separately.
1921

2022
### Full Config
2123

@@ -24,20 +26,20 @@ The badge appears inline after the field label, between the required asterisk an
2426
'type' => 'select',
2527
'label' => 'Webhook Mode',
2628
'badge' => [
27-
'text' => 'Business',
28-
'url' => 'https://example.com/upgrade',
29-
'class' => 'setting-fields-badge--gold',
30-
'icon' => 'dashicons-lock',
29+
'text' => 'Business',
30+
'url' => 'https://example.com/upgrade',
31+
'class' => 'setting-fields-badge--gold',
32+
'icon' => 'dashicons-lock',
33+
'disabled' => fn() => is_setting_field_license_active( 'my_plugin', 'license' ),
3134
],
32-
'disabled' => true,
3335
],
3436
```
3537

3638
When `url` is set, the badge renders as a link that opens in a new tab — useful for pointing users to an upgrade page.
3739

3840
## Section Badge
3941

40-
Add a `badge` to a section definition. When combined with `disabled`, all fields in the section are automatically disabled:
42+
Add a `badge` to a section definition. When the badge is visible, all fields in the section are automatically disabled:
4143

4244
```php
4345
'sections' => [
@@ -46,38 +48,65 @@ Add a `badge` to a section definition. When combined with `disabled`, all fields
4648
'description' => 'These features require a Pro license.',
4749
'tab' => 'general',
4850
'badge' => [
49-
'text' => 'Pro',
50-
'url' => 'https://example.com/upgrade',
51+
'text' => 'Pro',
52+
'url' => 'https://example.com/upgrade',
53+
'disabled' => fn() => is_setting_field_license_active( 'my_plugin', 'license' ),
5154
],
52-
'disabled' => true,
5355
],
5456
],
5557
```
5658

57-
The section title and badge remain fully visible and clickable (for upgrade links) even when the section's fields are dimmed and disabled.
59+
The section title and badge remain fully visible and clickable (for upgrade links) even when the section's fields are
60+
dimmed and disabled.
5861

5962
## Badge Options
6063

61-
| Key | Type | Required | Default | Description |
62-
|---------|--------|----------|---------|------------------------------------------------|
63-
| `text` | string | Yes || Badge label |
64-
| `url` | string | No | `''` | Links badge to upgrade page (opens new tab) |
65-
| `class` | string | No | `''` | Additional CSS class for styling |
66-
| `icon` | string | No | `''` | Dashicon class (e.g., `dashicons-lock`) |
64+
| Key | Type | Required | Default | Description |
65+
|------------|----------------|----------|---------|---------------------------------------------|
66+
| `text` | string | Yes || Badge label |
67+
| `url` | string | No | `''` | Links badge to upgrade page (opens new tab) |
68+
| `class` | string | No | `''` | Additional CSS class for styling |
69+
| `icon` | string | No | `''` | Dashicon class (e.g., `dashicons-lock`) |
70+
| `disabled` | bool\|callable | No | `false` | When truthy, hides badge and unlocks field |
6771

6872
If `badge` is a string (e.g., `'Pro'`), it's treated as `['text' => 'Pro']`.
6973

74+
## The `disabled` Key
75+
76+
The `disabled` key on a badge controls both the badge visibility and the field's disabled state in a single declaration.
77+
This avoids the need to conditionally build the field array:
78+
79+
- When `disabled` is **falsy or absent** — badge is visible, field is disabled
80+
- When `disabled` is **truthy** — badge is hidden, field is editable
81+
- When `disabled` is a **callable** — it's called at render time and the return value is used as a bool
82+
83+
```php
84+
'badge' => [
85+
'text' => 'Pro',
86+
'disabled' => fn() => has_pro_license(),
87+
],
88+
```
89+
90+
This is equivalent to writing:
91+
92+
```php
93+
'badge' => has_pro_license() ? '' : 'Pro',
94+
'disabled' => ! has_pro_license(),
95+
```
96+
97+
But the `disabled` key keeps the config static and declarative — the array never needs to be built conditionally.
98+
7099
## Built-in Color Variants
71100

72101
Use the `class` key to apply a color variant:
73102

74-
| Class | Colors |
75-
|----------------------------------|----------------|
76-
| *(default — no class)* | Neutral grey |
77-
| `setting-fields-badge--pro` | Amber/orange |
78-
| `setting-fields-badge--premium` | Purple |
79-
| `setting-fields-badge--business` | Green |
80-
| `setting-fields-badge--gold` | Gold/yellow |
103+
| Class | Colors |
104+
|----------------------------------|--------------|
105+
| *(default — no class)* | Neutral grey |
106+
| `setting-fields-badge--pro` | Amber/orange |
107+
| `setting-fields-badge--premium` | Purple |
108+
| `setting-fields-badge--business` | Green |
109+
| `setting-fields-badge--gold` | Gold/yellow |
81110

82111
```php
83112
'badge' => [
@@ -91,24 +120,46 @@ Linked badges change to a solid color on hover.
91120

92121
## Disabled Cascade
93122

94-
When a section has `'disabled' => true`, all child fields inherit the disabled state automatically. You don't need to set `disabled` on each field individually. The section's form table is dimmed with reduced opacity while the section header remains fully interactive.
123+
When a section has an active badge, all child fields inherit the disabled state automatically. You don't need to set
124+
`disabled` on each field individually. The section's form table is dimmed with reduced opacity while the section header
125+
remains fully interactive.
95126

96-
## Dynamic Badges
127+
## Example: License-Gated Features
97128

98-
You can conditionally add badges based on license status or feature flags:
129+
A common pattern for freemium plugins — fields are locked with a badge until the user activates a license:
99130

100131
```php
101-
$is_pro = is_setting_field_license_active( 'my_plugin', 'license' );
132+
$license_check = fn() => is_setting_field_license_active( 'my_plugin', 'license' );
102133

103-
'advanced_sync' => [
104-
'type' => 'toggle',
105-
'label' => 'Advanced Sync',
106-
'badge' => $is_pro ? '' : [
107-
'text' => 'Pro',
108-
'url' => 'https://example.com/upgrade',
134+
'fields' => [
135+
'basic_feature' => [
136+
'type' => 'toggle',
137+
'label' => 'Basic Feature',
138+
],
139+
'advanced_sync' => [
140+
'type' => 'toggle',
141+
'label' => 'Advanced Sync',
142+
'badge' => [
143+
'text' => 'Pro',
144+
'url' => 'https://example.com/upgrade',
145+
'class' => 'setting-fields-badge--pro',
146+
'icon' => 'dashicons-lock',
147+
'disabled' => $license_check,
148+
],
149+
],
150+
'webhook_mode' => [
151+
'type' => 'select',
152+
'label' => 'Webhook Mode',
153+
'options' => [ 'basic' => 'Basic', 'advanced' => 'Advanced' ],
154+
'badge' => [
155+
'text' => 'Pro',
156+
'url' => 'https://example.com/upgrade',
157+
'class' => 'setting-fields-badge--pro',
158+
'icon' => 'dashicons-lock',
159+
'disabled' => $license_check,
160+
],
109161
],
110-
'disabled' => ! $is_pro,
111162
],
112163
```
113164

114-
When the license is active, the badge disappears and the field becomes editable.
165+
When the license is active, badges disappear and fields become editable — all from a static config array.

src/SettingFields.php

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,9 @@ protected function render_fields_for_tab( string $tab ): void {
642642
* div with a data-section attribute. This allows the conditional logic
643643
* JS to hide entire sections when all their fields are hidden.
644644
*
645-
* If the section has 'disabled' => true, all child fields inherit the
646-
* disabled state automatically.
645+
* If the section has a badge with an active (non-disabled) state, or
646+
* has 'disabled' => true, all child fields inherit the disabled state
647+
* automatically.
647648
*
648649
* @param string $section_key Section key.
649650
* @param array $section Section config.
@@ -652,8 +653,11 @@ protected function render_fields_for_tab( string $tab ): void {
652653
* @return void
653654
*/
654655
protected function render_section( string $section_key, array $section, array $fields ): void {
655-
$is_disabled = ! empty( $section['disabled'] );
656-
$has_badge = ! empty( $section['badge'] );
656+
// Resolve badge (returns null if badge is disabled/hidden)
657+
$badge = isset( $section['badge'] ) ? self::resolve_badge( $section['badge'] ) : null;
658+
659+
// Section is disabled if explicitly set OR if badge is active (visible)
660+
$is_disabled = ! empty( $section['disabled'] ) || $badge !== null;
657661

658662
$classes = 'setting-fields-section';
659663
if ( $is_disabled ) {
@@ -662,15 +666,15 @@ protected function render_section( string $section_key, array $section, array $f
662666

663667
echo '<div class="' . esc_attr( $classes ) . '" data-section="' . esc_attr( $section_key ) . '">';
664668

665-
if ( ! empty( $section['title'] ) || $has_badge ) {
669+
if ( ! empty( $section['title'] ) || $badge !== null ) {
666670
echo '<div class="setting-fields-section-header">';
667671

668672
if ( ! empty( $section['title'] ) ) {
669673
echo '<h2 class="setting-fields-section-title">' . esc_html( $section['title'] ) . '</h2>';
670674
}
671675

672-
if ( $has_badge ) {
673-
self::render_badge( $section['badge'] );
676+
if ( $badge !== null ) {
677+
self::render_badge( $badge );
674678
}
675679

676680
echo '</div>';
@@ -695,26 +699,56 @@ protected function render_section( string $section_key, array $section, array $f
695699
}
696700

697701
/**
698-
* Render an inline badge.
702+
* Resolve a badge configuration and check if it should render.
699703
*
700-
* Outputs a small pill badge next to a field label or section title.
701-
* Supports a simple string or a full config array with text, url,
702-
* class, and icon options.
704+
* Normalizes string shorthand to array, resolves callable 'disabled'
705+
* values, and returns null if the badge is disabled. When the badge
706+
* has a 'disabled' key that resolves truthy, the badge is hidden
707+
* and the associated field/section should become editable.
703708
*
704709
* @param string|array $badge Badge configuration.
705710
*
706-
* @return void
711+
* @return array|null Normalized badge config, or null if disabled.
707712
*/
708-
private static function render_badge( $badge ): void {
713+
private static function resolve_badge( $badge ): ?array {
709714
// Normalize string shorthand to array
710715
if ( is_string( $badge ) ) {
711716
$badge = [ 'text' => $badge ];
712717
}
713718

714719
if ( ! is_array( $badge ) || empty( $badge['text'] ) ) {
715-
return;
720+
return null;
716721
}
717722

723+
// Resolve the disabled condition
724+
$disabled = $badge['disabled'] ?? false;
725+
if ( is_callable( $disabled ) ) {
726+
$disabled = (bool) call_user_func( $disabled );
727+
}
728+
729+
// If disabled resolves truthy, badge should not render
730+
if ( $disabled ) {
731+
return null;
732+
}
733+
734+
return $badge;
735+
}
736+
737+
/**
738+
* Render an inline badge.
739+
*
740+
* Outputs a small pill badge next to a field label or section title.
741+
* Supports a simple string or a full config array with text, url,
742+
* class, and icon options.
743+
*
744+
* Use resolve_badge() first to check if the badge should render
745+
* and to normalize the configuration.
746+
*
747+
* @param array $badge Normalized badge configuration.
748+
*
749+
* @return void
750+
*/
751+
private static function render_badge( array $badge ): void {
718752
$text = $badge['text'];
719753
$url = $badge['url'] ?? '';
720754
$class = $badge['class'] ?? '';
@@ -765,6 +799,14 @@ protected function render_field_row( string $field_key, array $field ): void {
765799
// Add the field key to the field config
766800
$field['_key'] = $field_key;
767801

802+
// Resolve badge (returns null if badge is disabled/hidden)
803+
$badge = isset( $field['badge'] ) ? self::resolve_badge( $field['badge'] ) : null;
804+
805+
// When badge is active (visible), disable the field
806+
if ( $badge !== null ) {
807+
$field['disabled'] = true;
808+
}
809+
768810
// Build conditional logic data attributes
769811
$row_attrs = $this->get_conditional_attributes( $field );
770812

@@ -799,8 +841,8 @@ protected function render_field_row( string $field_key, array $field ): void {
799841
<?php if ( ! empty( $field['required'] ) ) : ?>
800842
<span class="required">*</span>
801843
<?php endif; ?>
802-
<?php if ( ! empty( $field['badge'] ) ) : ?>
803-
<?php self::render_badge( $field['badge'] ); ?>
844+
<?php if ( $badge !== null ) : ?>
845+
<?php self::render_badge( $badge ); ?>
804846
<?php endif; ?>
805847
<?php if ( ! empty( $field['tooltip'] ) ) : ?>
806848
<span class="setting-fields-tooltip">

0 commit comments

Comments
 (0)