Skip to content

Commit 1fde455

Browse files
authored
Merge pull request #42 from hotwired-laravel/tm/boost-publish
Adds a Boost publish command
2 parents 1a99713 + b9f1571 commit 1fde455

File tree

4 files changed

+304
-0
lines changed

4 files changed

+304
-0
lines changed

.ai/stimulus-bridge.blade.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
## Hotwired Native Bridge Components Guidelines
2+
3+
### BridgeComponent Class
4+
- `BridgeComponent` extends Stimulus `Controller` with native bridge functionality
5+
- Always set `static component` property that matches your native component name
6+
- Place bridge components in `/bridge` subdirectory for easy identification
7+
8+
@verbatim
9+
```javascript
10+
// resources/js/controllers/bridge/form_controller.js
11+
import { BridgeComponent, BridgeElement } from "@hotwired/hotwire-native-bridge"
12+
13+
export default class extends BridgeComponent {
14+
static component = "form" // Must match native component name
15+
static targets = [ "submit" ]
16+
17+
submitTargetConnected(target) {
18+
const submitButton = new BridgeElement(target)
19+
const submitTitle = submitButton.title
20+
21+
this.send("connect", { submitTitle }, () => {
22+
target.click()
23+
})
24+
}
25+
}
26+
```
27+
@endverbatim
28+
29+
### BridgeComponent Properties
30+
- `this.platformOptingOut`: Whether controller is opted out for current platform
31+
- `this.enabled`: Whether component is supported by the native app
32+
- `this.bridgeElement`: Provides `this.element` wrapped in a BridgeElement
33+
- `this.send(event, data, callback)`: Send message to native component
34+
35+
### BridgeElement Features
36+
- `title`: Gets title from `data-bridge-title`, `aria-label`, or `textContent`/`value`
37+
- `disabled`/`enabled`: Check/set disabled state
38+
- `enableForComponent(component)`: Remove `data-bridge-disabled` attribute
39+
- `bridgeAttribute(name)`: Get `data-bridge-{name}` attribute value
40+
- `setBridgeAttribute(name, value)`: Set `data-bridge-{name}` attribute
41+
- `removeBridgeAttribute(name)`: Remove `data-bridge-{name}` attribute
42+
43+
### HTML Structure
44+
45+
@verbatim
46+
```html
47+
<form method="post" data-controller="bridge--form">
48+
<!-- form elements -->
49+
<button
50+
class="button"
51+
type="submit"
52+
data-bridge--form-target="submit"
53+
data-bridge-title="Submit">
54+
Submit Form
55+
</button>
56+
</form>
57+
```
58+
@endverbatim
59+
60+
### Data Attributes
61+
- `data-bridge-title="My Title"`: Custom bridge title
62+
- `data-bridge-disabled="true|false|ios|android"`: Control element availability
63+
- `data-bridge-*`: Custom attributes accessible via BridgeElement
64+
- `data-controller-optout-ios`: Opt-out component for iOS
65+
- `data-controller-optout-android`: Opt-out component for Android
66+
67+
### Bridge Communication Pattern
68+
1. Use target connection callbacks to initialize bridge elements
69+
2. Send messages to native components with `this.send(event, data, callback)`
70+
3. Always check `this.enabled` before bridge operations
71+
4. Provide callback functions to handle native responses

