@getvitops/generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/assets/bricks/elements/carousel.php +89 -0
- package/assets/bricks/elements/centered.php +128 -0
- package/assets/bricks/elements/color-scheme-toggle.php +61 -0
- package/assets/bricks/elements/copy-button.php +65 -0
- package/assets/bricks/elements/dismissable.php +93 -0
- package/assets/bricks/elements/entries.php +96 -0
- package/assets/bricks/elements/image-compare.php +132 -0
- package/assets/bricks/elements/multi-field.php +148 -0
- package/assets/bricks/elements/sitenav.php +298 -0
- package/assets/bricks/elements/split-link.php +170 -0
- package/assets/bricks/elements/split-panel.php +155 -0
- package/assets/bricks/elements/split.php +83 -0
- package/assets/bricks/load.php +129 -0
- package/assets/css/animation.css +526 -0
- package/assets/css/global.css +17 -0
- package/assets/css/index.css +58 -0
- package/assets/css/layout.css +723 -0
- package/assets/css/patterns/anchor-link.css +66 -0
- package/assets/css/patterns/banner.css +64 -0
- package/assets/css/patterns/carousel.css +269 -0
- package/assets/css/patterns/cluster.css +64 -0
- package/assets/css/patterns/code.css +17 -0
- package/assets/css/patterns/combobox.css +97 -0
- package/assets/css/patterns/comment.css +88 -0
- package/assets/css/patterns/copy.css +82 -0
- package/assets/css/patterns/details.css +87 -0
- package/assets/css/patterns/dialog.css +99 -0
- package/assets/css/patterns/drawer.css +108 -0
- package/assets/css/patterns/dropdown.css +45 -0
- package/assets/css/patterns/forms.css +335 -0
- package/assets/css/patterns/grouped.css +86 -0
- package/assets/css/patterns/horizontal-scroll.css +40 -0
- package/assets/css/patterns/icon.css +72 -0
- package/assets/css/patterns/lightbox.css +156 -0
- package/assets/css/patterns/list.css +272 -0
- package/assets/css/patterns/marquee.css +68 -0
- package/assets/css/patterns/masonry.css +40 -0
- package/assets/css/patterns/media.css +70 -0
- package/assets/css/patterns/nav.css +241 -0
- package/assets/css/patterns/notification.css +114 -0
- package/assets/css/patterns/overlay.css +28 -0
- package/assets/css/patterns/popover.css +68 -0
- package/assets/css/patterns/pull-quote.css +71 -0
- package/assets/css/patterns/scroll-based.css +227 -0
- package/assets/css/patterns/scroll-target.css +124 -0
- package/assets/css/patterns/separator.css +130 -0
- package/assets/css/patterns/sitenav.css +582 -0
- package/assets/css/patterns/split-link.css +113 -0
- package/assets/css/patterns/splitter.css +212 -0
- package/assets/css/patterns/stack.css +67 -0
- package/assets/css/patterns/sticky.css +127 -0
- package/assets/css/patterns/subgrid.css +178 -0
- package/assets/css/patterns/svg.css +166 -0
- package/assets/css/patterns/table.css +106 -0
- package/assets/css/patterns/tag.css +75 -0
- package/assets/css/patterns/text-effects.css +317 -0
- package/assets/css/patterns/tooltip.css +68 -0
- package/assets/css/patterns/tree.css +126 -0
- package/assets/css/utilities.css +351 -0
- package/assets/js/deferred.js +1 -0
- package/assets/js/elements.js +887 -0
- package/assets/js/polyfills/adoptedStyleSheets.js +1 -0
- package/assets/js/polyfills/css-anchor-positioning-fn.js +17 -0
- package/assets/js/polyfills/global.esm.js +1 -0
- package/assets/js/polyfills/popover.js +63 -0
- package/assets/js/polyfills/template-shadowroot.js +24 -0
- package/assets/js/polyfills/webcomponents-bundle.js +27 -0
- package/assets/js/polyfills.js +1 -0
- package/dist/index.d.mts +1425 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2611 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
- package/schema.json +508 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Vitops — Image Compare element (Bricks Builder).
|
|
4
|
+
*
|
|
5
|
+
* Renders the <wc-image-compare> Lit component (src/web-components/WCImageCompare.ts):
|
|
6
|
+
* a before/after comparison slider. Author fills the two seeded slots (slot="before" /
|
|
7
|
+
* slot="after") — typically an Image element in each.
|
|
8
|
+
*
|
|
9
|
+
* Nestable: the live slider appears on the frontend; the builder canvas shows the two
|
|
10
|
+
* images stacked (upgrade happens client-side via dist/elements.js).
|
|
11
|
+
*
|
|
12
|
+
* Owned by the framework repo (vitops: bricks/elements/); copied into the theme's
|
|
13
|
+
* dist/bricks/ on build — do not hand-edit in the theme.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
17
|
+
exit;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Vitops_Element_Image_Compare extends \Bricks\Element {
|
|
21
|
+
public $category = 'vitops';
|
|
22
|
+
public $name = 'vitops-image-compare';
|
|
23
|
+
public $icon = 'ti-layout-slider';
|
|
24
|
+
public $css_selector = 'wc-image-compare';
|
|
25
|
+
public $nestable = true;
|
|
26
|
+
public $vue_component = 'bricks-nestable';
|
|
27
|
+
|
|
28
|
+
public function get_label() {
|
|
29
|
+
return esc_html__( 'Image Compare', 'bricks' );
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public function get_keywords() {
|
|
33
|
+
return array( 'image', 'compare', 'before', 'after', 'slider', 'vitops' );
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public function set_controls() {
|
|
37
|
+
$this->controls['position'] = array(
|
|
38
|
+
'tab' => 'content',
|
|
39
|
+
'label' => esc_html__( 'Initial position (%)', 'bricks' ),
|
|
40
|
+
'type' => 'number',
|
|
41
|
+
'min' => 0,
|
|
42
|
+
'max' => 100,
|
|
43
|
+
'default' => 50,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
$this->controls['vertical'] = array(
|
|
47
|
+
'tab' => 'content',
|
|
48
|
+
'label' => esc_html__( 'Vertical split', 'bricks' ),
|
|
49
|
+
'type' => 'checkbox',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
$this->controls['discrete'] = array(
|
|
53
|
+
'tab' => 'content',
|
|
54
|
+
'label' => esc_html__( 'Discrete (step) dragging', 'bricks' ),
|
|
55
|
+
'type' => 'checkbox',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
$this->controls['keyboardStep'] = array(
|
|
59
|
+
'tab' => 'content',
|
|
60
|
+
'label' => esc_html__( 'Keyboard step (%)', 'bricks' ),
|
|
61
|
+
'type' => 'number',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
$this->controls['beforeLabel'] = array(
|
|
65
|
+
'tab' => 'content',
|
|
66
|
+
'label' => esc_html__( 'Before label', 'bricks' ),
|
|
67
|
+
'type' => 'text',
|
|
68
|
+
'placeholder' => esc_html__( 'Before', 'bricks' ),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
$this->controls['afterLabel'] = array(
|
|
72
|
+
'tab' => 'content',
|
|
73
|
+
'label' => esc_html__( 'After label', 'bricks' ),
|
|
74
|
+
'type' => 'text',
|
|
75
|
+
'placeholder' => esc_html__( 'After', 'bricks' ),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Two slots seeded: before / after (drop an Image into each).
|
|
80
|
+
public function get_nestable_children() {
|
|
81
|
+
return array(
|
|
82
|
+
array(
|
|
83
|
+
'name' => 'image',
|
|
84
|
+
'label' => esc_html__( 'Before', 'bricks' ),
|
|
85
|
+
'settings' => array(
|
|
86
|
+
'_attributes' => array(
|
|
87
|
+
array( 'id' => 'vslotbefore', 'name' => 'slot', 'value' => 'before' ),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
array(
|
|
92
|
+
'name' => 'image',
|
|
93
|
+
'label' => esc_html__( 'After', 'bricks' ),
|
|
94
|
+
'settings' => array(
|
|
95
|
+
'_attributes' => array(
|
|
96
|
+
array( 'id' => 'vslotafter', 'name' => 'slot', 'value' => 'after' ),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public function render() {
|
|
104
|
+
$s = $this->settings;
|
|
105
|
+
$attrs = array();
|
|
106
|
+
|
|
107
|
+
$num = array(
|
|
108
|
+
'position' => 'position',
|
|
109
|
+
'keyboardStep' => 'keyboard-step',
|
|
110
|
+
'beforeLabel' => 'before-label',
|
|
111
|
+
'afterLabel' => 'after-label',
|
|
112
|
+
);
|
|
113
|
+
foreach ( $num as $key => $attr ) {
|
|
114
|
+
if ( isset( $s[ $key ] ) && '' !== $s[ $key ] ) {
|
|
115
|
+
$attrs[] = $attr . '="' . esc_attr( $s[ $key ] ) . '"';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if ( ! empty( $s['vertical'] ) ) {
|
|
120
|
+
$attrs[] = 'vertical';
|
|
121
|
+
}
|
|
122
|
+
if ( ! empty( $s['discrete'] ) ) {
|
|
123
|
+
$attrs[] = 'discrete';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
$attr_str = implode( ' ', $attrs );
|
|
127
|
+
|
|
128
|
+
echo "<wc-image-compare {$attr_str} {$this->render_attributes( '_root' )}>";
|
|
129
|
+
echo \Bricks\Frontend::render_children( $this );
|
|
130
|
+
echo '</wc-image-compare>';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Vitops — Multi Field element (Bricks Builder).
|
|
4
|
+
*
|
|
5
|
+
* Renders the <multi-field> Lit component (src/web-components/WCMultiField.ts): a
|
|
6
|
+
* form-associated repeatable input group (add / remove entries, min / max). Default
|
|
7
|
+
* entries are supplied as slotted <input value="…"> children, which the component reads
|
|
8
|
+
* on connect.
|
|
9
|
+
*
|
|
10
|
+
* Non-nestable: PHP render() runs in the builder canvas too, so the field group upgrades
|
|
11
|
+
* live while editing.
|
|
12
|
+
*
|
|
13
|
+
* Owned by the framework repo (vitops: bricks/elements/); copied into the theme's
|
|
14
|
+
* dist/bricks/ on build — do not hand-edit in the theme.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
18
|
+
exit;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Vitops_Element_Multi_Field extends \Bricks\Element {
|
|
22
|
+
public $category = 'vitops';
|
|
23
|
+
public $name = 'vitops-multi-field';
|
|
24
|
+
public $icon = 'ti-list';
|
|
25
|
+
public $css_selector = 'multi-field';
|
|
26
|
+
|
|
27
|
+
public function get_label() {
|
|
28
|
+
return esc_html__( 'Multi Field', 'bricks' );
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public function get_keywords() {
|
|
32
|
+
return array( 'multi', 'field', 'repeatable', 'form', 'input', 'vitops' );
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function set_controls() {
|
|
36
|
+
$this->controls['name'] = array(
|
|
37
|
+
'tab' => 'content',
|
|
38
|
+
'label' => esc_html__( 'Field name', 'bricks' ),
|
|
39
|
+
'type' => 'text',
|
|
40
|
+
'description' => esc_html__( 'Submitted as name[] for each entry.', 'bricks' ),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
$this->controls['type'] = array(
|
|
44
|
+
'tab' => 'content',
|
|
45
|
+
'label' => esc_html__( 'Input type', 'bricks' ),
|
|
46
|
+
'type' => 'text',
|
|
47
|
+
'placeholder' => 'text',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
$this->controls['placeholder'] = array(
|
|
51
|
+
'tab' => 'content',
|
|
52
|
+
'label' => esc_html__( 'Placeholder', 'bricks' ),
|
|
53
|
+
'type' => 'text',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
$this->controls['min'] = array(
|
|
57
|
+
'tab' => 'content',
|
|
58
|
+
'label' => esc_html__( 'Min entries', 'bricks' ),
|
|
59
|
+
'type' => 'number',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
$this->controls['max'] = array(
|
|
63
|
+
'tab' => 'content',
|
|
64
|
+
'label' => esc_html__( 'Max entries', 'bricks' ),
|
|
65
|
+
'type' => 'number',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
$this->controls['protectDefaults'] = array(
|
|
69
|
+
'tab' => 'content',
|
|
70
|
+
'label' => esc_html__( 'Protect default entries', 'bricks' ),
|
|
71
|
+
'type' => 'checkbox',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
$this->controls['addLabel'] = array(
|
|
75
|
+
'tab' => 'content',
|
|
76
|
+
'label' => esc_html__( 'Add button label', 'bricks' ),
|
|
77
|
+
'type' => 'text',
|
|
78
|
+
'placeholder' => esc_html__( 'Add', 'bricks' ),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
$this->controls['clearLabel'] = array(
|
|
82
|
+
'tab' => 'content',
|
|
83
|
+
'label' => esc_html__( 'Clear button label', 'bricks' ),
|
|
84
|
+
'type' => 'text',
|
|
85
|
+
'placeholder' => esc_html__( 'Clear', 'bricks' ),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
$this->controls['deleteLabel'] = array(
|
|
89
|
+
'tab' => 'content',
|
|
90
|
+
'label' => esc_html__( 'Delete button label', 'bricks' ),
|
|
91
|
+
'type' => 'text',
|
|
92
|
+
'placeholder' => esc_html__( 'Delete', 'bricks' ),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
$this->controls['defaults'] = array(
|
|
96
|
+
'tab' => 'content',
|
|
97
|
+
'label' => esc_html__( 'Default entries', 'bricks' ),
|
|
98
|
+
'type' => 'repeater',
|
|
99
|
+
'titleProperty' => 'value',
|
|
100
|
+
'fields' => array(
|
|
101
|
+
'value' => array(
|
|
102
|
+
'label' => esc_html__( 'Value', 'bricks' ),
|
|
103
|
+
'type' => 'text',
|
|
104
|
+
),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public function render() {
|
|
110
|
+
$s = $this->settings;
|
|
111
|
+
$attrs = array();
|
|
112
|
+
|
|
113
|
+
$text = array(
|
|
114
|
+
'name' => 'name',
|
|
115
|
+
'type' => 'type',
|
|
116
|
+
'placeholder' => 'placeholder',
|
|
117
|
+
'min' => 'min',
|
|
118
|
+
'max' => 'max',
|
|
119
|
+
'addLabel' => 'add-label',
|
|
120
|
+
'clearLabel' => 'clear-label',
|
|
121
|
+
'deleteLabel' => 'delete-label',
|
|
122
|
+
);
|
|
123
|
+
foreach ( $text as $key => $attr ) {
|
|
124
|
+
if ( isset( $s[ $key ] ) && '' !== $s[ $key ] ) {
|
|
125
|
+
$attrs[] = $attr . '="' . esc_attr( $s[ $key ] ) . '"';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if ( ! empty( $s['protectDefaults'] ) ) {
|
|
130
|
+
$attrs[] = 'protect-defaults';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
$attr_str = implode( ' ', $attrs );
|
|
134
|
+
|
|
135
|
+
// Default entries → slotted <input value="…"> children.
|
|
136
|
+
$inputs = '';
|
|
137
|
+
if ( ! empty( $s['defaults'] ) && is_array( $s['defaults'] ) ) {
|
|
138
|
+
$input_type = ! empty( $s['type'] ) ? $s['type'] : 'text';
|
|
139
|
+
foreach ( $s['defaults'] as $row ) {
|
|
140
|
+
if ( isset( $row['value'] ) && '' !== $row['value'] ) {
|
|
141
|
+
$inputs .= '<input type="' . esc_attr( $input_type ) . '" value="' . esc_attr( $row['value'] ) . '">';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
echo "<multi-field {$attr_str} {$this->render_attributes( '_root' )}>{$inputs}</multi-field>";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Vitops — Site Nav element (Bricks Builder).
|
|
4
|
+
*
|
|
5
|
+
* The site's primary navigation, generated from a WordPress menu. Two presentations
|
|
6
|
+
* from one markup tree, switched purely by container width — no JS beyond the native
|
|
7
|
+
* Popover API:
|
|
8
|
+
*
|
|
9
|
+
* • Mobile (below the chosen breakpoint, .sitenav--bp-{sm,md,lg,xl}): a hamburger
|
|
10
|
+
* button (a Popover API invoker) opens a slide-in DRAWER — its own sibling
|
|
11
|
+
* [popover] element. Light-dismiss, Esc, and focus handling come free from the
|
|
12
|
+
* platform. Inside the drawer, branch items are native <details> ACCORDIONS.
|
|
13
|
+
* • Desktop (≥ the breakpoint): the hamburger + drawer chrome fall away and the same
|
|
14
|
+
* list lays out inline as a NAVBAR; branch submenus promote to hover/focus/click
|
|
15
|
+
* pop-out DROPDOWNS. See sitenav.css.
|
|
16
|
+
*
|
|
17
|
+
* Each branch item is a SPLIT-LINK: a real parent <a> that navigates, flush with a
|
|
18
|
+
* separate <details> disclosure whose <summary> is *only* the caret toggle. The link
|
|
19
|
+
* sits OUTSIDE the summary (a sibling of <details>), so there is no interactive control
|
|
20
|
+
* nested inside the summary's button role — valid HTML and axe-clean (no
|
|
21
|
+
* nested-interactive). The submenu lives inside <details> and reveals as an accordion
|
|
22
|
+
* (mobile) or a dropdown (desktop).
|
|
23
|
+
*
|
|
24
|
+
* Per-breakpoint depth caps: "Desktop depth" / "Mobile depth" limit how deep the tree
|
|
25
|
+
* shows at each breakpoint. PHP resolves the caps into `sitenav__item--desktop-branch`
|
|
26
|
+
* / `--mobile-branch` classes on the boundary nodes; sitenav.css then hides just the
|
|
27
|
+
* caret + submenu at the capped breakpoint, leaving the parent link — so a 3-tier
|
|
28
|
+
* desktop megamenu collapses to a 2-tier tap-through on mobile.
|
|
29
|
+
*
|
|
30
|
+
* "Menu" was this element's former name; it was renamed to Site Nav so "Menu" can name
|
|
31
|
+
* a more generic interactive dropdown. Non-nestable: the tree comes from the WP menu,
|
|
32
|
+
* so render() drives all markup and runs in the builder canvas too.
|
|
33
|
+
*
|
|
34
|
+
* Owned by the framework repo (vitops: bricks/elements/); copied into the theme's
|
|
35
|
+
* dist/bricks/ on build — do not hand-edit in the theme.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
39
|
+
exit;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class Vitops_Element_Sitenav extends \Bricks\Element {
|
|
43
|
+
public $category = 'vitops';
|
|
44
|
+
public $name = 'vitops-sitenav';
|
|
45
|
+
public $icon = 'ti-menu';
|
|
46
|
+
public $css_selector = '.sitenav';
|
|
47
|
+
|
|
48
|
+
public function get_label() {
|
|
49
|
+
return esc_html__( 'Site Nav', 'bricks' );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public function get_keywords() {
|
|
53
|
+
return array( 'sitenav', 'site nav', 'nav', 'navigation', 'navbar', 'drawer', 'header', 'menu', 'megamenu', 'vitops' );
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public function set_controls() {
|
|
57
|
+
// Populate the menu picker from registered WP nav menus (available in the
|
|
58
|
+
// builder/admin context where set_controls runs).
|
|
59
|
+
$menu_options = array();
|
|
60
|
+
if ( function_exists( 'wp_get_nav_menus' ) ) {
|
|
61
|
+
foreach ( wp_get_nav_menus() as $menu ) {
|
|
62
|
+
$menu_options[ $menu->term_id ] = $menu->name;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
$this->controls['menu'] = array(
|
|
67
|
+
'tab' => 'content',
|
|
68
|
+
'label' => esc_html__( 'WordPress menu', 'bricks' ),
|
|
69
|
+
'type' => 'select',
|
|
70
|
+
'options' => $menu_options,
|
|
71
|
+
'placeholder' => esc_html__( 'Select a menu', 'bricks' ),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if ( empty( $menu_options ) ) {
|
|
75
|
+
$this->controls['menuInfo'] = array(
|
|
76
|
+
'tab' => 'content',
|
|
77
|
+
'type' => 'info',
|
|
78
|
+
'content' => esc_html__( 'No WordPress menus found. Create one under Appearance → Menus.', 'bricks' ),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
$this->controls['ariaLabel'] = array(
|
|
83
|
+
'tab' => 'content',
|
|
84
|
+
'label' => esc_html__( 'Accessible label', 'bricks' ),
|
|
85
|
+
'type' => 'text',
|
|
86
|
+
'placeholder' => esc_html__( 'Primary', 'bricks' ),
|
|
87
|
+
'description' => esc_html__( 'aria-label for the <nav> landmark.', 'bricks' ),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
$this->controls['breakpoint'] = array(
|
|
91
|
+
'tab' => 'content',
|
|
92
|
+
'label' => esc_html__( 'Navbar breakpoint', 'bricks' ),
|
|
93
|
+
'type' => 'select',
|
|
94
|
+
'inline' => true,
|
|
95
|
+
'options' => array(
|
|
96
|
+
'sm' => esc_html__( 'sm — 30rem', 'bricks' ),
|
|
97
|
+
'md' => esc_html__( 'md — 48rem', 'bricks' ),
|
|
98
|
+
'lg' => esc_html__( 'lg — 64rem', 'bricks' ),
|
|
99
|
+
'xl' => esc_html__( 'xl — 80rem', 'bricks' ),
|
|
100
|
+
),
|
|
101
|
+
'default' => 'md',
|
|
102
|
+
'description' => esc_html__( 'Container width at/above which the drawer becomes an inline navbar with pop-out dropdowns. Below it, a hamburger opens a drawer with accordions.', 'bricks' ),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
$this->controls['drawerSide'] = array(
|
|
106
|
+
'tab' => 'content',
|
|
107
|
+
'label' => esc_html__( 'Drawer side', 'bricks' ),
|
|
108
|
+
'type' => 'select',
|
|
109
|
+
'inline' => true,
|
|
110
|
+
'options' => array(
|
|
111
|
+
'start' => esc_html__( 'Inline-start (left)', 'bricks' ),
|
|
112
|
+
'end' => esc_html__( 'Inline-end (right)', 'bricks' ),
|
|
113
|
+
),
|
|
114
|
+
'default' => 'end',
|
|
115
|
+
'description' => esc_html__( 'Edge the mobile drawer slides in from.', 'bricks' ),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
$this->controls['toggleLabel'] = array(
|
|
119
|
+
'tab' => 'content',
|
|
120
|
+
'label' => esc_html__( 'Toggle label (accessible name)', 'bricks' ),
|
|
121
|
+
'type' => 'text',
|
|
122
|
+
'placeholder' => esc_html__( 'Menu', 'bricks' ),
|
|
123
|
+
'description' => esc_html__( 'aria-label for the mobile hamburger button.', 'bricks' ),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
$this->controls['depthDesktop'] = array(
|
|
127
|
+
'tab' => 'content',
|
|
128
|
+
'label' => esc_html__( 'Desktop depth', 'bricks' ),
|
|
129
|
+
'type' => 'number',
|
|
130
|
+
'min' => 1,
|
|
131
|
+
'default' => 3,
|
|
132
|
+
'description' => esc_html__( 'Levels shown at/above the breakpoint. Empty = unlimited.', 'bricks' ),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
$this->controls['depthMobile'] = array(
|
|
136
|
+
'tab' => 'content',
|
|
137
|
+
'label' => esc_html__( 'Mobile depth', 'bricks' ),
|
|
138
|
+
'type' => 'number',
|
|
139
|
+
'min' => 1,
|
|
140
|
+
'default' => 2,
|
|
141
|
+
'description' => esc_html__( 'Levels shown below the breakpoint; deeper toggles/markers are hidden. Empty = unlimited.', 'bricks' ),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public function render() {
|
|
146
|
+
$s = $this->settings;
|
|
147
|
+
$bp = ! empty( $s['breakpoint'] ) ? $s['breakpoint'] : 'md';
|
|
148
|
+
$side = ! empty( $s['drawerSide'] ) && 'start' === $s['drawerSide'] ? 'start' : 'end';
|
|
149
|
+
$dd = isset( $s['depthDesktop'] ) && '' !== $s['depthDesktop'] ? (int) $s['depthDesktop'] : PHP_INT_MAX;
|
|
150
|
+
$dm = isset( $s['depthMobile'] ) && '' !== $s['depthMobile'] ? (int) $s['depthMobile'] : PHP_INT_MAX;
|
|
151
|
+
$label = ! empty( $s['ariaLabel'] ) ? $s['ariaLabel'] : esc_html__( 'Primary', 'bricks' );
|
|
152
|
+
$toggle_label = ! empty( $s['toggleLabel'] ) ? $s['toggleLabel'] : esc_html__( 'Menu', 'bricks' );
|
|
153
|
+
|
|
154
|
+
// Unique id ties the hamburger invoker to its sibling drawer popover.
|
|
155
|
+
$drawer_id = 'sitenav-' . $this->id;
|
|
156
|
+
|
|
157
|
+
$this->set_attribute( '_root', 'class', array( 'sitenav', 'sitenav--bp-' . $bp, 'sitenav--drawer-' . $side ) );
|
|
158
|
+
$this->set_attribute( '_root', 'aria-label', $label );
|
|
159
|
+
|
|
160
|
+
echo "<nav {$this->render_attributes( '_root' )}>";
|
|
161
|
+
|
|
162
|
+
// Hamburger — a Popover API invoker controlling the sibling drawer. Hidden at
|
|
163
|
+
// the navbar breakpoint via CSS.
|
|
164
|
+
echo '<button type="button" class="sitenav__toggle" popovertarget="' . esc_attr( $drawer_id ) . '"'
|
|
165
|
+
. ' aria-label="' . esc_attr( $toggle_label ) . '">'
|
|
166
|
+
. $this->hamburger_svg()
|
|
167
|
+
. '</button>';
|
|
168
|
+
|
|
169
|
+
// Drawer (mobile) / navbar body (desktop): the popover sibling.
|
|
170
|
+
echo '<div id="' . esc_attr( $drawer_id ) . '" popover class="sitenav__panel">';
|
|
171
|
+
|
|
172
|
+
echo '<button type="button" class="sitenav__close" popovertarget="' . esc_attr( $drawer_id ) . '"'
|
|
173
|
+
. ' popovertargetaction="hide" aria-label="' . esc_attr__( 'Close menu', 'bricks' ) . '">'
|
|
174
|
+
. $this->close_svg()
|
|
175
|
+
. '</button>';
|
|
176
|
+
|
|
177
|
+
$menu_id = isset( $s['menu'] ) ? $s['menu'] : '';
|
|
178
|
+
$items = $menu_id && function_exists( 'wp_get_nav_menu_items' ) ? wp_get_nav_menu_items( $menu_id ) : array();
|
|
179
|
+
|
|
180
|
+
if ( empty( $items ) ) {
|
|
181
|
+
// Builder/empty state: keep the element discoverable.
|
|
182
|
+
echo '<p class="sitenav__empty">' . esc_html__( 'Select a WordPress menu.', 'bricks' ) . '</p>';
|
|
183
|
+
} else {
|
|
184
|
+
// Group flat items by parent id to walk as a tree.
|
|
185
|
+
$by_parent = array();
|
|
186
|
+
foreach ( $items as $item ) {
|
|
187
|
+
$by_parent[ (int) $item->menu_item_parent ][] = $item;
|
|
188
|
+
}
|
|
189
|
+
echo $this->render_level( $by_parent, 0, 1, $dd, $dm, true );
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
echo '</div>'; // .sitenav__panel
|
|
193
|
+
echo '</nav>';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Render one <ul> level and recurse. $depth is 1-based (top list = 1).
|
|
198
|
+
* $top marks the outer list (gets .sitenav__list vs nested .sitenav__submenu).
|
|
199
|
+
*/
|
|
200
|
+
private function render_level( $by_parent, $parent_id, $depth, $dd, $dm, $top ) {
|
|
201
|
+
if ( empty( $by_parent[ $parent_id ] ) ) {
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
$list_class = $top ? 'sitenav__list' : 'sitenav__submenu';
|
|
206
|
+
$html = '<ul class="' . $list_class . '">';
|
|
207
|
+
|
|
208
|
+
foreach ( $by_parent[ $parent_id ] as $item ) {
|
|
209
|
+
$id = (int) $item->ID;
|
|
210
|
+
$child_depth = $depth + 1;
|
|
211
|
+
$has_kids = ! empty( $by_parent[ $id ] );
|
|
212
|
+
|
|
213
|
+
// A child list at $child_depth is shown per breakpoint if within its cap.
|
|
214
|
+
$show_desktop = $has_kids && $child_depth <= $dd;
|
|
215
|
+
$show_mobile = $has_kids && $child_depth <= $dm;
|
|
216
|
+
|
|
217
|
+
$link = $this->link_markup( $item );
|
|
218
|
+
|
|
219
|
+
// Leaf (no children, or children capped out at BOTH breakpoints).
|
|
220
|
+
if ( ! $has_kids || ( ! $show_desktop && ! $show_mobile ) ) {
|
|
221
|
+
$html .= '<li class="sitenav__item">' . $link . '</li>';
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Branch: caret/submenu may be capped at one breakpoint. (The item-level
|
|
226
|
+
// split-link styling comes from the `split-link__*` element classes below,
|
|
227
|
+
// not the `.split-link` wrapper, whose inline-flex would fight the layout.)
|
|
228
|
+
$item_classes = array( 'sitenav__item', 'sitenav__item--branch' );
|
|
229
|
+
if ( $show_desktop && ! $show_mobile ) {
|
|
230
|
+
$item_classes[] = 'sitenav__item--desktop-branch';
|
|
231
|
+
} elseif ( $show_mobile && ! $show_desktop ) {
|
|
232
|
+
$item_classes[] = 'sitenav__item--mobile-branch';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
$submenu = $this->render_level( $by_parent, $id, $child_depth, $dd, $dm, false );
|
|
236
|
+
$toggle_name = sprintf(
|
|
237
|
+
/* translators: %s: parent menu item title */
|
|
238
|
+
esc_attr__( 'Show %s submenu', 'bricks' ),
|
|
239
|
+
wp_strip_all_tags( $item->title )
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// SPLIT-LINK, restructured for accessibility + a robust layout:
|
|
243
|
+
// • The parent <a> is a SIBLING of <details> (not nested in <summary>), so
|
|
244
|
+
// no interactive control sits inside the summary's button role — valid
|
|
245
|
+
// HTML, axe-clean (no nested-interactive).
|
|
246
|
+
// • <details> holds ONLY the caret <summary>; the submenu <ul> is a further
|
|
247
|
+
// SIBLING, controlled via `:has(> details[open])` in CSS. Keeping the ul
|
|
248
|
+
// out of <details> avoids the global ::details-content accordion in
|
|
249
|
+
// details.css and lets the ul be a real grid child of the <li> — inline
|
|
250
|
+
// accordion on mobile, absolute dropdown on desktop.
|
|
251
|
+
$html .= '<li class="' . esc_attr( implode( ' ', $item_classes ) ) . '">'
|
|
252
|
+
. $link
|
|
253
|
+
. '<details class="sitenav__disclosure">'
|
|
254
|
+
. '<summary class="sitenav__subtoggle split-link__toggle" aria-label="' . esc_attr( $toggle_name ) . '">'
|
|
255
|
+
. '<span class="split-link__caret" aria-hidden="true">▾</span>'
|
|
256
|
+
. '</summary>'
|
|
257
|
+
. '</details>'
|
|
258
|
+
. $submenu
|
|
259
|
+
. '</li>';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return $html . '</ul>';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Build the item's <a> from WP menu-item fields.
|
|
266
|
+
private function link_markup( $item ) {
|
|
267
|
+
$url = ! empty( $item->url ) ? $item->url : '#';
|
|
268
|
+
$target = ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) . '"' : '';
|
|
269
|
+
$rel = ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) . '"' : '';
|
|
270
|
+
$title = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) . '"' : '';
|
|
271
|
+
$classes = 'sitenav__link split-link__link';
|
|
272
|
+
if ( ! empty( $item->classes ) && is_array( $item->classes ) ) {
|
|
273
|
+
$extra = trim( implode( ' ', array_filter( $item->classes ) ) );
|
|
274
|
+
if ( '' !== $extra ) {
|
|
275
|
+
$classes .= ' ' . $extra;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return '<a class="' . esc_attr( $classes ) . '" href="' . esc_url( $url ) . '"'
|
|
280
|
+
. $target . $rel . $title . '>' . esc_html( $item->title ) . '</a>';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Inline hamburger glyph (three bars). aria-hidden — the button is labelled.
|
|
284
|
+
private function hamburger_svg() {
|
|
285
|
+
return '<svg class="sitenav__toggle-icon" viewBox="0 0 24 24" width="24" height="24" fill="none"'
|
|
286
|
+
. ' stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false">'
|
|
287
|
+
. '<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>'
|
|
288
|
+
. '</svg>';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Inline close (✕) glyph. aria-hidden — the button is labelled.
|
|
292
|
+
private function close_svg() {
|
|
293
|
+
return '<svg class="sitenav__close-icon" viewBox="0 0 24 24" width="24" height="24" fill="none"'
|
|
294
|
+
. ' stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false">'
|
|
295
|
+
. '<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>'
|
|
296
|
+
. '</svg>';
|
|
297
|
+
}
|
|
298
|
+
}
|