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 +7 -0
- data/README.md +75 -0
- data/lib/generators/odt/install_generator.rb +76 -0
- data/lib/generators/odt/templates/odt_component_controller.js +48 -0
- data/lib/generators/odt/templates/odt_registry.js +28 -0
- data/lib/odt-ui-rails.rb +6 -0
- data/lib/odt_ui/engine.rb +11 -0
- data/lib/odt_ui/helper.rb +102 -0
- data/lib/odt_ui/version.rb +3 -0
- metadata +63 -0
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
|
+
};
|
data/lib/odt-ui-rails.rb
ADDED
|
@@ -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
|
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: []
|