.ai/stimulus.blade.php

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
## Stimulus Core Philosophy
2+
- Make Stimulus Controllers using the `artisan make:stimulus {name}` command
3+
- Make Stimulus Bridge Controllers using the `artisan make:stimulus --bridge {name}` command
4+
- Stimulus enhances static or server-rendered HTML with JavaScript controllers
5+
- Store state in HTML data attributes, not JavaScript objects
6+
- Design for progressive enhancement - work without JavaScript, enhance with it
7+
- Build small, reusable controllers that connect to DOM elements
8+
9+
### Best Practices
10+
- One responsibility per controller
11+
- Design for multiple instances on same page
12+
- Always cleanup in `disconnect()`
13+
- Use semantic HTML first, enhance with Stimulus
14+
15+
#### Error Handling
16+
```javascript
17+
// Global
18+
Stimulus.handleError = (error, message, detail) => {
19+
console.warn(message, detail)
20+
// Send to error tracking
21+
}
22+
23+
// Controller method
24+
async fetchData() {
25+
try {
26+
// async operation
27+
} catch (error) {
28+
this.showError(error.message)
29+
}
30+
}
31+
```
32+
33+
#### Default Events by Element
34+
- `a`, `button`: `click`
35+
- `form`: `submit`
36+
- `input`, `textarea`: `input`
37+
- `select`: `change`
38+
- `details`: `toggle`
39+
40+
### Controller Structure
41+
42+
#### Basic Controller Template
43+
44+
@verbatim
45+
```javascript
46+
// resources/js/controllers/example_controller.js
47+
import { Controller } from "@hotwired/stimulus"
48+
49+
export default class extends Controller {
50+
static targets = [ "targetName" ]
51+
static values = { propertyName: Type }
52+
static classes = [ "className" ]
53+
54+
connect() { /* When connected to DOM */ }
55+
disconnect() { /* When disconnected - cleanup here */ }
56+
57+
actionMethod() { /* Handle events */ }
58+
propertyNameValueChanged() { /* When value changes */ }
59+
}
60+
```
61+
@endverbatim
62+
63+
### Naming Conventions
64+
- **Files**: `hello_controller.js` → identifier `hello`
65+
- **Nested**: `admin/users_controller.js` → identifier `admin--users`
66+
- **Underscores**: become dashes in identifiers
67+
68+
### HTML Data Attributes
69+
70+
#### Controller Binding
71+
72+
@verbatim
73+
```html
74+
<div data-controller="slideshow clipboard">
75+
<!-- Multiple controllers on one element -->
76+
</div>
77+
```
78+
@endverbatim
79+
80+
#### Actions (Event Handling)
81+
82+
@verbatim
83+
```html
84+
<!-- Full syntax -->
85+
<button data-action="click->slideshow#next">Next</button>
86+
87+
<!-- Default events (click for buttons) -->
88+
<button data-action="slideshow#next">Next</button>
89+
90+
<!-- Multiple actions -->
91+
<input data-action="focus->form#highlight blur->form#reset">
92+
```
93+
@endverbatim
94+
95+
#### Targets
96+
97+
@verbatim
98+
```html
99+
<!-- HTML -->
100+
<input data-slideshow-target="slide">
101+
102+
<!-- Controller -->
103+
static targets = [ "slide" ]
104+
// Creates: this.slideTarget, this.slideTargets, this.hasSlideTarget
105+
```
106+
@endverbatim
107+
108+
#### Values (State Management)
109+
110+
@verbatim
111+
```html
112+
<!-- HTML -->
113+
<div data-slideshow-index-value="1" data-slideshow-autoplay-value="true">
114+
115+
<!-- Controller -->
116+
static values = {
117+
index: Number,
118+
autoplay: Boolean,
119+
delay: { type: Number, default: 5000 }
120+
}
121+
// Creates: this.indexValue, this.autoplayValue, etc.
122+
```
123+
@endverbatim
124+
125+
### Common Patterns
126+
127+
#### Lifecycle Management
128+
129+
@verbatim
130+
```javascript
131+
connect() {
132+
this.startTimer()
133+
}
134+
135+
disconnect() {
136+
this.stopTimer() // Always cleanup external resources
137+
}
138+
139+
startTimer() {
140+
this.timer = setInterval(() => this.refresh(), 1000)
141+
}
142+
143+
stopTimer() {
144+
if (this.timer) {
145+
clearInterval(this.timer)
146+
}
147+
}
148+
```
149+
@endverbatim
150+
151+
#### Progressive Enhancement
152+
153+
@verbatim
154+
```javascript
155+
static classes = [ "supported" ]
156+
157+
connect() {
158+
if ("clipboard" in navigator) {
159+
this.element.classList.add(this.supportedClass)
160+
}
161+
}
162+
```
163+
@endverbatim
164+
165+
#### Value Change Callbacks
166+
167+
@verbatim
168+
```javascript
169+
static values = { index: Number }
170+
171+
indexValueChanged() {
172+
// Called on initialization and value changes
173+
this.showCurrentSlide()
174+
}
175+
```
176+
@endverbatim
177+
178+
#### Action Parameters
179+
180+
@verbatim
181+
```html
182+
<a data-loader-url-param="/messages" data-action="loader#load">Load</a>
183+
```
184+
185+
```javascript
186+
load({ params: { url } }) {
187+
fetch(url).then(/* handle response */)
188+
}
189+
```
190+
@endverbatim
191+
192+
### Manual Registration
193+
194+
@verbatim
195+
```javascript
196+
import { Application } from "@hotwired/stimulus"
197+
import HelloController from "./controllers/hello_controller"
198+
199+
window.Stimulus = Application.start()
200+
Stimulus.register("hello", HelloController)
201+
```
202+
@endverbatim
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace HotwiredLaravel\StimulusLaravel\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\File;
7+
8+
class PublishBoostCommand extends Command
9+
{
10+
public $signature = 'stimulus:publish-boost {--bridge : Publish the Bridge Guideline} {--all : Publish all guidelines}';
11+
12+
public $description = 'Publishes the Stimulus Boost Guideline.';
13+
14+
public function handle()
15+
{
16+
$guidelines = array_values(array_filter([
17+
'stimulus.blade.php',
18+
$this->option('bridge') || $this->option('all') ? 'stimulus-bridge.blade.php' : null,
19+
]));
20+
21+
foreach ($guidelines as $guideline) {
22+
$from = dirname(__DIR__, levels: 2).DIRECTORY_SEPARATOR.'.ai'.DIRECTORY_SEPARATOR.$guideline;
23+
24+
File::ensureDirectoryExists(base_path(implode(DIRECTORY_SEPARATOR, ['.ai', 'guidelines'])), recursive: true);
25+
File::copy($from, base_path(implode(DIRECTORY_SEPARATOR, ['.ai', 'guidelines', $guideline])));
26+
}
27+
28+
$this->info('Boost guideline was published!');
29+
}
30+
}

src/StimulusLaravelServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function configurePackage(Package $package): void
2424
Commands\MakeCommand::class,
2525
Commands\CoreMakeCommand::class,
2626
Commands\PublishCommand::class,
27+
Commands\PublishBoostCommand::class,
2728
Commands\ManifestCommand::class,
2829
]);
2930
}

0 commit comments

Comments
 (0)