hotwire_native_rails 0.1.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cd95ffaf194bb91e5f601aa7dde53a93d3e47bc7e859b69348abc6d86aba97a
4
- data.tar.gz: 7575cd5a82c3499610e41db9ea37d1a58aa8850e5fda8d3f516e31752e6d7357
3
+ metadata.gz: c99841c967e01bca985531db7d5cfcc08b4068542a9377d4785accf30df06647
4
+ data.tar.gz: 0347e31a0697276735e135a3992a7827148a42287e7ebb766e153c4bcbb0f054
5
5
  SHA512:
6
- metadata.gz: 3755dcc7413bf37957901a0a877168bee454b65f29e6942d81d18b0492b6f4ea5fabdd8167591811d0ac2766e0377d6ff249c04d5fb3e867623e205b9eb1b1e6
7
- data.tar.gz: aa9862acb940543a56d8cec09f0461af28bc21038cc15b77569f54bd91350613fe06b7f2a86fe6acc5b5304b4ab1698728e85190da89e420197cea492f17c60e
6
+ metadata.gz: aecb1c68523353ca6ffe80e95427fb155fc57a9d8041c71f4c6713767ea371453a3e907e2cec799e298fa1b80cbc5092f806278bd4f9743e88831386107b9f48
7
+ data.tar.gz: 238a0fc863a5e1310c0451169d0fcf32cba2817aa36c99522012c1893b0a87cd55ba690b7f78b29d21ccb43ea58740faf96b12e148b9e2c414613f0c01c9ab35
data/README.md CHANGED
@@ -1,43 +1,52 @@
1
- # HotwireNativeRails
1
+ # Hotwire Native Rails generator
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hotwire_native_rails`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Power pack to make your Rails app [Hotwire Native](https://native.hotwired.dev)
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
- Install the gem and add to the application's Gemfile by executing:
7
+ Install the gem:
12
8
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
9
+ ```sh
10
+ bundle add hotwire_native_rails
15
11
  ```
16
12
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
13
+ Run the generator:
18
14
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```sh
16
+ rails g hotwire_native
21
17
  ```
22
18
 
23
19
  ## Usage
24
20
 
25
- TODO: Write usage instructions here
26
-
27
- ## Development
21
+ #### Helpers
22
+ - `viewport_meta_tag` - forbid zooming on mobile/native
23
+ - use `data: { turbo_action: replace_if_native }` to submit authentication forms & forms in modals
24
+ - `:mobile` request variant. `index.html+mobile.erb`
25
+ - override link_to to not open internal links in in-app browser on native app
28
26
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
27
+ #### CSS
28
+ - `turbo-native:` css variant (works with CSS and Tailwind)
30
29
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+ #### Bridge Components
31
+ - install Hotwire Native Bridge (works with Importmaps and Node)
32
+ - add default bridge components (`Form`, `Menu`, `Button`)
33
+ - add `Nav` (UIMenu) bridge component
34
+ - `bridge_form_with` - easily apply Bridge `Form` component
32
35
 
33
- ## Contributing
36
+ #### Path Configuration
37
+ - `path_configuration_controller` for `ios` and `android`
34
38
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hotwire_native_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/hotwire_native_rails/blob/master/CODE_OF_CONDUCT.md).
36
-
37
- ## License
39
+ ## Development
38
40
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
41
+ Make a release to rubygems:
40
42
 
41
- ## Code of Conduct
43
+ ```sh
44
+ # 1. update version in version.rb
45
+ # 2. zip the gem
46
+ gem build hotwire_native_rails.gemspec
47
+ # 3. push the zip to rubygems
48
+ gem push hotwire_native_rails-0.2.0.gem
49
+ ```
42
50
 
43
- Everyone interacting in the HotwireNativeRails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/hotwire_native_rails/blob/master/CODE_OF_CONDUCT.md).
51
+ - [Github source](https://github.com/yshmarov/hotwire_native_rails)
52
+ - [Rubygems source](https://rubygems.org/gems/hotwire_native_rails)
@@ -1,17 +1,103 @@
1
1
  class HotwireNativeGenerator < Rails::Generators::Base
2
2
  source_root File.expand_path("templates", __dir__)
3
3
 
4
+ def add_gems
5
+ gem "browser"
6
+ end
7
+
4
8
  def copy_files
9
+ # helpers
5
10
  copy_file "helpers/hotwire_native_helper.rb", "app/helpers/hotwire_native_helper.rb"
6
11
  copy_file "test_unit/hotwire_native_helper_test.rb", "test/helpers/hotwire_native_helper_test.rb"
7
12
 
8
- # copy conent of https://raw.githubusercontent.com/hotwired/hotwire-native-demo/refs/heads/main/public/javascript/controllers/bridge/form_controller.js to app/javascript/controllers/bridge/form_controller.js
9
- # copy_file "bridge/form_controller.js", "app/javascript/controllers/bridge/form_controller.js"
13
+ # routes
14
+ copy_file "routes/hotwire_native.rb", "config/routes/hotwire_native.rb"
15
+ copy_file "controllers/hotwire_native/v1/android/path_configuration_controller.rb", "app/controllers/hotwire_native/v1/android/path_configuration_controller.rb"
16
+ copy_file "controllers/hotwire_native/v1/ios/path_configuration_controller.rb", "app/controllers/hotwire_native/v1/ios/path_configuration_controller.rb"
17
+
18
+ # :native request variant
19
+ copy_file "controllers/concerns/device_format.rb", "app/controllers/concerns/device_format.rb"
20
+ end
21
+
22
+ def add_detect_device_to_application_controller
23
+ inject_into_class "app/controllers/application_controller.rb", ApplicationController, " include DetectDevice\n"
24
+ end
25
+
26
+ def add_routes
27
+ route "draw(:hotwire_native)"
28
+ end
29
+
30
+ # https://native.hotwired.dev/reference/bridge-installation
31
+ def install_javascript
32
+ copy_file "javascript/controllers/bridge/button_controller.js", "app/javascript/controllers/bridge/button_controller.js"
33
+ copy_file "javascript/controllers/bridge/menu_controller.js", "app/javascript/controllers/bridge/menu_controller.js"
34
+ copy_file "javascript/controllers/bridge/form_controller.js", "app/javascript/controllers/bridge/form_controller.js"
35
+ copy_file "javascript/controllers/bridge/overflow_menu_controller.js", "app/javascript/controllers/bridge/overflow_menu_controller.js"
36
+ copy_file "javascript/controllers/bridge/nav_controller.js", "app/javascript/controllers/bridge/nav_controller.js"
37
+
38
+ run "bin/importmap pin @hotwired/stimulus @hotwired/hotwire-native-bridge" if importmaps?
39
+ run "yarn add @hotwired/stimulus @hotwired/hotwire-native-bridge" if node?
40
+ end
41
+
42
+ def add_viewport_meta_tag
43
+ gsub_file "app/views/layouts/application.html.erb", "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">", "<%= viewport_meta_tag %>"
44
+ end
45
+
46
+ def add_css_variants
47
+ return add_tailwind_css_variants if tailwind?
48
+
49
+ add_turbo_native_css
50
+ end
51
+
52
+ def add_platform_identifier
53
+ gsub_file "app/views/layouts/application.html.erb", "<html>", "<html <%= platform_identifier %>>"
54
+ end
55
+
56
+ def set_page_title
57
+ gsub_file "app/views/layouts/application.html.erb", /<title>.*<\/title>/, "<title><%= page_title %></title>"
58
+ end
59
+
60
+ private
61
+
62
+ def importmaps?
63
+ Rails.root.join("config/importmap.rb").exist?
64
+ end
65
+
66
+ def node?
67
+ Rails.root.join("package.json").exist?
68
+ end
69
+
70
+ def tailwind?
71
+ Rails.root.join("config/tailwind.config.js").exist?
72
+ end
73
+
74
+ # class="turbo-native:hidden"
75
+ # class="non-turbo-native:hidden"
76
+ def add_tailwind_css_variants
77
+ prepend_to_file "config/tailwind.config.js", "const plugin = require('tailwindcss/plugin')\n"
78
+
79
+ inject_into_file "config/tailwind.config.js", after: "plugins: [" do
80
+ <<-JS
81
+
82
+ plugin(function({ addVariant }) {
83
+ addVariant("turbo-native", "html[data-turbo-native] &"),
84
+ addVariant("non-turbo-native", "html:not([data-turbo-native]) &")
85
+ }),
86
+ JS
87
+ end
88
+ end
10
89
 
11
- # path_configuration file
90
+ # class="turbo-native:hidden"
91
+ def add_turbo_native_css
92
+ gsub_file "app/views/layouts/application.html.erb", "<body>", "<body class=\"<%= \"turbo-native\" if turbo_native_app? %>\">"
12
93
 
13
- # tailwind
94
+ append_to_file "app/assets/stylesheets/application.css" do
95
+ <<-CSS
14
96
 
15
- # devise variant
97
+ body.turbo-native .turbo-native:hidden {
98
+ display: none;
99
+ }
100
+ CSS
101
+ end
16
102
  end
17
103
  end
@@ -0,0 +1,13 @@
1
+ module DeviceFormat
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :set_variant
6
+ end
7
+
8
+ private
9
+
10
+ def set_variant
11
+ return request.variant = :mobile if turbo_native_app? || browser.device.mobile?
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class HotwireNative::V1::Android::PathConfigurationsController < ActionController::Base
2
+ def show
3
+ render json: {}
4
+ end
5
+ end
@@ -0,0 +1,49 @@
1
+ class HotwireNative::V1::Ios::PathConfigurationsController < ActionController::Base
2
+ def show
3
+ render json:
4
+ {
5
+ "settings": {
6
+ "enable_feature_x": true
7
+ },
8
+ "rules": [
9
+ {
10
+ "patterns": [
11
+ "/new$",
12
+ "/edit$",
13
+ "/signin$",
14
+ "/strada-form$"
15
+ ],
16
+ "properties": {
17
+ "context": "modal",
18
+ "pull_to_refresh_enabled": false
19
+ }
20
+ },
21
+ {
22
+ "patterns": [
23
+ "/numbers$"
24
+ ],
25
+ "properties": {
26
+ "view_controller": "numbers"
27
+ }
28
+ },
29
+ {
30
+ "patterns": [
31
+ "/numbers/[0-9]+$"
32
+ ],
33
+ "properties": {
34
+ "view_controller": "numbers_detail",
35
+ "context": "modal"
36
+ }
37
+ },
38
+ {
39
+ "patterns": [
40
+ "^/$"
41
+ ],
42
+ "properties": {
43
+ "presentation": "replace_root"
44
+ }
45
+ },
46
+ ]
47
+ }
48
+ end
49
+ end
@@ -1,27 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HotwireNativeHelper
4
+ # before
5
+ # <title><%= content_for(:title) || "My App" %></title>
6
+ # after
7
+ # <title><%= page_title %></title>
8
+ # usage
9
+ # <% content_for :turbo_native_title, "Sign in" %>
10
+ # <% content_for :title, "Sign in | My App" %>
11
+ def page_title
12
+ if turbo_native_app?
13
+ content_for(:turbo_native_title) || content_for(:title) || Rails.application.class.module_parent.name
14
+ else
15
+ content_for(:title) || Rails.application.class.module_parent.name
16
+ end
17
+ end
18
+
19
+ # forbid zooming on mobile devices
4
20
  def viewport_meta_tag
5
21
  content = ['width=device-width,initial-scale=1']
6
22
  content << 'maximum-scale=1, user-scalable=0' if turbo_native_app? || browser.device.mobile?
7
23
  tag.meta(name: 'viewport', content: content.join(','))
8
24
  end
9
25
 
26
+ # set on <html> tag
10
27
  def platform_identifier
11
28
  'data-turbo-native' if turbo_native_app?
12
29
  end
13
30
 
31
+ # link_to 'Next', next_path, data: { turbo_action: replace_if_native }
32
+ # https://turbo.hotwired.dev/handbook/drive#application-visits
14
33
  def replace_if_native
15
34
  return 'replace' if turbo_native_app?
16
35
 
17
36
  'advance'
18
37
  end
19
38
 
39
+ # override link_to to not open internal links in in-app browser on native app
20
40
  def link_to(name = nil, options = nil, html_options = {}, &block)
21
41
  html_options[:target] = '' if turbo_native_app? && internal_url?(url_for(options))
22
42
  super(name, options, html_options, &block)
23
43
  end
24
44
 
45
+ # https://github.com/joemasilotti/daily-log/blob/main/rails/app/helpers/form_helper.rb
25
46
  class BridgeFormBuilder < ActionView::Helpers::FormBuilder
26
47
  def submit(value = nil, options = {})
27
48
  options[:data] ||= {}
@@ -0,0 +1,20 @@
1
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
2
+ // Source:
3
+ // https://native.hotwired.dev/ios/bridge-components
4
+ // Docs:
5
+ // https://blog.corsego.com/hotwire-native-bridge-button
6
+ export default class extends BridgeComponent {
7
+ static component = "button"
8
+
9
+ connect() {
10
+ super.connect()
11
+
12
+ const element = this.bridgeElement
13
+ const title = element.bridgeAttribute("title")
14
+ const image = element.bridgeAttribute("ios-image")
15
+ const side = element.bridgeAttribute("side") || "right"
16
+ this.send("connect", {title, image, side}, () => {
17
+ this.element.click()
18
+ })
19
+ }
20
+ }
@@ -0,0 +1,34 @@
1
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
2
+ import { BridgeElement } from "@hotwired/hotwire-native-bridge"
3
+ // Source:
4
+ // https://github.com/hotwired/hotwire-native-demo/blob/main/public/javascript/controllers/bridge/form_controller.js
5
+ // Docs:
6
+ // https://blog.corsego.com/hotwire-native-form-component
7
+ export default class extends BridgeComponent {
8
+ static component = "form"
9
+ static targets = [ "submit" ]
10
+
11
+ connect() {
12
+ super.connect()
13
+ this.notifyBridgeOfConnect()
14
+ }
15
+
16
+ notifyBridgeOfConnect() {
17
+ const submitButton = new BridgeElement(this.submitTarget)
18
+ const submitTitle = submitButton.title
19
+
20
+ this.send("connect", { submitTitle }, () => {
21
+ this.submitTarget.click()
22
+ })
23
+ }
24
+
25
+ submitStart(event) {
26
+ this.submitTarget.disabled = true
27
+ this.send("submitDisabled")
28
+ }
29
+
30
+ submitEnd(event) {
31
+ this.submitTarget.disabled = false
32
+ this.send("submitEnabled")
33
+ }
34
+ }
@@ -0,0 +1,47 @@
1
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
2
+ import { BridgeElement } from "@hotwired/hotwire-native-bridge"
3
+ // Source:
4
+ // https://github.com/hotwired/hotwire-native-demo/blob/main/public/javascript/controllers/bridge/menu_controller.js
5
+ // Docs:
6
+ // https://blog.corsego.com/hotwire-native-bridge-menu-component
7
+ export default class extends BridgeComponent {
8
+ static component = "menu"
9
+ static targets = [ "title", "item" ]
10
+
11
+ show(event) {
12
+ if (this.enabled) {
13
+ event.stopImmediatePropagation()
14
+ this.notifyBridgeToDisplayMenu(event)
15
+ }
16
+ }
17
+
18
+ notifyBridgeToDisplayMenu(event) {
19
+ const title = new BridgeElement(this.titleTarget).title
20
+ const items = this.makeMenuItems(this.itemTargets)
21
+
22
+ this.send("display", { title, items }, message => {
23
+ const selectedIndex = message.data.selectedIndex
24
+ const selectedItem = new BridgeElement(this.itemTargets[selectedIndex])
25
+
26
+ selectedItem.click()
27
+ })
28
+ }
29
+
30
+ makeMenuItems(elements) {
31
+ const items = elements.map((element, index) => this.menuItem(element, index))
32
+ const enabledItems = items.filter(item => item)
33
+
34
+ return enabledItems
35
+ }
36
+
37
+ menuItem(element, index) {
38
+ const bridgeElement = new BridgeElement(element)
39
+
40
+ if (bridgeElement.disabled) return null
41
+
42
+ return {
43
+ title: bridgeElement.title,
44
+ index: index
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,36 @@
1
+ import { BridgeComponent, BridgeElement } from "@hotwired/hotwire-native-bridge"
2
+
3
+ // Docs:
4
+ // https://blog.corsego.com/hotwire-native-ui-menu-dropdown
5
+ export default class extends BridgeComponent {
6
+ static component = "nav"
7
+ static targets = ["item"]
8
+
9
+ connect() {
10
+ super.connect()
11
+
12
+ const items = this.itemTargets.map((item, index) => {
13
+ const itemElement = new BridgeElement(item)
14
+
15
+ return {
16
+ title: itemElement.title,
17
+ image: itemElement.bridgeAttribute("image") ?? "none",
18
+ destructive: item.dataset.turboMethod === "delete",
19
+ state: itemElement.bridgeAttribute("state") ?? "off",
20
+ index
21
+ }
22
+ })
23
+
24
+ const element = this.bridgeElement
25
+ const title = element.bridgeAttribute("title") ?? ""
26
+ const side = element.bridgeAttribute("side") || "left"
27
+ const image = element.bridgeAttribute("image") || "none"
28
+
29
+ this.send("connect", { items, title, image, side }, (message) => {
30
+ const selectedIndex = message.data.selectedIndex
31
+ const selectedItem = new BridgeElement(this.itemTargets[selectedIndex]);
32
+
33
+ selectedItem.click()
34
+ })
35
+ }
36
+ }
@@ -0,0 +1,22 @@
1
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
2
+ // Source:
3
+ // https://github.com/hotwired/hotwire-native-demo/blob/main/public/javascript/controllers/bridge/overflow_menu_controller.js
4
+ // Docs:
5
+ // https://blog.corsego.com/hotwire-native-bridge-menu-component
6
+
7
+ export default class extends BridgeComponent {
8
+ static component = "overflow-menu"
9
+
10
+ connect() {
11
+ super.connect()
12
+ this.notifyBridgeOfConnect()
13
+ }
14
+
15
+ notifyBridgeOfConnect() {
16
+ const label = this.bridgeElement.title
17
+
18
+ this.send("connect", { label }, () => {
19
+ this.bridgeElement.click()
20
+ })
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
2
+ // Docs:
3
+ // https://blog.corsego.com/hotwire-native-leave-a-review-bridge-component
4
+ export default class extends BridgeComponent {
5
+ static component = "review-prompt"
6
+
7
+ connect() {
8
+ super.connect()
9
+ this.send("connect")
10
+ }
11
+ }
@@ -0,0 +1,10 @@
1
+ namespace :hotwire_native do
2
+ namespace :v1 do
3
+ namespace :android do
4
+ resource :path_configuration, only: :show
5
+ end
6
+ namespace :ios do
7
+ resource :path_configuration, only: :show
8
+ end
9
+ end
10
+ end
@@ -1,6 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class HotwireNativeHelperTest < ActionView::TestCase
4
+ # add more tests
4
5
  setup do
5
6
  def root_url
6
7
  "http://test.host/"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HotwireNativeRails
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotwire_native_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaro Shm
@@ -28,7 +28,17 @@ files:
28
28
  - Rakefile
29
29
  - hotwire_native_rails.gemspec
30
30
  - lib/generators/hotwire_native/hotwire_native_generator.rb
31
+ - lib/generators/hotwire_native/templates/controllers/concerns/device_format.rb
32
+ - lib/generators/hotwire_native/templates/controllers/hotwire_native/v1/android/path_configuration_controller.rb
33
+ - lib/generators/hotwire_native/templates/controllers/hotwire_native/v1/ios/path_configuration_controller.rb
31
34
  - lib/generators/hotwire_native/templates/helpers/hotwire_native_helper.rb
35
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/button_controller.js
36
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/form_controller.js
37
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/menu_controller.js
38
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/nav_controller.js
39
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/overflow_menu_controller.js
40
+ - lib/generators/hotwire_native/templates/javascript/controllers/bridge/review_prompt_controller.js
41
+ - lib/generators/hotwire_native/templates/routes/hotwire_native.rb
32
42
  - lib/generators/hotwire_native/templates/test_unit/hotwire_native_helper_test.rb
33
43
  - lib/hotwire_native_rails.rb
34
44
  - lib/hotwire_native_rails/version.rb