This guide covers how to develop, test, and contribute to the Fluxbit ViewComponents JavaScript package.
Development Environment Setup
Prerequisites
- Node.js 16+ and npm/yarn
- Ruby 3.0+ and Bundler
- Basic knowledge of Stimulus and Rails
Getting Started
- Clone and Install Dependencies
git clone https://github.com/arthurmolina/fluxbit_view_components.git cd fluxbit_view_components bundle install yarn install - Start Development Server
bin/dev
This starts the demo Rails application at localhost:3000 with Lookbook component previews.
Project Structure
JavaScript Source Files
app/assets/javascripts/fluxbit_view_components/
├── index.js # Main export file
├── assigner_controller.js # Dynamic assignment controller
├── auto_submit_controller.js # Auto-submit functionality
├── drawer_controller.js # Drawer/sidebar controller
├── method_link_controller.js # HTTP method link controller
├── modal_controller.js # Modal dialog controller
├── row_click_controller.js # Clickable rows controller
├── select_all_controller.js # Bulk selection controller
└── spinner_percent_controller.js # Circular progress indicators
Build Configuration
rollup.config.js # Rollup build configuration
package.json # NPM package configuration
Development Workflow
1. Building JavaScript Assets
The JavaScript package uses Rollup for building and bundling:
# Build for development
yarn build
# Build and preview changes (shows diff and package contents)
yarn prerelease
# Watch for changes (if configured)
yarn dev
2. Testing Controllers
Manual Testing via Demo App
The demo application includes comprehensive previews for testing controllers:
bin/dev
# Navigate to localhost:3000
# Test components in the Lookbook interface
Controller Integration Testing
Create test scenarios in the demo app:
<!-- demo/app/views/test/controller_test.html.erb -->
<div data-controller="fx-modal fx-auto-submit">
<!-- Test multiple controller integration -->
</div>
Browser Console Testing
Use browser developer tools to test controller functionality:
// Test modal controller
document.dispatchEvent(new CustomEvent("showModal:test-modal"))
// Test select-all functionality
const controller = application.getControllerForElementAndIdentifier(
document.querySelector('[data-controller="fx-select-all"]'),
'fx-select-all'
)
controller.refresh()
3. Creating New Controllers
Controller Template
// app/assets/javascripts/fluxbit_view_components/my_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["element"]
static values = {
option: { type: String, default: "default" }
}
connect() {
// Initialize controller
console.log("MyController connected")
}
disconnect() {
// Clean up resources
console.log("MyController disconnected")
}
// Action methods
myAction(event) {
// Handle action
}
}
Export New Controller
Update index.js to export the new controller:
// app/assets/javascripts/fluxbit_view_components/index.js
import FxMy from './my_controller'
export {
// ... existing exports like FxModal, FxDrawer, FxSpinnerPercent
FxMy
}
export function registerFluxbitControllers(application) {
// ... existing registrations like fx-modal, fx-drawer, fx-spinner-percent
application.register('fx-my', FxMy)
}
4. Component Integration
ViewComponent Integration
Create corresponding ViewComponent if needed:
# app/components/fluxbit/my_component.rb
module Fluxbit
class MyComponent < ViewComponent::Base
def initialize(**options)
@options = options
end
private
attr_reader :options
def controller_data
{
controller: "fx-my",
"fx-my-option-value": options[:option]
}
end
end
end
Template with Controller
<!-- app/components/fluxbit/my_component.html.erb -->
<div <%= html_attributes(data: controller_data) %>>
<%= content %>
</div>
Testing Strategy
1. Unit Testing Controllers
Create test files for individual controllers:
// test/javascript/controllers/modal_controller_test.js
import { Application } from "@hotwired/stimulus"
import FxModal from "../../../app/assets/javascripts/fluxbit_view_components/modal_controller"
describe("FxModal Controller", () => {
let application
let container
beforeEach(() => {
container = document.createElement("div")
document.body.appendChild(container)
application = Application.start()
application.register("fx-modal", FxModal)
})
afterEach(() => {
document.body.removeChild(container)
})
test("connects successfully", () => {
container.innerHTML = `
<div data-controller="fx-modal" id="test-modal">
Modal content
</div>
`
const controller = application.getControllerForElementAndIdentifier(
container.querySelector('[data-controller="fx-modal"]'),
'fx-modal'
)
expect(controller).toBeDefined()
})
})
2. Integration Testing
Test controllers with ViewComponents:
# test/components/fluxbit/modal_component_test.rb
require "test_helper"
class Fluxbit::ModalComponentTest < ViewComponent::TestCase
test "renders with controller data attributes" do
render_inline(Fluxbit::ModalComponent.new(id: "test-modal")) { "Content" }
assert_selector '[data-controller="fx-modal"]'
assert_selector '[id="test-modal"]'
end
test "includes auto-show configuration" do
render_inline(
Fluxbit::ModalComponent.new(
id: "auto-modal",
auto_show: true
)
) { "Content" }
assert_selector '[data-fx-modal-auto-show-value="true"]'
end
end
3. System Testing
Test full user interactions:
# test/system/modal_interaction_test.rb
require "application_system_test_case"
class ModalInteractionTest < ApplicationSystemTestCase
test "opening and closing modal" do
visit root_path
click_button "Open Modal"
assert_selector "#test-modal", visible: true
click_button "Close"
assert_no_selector "#test-modal", visible: true
end
end
Build and Release Process
1. Version Management
Update version in package.json:
{
"name": "fluxbit-view-components",
"version": "0.2.0",
"description": "ViewComponents for Fluxbit View Components"
}
2. Build Process
# Clean build
rm -rf dist/
yarn build
# Verify build output
ls -la app/assets/javascripts/
3. Release Checklist
- Update version in
package.json - Run tests:
yarn test - Build package:
yarn build - Test integration in demo app
- Preview release:
yarn prerelease - Publish:
yarn release - Update gem version if needed
- Create GitHub release
- Update documentation
4. NPM Publishing
# Build and preview
yarn prerelease
# Publish to NPM
yarn release
# Verify publication
npm view fluxbit-view-components
Debugging
1. Common Issues
Controller Not Loading
- Check Stimulus is properly configured
- Verify controller is registered in
index.js - Check browser console for import errors
- Ensure peer dependencies are installed
Flowbite Integration Issues
// Debug Flowbite loading
console.log('Modal available:', typeof Modal !== 'undefined')
console.log('Drawer available:', typeof Drawer !== 'undefined')
// Force load Flowbite
import('flowbite').then(module => {
console.log('Flowbite loaded:', module)
})
Event Handling Problems
// Debug event listeners
const controller = application.getControllerForElementAndIdentifier(element, 'fx-modal')
console.log('Controller:', controller)
console.log('Element:', controller.element)
2. Development Tools
Browser DevTools
- Use Stimulus Inspector browser extension
- Monitor network requests for Flowbite imports
- Check console for JavaScript errors
- Use Performance tab for optimization
Logging
Add debug logging to controllers:
connect() {
if (this.debugValue) {
console.log(`${this.identifier} connected:`, this.element)
}
}
Testing in Different Browsers
- Test in Chrome, Firefox, Safari
- Check mobile browser compatibility
- Verify touch event handling (for drawers)
Contributing
1. Code Style
Follow existing patterns:
- Use ES6+ syntax
- Follow Stimulus conventions
- Add JSDoc comments for complex methods
- Use consistent error handling
2. Pull Request Process
- Fork the repository
- Create feature branch:
git checkout -b feature/my-controller - Make changes with tests
- Run tests:
yarn test && rake test - Build and test:
yarn prerelease - Submit pull request with description
3. Documentation
- Update controller documentation
- Add usage examples
- Include integration examples
- Update changelog
Performance Considerations
1. Controller Optimization
- Use
connect()for initialization only - Clean up resources in
disconnect() - Debounce frequent operations
- Lazy load Flowbite components
2. Bundle Size
- Keep controllers focused and small
- Avoid unnecessary dependencies
- Use dynamic imports for large libraries
- Monitor bundle size changes
3. Memory Management
disconnect() {
// Clean up event listeners
if (this.timeout) {
clearTimeout(this.timeout)
}
// Remove Flowbite instances
Object.values(this.modals).forEach(modal => {
if (modal.destroy) modal.destroy()
})
// Clear references
this.modals = {}
}