odt-ui-rails 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7cfd3ce3bb87002fd436c806e91c5265f0e6c86ad6526d317e480d2fdc6fa833
4
+ data.tar.gz: f0e8f657c95a809ff669ab4bede5faca1175ceea322478cbd2d139700dcbe475
5
+ SHA512:
6
+ metadata.gz: 2abe1004260233049d5a659e6efe5634167eb92f9704c9d33f74741215db957a55ba95d1c30ca4009389add9ebfb3bfc8e0ac062bbbebac36fe36988095c2cf4
7
+ data.tar.gz: 9beb448b7fe69848789ff861b29c57baa370cdd75dc365858a8c353bfc74457572c09b5450e7048be19be9c08561a593f00e98e74a724d29af4584335dc2a5a2
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # ODT UI Rails Integration (`odt-ui-rails`)
2
+
3
+ Official Ruby on Rails integration for **ODT-Lightweight-UI**. Allows you to use React-based ODT design tokens and components seamlessly inside Rails MVC views (`.html.erb`) with zero boilerplate.
4
+
5
+ ---
6
+
7
+ ## Quick Start
8
+
9
+ ### 1. Add Gem to Rails `Gemfile`
10
+
11
+ ```ruby
12
+ # From local monorepo / subfolder:
13
+ gem "odt-ui-rails", path: "path/to/ODT-Lightweight-UI/integrations/rails"
14
+
15
+ # Or from GitHub:
16
+ # gem "odt-ui-rails", github: "PixxelTK/ODT-Lightweight-UI", glob: "integrations/rails/*.gemspec"
17
+ ```
18
+
19
+ Then run:
20
+
21
+ ```bash
22
+ bundle install
23
+ ```
24
+
25
+ ### 2. Run the Generator
26
+
27
+ ```bash
28
+ bin/rails g odt:install
29
+ ```
30
+
31
+ _This command will automatically install the npm dependencies, generate the Stimulus controller + component registry, and link the ODT stylesheets._
32
+
33
+ ---
34
+
35
+ ## Usage in ERB Views
36
+
37
+ ### Buttons
38
+
39
+ ```erb
40
+ <%= odt_button "บันทึกข้อมูล", variant: "primary", size: "md" %>
41
+ <%= odt_button "ยกเลิก", variant: "ghost", size: "md" %>
42
+ ```
43
+
44
+ ### Form Inputs
45
+
46
+ ```erb
47
+ <%= form_with url: users_path do |f| %>
48
+ <%= odt_input :email, label: "อีเมล", placeholder: "user@odt.com", required: true %>
49
+ <%= odt_password_input :password, label: "รหัสผ่าน" %>
50
+ <%= odt_button "เข้าสู่ระบบ", variant: "primary", type: "submit" %>
51
+ <% end %>
52
+ ```
53
+
54
+ ### Cards & Layouts
55
+
56
+ ```erb
57
+ <%= odt_card title: "ภาพรวมสถิติ" do %>
58
+ <%= odt_stat_card title: "Total Users", value: "12,450", change: "+14.2%" %>
59
+ <% end %>
60
+ ```
61
+
62
+ ### Generic Component Mounting
63
+
64
+ ```erb
65
+ <%= odt_component :Badge, variant: "success", children: "Active" %>
66
+ <%= odt_component :Avatar, src: "https://avatar.iran.liara.run/public", name: "John Doe" %>
67
+ ```
68
+
69
+ ---
70
+
71
+ ## How It Works Under the Hood
72
+
73
+ 1. **Rails Engine**: Automatically injects `OdtUi::Helper` methods into ActionView templates.
74
+ 2. **Stimulus Adapter (`odt_component_controller.js`)**: Safely mounts and unmounts React components in sync with Hotwire/Turbo navigation.
75
+ 3. **Registry (`odt_registry.js`)**: Maps string names to ODT React components. You can register custom app-specific wrappers here easily.
@@ -0,0 +1,76 @@
1
+ require "rails/generators/base"
2
+
3
+ module Odt
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Installs ODT-Lightweight-UI dependencies, Stimulus controllers, and stylesheet imports into Rails"
9
+
10
+ def install_javascript_dependencies
11
+ say "Installing JS dependencies (odt-lightweight-ui, react, react-dom)...", :green
12
+
13
+ packages = "odt-lightweight-ui react react-dom @hotwired/stimulus"
14
+
15
+ if File.exist?("yarn.lock")
16
+ run "yarn add #{packages}"
17
+ elsif File.exist?("pnpm-lock.yaml")
18
+ run "pnpm add #{packages}"
19
+ elsif File.exist?("bun.lockb") || File.exist?("bun.lock")
20
+ run "bun add #{packages}"
21
+ elsif File.exist?("package.json")
22
+ run "npm install #{packages}"
23
+ else
24
+ say "No package.json detected. Please ensure your Rails JS bundler (esbuild, vite, or importmap) is configured.", :yellow
25
+ end
26
+ end
27
+
28
+ def copy_stimulus_controller_and_registry
29
+ say "Copying Stimulus controller and component registry...", :green
30
+ template "odt_registry.js", "app/javascript/odt_registry.js"
31
+ template "odt_component_controller.js", "app/javascript/controllers/odt_component_controller.js"
32
+
33
+ # If stimulus manifest exists (rails stimulus:manifest:update)
34
+ if File.exist?("bin/rails")
35
+ run "bin/rails stimulus:manifest:update", abort_on_failure: false
36
+ end
37
+ end
38
+
39
+ def inject_stylesheets
40
+ say "Configuring ODT stylesheets...", :green
41
+
42
+ css_candidates = [
43
+ "app/assets/stylesheets/application.tailwind.css",
44
+ "app/assets/stylesheets/application.css",
45
+ "app/assets/stylesheets/application.scss",
46
+ "app/javascript/application.css",
47
+ "app/javascript/entrypoints/application.css"
48
+ ]
49
+
50
+ target_css = css_candidates.find { |file| File.exist?(file) }
51
+
52
+ if target_css
53
+ imports = %(\n/* ODT-Lightweight-UI tokens & styles */\n@import "odt-lightweight-ui/tokens.css";\n@import "odt-lightweight-ui/styles.css";\n)
54
+ append_to_file target_css, imports
55
+ say "Added ODT CSS imports to #{target_css}", :green
56
+ else
57
+ say "Could not automatically locate your main CSS file. Please add:\n @import \"odt-lightweight-ui/tokens.css\";\n @import \"odt-lightweight-ui/styles.css\";\nmanually to your application stylesheet.", :yellow
58
+ end
59
+ end
60
+
61
+ def show_finish_message
62
+ say ""
63
+ say "=========================================================", :cyan
64
+ say " 🎉 ODT-Lightweight-UI for Rails successfully installed!", :cyan
65
+ say "=========================================================", :cyan
66
+ say ""
67
+ say "Usage Example in any ERB view (.html.erb):"
68
+ say ' <%= odt_button "Click me", variant: "primary" %>'
69
+ say ' <%= odt_card title: "Profile" do %>'
70
+ say ' <%= odt_input :email, label: "Email" %>'
71
+ say ' <% end %>'
72
+ say ""
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import React from "react";
3
+ import ReactDOM from "react-dom/client";
4
+ import { ODT_COMPONENTS } from "../odt_registry";
5
+
6
+ /**
7
+ * Stimulus Controller to mount and manage React component lifecycles in Rails.
8
+ * Compatible with Hotwire / Turbo navigation.
9
+ */
10
+ export default class extends Controller {
11
+ static values = {
12
+ name: String,
13
+ props: Object,
14
+ };
15
+
16
+ connect() {
17
+ const Component = ODT_COMPONENTS[this.nameValue];
18
+
19
+ if (!Component) {
20
+ console.warn(`[ODT-UI-Rails] Component "${this.nameValue}" not found in ODT_COMPONENTS registry.`);
21
+ return;
22
+ }
23
+
24
+ this.root = ReactDOM.createRoot(this.element);
25
+ this.renderComponent(Component);
26
+ }
27
+
28
+ disconnect() {
29
+ if (this.root) {
30
+ this.root.unmount();
31
+ this.root = null;
32
+ }
33
+ }
34
+
35
+ propsValueChanged() {
36
+ if (this.root) {
37
+ const Component = ODT_COMPONENTS[this.nameValue];
38
+ if (Component) {
39
+ this.renderComponent(Component);
40
+ }
41
+ }
42
+ }
43
+
44
+ renderComponent(Component) {
45
+ const props = { ...(this.propsValue || {}) };
46
+ this.root.render(React.createElement(Component, props));
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ import * as ODT from 'odt-lightweight-ui';
2
+
3
+ /**
4
+ * Registry mapping component names to ODT React components.
5
+ * Add custom components or wrappers here if needed.
6
+ */
7
+ export const ODT_COMPONENTS = {
8
+ Button: ODT.Button,
9
+ Heading: ODT.Heading,
10
+ Text: ODT.Text,
11
+ Badge: ODT.Badge,
12
+ Avatar: ODT.Avatar,
13
+ Card: ODT.Card,
14
+ StatCard: ODT.StatCard,
15
+ ProgressBar: ODT.ProgressBar,
16
+ Input: ODT.Input,
17
+ TextArea: ODT.TextArea,
18
+ PasswordInput: ODT.PasswordInput,
19
+ SearchInput: ODT.SearchInput,
20
+ PinInput: ODT.PinInput,
21
+ Switch: ODT.Switch,
22
+ Checkbox: ODT.Checkbox,
23
+ Radio: ODT.Radio,
24
+ RadioGroup: ODT.RadioGroup,
25
+ Modal: ODT.Modal,
26
+ DropdownMenu: ODT.DropdownMenu,
27
+ Toaster: ODT.Toaster,
28
+ };
@@ -0,0 +1,6 @@
1
+ require "odt_ui/version"
2
+ require "odt_ui/helper"
3
+ require "odt_ui/engine" if defined?(Rails)
4
+
5
+ module OdtUi
6
+ end
@@ -0,0 +1,11 @@
1
+ module OdtUi
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace OdtUi
4
+
5
+ initializer "odt_ui.helper" do
6
+ ActiveSupport.on_load(:action_view) do
7
+ include OdtUi::Helper
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,102 @@
1
+ require "json"
2
+
3
+ module OdtUi
4
+ module Helper
5
+ # Generic component mount helper
6
+ # Example:
7
+ # <%= odt_component :Button, variant: "primary" do %>
8
+ # Submit
9
+ # <% end %>
10
+ def odt_component(name, props = {}, html_options = {}, &block)
11
+ props_hash = props.dup
12
+
13
+ if block_given?
14
+ content = capture(&block)
15
+ props_hash[:children] = content.strip if content.present?
16
+ end
17
+
18
+ # Camelize keys if needed, or pass directly as camelCase
19
+ formatted_props = props_hash.transform_keys { |key| key.to_s.camelize(:lower) }
20
+
21
+ tag.div(
22
+ "",
23
+ class: ["odt-rails-root", html_options[:class]].compact.join(" "),
24
+ data: {
25
+ controller: "odt-component",
26
+ odt_component_name_value: name.to_s,
27
+ odt_component_props_value: formatted_props.to_json
28
+ },
29
+ **html_options.except(:class)
30
+ )
31
+ end
32
+
33
+ # =========================================================================
34
+ # Shorthand Component Helpers
35
+ # =========================================================================
36
+
37
+ def odt_button(label = nil, **props, &block)
38
+ props[:children] = label if label.present?
39
+ odt_component(:Button, props, &block)
40
+ end
41
+
42
+ def odt_heading(text = nil, level: 1, **props, &block)
43
+ props[:level] = level
44
+ props[:children] = text if text.present?
45
+ odt_component(:Heading, props, &block)
46
+ end
47
+
48
+ def odt_text(text = nil, **props, &block)
49
+ props[:children] = text if text.present?
50
+ odt_component(:Text, props, &block)
51
+ end
52
+
53
+ def odt_badge(label = nil, **props, &block)
54
+ props[:children] = label if label.present?
55
+ odt_component(:Badge, props, &block)
56
+ end
57
+
58
+ def odt_avatar(src: nil, name: nil, **props)
59
+ props[:src] = src if src.present?
60
+ props[:name] = name if name.present?
61
+ odt_component(:Avatar, props)
62
+ end
63
+
64
+ def odt_input(name = nil, **props)
65
+ props[:name] = name if name.present?
66
+ odt_component(:Input, props)
67
+ end
68
+
69
+ def odt_password_input(name = nil, **props)
70
+ props[:name] = name if name.present?
71
+ odt_component(:PasswordInput, props)
72
+ end
73
+
74
+ def odt_search_input(name = nil, **props)
75
+ props[:name] = name if name.present?
76
+ odt_component(:SearchInput, props)
77
+ end
78
+
79
+ def odt_card(**props, &block)
80
+ odt_component(:Card, props, &block)
81
+ end
82
+
83
+ def odt_stat_card(**props)
84
+ odt_component(:StatCard, props)
85
+ end
86
+
87
+ def odt_switch(name = nil, **props)
88
+ props[:name] = name if name.present?
89
+ odt_component(:Switch, props)
90
+ end
91
+
92
+ def odt_checkbox(name = nil, **props)
93
+ props[:name] = name if name.present?
94
+ odt_component(:Checkbox, props)
95
+ end
96
+
97
+ def odt_radio_group(name = nil, **props)
98
+ props[:name] = name if name.present?
99
+ odt_component(:RadioGroup, props)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module OdtUi
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: odt-ui-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ODT Team
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.1'
26
+ description: Seamlessly mount and render ODT-Lightweight-UI components in Rails MVC
27
+ views via Hotwire Stimulus.
28
+ email:
29
+ - dev@odt.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/generators/odt/install_generator.rb
36
+ - lib/generators/odt/templates/odt_component_controller.js
37
+ - lib/generators/odt/templates/odt_registry.js
38
+ - lib/odt-ui-rails.rb
39
+ - lib/odt_ui/engine.rb
40
+ - lib/odt_ui/helper.rb
41
+ - lib/odt_ui/version.rb
42
+ homepage: https://github.com/PixxelTK/ODT-Lightweight-UI
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 3.0.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.6.9
61
+ specification_version: 4
62
+ summary: Ruby on Rails integration and helpers for ODT-Lightweight-UI
63
+ test_files: []